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