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