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