task2/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/task2/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java Tue Oct 07 00:21:03 2008 +0200
1.3 @@ -0,0 +1,314 @@
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.0
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 + /**
1.227 + * Convert an amount from one currency to another.
1.228 + *
1.229 + * @param from the currency to convert from.
1.230 + * @param to the currency to convert to.
1.231 + * @param amount the amount to convert.
1.232 + * @return the converted amount.
1.233 + * @throws IllegalArgumentException if any of the arguments are null.
1.234 + * @throws InvalidConversionException if either from or to are not valid for the convertor.
1.235 + */
1.236 + public BigDecimal convert(final Currency from,
1.237 + final Currency to,
1.238 + final BigDecimal amount)
1.239 + throws InvalidConversionException
1.240 + {
1.241 + final BigDecimal result;
1.242 +
1.243 + if(amount == null)
1.244 + {
1.245 + throw new IllegalArgumentException("amount cannot be null");
1.246 + }
1.247 +
1.248 + if(from == null)
1.249 + {
1.250 + throw new IllegalArgumentException("from cannot be null");
1.251 + }
1.252 +
1.253 + if(to == null)
1.254 + {
1.255 + throw new IllegalArgumentException("to cannot be null");
1.256 + }
1.257 +
1.258 + result = amount.multiply(getConversionRate(from, to));
1.259 +
1.260 + return (result.setScale(2, RoundingMode.HALF_DOWN));
1.261 + }
1.262 +
1.263 + /**
1.264 + * Create a convertor between two currencies using another currency that is able to convert between both.
1.265 + *
1.266 + * @param from the currency to convert from.
1.267 + * @param to the currency to convert to.
1.268 + * @param intermediary the currency to use as a go-between.
1.269 + * @return a Convertor that is able to convert between from an to.
1.270 + * @throws IllegalArgumentException if any of the arguments are null.
1.271 + */
1.272 + private Convertor createConvertor(final Currency from,
1.273 + final Currency to,
1.274 + final Currency intermediary)
1.275 + {
1.276 + final Convertor fromIntermediary;
1.277 + final Convertor toIntermediary;
1.278 +
1.279 + if(from == null)
1.280 + {
1.281 + throw new IllegalArgumentException("from cannot be null");
1.282 + }
1.283 +
1.284 + if(to == null)
1.285 + {
1.286 + throw new IllegalArgumentException("to cannot be null");
1.287 + }
1.288 +
1.289 + if(intermediary == null)
1.290 + {
1.291 + throw new IllegalArgumentException("intermediary cannot be null");
1.292 + }
1.293 +
1.294 + fromIntermediary = possibleConversions.get(from).get(intermediary);
1.295 + toIntermediary = possibleConversions.get(to).get(intermediary);
1.296 +
1.297 + try
1.298 + {
1.299 + final BigDecimal fromRate;
1.300 + final BigDecimal toRate;
1.301 + final BigDecimal rate;
1.302 + final Convertor convertor;
1.303 +
1.304 + fromRate = fromIntermediary.getConversionRate(from, intermediary);
1.305 + toRate = toIntermediary.getConversionRate(intermediary, to);
1.306 + rate = fromRate.multiply(toRate);
1.307 +
1.308 + convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
1.309 +
1.310 + return (convertor);
1.311 + }
1.312 + catch (InvalidConversionException ex)
1.313 + {
1.314 + throw new Error();
1.315 + }
1.316 + }
1.317 +}