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