MoneyWiseSecurityHolding.java
/*
* MoneyWise: Finance Application
* Copyright 2012-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.oceanus.format.OceanusDataFormatter;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataMap;
import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataObjectFormat;
import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
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.statics.MoneyWiseCurrency;
import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Currency;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Portfolio/Security combination.
*/
public final class MoneyWiseSecurityHolding
implements MetisFieldItem, MoneyWiseTransAsset, Comparable<Object> {
/**
* Name separator.
*/
public static final String SECURITYHOLDING_SEP = ":";
/**
* New Security Holding text.
*/
public static final String SECURITYHOLDING_NEW = MoneyWiseBasicResource.SECURITYHOLDING_NEW.getValue();
/**
* Report fields.
*/
private static final MetisFieldSet<MoneyWiseSecurityHolding> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseSecurityHolding.class);
/*
* UnderlyingMap Field Id.
*/
static {
FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_ID, MoneyWiseSecurityHolding::getExternalId);
FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAITEM_FIELD_NAME, MoneyWiseSecurityHolding::getName);
FIELD_DEFS.declareLocalField(MoneyWiseBasicDataType.PORTFOLIO, MoneyWiseSecurityHolding::getPortfolio);
FIELD_DEFS.declareLocalField(MoneyWiseBasicDataType.SECURITY, MoneyWiseSecurityHolding::getSecurity);
}
/**
* The id of the holding.
*/
private final Long theId;
/**
* The portfolio of the holding.
*/
private MoneyWisePortfolio thePortfolio;
/**
* The security of the holding.
*/
private MoneyWiseSecurity theSecurity;
/**
* Constructor.
*
* @param pPortfolio the portfolio
* @param pSecurity the security
*/
private MoneyWiseSecurityHolding(final MoneyWisePortfolio pPortfolio,
final MoneyWiseSecurity pSecurity) {
/* Set portfolio and security */
thePortfolio = pPortfolio;
theSecurity = pSecurity;
/* Generate the id */
theId = MoneyWiseAssetType.createExternalId(MoneyWiseAssetType.SECURITYHOLDING, thePortfolio.getIndexedId(), theSecurity.getIndexedId());
}
@Override
public MetisFieldSet<MoneyWiseSecurityHolding> getDataFieldSet() {
return FIELD_DEFS;
}
@Override
public String formatObject(final OceanusDataFormatter pFormatter) {
return toString();
}
@Override
public String toString() {
return getName();
}
@Override
public Boolean isClosed() {
/* Access constituent parts */
final MoneyWisePortfolio myPortfolio = getPortfolio();
final MoneyWiseSecurity mySecurity = getSecurity();
/* Test underlying items */
return myPortfolio.isClosed()
|| mySecurity.isClosed();
}
/**
* Is the holding deleted?
*
* @return true/false
*/
public boolean isDeleted() {
/* Access constituent parts */
final MoneyWisePortfolio myPortfolio = getPortfolio();
final MoneyWiseSecurity mySecurity = getSecurity();
/* Test underlying items */
return myPortfolio.isDeleted()
|| mySecurity.isDeleted();
}
@Override
public Long getExternalId() {
return theId;
}
@Override
public String getName() {
return generateName();
}
@Override
public MoneyWisePayee getParent() {
return theSecurity.getParent();
}
@Override
public boolean isTaxFree() {
return thePortfolio.isTaxFree();
}
@Override
public boolean isGross() {
return thePortfolio.isGross();
}
@Override
public boolean isShares() {
return theSecurity.isShares();
}
@Override
public boolean isCapital() {
return theSecurity.isCapital();
}
@Override
public boolean isAutoExpense() {
return false;
}
@Override
public boolean isHidden() {
return false;
}
/**
* Obtain Portfolio.
*
* @return the portfolio
*/
public MoneyWisePortfolio getPortfolio() {
return thePortfolio;
}
/**
* Obtain Security.
*
* @return the security
*/
public MoneyWiseSecurity getSecurity() {
return theSecurity;
}
@Override
public MoneyWiseAssetType getAssetType() {
return MoneyWiseAssetType.SECURITYHOLDING;
}
@Override
public MoneyWiseCurrency getAssetCurrency() {
return theSecurity.getAssetCurrency();
}
@Override
public Currency getCurrency() {
return getAssetCurrency().getCurrency();
}
@Override
public boolean isForeign() {
final MoneyWiseCurrency myDefault = thePortfolio.getDataSet().getReportingCurrency();
return !myDefault.equals(getAssetCurrency());
}
/**
* Generate the name.
*
* @return the name.
*/
private String generateName() {
/* Build and return the name */
final StringBuilder myBuilder = new StringBuilder();
myBuilder.append(thePortfolio.getName());
myBuilder.append(SECURITYHOLDING_SEP);
myBuilder.append(theSecurity.getName());
return myBuilder.toString();
}
/**
* Obtain the security class.
*
* @return the security class.
*/
private MoneyWiseSecurityClass getSecurityTypeClass() {
/* Check for match */
return theSecurity.getCategoryClass();
}
/**
* Is this holding the required security class.
*
* @param pClass the required security class.
* @return true/false
*/
public boolean isSecurityClass(final MoneyWiseSecurityClass pClass) {
/* Check for match */
return getSecurityTypeClass() == pClass;
}
@Override
public void touchItem(final PrometheusDataItem pSource) {
/* Touch references */
thePortfolio.touchItem(pSource);
theSecurity.touchItem(pSource);
}
@Override
public boolean equals(final Object pThat) {
/* Handle the trivial cases */
if (this == pThat) {
return true;
}
if (pThat == null) {
return false;
}
if (!(pThat instanceof MoneyWiseSecurityHolding)) {
return false;
}
/* Compare the Portfolios */
final MoneyWiseSecurityHolding myThat = (MoneyWiseSecurityHolding) pThat;
if (!getPortfolio().equals(myThat.getPortfolio())) {
return false;
}
/* Compare securities */
return getSecurity().equals(myThat.getSecurity());
}
@Override
public int hashCode() {
final int myHash = MetisFieldSet.HASH_PRIME * getPortfolio().hashCode();
return myHash + getSecurity().hashCode();
}
@Override
public int compareTo(final Object pThat) {
/* Handle the trivial cases */
if (this.equals(pThat)) {
return 0;
}
if (pThat == null) {
return -1;
}
/* If the Asset is not a SecurityHolding we are last */
if (!(pThat instanceof MoneyWiseSecurityHolding)) {
return 1;
}
/* Access as AssetBase */
final MoneyWiseSecurityHolding myThat = (MoneyWiseSecurityHolding) pThat;
/* Compare portfolios */
int iDiff = getPortfolio().compareTo(myThat.getPortfolio());
if (iDiff == 0) {
/* Compare securities */
iDiff = getSecurity().compareTo(myThat.getSecurity());
}
/* Return the result */
return iDiff;
}
/**
* Are the currencies the same?
*
* @return true/false
*/
protected boolean validCurrencies() {
return thePortfolio.getCurrency().equals(theSecurity.getCurrency());
}
/**
* SecurityHolding Map.
*/
public static class MoneyWiseSecurityHoldingMap
implements MetisDataObjectFormat, MetisDataMap<Integer, MoneyWisePortfolioHoldingsMap> {
/**
* 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 MoneyWiseDataSet pData) {
/* Access lists */
thePortfolios = pData.getPortfolios();
theSecurities = pData.getSecurities();
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();
}
/**
* Clear the map.
*/
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(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(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));
}
/**
* DeRegister Portfolio.
*
* @param pPortfolio the portfolio
*/
public 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
*/
public 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 (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 (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 (!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 (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();
}
}
}