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