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.MetisDataItem.MetisDataFieldId;
21  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
22  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
23  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
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.statics.MoneyWiseCurrency;
27  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency.MoneyWiseCurrencyList;
28  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticResource;
29  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
30  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
31  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
32  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateFormatter;
33  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
34  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
35  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
36  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
37  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInstanceMap;
38  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
39  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataList;
40  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataMapItem;
41  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataValues;
42  
43  import java.util.ArrayList;
44  import java.util.Currency;
45  import java.util.HashMap;
46  import java.util.Iterator;
47  import java.util.List;
48  import java.util.ListIterator;
49  import java.util.Map;
50  
51  /**
52   * ExchangeRate class.
53   */
54  public class MoneyWiseExchangeRate
55          extends PrometheusDataItem {
56      /**
57       * Object name.
58       */
59      public static final String OBJECT_NAME = MoneyWiseBasicDataType.EXCHANGERATE.getItemName();
60  
61      /**
62       * List name.
63       */
64      public static final String LIST_NAME = MoneyWiseBasicDataType.EXCHANGERATE.getListName();
65  
66      /**
67       * Local Report fields.
68       */
69      private static final MetisFieldVersionedSet<MoneyWiseExchangeRate> FIELD_DEFS = MetisFieldVersionedSet.newVersionedFieldSet(MoneyWiseExchangeRate.class);
70  
71      /*
72       * FieldIds.
73       */
74      static {
75          FIELD_DEFS.declareDateField(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
76          FIELD_DEFS.declareLinkField(MoneyWiseBasicResource.XCHGRATE_FROM);
77          FIELD_DEFS.declareLinkField(MoneyWiseBasicResource.XCHGRATE_TO);
78          FIELD_DEFS.declareRatioField(MoneyWiseBasicResource.XCHGRATE_RATE);
79      }
80  
81      /**
82       * Circular Rate Error.
83       */
84      public static final String ERROR_CIRCLE = MoneyWiseBasicResource.XCHGRATE_ERROR_CIRCLE.getValue();
85  
86      /**
87       * Default Rate Error.
88       */
89      public static final String ERROR_DEF = MoneyWiseBasicResource.XCHGRATE_ERROR_DEFAULT.getValue();
90  
91      /**
92       * Copy Constructor.
93       *
94       * @param pList the list
95       * @param pRate The Rate to copy
96       */
97      protected MoneyWiseExchangeRate(final MoneyWiseExchangeRateBaseList<? extends MoneyWiseExchangeRate> pList,
98                                      final MoneyWiseExchangeRate pRate) {
99          /* Set standard values */
100         super(pList, pRate);
101     }
102 
103     /**
104      * Edit Constructor.
105      *
106      * @param pList the list
107      */
108     public MoneyWiseExchangeRate(final MoneyWiseExchangeRateBaseList<? extends MoneyWiseExchangeRate> pList) {
109         super(pList, 0);
110     }
111 
112     /**
113      * Values constructor.
114      *
115      * @param pList   the List to add to
116      * @param pValues the values constructor
117      * @throws OceanusException on error
118      */
119     private MoneyWiseExchangeRate(final MoneyWiseExchangeRateList pList,
120                                   final PrometheusDataValues pValues) throws OceanusException {
121         /* Initialise the item */
122         super(pList, pValues);
123 
124         /* Access formatter */
125         final OceanusDataFormatter myFormatter = getDataSet().getDataFormatter();
126 
127         /* Protect against exceptions */
128         try {
129             /* Store the Date */
130             Object myValue = pValues.getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE);
131             if (myValue instanceof OceanusDate d) {
132                 setValueDate(d);
133             } else if (myValue instanceof String s) {
134                 final OceanusDateFormatter myParser = myFormatter.getDateFormatter();
135                 setValueDate(myParser.parseDate(s));
136             }
137 
138             /* Store the From currency */
139             myValue = pValues.getValue(MoneyWiseBasicResource.XCHGRATE_FROM);
140             if (myValue instanceof Integer i) {
141                 setValueFromCurrency(i);
142             } else if (myValue instanceof String s) {
143                 setValueFromCurrency(s);
144             }
145 
146             /* Store the To currency */
147             myValue = pValues.getValue(MoneyWiseBasicResource.XCHGRATE_TO);
148             if (myValue instanceof Integer i) {
149                 setValueToCurrency(i);
150             } else if (myValue instanceof String s) {
151                 setValueToCurrency(s);
152             }
153 
154             /* Store the Rate */
155             myValue = pValues.getValue(MoneyWiseBasicResource.XCHGRATE_RATE);
156             if (myValue instanceof OceanusRatio r) {
157                 setValueExchangeRate(r);
158             } else if (myValue instanceof String myString) {
159                 setValueExchangeRate(myString);
160                 setValueExchangeRate(myFormatter.parseValue(myString, OceanusRatio.class));
161             }
162 
163             /* Catch Exceptions */
164         } catch (IllegalArgumentException e) {
165             /* Pass on exception */
166             throw new MoneyWiseDataException(this, ERROR_CREATEITEM, e);
167         }
168     }
169 
170     /**
171      * Edit Constructor.
172      *
173      * @param pList the list
174      */
175     public MoneyWiseExchangeRate(final MoneyWiseExchangeRateList pList) {
176         super(pList, 0);
177     }
178 
179     @Override
180     public MetisFieldSetDef getDataFieldSet() {
181         return FIELD_DEFS;
182     }
183 
184     @Override
185     public String formatObject(final OceanusDataFormatter pFormatter) {
186         return toString();
187     }
188 
189     @Override
190     public String toString() {
191         /* Access formatter */
192         final OceanusDataFormatter myFormatter = getDataSet().getDataFormatter();
193 
194         /* Create string builder */
195         final StringBuilder myBuilder = new StringBuilder();
196         myBuilder.append(myFormatter.formatObject(getDate()));
197         myBuilder.append(" ");
198         myBuilder.append(myFormatter.formatObject(getFromCurrency().getCurrency().getCurrencyCode()));
199         myBuilder.append(": ");
200         myBuilder.append(myFormatter.formatObject(getToCurrency().getCurrency().getCurrencyCode()));
201         myBuilder.append('=');
202         myBuilder.append(myFormatter.formatObject(getExchangeRate()));
203         return myBuilder.toString();
204     }
205 
206     @Override
207     public boolean includeXmlField(final MetisDataFieldId pField) {
208         /* Determine whether fields should be included */
209         if (MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE.equals(pField)) {
210             return true;
211         }
212         if (MoneyWiseBasicResource.XCHGRATE_FROM.equals(pField)) {
213             return true;
214         }
215         if (MoneyWiseBasicResource.XCHGRATE_TO.equals(pField)) {
216             return true;
217         }
218         if (MoneyWiseBasicResource.XCHGRATE_RATE.equals(pField)) {
219             return true;
220         }
221 
222         /* Pass call on */
223         return super.includeXmlField(pField);
224     }
225 
226     /**
227      * Obtain Date.
228      *
229      * @return the name
230      */
231     public OceanusDate getDate() {
232         return getValues().getValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE, OceanusDate.class);
233     }
234 
235     /**
236      * Obtain From currency.
237      *
238      * @return the currency
239      */
240     public MoneyWiseCurrency getFromCurrency() {
241         return getValues().getValue(MoneyWiseBasicResource.XCHGRATE_FROM, MoneyWiseCurrency.class);
242     }
243 
244     /**
245      * Obtain fromCurrencyId.
246      *
247      * @return the fromCurrencyId
248      */
249     public Integer getFromCurrencyId() {
250         final MoneyWiseCurrency myCurr = getFromCurrency();
251         return myCurr == null
252                 ? null
253                 : myCurr.getIndexedId();
254     }
255 
256     /**
257      * Obtain FromCurrencyName.
258      *
259      * @return the fromCurrencyName
260      */
261     public String getFromCurrencyName() {
262         final MoneyWiseCurrency myCurr = getFromCurrency();
263         return myCurr == null
264                 ? null
265                 : myCurr.getName();
266     }
267 
268     /**
269      * Obtain To currency.
270      *
271      * @return the currency
272      */
273     public MoneyWiseCurrency getToCurrency() {
274         return getValues().getValue(MoneyWiseBasicResource.XCHGRATE_TO, MoneyWiseCurrency.class);
275     }
276 
277     /**
278      * Obtain toCurrencyId.
279      *
280      * @return the toCurrencyId
281      */
282     public Integer getToCurrencyId() {
283         final MoneyWiseCurrency myCurr = getToCurrency();
284         return myCurr == null
285                 ? null
286                 : myCurr.getIndexedId();
287     }
288 
289     /**
290      * Obtain ToCurrencyName.
291      *
292      * @return the toCurrencyName
293      */
294     public String getToCurrencyName() {
295         final MoneyWiseCurrency myCurr = getToCurrency();
296         return myCurr == null
297                 ? null
298                 : myCurr.getName();
299     }
300 
301     /**
302      * Obtain ExchangeRate.
303      *
304      * @return the rate
305      */
306     public OceanusRatio getExchangeRate() {
307         return getValues().getValue(MoneyWiseBasicResource.XCHGRATE_RATE, OceanusRatio.class);
308     }
309 
310     /**
311      * Obtain InverseRate.
312      *
313      * @return the inverse rate
314      */
315     public OceanusRatio getInverseRate() {
316         final OceanusRatio myRate = getExchangeRate();
317         return myRate == null
318                 ? null
319                 : myRate.getInverseRatio();
320     }
321 
322     /**
323      * Set date value.
324      *
325      * @param pValue the value
326      */
327     private void setValueDate(final OceanusDate pValue) {
328         getValues().setUncheckedValue(MoneyWiseBasicResource.MONEYWISEDATA_FIELD_DATE, pValue);
329     }
330 
331     /**
332      * Set from currency value.
333      *
334      * @param pValue the value
335      */
336     private void setValueFromCurrency(final MoneyWiseCurrency pValue) {
337         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_FROM, pValue);
338     }
339 
340     /**
341      * Set from currency value.
342      *
343      * @param pValue the value
344      */
345     private void setValueFromCurrency(final Integer pValue) {
346         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_FROM, pValue);
347     }
348 
349     /**
350      * Set from currency value.
351      *
352      * @param pValue the value
353      */
354     private void setValueFromCurrency(final String pValue) {
355         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_FROM, pValue);
356     }
357 
358     /**
359      * Set to currency value.
360      *
361      * @param pValue the value
362      */
363     private void setValueToCurrency(final MoneyWiseCurrency pValue) {
364         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_TO, pValue);
365     }
366 
367     /**
368      * Set to currency value.
369      *
370      * @param pValue the value
371      */
372     private void setValueToCurrency(final Integer pValue) {
373         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_TO, pValue);
374     }
375 
376     /**
377      * Set to currency value.
378      *
379      * @param pValue the value
380      */
381     private void setValueToCurrency(final String pValue) {
382         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_TO, pValue);
383     }
384 
385     /**
386      * Set exchange rate value.
387      *
388      * @param pValue the value
389      */
390     protected void setValueExchangeRate(final OceanusRatio pValue) {
391         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_RATE, pValue);
392     }
393 
394     /**
395      * Set exchange rate value.
396      *
397      * @param pValue the value
398      */
399     private void setValueExchangeRate(final String pValue) {
400         getValues().setUncheckedValue(MoneyWiseBasicResource.XCHGRATE_RATE, pValue);
401     }
402 
403     @Override
404     public MoneyWiseDataSet getDataSet() {
405         return (MoneyWiseDataSet) super.getDataSet();
406     }
407 
408     @Override
409     public MoneyWiseExchangeRate getBase() {
410         return (MoneyWiseExchangeRate) super.getBase();
411     }
412 
413     @Override
414     @SuppressWarnings("unchecked")
415     public MoneyWiseExchangeRateBaseList<? extends MoneyWiseExchangeRate> getList() {
416         return (MoneyWiseExchangeRateBaseList<? extends MoneyWiseExchangeRate>) super.getList();
417     }
418 
419     @Override
420     public int compareValues(final PrometheusDataItem pThat) {
421         /* Access as ExchangeRate */
422         final MoneyWiseExchangeRate myThat = (MoneyWiseExchangeRate) pThat;
423 
424         /* If the date differs */
425         int iDiff = MetisDataDifference.compareObject(getDate(), myThat.getDate());
426         if (iDiff != 0) {
427             /* Sort in reverse date order !! */
428             return -iDiff;
429         }
430 
431         /* Compare From Currency */
432         iDiff = MetisDataDifference.compareObject(getFromCurrency(), myThat.getFromCurrency());
433         if (iDiff != 0) {
434             return iDiff;
435         }
436 
437         /* Compare the toCurrency */
438         return getToCurrency().compareTo(myThat.getToCurrency());
439     }
440 
441     @Override
442     public void resolveDataSetLinks() throws OceanusException {
443         /* Update the Encryption details */
444         super.resolveDataSetLinks();
445 
446         /* Resolve currencies */
447         final MoneyWiseDataSet myData = getDataSet();
448         final MoneyWiseCurrencyList myCurrencies = myData.getAccountCurrencies();
449         resolveDataLink(MoneyWiseBasicResource.XCHGRATE_FROM, myCurrencies);
450         resolveDataLink(MoneyWiseBasicResource.XCHGRATE_TO, myCurrencies);
451     }
452 
453     /**
454      * Set a new date.
455      *
456      * @param pDate the new date
457      */
458     public void setDate(final OceanusDate pDate) {
459         setValueDate(pDate);
460     }
461 
462     /**
463      * Set a new from currency.
464      *
465      * @param pCurrency the new from currency
466      */
467     public void setFromCurrency(final MoneyWiseCurrency pCurrency) {
468         setValueFromCurrency(pCurrency);
469     }
470 
471     /**
472      * Set a new to currency.
473      *
474      * @param pCurrency the new to currency
475      */
476     public void setToCurrency(final MoneyWiseCurrency pCurrency) {
477         setValueToCurrency(pCurrency);
478     }
479 
480     /**
481      * Set a new exchange rate.
482      *
483      * @param pRate the new rate
484      */
485     public void setExchangeRate(final OceanusRatio pRate) {
486         setValueExchangeRate(pRate);
487     }
488 
489     @Override
490     public void touchUnderlyingItems() {
491         /* touch the currencies referred to */
492         getFromCurrency().touchItem(this);
493         getToCurrency().touchItem(this);
494     }
495 
496     /**
497      * Update base rate from an edited rate.
498      *
499      * @param pRate the edited rate
500      * @return whether changes have been made
501      */
502     @Override
503     public boolean applyChanges(final PrometheusDataItem pRate) {
504         /* Can only update from an event exchange rate */
505         if (!(pRate instanceof MoneyWiseExchangeRate myRate)) {
506             return false;
507         }
508 
509         /* Store the current detail into history */
510         pushHistory();
511 
512         /* Update the Date if required */
513         if (!MetisDataDifference.isEqual(getDate(), myRate.getDate())) {
514             setValueDate(myRate.getDate());
515         }
516 
517         /* Update the from currency if required */
518         if (!MetisDataDifference.isEqual(getFromCurrency(), myRate.getFromCurrency())) {
519             setValueFromCurrency(myRate.getFromCurrency());
520         }
521 
522         /* Update the to currency if required */
523         if (!MetisDataDifference.isEqual(getToCurrency(), myRate.getToCurrency())) {
524             setValueToCurrency(myRate.getToCurrency());
525         }
526 
527         /* Update the rate if required */
528         if (!MetisDataDifference.isEqual(getExchangeRate(), myRate.getExchangeRate())) {
529             setValueExchangeRate(myRate.getExchangeRate());
530         }
531 
532         /* Check for changes */
533         return checkForHistory();
534     }
535 
536     @Override
537     public void adjustMapForItem() {
538         final MoneyWiseExchangeRateBaseList<? extends MoneyWiseExchangeRate> myList = getList();
539         final MoneyWiseExchangeRateDataMap myMap = myList.getDataMap();
540         myMap.adjustForItem(this);
541     }
542 
543     /**
544      * Price List.
545      *
546      * @param <T> the data type
547      */
548     public abstract static class MoneyWiseExchangeRateBaseList<T extends MoneyWiseExchangeRate>
549             extends PrometheusDataList<T> {
550         /*
551          * Report fields.
552          */
553         static {
554             MetisFieldSet.newFieldSet(MoneyWiseExchangeRateBaseList.class);
555         }
556 
557         /**
558          * Construct an empty CORE Price list.
559          *
560          * @param pData     the DataSet for the list
561          * @param pClass    the class of the item
562          * @param pItemType the item type
563          */
564         protected MoneyWiseExchangeRateBaseList(final MoneyWiseDataSet pData,
565                                                 final Class<T> pClass,
566                                                 final MoneyWiseBasicDataType pItemType) {
567             /* Call super-constructor */
568             super(pClass, pData, pItemType, PrometheusListStyle.CORE);
569         }
570 
571         /**
572          * Constructor for a cloned List.
573          *
574          * @param pSource the source List
575          */
576         protected MoneyWiseExchangeRateBaseList(final MoneyWiseExchangeRateBaseList<T> pSource) {
577             /* Call super-constructor */
578             super(pSource);
579         }
580 
581         @Override
582         public MoneyWiseExchangeRateDataMap getDataMap() {
583             return (MoneyWiseExchangeRateDataMap) super.getDataMap();
584         }
585 
586         @Override
587         protected MoneyWiseExchangeRateDataMap allocateDataMap() {
588             return new MoneyWiseExchangeRateDataMap();
589         }
590     }
591 
592     /**
593      * The ExchangeRate List class.
594      */
595     public static class MoneyWiseExchangeRateList
596             extends MoneyWiseExchangeRateBaseList<MoneyWiseExchangeRate> {
597         /**
598          * Report fields.
599          */
600         private static final MetisFieldSet<MoneyWiseExchangeRateList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseExchangeRateList.class);
601 
602         /*
603          * Declare Fields.
604          */
605         static {
606             FIELD_DEFS.declareLocalField(MoneyWiseStaticResource.CURRENCY_REPORTING, MoneyWiseExchangeRateList::getReportingCurrency);
607         }
608 
609         /**
610          * The reporting currency.
611          */
612         private MoneyWiseCurrency theReporting;
613 
614         /**
615          * Construct an empty CORE ExchangeRate list.
616          *
617          * @param pData the DataSet for the list
618          */
619         protected MoneyWiseExchangeRateList(final MoneyWiseDataSet pData) {
620             super(pData, MoneyWiseExchangeRate.class, MoneyWiseBasicDataType.EXCHANGERATE);
621         }
622 
623         /**
624          * Constructor for a cloned List.
625          *
626          * @param pSource the source List
627          */
628         protected MoneyWiseExchangeRateList(final MoneyWiseExchangeRateList pSource) {
629             super(pSource);
630         }
631 
632         @Override
633         public MetisFieldSet<MoneyWiseExchangeRateList> getDataFieldSet() {
634             return FIELD_DEFS;
635         }
636 
637         /**
638          * Obtain reporting currency.
639          *
640          * @return the reporting currency
641          */
642         public MoneyWiseCurrency getReportingCurrency() {
643             return theReporting;
644         }
645 
646         @Override
647         public String listName() {
648             return LIST_NAME;
649         }
650 
651         @Override
652         public MetisFieldSetDef getItemFields() {
653             return MoneyWiseExchangeRate.FIELD_DEFS;
654         }
655 
656         @Override
657         public MoneyWiseDataSet getDataSet() {
658             return (MoneyWiseDataSet) super.getDataSet();
659         }
660 
661         @Override
662         protected MoneyWiseExchangeRateList getEmptyList(final PrometheusListStyle pStyle) {
663             final MoneyWiseExchangeRateList myList = new MoneyWiseExchangeRateList(this);
664             myList.setStyle(pStyle);
665             return myList;
666         }
667 
668         /**
669          * Add a new item to the core list.
670          *
671          * @param pRate item
672          * @return the newly added item
673          */
674         @Override
675         public MoneyWiseExchangeRate addCopyItem(final PrometheusDataItem pRate) {
676             /* Can only clone an ExchangeRate */
677             if (!(pRate instanceof MoneyWiseExchangeRate)) {
678                 throw new UnsupportedOperationException();
679             }
680 
681             final MoneyWiseExchangeRate myRate = new MoneyWiseExchangeRate(this, (MoneyWiseExchangeRate) pRate);
682             add(myRate);
683             return myRate;
684         }
685 
686         /**
687          * Add a new item to the edit list.
688          *
689          * @return the new item
690          */
691         @Override
692         public MoneyWiseExchangeRate addNewItem() {
693             final MoneyWiseExchangeRate myRate = new MoneyWiseExchangeRate(this);
694             add(myRate);
695             return myRate;
696         }
697 
698         @Override
699         public MoneyWiseExchangeRate addValuesItem(final PrometheusDataValues pValues)
700                 throws OceanusException {
701             /* Create the rate */
702             final MoneyWiseExchangeRate myRate = new MoneyWiseExchangeRate(this, pValues);
703 
704             /* Check that this RateId has not been previously added */
705             if (!isIdUnique(myRate.getIndexedId())) {
706                 myRate.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
707                 throw new MoneyWiseDataException(myRate, ERROR_VALIDATION);
708             }
709 
710             /* Add to the list */
711             add(myRate);
712 
713             /* Return it */
714             return myRate;
715         }
716 
717         /**
718          * Convert a monetary value to the currency.
719          *
720          * @param pValue    the value to convert
721          * @param pCurrency the required currency
722          * @param pDate     the date of the conversion
723          * @return the converted value
724          */
725         public OceanusMoney convertCurrency(final OceanusMoney pValue,
726                                             final MoneyWiseCurrency pCurrency,
727                                             final OceanusDate pDate) {
728             /* Obtain the existing currency */
729             OceanusMoney myValue = pValue;
730             final MoneyWiseCurrencyList myCurrencies = getDataSet().getAccountCurrencies();
731             final Currency myCurrent = pValue.getCurrency();
732             final Currency myReporting = theReporting.getCurrency();
733             final Currency myTarget = pCurrency.getCurrency();
734 
735             /* Handle no conversion required */
736             if (myCurrent.equals(myTarget)) {
737                 return pValue;
738             }
739 
740             /* If the value is not already the reporting currency */
741             if (!myCurrent.equals(myReporting)) {
742                 /* Find the required exchange rate */
743                 final OceanusRatio myRate = findRate(myCurrencies.findCurrency(myCurrent), pDate);
744 
745                 /* Convert the currency */
746                 myValue = myValue.convertCurrency(myReporting, myRate);
747             }
748 
749             /* If we need to convert to a non-default currency */
750             if (!myReporting.equals(myTarget)) {
751                 /* Find the required exchange rate */
752                 final OceanusRatio myRate = findRate(pCurrency, pDate);
753 
754                 /* Convert the currency */
755                 myValue = myValue.convertCurrency(myTarget, myRate);
756             }
757 
758             /* Return the converted currency */
759             return myValue;
760         }
761 
762         /**
763          * Find the exchange rate.
764          *
765          * @param pCurrency the currency to find
766          * @param pDate     the date to find the exchange rate for
767          * @return the exchange rate
768          */
769         private OceanusRatio findRate(final MoneyWiseCurrency pCurrency,
770                                       final OceanusDate pDate) {
771             /* pass call to data map */
772             return getDataMap().getRateForDate(pCurrency, pDate);
773         }
774 
775         /**
776          * Set the default currency.
777          *
778          * @param pCurrency the new default currency
779          */
780         public void setReportingCurrency(final MoneyWiseCurrency pCurrency) {
781             /* Access the iterator */
782             final Iterator<MoneyWiseExchangeRate> myIterator = iterator();
783             OceanusRatio myCurrRate = null;
784             OceanusDate myCurrDate = null;
785 
786             /* Loop through the items to find the entry */
787             while (myIterator.hasNext()) {
788                 final MoneyWiseExchangeRate myCurr = myIterator.next();
789 
790                 /* Access details */
791                 final OceanusDate myDate = myCurr.getDate();
792                 final MoneyWiseCurrency myTo = myCurr.getToCurrency();
793                 final OceanusRatio myRatio = myCurr.getExchangeRate();
794 
795                 /* If this is a new date */
796                 if (myCurrDate == null || !myDate.equals(myCurrDate)) {
797                     /* Access the current rate for the new default currency */
798                     /*
799                      * TODO This must exist on the same date or else the currency cannot be set as
800                      * default
801                      */
802                     myCurrRate = findRate(pCurrency, myDate);
803                     myCurrDate = myDate;
804                 }
805 
806                 /* Update the item */
807                 myCurr.pushHistory();
808 
809                 /* If this is a conversion to the new default */
810                 if (myTo.equals(pCurrency)) {
811                     /* Switch the direction of the currencies */
812                     myCurr.setToCurrency(myCurr.getFromCurrency());
813 
814                     /* Invert the ratio */
815                     myCurr.setExchangeRate(myRatio.getInverseRatio());
816 
817                     /* Else does not currently involve the new currency */
818                 } else {
819                     /* Need to combine the rates */
820                     myCurr.setExchangeRate(new OceanusRatio(myRatio, myCurrRate));
821                 }
822 
823                 /* Set from currency */
824                 myCurr.setFromCurrency(pCurrency);
825             }
826 
827             /* Set the new reporting currency */
828             theReporting = pCurrency;
829         }
830     }
831 
832     /**
833      * The dataMap class.
834      */
835     public static class MoneyWiseExchangeRateDataMap
836             implements PrometheusDataMapItem, MetisFieldItem {
837         /**
838          * Report fields.
839          */
840         @SuppressWarnings("rawtypes")
841         private static final MetisFieldSet<MoneyWiseExchangeRateDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseExchangeRateDataMap.class);
842 
843         /*
844          * UnderlyingMap Field Id.
845          */
846         static {
847             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_MAPOFMAPS, MoneyWiseExchangeRateDataMap::getMapOfMaps);
848             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.XCHGRATE_MAP_MAPOFRATES, MoneyWiseExchangeRateDataMap::getMapOfRates);
849         }
850 
851         /**
852          * Map of Maps.
853          */
854         private final Map<MoneyWiseCurrency, Map<OceanusDate, Integer>> theMapOfMaps;
855 
856         /**
857          * Map of Rates.
858          */
859         private final Map<MoneyWiseCurrency, MoneyWiseRateList> theMapOfRates;
860 
861         /**
862          * Constructor.
863          */
864         public MoneyWiseExchangeRateDataMap() {
865             /* Create the maps */
866             theMapOfMaps = new HashMap<>();
867             theMapOfRates = new HashMap<>();
868         }
869 
870         @SuppressWarnings("rawtypes")
871         @Override
872         public MetisFieldSet<MoneyWiseExchangeRateDataMap> getDataFieldSet() {
873             return FIELD_DEFS;
874         }
875 
876         @Override
877         public String formatObject(final OceanusDataFormatter pFormatter) {
878             return FIELD_DEFS.getName();
879         }
880 
881         /**
882          * Obtain mapOfMaps.
883          *
884          * @return the map
885          */
886         private Map<MoneyWiseCurrency, Map<OceanusDate, Integer>> getMapOfMaps() {
887             return theMapOfMaps;
888         }
889 
890         /**
891          * Obtain mapOfRates.
892          *
893          * @return the map
894          */
895         private Map<MoneyWiseCurrency, MoneyWiseRateList> getMapOfRates() {
896             return theMapOfRates;
897         }
898 
899         @Override
900         public void resetMap() {
901             theMapOfMaps.clear();
902             theMapOfRates.clear();
903         }
904 
905         @Override
906         public void adjustForItem(final PrometheusDataItem pItem) {
907             /* Access the Currency Id */
908             final MoneyWiseExchangeRate myItem = (MoneyWiseExchangeRate) pItem;
909             final MoneyWiseCurrency myCurrency = myItem.getToCurrency();
910             if (myCurrency == null) {
911                 return;
912             }
913 
914             /* Access the map */
915             final Map<OceanusDate, Integer> myMap = theMapOfMaps.computeIfAbsent(myCurrency, c -> new HashMap<>());
916 
917             /* Adjust rate count */
918             final OceanusDate myDate = myItem.getDate();
919             final Integer myCount = myMap.get(myDate);
920             if (myCount == null) {
921                 myMap.put(myDate, PrometheusDataInstanceMap.ONE);
922             } else {
923                 myMap.put(myDate, myCount + 1);
924             }
925 
926             /* Access the list */
927             final MoneyWiseRateList myList = theMapOfRates.computeIfAbsent(myCurrency, MoneyWiseRateList::new);
928 
929             /* Add element to the list */
930             myList.add(myItem);
931         }
932 
933         /**
934          * Check validity of Rate.
935          *
936          * @param pItem the rate
937          * @return true/false
938          */
939         public boolean validRateCount(final MoneyWiseExchangeRate pItem) {
940             /* Access the Details */
941             final MoneyWiseCurrency myCurrency = pItem.getToCurrency();
942             final OceanusDate myDate = pItem.getDate();
943 
944             /* Access the map */
945             final Map<OceanusDate, Integer> myMap = theMapOfMaps.get(myCurrency);
946             if (myMap != null) {
947                 final Integer myResult = myMap.get(myDate);
948                 return PrometheusDataInstanceMap.ONE.equals(myResult);
949             }
950             return false;
951         }
952 
953         /**
954          * Check availability of date for a currency.
955          *
956          * @param pCurrency the currency
957          * @param pDate     the key to look up
958          * @return true/false
959          */
960         public boolean availableDate(final MoneyWiseCurrency pCurrency,
961                                      final OceanusDate pDate) {
962             /* Access the map */
963             final Map<OceanusDate, Integer> myMap = theMapOfMaps.get(pCurrency);
964             return myMap == null
965                     || myMap.get(pDate) == null;
966         }
967 
968         /**
969          * Obtain rate for date.
970          *
971          * @param pCurrency the currency
972          * @param pDate     the date
973          * @return the latest rate for the date.
974          */
975         public OceanusRatio getRateForDate(final MoneyWiseCurrency pCurrency,
976                                            final OceanusDate pDate) {
977             /* Access list for currency */
978             final MoneyWiseRateList myList = theMapOfRates.get(pCurrency);
979             if (myList != null) {
980                 /* Loop through the rates */
981                 final Iterator<MoneyWiseExchangeRate> myIterator = myList.iterator();
982                 while (myIterator.hasNext()) {
983                     final MoneyWiseExchangeRate myCurr = myIterator.next();
984 
985                     /* Access the date */
986                     final OceanusDate myDate = myCurr.getDate();
987 
988                     /* break loop if we have the correct record */
989                     if (myDate.compareTo(pDate) >= 0) {
990                         return myCurr.getExchangeRate();
991                     }
992                 }
993             }
994 
995             /* return null */
996             return null;
997         }
998 
999         /**
1000          * Obtain rates for range.
1001          *
1002          * @param pCurrency the currency
1003          * @param pRange    the date range
1004          * @return the two deep array of rates for the range.
1005          */
1006         public OceanusRatio[] getRatesForRange(final MoneyWiseCurrency pCurrency,
1007                                                final OceanusDateRange pRange) {
1008             /* Set rate */
1009             OceanusRatio myFirst = OceanusRatio.ONE;
1010             OceanusRatio myLatest = OceanusRatio.ONE;
1011             final OceanusDate myStart = pRange.getStart();
1012 
1013             /* Access list for security */
1014             final MoneyWiseRateList myList = theMapOfRates.get(pCurrency);
1015             if (myList != null) {
1016                 /* Loop through the rates */
1017                 final ListIterator<MoneyWiseExchangeRate> myIterator = myList.listIterator(myList.size());
1018                 while (myIterator.hasPrevious()) {
1019                     final MoneyWiseExchangeRate myCurr = myIterator.previous();
1020 
1021                     /* Check for the range of the date */
1022                     final OceanusDate myDate = myCurr.getDate();
1023                     final int iComp = pRange.compareToDate(myDate);
1024 
1025                     /* If this is later than the range we are finished */
1026                     if (iComp < 0) {
1027                         break;
1028                     }
1029 
1030                     /* Record as best rate */
1031                     myLatest = myCurr.getExchangeRate();
1032 
1033                     /* Adjust first rate */
1034                     if (iComp > 0
1035                             || myDate.compareTo(myStart) == 0) {
1036                         myFirst = myLatest;
1037                     }
1038                 }
1039             }
1040 
1041             /* Return the rates */
1042             return new OceanusRatio[]
1043                     {myFirst, myLatest};
1044         }
1045 
1046         /**
1047          * Obtain rateList cursor.
1048          *
1049          * @param pCurrency the currency
1050          * @return the latest rate for the date.
1051          */
1052         public ListIterator<MoneyWiseExchangeRate> rateIterator(final MoneyWiseCurrency pCurrency) {
1053             /* Access list for currency */
1054             final MoneyWiseRateList myList = theMapOfRates.get(pCurrency);
1055             return myList != null
1056                     ? myList.listIterator(myList.size())
1057                     : null;
1058         }
1059 
1060         /**
1061          * Rate List class.
1062          */
1063         private static final class MoneyWiseRateList
1064                 implements MetisFieldItem, MetisDataList<MoneyWiseExchangeRate> {
1065             /**
1066              * Report fields.
1067              */
1068             private static final MetisFieldSet<MoneyWiseRateList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseRateList.class);
1069 
1070             /*
1071              * UnderlyingMap Field Id.
1072              */
1073             static {
1074                 FIELD_DEFS.declareLocalField(MetisDataResource.LIST_SIZE, MoneyWiseRateList::size);
1075             }
1076 
1077             /**
1078              * The list.
1079              */
1080             private final List<MoneyWiseExchangeRate> theList;
1081 
1082             /**
1083              * The currency.
1084              */
1085             private final MoneyWiseCurrency theCurrency;
1086 
1087             /**
1088              * Constructor.
1089              *
1090              * @param pCurrency the currency
1091              */
1092             private MoneyWiseRateList(final MoneyWiseCurrency pCurrency) {
1093                 theCurrency = pCurrency;
1094                 theList = new ArrayList<>();
1095             }
1096 
1097             @Override
1098             public MetisFieldSet<MoneyWiseRateList> getDataFieldSet() {
1099                 return FIELD_DEFS;
1100             }
1101 
1102             @Override
1103             public String formatObject(final OceanusDataFormatter pFormatter) {
1104                 return theCurrency.formatObject(pFormatter)
1105                         + "("
1106                         + size()
1107                         + ")";
1108             }
1109 
1110             @Override
1111             public String toString() {
1112                 return theCurrency.toString()
1113                         + "("
1114                         + size()
1115                         + ")";
1116             }
1117 
1118             @Override
1119             public List<MoneyWiseExchangeRate> getUnderlyingList() {
1120                 return theList;
1121             }
1122         }
1123     }
1124 }