task4/solution04/src/org/apidesign/apifest08/currency/DatedCompositeConvertorImpl.java
author Jaroslav Tulach <jtulach@netbeans.org>
Sat, 25 Oct 2008 20:53:00 +0200
changeset 84 2ae6e4aa7aef
permissions -rw-r--r--
Solutions by Petr Smid
     1 package org.apidesign.apifest08.currency;
     2 
     3 import java.math.BigDecimal;
     4 import java.util.Currency;
     5 import java.util.Date;
     6 import java.util.HashMap;
     7 import java.util.Map;
     8 import java.util.Set;
     9 
    10 
    11 final class DatedCompositeConvertorImpl
    12     implements TimedConvertor
    13 {
    14     /**
    15      * The convertors that are supported.
    16      */
    17     private final DatedConvertor[] convertors;
    18     
    19     /**
    20      * Keeps track of what convertors to use to convert between currencies.
    21      */
    22     private final Map<DateRange, Map<Currency, Map<Currency, Convertor>>> datedConversions;
    23     
    24     {
    25         datedConversions = new HashMap<DateRange, Map<Currency, Map<Currency, Convertor>>>();
    26     }
    27 
    28     /**
    29      * Construct a ComositeConvertorImpl with the specified convertors.
    30      * This will result in all possible conversions between the supplied currencies being made.
    31      * 
    32      * @param cs the convertors to use.
    33      * @throws IllegalArgumentException if any of the items in cs are null.
    34      */
    35     DatedCompositeConvertorImpl(Convertor ... cs)
    36     {
    37         int i;
    38 
    39         convertors = new DatedConvertor[cs.length];
    40         i          = 0;
    41 
    42         for(final Convertor c : cs)
    43         {
    44             if(!(c instanceof DatedConvertor))
    45             {
    46                 throw new IllegalArgumentException("cs must only contain DatedConvertors");
    47             }
    48 
    49             convertors[i] = (DatedConvertor)c;
    50             i++;
    51         }
    52 
    53         // track all of the known conversion
    54         for(final DatedConvertor convertor : convertors)
    55         {
    56             final Set<Currency>      currencies;
    57             Map<Currency, Convertor> possible;
    58             DateRange                range;
    59             
    60             if(convertor == null)
    61             {
    62                 throw new IllegalArgumentException("cs cannot contain null");
    63             }
    64             
    65             currencies = convertor.getCurrencies();
    66             range      = convertor.getDateRange();
    67             
    68             for(final Currency currency : currencies)
    69             {
    70                 Map<Currency, Map<Currency, Convertor>> possibleConversions;
    71                 
    72                 possibleConversions = datedConversions.get(range);
    73                 
    74                 if(possibleConversions == null)
    75                 {
    76                     possibleConversions = new HashMap<Currency, Map<Currency, Convertor>>();
    77                     datedConversions.put(range, possibleConversions);
    78                 }
    79 
    80                 possible = possibleConversions.get(currency);
    81 
    82                 if(possible == null)
    83                 {
    84                     possible = new HashMap<Currency, Convertor>();
    85                     possibleConversions.put(currency, possible);
    86                 }
    87 
    88                 for(final Currency c : currencies)
    89                 {
    90                     possible.put(c, convertor);
    91                 }
    92             }
    93         }
    94 
    95 
    96         /*
    97         // make up conversions that can be derived... eg:
    98         //   we have:
    99         //      USD <-> CAD
   100         //      CAD <-> CZK
   101         //      SSK <-> GBP
   102         //   we can derive:
   103         //      USD <-> CZK
   104         //   we cannot derive:
   105         //      USD <-> GBP
   106         //      CAD <-> GBP
   107         //      CZK <-> GBP        
   108         // 
   109         // NOTE: no attempt is made to deal with dates for DatedConvertors... nothing we can do about it.
   110         do
   111         {
   112             newConvertors = 0;
   113 
   114             // todo... need to loop this until all the ones that can be handled are done.
   115             for(final Currency from : getCurrencies())
   116             {
   117                 for(final Currency to : getCurrencies())
   118                 {
   119                     if(!(canConvert(from, to)))
   120                     {
   121                         final Set<Currency> fromCurrencies;
   122                         final Set<Currency> toCurrencies;
   123                         final Set<Currency> common;
   124                         Map<Currency, Map<Currency, Convertor>> possibleConversions;
   125                 
   126                         possibleConversions.get(range);
   127                         fromCurrencies = possibleConversions.get(from).keySet();
   128                         toCurrencies   = possibleConversions.get(to).keySet();
   129                         common         = new HashSet<Currency>();
   130 
   131                         for(final Currency currency : fromCurrencies)
   132                         {
   133                             if(toCurrencies.contains(currency))
   134                             {
   135                                 common.add(currency);
   136                             }
   137                         }
   138 
   139                         for(final Currency currency : common)
   140                         {
   141                             final Convertor convertor;
   142 
   143                             convertor = createConvertor(from, to, currency);
   144                             possibleConversions.get(from).put(to, convertor);
   145                             possibleConversions.get(to).put(from, convertor);
   146                             newConvertors++;
   147                         }
   148                     }
   149                 }
   150             }
   151         }
   152         while(newConvertors > 0);
   153         */
   154     }
   155 
   156     /**
   157      * Check to see if converting between the two currencies is possible.
   158      *
   159      * @param from the currency to convert from.
   160      * @param to the currency to convert to.
   161      * @return true if the conversion is possible.
   162      * @throws IllegalArgumentException if either from or to are null.
   163      */
   164     public boolean canConvert(final Currency from, final Currency to)
   165     {
   166         throw new UnsupportedOperationException();
   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         throw new UnsupportedOperationException();
   178     }
   179     
   180     /**
   181      * Get the conversion rate between two currencies.
   182      * 
   183      * @param from the currency to convert from.
   184      * @param to the currency to convert to.
   185      * @return the conversion rate between the two currencies.
   186      * @throws IllegalArgumentException if either from or to is null.
   187      * @throws InvalidConversionException if canConvert would return false.
   188      */
   189     public BigDecimal getConversionRate(final Currency from, final Currency to)
   190         throws InvalidConversionException
   191     {
   192         throw new UnsupportedOperationException();
   193     }
   194 
   195     private Convertor getConvertor(final Currency from, final Currency to, final Date date)
   196     {
   197         Map<Currency, Map<Currency, Convertor>> possibleConversions;
   198         final Map<Currency, Convertor> possible;
   199         Convertor                      convertor;
   200 
   201         if(from == null)
   202         {
   203             throw new IllegalArgumentException("from cannot be null");
   204         }
   205         
   206         if(to == null)
   207         {
   208             throw new IllegalArgumentException("to cannot be null");
   209         }
   210         
   211         possibleConversions = null;
   212 
   213         for(final DateRange range : datedConversions.keySet())
   214         {
   215             if(range.isInRange(date))
   216             {
   217                 possibleConversions = datedConversions.get(range);
   218                 break;
   219             }
   220         }
   221 
   222         if(possibleConversions == null)
   223         {
   224             return (null);
   225         }
   226 
   227         possible  = possibleConversions.get(from);
   228         
   229                 
   230         if(possible == null)
   231         {
   232             return (null);
   233         }
   234 
   235 
   236         convertor = possible.get(to);
   237 
   238         
   239         if(convertor == null)
   240         {
   241             return (null);
   242         }
   243 
   244         return (convertor);
   245     }
   246 
   247     /**
   248      * Convert an amount from one currency to another.
   249      * 
   250      * @param from the currency to convert from.
   251      * @param to the currency to convert to.
   252      * @param amount the amount to convert.
   253      * @return the converted amount.
   254      * @throws IllegalArgumentException if any of the arguments are null.
   255      * @throws InvalidConversionException if either from or to are not valid for the convertor.
   256      */
   257     public BigDecimal convert(final Currency   from,
   258                               final Currency   to,
   259                               final BigDecimal amount)
   260         throws InvalidConversionException
   261     {
   262         throw new InvalidConversionException("No date for the conversion", from);
   263     }
   264 
   265     public BigDecimal convert(final Currency   from, 
   266                               final Currency   to, 
   267                               final BigDecimal amount, 
   268                               final Date       date) 
   269         throws InvalidConversionException 
   270     {
   271         final Convertor  convertor;
   272         final BigDecimal total;
   273         
   274         convertor = getConvertor(from, to, date);
   275 
   276         if(convertor == null)
   277         {
   278             throw new InvalidConversionException("cannot convert", from);
   279         }
   280         
   281         if(canConvert(from, to, date))
   282         {
   283             final TimedConvertor timeConvertor;
   284             
   285             timeConvertor = (TimedConvertor)convertor;
   286             total         = timeConvertor.convert(from, to, amount, date);
   287         }
   288         else
   289         {
   290             throw new InvalidConversionException("cannot convert", from);
   291         }
   292         
   293         return (total);
   294     }
   295 
   296     public boolean canConvert(final Currency from,
   297                               final Currency to,
   298                               final Date     date)
   299     {
   300         Convertor     convertor;
   301         final boolean retVal;
   302 
   303         convertor = getConvertor(from, to, date);
   304 
   305         if(convertor != null)
   306         {
   307             final TimedConvertor timeConvertor;
   308 
   309             timeConvertor = (TimedConvertor)convertor;
   310             retVal        = timeConvertor.canConvert(from, to, date);
   311         }
   312         else
   313         {
   314             retVal = false;
   315         }
   316 
   317         return (retVal);
   318     }
   319 }