task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.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 
     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.2
    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         // 
    93         // NOTE: no attempt is made to deal with dates for DatedConvertors... nothing we can do about it.
    94         do
    95         {
    96             newConvertors = 0;
    97 
    98             // todo... need to loop this until all the ones that can be handled are done.
    99             for(final Currency from : getCurrencies())
   100             {
   101                 for(final Currency to : getCurrencies())
   102                 {
   103                     if(!(canConvert(from, to)))
   104                     {
   105                         final Set<Currency> fromCurrencies;
   106                         final Set<Currency> toCurrencies;
   107                         final Set<Currency> common;
   108 
   109                         fromCurrencies = possibleConversions.get(from).keySet();
   110                         toCurrencies   = possibleConversions.get(to).keySet();
   111                         common         = new HashSet<Currency>();
   112 
   113                         for(final Currency currency : fromCurrencies)
   114                         {
   115                             if(toCurrencies.contains(currency))
   116                             {
   117                                 common.add(currency);
   118                             }
   119                         }
   120 
   121                         for(final Currency currency : common)
   122                         {
   123                             final Convertor convertor;
   124 
   125                             convertor = createConvertor(from, to, currency);
   126                             possibleConversions.get(from).put(to, convertor);
   127                             possibleConversions.get(to).put(from, convertor);
   128                             newConvertors++;
   129                         }
   130                     }
   131                 }
   132             }
   133         }
   134         while(newConvertors > 0);
   135     }
   136 
   137     /**
   138      * Check to see if converting between the two currencies is possible.
   139      *
   140      * @param from the currency to convert from.
   141      * @param to the currency to convert to.
   142      * @return true if the conversion is possible.
   143      * @throws IllegalArgumentException if either from or to are null.
   144      */
   145     public boolean canConvert(final Currency from, final Currency to)
   146     {
   147         final Map<Currency, Convertor> possible;
   148 
   149         if(from == null)
   150         {
   151             throw new IllegalArgumentException("from cannot be null");
   152         }
   153         
   154         if(to == null)
   155         {
   156             throw new IllegalArgumentException("to cannot be null");
   157         }
   158                 
   159         possible = possibleConversions.get(from);
   160 
   161         if(possible.containsKey(to))
   162         {
   163             return (true);
   164         }
   165 
   166         return (false);
   167     }
   168 
   169     /**
   170      * Get the currencies that the convertor supports.  Just because a currency is
   171      * supported does not mean that canConvert will return true.
   172      * 
   173      * @return the supported currencies.
   174      */
   175     public Set<Currency> getCurrencies()
   176     {
   177         final Set<Currency> currencies;
   178 
   179         currencies = possibleConversions.keySet();
   180 
   181         return (Collections.unmodifiableSet(currencies));
   182     }
   183     
   184     /**
   185      * Get the conversion rate between two currencies.
   186      * 
   187      * @param from the currency to convert from.
   188      * @param to the currency to convert to.
   189      * @return the conversion rate between the two currencies.
   190      * @throws IllegalArgumentException if either from or to is null.
   191      * @throws InvalidConversionException if canConvert would return false.
   192      */
   193     public BigDecimal getConversionRate(final Currency from, final Currency to)
   194         throws InvalidConversionException
   195     {
   196         final Map<Currency, Convertor> possible;
   197         Convertor                      convertor;
   198 
   199         if(from == null)
   200         {
   201             throw new IllegalArgumentException("from cannot be null");
   202         }
   203         
   204         if(to == null)
   205         {
   206             throw new IllegalArgumentException("to cannot be null");
   207         }
   208         
   209         if(!(canConvert(from, to)))
   210         {
   211             throw new InvalidConversionException("cannot convert", to);
   212         }
   213 
   214         possible  = possibleConversions.get(from);
   215         convertor = possible.get(to);
   216 
   217         if(convertor == null)
   218         {
   219             throw new Error();
   220         }
   221 
   222         return (convertor.getConversionRate(from, to));
   223     }
   224 
   225     private Convertor getConvertor(final Currency from, final Currency to)
   226         throws InvalidConversionException
   227     {
   228         final Map<Currency, Convertor> possible;
   229         Convertor                      convertor;
   230 
   231         if(from == null)
   232         {
   233             throw new IllegalArgumentException("from cannot be null");
   234         }
   235         
   236         if(to == null)
   237         {
   238             throw new IllegalArgumentException("to cannot be null");
   239         }
   240         
   241         if(!(canConvert(from, to)))
   242         {
   243             throw new InvalidConversionException("cannot convert", to);
   244         }
   245 
   246         possible  = possibleConversions.get(from);
   247         convertor = possible.get(to);
   248 
   249         if(convertor == null)
   250         {
   251             throw new Error();
   252         }
   253 
   254         return (convertor);
   255     }
   256 
   257     /**
   258      * Convert an amount from one currency to another.
   259      * 
   260      * @param from the currency to convert from.
   261      * @param to the currency to convert to.
   262      * @param amount the amount to convert.
   263      * @return the converted amount.
   264      * @throws IllegalArgumentException if any of the arguments are null.
   265      * @throws InvalidConversionException if either from or to are not valid for the convertor.
   266      */
   267     public BigDecimal convert(final Currency   from,
   268                               final Currency   to,
   269                               final BigDecimal amount)
   270         throws InvalidConversionException
   271     {
   272         final BigDecimal result;
   273         final Convertor  convertor;
   274         
   275         if(amount == null)
   276         {
   277             throw new IllegalArgumentException("amount cannot be null");
   278         }
   279         
   280         if(from == null)
   281         {
   282             throw new IllegalArgumentException("from cannot be null");
   283         }
   284         
   285         if(to == null)
   286         {
   287             throw new IllegalArgumentException("to cannot be null");
   288         }
   289 
   290         // fixed a bug from Task2 that showed up in Task3... before we did the conversion here,
   291         // but that meant that the underlying covnerter convert method never got called... which
   292         // meant that in Task3 the exchange rate never changed.
   293         convertor = getConvertor(from, to);
   294         result    = convertor.convert(from, to, amount);
   295 
   296         return (result.setScale(2, RoundingMode.HALF_DOWN));
   297     }
   298 
   299     /**
   300      * Create a convertor between two currencies using another currency that is able to convert between both.
   301      * 
   302      * @param from the currency to convert from.
   303      * @param to the currency to convert to.
   304      * @param intermediary the currency to use as a go-between.
   305      * @return a Convertor that is able to convert between from an to.
   306      * @throws IllegalArgumentException if any of the arguments are null.
   307      */
   308     private Convertor createConvertor(final Currency from,
   309                                       final Currency to,
   310                                       final Currency intermediary) 
   311     {
   312         final Convertor fromIntermediary;
   313         final Convertor toIntermediary;
   314 
   315         if(from == null)
   316         {
   317             throw new IllegalArgumentException("from cannot be null");
   318         }
   319         
   320         if(to == null)
   321         {
   322             throw new IllegalArgumentException("to cannot be null");
   323         }
   324         
   325         if(intermediary == null)
   326         {
   327             throw new IllegalArgumentException("intermediary cannot be null");
   328         }
   329         
   330         fromIntermediary = possibleConversions.get(from).get(intermediary);
   331         toIntermediary   = possibleConversions.get(to).get(intermediary);
   332 
   333         try
   334         {
   335             final BigDecimal fromRate;
   336             final BigDecimal toRate;
   337             final BigDecimal rate;
   338             final Convertor  convertor;
   339             
   340             fromRate  = fromIntermediary.getConversionRate(from, intermediary);
   341             toRate    = toIntermediary.getConversionRate(intermediary, to);
   342             rate      = fromRate.multiply(toRate);            
   343             convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
   344             
   345             return (convertor);
   346         }
   347         catch (InvalidConversionException ex)
   348         {
   349             throw new Error();
   350         }
   351     }
   352 }