MoneyWiseSecurityHoldingMap.java
/*
* MoneyWise: Finance Application
* Copyright 2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.moneywise.data.basic;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataMap;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataObjectFormat;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity.MoneyWiseSecurityList;
import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHoldingMap.MoneyWisePortfolioHoldingsMap;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* SecurityHolding Map.
*/
public class MoneyWiseSecurityHoldingMap
implements MetisDataObjectFormat, MetisDataMap<Integer, MoneyWisePortfolioHoldingsMap>, MoneyWiseSecurityHoldingMapCtl {
/**
* Underlying portfolio list.
*/
private final MoneyWisePortfolioList thePortfolios;
/**
* Underlying security list.
*/
private final MoneyWiseSecurityList theSecurities;
/**
* The underlying map.
*/
private final Map<Integer, MoneyWisePortfolioHoldingsMap> theMap;
/**
* Constructor.
*
* @param pData the dataSet
*/
public MoneyWiseSecurityHoldingMap(final PrometheusDataSet pData) {
/* Access lists */
thePortfolios = pData.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
theSecurities = pData.getDataList(MoneyWiseBasicDataType.SECURITY, MoneyWiseSecurityList.class);
theMap = new HashMap<>();
}
/**
* Constructor.
*
* @param pEditSet the editSet
*/
public MoneyWiseSecurityHoldingMap(final PrometheusEditSet pEditSet) {
/* Access lists */
thePortfolios = pEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
theSecurities = pEditSet.getDataList(MoneyWiseBasicDataType.SECURITY, MoneyWiseSecurityList.class);
theMap = new HashMap<>();
}
@Override
public Map<Integer, MoneyWisePortfolioHoldingsMap> getUnderlyingMap() {
return theMap;
}
@Override
public String formatObject(final OceanusDataFormatter pFormatter) {
return MoneyWiseSecurityHoldingMap.class.getSimpleName();
}
@Override
public void clear() {
theMap.clear();
}
/**
* Look up a security holding.
*
* @param pId the id of the security holding
* @return the security holding
*/
public MoneyWiseSecurityHolding findHoldingById(final Long pId) {
/* Access the component IDs */
final Integer myPortId = getPortfolioId(pId);
final Integer mySecId = getSecurityId(pId);
/* Look up security map for portfolio */
final MoneyWisePortfolioHoldingsMap myMap = getMapForPortfolio(myPortId);
return myMap == null
? null
: myMap.getHoldingForSecurity(mySecId);
}
/**
* Look up a security holding.
*
* @param pName the name of the security holding
* @return the security holding
*/
public MoneyWiseSecurityHolding findHoldingByName(final String pName) {
/* Access the component Names */
final String myPortName = getPortfolioName(pName);
final String mySecName = getSecurityName(pName);
/* Look up security map for portfolio */
final MoneyWisePortfolioHoldingsMap myMap = getMapForPortfolio(myPortName);
return myMap == null
? null
: myMap.getHoldingForSecurity(mySecName);
}
/**
* Obtain portfolio name from composite name.
*
* @param pName the composite name
* @return the portfolio name
*/
private static String getPortfolioName(final String pName) {
final int iIndex = pName.indexOf(MoneyWiseSecurityHolding.SECURITYHOLDING_SEP);
return iIndex == -1
? pName
: pName.substring(0, iIndex);
}
/**
* Obtain security name from composite name.
*
* @param pName the composite name
* @return the security name
*/
private static String getSecurityName(final String pName) {
final int iIndex = pName.indexOf(MoneyWiseSecurityHolding.SECURITYHOLDING_SEP);
return iIndex == -1
? ""
: pName.substring(iIndex + 1);
}
/**
* Obtain portfolio id from composite id.
*
* @param pId the composite id
* @return the portfolio id
*/
private static Integer getPortfolioId(final Long pId) {
return MoneyWiseAssetType.getMajorId(pId);
}
/**
* Obtain security id from composite id.
*
* @param pId the composite id
* @return the security id
*/
private static Integer getSecurityId(final Long pId) {
return MoneyWiseAssetType.getBaseId(pId);
}
/**
* Declare holding.
*
* @param pPortfolio the portfolio
* @param pSecurity the security
* @return the holding
*/
public MoneyWiseSecurityHolding declareHolding(final MoneyWisePortfolio pPortfolio,
final MoneyWiseSecurity pSecurity) {
/* Access the portfolio map */
final MoneyWisePortfolioHoldingsMap myPortMap = getMapForPortfolio(pPortfolio.getIndexedId());
if (myPortMap == null) {
throw new IllegalStateException("Invalid Portfolio");
}
final Map<Integer, MoneyWiseSecurityHolding> myMap = myPortMap.getUnderlyingMap();
/* Look up existing holding */
final Integer myId = pSecurity.getIndexedId();
return myMap.computeIfAbsent(myId, i -> new MoneyWiseSecurityHolding(pPortfolio, pSecurity));
}
/**
* Obtain or allocate the map for the portfolio.
*
* @param pId the id of the portfolio
* @return the map
*/
private MoneyWisePortfolioHoldingsMap getMapForPortfolio(final Integer pId) {
/* Look up in the map */
MoneyWisePortfolioHoldingsMap myMap = theMap.get(pId);
/* If the Id is not found */
if (myMap == null) {
/* Look up the portfolio as a double check */
final MoneyWisePortfolio myPortfolio = thePortfolios.findItemById(pId);
/* Reject if no such portfolio */
if (myPortfolio == null
|| myPortfolio.isDeleted()) {
return null;
}
/* Allocate and store the map */
myMap = new MoneyWisePortfolioHoldingsMap(myPortfolio, theSecurities);
theMap.put(pId, myMap);
}
/* Return the map */
return myMap;
}
/**
* Obtain or allocate the map for the portfolio.
*
* @param pName the name of the portfolio
* @return the map
*/
private MoneyWisePortfolioHoldingsMap getMapForPortfolio(final String pName) {
/* Look up the portfolio */
final MoneyWisePortfolio myPortfolio = thePortfolios.findItemByName(pName);
/* Reject if no such portfolio */
if (myPortfolio == null) {
return null;
}
/* Look up in the map */
final Integer myId = myPortfolio.getIndexedId();
return theMap.computeIfAbsent(myId, i -> new MoneyWisePortfolioHoldingsMap(myPortfolio, theSecurities));
}
@Override
public void deRegister(final MoneyWiseAssetBase pAsset) {
/* Ensure that we do not reference this asset */
switch (pAsset) {
case MoneyWisePortfolio myPortfolio -> deRegister(myPortfolio);
case MoneyWiseSecurity mySecurity -> deRegister(mySecurity);
default -> {
/* NoOp */
}
}
}
/**
* DeRegister Portfolio.
*
* @param pPortfolio the portfolio
*/
private void deRegister(final MoneyWisePortfolio pPortfolio) {
/* Ensure that we do not reference this portfolio */
final Integer myId = pPortfolio.getIndexedId();
theMap.remove(myId);
}
/**
* DeRegister Security.
*
* @param pSecurity the security
*/
private void deRegister(final MoneyWiseSecurity pSecurity) {
/* Access the id */
final Integer myId = pSecurity.getIndexedId();
/* Iterate through the portfolio maps */
final Iterator<MoneyWisePortfolioHoldingsMap> myIterator = theMap.values().iterator();
while (myIterator.hasNext()) {
final MoneyWisePortfolioHoldingsMap myMap = myIterator.next();
/* Ensure that we do not reference this security */
myMap.getUnderlyingMap().remove(myId);
}
}
/**
* Obtain existing holding list for Portfolio.
*
* @param pPortfolio the portfolio
* @return the iterator
*/
public Iterator<MoneyWiseSecurityHolding> existingIterator(final MoneyWisePortfolio pPortfolio) {
/* Look up in the map */
final MoneyWisePortfolioHoldingsMap myMap = theMap.get(pPortfolio.getIndexedId());
/* return the iterator */
return myMap == null
? null
: myMap.existingIterator();
}
/**
* Obtain new holding list for Portfolio.
*
* @param pPortfolio the portfolio
* @return the iterator
*/
public Iterator<MoneyWiseSecurityHolding> newIterator(final MoneyWisePortfolio pPortfolio) {
return newIterator(pPortfolio, null);
}
/**
* Obtain new holding list for Portfolio.
*
* @param pPortfolio the portfolio
* @param pClass the class of holdings or null for all
* @return the iterator
*/
public Iterator<MoneyWiseSecurityHolding> newIterator(final MoneyWisePortfolio pPortfolio,
final MoneyWiseSecurityClass pClass) {
/* Look up in the map */
final MoneyWisePortfolioHoldingsMap myMap = theMap.get(pPortfolio.getIndexedId());
/* return the iterator */
return myMap == null
? fullIterator(pPortfolio, pClass)
: myMap.newIterator(pClass);
}
/**
* Obtain new holding list for Portfolio.
*
* @param pPortfolio the portfolio
* @param pClass the class of holdings or null for all
* @return the iterator
*/
private Iterator<MoneyWiseSecurityHolding> fullIterator(final MoneyWisePortfolio pPortfolio,
final MoneyWiseSecurityClass pClass) {
/* If the portfolio is closed/deleted */
if (Boolean.TRUE.equals(pPortfolio.isClosed()) || pPortfolio.isDeleted()) {
/* No iterator */
return null;
}
/* Create an empty list */
List<MoneyWiseSecurityHolding> myList = new ArrayList<>();
/* Loop through the securities */
final Iterator<MoneyWiseSecurity> myIterator = theSecurities.iterator();
while (myIterator.hasNext()) {
final MoneyWiseSecurity mySecurity = myIterator.next();
/* Ignore closed/deleted and wrong class */
boolean bIgnore = mySecurity.isClosed() || mySecurity.isDeleted();
bIgnore |= pClass != null && !pClass.equals(mySecurity.getCategoryClass());
if (bIgnore) {
continue;
}
/* Create a new holding and add to the list */
final MoneyWiseSecurityHolding myHolding = new MoneyWiseSecurityHolding(pPortfolio, mySecurity);
myList.add(myHolding);
}
/* Sort list or delete if empty */
if (myList.isEmpty()) {
myList = null;
} else {
Collections.sort(myList);
}
/* return iterator */
return myList == null
? null
: myList.iterator();
}
/**
* PortfolioHoldings Map.
*/
public static final class MoneyWisePortfolioHoldingsMap
implements MetisDataObjectFormat, MetisDataMap<Integer, MoneyWiseSecurityHolding> {
/**
* Portfolio.
*/
private final MoneyWisePortfolio thePortfolio;
/**
* Underlying security list.
*/
private final MoneyWiseSecurityList theSecurities;
/**
* The underlying map.
*/
private final Map<Integer, MoneyWiseSecurityHolding> theMap;
/**
* Constructor.
*
* @param pPortfolio the portfolio
* @param pSecurities the security list
*/
private MoneyWisePortfolioHoldingsMap(final MoneyWisePortfolio pPortfolio,
final MoneyWiseSecurityList pSecurities) {
thePortfolio = pPortfolio;
theSecurities = pSecurities;
theMap = new HashMap<>();
}
@Override
public Map<Integer, MoneyWiseSecurityHolding> getUnderlyingMap() {
return theMap;
}
@Override
public String formatObject(final OceanusDataFormatter pFormatter) {
return thePortfolio.formatObject(pFormatter);
}
@Override
public String toString() {
return thePortfolio.toString();
}
/**
* Obtain or allocate the holding for the security.
*
* @param pId the id of the security
* @return the holding
*/
private MoneyWiseSecurityHolding getHoldingForSecurity(final Integer pId) {
/* Look up in the map */
MoneyWiseSecurityHolding myHolding = theMap.get(pId);
/* If the Id is not found */
if (myHolding == null) {
/* Look up the security as a double check */
final MoneyWiseSecurity mySecurity = theSecurities.findItemById(pId);
/* Reject if no such security */
if (mySecurity == null || mySecurity.isDeleted()) {
return null;
}
/* Allocate and store the holding */
myHolding = new MoneyWiseSecurityHolding(thePortfolio, mySecurity);
theMap.put(pId, myHolding);
}
/* Return the holding */
return myHolding;
}
/**
* Obtain or allocate the holding for the security.
*
* @param pName the name of the security
* @return the holding
*/
private MoneyWiseSecurityHolding getHoldingForSecurity(final String pName) {
/* Look up the security */
final MoneyWiseSecurity mySecurity = theSecurities.findItemByName(pName);
/* Reject if no such security */
if (mySecurity == null) {
return null;
}
/* Look up in the map */
final Integer myId = mySecurity.getIndexedId();
return theMap.computeIfAbsent(myId, i -> new MoneyWiseSecurityHolding(thePortfolio, mySecurity));
}
/**
* Obtain existing holding list for Portfolio.
*
* @return the iterator
*/
private Iterator<MoneyWiseSecurityHolding> existingIterator() {
/* If the portfolio is closed/deleted */
if (Boolean.TRUE.equals(thePortfolio.isClosed()) || thePortfolio.isDeleted()) {
/* No iterator */
return null;
}
/* Create an empty list */
List<MoneyWiseSecurityHolding> myList = new ArrayList<>();
/* Loop through the holdings */
final Iterator<MoneyWiseSecurityHolding> myIterator = theMap.values().iterator();
while (myIterator.hasNext()) {
final MoneyWiseSecurityHolding myHolding = myIterator.next();
final MoneyWiseSecurity mySecurity = myHolding.getSecurity();
/* Ignore closed/deleted */
if (Boolean.FALSE.equals(mySecurity.isClosed()) && !mySecurity.isDeleted()) {
/* Add to the list */
myList.add(myHolding);
}
}
/* Sort list or delete if empty */
if (myList.isEmpty()) {
myList = null;
} else {
Collections.sort(myList);
}
/* return iterator */
return myList == null
? null
: myList.iterator();
}
/**
* Obtain new holding list for Portfolio.
*
* @param pClass the class of holdings or null for all
* @return the iterator
*/
private Iterator<MoneyWiseSecurityHolding> newIterator(final MoneyWiseSecurityClass pClass) {
/* If the portfolio is closed/deleted */
if (Boolean.TRUE.equals(thePortfolio.isClosed()) || thePortfolio.isDeleted()) {
/* No iterator */
return null;
}
/* Create an empty list */
List<MoneyWiseSecurityHolding> myList = new ArrayList<>();
/* Loop through the securities */
final Iterator<MoneyWiseSecurity> myIterator = theSecurities.iterator();
while (myIterator.hasNext()) {
final MoneyWiseSecurity mySecurity = myIterator.next();
/* Ignore closed/deleted and wrong class/currency */
boolean bIgnore = mySecurity.isClosed() || mySecurity.isDeleted();
bIgnore |= pClass != null && pClass.equals(mySecurity.getCategoryClass());
if (bIgnore) {
continue;
}
/* Only add if not currently present */
if (theMap.get(mySecurity.getIndexedId()) == null) {
/* Create a new holding and add to the list */
final MoneyWiseSecurityHolding myHolding = new MoneyWiseSecurityHolding(thePortfolio, mySecurity);
myList.add(myHolding);
}
}
/* Sort list or delete if empty */
if (myList.isEmpty()) {
myList = null;
} else {
Collections.sort(myList);
}
/* return iterator */
return myList == null
? null
: myList.iterator();
}
}
}