View Javadoc
1   /*
2    * MoneyWise: Finance Application
3    * Copyright 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  
18  package io.github.tonywasher.joceanus.moneywise.data.basic;
19  
20  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataMap;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataObjectFormat;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWisePortfolio.MoneyWisePortfolioList;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity.MoneyWiseSecurityList;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityHoldingMap.MoneyWisePortfolioHoldingsMap;
25  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
26  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
27  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
28  import io.github.tonywasher.joceanus.prometheus.views.PrometheusEditSet;
29  
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
36  
37  /**
38   * SecurityHolding Map.
39   */
40  public class MoneyWiseSecurityHoldingMap
41          implements MetisDataObjectFormat, MetisDataMap<Integer, MoneyWisePortfolioHoldingsMap>, MoneyWiseSecurityHoldingMapCtl {
42      /**
43       * Underlying portfolio list.
44       */
45      private final MoneyWisePortfolioList thePortfolios;
46  
47      /**
48       * Underlying security list.
49       */
50      private final MoneyWiseSecurityList theSecurities;
51  
52      /**
53       * The underlying map.
54       */
55      private final Map<Integer, MoneyWisePortfolioHoldingsMap> theMap;
56  
57      /**
58       * Constructor.
59       *
60       * @param pData the dataSet
61       */
62      public MoneyWiseSecurityHoldingMap(final PrometheusDataSet pData) {
63          /* Access lists */
64          thePortfolios = pData.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
65          theSecurities = pData.getDataList(MoneyWiseBasicDataType.SECURITY, MoneyWiseSecurityList.class);
66          theMap = new HashMap<>();
67      }
68  
69      /**
70       * Constructor.
71       *
72       * @param pEditSet the editSet
73       */
74      public MoneyWiseSecurityHoldingMap(final PrometheusEditSet pEditSet) {
75          /* Access lists */
76          thePortfolios = pEditSet.getDataList(MoneyWiseBasicDataType.PORTFOLIO, MoneyWisePortfolioList.class);
77          theSecurities = pEditSet.getDataList(MoneyWiseBasicDataType.SECURITY, MoneyWiseSecurityList.class);
78          theMap = new HashMap<>();
79      }
80  
81      @Override
82      public Map<Integer, MoneyWisePortfolioHoldingsMap> getUnderlyingMap() {
83          return theMap;
84      }
85  
86      @Override
87      public String formatObject(final OceanusDataFormatter pFormatter) {
88          return MoneyWiseSecurityHoldingMap.class.getSimpleName();
89      }
90  
91      @Override
92      public void clear() {
93          theMap.clear();
94      }
95  
96      /**
97       * Look up a security holding.
98       *
99       * @param pId the id of the security holding
100      * @return the security holding
101      */
102     public MoneyWiseSecurityHolding findHoldingById(final Long pId) {
103         /* Access the component IDs */
104         final Integer myPortId = getPortfolioId(pId);
105         final Integer mySecId = getSecurityId(pId);
106 
107         /* Look up security map for portfolio */
108         final MoneyWisePortfolioHoldingsMap myMap = getMapForPortfolio(myPortId);
109         return myMap == null
110                 ? null
111                 : myMap.getHoldingForSecurity(mySecId);
112     }
113 
114     /**
115      * Look up a security holding.
116      *
117      * @param pName the name of the security holding
118      * @return the security holding
119      */
120     public MoneyWiseSecurityHolding findHoldingByName(final String pName) {
121         /* Access the component Names */
122         final String myPortName = getPortfolioName(pName);
123         final String mySecName = getSecurityName(pName);
124 
125         /* Look up security map for portfolio */
126         final MoneyWisePortfolioHoldingsMap myMap = getMapForPortfolio(myPortName);
127         return myMap == null
128                 ? null
129                 : myMap.getHoldingForSecurity(mySecName);
130     }
131 
132     /**
133      * Obtain portfolio name from composite name.
134      *
135      * @param pName the composite name
136      * @return the portfolio name
137      */
138     private static String getPortfolioName(final String pName) {
139         final int iIndex = pName.indexOf(MoneyWiseSecurityHolding.SECURITYHOLDING_SEP);
140         return iIndex == -1
141                 ? pName
142                 : pName.substring(0, iIndex);
143     }
144 
145     /**
146      * Obtain security name from composite name.
147      *
148      * @param pName the composite name
149      * @return the security name
150      */
151     private static String getSecurityName(final String pName) {
152         final int iIndex = pName.indexOf(MoneyWiseSecurityHolding.SECURITYHOLDING_SEP);
153         return iIndex == -1
154                 ? ""
155                 : pName.substring(iIndex + 1);
156     }
157 
158     /**
159      * Obtain portfolio id from composite id.
160      *
161      * @param pId the composite id
162      * @return the portfolio id
163      */
164     private static Integer getPortfolioId(final Long pId) {
165         return MoneyWiseAssetType.getMajorId(pId);
166     }
167 
168     /**
169      * Obtain security id from composite id.
170      *
171      * @param pId the composite id
172      * @return the security id
173      */
174     private static Integer getSecurityId(final Long pId) {
175         return MoneyWiseAssetType.getBaseId(pId);
176     }
177 
178     /**
179      * Declare holding.
180      *
181      * @param pPortfolio the portfolio
182      * @param pSecurity  the security
183      * @return the holding
184      */
185     public MoneyWiseSecurityHolding declareHolding(final MoneyWisePortfolio pPortfolio,
186                                                    final MoneyWiseSecurity pSecurity) {
187         /* Access the portfolio map */
188         final MoneyWisePortfolioHoldingsMap myPortMap = getMapForPortfolio(pPortfolio.getIndexedId());
189         if (myPortMap == null) {
190             throw new IllegalStateException("Invalid Portfolio");
191         }
192         final Map<Integer, MoneyWiseSecurityHolding> myMap = myPortMap.getUnderlyingMap();
193 
194         /* Look up existing holding */
195         final Integer myId = pSecurity.getIndexedId();
196         return myMap.computeIfAbsent(myId, i -> new MoneyWiseSecurityHolding(pPortfolio, pSecurity));
197     }
198 
199     /**
200      * Obtain or allocate the map for the portfolio.
201      *
202      * @param pId the id of the portfolio
203      * @return the map
204      */
205     private MoneyWisePortfolioHoldingsMap getMapForPortfolio(final Integer pId) {
206         /* Look up in the map */
207         MoneyWisePortfolioHoldingsMap myMap = theMap.get(pId);
208 
209         /* If the Id is not found */
210         if (myMap == null) {
211             /* Look up the portfolio as a double check */
212             final MoneyWisePortfolio myPortfolio = thePortfolios.findItemById(pId);
213 
214             /* Reject if no such portfolio */
215             if (myPortfolio == null
216                     || myPortfolio.isDeleted()) {
217                 return null;
218             }
219 
220             /* Allocate and store the map */
221             myMap = new MoneyWisePortfolioHoldingsMap(myPortfolio, theSecurities);
222             theMap.put(pId, myMap);
223         }
224 
225         /* Return the map */
226         return myMap;
227     }
228 
229     /**
230      * Obtain or allocate the map for the portfolio.
231      *
232      * @param pName the name of the portfolio
233      * @return the map
234      */
235     private MoneyWisePortfolioHoldingsMap getMapForPortfolio(final String pName) {
236         /* Look up the portfolio */
237         final MoneyWisePortfolio myPortfolio = thePortfolios.findItemByName(pName);
238 
239         /* Reject if no such portfolio */
240         if (myPortfolio == null) {
241             return null;
242         }
243 
244         /* Look up in the map */
245         final Integer myId = myPortfolio.getIndexedId();
246         return theMap.computeIfAbsent(myId, i -> new MoneyWisePortfolioHoldingsMap(myPortfolio, theSecurities));
247     }
248 
249     @Override
250     public void deRegister(final MoneyWiseAssetBase pAsset) {
251         /* Ensure that we do not reference this asset */
252         switch (pAsset) {
253             case MoneyWisePortfolio myPortfolio -> deRegister(myPortfolio);
254             case MoneyWiseSecurity mySecurity -> deRegister(mySecurity);
255             default -> {
256                 /* NoOp */
257             }
258         }
259     }
260 
261     /**
262      * DeRegister Portfolio.
263      *
264      * @param pPortfolio the portfolio
265      */
266     private void deRegister(final MoneyWisePortfolio pPortfolio) {
267         /* Ensure that we do not reference this portfolio */
268         final Integer myId = pPortfolio.getIndexedId();
269         theMap.remove(myId);
270     }
271 
272     /**
273      * DeRegister Security.
274      *
275      * @param pSecurity the security
276      */
277     private void deRegister(final MoneyWiseSecurity pSecurity) {
278         /* Access the id */
279         final Integer myId = pSecurity.getIndexedId();
280 
281         /* Iterate through the portfolio maps */
282         final Iterator<MoneyWisePortfolioHoldingsMap> myIterator = theMap.values().iterator();
283         while (myIterator.hasNext()) {
284             final MoneyWisePortfolioHoldingsMap myMap = myIterator.next();
285 
286             /* Ensure that we do not reference this security */
287             myMap.getUnderlyingMap().remove(myId);
288         }
289     }
290 
291     /**
292      * Obtain existing holding list for Portfolio.
293      *
294      * @param pPortfolio the portfolio
295      * @return the iterator
296      */
297     public Iterator<MoneyWiseSecurityHolding> existingIterator(final MoneyWisePortfolio pPortfolio) {
298         /* Look up in the map */
299         final MoneyWisePortfolioHoldingsMap myMap = theMap.get(pPortfolio.getIndexedId());
300 
301         /* return the iterator */
302         return myMap == null
303                 ? null
304                 : myMap.existingIterator();
305     }
306 
307     /**
308      * Obtain new holding list for Portfolio.
309      *
310      * @param pPortfolio the portfolio
311      * @return the iterator
312      */
313     public Iterator<MoneyWiseSecurityHolding> newIterator(final MoneyWisePortfolio pPortfolio) {
314         return newIterator(pPortfolio, null);
315     }
316 
317     /**
318      * Obtain new holding list for Portfolio.
319      *
320      * @param pPortfolio the portfolio
321      * @param pClass     the class of holdings or null for all
322      * @return the iterator
323      */
324     public Iterator<MoneyWiseSecurityHolding> newIterator(final MoneyWisePortfolio pPortfolio,
325                                                           final MoneyWiseSecurityClass pClass) {
326         /* Look up in the map */
327         final MoneyWisePortfolioHoldingsMap myMap = theMap.get(pPortfolio.getIndexedId());
328 
329         /* return the iterator */
330         return myMap == null
331                 ? fullIterator(pPortfolio, pClass)
332                 : myMap.newIterator(pClass);
333     }
334 
335     /**
336      * Obtain new holding list for Portfolio.
337      *
338      * @param pPortfolio the portfolio
339      * @param pClass     the class of holdings or null for all
340      * @return the iterator
341      */
342     private Iterator<MoneyWiseSecurityHolding> fullIterator(final MoneyWisePortfolio pPortfolio,
343                                                             final MoneyWiseSecurityClass pClass) {
344         /* If the portfolio is closed/deleted */
345         if (Boolean.TRUE.equals(pPortfolio.isClosed()) || pPortfolio.isDeleted()) {
346             /* No iterator */
347             return null;
348         }
349 
350         /* Create an empty list */
351         List<MoneyWiseSecurityHolding> myList = new ArrayList<>();
352 
353         /* Loop through the securities */
354         final Iterator<MoneyWiseSecurity> myIterator = theSecurities.iterator();
355         while (myIterator.hasNext()) {
356             final MoneyWiseSecurity mySecurity = myIterator.next();
357 
358             /* Ignore closed/deleted and wrong class */
359             boolean bIgnore = mySecurity.isClosed() || mySecurity.isDeleted();
360             bIgnore |= pClass != null && !pClass.equals(mySecurity.getCategoryClass());
361             if (bIgnore) {
362                 continue;
363             }
364 
365             /* Create a new holding and add to the list */
366             final MoneyWiseSecurityHolding myHolding = new MoneyWiseSecurityHolding(pPortfolio, mySecurity);
367             myList.add(myHolding);
368         }
369 
370         /* Sort list or delete if empty */
371         if (myList.isEmpty()) {
372             myList = null;
373         } else {
374             Collections.sort(myList);
375         }
376 
377         /* return iterator */
378         return myList == null
379                 ? null
380                 : myList.iterator();
381     }
382 
383     /**
384      * PortfolioHoldings Map.
385      */
386     public static final class MoneyWisePortfolioHoldingsMap
387             implements MetisDataObjectFormat, MetisDataMap<Integer, MoneyWiseSecurityHolding> {
388         /**
389          * Portfolio.
390          */
391         private final MoneyWisePortfolio thePortfolio;
392 
393         /**
394          * Underlying security list.
395          */
396         private final MoneyWiseSecurityList theSecurities;
397 
398         /**
399          * The underlying map.
400          */
401         private final Map<Integer, MoneyWiseSecurityHolding> theMap;
402 
403         /**
404          * Constructor.
405          *
406          * @param pPortfolio  the portfolio
407          * @param pSecurities the security list
408          */
409         private MoneyWisePortfolioHoldingsMap(final MoneyWisePortfolio pPortfolio,
410                                               final MoneyWiseSecurityList pSecurities) {
411             thePortfolio = pPortfolio;
412             theSecurities = pSecurities;
413             theMap = new HashMap<>();
414         }
415 
416         @Override
417         public Map<Integer, MoneyWiseSecurityHolding> getUnderlyingMap() {
418             return theMap;
419         }
420 
421         @Override
422         public String formatObject(final OceanusDataFormatter pFormatter) {
423             return thePortfolio.formatObject(pFormatter);
424         }
425 
426         @Override
427         public String toString() {
428             return thePortfolio.toString();
429         }
430 
431         /**
432          * Obtain or allocate the holding for the security.
433          *
434          * @param pId the id of the security
435          * @return the holding
436          */
437         private MoneyWiseSecurityHolding getHoldingForSecurity(final Integer pId) {
438             /* Look up in the map */
439             MoneyWiseSecurityHolding myHolding = theMap.get(pId);
440 
441             /* If the Id is not found */
442             if (myHolding == null) {
443                 /* Look up the security as a double check */
444                 final MoneyWiseSecurity mySecurity = theSecurities.findItemById(pId);
445 
446                 /* Reject if no such security */
447                 if (mySecurity == null || mySecurity.isDeleted()) {
448                     return null;
449                 }
450 
451                 /* Allocate and store the holding */
452                 myHolding = new MoneyWiseSecurityHolding(thePortfolio, mySecurity);
453                 theMap.put(pId, myHolding);
454             }
455 
456             /* Return the holding */
457             return myHolding;
458         }
459 
460         /**
461          * Obtain or allocate the holding for the security.
462          *
463          * @param pName the name of the security
464          * @return the holding
465          */
466         private MoneyWiseSecurityHolding getHoldingForSecurity(final String pName) {
467             /* Look up the security */
468             final MoneyWiseSecurity mySecurity = theSecurities.findItemByName(pName);
469 
470             /* Reject if no such security */
471             if (mySecurity == null) {
472                 return null;
473             }
474 
475             /* Look up in the map */
476             final Integer myId = mySecurity.getIndexedId();
477             return theMap.computeIfAbsent(myId, i -> new MoneyWiseSecurityHolding(thePortfolio, mySecurity));
478         }
479 
480         /**
481          * Obtain existing holding list for Portfolio.
482          *
483          * @return the iterator
484          */
485         private Iterator<MoneyWiseSecurityHolding> existingIterator() {
486             /* If the portfolio is closed/deleted */
487             if (Boolean.TRUE.equals(thePortfolio.isClosed()) || thePortfolio.isDeleted()) {
488                 /* No iterator */
489                 return null;
490             }
491 
492             /* Create an empty list */
493             List<MoneyWiseSecurityHolding> myList = new ArrayList<>();
494 
495             /* Loop through the holdings */
496             final Iterator<MoneyWiseSecurityHolding> myIterator = theMap.values().iterator();
497             while (myIterator.hasNext()) {
498                 final MoneyWiseSecurityHolding myHolding = myIterator.next();
499                 final MoneyWiseSecurity mySecurity = myHolding.getSecurity();
500 
501                 /* Ignore closed/deleted */
502                 if (Boolean.FALSE.equals(mySecurity.isClosed()) && !mySecurity.isDeleted()) {
503                     /* Add to the list */
504                     myList.add(myHolding);
505                 }
506             }
507 
508             /* Sort list or delete if empty */
509             if (myList.isEmpty()) {
510                 myList = null;
511             } else {
512                 Collections.sort(myList);
513             }
514 
515             /* return iterator */
516             return myList == null
517                     ? null
518                     : myList.iterator();
519         }
520 
521         /**
522          * Obtain new holding list for Portfolio.
523          *
524          * @param pClass the class of holdings or null for all
525          * @return the iterator
526          */
527         private Iterator<MoneyWiseSecurityHolding> newIterator(final MoneyWiseSecurityClass pClass) {
528             /* If the portfolio is closed/deleted */
529             if (Boolean.TRUE.equals(thePortfolio.isClosed()) || thePortfolio.isDeleted()) {
530                 /* No iterator */
531                 return null;
532             }
533 
534             /* Create an empty list */
535             List<MoneyWiseSecurityHolding> myList = new ArrayList<>();
536 
537             /* Loop through the securities */
538             final Iterator<MoneyWiseSecurity> myIterator = theSecurities.iterator();
539             while (myIterator.hasNext()) {
540                 final MoneyWiseSecurity mySecurity = myIterator.next();
541 
542                 /* Ignore closed/deleted and wrong class/currency */
543                 boolean bIgnore = mySecurity.isClosed() || mySecurity.isDeleted();
544                 bIgnore |= pClass != null && pClass.equals(mySecurity.getCategoryClass());
545                 if (bIgnore) {
546                     continue;
547                 }
548 
549                 /* Only add if not currently present */
550                 if (theMap.get(mySecurity.getIndexedId()) == null) {
551                     /* Create a new holding and add to the list */
552                     final MoneyWiseSecurityHolding myHolding = new MoneyWiseSecurityHolding(thePortfolio, mySecurity);
553                     myList.add(myHolding);
554                 }
555             }
556 
557             /* Sort list or delete if empty */
558             if (myList.isEmpty()) {
559                 myList = null;
560             } else {
561                 Collections.sort(myList);
562             }
563 
564             /* return iterator */
565             return myList == null
566                     ? null
567                     : myList.iterator();
568         }
569     }
570 }