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.atlas.data.analysis.buckets;
18  
19  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
20  import io.github.tonywasher.joceanus.metis.data.MetisDataFieldValue;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
22  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
23  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem.MetisFieldTableItem;
24  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
25  import io.github.tonywasher.joceanus.metis.list.MetisListIndexed;
26  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisBaseResource;
27  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisEvent;
28  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.base.MoneyWiseXAnalysisHistory;
29  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisBucketForeign;
30  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.buckets.MoneyWiseXAnalysisInterfaces.MoneyWiseXAnalysisCursor;
31  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.values.MoneyWiseXAnalysisAccountAttr;
32  import io.github.tonywasher.joceanus.moneywise.atlas.data.analysis.values.MoneyWiseXAnalysisAccountValues;
33  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseAssetBase;
34  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDeposit;
35  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
36  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
37  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
38  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
39  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
40  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusDecimal;
41  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
42  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRate;
43  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
44  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
45  
46  import java.util.Comparator;
47  import java.util.Currency;
48  import java.util.Iterator;
49  import java.util.List;
50  
51  /**
52   * The Account Bucket class.
53   *
54   * @param <T> the account data type
55   */
56  public abstract class MoneyWiseXAnalysisAccountBucket<T extends MoneyWiseAssetBase>
57          implements MetisFieldTableItem, MoneyWiseXAnalysisBucketForeign {
58      /**
59       * Default currency.
60       */
61      protected static final Currency DEFAULT_CURRENCY = OceanusMoney.getDefaultCurrency();
62  
63      /**
64       * Report fields.
65       */
66      @SuppressWarnings("rawtypes")
67      private static final MetisFieldSet<MoneyWiseXAnalysisAccountBucket> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisAccountBucket.class);
68  
69      /*
70       * Declare Fields.
71       */
72      static {
73          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_NAME, MoneyWiseXAnalysisAccountBucket::getAnalysis);
74          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.BUCKET_ACCOUNT, MoneyWiseXAnalysisAccountBucket::getAccount);
75          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.BUCKET_BASEVALUES, MoneyWiseXAnalysisAccountBucket::getBaseValues);
76          FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBaseResource.BUCKET_HISTORY, MoneyWiseXAnalysisAccountBucket::getHistoryMap);
77          FIELD_DEFS.declareLocalFieldsForEnum(MoneyWiseXAnalysisAccountAttr.class, MoneyWiseXAnalysisAccountBucket::getAttributeValue);
78      }
79  
80      /**
81       * Totals bucket name.
82       */
83      private static final String NAME_TOTALS = MoneyWiseXAnalysisBucketResource.ANALYSIS_TOTALS.getValue();
84  
85      /**
86       * The analysis.
87       */
88      private final MoneyWiseXAnalysisHolder theAnalysis;
89  
90      /**
91       * The account.
92       */
93      private final T theAccount;
94  
95      /**
96       * Is this a foreign currency?
97       */
98      private final boolean isForeignCurrency;
99  
100     /**
101      * Values.
102      */
103     private final MoneyWiseXAnalysisAccountValues theValues;
104 
105     /**
106      * The base values.
107      */
108     private final MoneyWiseXAnalysisAccountValues theBaseValues;
109 
110     /**
111      * History Map.
112      */
113     private final MoneyWiseXAnalysisHistory<MoneyWiseXAnalysisAccountValues, MoneyWiseXAnalysisAccountAttr> theHistory;
114 
115     /**
116      * Constructor.
117      *
118      * @param pAnalysis the analysis
119      * @param pAccount  the account
120      */
121     protected MoneyWiseXAnalysisAccountBucket(final MoneyWiseXAnalysisHolder pAnalysis,
122                                               final T pAccount) {
123         /* Store the details */
124         theAccount = pAccount;
125         theAnalysis = pAnalysis;
126 
127         /* Determine currency */
128         final MoneyWiseCurrency myReportingCurrency = pAnalysis.getCurrency();
129         final MoneyWiseCurrency myAccountCurrency = pAccount == null
130                 ? myReportingCurrency
131                 : pAccount.getAssetCurrency();
132 
133         /* Determine whether we are a foreign currency */
134         isForeignCurrency = !MetisDataDifference.isEqual(myReportingCurrency, myAccountCurrency);
135         final Currency myCurrency = deriveCurrency(myAccountCurrency);
136 
137         /* Create the history map */
138         final MoneyWiseXAnalysisAccountValues myValues = allocateValues(myCurrency);
139         theHistory = new MoneyWiseXAnalysisHistory<>(myValues);
140 
141         /* Access the key value maps */
142         theValues = theHistory.getValues();
143         theBaseValues = theHistory.getBaseValues();
144 
145         /* If this is a foreign currency account */
146         if (isForeignCurrency) {
147             /* Register for xchangeRate Updates */
148             theAnalysis.getCursor().registerForXchgRateUpdates(this);
149 
150             /* Record the exchangeRate and copy to base */
151             recordExchangeRate();
152             theBaseValues.setValue(MoneyWiseXAnalysisAccountAttr.EXCHANGERATE, getValue(MoneyWiseXAnalysisAccountAttr.EXCHANGERATE));
153 
154             /* Create a new reportedValuation */
155             final OceanusMoney myReported = new OceanusMoney(theAnalysis.getCurrency().getCurrency());
156             theValues.setValue(MoneyWiseXAnalysisAccountAttr.VALUATION, myReported);
157             theBaseValues.setValue(MoneyWiseXAnalysisAccountAttr.VALUATION, myReported);
158         }
159     }
160 
161     /**
162      * Constructor.
163      *
164      * @param pAnalysis the analysis
165      * @param pBase     the underlying bucket
166      */
167     protected MoneyWiseXAnalysisAccountBucket(final MoneyWiseXAnalysisHolder pAnalysis,
168                                               final MoneyWiseXAnalysisAccountBucket<T> pBase) {
169         /* Copy details from base */
170         theAccount = pBase.getAccount();
171         theAnalysis = pAnalysis;
172         isForeignCurrency = pBase.isForeignCurrency();
173 
174         /* Access the relevant history */
175         theHistory = new MoneyWiseXAnalysisHistory<>(pBase.getHistoryMap());
176 
177         /* Access the key value maps */
178         theValues = theHistory.getValues();
179         theBaseValues = theHistory.getBaseValues();
180     }
181 
182     /**
183      * Constructor.
184      *
185      * @param pAnalysis the analysis
186      * @param pBase     the underlying bucket
187      * @param pDate     the date for the bucket
188      */
189     protected MoneyWiseXAnalysisAccountBucket(final MoneyWiseXAnalysisHolder pAnalysis,
190                                               final MoneyWiseXAnalysisAccountBucket<T> pBase,
191                                               final OceanusDate pDate) {
192         /* Copy details from base */
193         theAccount = pBase.getAccount();
194         theAnalysis = pAnalysis;
195         isForeignCurrency = pBase.isForeignCurrency();
196 
197         /* Access the relevant history */
198         theHistory = new MoneyWiseXAnalysisHistory<>(pBase.getHistoryMap(), pDate);
199 
200         /* Access the key value maps */
201         theValues = theHistory.getValues();
202         theBaseValues = theHistory.getBaseValues();
203     }
204 
205     /**
206      * Constructor.
207      *
208      * @param pAnalysis the analysis
209      * @param pBase     the underlying bucket
210      * @param pRange    the range for the bucket
211      */
212     protected MoneyWiseXAnalysisAccountBucket(final MoneyWiseXAnalysisHolder pAnalysis,
213                                               final MoneyWiseXAnalysisAccountBucket<T> pBase,
214                                               final OceanusDateRange pRange) {
215         /* Copy details from base */
216         theAccount = pBase.getAccount();
217         theAnalysis = pAnalysis;
218         isForeignCurrency = pBase.isForeignCurrency();
219 
220         /* Access the relevant history */
221         theHistory = new MoneyWiseXAnalysisHistory<>(pBase.getHistoryMap(), pRange);
222 
223         /* Access the key value maps */
224         theValues = theHistory.getValues();
225         theBaseValues = theHistory.getBaseValues();
226     }
227 
228     @Override
229     public String formatObject(final OceanusDataFormatter pFormatter) {
230         return toString();
231     }
232 
233     @Override
234     public String toString() {
235         return getName() + " " + theValues;
236     }
237 
238     /**
239      * derive currency.
240      *
241      * @param pAssetCurrency the asset currency
242      * @return the actual currency to use
243      */
244     protected static Currency deriveCurrency(final MoneyWiseCurrency pAssetCurrency) {
245         return pAssetCurrency == null
246                 ? DEFAULT_CURRENCY
247                 : pAssetCurrency.getCurrency();
248     }
249 
250     @Override
251     public MoneyWiseCurrency getCurrency() {
252         return theAccount == null
253                 ? theAnalysis.getCurrency()
254                 : theAccount.getAssetCurrency();
255     }
256 
257     /**
258      * allocate standard values.
259      *
260      * @param pCurrency the asset currency
261      * @return the actual currency to use
262      */
263     protected MoneyWiseXAnalysisAccountValues allocateValues(final Currency pCurrency) {
264         return new MoneyWiseXAnalysisAccountValues(pCurrency);
265     }
266 
267     /**
268      * Obtain the name.
269      *
270      * @return the name
271      */
272     public String getName() {
273         return theAccount == null
274                 ? NAME_TOTALS
275                 : theAccount.getName();
276     }
277 
278     /**
279      * Obtain the account.
280      *
281      * @return the account
282      */
283     public T getAccount() {
284         return theAccount;
285     }
286 
287     /**
288      * Is this a foreign currency?
289      *
290      * @return true/false
291      */
292     public boolean isForeignCurrency() {
293         return isForeignCurrency;
294     }
295 
296     @Override
297     public Integer getIndexedId() {
298         return theAccount.getIndexedId();
299     }
300 
301     /**
302      * Is this bucket idle?
303      *
304      * @return true/false
305      */
306     public boolean isIdle() {
307         return theHistory.isIdle();
308     }
309 
310     /**
311      * Obtain the analysis.
312      *
313      * @return the analysis
314      */
315     protected MoneyWiseXAnalysisHolder getAnalysis() {
316         return theAnalysis;
317     }
318 
319     /**
320      * Obtain date range.
321      *
322      * @return the range
323      */
324     public OceanusDateRange getDateRange() {
325         return theAnalysis.getDateRange();
326     }
327 
328     /**
329      * Obtain the value map.
330      *
331      * @return the value map
332      */
333     public MoneyWiseXAnalysisAccountValues getValues() {
334         return theValues;
335     }
336 
337     /**
338      * Obtain the base value map.
339      *
340      * @return the base value map
341      */
342     public MoneyWiseXAnalysisAccountValues getBaseValues() {
343         return theBaseValues;
344     }
345 
346     /**
347      * Obtain values for event.
348      *
349      * @param pEvent the event
350      * @return the values (or null)
351      */
352     public MoneyWiseXAnalysisAccountValues getValuesForEvent(final MoneyWiseXAnalysisEvent pEvent) {
353         return theHistory.getValuesForEvent(pEvent);
354     }
355 
356     /**
357      * Obtain previous values for event.
358      *
359      * @param pEvent the event
360      * @return the values (or null)
361      */
362     public MoneyWiseXAnalysisAccountValues getPreviousValuesForEvent(final MoneyWiseXAnalysisEvent pEvent) {
363         return theHistory.getPreviousValuesForEvent(pEvent);
364     }
365 
366     /**
367      * Obtain delta for event.
368      *
369      * @param pEvent the event
370      * @param pAttr  the attribute
371      * @return the delta (or null)
372      */
373     public OceanusDecimal getDeltaForEvent(final MoneyWiseXAnalysisEvent pEvent,
374                                            final MoneyWiseXAnalysisAccountAttr pAttr) {
375         /* Obtain delta for transaction */
376         return theHistory.getDeltaValue(pEvent, pAttr);
377     }
378 
379     /**
380      * Obtain money delta for event.
381      *
382      * @param pEvent the event
383      * @param pAttr  the attribute
384      * @return the delta (or null)
385      */
386     public OceanusMoney getMoneyDeltaForEvent(final MoneyWiseXAnalysisEvent pEvent,
387                                               final MoneyWiseXAnalysisAccountAttr pAttr) {
388         /* Obtain delta for transaction */
389         return theHistory.getDeltaMoneyValue(pEvent, pAttr);
390     }
391 
392     /**
393      * Obtain the history map.
394      *
395      * @return the history map
396      */
397     private MoneyWiseXAnalysisHistory<MoneyWiseXAnalysisAccountValues, MoneyWiseXAnalysisAccountAttr> getHistoryMap() {
398         return theHistory;
399     }
400 
401     /**
402      * Set Value.
403      *
404      * @param pAttr  the attribute
405      * @param pValue the value of the attribute
406      */
407     protected void setValue(final MoneyWiseXAnalysisAccountAttr pAttr,
408                             final Object pValue) {
409         /* Set the value */
410         theValues.setValue(pAttr, pValue);
411     }
412 
413     /**
414      * Get an attribute value.
415      *
416      * @param pAttr the attribute
417      * @return the value to set
418      */
419     private Object getAttributeValue(final MoneyWiseXAnalysisAccountAttr pAttr) {
420         /* Access value of object */
421         final Object myValue = getValue(pAttr);
422 
423         /* Return the value */
424         return myValue != null
425                 ? myValue
426                 : MetisDataFieldValue.SKIP;
427     }
428 
429     /**
430      * Obtain an attribute value.
431      *
432      * @param pAttr the attribute
433      * @return the value of the attribute or null
434      */
435     private Object getValue(final MoneyWiseXAnalysisAccountAttr pAttr) {
436         /* Obtain the attribute value */
437         return theValues.getValue(pAttr);
438     }
439 
440     /**
441      * Record opening balance.
442      */
443     public void recordOpeningBalance() {
444         /* Obtain the base valuation */
445         final MoneyWiseXAnalysisAccountValues myValues = getBaseValues();
446         final OceanusMoney myBaseValue = myValues.getMoneyValue(MoneyWiseXAnalysisAccountAttr.BALANCE);
447 
448         /* Set the base value (this will set the current value as well) */
449         myBaseValue.addAmount(getAccount().getOpeningBalance());
450 
451         /* If this is a foreign currency */
452         if (isForeignCurrency) {
453             /* Access the current exchange Rate */
454             final MoneyWiseCurrency myCurrency = theAnalysis.getCurrency();
455             final OceanusRatio myRate = theValues.getRatioValue(MoneyWiseXAnalysisAccountAttr.EXCHANGERATE);
456 
457             /* Record the starting reporting balance and copy to base values */
458             final OceanusMoney myReport = myBaseValue.convertCurrency(myCurrency.getCurrency(), myRate);
459             theValues.setValue(MoneyWiseXAnalysisAccountAttr.VALUATION, myReport);
460             myValues.setValue(MoneyWiseXAnalysisAccountAttr.VALUATION, myReport);
461             myValues.setValue(MoneyWiseXAnalysisAccountAttr.EXCHANGERATE, myRate);
462         }
463     }
464 
465     /**
466      * Add to balance.
467      *
468      * @param pDelta the delta
469      */
470     public void addToBalance(final OceanusMoney pDelta) {
471         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisAccountAttr.BALANCE);
472         myValue = new OceanusMoney(myValue);
473         myValue.addAmount(pDelta);
474         setValue(MoneyWiseXAnalysisAccountAttr.BALANCE, myValue);
475     }
476 
477     /**
478      * Subtract from balance.
479      *
480      * @param pDelta the delta
481      */
482     public void subtractFromBalance(final OceanusMoney pDelta) {
483         OceanusMoney myValue = theValues.getMoneyValue(MoneyWiseXAnalysisAccountAttr.BALANCE);
484         myValue = new OceanusMoney(myValue);
485         myValue.subtractAmount(pDelta);
486         setValue(MoneyWiseXAnalysisAccountAttr.BALANCE, myValue);
487     }
488 
489     /**
490      * Set maturity.
491      */
492     public void recordMaturity() {
493         final OceanusDate myMaturity = ((MoneyWiseDeposit) getAccount()).getMaturity();
494         if (myMaturity != null) {
495             theValues.setValue(MoneyWiseXAnalysisAccountAttr.MATURITY, ((MoneyWiseDeposit) getAccount()).getMaturity());
496         }
497     }
498 
499     @Override
500     public void recordExchangeRate() {
501         final MoneyWiseXAnalysisCursor myCursor = theAnalysis.getCursor();
502         final OceanusRatio myRate = myCursor.getCurrentXchgRate(getAccount().getAssetCurrency());
503         theValues.setValue(MoneyWiseXAnalysisAccountAttr.EXCHANGERATE, myRate);
504     }
505 
506     /**
507      * Record depositRate.
508      */
509     public void recordDepositRate() {
510         final MoneyWiseXAnalysisCursor myCursor = theAnalysis.getCursor();
511         final OceanusRate myRate = myCursor.getCurrentDepositRate((MoneyWiseDeposit) getAccount());
512         theValues.setValue(MoneyWiseXAnalysisAccountAttr.DEPOSITRATE, myRate);
513     }
514 
515     @Override
516     public void adjustValuation() {
517         /* Determine reported balance */
518         OceanusMoney myBalance = theValues.getMoneyValue(MoneyWiseXAnalysisAccountAttr.BALANCE);
519         if (isForeignCurrency) {
520             final OceanusRatio myRate = theValues.getRatioValue(MoneyWiseXAnalysisAccountAttr.EXCHANGERATE);
521             myBalance = myBalance.convertCurrency(theAnalysis.getCurrency().getCurrency(), myRate);
522         }
523         theValues.setValue(MoneyWiseXAnalysisAccountAttr.VALUATION, myBalance);
524     }
525 
526     @Override
527     public OceanusMoney getDeltaValuation() {
528         /* Determine the delta */
529         final OceanusMoney myDelta = new OceanusMoney(theValues.getMoneyValue(MoneyWiseXAnalysisAccountAttr.VALUATION));
530         myDelta.subtractAmount(theHistory.getLastValues().getMoneyValue(MoneyWiseXAnalysisAccountAttr.VALUATION));
531         return myDelta;
532     }
533 
534     @Override
535     public void registerEvent(final MoneyWiseXAnalysisEvent pEvent) {
536         /* Make sure that valuation is correct */
537         adjustValuation();
538 
539         /* Register the transaction in the history */
540         theHistory.registerEvent(pEvent, theValues);
541     }
542 
543     /**
544      * Calculate delta.
545      */
546     protected void calculateDelta() {
547         /* Obtain a copy of the value */
548         OceanusMoney myDelta = theValues.getMoneyValue(MoneyWiseXAnalysisAccountAttr.VALUATION);
549         myDelta = new OceanusMoney(myDelta);
550 
551         /* Subtract any base value */
552         final OceanusMoney myBase = theBaseValues.getMoneyValue(MoneyWiseXAnalysisAccountAttr.VALUATION);
553         myDelta.subtractAmount(myBase);
554 
555         /* Set the delta */
556         setValue(MoneyWiseXAnalysisAccountAttr.VALUEDELTA, myDelta);
557 
558         /* Adjust to base values */
559         theValues.adjustToBaseValues(theBaseValues);
560         theBaseValues.resetBaseValues();
561     }
562 
563     /**
564      * Is the bucket active?
565      *
566      * @return true/false
567      */
568     public boolean isActive() {
569         return theValues.isActive();
570     }
571 
572     /**
573      * AccountBucket list class.
574      *
575      * @param <B> the account bucket data type
576      * @param <T> the account data type
577      */
578     public abstract static class MoneyWiseXAnalysisAccountBucketList<B extends MoneyWiseXAnalysisAccountBucket<T>, T extends MoneyWiseAssetBase>
579             implements MetisFieldItem, MetisDataList<B> {
580         /**
581          * Local Report fields.
582          */
583         @SuppressWarnings("rawtypes")
584         private static final MetisFieldSet<MoneyWiseXAnalysisAccountBucketList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseXAnalysisAccountBucketList.class);
585 
586         /*
587          * Field IDs.
588          */
589         static {
590             FIELD_DEFS.declareLocalField(MoneyWiseXAnalysisBucketResource.ANALYSIS_NAME, MoneyWiseXAnalysisAccountBucketList::getAnalysis);
591         }
592 
593         /**
594          * The analysis.
595          */
596         private final MoneyWiseXAnalysisHolder theAnalysis;
597 
598         /**
599          * The list.
600          */
601         private final MetisListIndexed<B> theList;
602 
603         /**
604          * Construct a top-level List.
605          *
606          * @param pAnalysis the analysis
607          */
608         protected MoneyWiseXAnalysisAccountBucketList(final MoneyWiseXAnalysisHolder pAnalysis) {
609             /* Initialise class */
610             theAnalysis = pAnalysis;
611             theList = new MetisListIndexed<>();
612             theList.setComparator(Comparator.comparing(MoneyWiseXAnalysisAccountBucket::getAccount));
613         }
614 
615         @Override
616         public List<B> getUnderlyingList() {
617             return theList.getUnderlyingList();
618         }
619 
620         @Override
621         public String formatObject(final OceanusDataFormatter pFormatter) {
622             return getDataFieldSet().getName();
623         }
624 
625         /**
626          * Obtain the analysis.
627          *
628          * @return the analysis
629          */
630         protected MoneyWiseXAnalysisHolder getAnalysis() {
631             return theAnalysis;
632         }
633 
634         /**
635          * Construct a view bucket.
636          *
637          * @param pBase the base bucket
638          * @return the new bucket
639          */
640         protected abstract B newBucket(B pBase);
641 
642         /**
643          * Construct a dated List.
644          *
645          * @param pBase the base list
646          * @param pDate the Date
647          */
648         protected void constructFromBase(final MoneyWiseXAnalysisAccountBucketList<B, T> pBase,
649                                          final OceanusDate pDate) {
650             /* Loop through the buckets */
651             final Iterator<B> myIterator = pBase.iterator();
652             while (myIterator.hasNext()) {
653                 final B myCurr = myIterator.next();
654 
655                 /* Access the bucket for this date */
656                 final B myBucket = newBucket(myCurr, pDate);
657 
658                 /* If the bucket is non-idle or active */
659                 if (myBucket.isActive() || !myBucket.isIdle()) {
660                     theList.add(myBucket);
661                 }
662             }
663         }
664 
665         /**
666          * Construct a dated bucket.
667          *
668          * @param pBase the base bucket
669          * @param pDate the Date
670          * @return the new bucket
671          */
672         protected abstract B newBucket(B pBase,
673                                        OceanusDate pDate);
674 
675         /**
676          * Construct a ranged List.
677          *
678          * @param pBase  the base list
679          * @param pRange the Date Range
680          */
681         protected void constructFromBase(final MoneyWiseXAnalysisAccountBucketList<B, T> pBase,
682                                          final OceanusDateRange pRange) {
683             /* Loop through the buckets */
684             final Iterator<B> myIterator = pBase.iterator();
685             while (myIterator.hasNext()) {
686                 final B myCurr = myIterator.next();
687 
688                 /* Access the bucket for this range */
689                 final B myBucket = newBucket(myCurr, pRange);
690 
691                 /* If the bucket is non-idle or active */
692                 if (myBucket.isActive() || !myBucket.isIdle()) {
693                     /* Add to the list */
694                     theList.add(myBucket);
695                 }
696             }
697         }
698 
699         /**
700          * Obtain item by id.
701          *
702          * @param pId the id to lookup
703          * @return the item (or null if not present)
704          */
705         public B findItemById(final Integer pId) {
706             /* Return results */
707             return theList.getItemById(pId);
708         }
709 
710         /**
711          * Construct a ranged bucket.
712          *
713          * @param pBase  the base bucket
714          * @param pRange the Range
715          * @return the new bucket
716          */
717         protected abstract B newBucket(B pBase,
718                                        OceanusDateRange pRange);
719 
720         /**
721          * Obtain the AccountBucket for a given account.
722          *
723          * @param pAccount the account
724          * @return the bucket
725          */
726         public B getBucket(final T pAccount) {
727             /* Locate the bucket in the list */
728             B myItem = findItemById(pAccount.getIndexedId());
729 
730             /* If the item does not yet exist */
731             if (myItem == null) {
732                 /* Create the new bucket */
733                 myItem = newBucket(pAccount);
734 
735                 /* Add to the list */
736                 theList.add(myItem);
737             }
738 
739             /* Return the bucket */
740             return myItem;
741         }
742 
743         /**
744          * Construct a standard bucket.
745          *
746          * @param pAccount the Account
747          * @return the new bucket
748          */
749         protected abstract B newBucket(T pAccount);
750 
751         /**
752          * SortBuckets.
753          */
754         protected void sortBuckets() {
755             theList.sortList();
756         }
757 
758         /**
759          * Mark active accounts.
760          *
761          * @throws OceanusException on error
762          */
763         public void markActiveAccounts() throws OceanusException {
764             /* Loop through the buckets */
765             final Iterator<B> myIterator = iterator();
766             while (myIterator.hasNext()) {
767                 final B myCurr = myIterator.next();
768                 final T myAccount = myCurr.getAccount();
769 
770                 /* If we are active */
771                 if (myCurr.isActive()) {
772                     /* Set the account as relevant */
773                     myAccount.setRelevant();
774                 }
775 
776                 /* If we are closed */
777                 if (Boolean.TRUE.equals(myAccount.isClosed())) {
778                     /* Ensure that we have correct closed/maturity dates */
779                     myAccount.adjustClosed();
780 
781                     /* If we are Relevant */
782                     if (myAccount.isRelevant()
783                             && theAnalysis.getData().checkClosedAccounts()) {
784                         /* throw exception */
785                         throw new MoneyWiseDataException(myCurr, "Illegally closed account");
786                     }
787                 }
788             }
789         }
790     }
791 }