task3/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 07 Oct 2008 11:05:34 +0200
changeset 45 251d0ed461fb
parent 35 task2/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java@8898c620fe96
child 55 14e78f48ac2b
permissions -rw-r--r--
Copying all solution that advanced into round #3 into task3 directory
     1 package org.apidesign.apifest08.currency;
     2 
     3 
     4 import java.math.BigDecimal;
     5 import java.math.RoundingMode;
     6 import java.util.Collections;
     7 import java.util.Currency;
     8 import java.util.HashMap;
     9 import java.util.HashSet;
    10 import java.util.Map;
    11 import java.util.Set;
    12 
    13 
    14 /**
    15  * A composite convertor allows conversions between many currencies by forwarding conversion requests to stored convertors.
    16  * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors.
    17  *
    18  * @author D'Arcy Smith
    19  * @verson 1.0
    20  */
    21 final class CompositeConvertorImpl
    22     implements Convertor
    23 {
    24     /**
    25      * The convertors that are supported.
    26      */
    27     private final Convertor[] convertors;
    28     
    29     /**
    30      * Keeps track of what convertors to use to convert between currencies.
    31      */
    32     private final Map<Currency, Map<Currency, Convertor>> possibleConversions;
    33     
    34     {
    35         possibleConversions = new HashMap<Currency, Map<Currency, Convertor>>();
    36     }
    37 
    38     /**
    39      * Construct a ComositeConvertorImpl with the specified convertors.
    40      * This will result in all possible conversions between the supplied currencies being made.
    41      * 
    42      * @param cs the convertors to use.
    43      * @throws IllegalArgumentException if any of the items in cs are null.
    44      */
    45     CompositeConvertorImpl(Convertor ... cs)
    46     {
    47         int newConvertors;
    48         
    49         convertors = cs;
    50 
    51         // track all of the known conversion
    52         for(final Convertor convertor : convertors)
    53         {
    54             final Set<Currency>      currencies;
    55             Map<Currency, Convertor> possible;
    56 
    57             if(convertor == null)
    58             {
    59                 throw new IllegalArgumentException("cs cannot contain null");
    60             }
    61             
    62             currencies = convertor.getCurrencies();
    63 
    64             for(final Currency currency : currencies)
    65             {
    66                 possible = possibleConversions.get(currency);
    67 
    68                 if(possible == null)
    69                 {
    70                     possible = new HashMap<Currency, Convertor>();
    71                     possibleConversions.put(currency, possible);
    72                 }
    73 
    74                 for(final Currency c : currencies)
    75                 {
    76                     possible.put(c, convertor);
    77                 }
    78             }
    79         }
    80 
    81         // make up conversions that can be derived... eg:
    82         //   we have:
    83         //      USD <-> CAD
    84         //      CAD <-> CZK
    85         //      SSK <-> GBP
    86         //   we can derive:
    87         //      USD <-> CZK
    88         //   we cannot derive:
    89         //      USD <-> GBP
    90         //      CAD <-> GBP
    91         //      CZK <-> GBP
    92         do
    93         {
    94             newConvertors = 0;
    95 
    96             // todo... need to loop this until all the ones that can be handled are done.
    97             for(final Currency from : getCurrencies())
    98             {
    99                 for(final Currency to : getCurrencies())
   100                 {
   101                     if(!(canConvert(from, to)))
   102                     {
   103                         final Set<Currency> fromCurrencies;
   104                         final Set<Currency> toCurrencies;
   105                         final Set<Currency> common;
   106 
   107                         fromCurrencies = possibleConversions.get(from).keySet();
   108                         toCurrencies   = possibleConversions.get(to).keySet();
   109                         common         = new HashSet<Currency>();
   110 
   111                         for(final Currency currency : fromCurrencies)
   112                         {
   113                             if(toCurrencies.contains(currency))
   114                             {
   115                                 common.add(currency);
   116                             }
   117                         }
   118 
   119                         for(final Currency currency : common)
   120                         {
   121                             final Convertor convertor;
   122 
   123                             convertor = createConvertor(from, to, currency);
   124                             possibleConversions.get(from).put(to, convertor);
   125                             possibleConversions.get(to).put(from, convertor);
   126                             newConvertors++;
   127                         }
   128                     }
   129                 }
   130             }
   131         }
   132         while(newConvertors > 0);
   133     }
   134 
   135     /**
   136      * Check to see if converting between the two currencies is possible.
   137      *
   138      * @param from the currency to convert from.
   139      * @param to the currency to convert to.
   140      * @return true if the conversion is possible.
   141      * @throws IllegalArgumentException if either from or to are null.
   142      */
   143     public boolean canConvert(final Currency from, final Currency to)
   144     {
   145         final Map<Currency, Convertor> possible;
   146 
   147         if(from == null)
   148         {
   149             throw new IllegalArgumentException("from cannot be null");
   150         }
   151         
   152         if(to == null)
   153         {
   154             throw new IllegalArgumentException("to cannot be null");
   155         }
   156                 
   157         possible = possibleConversions.get(from);
   158 
   159         if(possible.containsKey(to))
   160         {
   161             return (true);
   162         }
   163 
   164         return (false);
   165     }
   166 
   167     /**
   168      * Get the currencies that the convertor supports.  Just because a currency is
   169      * supported does not mean that canConvert will return true.
   170      * 
   171      * @return the supported currencies.
   172      */
   173     public Set<Currency> getCurrencies()
   174     {
   175         final Set<Currency> currencies;
   176 
   177         currencies = possibleConversions.keySet();
   178 
   179         return (Collections.unmodifiableSet(currencies));
   180     }
   181     
   182     /**
   183      * Get the conversion rate between two currencies.
   184      * 
   185      * @param from the currency to convert from.
   186      * @param to the currency to convert to.
   187      * @return the conversion rate between the two currencies.
   188      * @throws IllegalArgumentException if either from or to is null.
   189      * @throws InvalidConversionException if canConvert would return false.
   190      */
   191     public BigDecimal getConversionRate(final Currency from, final Currency to)
   192         throws InvalidConversionException
   193     {
   194         final Map<Currency, Convertor> possible;
   195         Convertor                      convertor;
   196 
   197         if(from == null)
   198         {
   199             throw new IllegalArgumentException("from cannot be null");
   200         }
   201         
   202         if(to == null)
   203         {
   204             throw new IllegalArgumentException("to cannot be null");
   205         }
   206         
   207         if(!(canConvert(from, to)))
   208         {
   209             throw new InvalidConversionException("cannot convert", to);
   210         }
   211 
   212         possible  = possibleConversions.get(from);
   213         convertor = possible.get(to);
   214 
   215         if(convertor == null)
   216         {
   217             throw new Error();
   218         }
   219 
   220         return (convertor.getConversionRate(from, to));
   221     }
   222 
   223     /**
   224      * Convert an amount from one currency to another.
   225      * 
   226      * @param from the currency to convert from.
   227      * @param to the currency to convert to.
   228      * @param amount the amount to convert.
   229      * @return the converted amount.
   230      * @throws IllegalArgumentException if any of the arguments are null.
   231      * @throws InvalidConversionException if either from or to are not valid for the convertor.
   232      */
   233     public BigDecimal convert(final Currency   from,
   234                               final Currency   to,
   235                               final BigDecimal amount)
   236         throws InvalidConversionException
   237     {
   238         final BigDecimal result;
   239         
   240         if(amount == null)
   241         {
   242             throw new IllegalArgumentException("amount cannot be null");
   243         }
   244         
   245         if(from == null)
   246         {
   247             throw new IllegalArgumentException("from cannot be null");
   248         }
   249         
   250         if(to == null)
   251         {
   252             throw new IllegalArgumentException("to cannot be null");
   253         }
   254 
   255         result = amount.multiply(getConversionRate(from, to));
   256 
   257         return (result.setScale(2, RoundingMode.HALF_DOWN));
   258     }
   259 
   260     /**
   261      * Create a convertor between two currencies using another currency that is able to convert between both.
   262      * 
   263      * @param from the currency to convert from.
   264      * @param to the currency to convert to.
   265      * @param intermediary the currency to use as a go-between.
   266      * @return a Convertor that is able to convert between from an to.
   267      * @throws IllegalArgumentException if any of the arguments are null.
   268      */
   269     private Convertor createConvertor(final Currency from,
   270                                       final Currency to,
   271                                       final Currency intermediary) 
   272     {
   273         final Convertor fromIntermediary;
   274         final Convertor toIntermediary;
   275 
   276         if(from == null)
   277         {
   278             throw new IllegalArgumentException("from cannot be null");
   279         }
   280         
   281         if(to == null)
   282         {
   283             throw new IllegalArgumentException("to cannot be null");
   284         }
   285         
   286         if(intermediary == null)
   287         {
   288             throw new IllegalArgumentException("intermediary cannot be null");
   289         }
   290         
   291         fromIntermediary = possibleConversions.get(from).get(intermediary);
   292         toIntermediary   = possibleConversions.get(to).get(intermediary);
   293 
   294         try
   295         {
   296             final BigDecimal fromRate;
   297             final BigDecimal toRate;
   298             final BigDecimal rate;
   299             final Convertor  convertor;
   300             
   301             fromRate  = fromIntermediary.getConversionRate(from, intermediary);
   302             toRate    = toIntermediary.getConversionRate(intermediary, to);
   303             rate      = fromRate.multiply(toRate);
   304             
   305             convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
   306             
   307             return (convertor);
   308         }
   309         catch (InvalidConversionException ex)
   310         {
   311             throw new Error();
   312         }
   313     }
   314 }