task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Sat, 11 Oct 2008 23:38:46 +0200
changeset 61 58ec6da75f6f
parent 55 task3/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java@14e78f48ac2b
child 69 420baec87dc5
permissions -rw-r--r--
Copying structure for task4
     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.1
    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     private Convertor getConvertor(final Currency from, final Currency to)
   224         throws InvalidConversionException
   225     {
   226         final Map<Currency, Convertor> possible;
   227         Convertor                      convertor;
   228 
   229         if(from == null)
   230         {
   231             throw new IllegalArgumentException("from cannot be null");
   232         }
   233         
   234         if(to == null)
   235         {
   236             throw new IllegalArgumentException("to cannot be null");
   237         }
   238         
   239         if(!(canConvert(from, to)))
   240         {
   241             throw new InvalidConversionException("cannot convert", to);
   242         }
   243 
   244         possible  = possibleConversions.get(from);
   245         convertor = possible.get(to);
   246 
   247         if(convertor == null)
   248         {
   249             throw new Error();
   250         }
   251 
   252         return (convertor);
   253     }
   254 
   255     /**
   256      * Convert an amount from one currency to another.
   257      * 
   258      * @param from the currency to convert from.
   259      * @param to the currency to convert to.
   260      * @param amount the amount to convert.
   261      * @return the converted amount.
   262      * @throws IllegalArgumentException if any of the arguments are null.
   263      * @throws InvalidConversionException if either from or to are not valid for the convertor.
   264      */
   265     public BigDecimal convert(final Currency   from,
   266                               final Currency   to,
   267                               final BigDecimal amount)
   268         throws InvalidConversionException
   269     {
   270         final BigDecimal result;
   271         final Convertor  convertor;
   272         
   273         if(amount == null)
   274         {
   275             throw new IllegalArgumentException("amount cannot be null");
   276         }
   277         
   278         if(from == null)
   279         {
   280             throw new IllegalArgumentException("from cannot be null");
   281         }
   282         
   283         if(to == null)
   284         {
   285             throw new IllegalArgumentException("to cannot be null");
   286         }
   287 
   288         // fixed a bug from Task2 that showed up in Task3... before we did the conversion here,
   289         // but that meant that the underlying covnerter convert method never got called... which
   290         // meant that in Task3 the exchange rate never changed.
   291         convertor = getConvertor(from, to);
   292         result    = convertor.convert(from, to, amount);
   293 
   294         return (result.setScale(2, RoundingMode.HALF_DOWN));
   295     }
   296 
   297     /**
   298      * Create a convertor between two currencies using another currency that is able to convert between both.
   299      * 
   300      * @param from the currency to convert from.
   301      * @param to the currency to convert to.
   302      * @param intermediary the currency to use as a go-between.
   303      * @return a Convertor that is able to convert between from an to.
   304      * @throws IllegalArgumentException if any of the arguments are null.
   305      */
   306     private Convertor createConvertor(final Currency from,
   307                                       final Currency to,
   308                                       final Currency intermediary) 
   309     {
   310         final Convertor fromIntermediary;
   311         final Convertor toIntermediary;
   312 
   313         if(from == null)
   314         {
   315             throw new IllegalArgumentException("from cannot be null");
   316         }
   317         
   318         if(to == null)
   319         {
   320             throw new IllegalArgumentException("to cannot be null");
   321         }
   322         
   323         if(intermediary == null)
   324         {
   325             throw new IllegalArgumentException("intermediary cannot be null");
   326         }
   327         
   328         fromIntermediary = possibleConversions.get(from).get(intermediary);
   329         toIntermediary   = possibleConversions.get(to).get(intermediary);
   330 
   331         try
   332         {
   333             final BigDecimal fromRate;
   334             final BigDecimal toRate;
   335             final BigDecimal rate;
   336             final Convertor  convertor;
   337             
   338             fromRate  = fromIntermediary.getConversionRate(from, intermediary);
   339             toRate    = toIntermediary.getConversionRate(intermediary, to);
   340             rate      = fromRate.multiply(toRate);            
   341             convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
   342             
   343             return (convertor);
   344         }
   345         catch (InvalidConversionException ex)
   346         {
   347             throw new Error();
   348         }
   349     }
   350 }