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.decimal.OceanusPrice;
21  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
23  import io.github.tonywasher.joceanus.metis.data.MetisDataEditState;
24  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
25  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
26  import io.github.tonywasher.joceanus.metis.data.MetisDataState;
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.MoneyWisePortfolio.MoneyWisePortfolioList;
31  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHolding.MoneyWiseSecurityHoldingMap;
32  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityInfo.MoneyWiseSecurityInfoList;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTax.MoneyWiseTaxCredit;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseTransCategory.MoneyWiseTransCategoryList;
35  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoClass;
36  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoType.MoneyWiseAccountInfoTypeList;
37  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
38  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency.MoneyWiseCurrencyList;
39  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
40  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityType;
41  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityType.MoneyWiseSecurityTypeList;
42  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticDataType;
43  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseTransCategoryClass;
44  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
45  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseLogicException;
46  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInstanceMap;
47  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
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   * Security class.
60   */
61  public class MoneyWiseSecurity
62          extends MoneyWiseAssetBase
63          implements PrometheusInfoSetItem {
64      /**
65       * Object name.
66       */
67      public static final String OBJECT_NAME = MoneyWiseBasicDataType.SECURITY.getItemName();
68  
69      /**
70       * List name.
71       */
72      public static final String LIST_NAME = MoneyWiseBasicDataType.SECURITY.getListName();
73  
74      /**
75       * Local Report fields.
76       */
77      private static final MetisFieldVersionedSet<MoneyWiseSecurity> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MoneyWiseSecurity.class);
78  
79      /*
80       * FieldIds.
81       */
82      static {
83          FIELD_DEFS.declareLocalField(PrometheusDataResource.DATAINFOSET_NAME, MoneyWiseSecurity::getInfoSet);
84          FIELD_DEFS.buildFieldMap(MoneyWiseAccountInfoClass.class, MoneyWiseSecurity::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       * SecurityInfoSet.
99       */
100     private final MoneyWiseSecurityInfoSet theInfoSet;
101 
102     /**
103      * Copy Constructor.
104      *
105      * @param pList     the list
106      * @param pSecurity The Security to copy
107      */
108     protected MoneyWiseSecurity(final MoneyWiseSecurityList pList,
109                                 final MoneyWiseSecurity pSecurity) {
110         /* Set standard values */
111         super(pList, pSecurity);
112 
113         /* switch on list type */
114         switch (getList().getStyle()) {
115             case EDIT:
116                 theInfoSet = new MoneyWiseSecurityInfoSet(this, pList.getActInfoTypes(), pList.getSecurityInfo());
117                 theInfoSet.cloneDataInfoSet(pSecurity.getInfoSet());
118                 hasInfoSet = true;
119                 useInfoSet = true;
120                 break;
121             case CLONE:
122             case CORE:
123                 theInfoSet = new MoneyWiseSecurityInfoSet(this, pList.getActInfoTypes(), pList.getSecurityInfo());
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 MoneyWiseSecurity(final MoneyWiseSecurityList pList,
143                               final PrometheusDataValues pValues) throws OceanusException {
144         /* Initialise the item */
145         super(pList, pValues);
146 
147         /* Create the InfoSet */
148         theInfoSet = new MoneyWiseSecurityInfoSet(this, pList.getActInfoTypes(), pList.getSecurityInfo());
149         hasInfoSet = true;
150         useInfoSet = false;
151     }
152 
153     /**
154      * Edit Constructor.
155      *
156      * @param pList the list
157      */
158     public MoneyWiseSecurity(final MoneyWiseSecurityList pList) {
159         super(pList);
160 
161         /* Build InfoSet */
162         theInfoSet = new MoneyWiseSecurityInfoSet(this, pList.getActInfoTypes(), pList.getSecurityInfo());
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 (MoneyWiseStaticDataType.CURRENCY.equals(pField)) {
179             return true;
180         }
181         if (MoneyWiseBasicResource.ASSET_PARENT.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.SECURITY, getIndexedId());
192     }
193 
194     @Override
195     public MoneyWiseSecurityInfoSet 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 Notes.
211      *
212      * @return the notes
213      */
214     public char[] getNotes() {
215         return hasInfoSet
216                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.NOTES, char[].class)
217                 : null;
218     }
219 
220     /**
221      * Obtain Symbol.
222      *
223      * @return the symbol
224      */
225     public String getSymbol() {
226         return hasInfoSet
227                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.SYMBOL, String.class)
228                 : null;
229     }
230 
231     /**
232      * Obtain Region.
233      *
234      * @return the region
235      */
236     public MoneyWiseRegion getRegion() {
237         return hasInfoSet
238                 ? theInfoSet.getRegion(MoneyWiseAccountInfoClass.REGION)
239                 : null;
240     }
241 
242     /**
243      * Obtain UnderlyingStock.
244      *
245      * @return the stock
246      */
247     public MoneyWiseSecurity getUnderlyingStock() {
248         return hasInfoSet
249                 ? theInfoSet.getSecurity(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK)
250                 : null;
251     }
252 
253     /**
254      * Obtain OptionPrice.
255      *
256      * @return the price
257      */
258     public OceanusPrice getOptionPrice() {
259         return hasInfoSet
260                 ? theInfoSet.getValue(MoneyWiseAccountInfoClass.OPTIONPRICE, OceanusPrice.class)
261                 : null;
262     }
263 
264     @Override
265     public MoneyWiseSecurityType getCategory() {
266         return getValues().getValue(MoneyWiseBasicResource.CATEGORY_NAME, MoneyWiseSecurityType.class);
267     }
268 
269     /**
270      * Obtain CategoryId.
271      *
272      * @return the categoryId
273      */
274     public Integer getCategoryId() {
275         final MoneyWiseSecurityType myType = getCategory();
276         return myType == null
277                 ? null
278                 : myType.getIndexedId();
279     }
280 
281     /**
282      * Obtain CategoryName.
283      *
284      * @return the categoryName
285      */
286     public String getCategoryName() {
287         final MoneyWiseSecurityType myType = getCategory();
288         return myType == null
289                 ? null
290                 : myType.getName();
291     }
292 
293     /**
294      * Obtain SecurityClass.
295      *
296      * @return the securityClass
297      */
298     public MoneyWiseSecurityClass getCategoryClass() {
299         final MoneyWiseSecurityType myType = getCategory();
300         return myType == null
301                 ? null
302                 : myType.getSecurityClass();
303     }
304 
305     @Override
306     public boolean isForeign() {
307         final MoneyWiseCurrency myDefault = getDataSet().getReportingCurrency();
308         return !myDefault.equals(getAssetCurrency());
309     }
310 
311     @Override
312     public MoneyWiseSecurity getBase() {
313         return (MoneyWiseSecurity) super.getBase();
314     }
315 
316     @Override
317     public MoneyWiseSecurityList getList() {
318         return (MoneyWiseSecurityList) super.getList();
319     }
320 
321     @Override
322     public MetisDataState getState() {
323         /* Pop history for self */
324         MetisDataState myState = super.getState();
325 
326         /* If we should use the InfoSet */
327         if ((myState == MetisDataState.CLEAN) && useInfoSet) {
328             /* Get state for infoSet */
329             myState = theInfoSet.getState();
330         }
331 
332         /* Return the state */
333         return myState;
334     }
335 
336     @Override
337     public MetisDataEditState getEditState() {
338         /* Pop history for self */
339         MetisDataEditState myState = super.getEditState();
340 
341         /* If we should use the InfoSet */
342         if (myState == MetisDataEditState.CLEAN
343                 && useInfoSet) {
344             /* Get state for infoSet */
345             myState = theInfoSet.getEditState();
346         }
347 
348         /* Return the state */
349         return myState;
350     }
351 
352     @Override
353     public boolean hasHistory() {
354         /* Check for history for self */
355         boolean hasHistory = super.hasHistory();
356 
357         /* If we should use the InfoSet */
358         if (!hasHistory && useInfoSet) {
359             /* Check history for infoSet */
360             hasHistory = theInfoSet.hasHistory();
361         }
362 
363         /* Return details */
364         return hasHistory;
365     }
366 
367     @Override
368     public void pushHistory() {
369         /* Push history for self */
370         super.pushHistory();
371 
372         /* If we should use the InfoSet */
373         if (useInfoSet) {
374             /* Push history for infoSet */
375             theInfoSet.pushHistory();
376         }
377     }
378 
379     @Override
380     public void popHistory() {
381         /* Pop history for self */
382         super.popHistory();
383 
384         /* If we should use the InfoSet */
385         if (useInfoSet) {
386             /* Pop history for infoSet */
387             theInfoSet.popHistory();
388         }
389     }
390 
391     @Override
392     public boolean checkForHistory() {
393         /* Check for history for self */
394         boolean bChanges = super.checkForHistory();
395 
396         /* If we should use the InfoSet */
397         if (useInfoSet) {
398             /* Check for history for infoSet */
399             bChanges |= theInfoSet.checkForHistory();
400         }
401 
402         /* return result */
403         return bChanges;
404     }
405 
406     @Override
407     public MetisDataDifference fieldChanged(final MetisDataFieldId pField) {
408         /* Handle InfoSet fields */
409         final MoneyWiseAccountInfoClass myClass = MoneyWiseSecurityInfoSet.getClassForField(pField);
410         if (myClass != null) {
411             return useInfoSet
412                     ? theInfoSet.fieldChanged(myClass)
413                     : MetisDataDifference.IDENTICAL;
414         }
415 
416         /* Check super fields */
417         return super.fieldChanged(pField);
418     }
419 
420     @Override
421     public void setDeleted(final boolean bDeleted) {
422         /* Pass call to infoSet if required */
423         if (useInfoSet) {
424             theInfoSet.setDeleted(bDeleted);
425         }
426 
427         /* Pass call onwards */
428         super.setDeleted(bDeleted);
429     }
430 
431     /**
432      * Is this security the required class.
433      *
434      * @param pClass the required security class.
435      * @return true/false
436      */
437     public boolean isSecurityClass(final MoneyWiseSecurityClass pClass) {
438         /* Check for match */
439         return getCategoryClass() == pClass;
440     }
441 
442     @Override
443     public boolean isShares() {
444         return isSecurityClass(MoneyWiseSecurityClass.SHARES);
445     }
446 
447     @Override
448     public boolean isCapital() {
449         switch (getCategoryClass()) {
450             case INCOMEUNITTRUST:
451             case GROWTHUNITTRUST:
452             case LIFEBOND:
453             case SHARES:
454                 return true;
455             default:
456                 return false;
457         }
458     }
459 
460     @Override
461     public void deRegister() {
462         final PrometheusEditSet myEditSet = getList().getEditSet();
463         final MoneyWisePortfolioList myPortfolios = myEditSet == null
464                 ? getDataSet().getPortfolios()
465                 : myEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
466         final MoneyWiseSecurityHoldingMap myMap = myPortfolios.getSecurityHoldingsMap();
467         myMap.deRegister(this);
468     }
469 
470     /**
471      * Set defaults.
472      *
473      * @throws OceanusException on error
474      */
475     public void setDefaults() throws OceanusException {
476         getList().getValidator().setDefaults(this);
477     }
478 
479     /**
480      * autoCorrect values after change.
481      *
482      * @throws OceanusException on error
483      */
484     public void autoCorrect() throws OceanusException {
485         getList().getValidator().autoCorrect(this);
486     }
487 
488     @Override
489     public int compareValues(final PrometheusDataItem pThat) {
490         /* Check the category and then the name */
491         final MoneyWiseSecurity myThat = (MoneyWiseSecurity) pThat;
492         int iDiff = MetisDataDifference.compareObject(getCategory(), myThat.getCategory());
493         if (iDiff == 0) {
494             iDiff = MetisDataDifference.compareObject(getName(), myThat.getName());
495         }
496         return iDiff;
497     }
498 
499     @Override
500     public void resolveDataSetLinks() throws OceanusException {
501         /* Update the Encryption details */
502         super.resolveDataSetLinks();
503 
504         /* Resolve data links */
505         final MoneyWiseDataSet myData = getDataSet();
506         resolveDataLink(MoneyWiseBasicResource.CATEGORY_NAME, myData.getSecurityTypes());
507         resolveDataLink(MoneyWiseStaticDataType.CURRENCY, myData.getAccountCurrencies());
508         resolveDataLink(MoneyWiseBasicResource.ASSET_PARENT, myData.getPayees());
509     }
510 
511     @Override
512     protected void resolveEditSetLinks() throws OceanusException {
513         /* Access the editSet */
514         final PrometheusEditSet myEditSet = getList().getEditSet();
515 
516         /* Resolve Parent/Category/Currency if required */
517         resolveDataLink(MoneyWiseBasicResource.ASSET_PARENT, myEditSet.getDataList(MoneyWiseBasicDataType.PAYEE, MoneyWisePayeeList.class));
518         if (myEditSet.hasDataType(MoneyWiseStaticDataType.SECURITYTYPE)) {
519             resolveDataLink(MoneyWiseBasicResource.CATEGORY_NAME, myEditSet.getDataList(MoneyWiseStaticDataType.SECURITYTYPE, MoneyWiseSecurityTypeList.class));
520         }
521         if (myEditSet.hasDataType(MoneyWiseStaticDataType.CURRENCY)) {
522             resolveDataLink(MoneyWiseStaticDataType.CURRENCY, myEditSet.getDataList(MoneyWiseStaticDataType.CURRENCY, MoneyWiseCurrencyList.class));
523         }
524 
525         /* Resolve links in infoSet */
526         theInfoSet.resolveEditSetLinks(myEditSet);
527     }
528 
529     /**
530      * Set a new Notes.
531      *
532      * @param pNotes the new notes
533      * @throws OceanusException on error
534      */
535     public void setNotes(final char[] pNotes) throws OceanusException {
536         setInfoSetValue(MoneyWiseAccountInfoClass.NOTES, pNotes);
537     }
538 
539     /**
540      * Set a new symbol.
541      *
542      * @param pSymbol the symbol
543      * @throws OceanusException on error
544      */
545     public void setSymbol(final String pSymbol) throws OceanusException {
546         setInfoSetValue(MoneyWiseAccountInfoClass.SYMBOL, pSymbol);
547     }
548 
549     /**
550      * Set a new region.
551      *
552      * @param pRegion the new region
553      * @throws OceanusException on error
554      */
555     public void setRegion(final MoneyWiseRegion pRegion) throws OceanusException {
556         setInfoSetValue(MoneyWiseAccountInfoClass.REGION, pRegion);
557     }
558 
559     /**
560      * Set a new underlying stock.
561      *
562      * @param pStock the new stock
563      * @throws OceanusException on error
564      */
565     public void setUnderlyingStock(final MoneyWiseSecurity pStock) throws OceanusException {
566         setInfoSetValue(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK, pStock);
567     }
568 
569     /**
570      * Set a new option price.
571      *
572      * @param pPrice the new price
573      * @throws OceanusException on error
574      */
575     public void setOptionPrice(final OceanusPrice pPrice) throws OceanusException {
576         setInfoSetValue(MoneyWiseAccountInfoClass.OPTIONPRICE, pPrice);
577     }
578 
579     /**
580      * Set an infoSet value.
581      *
582      * @param pInfoClass the class of info to set
583      * @param pValue     the value to set
584      * @throws OceanusException on error
585      */
586     private void setInfoSetValue(final MoneyWiseAccountInfoClass pInfoClass,
587                                  final Object pValue) throws OceanusException {
588         /* Reject if there is no infoSet */
589         if (!hasInfoSet) {
590             throw new MoneyWiseLogicException(ERROR_BADINFOSET);
591         }
592 
593         /* Set the value */
594         theInfoSet.setValue(pInfoClass, pValue);
595     }
596 
597     @Override
598     public MoneyWiseTransCategory getDetailedCategory(final MoneyWiseTransCategory pCategory,
599                                                       final MoneyWiseTaxCredit pYear) {
600         /* Switch on category type */
601         if (MoneyWiseTransCategoryClass.DIVIDEND.equals(pCategory.getCategoryTypeClass())) {
602             final MoneyWiseTransCategoryList myCategories = getDataSet().getTransCategories();
603             if (isForeign()) {
604                 return myCategories.getSingularClass(MoneyWiseTransCategoryClass.FOREIGNDIVIDEND);
605             }
606             return myCategories.getSingularClass(getCategoryClass().isUnitTrust()
607                     ? MoneyWiseTransCategoryClass.UNITTRUSTDIVIDEND
608                     : MoneyWiseTransCategoryClass.SHAREDIVIDEND);
609         }
610         return pCategory;
611     }
612 
613     @Override
614     public void touchUnderlyingItems() {
615         /* touch the security type, currency and parent */
616         getCategory().touchItem(this);
617         getAssetCurrency().touchItem(this);
618         getParent().touchItem(this);
619 
620         /* touch infoSet items */
621         theInfoSet.touchUnderlyingItems();
622     }
623 
624     @Override
625     public void touchOnUpdate() {
626         /* Reset touches from update set */
627         clearTouches(MoneyWiseBasicDataType.SECURITYPRICE);
628 
629         /* Touch parent */
630         getParent().touchItem(this);
631     }
632 
633     /**
634      * Update base security from an edited security.
635      *
636      * @param pSecurity the edited security
637      * @return whether changes have been made
638      */
639     @Override
640     public boolean applyChanges(final PrometheusDataItem pSecurity) {
641         /* Can only update from a security */
642         if (!(pSecurity instanceof MoneyWiseSecurity)) {
643             return false;
644         }
645         final MoneyWiseSecurity mySecurity = (MoneyWiseSecurity) pSecurity;
646 
647         /* Store the current detail into history */
648         pushHistory();
649 
650         /* Apply basic changes */
651         applyBasicChanges(mySecurity);
652 
653         /* Check for changes */
654         return checkForHistory();
655     }
656 
657     @Override
658     public void adjustMapForItem() {
659         final MoneyWiseSecurityList myList = getList();
660         final MoneyWiseSecurityDataMap myMap = myList.getDataMap();
661         myMap.adjustForItem(this);
662     }
663 
664     @Override
665     public void removeItem() {
666         theInfoSet.removeItems();
667         super.removeItem();
668     }
669 
670     /**
671      * The Security List class.
672      */
673     public static class MoneyWiseSecurityList
674             extends MoneyWiseAssetBaseList<MoneyWiseSecurity> {
675         /**
676          * Report fields.
677          */
678         private static final MetisFieldSet<MoneyWiseSecurityList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseSecurityList.class);
679 
680         /**
681          * The SecurityInfo List.
682          */
683         private MoneyWiseSecurityInfoList theInfoList;
684 
685         /**
686          * The AccountInfoType list.
687          */
688         private MoneyWiseAccountInfoTypeList theInfoTypeList;
689 
690         /**
691          * Construct an empty CORE Security list.
692          *
693          * @param pData the DataSet for the list
694          */
695         public MoneyWiseSecurityList(final MoneyWiseDataSet pData) {
696             super(pData, MoneyWiseSecurity.class, MoneyWiseBasicDataType.SECURITY);
697         }
698 
699         /**
700          * Constructor for a cloned List.
701          *
702          * @param pSource the source List
703          */
704         protected MoneyWiseSecurityList(final MoneyWiseSecurityList pSource) {
705             super(pSource);
706         }
707 
708         @Override
709         public MetisFieldSet<MoneyWiseSecurityList> getDataFieldSet() {
710             return FIELD_DEFS;
711         }
712 
713         @Override
714         public String listName() {
715             return LIST_NAME;
716         }
717 
718         @Override
719         public MetisFieldSetDef getItemFields() {
720             return MoneyWiseSecurity.FIELD_DEFS;
721         }
722 
723         @Override
724         public MoneyWiseSecurityDataMap getDataMap() {
725             return (MoneyWiseSecurityDataMap) super.getDataMap();
726         }
727 
728         /**
729          * Obtain the securityInfoList.
730          *
731          * @return the security info list
732          */
733         public MoneyWiseSecurityInfoList getSecurityInfo() {
734             if (theInfoList == null) {
735                 theInfoList = getDataSet().getSecurityInfo();
736             }
737             return theInfoList;
738         }
739 
740         /**
741          * Obtain the accountInfoTypeList.
742          *
743          * @return the account info type list
744          */
745         public MoneyWiseAccountInfoTypeList getActInfoTypes() {
746             if (theInfoTypeList == null) {
747                 theInfoTypeList = getEditSet() == null
748                         ? getDataSet().getActInfoTypes()
749                         : getEditSet().getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class);
750             }
751             return theInfoTypeList;
752         }
753 
754         @Override
755         protected MoneyWiseSecurityList getEmptyList(final PrometheusListStyle pStyle) {
756             final MoneyWiseSecurityList myList = new MoneyWiseSecurityList(this);
757             myList.setStyle(pStyle);
758             return myList;
759         }
760 
761         /**
762          * Derive Edit list.
763          *
764          * @param pEditSet the editSet
765          * @return the edit list
766          * @throws OceanusException on error
767          */
768         public MoneyWiseSecurityList deriveEditList(final PrometheusEditSet pEditSet) throws OceanusException {
769             /* Build an empty List */
770             final MoneyWiseSecurityList myList = getEmptyList(PrometheusListStyle.EDIT);
771             myList.ensureMap();
772             pEditSet.setEditEntryList(MoneyWiseBasicDataType.SECURITY, myList);
773             myList.getValidator().setEditSet(pEditSet);
774 
775             /* Store InfoType list */
776             myList.theInfoTypeList = pEditSet.getDataList(MoneyWiseStaticDataType.ACCOUNTINFOTYPE, MoneyWiseAccountInfoTypeList.class);
777 
778             /* Create info List */
779             final MoneyWiseSecurityInfoList mySecInfo = getSecurityInfo();
780             myList.theInfoList = mySecInfo.getEmptyList(PrometheusListStyle.EDIT);
781             pEditSet.setEditEntryList(MoneyWiseBasicDataType.SECURITYINFO, myList.theInfoList);
782 
783             /* Store the editSet */
784             myList.setEditSet(pEditSet);
785 
786             /* Loop through the securities */
787             final Iterator<MoneyWiseSecurity> myIterator = iterator();
788             while (myIterator.hasNext()) {
789                 final MoneyWiseSecurity myCurr = myIterator.next();
790 
791                 /* Ignore deleted securities */
792                 if (myCurr.isDeleted()) {
793                     continue;
794                 }
795 
796                 /* Build the new linked security and add it to the list */
797                 final MoneyWiseSecurity mySecurity = new MoneyWiseSecurity(myList, myCurr);
798                 myList.add(mySecurity);
799                 mySecurity.resolveEditSetLinks();
800 
801                 /* Adjust the map */
802                 mySecurity.adjustMapForItem();
803             }
804 
805             /* Return the list */
806             return myList;
807         }
808 
809         @Override
810         public MoneyWiseSecurity findItemByName(final String pName) {
811             /* look up the name in the map */
812             return getDataMap().findItemByName(pName);
813         }
814 
815         @Override
816         public boolean checkAvailableName(final String pName) {
817             /* check availability */
818             return findItemByName(pName) == null;
819         }
820 
821         @Override
822         public boolean validNameCount(final String pName) {
823             /* check availability in map */
824             return getDataMap().validNameCount(pName);
825         }
826 
827         /**
828          * Find the item that uses the symbol.
829          *
830          * @param pSymbol the symbol to lookup
831          * @return the item (or null)
832          */
833         public MoneyWiseSecurity findItemBySymbol(final String pSymbol) {
834             /* look up the symbol in the map */
835             return getDataMap().findItemBySymbol(pSymbol);
836         }
837 
838         /**
839          * check that symbol is unique.
840          *
841          * @param pSymbol the symbol
842          * @return true/false
843          */
844         public boolean validSymbolCount(final String pSymbol) {
845             /* check availability in map */
846             return getDataMap().validSymbolCount(pSymbol);
847         }
848 
849         /**
850          * Add a new item to the core list.
851          *
852          * @param pSecurity item
853          * @return the newly added item
854          */
855         @Override
856         public MoneyWiseSecurity addCopyItem(final PrometheusDataItem pSecurity) {
857             /* Can only clone a Security */
858             if (!(pSecurity instanceof MoneyWiseSecurity)) {
859                 throw new UnsupportedOperationException();
860             }
861 
862             final MoneyWiseSecurity mySecurity = new MoneyWiseSecurity(this, (MoneyWiseSecurity) pSecurity);
863             add(mySecurity);
864             return mySecurity;
865         }
866 
867         /**
868          * Add a new item to the edit list.
869          *
870          * @return the new item
871          */
872         @Override
873         public MoneyWiseSecurity addNewItem() {
874             final MoneyWiseSecurity mySecurity = new MoneyWiseSecurity(this);
875             add(mySecurity);
876             return mySecurity;
877         }
878 
879         /**
880          * Obtain the first security for the specified class.
881          *
882          * @param pClass the security class
883          * @return the security
884          */
885         public MoneyWiseSecurity getSingularClass(final MoneyWiseSecurityClass pClass) {
886             /* Lookup in the map */
887             return getDataMap().findSingularItem(pClass);
888         }
889 
890         @Override
891         public MoneyWiseSecurity addValuesItem(final PrometheusDataValues pValues) throws OceanusException {
892             /* Create the security */
893             final MoneyWiseSecurity mySecurity = new MoneyWiseSecurity(this, pValues);
894 
895             /* Check that this SecurityId has not been previously added */
896             if (!isIdUnique(mySecurity.getIndexedId())) {
897                 mySecurity.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
898                 throw new MoneyWiseDataException(mySecurity, ERROR_VALIDATION);
899             }
900 
901             /* Add to the list */
902             add(mySecurity);
903 
904             /* Loop through the info items */
905             if (pValues.hasInfoItems()) {
906                 /* Loop through the items */
907                 final Iterator<PrometheusInfoItem> myIterator = pValues.infoIterator();
908                 while (myIterator.hasNext()) {
909                     final PrometheusInfoItem myItem = myIterator.next();
910 
911                     /* Build info */
912                     final PrometheusDataValues myValues = myItem.getValues(mySecurity);
913                     theInfoList.addValuesItem(myValues);
914                 }
915             }
916 
917             /* Return it */
918             return mySecurity;
919         }
920 
921         @Override
922         protected MoneyWiseSecurityDataMap allocateDataMap() {
923             return new MoneyWiseSecurityDataMap();
924         }
925 
926         @Override
927         public void postProcessOnLoad() throws OceanusException {
928             /* Resolve links and sort the data */
929             super.resolveDataSetLinks();
930             reSort();
931         }
932     }
933 
934     /**
935      * The dataMap class.
936      */
937     public static class MoneyWiseSecurityDataMap
938             extends PrometheusDataInstanceMap<MoneyWiseSecurity, String> {
939         /**
940          * Report fields.
941          */
942         private static final MetisFieldSet<MoneyWiseSecurityDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseSecurityDataMap.class);
943 
944         /*
945          * Declare Fields.
946          */
947         static {
948             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARMAP, MoneyWiseSecurityDataMap::getSingularMap);
949             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_SINGULARCOUNTS, MoneyWiseSecurityDataMap::getSingularCountMap);
950             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.SECURITY_SYMBOLMAP, MoneyWiseSecurityDataMap::getSymbolMap);
951             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.SECURITY_SYMBOLCOUNTMAP, MoneyWiseSecurityDataMap::getSymbolCountMap);
952         }
953 
954         /**
955          * Map of symbol counts.
956          */
957         private final Map<String, Integer> theSymbolCountMap;
958 
959         /**
960          * Map of symbols.
961          */
962         private final Map<String, MoneyWiseSecurity> theSymbolMap;
963 
964         /**
965          * Map of category counts.
966          */
967         private final Map<Integer, Integer> theSecurityCountMap;
968 
969         /**
970          * Map of singular categories.
971          */
972         private final Map<Integer, MoneyWiseSecurity> theSecurityMap;
973 
974         /**
975          * Constructor.
976          */
977         public MoneyWiseSecurityDataMap() {
978             /* Create the maps */
979             theSecurityCountMap = new HashMap<>();
980             theSecurityMap = new HashMap<>();
981             theSymbolCountMap = new HashMap<>();
982             theSymbolMap = new HashMap<>();
983         }
984 
985         @Override
986         public MetisFieldSet<MoneyWiseSecurityDataMap> getDataFieldSet() {
987             return FIELD_DEFS;
988         }
989 
990         @Override
991         public String formatObject(final OceanusDataFormatter pFormatter) {
992             return FIELD_DEFS.getName();
993         }
994 
995         /**
996          * Obtain the securityMap.
997          *
998          * @return the map
999          */
1000         private Map<Integer, MoneyWiseSecurity> getSingularMap() {
1001             return theSecurityMap;
1002         }
1003 
1004         /**
1005          * Obtain the securityCountMap.
1006          *
1007          * @return the map
1008          */
1009         private Map<Integer, Integer> getSingularCountMap() {
1010             return theSecurityCountMap;
1011         }
1012 
1013         /**
1014          * Obtain the keyMap.
1015          *
1016          * @return the map
1017          */
1018         private Map<String, MoneyWiseSecurity> getSymbolMap() {
1019             return theSymbolMap;
1020         }
1021 
1022         /**
1023          * Obtain the keyCountMap.
1024          *
1025          * @return the map
1026          */
1027         private Map<String, Integer> getSymbolCountMap() {
1028             return theSymbolCountMap;
1029         }
1030 
1031         @Override
1032         public void resetMap() {
1033             super.resetMap();
1034             theSecurityCountMap.clear();
1035             theSecurityMap.clear();
1036             theSymbolCountMap.clear();
1037             theSymbolMap.clear();
1038         }
1039 
1040         @Override
1041         public void adjustForItem(final PrometheusDataItem pItem) {
1042             /* If the class is singular */
1043             final MoneyWiseSecurity myItem = (MoneyWiseSecurity) pItem;
1044             final MoneyWiseSecurityClass myClass = myItem.getCategoryClass();
1045             if (myClass.isSingular()) {
1046                 /* Adjust category count */
1047                 final Integer myId = myClass.getClassId();
1048                 final Integer myCount = theSecurityCountMap.get(myId);
1049                 if (myCount == null) {
1050                     theSecurityCountMap.put(myId, ONE);
1051                 } else {
1052                     theSecurityCountMap.put(myId, myCount + 1);
1053                 }
1054 
1055                 /* Adjust security map */
1056                 theSecurityMap.put(myId, myItem);
1057             }
1058 
1059             /* Adjust symbol count */
1060             if (myClass.needsSymbol()) {
1061                 final String mySymbol = myItem.getSymbol();
1062                 final Integer myCount = theSymbolCountMap.get(mySymbol);
1063                 if (myCount == null) {
1064                     theSymbolCountMap.put(mySymbol, ONE);
1065                 } else {
1066                     theSymbolCountMap.put(mySymbol, myCount + 1);
1067                 }
1068 
1069                 /* Adjust symbol map */
1070                 theSymbolMap.put(mySymbol, myItem);
1071             }
1072 
1073             /* Adjust name count */
1074             adjustForItem(myItem, myItem.getName());
1075         }
1076 
1077         /**
1078          * find item by symbol.
1079          *
1080          * @param pSymbol the symbol to look up
1081          * @return the matching item
1082          */
1083         public MoneyWiseSecurity findItemBySymbol(final String pSymbol) {
1084             return theSymbolMap.get(pSymbol);
1085         }
1086 
1087         /**
1088          * find item by name.
1089          *
1090          * @param pName the name to look up
1091          * @return the matching item
1092          */
1093         public MoneyWiseSecurity findItemByName(final String pName) {
1094             return findItemByKey(pName);
1095         }
1096 
1097         /**
1098          * Check validity of symbol count.
1099          *
1100          * @param pSymbol the symbol to look up
1101          * @return true/false
1102          */
1103         public boolean validSymbolCount(final String pSymbol) {
1104             final Integer myResult = theSymbolCountMap.get(pSymbol);
1105             return ONE.equals(myResult);
1106         }
1107 
1108         /**
1109          * Check validity of name.
1110          *
1111          * @param pName the name to look up
1112          * @return true/false
1113          */
1114         public boolean validNameCount(final String pName) {
1115             return validKeyCount(pName);
1116         }
1117 
1118         /**
1119          * Check availability of name.
1120          *
1121          * @param pName the key to look up
1122          * @return true/false
1123          */
1124         public boolean availableName(final String pName) {
1125             return availableKey(pName);
1126         }
1127 
1128         /**
1129          * find singular item.
1130          *
1131          * @param pClass the class to look up
1132          * @return the matching item
1133          */
1134         public MoneyWiseSecurity findSingularItem(final MoneyWiseSecurityClass pClass) {
1135             return theSecurityMap.get(pClass.getClassId());
1136         }
1137 
1138         /**
1139          * Check validity of singular count.
1140          *
1141          * @param pClass the class to look up
1142          * @return true/false
1143          */
1144         public boolean validSingularCount(final MoneyWiseSecurityClass pClass) {
1145             final Integer myResult = theSecurityCountMap.get(pClass.getClassId());
1146             return ONE.equals(myResult);
1147         }
1148     }
1149 }