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