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