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