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.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
21  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateFormatter;
22  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateRange;
23  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusMoney;
24  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusRatio;
25  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
26  import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
27  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataFieldId;
28  import io.github.tonywasher.joceanus.metis.data.MetisDataItem.MetisDataList;
29  import io.github.tonywasher.joceanus.metis.data.MetisDataResource;
30  import io.github.tonywasher.joceanus.metis.field.MetisFieldItem;
31  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
32  import io.github.tonywasher.joceanus.metis.field.MetisFieldVersionedSet;
33  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
34  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency.MoneyWiseCurrencyList;
35  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseStaticResource;
36  import io.github.tonywasher.joceanus.moneywise.exc.MoneyWiseDataException;
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)) {
506             return false;
507         }
508         final MoneyWiseExchangeRate myRate = (MoneyWiseExchangeRate) pRate;
509 
510         /* Store the current detail into history */
511         pushHistory();
512 
513         /* Update the Date if required */
514         if (!MetisDataDifference.isEqual(getDate(), myRate.getDate())) {
515             setValueDate(myRate.getDate());
516         }
517 
518         /* Update the from currency if required */
519         if (!MetisDataDifference.isEqual(getFromCurrency(), myRate.getFromCurrency())) {
520             setValueFromCurrency(myRate.getFromCurrency());
521         }
522 
523         /* Update the to currency if required */
524         if (!MetisDataDifference.isEqual(getToCurrency(), myRate.getToCurrency())) {
525             setValueToCurrency(myRate.getToCurrency());
526         }
527 
528         /* Update the rate if required */
529         if (!MetisDataDifference.isEqual(getExchangeRate(), myRate.getExchangeRate())) {
530             setValueExchangeRate(myRate.getExchangeRate());
531         }
532 
533         /* Check for changes */
534         return checkForHistory();
535     }
536 
537     @Override
538     public void adjustMapForItem() {
539         final MoneyWiseExchangeRateBaseList<? extends MoneyWiseExchangeRate> myList = getList();
540         final MoneyWiseExchangeRateDataMap myMap = myList.getDataMap();
541         myMap.adjustForItem(this);
542     }
543 
544     /**
545      * Price List.
546      *
547      * @param <T> the data type
548      */
549     public abstract static class MoneyWiseExchangeRateBaseList<T extends MoneyWiseExchangeRate>
550             extends PrometheusDataList<T> {
551         /*
552          * Report fields.
553          */
554         static {
555             MetisFieldSet.newFieldSet(MoneyWiseExchangeRateBaseList.class);
556         }
557 
558         /**
559          * Construct an empty CORE Price list.
560          *
561          * @param pData     the DataSet for the list
562          * @param pClass    the class of the item
563          * @param pItemType the item type
564          */
565         protected MoneyWiseExchangeRateBaseList(final MoneyWiseDataSet pData,
566                                                 final Class<T> pClass,
567                                                 final MoneyWiseBasicDataType pItemType) {
568             /* Call super-constructor */
569             super(pClass, pData, pItemType, PrometheusListStyle.CORE);
570         }
571 
572         /**
573          * Constructor for a cloned List.
574          *
575          * @param pSource the source List
576          */
577         protected MoneyWiseExchangeRateBaseList(final MoneyWiseExchangeRateBaseList<T> pSource) {
578             /* Call super-constructor */
579             super(pSource);
580         }
581 
582         @Override
583         public MoneyWiseExchangeRateDataMap getDataMap() {
584             return (MoneyWiseExchangeRateDataMap) super.getDataMap();
585         }
586 
587         @Override
588         protected MoneyWiseExchangeRateDataMap allocateDataMap() {
589             return new MoneyWiseExchangeRateDataMap();
590         }
591     }
592 
593     /**
594      * The ExchangeRate List class.
595      */
596     public static class MoneyWiseExchangeRateList
597             extends MoneyWiseExchangeRateBaseList<MoneyWiseExchangeRate> {
598         /**
599          * Report fields.
600          */
601         private static final MetisFieldSet<MoneyWiseExchangeRateList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseExchangeRateList.class);
602 
603         /*
604          * Declare Fields.
605          */
606         static {
607             FIELD_DEFS.declareLocalField(MoneyWiseStaticResource.CURRENCY_REPORTING, MoneyWiseExchangeRateList::getReportingCurrency);
608         }
609 
610         /**
611          * The reporting currency.
612          */
613         private MoneyWiseCurrency theReporting;
614 
615         /**
616          * Construct an empty CORE ExchangeRate list.
617          *
618          * @param pData the DataSet for the list
619          */
620         protected MoneyWiseExchangeRateList(final MoneyWiseDataSet pData) {
621             super(pData, MoneyWiseExchangeRate.class, MoneyWiseBasicDataType.EXCHANGERATE);
622         }
623 
624         /**
625          * Constructor for a cloned List.
626          *
627          * @param pSource the source List
628          */
629         protected MoneyWiseExchangeRateList(final MoneyWiseExchangeRateList pSource) {
630             super(pSource);
631         }
632 
633         @Override
634         public MetisFieldSet<MoneyWiseExchangeRateList> getDataFieldSet() {
635             return FIELD_DEFS;
636         }
637 
638         /**
639          * Obtain reporting currency.
640          *
641          * @return the reporting currency
642          */
643         public MoneyWiseCurrency getReportingCurrency() {
644             return theReporting;
645         }
646 
647         @Override
648         public String listName() {
649             return LIST_NAME;
650         }
651 
652         @Override
653         public MetisFieldSetDef getItemFields() {
654             return MoneyWiseExchangeRate.FIELD_DEFS;
655         }
656 
657         @Override
658         public MoneyWiseDataSet getDataSet() {
659             return (MoneyWiseDataSet) super.getDataSet();
660         }
661 
662         @Override
663         protected MoneyWiseExchangeRateList getEmptyList(final PrometheusListStyle pStyle) {
664             final MoneyWiseExchangeRateList myList = new MoneyWiseExchangeRateList(this);
665             myList.setStyle(pStyle);
666             return myList;
667         }
668 
669         /**
670          * Add a new item to the core list.
671          *
672          * @param pRate item
673          * @return the newly added item
674          */
675         @Override
676         public MoneyWiseExchangeRate addCopyItem(final PrometheusDataItem pRate) {
677             /* Can only clone an ExchangeRate */
678             if (!(pRate instanceof MoneyWiseExchangeRate)) {
679                 throw new UnsupportedOperationException();
680             }
681 
682             final MoneyWiseExchangeRate myRate = new MoneyWiseExchangeRate(this, (MoneyWiseExchangeRate) pRate);
683             add(myRate);
684             return myRate;
685         }
686 
687         /**
688          * Add a new item to the edit list.
689          *
690          * @return the new item
691          */
692         @Override
693         public MoneyWiseExchangeRate addNewItem() {
694             final MoneyWiseExchangeRate myRate = new MoneyWiseExchangeRate(this);
695             add(myRate);
696             return myRate;
697         }
698 
699         @Override
700         public MoneyWiseExchangeRate addValuesItem(final PrometheusDataValues pValues)
701                 throws OceanusException {
702             /* Create the rate */
703             final MoneyWiseExchangeRate myRate = new MoneyWiseExchangeRate(this, pValues);
704 
705             /* Check that this RateId has not been previously added */
706             if (!isIdUnique(myRate.getIndexedId())) {
707                 myRate.addError(ERROR_DUPLICATE, MetisDataResource.DATA_ID);
708                 throw new MoneyWiseDataException(myRate, ERROR_VALIDATION);
709             }
710 
711             /* Add to the list */
712             add(myRate);
713 
714             /* Return it */
715             return myRate;
716         }
717 
718         /**
719          * Convert a monetary value to the currency.
720          *
721          * @param pValue    the value to convert
722          * @param pCurrency the required currency
723          * @param pDate     the date of the conversion
724          * @return the converted value
725          */
726         public OceanusMoney convertCurrency(final OceanusMoney pValue,
727                                             final MoneyWiseCurrency pCurrency,
728                                             final OceanusDate pDate) {
729             /* Obtain the existing currency */
730             OceanusMoney myValue = pValue;
731             final MoneyWiseCurrencyList myCurrencies = getDataSet().getAccountCurrencies();
732             final Currency myCurrent = pValue.getCurrency();
733             final Currency myReporting = theReporting.getCurrency();
734             final Currency myTarget = pCurrency.getCurrency();
735 
736             /* Handle no conversion required */
737             if (myCurrent.equals(myTarget)) {
738                 return pValue;
739             }
740 
741             /* If the value is not already the reporting currency */
742             if (!myCurrent.equals(myReporting)) {
743                 /* Find the required exchange rate */
744                 final OceanusRatio myRate = findRate(myCurrencies.findCurrency(myCurrent), pDate);
745 
746                 /* Convert the currency */
747                 myValue = myValue.convertCurrency(myReporting, myRate);
748             }
749 
750             /* If we need to convert to a non-default currency */
751             if (!myReporting.equals(myTarget)) {
752                 /* Find the required exchange rate */
753                 final OceanusRatio myRate = findRate(pCurrency, pDate);
754 
755                 /* Convert the currency */
756                 myValue = myValue.convertCurrency(myTarget, myRate);
757             }
758 
759             /* Return the converted currency */
760             return myValue;
761         }
762 
763         /**
764          * Find the exchange rate.
765          *
766          * @param pCurrency the currency to find
767          * @param pDate     the date to find the exchange rate for
768          * @return the exchange rate
769          */
770         private OceanusRatio findRate(final MoneyWiseCurrency pCurrency,
771                                       final OceanusDate pDate) {
772             /* pass call to data map */
773             return getDataMap().getRateForDate(pCurrency, pDate);
774         }
775 
776         /**
777          * Set the default currency.
778          *
779          * @param pCurrency the new default currency
780          */
781         public void setReportingCurrency(final MoneyWiseCurrency pCurrency) {
782             /* Access the iterator */
783             final Iterator<MoneyWiseExchangeRate> myIterator = iterator();
784             OceanusRatio myCurrRate = null;
785             OceanusDate myCurrDate = null;
786 
787             /* Loop through the items to find the entry */
788             while (myIterator.hasNext()) {
789                 final MoneyWiseExchangeRate myCurr = myIterator.next();
790 
791                 /* Access details */
792                 final OceanusDate myDate = myCurr.getDate();
793                 final MoneyWiseCurrency myTo = myCurr.getToCurrency();
794                 final OceanusRatio myRatio = myCurr.getExchangeRate();
795 
796                 /* If this is a new date */
797                 if (myCurrDate == null || !myDate.equals(myCurrDate)) {
798                     /* Access the current rate for the new default currency */
799                     /*
800                      * TODO This must exist on the same date or else the currency cannot be set as
801                      * default
802                      */
803                     myCurrRate = findRate(pCurrency, myDate);
804                     myCurrDate = myDate;
805                 }
806 
807                 /* Update the item */
808                 myCurr.pushHistory();
809 
810                 /* If this is a conversion to the new default */
811                 if (myTo.equals(pCurrency)) {
812                     /* Switch the direction of the currencies */
813                     myCurr.setToCurrency(myCurr.getFromCurrency());
814 
815                     /* Invert the ratio */
816                     myCurr.setExchangeRate(myRatio.getInverseRatio());
817 
818                     /* Else does not currently involve the new currency */
819                 } else {
820                     /* Need to combine the rates */
821                     myCurr.setExchangeRate(new OceanusRatio(myRatio, myCurrRate));
822                 }
823 
824                 /* Set from currency */
825                 myCurr.setFromCurrency(pCurrency);
826             }
827 
828             /* Set the new reporting currency */
829             theReporting = pCurrency;
830         }
831     }
832 
833     /**
834      * The dataMap class.
835      */
836     public static class MoneyWiseExchangeRateDataMap
837             implements PrometheusDataMapItem, MetisFieldItem {
838         /**
839          * Report fields.
840          */
841         @SuppressWarnings("rawtypes")
842         private static final MetisFieldSet<MoneyWiseExchangeRateDataMap> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseExchangeRateDataMap.class);
843 
844         /*
845          * UnderlyingMap Field Id.
846          */
847         static {
848             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.MONEYWISEDATA_MAP_MAPOFMAPS, MoneyWiseExchangeRateDataMap::getMapOfMaps);
849             FIELD_DEFS.declareLocalField(MoneyWiseBasicResource.XCHGRATE_MAP_MAPOFRATES, MoneyWiseExchangeRateDataMap::getMapOfRates);
850         }
851 
852         /**
853          * Map of Maps.
854          */
855         private final Map<MoneyWiseCurrency, Map<OceanusDate, Integer>> theMapOfMaps;
856 
857         /**
858          * Map of Rates.
859          */
860         private final Map<MoneyWiseCurrency, MoneyWiseRateList> theMapOfRates;
861 
862         /**
863          * Constructor.
864          */
865         public MoneyWiseExchangeRateDataMap() {
866             /* Create the maps */
867             theMapOfMaps = new HashMap<>();
868             theMapOfRates = new HashMap<>();
869         }
870 
871         @SuppressWarnings("rawtypes")
872         @Override
873         public MetisFieldSet<MoneyWiseExchangeRateDataMap> getDataFieldSet() {
874             return FIELD_DEFS;
875         }
876 
877         @Override
878         public String formatObject(final OceanusDataFormatter pFormatter) {
879             return FIELD_DEFS.getName();
880         }
881 
882         /**
883          * Obtain mapOfMaps.
884          *
885          * @return the map
886          */
887         private Map<MoneyWiseCurrency, Map<OceanusDate, Integer>> getMapOfMaps() {
888             return theMapOfMaps;
889         }
890 
891         /**
892          * Obtain mapOfRates.
893          *
894          * @return the map
895          */
896         private Map<MoneyWiseCurrency, MoneyWiseRateList> getMapOfRates() {
897             return theMapOfRates;
898         }
899 
900         @Override
901         public void resetMap() {
902             theMapOfMaps.clear();
903             theMapOfRates.clear();
904         }
905 
906         @Override
907         public void adjustForItem(final PrometheusDataItem pItem) {
908             /* Access the Currency Id */
909             final MoneyWiseExchangeRate myItem = (MoneyWiseExchangeRate) pItem;
910             final MoneyWiseCurrency myCurrency = myItem.getToCurrency();
911             if (myCurrency == null) {
912                 return;
913             }
914 
915             /* Access the map */
916             final Map<OceanusDate, Integer> myMap = theMapOfMaps.computeIfAbsent(myCurrency, c -> new HashMap<>());
917 
918             /* Adjust rate count */
919             final OceanusDate myDate = myItem.getDate();
920             final Integer myCount = myMap.get(myDate);
921             if (myCount == null) {
922                 myMap.put(myDate, PrometheusDataInstanceMap.ONE);
923             } else {
924                 myMap.put(myDate, myCount + 1);
925             }
926 
927             /* Access the list */
928             final MoneyWiseRateList myList = theMapOfRates.computeIfAbsent(myCurrency, MoneyWiseRateList::new);
929 
930             /* Add element to the list */
931             myList.add(myItem);
932         }
933 
934         /**
935          * Check validity of Rate.
936          *
937          * @param pItem the rate
938          * @return true/false
939          */
940         public boolean validRateCount(final MoneyWiseExchangeRate pItem) {
941             /* Access the Details */
942             final MoneyWiseCurrency myCurrency = pItem.getToCurrency();
943             final OceanusDate myDate = pItem.getDate();
944 
945             /* Access the map */
946             final Map<OceanusDate, Integer> myMap = theMapOfMaps.get(myCurrency);
947             if (myMap != null) {
948                 final Integer myResult = myMap.get(myDate);
949                 return PrometheusDataInstanceMap.ONE.equals(myResult);
950             }
951             return false;
952         }
953 
954         /**
955          * Check availability of date for a currency.
956          *
957          * @param pCurrency the currency
958          * @param pDate     the key to look up
959          * @return true/false
960          */
961         public boolean availableDate(final MoneyWiseCurrency pCurrency,
962                                      final OceanusDate pDate) {
963             /* Access the map */
964             final Map<OceanusDate, Integer> myMap = theMapOfMaps.get(pCurrency);
965             return myMap == null
966                     || myMap.get(pDate) == null;
967         }
968 
969         /**
970          * Obtain rate for date.
971          *
972          * @param pCurrency the currency
973          * @param pDate     the date
974          * @return the latest rate for the date.
975          */
976         public OceanusRatio getRateForDate(final MoneyWiseCurrency pCurrency,
977                                            final OceanusDate pDate) {
978             /* Access list for currency */
979             final MoneyWiseRateList myList = theMapOfRates.get(pCurrency);
980             if (myList != null) {
981                 /* Loop through the rates */
982                 final Iterator<MoneyWiseExchangeRate> myIterator = myList.iterator();
983                 while (myIterator.hasNext()) {
984                     final MoneyWiseExchangeRate myCurr = myIterator.next();
985 
986                     /* Access the date */
987                     final OceanusDate myDate = myCurr.getDate();
988 
989                     /* break loop if we have the correct record */
990                     if (myDate.compareTo(pDate) >= 0) {
991                         return myCurr.getExchangeRate();
992                     }
993                 }
994             }
995 
996             /* return null */
997             return null;
998         }
999 
1000         /**
1001          * Obtain rates for range.
1002          *
1003          * @param pCurrency the currency
1004          * @param pRange    the date range
1005          * @return the two deep array of rates for the range.
1006          */
1007         public OceanusRatio[] getRatesForRange(final MoneyWiseCurrency pCurrency,
1008                                                final OceanusDateRange pRange) {
1009             /* Set rate */
1010             OceanusRatio myFirst = OceanusRatio.ONE;
1011             OceanusRatio myLatest = OceanusRatio.ONE;
1012             final OceanusDate myStart = pRange.getStart();
1013 
1014             /* Access list for security */
1015             final MoneyWiseRateList myList = theMapOfRates.get(pCurrency);
1016             if (myList != null) {
1017                 /* Loop through the rates */
1018                 final ListIterator<MoneyWiseExchangeRate> myIterator = myList.listIterator(myList.size());
1019                 while (myIterator.hasPrevious()) {
1020                     final MoneyWiseExchangeRate myCurr = myIterator.previous();
1021 
1022                     /* Check for the range of the date */
1023                     final OceanusDate myDate = myCurr.getDate();
1024                     final int iComp = pRange.compareToDate(myDate);
1025 
1026                     /* If this is later than the range we are finished */
1027                     if (iComp < 0) {
1028                         break;
1029                     }
1030 
1031                     /* Record as best rate */
1032                     myLatest = myCurr.getExchangeRate();
1033 
1034                     /* Adjust first rate */
1035                     if (iComp > 0
1036                             || myDate.compareTo(myStart) == 0) {
1037                         myFirst = myLatest;
1038                     }
1039                 }
1040             }
1041 
1042             /* Return the rates */
1043             return new OceanusRatio[]
1044                     {myFirst, myLatest};
1045         }
1046 
1047         /**
1048          * Obtain rateList cursor.
1049          *
1050          * @param pCurrency the currency
1051          * @return the latest rate for the date.
1052          */
1053         public ListIterator<MoneyWiseExchangeRate> rateIterator(final MoneyWiseCurrency pCurrency) {
1054             /* Access list for currency */
1055             final MoneyWiseRateList myList = theMapOfRates.get(pCurrency);
1056             return myList != null
1057                     ? myList.listIterator(myList.size())
1058                     : null;
1059         }
1060 
1061         /**
1062          * Rate List class.
1063          */
1064         private static final class MoneyWiseRateList
1065                 implements MetisFieldItem, MetisDataList<MoneyWiseExchangeRate> {
1066             /**
1067              * Report fields.
1068              */
1069             private static final MetisFieldSet<MoneyWiseRateList> FIELD_DEFS = MetisFieldSet.newFieldSet(MoneyWiseRateList.class);
1070 
1071             /*
1072              * UnderlyingMap Field Id.
1073              */
1074             static {
1075                 FIELD_DEFS.declareLocalField(MetisDataResource.LIST_SIZE, MoneyWiseRateList::size);
1076             }
1077 
1078             /**
1079              * The list.
1080              */
1081             private final List<MoneyWiseExchangeRate> theList;
1082 
1083             /**
1084              * The currency.
1085              */
1086             private final MoneyWiseCurrency theCurrency;
1087 
1088             /**
1089              * Constructor.
1090              *
1091              * @param pCurrency the currency
1092              */
1093             private MoneyWiseRateList(final MoneyWiseCurrency pCurrency) {
1094                 theCurrency = pCurrency;
1095                 theList = new ArrayList<>();
1096             }
1097 
1098             @Override
1099             public MetisFieldSet<MoneyWiseRateList> getDataFieldSet() {
1100                 return FIELD_DEFS;
1101             }
1102 
1103             @Override
1104             public String formatObject(final OceanusDataFormatter pFormatter) {
1105                 return theCurrency.formatObject(pFormatter)
1106                         + "("
1107                         + size()
1108                         + ")";
1109             }
1110 
1111             @Override
1112             public String toString() {
1113                 return theCurrency.toString()
1114                         + "("
1115                         + size()
1116                         + ")";
1117             }
1118 
1119             @Override
1120             public List<MoneyWiseExchangeRate> getUnderlyingList() {
1121                 return theList;
1122             }
1123         }
1124     }
1125 }