task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java Sat Oct 11 23:38:46 2008 +0200
1.3 @@ -0,0 +1,350 @@
1.4 +package org.apidesign.apifest08.currency;
1.5 +
1.6 +
1.7 +import java.math.BigDecimal;
1.8 +import java.math.RoundingMode;
1.9 +import java.util.Collections;
1.10 +import java.util.Currency;
1.11 +import java.util.HashMap;
1.12 +import java.util.HashSet;
1.13 +import java.util.Map;
1.14 +import java.util.Set;
1.15 +
1.16 +
1.17 +/**
1.18 + * A composite convertor allows conversions between many currencies by forwarding conversion requests to stored convertors.
1.19 + * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors.
1.20 + *
1.21 + * @author D'Arcy Smith
1.22 + * @verson 1.1
1.23 + */
1.24 +final class CompositeConvertorImpl
1.25 + implements Convertor
1.26 +{
1.27 + /**
1.28 + * The convertors that are supported.
1.29 + */
1.30 + private final Convertor[] convertors;
1.31 +
1.32 + /**
1.33 + * Keeps track of what convertors to use to convert between currencies.
1.34 + */
1.35 + private final Map<Currency, Map<Currency, Convertor>> possibleConversions;
1.36 +
1.37 + {
1.38 + possibleConversions = new HashMap<Currency, Map<Currency, Convertor>>();
1.39 + }
1.40 +
1.41 + /**
1.42 + * Construct a ComositeConvertorImpl with the specified convertors.
1.43 + * This will result in all possible conversions between the supplied currencies being made.
1.44 + *
1.45 + * @param cs the convertors to use.
1.46 + * @throws IllegalArgumentException if any of the items in cs are null.
1.47 + */
1.48 + CompositeConvertorImpl(Convertor ... cs)
1.49 + {
1.50 + int newConvertors;
1.51 +
1.52 + convertors = cs;
1.53 +
1.54 + // track all of the known conversion
1.55 + for(final Convertor convertor : convertors)
1.56 + {
1.57 + final Set<Currency> currencies;
1.58 + Map<Currency, Convertor> possible;
1.59 +
1.60 + if(convertor == null)
1.61 + {
1.62 + throw new IllegalArgumentException("cs cannot contain null");
1.63 + }
1.64 +
1.65 + currencies = convertor.getCurrencies();
1.66 +
1.67 + for(final Currency currency : currencies)
1.68 + {
1.69 + possible = possibleConversions.get(currency);
1.70 +
1.71 + if(possible == null)
1.72 + {
1.73 + possible = new HashMap<Currency, Convertor>();
1.74 + possibleConversions.put(currency, possible);
1.75 + }
1.76 +
1.77 + for(final Currency c : currencies)
1.78 + {
1.79 + possible.put(c, convertor);
1.80 + }
1.81 + }
1.82 + }
1.83 +
1.84 + // make up conversions that can be derived... eg:
1.85 + // we have:
1.86 + // USD <-> CAD
1.87 + // CAD <-> CZK
1.88 + // SSK <-> GBP
1.89 + // we can derive:
1.90 + // USD <-> CZK
1.91 + // we cannot derive:
1.92 + // USD <-> GBP
1.93 + // CAD <-> GBP
1.94 + // CZK <-> GBP
1.95 + do
1.96 + {
1.97 + newConvertors = 0;
1.98 +
1.99 + // todo... need to loop this until all the ones that can be handled are done.
1.100 + for(final Currency from : getCurrencies())
1.101 + {
1.102 + for(final Currency to : getCurrencies())
1.103 + {
1.104 + if(!(canConvert(from, to)))
1.105 + {
1.106 + final Set<Currency> fromCurrencies;
1.107 + final Set<Currency> toCurrencies;
1.108 + final Set<Currency> common;
1.109 +
1.110 + fromCurrencies = possibleConversions.get(from).keySet();
1.111 + toCurrencies = possibleConversions.get(to).keySet();
1.112 + common = new HashSet<Currency>();
1.113 +
1.114 + for(final Currency currency : fromCurrencies)
1.115 + {
1.116 + if(toCurrencies.contains(currency))
1.117 + {
1.118 + common.add(currency);
1.119 + }
1.120 + }
1.121 +
1.122 + for(final Currency currency : common)
1.123 + {
1.124 + final Convertor convertor;
1.125 +
1.126 + convertor = createConvertor(from, to, currency);
1.127 + possibleConversions.get(from).put(to, convertor);
1.128 + possibleConversions.get(to).put(from, convertor);
1.129 + newConvertors++;
1.130 + }
1.131 + }
1.132 + }
1.133 + }
1.134 + }
1.135 + while(newConvertors > 0);
1.136 + }
1.137 +
1.138 + /**
1.139 + * Check to see if converting between the two currencies is possible.
1.140 + *
1.141 + * @param from the currency to convert from.
1.142 + * @param to the currency to convert to.
1.143 + * @return true if the conversion is possible.
1.144 + * @throws IllegalArgumentException if either from or to are null.
1.145 + */
1.146 + public boolean canConvert(final Currency from, final Currency to)
1.147 + {
1.148 + final Map<Currency, Convertor> possible;
1.149 +
1.150 + if(from == null)
1.151 + {
1.152 + throw new IllegalArgumentException("from cannot be null");
1.153 + }
1.154 +
1.155 + if(to == null)
1.156 + {
1.157 + throw new IllegalArgumentException("to cannot be null");
1.158 + }
1.159 +
1.160 + possible = possibleConversions.get(from);
1.161 +
1.162 + if(possible.containsKey(to))
1.163 + {
1.164 + return (true);
1.165 + }
1.166 +
1.167 + return (false);
1.168 + }
1.169 +
1.170 + /**
1.171 + * Get the currencies that the convertor supports. Just because a currency is
1.172 + * supported does not mean that canConvert will return true.
1.173 + *
1.174 + * @return the supported currencies.
1.175 + */
1.176 + public Set<Currency> getCurrencies()
1.177 + {
1.178 + final Set<Currency> currencies;
1.179 +
1.180 + currencies = possibleConversions.keySet();
1.181 +
1.182 + return (Collections.unmodifiableSet(currencies));
1.183 + }
1.184 +
1.185 + /**
1.186 + * Get the conversion rate between two currencies.
1.187 + *
1.188 + * @param from the currency to convert from.
1.189 + * @param to the currency to convert to.
1.190 + * @return the conversion rate between the two currencies.
1.191 + * @throws IllegalArgumentException if either from or to is null.
1.192 + * @throws InvalidConversionException if canConvert would return false.
1.193 + */
1.194 + public BigDecimal getConversionRate(final Currency from, final Currency to)
1.195 + throws InvalidConversionException
1.196 + {
1.197 + final Map<Currency, Convertor> possible;
1.198 + Convertor convertor;
1.199 +
1.200 + if(from == null)
1.201 + {
1.202 + throw new IllegalArgumentException("from cannot be null");
1.203 + }
1.204 +
1.205 + if(to == null)
1.206 + {
1.207 + throw new IllegalArgumentException("to cannot be null");
1.208 + }
1.209 +
1.210 + if(!(canConvert(from, to)))
1.211 + {
1.212 + throw new InvalidConversionException("cannot convert", to);
1.213 + }
1.214 +
1.215 + possible = possibleConversions.get(from);
1.216 + convertor = possible.get(to);
1.217 +
1.218 + if(convertor == null)
1.219 + {
1.220 + throw new Error();
1.221 + }
1.222 +
1.223 + return (convertor.getConversionRate(from, to));
1.224 + }
1.225 +
1.226 + private Convertor getConvertor(final Currency from, final Currency to)
1.227 + throws InvalidConversionException
1.228 + {
1.229 + final Map<Currency, Convertor> possible;
1.230 + Convertor convertor;
1.231 +
1.232 + if(from == null)
1.233 + {
1.234 + throw new IllegalArgumentException("from cannot be null");
1.235 + }
1.236 +
1.237 + if(to == null)
1.238 + {
1.239 + throw new IllegalArgumentException("to cannot be null");
1.240 + }
1.241 +
1.242 + if(!(canConvert(from, to)))
1.243 + {
1.244 + throw new InvalidConversionException("cannot convert", to);
1.245 + }
1.246 +
1.247 + possible = possibleConversions.get(from);
1.248 + convertor = possible.get(to);
1.249 +
1.250 + if(convertor == null)
1.251 + {
1.252 + throw new Error();
1.253 + }
1.254 +
1.255 + return (convertor);
1.256 + }
1.257 +
1.258 + /**
1.259 + * Convert an amount from one currency to another.
1.260 + *
1.261 + * @param from the currency to convert from.
1.262 + * @param to the currency to convert to.
1.263 + * @param amount the amount to convert.
1.264 + * @return the converted amount.
1.265 + * @throws IllegalArgumentException if any of the arguments are null.
1.266 + * @throws InvalidConversionException if either from or to are not valid for the convertor.
1.267 + */
1.268 + public BigDecimal convert(final Currency from,
1.269 + final Currency to,
1.270 + final BigDecimal amount)
1.271 + throws InvalidConversionException
1.272 + {
1.273 + final BigDecimal result;
1.274 + final Convertor convertor;
1.275 +
1.276 + if(amount == null)
1.277 + {
1.278 + throw new IllegalArgumentException("amount cannot be null");
1.279 + }
1.280 +
1.281 + if(from == null)
1.282 + {
1.283 + throw new IllegalArgumentException("from cannot be null");
1.284 + }
1.285 +
1.286 + if(to == null)
1.287 + {
1.288 + throw new IllegalArgumentException("to cannot be null");
1.289 + }
1.290 +
1.291 + // fixed a bug from Task2 that showed up in Task3... before we did the conversion here,
1.292 + // but that meant that the underlying covnerter convert method never got called... which
1.293 + // meant that in Task3 the exchange rate never changed.
1.294 + convertor = getConvertor(from, to);
1.295 + result = convertor.convert(from, to, amount);
1.296 +
1.297 + return (result.setScale(2, RoundingMode.HALF_DOWN));
1.298 + }
1.299 +
1.300 + /**
1.301 + * Create a convertor between two currencies using another currency that is able to convert between both.
1.302 + *
1.303 + * @param from the currency to convert from.
1.304 + * @param to the currency to convert to.
1.305 + * @param intermediary the currency to use as a go-between.
1.306 + * @return a Convertor that is able to convert between from an to.
1.307 + * @throws IllegalArgumentException if any of the arguments are null.
1.308 + */
1.309 + private Convertor createConvertor(final Currency from,
1.310 + final Currency to,
1.311 + final Currency intermediary)
1.312 + {
1.313 + final Convertor fromIntermediary;
1.314 + final Convertor toIntermediary;
1.315 +
1.316 + if(from == null)
1.317 + {
1.318 + throw new IllegalArgumentException("from cannot be null");
1.319 + }
1.320 +
1.321 + if(to == null)
1.322 + {
1.323 + throw new IllegalArgumentException("to cannot be null");
1.324 + }
1.325 +
1.326 + if(intermediary == null)
1.327 + {
1.328 + throw new IllegalArgumentException("intermediary cannot be null");
1.329 + }
1.330 +
1.331 + fromIntermediary = possibleConversions.get(from).get(intermediary);
1.332 + toIntermediary = possibleConversions.get(to).get(intermediary);
1.333 +
1.334 + try
1.335 + {
1.336 + final BigDecimal fromRate;
1.337 + final BigDecimal toRate;
1.338 + final BigDecimal rate;
1.339 + final Convertor convertor;
1.340 +
1.341 + fromRate = fromIntermediary.getConversionRate(from, intermediary);
1.342 + toRate = toIntermediary.getConversionRate(intermediary, to);
1.343 + rate = fromRate.multiply(toRate);
1.344 + convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
1.345 +
1.346 + return (convertor);
1.347 + }
1.348 + catch (InvalidConversionException ex)
1.349 + {
1.350 + throw new Error();
1.351 + }
1.352 + }
1.353 +}