View Javadoc
1   /*
2    * MoneyWise: Finance Application
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.moneywise.data.basic;
18  
19  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataEditState;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
23  import io.github.tonywasher.joceanus.metis.data.MetisDataState;
24  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
25  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
26  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePayee.MoneyWisePayeeList;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolioInfo.MoneyWisePortfolioInfoList;
29  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHoldingMapCtl.MoneyWiseSecurityHoldingMapGenerator;
30  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxCredit;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory.MoneyWiseTransCategoryList;
32  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoClass;
33  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoType.MoneyWiseAccountInfoTypeList;
34  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
35  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency.MoneyWiseCurrencyList;
36  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWisePortfolioClass;
37  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWisePortfolioType;
38  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWisePortfolioType.MoneyWisePortfolioTypeList;
39  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
40  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
41  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
42  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseLogicException;
43  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
44  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
45  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInstanceMap;
46  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
47  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataMapItem;
48  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataResource;
49  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
50  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
51  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues.PrometheusInfoItem;
52  import io.github.tonywasher.joceanus.prometheus.data.PrometheusEncrypted.PrometheusDataInfoSetItemCtl;
53  import io.github.tonywasher.joceanus.prometheus.data.PrometheusListStyle;
54  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
55  
56  import java.util.HashMap;
57  import java.util.Iterator;
58  import java.util.Map;
59  import java.util.Objects;
60  
61  /**
62   * Portfolio class.
63   */
64  public class MoneyWisePortfolio
65          extends MoneyWiseAssetBase
66          implements PrometheusDataInfoSetItemCtl<MoneyWisePortfolioInfo> {
67      /**
68       * Object name.
69       */
70      public static final String OBJECT_NAME = MoneyWiseBasicDataType.PORTFOLIO.getItemName();
71  
72      /**
73       * List name.
74       */
75      public static final String LIST_NAME = MoneyWiseBasicDataType.PORTFOLIO.getListName();
76  
77      /**
78       * Local Report fields.
79       */
80      private static final MetisFieldVersionedSet<MoneyWisePortfolio> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MoneyWisePortfolio.class);
81  
82      /*
83       * FieldIds.
84       */
85      static {
86          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAINFOSET_NAME, MoneyWisePortfolio::getInfoSet);
87          FIELD_DEFS.buildFieldMap(MoneyWiseAccountInfoClass.class, MoneyWisePortfolio::getFieldValue);
88      }
89  
90      /**
91       * Do we have an InfoSet.
92       */
93      private final boolean hasInfoSet;
94  
95      /**
96       * Should we use infoSet for DataState etc.
97       */
98      private final boolean useInfoSet;
99  
100     /**
101      * PortfolioInfoSet.
102      */
103     private final MoneyWisePortfolioInfoSet theInfoSet;
104 
105     /**
106      * Copy Constructor.
107      *
108      * @param pList      the list
109      * @param pPortfolio The Portfolio to copy
110      */
111     protected MoneyWisePortfolio(final MoneyWisePortfolioList pList,
112                                  final MoneyWisePortfolio pPortfolio) {
113         /* Set standard values */
114         super(pList, pPortfolio);
115 
116         /* switch on list type */
117         switch (getList().getStyle()) {
118             case EDIT:
119                 theInfoSet = new MoneyWisePortfolioInfoSet(this, pList.getActInfoTypes(), pList.getPortfolioInfo());
120                 theInfoSet.cloneDataInfoSet(pPortfolio.getInfoSet());
121                 hasInfoSet = true;
122                 useInfoSet = true;
123                 break;
124             case CLONE, CORE:
125                 theInfoSet = new MoneyWisePortfolioInfoSet(this, pList.getActInfoTypes(), pList.getPortfolioInfo());
126                 hasInfoSet = true;
127                 useInfoSet = false;
128                 break;
129             default:
130                 theInfoSet = null;
131                 hasInfoSet = false;
132                 useInfoSet = false;
133                 break;
134         }
135     }
136 
137     /**
138      * Values constructor.
139      *
140      * @param pList   the List to add to
141      * @param pValues the values constructor
142      * @throws OceanusException on error
143      */
144     private MoneyWisePortfolio(final MoneyWisePortfolioList pList,
145                                final PrometheusDataValues pValues) throws OceanusException {
146         /* Initialise the item */
147         super(pList, pValues);
148 
149         /* Create the InfoSet */
150         theInfoSet = new MoneyWisePortfolioInfoSet(this, pList.getActInfoTypes(), pList.getPortfolioInfo());
151         hasInfoSet = true;
152         useInfoSet = false;
153     }
154 
155     /**
156      * Edit Constructor.
157      *
158      * @param pList the list
159      */
160     public MoneyWisePortfolio(final MoneyWisePortfolioList pList) {
161         super(pList);
162 
163         /* Build InfoSet */
164         theInfoSet = new MoneyWisePortfolioInfoSet(this, pList.getActInfoTypes(), pList.getPortfolioInfo());
165         hasInfoSet = true;
166         useInfoSet = true;
167     }
168 
169     @Override
170     public MetisFieldSetDef getDataFieldSet() {
171         return FIELD_DEFS;
172     }
173 
174     @Override
175     public boolean includeXmlField(final MetisDataFieldId pField) {
176         /* Determine whether fields should be included */
177         if (MoneyWiseBasicResource.CATEGORY_NAME.equals(pField)) {
178             return true;
179         }
180         if (MoneyWiseBasicResource.ASSET_PARENT.equals(pField)) {
181             return true;
182         }
183         if (MoneyWiseStaticDataType.CURRENCY.equals(pField)) {
184             return true;
185         }
186 
187         /* Pass call on */
188         return super.includeXmlField(pField);
189     }
190 
191     @Override
192     public Long getExternalId() {
193         return MoneyWiseAssetType.createExternalId(MoneyWiseAssetType.PORTFOLIO, getIndexedId());
194     }
195 
196     @Override
197     public MoneyWisePortfolioInfoSet getInfoSet() {
198         return theInfoSet;
199     }
200 
201     @Override
202     public MoneyWisePayee getParent() {
203         return (MoneyWisePayee) super.getParent();
204     }
205 
206     /**
207      * Obtain fieldValue for infoSet.
208      *
209      * @param pFieldId the fieldId
210      * @return the value
211      */
212     private Object getFieldValue(final MetisDataFieldId pFieldId) {
213         return theInfoSet != null ? theInfoSet.getFieldValue(pFieldId) : null;
214     }
215 
216     /**
217      * Obtain WebSite.
218      *
219      * @return the webSite
220      */
221     public char[] getWebSite() {
222         return hasInfoSet
223                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.WEBSITE, char[].class)
224                 : null;
225     }
226 
227     /**
228      * Obtain CustNo.
229      *
230      * @return the customer #
231      */
232     public char[] getCustNo() {
233         return hasInfoSet
234                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.CUSTOMERNO, char[].class)
235                 : null;
236     }
237 
238     /**
239      * Obtain UserId.
240      *
241      * @return the userId
242      */
243     public char[] getUserId() {
244         return hasInfoSet
245                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.USERID, char[].class)
246                 : null;
247     }
248 
249     /**
250      * Obtain Password.
251      *
252      * @return the password
253      */
254     public char[] getPassword() {
255         return hasInfoSet
256                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.PASSWORD, char[].class)
257                 : null;
258     }
259 
260     /**
261      * Obtain SortCode.
262      *
263      * @return the sort code
264      */
265     public char[] getSortCode() {
266         return hasInfoSet
267                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.SORTCODE, char[].class)
268                 : null;
269     }
270 
271     /**
272      * Obtain Reference.
273      *
274      * @return the reference
275      */
276     public char[] getReference() {
277         return hasInfoSet
278                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.REFERENCE, char[].class)
279                 : null;
280     }
281 
282     /**
283      * Obtain Account.
284      *
285      * @return the account
286      */
287     public char[] getAccount() {
288         return hasInfoSet
289                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.ACCOUNT, char[].class)
290                 : null;
291     }
292 
293     /**
294      * Obtain Notes.
295      *
296      * @return the notes
297      */
298     public char[] getNotes() {
299         return hasInfoSet
300                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.NOTES, char[].class)
301                 : null;
302     }
303 
304     @Override
305     public MoneyWisePortfolioType getCategory() {
306         return getValues().getValue(MoneyWiseBasicResource.CATEGORY_NAME, MoneyWisePortfolioType.class);
307     }
308 
309     /**
310      * Obtain categoryId.
311      *
312      * @return the categoryId
313      */
314     public Integer getCategoryId() {
315         final MoneyWisePortfolioType myType = getCategory();
316         return myType == null
317                 ? null
318                 : myType.getIndexedId();
319     }
320 
321     /**
322      * Obtain categoryName.
323      *
324      * @return the categoryName
325      */
326     public String getCategoryName() {
327         final MoneyWisePortfolioType myType = getCategory();
328         return myType == null
329                 ? null
330                 : myType.getName();
331     }
332 
333     /**
334      * Obtain categoryClass.
335      *
336      * @return the categoryClass
337      */
338     public MoneyWisePortfolioClass getCategoryClass() {
339         final MoneyWisePortfolioType myType = getCategory();
340         return myType == null
341                 ? null
342                 : myType.getPortfolioClass();
343     }
344 
345     @Override
346     public boolean isTaxFree() {
347         final MoneyWisePortfolioClass myClass = getCategoryClass();
348         return myClass != null && myClass.isTaxFree();
349     }
350 
351     @Override
352     public boolean isForeign() {
353         final MoneyWiseCurrency myDefault = getDefaultCurrency();
354         return !myDefault.equals(getAssetCurrency());
355     }
356 
357     @Override
358     public MoneyWisePortfolio getBase() {
359         return (MoneyWisePortfolio) super.getBase();
360     }
361 
362     @Override
363     public MoneyWisePortfolioList getList() {
364         return (MoneyWisePortfolioList) super.getList();
365     }
366 
367     @Override
368     public MetisDataState getState() {
369         /* Pop history for self */
370         MetisDataState myState = super.getState();
371 
372         /* If we should use the InfoSet */
373         if ((myState == MetisDataState.CLEAN) && useInfoSet) {
374             /* Get state for infoSet */
375             myState = theInfoSet.getState();
376         }
377 
378         /* Return the state */
379         return myState;
380     }
381 
382     @Override
383     public MetisDataEditState getEditState() {
384         /* Pop history for self */
385         MetisDataEditState myState = super.getEditState();
386 
387         /* If we should use the InfoSet */
388         if (myState == MetisDataEditState.CLEAN
389                 && useInfoSet) {
390             /* Get state for infoSet */
391             myState = theInfoSet.getEditState();
392         }
393 
394         /* Return the state */
395         return myState;
396     }
397 
398     @Override
399     public boolean hasHistory() {
400         /* Check for history for self */
401         boolean hasHistory = super.hasHistory();
402 
403         /* If we should use the InfoSet */
404         if (!hasHistory && useInfoSet) {
405             /* Check history for infoSet */
406             hasHistory = theInfoSet.hasHistory();
407         }
408 
409         /* Return details */
410         return hasHistory;
411     }
412 
413     @Override
414     public void pushHistory() {
415         /* Push history for self */
416         super.pushHistory();
417 
418         /* If we should use the InfoSet */
419         if (useInfoSet) {
420             /* Push history for infoSet */
421             theInfoSet.pushHistory();
422         }
423     }
424 
425     @Override
426     public void popHistory() {
427         /* Pop history for self */
428         super.popHistory();
429 
430         /* If we should use the InfoSet */
431         if (useInfoSet) {
432             /* Pop history for infoSet */
433             theInfoSet.popHistory();
434         }
435     }
436 
437     @Override
438     public boolean checkForHistory() {
439         /* Check for history for self */
440         boolean bChanges = super.checkForHistory();
441 
442         /* If we should use the InfoSet */
443         if (useInfoSet) {
444             /* Check for history for infoSet */
445             bChanges |= theInfoSet.checkForHistory();
446         }
447 
448         /* return result */
449         return bChanges;
450     }
451 
452     @Override
453     public MetisDataDifference fieldChanged(final MetisDataFieldId pField) {
454         /* Handle InfoSet fields */
455         final MoneyWiseAccountInfoClass myClass = MoneyWisePortfolioInfoSet.getClassForField(pField);
456         if (myClass != null) {
457             return useInfoSet
458                     ? theInfoSet.fieldChanged(myClass)
459                     : MetisDataDifference.IDENTICAL;
460         }
461 
462         /* Check super fields */
463         return super.fieldChanged(pField);
464     }
465 
466     @Override
467     public void setDeleted(final boolean bDeleted) {
468         /* Pass call to infoSet if required */
469         if (useInfoSet) {
470             theInfoSet.setDeleted(bDeleted);
471         }
472 
473         /* Pass call onwards */
474         super.setDeleted(bDeleted);
475     }
476 
477     /**
478      * Is this portfolio the required class.
479      *
480      * @param pClass the required portfolio class.
481      * @return true/false
482      */
483     public boolean isPortfolioClass(final MoneyWisePortfolioClass pClass) {
484         /* Check for match */
485         return getCategoryClass() == pClass;
486     }
487 
488     @Override
489     public void deRegister() {
490         final MoneyWiseSecurityHoldingMapCtl myMap = getList().getSecurityHoldingsMap();
491         myMap.deRegister(this);
492     }
493 
494     /**
495      * Set defaults.
496      *
497      * @throws OceanusException on error
498      */
499     public void setDefaults() throws OceanusException {
500         getList().getValidator().setDefaults(this);
501     }
502 
503     /**
504      * adjust values after change.
505      *
506      * @throws OceanusException on error
507      */
508     public void autoCorrect() throws OceanusException {
509         getList().getValidator().autoCorrect(this);
510     }
511 
512     @Override
513     public int compareValues(final PrometheusDataItem pThat) {
514         /* Check the category and then the name */
515         final MoneyWisePortfolio myThat = (MoneyWisePortfolio) pThat;
516         int iDiff = MetisDataDifference.compareObject(getCategory(), myThat.getCategory());
517         if (iDiff == 0) {
518             iDiff = MetisDataDifference.compareObject(getName(), myThat.getName());
519         }
520         return iDiff;
521     }
522 
523     @Override
524     public void resolveDataSetLinks() throws OceanusException {
525         /* Update the Base details */
526         super.resolveDataSetLinks();
527 
528         /* Resolve holding account */
529         resolveDataLink(MoneyWiseBasicResource.ASSET_PARENT, MoneyWiseBasicDataType.PAYEE);
530         resolveDataLink(MoneyWiseBasicResource.CATEGORY_NAME, MoneyWiseStaticDataType.PORTFOLIOTYPE);
531         resolveDataLink(MoneyWiseStaticDataType.CURRENCY, MoneyWiseStaticDataType.CURRENCY);
532     }
533 
534     @Override
535     protected void resolveEditSetLinks() throws OceanusException {
536         /* Access the editSet */
537         final PrometheusEditSet myEditSet = getList().getEditSet();
538 
539         /* Resolve Parent/Category/Currency if required */
540         resolveDataLink(MoneyWiseBasicResource.ASSET_PARENT, myEditSet.getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class));
541         if (myEditSet.hasDataType(MoneyWiseStaticDataType.PORTFOLIOTYPE)) {
542             resolveDataLink(MoneyWiseBasicResource.CATEGORY_NAME, myEditSet.getDataList(MoneyWiseStaticDataType.PORTFOLIOTYPE, MoneyWisePortfolioTypeList.class));
543         }
544         if (myEditSet.hasDataType(MoneyWiseStaticDataType.CURRENCY)) {
545             resolveDataLink(MoneyWiseStaticDataType.CURRENCY, myEditSet.getDataList(MoneyWiseStaticDataType.CURRENCY, MoneyWiseCurrencyList.class));
546         }
547 
548         /* Resolve links in infoSet */
549         theInfoSet.resolveEditSetLinks(myEditSet);
550     }
551 
552     /**
553      * Set a new WebSite.
554      *
555      * @param pWebSite the new webSite
556      * @throws OceanusException on error
557      */
558     public void setWebSite(final char[] pWebSite) throws OceanusException {
559         setInfoSetValue(MoneyWiseAccountInfoClass.WEBSITE, pWebSite);
560     }
561 
562     /**
563      * Set a new CustNo.
564      *
565      * @param pCustNo the new custNo
566      * @throws OceanusException on error
567      */
568     public void setCustNo(final char[] pCustNo) throws OceanusException {
569         setInfoSetValue(MoneyWiseAccountInfoClass.CUSTOMERNO, pCustNo);
570     }
571 
572     /**
573      * Set a new UserId.
574      *
575      * @param pUserId the new userId
576      * @throws OceanusException on error
577      */
578     public void setUserId(final char[] pUserId) throws OceanusException {
579         setInfoSetValue(MoneyWiseAccountInfoClass.USERID, pUserId);
580     }
581 
582     /**
583      * Set a new Password.
584      *
585      * @param pPassword the new password
586      * @throws OceanusException on error
587      */
588     public void setPassword(final char[] pPassword) throws OceanusException {
589         setInfoSetValue(MoneyWiseAccountInfoClass.PASSWORD, pPassword);
590     }
591 
592     /**
593      * Set a new SortCode.
594      *
595      * @param pSortCode the new sort code
596      * @throws OceanusException on error
597      */
598     public void setSortCode(final char[] pSortCode) throws OceanusException {
599         setInfoSetValue(MoneyWiseAccountInfoClass.SORTCODE, pSortCode);
600     }
601 
602     /**
603      * Set a new Account.
604      *
605      * @param pAccount the new account
606      * @throws OceanusException on error
607      */
608     public void setAccount(final char[] pAccount) throws OceanusException {
609         setInfoSetValue(MoneyWiseAccountInfoClass.ACCOUNT, pAccount);
610     }
611 
612     /**
613      * Set a new Reference.
614      *
615      * @param pReference the new reference
616      * @throws OceanusException on error
617      */
618     public void setReference(final char[] pReference) throws OceanusException {
619         setInfoSetValue(MoneyWiseAccountInfoClass.REFERENCE, pReference);
620     }
621 
622     /**
623      * Set a new Notes.
624      *
625      * @param pNotes the new notes
626      * @throws OceanusException on error
627      */
628     public void setNotes(final char[] pNotes) throws OceanusException {
629         setInfoSetValue(MoneyWiseAccountInfoClass.NOTES, pNotes);
630     }
631 
632     /**
633      * Set an infoSet value.
634      *
635      * @param pInfoClass the class of info to set
636      * @param pValue     the value to set
637      * @throws OceanusException on error
638      */
639     private void setInfoSetValue(final MoneyWiseAccountInfoClass pInfoClass,
640                                  final Object pValue) throws OceanusException {
641         /* Reject if there is no infoSet */
642         if (!hasInfoSet) {
643             throw new MoneyWiseLogicException(ERROR_BADINFOSET);
644         }
645 
646         /* Set the value */
647         theInfoSet.setValue(pInfoClass, pValue);
648     }
649 
650     @Override
651     public MoneyWiseTransCategory getDetailedCategory(final MoneyWiseTransCategory pCategory,
652                                                       final MoneyWiseTaxCredit pYear) {
653         /* Switch on category type */
654         final MoneyWiseTransCategoryList myCategories = getDataSet().getDataList(MoneyWiseBasicDataType.TRANSCATEGORY, MoneyWiseTransCategoryList.class);
655         return switch (Objects.requireNonNull(pCategory.getCategoryTypeClass())) {
656             case INTEREST -> {
657                 if (isTaxFree()) {
658                     yield myCategories.getSingularClass(MoneyWiseTransCategoryClass.TAXFREEINTEREST);
659                 }
660                 yield myCategories.getSingularClass(isGross()
661                         || !pYear.isTaxCreditRequired()
662                         ? MoneyWiseTransCategoryClass.GROSSINTEREST
663                         : MoneyWiseTransCategoryClass.TAXEDINTEREST);
664             }
665             case LOYALTYBONUS -> {
666                 if (isTaxFree()) {
667                     yield myCategories.getSingularClass(MoneyWiseTransCategoryClass.TAXFREELOYALTYBONUS);
668                 }
669                 yield myCategories.getSingularClass(isGross()
670                         ? MoneyWiseTransCategoryClass.GROSSLOYALTYBONUS
671                         : MoneyWiseTransCategoryClass.TAXEDLOYALTYBONUS);
672             }
673             case DIVIDEND -> isTaxFree()
674                     ? myCategories.getSingularClass(MoneyWiseTransCategoryClass.TAXFREEDIVIDEND)
675                     : pCategory;
676             default -> pCategory;
677         };
678     }
679 
680     @Override
681     public void touchUnderlyingItems() {
682         /* Touch parent and currency */
683         getCategory().touchItem(this);
684         getParent().touchItem(this);
685         getAssetCurrency().touchItem(this);
686 
687         /* touch infoSet items */
688         theInfoSet.touchUnderlyingItems();
689     }
690 
691     @Override
692     public void touchOnUpdate() {
693         /* Touch parent */
694         getParent().touchItem(this);
695     }
696 
697     /**
698      * Update base portfolio from an edited portfolio.
699      *
700      * @param pPortfolio the edited portfolio
701      * @return whether changes have been made
702      */
703     @Override
704     public boolean applyChanges(final PrometheusDataItem pPortfolio) {
705         /* Can only update from a portfolio */
706         if (!(pPortfolio instanceof MoneyWisePortfolio myPortfolio)) {
707             return false;
708         }
709 
710         /* Store the current detail into history */
711         pushHistory();
712 
713         /* Apply basic changes */
714         applyBasicChanges(myPortfolio);
715 
716         /* Check for changes */
717         return checkForHistory();
718     }
719 
720     @Override
721     public void adjustMapForItem() {
722         final MoneyWisePortfolioList myList = getList();
723         final MoneyWisePortfolioDataMap myMap = myList.getDataMap();
724         myMap.adjustForItem(this);
725     }
726 
727     @Override
728     public void removeItem() {
729         theInfoSet.removeItems();
730         super.removeItem();
731     }
732 
733     /**
734      * The Portfolio List class.
735      */
736     public static class MoneyWisePortfolioList
737             extends MoneyWiseAssetBaseList<MoneyWisePortfolio> {
738         /**
739          * Report fields.
740          */
741         private static final MetisFieldSet<MoneyWisePortfolioList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWisePortfolioList.class);
742 
743         /*
744          * FieldIds.
745          */
746         static {
747             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_HOLDINGSMAP, MoneyWisePortfolioList::getSecurityHoldingsMap);
748         }
749 
750         /**
751          * The PortfolioInfo List.
752          */
753         private MoneyWisePortfolioInfoList theInfoList;
754 
755         /**
756          * The AccountInfoType list.
757          */
758         private MoneyWiseAccountInfoTypeList theInfoTypeList;
759 
760         /**
761          * SecurityHoldings Map.
762          */
763         private MoneyWiseSecurityHoldingMapCtl theSecurityHoldings;
764 
765         /**
766          * Construct an empty CORE Portfolio list.
767          *
768          * @param pData the DataSet for the list
769          */
770         public MoneyWisePortfolioList(final PrometheusDataSet pData) {
771             super(pData, MoneyWisePortfolio.class, MoneyWiseBasicDataType.PORTFOLIO);
772         }
773 
774         /**
775          * Constructor for a cloned List.
776          *
777          * @param pSource the source List
778          */
779         protected MoneyWisePortfolioList(final MoneyWisePortfolioList pSource) {
780             super(pSource);
781         }
782 
783         @Override
784         public MetisFieldSet<MoneyWisePortfolioList> getDataFieldSet() {
785             return FIELD_DEFS;
786         }
787 
788         @Override
789         public String listName() {
790             return LIST_NAME;
791         }
792 
793         @Override
794         public MetisFieldSetDef getItemFields() {
795             return MoneyWisePortfolio.FIELD_DEFS;
796         }
797 
798         @Override
799         public MoneyWisePortfolioDataMap getDataMap() {
800             return (MoneyWisePortfolioDataMap) super.getDataMap();
801         }
802 
803         /**
804          * Obtain the portfolioInfoList.
805          *
806          * @return the portfolio info list
807          */
808         public MoneyWisePortfolioInfoList getPortfolioInfo() {
809             if (theInfoList == null) {
810                 theInfoList = getDataSet().getDataList(MoneyWiseBasicDataType.PORTFOLIOINFO, MoneyWisePortfolioInfoList.class);
811             }
812             return theInfoList;
813         }
814 
815         /**
816          * Obtain the accountInfoTypeList.
817          *
818          * @return the account info type list
819          */
820         public MoneyWiseAccountInfoTypeList getActInfoTypes() {
821             if (theInfoTypeList == null) {
822                 theInfoTypeList = getEditSet() == null
823                         ? getDataSet().getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class)
824                         : getEditSet().getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class);
825             }
826             return theInfoTypeList;
827         }
828 
829         /**
830          * Obtain security holdings map.
831          *
832          * @return the holdings map
833          */
834         public MoneyWiseSecurityHoldingMapCtl getSecurityHoldingsMap() {
835             if (theSecurityHoldings == null) {
836                 final MoneyWiseSecurityHoldingMapGenerator myGenerator =
837                         (MoneyWiseSecurityHoldingMapGenerator) getDataSet();
838                 theSecurityHoldings = getEditSet() == null
839                         ? myGenerator.newSecurityHoldingMap()
840                         : myGenerator.newSecurityHoldingMap(getEditSet());
841             }
842             return theSecurityHoldings;
843         }
844 
845         @Override
846         protected MoneyWisePortfolioList getEmptyList(final PrometheusListStyle pStyle) {
847             final MoneyWisePortfolioList myList = new MoneyWisePortfolioList(this);
848             myList.setStyle(pStyle);
849             return myList;
850         }
851 
852         /**
853          * Derive Edit list.
854          *
855          * @param pEditSet the editSet
856          * @return the edit list
857          * @throws OceanusException on error
858          */
859         public MoneyWisePortfolioList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
860             /* Build an empty List */
861             final MoneyWisePortfolioList myList = getEmptyList(PrometheusListStyle.EDIT);
862             final MoneyWisePayeeList myPayees = pEditSet.getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class);
863             myList.ensureMap(myPayees);
864             pEditSet.setEditEntryList(MoneyWiseBasicDataType.PORTFOLIO, myList);
865             myList.getValidator().setEditSet(pEditSet);
866 
867             /* Store InfoType list */
868             myList.theInfoTypeList = pEditSet.getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class);
869 
870             /* Create info List */
871             final MoneyWisePortfolioInfoList myPortInfo = getPortfolioInfo();
872             myList.theInfoList = myPortInfo.getEmptyList(PrometheusListStyle.EDIT);
873             pEditSet.setEditEntryList(MoneyWiseBasicDataType.PORTFOLIOINFO, myList.theInfoList);
874 
875             /* Store the editSet */
876             myList.setEditSet(pEditSet);
877 
878             /* Loop through the portfolios */
879             final Iterator<MoneyWisePortfolio> myIterator = iterator();
880             while (myIterator.hasNext()) {
881                 final MoneyWisePortfolio myCurr = myIterator.next();
882 
883                 /* Ignore deleted portfolios */
884                 if (myCurr.isDeleted()) {
885                     continue;
886                 }
887 
888                 /* Build the new linked portfolio and add it to the list */
889                 final MoneyWisePortfolio myPortfolio = new MoneyWisePortfolio(myList, myCurr);
890                 myList.add(myPortfolio);
891                 myPortfolio.resolveEditSetLinks();
892 
893                 /* Adjust the map */
894                 myPortfolio.adjustMapForItem();
895             }
896 
897             /* Return the list */
898             return myList;
899         }
900 
901         @Override
902         public void clear() {
903             super.clear();
904             if (theSecurityHoldings != null) {
905                 theSecurityHoldings.clear();
906             }
907         }
908 
909         @Override
910         public MoneyWisePortfolio findItemByName(final String pName) {
911             /* look up the name in the map */
912             return getDataMap().findItemByName(pName);
913         }
914 
915         @Override
916         public boolean checkAvailableName(final String pName) {
917             /* check availability in map */
918             return getDataMap().availableName(pName);
919         }
920 
921         @Override
922         public boolean validNameCount(final String pName) {
923             /* check availability in map */
924             return getDataMap().validNameCount(pName);
925         }
926 
927         /**
928          * Add a new item to the core list.
929          *
930          * @param pPortfolio item
931          * @return the newly added item
932          */
933         @Override
934         public MoneyWisePortfolio addCopyItem(final PrometheusDataItem pPortfolio) {
935             /* Can only clone a Portfolio */
936             if (!(pPortfolio instanceof MoneyWisePortfolio)) {
937                 throw new UnsupportedOperationException();
938             }
939 
940             final MoneyWisePortfolio myPortfolio = new MoneyWisePortfolio(this, (MoneyWisePortfolio) pPortfolio);
941             add(myPortfolio);
942             return myPortfolio;
943         }
944 
945         /**
946          * Add a new item to the edit list.
947          *
948          * @return the new item
949          */
950         @Override
951         public MoneyWisePortfolio addNewItem() {
952             final MoneyWisePortfolio myPortfolio = new MoneyWisePortfolio(this);
953             add(myPortfolio);
954             return myPortfolio;
955         }
956 
957         /**
958          * Obtain the first portfolio for the specified class.
959          *
960          * @param pClass the portfolio class
961          * @return the portfolio
962          */
963         public MoneyWisePortfolio getSingularClass(final MoneyWisePortfolioClass pClass) {
964             /* Lookup in the map */
965             return getDataMap().findSingularItem(pClass);
966         }
967 
968         @Override
969         public MoneyWisePortfolio addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
970             /* Create the portfolio */
971             final MoneyWisePortfolio myPortfolio = new MoneyWisePortfolio(this, pValues);
972 
973             /* Check that this PortfolioId has not been previously added */
974             if (!isIdUnique(myPortfolio.getIndexedId())) {
975                 myPortfolio.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
976                 throw new MoneyWiseDataException(myPortfolio, ERROR_VALIDATION);
977             }
978 
979             /* Add to the list */
980             add(myPortfolio);
981 
982             /* Loop through the info items */
983             if (pValues.hasInfoItems()) {
984                 /* Loop through the items */
985                 final Iterator<PrometheusInfoItem> myIterator = pValues.infoIterator();
986                 while (myIterator.hasNext()) {
987                     final PrometheusInfoItem myItem = myIterator.next();
988 
989                     /* Build info */
990                     final PrometheusDataValues myValues = myItem.getValues(myPortfolio);
991                     theInfoList.addValuesItem(myValues);
992                 }
993             }
994 
995             /* Return it */
996             return myPortfolio;
997         }
998 
999         /**
1000          * Ensure Map based on the payee list.
1001          *
1002          * @param pPayees the payee list
1003          */
1004         private void ensureMap(final MoneyWisePayeeList pPayees) {
1005             setDataMap(new MoneyWisePortfolioDataMap(pPayees));
1006         }
1007 
1008         @Override
1009         protected MoneyWisePortfolioDataMap allocateDataMap() {
1010             return new MoneyWisePortfolioDataMap(getDataSet().getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class));
1011         }
1012 
1013         @Override
1014         public void postProcessOnLoad() throws OceanusException {
1015             /* Resolve links and sort the data */
1016             super.resolveDataSetLinks();
1017             reSort();
1018         }
1019     }
1020 
1021     /**
1022      * The dataMap class.
1023      */
1024     public static class MoneyWisePortfolioDataMap
1025             implements PrometheusDataMapItem, MetisFieldItem {
1026         /**
1027          * Report fields.
1028          */
1029         private static final MetisFieldSet<MoneyWisePortfolioDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWisePortfolioDataMap.class);
1030 
1031         /*
1032          * UnderlyingMap Field Id.
1033          */
1034         static {
1035             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_UNDERLYING, MoneyWisePortfolioDataMap::getUnderlyingMap);
1036             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARMAP, MoneyWisePortfolioDataMap::getPortfolioMap);
1037             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARCOUNTS, MoneyWisePortfolioDataMap::getPortfolioCountMap);
1038         }
1039 
1040         /**
1041          * The assetMap.
1042          */
1043         private final MoneyWiseAssetDataMap theUnderlyingMap;
1044 
1045         /**
1046          * Map of category counts.
1047          */
1048         private final Map<Integer, Integer> thePortfolioCountMap;
1049 
1050         /**
1051          * Map of singular categories.
1052          */
1053         private final Map<Integer, MoneyWisePortfolio> thePortfolioMap;
1054 
1055         /**
1056          * Constructor.
1057          *
1058          * @param pPayees the payee list
1059          */
1060         protected MoneyWisePortfolioDataMap(final MoneyWisePayeeList pPayees) {
1061             /* Access underlying nameMap */
1062             theUnderlyingMap = pPayees.getDataMap().getUnderlyingMap();
1063 
1064             /* Create the maps */
1065             thePortfolioCountMap = new HashMap<>();
1066             thePortfolioMap = new HashMap<>();
1067         }
1068 
1069         @Override
1070         public MetisFieldSet<MoneyWisePortfolioDataMap> getDataFieldSet() {
1071             return FIELD_DEFS;
1072         }
1073 
1074         @Override
1075         public String formatObject(final OceanusDataFormatter pFormatter) {
1076             return FIELD_DEFS.getName();
1077         }
1078 
1079         /**
1080          * Obtain the underlying map.
1081          *
1082          * @return the underlying map
1083          */
1084         private MoneyWiseAssetDataMap getUnderlyingMap() {
1085             return theUnderlyingMap;
1086         }
1087 
1088         /**
1089          * Obtain the underlying map.
1090          *
1091          * @return the underlying map
1092          */
1093         private Map<Integer, MoneyWisePortfolio> getPortfolioMap() {
1094             return thePortfolioMap;
1095         }
1096 
1097         /**
1098          * Obtain the underlying map.
1099          *
1100          * @return the underlying map
1101          */
1102         private Map<Integer, Integer> getPortfolioCountMap() {
1103             return thePortfolioCountMap;
1104         }
1105 
1106         @Override
1107         public void resetMap() {
1108             thePortfolioCountMap.clear();
1109             thePortfolioMap.clear();
1110         }
1111 
1112         @Override
1113         public void adjustForItem(final PrometheusDataItem pItem) {
1114             /* If the class is singular */
1115             final MoneyWisePortfolio myItem = (MoneyWisePortfolio) pItem;
1116             final MoneyWisePortfolioClass myClass = myItem.getCategoryClass();
1117             if (myClass.isSingular()) {
1118                 /* Adjust category count */
1119                 final Integer myId = myClass.getClassId();
1120                 final Integer myCount = thePortfolioCountMap.get(myId);
1121                 if (myCount == null) {
1122                     thePortfolioCountMap.put(myId, PrometheusDataInstanceMap.ONE);
1123                 } else {
1124                     thePortfolioCountMap.put(myId, myCount + 1);
1125                 }
1126 
1127                 /* Adjust portfolio map */
1128                 thePortfolioMap.put(myId, myItem);
1129             }
1130 
1131             /* Adjust name count */
1132             theUnderlyingMap.adjustForItem(pItem);
1133         }
1134 
1135         /**
1136          * find item by name.
1137          *
1138          * @param pName the name to look up
1139          * @return the matching item
1140          */
1141         public MoneyWisePortfolio findItemByName(final String pName) {
1142             final MoneyWiseAssetBase myAsset = theUnderlyingMap.findAssetByName(pName);
1143             return myAsset instanceof MoneyWisePortfolio myPortfolio
1144                     ? myPortfolio
1145                     : null;
1146         }
1147 
1148         /**
1149          * Check validity of name.
1150          *
1151          * @param pName the name to look up
1152          * @return true/false
1153          */
1154         public boolean validNameCount(final String pName) {
1155             return theUnderlyingMap.validNameCount(pName);
1156         }
1157 
1158         /**
1159          * Check availability of name.
1160          *
1161          * @param pName the key to look up
1162          * @return true/false
1163          */
1164         public boolean availableName(final String pName) {
1165             return theUnderlyingMap.availableKey(pName);
1166         }
1167 
1168         /**
1169          * find singular item.
1170          *
1171          * @param pClass the class to look up
1172          * @return the matching item
1173          */
1174         public MoneyWisePortfolio findSingularItem(final MoneyWisePortfolioClass pClass) {
1175             return thePortfolioMap.get(pClass.getClassId());
1176         }
1177 
1178         /**
1179          * Check validity of singular count.
1180          *
1181          * @param pClass the class to look up
1182          * @return true/false
1183          */
1184         public boolean validSingularCount(final MoneyWisePortfolioClass pClass) {
1185             final Integer myResult = thePortfolioCountMap.get(pClass.getClassId());
1186             return PrometheusDataInstanceMap.ONE.equals(myResult);
1187         }
1188     }
1189 }