task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java
1 package org.apidesign.apifest08.currency;
4 import java.math.BigDecimal;
5 import java.math.RoundingMode;
6 import java.util.Collections;
7 import java.util.Currency;
8 import java.util.HashMap;
9 import java.util.HashSet;
15 * A composite convertor allows conversions between many currencies by forwarding conversion requests to stored convertors.
16 * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors.
18 * @author D'Arcy Smith
21 final class CompositeConvertorImpl
25 * The convertors that are supported.
27 private final Convertor[] convertors;
30 * Keeps track of what convertors to use to convert between currencies.
32 private final Map<Currency, Map<Currency, Convertor>> possibleConversions;
35 possibleConversions = new HashMap<Currency, Map<Currency, Convertor>>();
39 * Construct a ComositeConvertorImpl with the specified convertors.
40 * This will result in all possible conversions between the supplied currencies being made.
42 * @param cs the convertors to use.
43 * @throws IllegalArgumentException if any of the items in cs are null.
45 CompositeConvertorImpl(Convertor ... cs)
51 // track all of the known conversion
52 for(final Convertor convertor : convertors)
54 final Set<Currency> currencies;
55 Map<Currency, Convertor> possible;
59 throw new IllegalArgumentException("cs cannot contain null");
62 currencies = convertor.getCurrencies();
64 for(final Currency currency : currencies)
66 possible = possibleConversions.get(currency);
70 possible = new HashMap<Currency, Convertor>();
71 possibleConversions.put(currency, possible);
74 for(final Currency c : currencies)
76 possible.put(c, convertor);
81 // make up conversions that can be derived... eg:
93 // NOTE: no attempt is made to deal with dates for DatedConvertors... nothing we can do about it.
98 // todo... need to loop this until all the ones that can be handled are done.
99 for(final Currency from : getCurrencies())
101 for(final Currency to : getCurrencies())
103 if(!(canConvert(from, to)))
105 final Set<Currency> fromCurrencies;
106 final Set<Currency> toCurrencies;
107 final Set<Currency> common;
109 fromCurrencies = possibleConversions.get(from).keySet();
110 toCurrencies = possibleConversions.get(to).keySet();
111 common = new HashSet<Currency>();
113 for(final Currency currency : fromCurrencies)
115 if(toCurrencies.contains(currency))
117 common.add(currency);
121 for(final Currency currency : common)
123 final Convertor convertor;
125 convertor = createConvertor(from, to, currency);
126 possibleConversions.get(from).put(to, convertor);
127 possibleConversions.get(to).put(from, convertor);
134 while(newConvertors > 0);
138 * Check to see if converting between the two currencies is possible.
140 * @param from the currency to convert from.
141 * @param to the currency to convert to.
142 * @return true if the conversion is possible.
143 * @throws IllegalArgumentException if either from or to are null.
145 public boolean canConvert(final Currency from, final Currency to)
147 final Map<Currency, Convertor> possible;
151 throw new IllegalArgumentException("from cannot be null");
156 throw new IllegalArgumentException("to cannot be null");
159 possible = possibleConversions.get(from);
161 if(possible.containsKey(to))
170 * Get the currencies that the convertor supports. Just because a currency is
171 * supported does not mean that canConvert will return true.
173 * @return the supported currencies.
175 public Set<Currency> getCurrencies()
177 final Set<Currency> currencies;
179 currencies = possibleConversions.keySet();
181 return (Collections.unmodifiableSet(currencies));
185 * Get the conversion rate between two currencies.
187 * @param from the currency to convert from.
188 * @param to the currency to convert to.
189 * @return the conversion rate between the two currencies.
190 * @throws IllegalArgumentException if either from or to is null.
191 * @throws InvalidConversionException if canConvert would return false.
193 public BigDecimal getConversionRate(final Currency from, final Currency to)
194 throws InvalidConversionException
196 final Map<Currency, Convertor> possible;
201 throw new IllegalArgumentException("from cannot be null");
206 throw new IllegalArgumentException("to cannot be null");
209 if(!(canConvert(from, to)))
211 throw new InvalidConversionException("cannot convert", to);
214 possible = possibleConversions.get(from);
215 convertor = possible.get(to);
217 if(convertor == null)
222 return (convertor.getConversionRate(from, to));
225 private Convertor getConvertor(final Currency from, final Currency to)
226 throws InvalidConversionException
228 final Map<Currency, Convertor> possible;
233 throw new IllegalArgumentException("from cannot be null");
238 throw new IllegalArgumentException("to cannot be null");
241 if(!(canConvert(from, to)))
243 throw new InvalidConversionException("cannot convert", to);
246 possible = possibleConversions.get(from);
247 convertor = possible.get(to);
249 if(convertor == null)
258 * Convert an amount from one currency to another.
260 * @param from the currency to convert from.
261 * @param to the currency to convert to.
262 * @param amount the amount to convert.
263 * @return the converted amount.
264 * @throws IllegalArgumentException if any of the arguments are null.
265 * @throws InvalidConversionException if either from or to are not valid for the convertor.
267 public BigDecimal convert(final Currency from,
269 final BigDecimal amount)
270 throws InvalidConversionException
272 final BigDecimal result;
273 final Convertor convertor;
277 throw new IllegalArgumentException("amount cannot be null");
282 throw new IllegalArgumentException("from cannot be null");
287 throw new IllegalArgumentException("to cannot be null");
290 // fixed a bug from Task2 that showed up in Task3... before we did the conversion here,
291 // but that meant that the underlying covnerter convert method never got called... which
292 // meant that in Task3 the exchange rate never changed.
293 convertor = getConvertor(from, to);
294 result = convertor.convert(from, to, amount);
296 return (result.setScale(2, RoundingMode.HALF_DOWN));
300 * Create a convertor between two currencies using another currency that is able to convert between both.
302 * @param from the currency to convert from.
303 * @param to the currency to convert to.
304 * @param intermediary the currency to use as a go-between.
305 * @return a Convertor that is able to convert between from an to.
306 * @throws IllegalArgumentException if any of the arguments are null.
308 private Convertor createConvertor(final Currency from,
310 final Currency intermediary)
312 final Convertor fromIntermediary;
313 final Convertor toIntermediary;
317 throw new IllegalArgumentException("from cannot be null");
322 throw new IllegalArgumentException("to cannot be null");
325 if(intermediary == null)
327 throw new IllegalArgumentException("intermediary cannot be null");
330 fromIntermediary = possibleConversions.get(from).get(intermediary);
331 toIntermediary = possibleConversions.get(to).get(intermediary);
335 final BigDecimal fromRate;
336 final BigDecimal toRate;
337 final BigDecimal rate;
338 final Convertor convertor;
340 fromRate = fromIntermediary.getConversionRate(from, intermediary);
341 toRate = toIntermediary.getConversionRate(intermediary, to);
342 rate = fromRate.multiply(toRate);
343 convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
347 catch (InvalidConversionException ex)