task4/solution11/src/org/apidesign/apifest08/currency/Convertor.java
author Jaroslav Tulach <jtulach@netbeans.org>
Sat, 25 Oct 2008 20:53:00 +0200
changeset 84 2ae6e4aa7aef
parent 61 58ec6da75f6f
permissions -rw-r--r--
Solutions by Petr Smid
     1 package org.apidesign.apifest08.currency;
     2 
     3 import java.util.ArrayList;
     4 import java.util.Collection;
     5 import java.util.Date;
     6 import java.util.HashSet;
     7 import java.util.List;
     8 import java.util.Set;
     9 import org.apidesign.apifest08.currency.Computer.ComputerRequest;
    10 import org.apidesign.apifest08.currency.Computer.ComputerResponse;
    11 
    12 /**
    13  * Convertor.
    14  * 
    15  * In Task 1's version provides conversion between currency values
    16  * with amount stored in integer or double, that are identified
    17  * with string value. Exchange rates are immutable.
    18  * 
    19  * In Task2's version provides support for multiple exchange rates
    20  * between different currencies & merging exchange rates from
    21  * existing convertors into new convertor's instance.
    22  * No time for javadoc these features, sorry.
    23  * 
    24  * In Task3's version supports reading of current exchange rates
    25  * from data sources. Data sources are merged during convertors' merging
    26  * as well as static exchange rates.
    27  * No time for javadoc, again.
    28  * 
    29  * In Task4's version takes into account validity range of data sources,
    30  * can convert using an exchange rate value according to the specified instant
    31  * of the time and provides a method for creating a new convertor with the same
    32  * data sources as the old one, but with their validity ranges limited
    33  * to the specified range.
    34  * As usual, no time for javadoc.
    35  * 
    36  * @author ked
    37  */
    38 public final class Convertor<AmountType, IdentifierType> {
    39 
    40     Computer<AmountType> computer;
    41     // each static exchange rate is a special case of an exchange rate data source
    42     // historically separated
    43     List<ExchangeRateDataSource<AmountType, IdentifierType>> staticExchangeRateDataSources =
    44             new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
    45     List<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources =
    46             new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
    47 
    48     // ---
    49     // BASICS
    50     // ---
    51     Convertor(Computer<AmountType> computer) {
    52         this.computer = computer;
    53     }
    54 
    55     void addExchangeRateDataSources(
    56             List<ExchangeRateDataSource<AmountType, IdentifierType>> target,
    57             Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources) {
    58         for (ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource : exchangeRateDataSources) {
    59             if (isOverlappingExchangeRate(
    60                     exchangeRateDataSource.getCurrencyAIdentifier(),
    61                     exchangeRateDataSource.getCurrencyBIdentifier(),
    62                     exchangeRateDataSource.getValidFrom(),
    63                     exchangeRateDataSource.getValidTill())) {
    64                 throw new IllegalArgumentException("Duplicate exchange rate!");
    65             }
    66             target.add(exchangeRateDataSource);
    67         }
    68     }
    69 
    70     ExchangeRateValue<AmountType, IdentifierType> findExchangeRate(
    71             IdentifierType currencyA,
    72             IdentifierType currencyB,
    73             Date instant) {
    74         ExchangeRateValue<AmountType, IdentifierType> result = null;
    75         result = findExchangeRateInternal(staticExchangeRateDataSources, currencyA, currencyB, instant);
    76         if (result != null) {
    77             return result;
    78         }
    79         result = findExchangeRateInternal(exchangeRateDataSources, currencyA, currencyB, instant);
    80         return result;
    81     }
    82 
    83     ExchangeRateValue<AmountType, IdentifierType> findExchangeRateInternal(
    84             List<ExchangeRateDataSource<AmountType, IdentifierType>> where,
    85             IdentifierType currencyA,
    86             IdentifierType currencyB,
    87             Date instant) {
    88         for (ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource : where) {
    89             if (((exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyA) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyB)) ||
    90                     (exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyB) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyA))) &&
    91                     DateUtil.isInRange(instant, exchangeRateDataSource.getValidFrom(), exchangeRateDataSource.getValidTill())) {
    92                 return exchangeRateDataSource.getExchangeRate();
    93             }
    94         }
    95         return null;
    96     }
    97 
    98     boolean isOverlappingExchangeRate(
    99             IdentifierType currencyA,
   100             IdentifierType currencyB,
   101             Date from,
   102             Date to) {
   103         boolean result = false;
   104         result = isOverlappingExchangeRateInternal(staticExchangeRateDataSources, currencyA, currencyB, from, to);
   105         if (result == true) {
   106             return result;
   107         }
   108         result = isOverlappingExchangeRateInternal(exchangeRateDataSources, currencyA, currencyB, from, to);
   109         return result;
   110     }
   111 
   112     boolean isOverlappingExchangeRateInternal(
   113             List<ExchangeRateDataSource<AmountType, IdentifierType>> where,
   114             IdentifierType currencyA,
   115             IdentifierType currencyB,
   116             Date from,
   117             Date to) {
   118         for (ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource : where) {
   119             if (((exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyA) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyB)) ||
   120                     (exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyB) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyA))) &&
   121                     DateUtil.isRangesOverlapping(from, to, exchangeRateDataSource.getValidFrom(), exchangeRateDataSource.getValidTill())) {
   122                 return true;
   123             }
   124         }
   125         return false;
   126     }
   127 
   128     /**
   129      * Convert an amount of the one currency to an amount of the another one currency
   130      * with respect to previously specified exchange rates.
   131      * 
   132      * @param targetCurrency an identifier of the requested currency
   133      * @param currencyValue an amount of the another one currency
   134      * @return an amount of the requested currency
   135      */
   136     public CurrencyValue<AmountType, IdentifierType> convert(
   137             IdentifierType targetCurrency,
   138             CurrencyValue<AmountType, IdentifierType> currencyValue) {
   139         return convert(targetCurrency, currencyValue, new Date()); // System.currentTimeMillis()
   140     }
   141 
   142     public CurrencyValue<AmountType, IdentifierType> convert(
   143             IdentifierType targetCurrency,
   144             CurrencyValue<AmountType, IdentifierType> currencyValue,
   145             Date instant) {
   146         ExchangeRateValue<AmountType, IdentifierType> exchangeRate =
   147                 findExchangeRate(currencyValue.getIdentifier(), targetCurrency, instant);
   148         if (exchangeRate == null) {
   149             throw new IllegalArgumentException("Inappropriate currencies to convert!");
   150         }
   151 
   152         ComputerRequest<AmountType> computerRequest = new ComputerRequest<AmountType>();
   153         computerRequest.setInput(currencyValue.getAmount());
   154 
   155         IdentifierType targetCurrencyRef; // just for backward compatibility :-(
   156         if (exchangeRate.getCurrencyA().getIdentifier().equals(targetCurrency)) {
   157             computerRequest.setInputCurrencyRatio(exchangeRate.getCurrencyB().getAmount());
   158             computerRequest.setOutputCurrencyRatio(exchangeRate.getCurrencyA().getAmount());
   159             targetCurrencyRef = exchangeRate.getCurrencyA().getIdentifier();
   160         } else {
   161             computerRequest.setInputCurrencyRatio(exchangeRate.getCurrencyA().getAmount());
   162             computerRequest.setOutputCurrencyRatio(exchangeRate.getCurrencyB().getAmount());
   163             targetCurrencyRef = exchangeRate.getCurrencyB().getIdentifier();
   164         }
   165 
   166         ComputerResponse<AmountType> computerResponse = new ComputerResponse<AmountType>();
   167         computer.compute(computerRequest, computerResponse);
   168 
   169         return CurrencyValue.getCurrencyValue(
   170                 computerResponse.getResult(),
   171                 targetCurrencyRef);
   172     }
   173 
   174     // ---
   175     // LIMITING
   176     // ---
   177     Collection<ExchangeRateDataSource<AmountType, IdentifierType>> limitDataSources(
   178             Collection<ExchangeRateDataSource<AmountType, IdentifierType>> source,
   179             Date from, Date till) {
   180         Collection<ExchangeRateDataSource<AmountType, IdentifierType>> result =
   181                 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
   182 
   183         for (ExchangeRateDataSource<AmountType, IdentifierType> dataSource : source) {
   184             result.add(ExchangeRateDataSource.getExchangeRateDataSource(
   185                     dataSource.getCurrencyAIdentifier(), dataSource.getCurrencyBIdentifier(),
   186                     dataSource.getExchangeRateProvider(),
   187                     DateUtil.getRangesIntersectionBottom(dataSource.getValidFrom(), from),
   188                     DateUtil.getRangesIntersectionTop(dataSource.getValidTill(), till)));
   189         }
   190 
   191         return result;
   192     }
   193 
   194     public Convertor<AmountType, IdentifierType> limitConvertor(Date from, Date till) {
   195         Collection<ExchangeRateDataSource<AmountType, IdentifierType>> limitedStatic =
   196                 limitDataSources(staticExchangeRateDataSources, from, till);
   197         Collection<ExchangeRateDataSource<AmountType, IdentifierType>> limited =
   198                 limitDataSources(exchangeRateDataSources, from, till);
   199 
   200         Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
   201         c.addExchangeRateDataSources(c.staticExchangeRateDataSources, limitedStatic);
   202         c.addExchangeRateDataSources(c.exchangeRateDataSources, limited);
   203         return c;
   204     }
   205 
   206     // ---
   207     // MERGING
   208     // ---
   209     static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> mergeConvertors(
   210             Computer<AmountType> computer,
   211             Collection<Convertor<AmountType, IdentifierType>> convertors) {
   212         Set<ExchangeRateDataSource<AmountType, IdentifierType>> mergedStatic =
   213                 new HashSet<ExchangeRateDataSource<AmountType, IdentifierType>>();
   214         Set<ExchangeRateDataSource<AmountType, IdentifierType>> merged =
   215                 new HashSet<ExchangeRateDataSource<AmountType, IdentifierType>>();
   216         for (Convertor<AmountType, IdentifierType> convertor : convertors) {
   217             mergedStatic.addAll(convertor.staticExchangeRateDataSources);
   218         }
   219         for (Convertor<AmountType, IdentifierType> convertor : convertors) {
   220             merged.addAll(convertor.exchangeRateDataSources);
   221         }
   222 
   223         Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
   224         c.addExchangeRateDataSources(c.staticExchangeRateDataSources, mergedStatic);
   225         c.addExchangeRateDataSources(c.exchangeRateDataSources, merged);
   226         return c;
   227     }
   228 
   229     static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> mergeConvertors(
   230             Computer<AmountType> computer,
   231             Convertor<AmountType, IdentifierType> convertorA,
   232             Convertor<AmountType, IdentifierType> convertorB) {
   233         Collection<Convertor<AmountType, IdentifierType>> convertors =
   234                 new ArrayList<Convertor<AmountType, IdentifierType>>();
   235         convertors.add(convertorA);
   236         convertors.add(convertorB);
   237         return mergeConvertors(computer, convertors);
   238     }
   239 
   240     public static Convertor<Double, String> mergeConvertorsDoubleString(
   241             Collection<Convertor<Double, String>> convertors) {
   242         return mergeConvertors(DoubleComputer, convertors);
   243     }
   244 
   245     public static Convertor<Double, String> mergeConvertorsDoubleString(
   246             Convertor<Double, String> convertorA,
   247             Convertor<Double, String> convertorB) {
   248         return mergeConvertors(DoubleComputer, convertorA, convertorB);
   249     }
   250 
   251     public static Convertor<Integer, String> mergeConvertorsIntegerString(
   252             Collection<Convertor<Integer, String>> convertors) {
   253         return mergeConvertors(IntegerComputer, convertors);
   254     }
   255 
   256     public static Convertor<Integer, String> mergeConvertorsIntegerString(
   257             Convertor<Integer, String> convertorA,
   258             Convertor<Integer, String> convertorB) {
   259         return mergeConvertors(IntegerComputer, convertorA, convertorB);
   260     }
   261 
   262     // ---
   263     // CREATION
   264     // ---
   265     static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertor(
   266             Computer<AmountType> computer, Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates) {
   267         Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
   268         Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources =
   269                 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
   270         for (ExchangeRateValue<AmountType, IdentifierType> exchangeRate : exchangeRates) {
   271             exchangeRateDataSources.add(
   272                     ExchangeRateDataSource.getExchangeRateDataSource(
   273                     exchangeRate.getCurrencyA().getIdentifier(), exchangeRate.getCurrencyB().getIdentifier(),
   274                     StaticExchangeRateProvider.getStaticExchangeRateProvider(exchangeRate)));
   275         }
   276         c.addExchangeRateDataSources(c.staticExchangeRateDataSources, exchangeRateDataSources);
   277         return c;
   278     }
   279 
   280     static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertorDataSource(
   281             Computer<AmountType> computer, Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources) {
   282         Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
   283         c.addExchangeRateDataSources(c.exchangeRateDataSources, exchangeRateDataSources);
   284         return c;
   285     }
   286 
   287     static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertor(
   288             Computer<AmountType> computer, ExchangeRateValue<AmountType, IdentifierType> exchangeRate) {
   289         Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates =
   290                 new ArrayList<ExchangeRateValue<AmountType, IdentifierType>>();
   291         exchangeRates.add(exchangeRate);
   292         return getConvertor(computer, exchangeRates);
   293     }
   294 
   295     static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertorDataSource(
   296             Computer<AmountType> computer, ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource) {
   297         Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources =
   298                 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
   299         exchangeRateDataSources.add(exchangeRateDataSource);
   300         return getConvertorDataSource(computer, exchangeRateDataSources);
   301     }
   302 
   303     public static Convertor<Double, String> getConvertorDoubleString(
   304             Collection<ExchangeRateValue<Double, String>> exchangeRates) {
   305         return getConvertor(DoubleComputer, exchangeRates);
   306     }
   307 
   308     public static Convertor<Double, String> getConvertorDoubleString(
   309             ExchangeRateValue<Double, String> exchangeRate) {
   310         return getConvertor(DoubleComputer, exchangeRate);
   311     }
   312 
   313     public static Convertor<Double, String> getConvertorDataSourceDoubleString(
   314             Collection<ExchangeRateDataSource<Double, String>> exchangeRateDataSources) {
   315         return getConvertorDataSource(DoubleComputer, exchangeRateDataSources);
   316     }
   317 
   318     public static Convertor<Double, String> getConvertorDataSourceDoubleString(
   319             ExchangeRateDataSource<Double, String> exchangeRateDataSource) {
   320         return getConvertorDataSource(DoubleComputer, exchangeRateDataSource);
   321     }
   322 
   323     public static Convertor<Integer, String> getConvertorIntegerString(
   324             Collection<ExchangeRateValue<Integer, String>> exchangeRates) {
   325         return getConvertor(IntegerComputer, exchangeRates);
   326     }
   327 
   328     public static Convertor<Integer, String> getConvertorIntegerString(
   329             ExchangeRateValue<Integer, String> exchangeRate) {
   330         return getConvertor(IntegerComputer, exchangeRate);
   331     }
   332 
   333     public static Convertor<Integer, String> getConvertorDataSourceIntegerString(
   334             Collection<ExchangeRateDataSource<Integer, String>> exchangeRateDataSources) {
   335         return getConvertorDataSource(IntegerComputer, exchangeRateDataSources);
   336     }
   337 
   338     public static Convertor<Integer, String> getConvertorDataSourceIntegerString(
   339             ExchangeRateDataSource<Integer, String> exchangeRateDataSource) {
   340         return getConvertorDataSource(IntegerComputer, exchangeRateDataSource);
   341     }
   342 
   343     // ---
   344     // BACKWARD COMPATIBILITY - CREATION
   345     // ---
   346     /**
   347      * Creates convertor for Double|String values with specified exchange rate
   348      * between two currencies.
   349      * 
   350      * @param firstCurrencyExchangeRate first currency
   351      * @param secondCurrencyExchangeRate second currency
   352      * @return convertor
   353      */
   354     public static Convertor<Double, String> getConvertorDoubleString(
   355             CurrencyValue<Double, String> firstCurrencyExchangeRate,
   356             CurrencyValue<Double, String> secondCurrencyExchangeRate) {
   357         return getConvertorDoubleString(ExchangeRateValue.getExchangeRate(firstCurrencyExchangeRate, secondCurrencyExchangeRate));
   358     }
   359 
   360     /**
   361      * Creates convertor for Integer|String values with specified exchange rate
   362      * between two currencies.
   363      * 
   364      * @param firstCurrencyExchangeRate first currency
   365      * @param secondCurrencyExchangeRate second currency
   366      * @return convertor
   367      */
   368     public static Convertor<Integer, String> getConvertorIntegerString(
   369             CurrencyValue<Integer, String> firstCurrencyExchangeRate,
   370             CurrencyValue<Integer, String> secondCurrencyExchangeRate) {
   371         return getConvertorIntegerString(ExchangeRateValue.getExchangeRate(firstCurrencyExchangeRate, secondCurrencyExchangeRate));
   372     }
   373 
   374     // ---
   375     // COMPUTERS
   376     // ---
   377     static final Computer<Double> DoubleComputer = new Computer<Double>() {
   378 
   379         public void compute(ComputerRequest<Double> request, ComputerResponse<Double> response) {
   380             response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());
   381         }
   382     };
   383     static final Computer<Integer> IntegerComputer = new Computer<Integer>() {
   384 
   385         public void compute(ComputerRequest<Integer> request, ComputerResponse<Integer> response) {
   386             response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());
   387         }
   388     };
   389 }