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