diff -r 14e78f48ac2b -r 58ec6da75f6f task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java Sat Oct 11 23:38:46 2008 +0200 @@ -0,0 +1,350 @@ +package org.apidesign.apifest08.currency; + + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.Currency; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + + +/** + * A composite convertor allows conversions between many currencies by forwarding conversion requests to stored convertors. + * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors. + * + * @author D'Arcy Smith + * @verson 1.1 + */ +final class CompositeConvertorImpl + implements Convertor +{ + /** + * The convertors that are supported. + */ + private final Convertor[] convertors; + + /** + * Keeps track of what convertors to use to convert between currencies. + */ + private final Map> possibleConversions; + + { + possibleConversions = new HashMap>(); + } + + /** + * Construct a ComositeConvertorImpl with the specified convertors. + * This will result in all possible conversions between the supplied currencies being made. + * + * @param cs the convertors to use. + * @throws IllegalArgumentException if any of the items in cs are null. + */ + CompositeConvertorImpl(Convertor ... cs) + { + int newConvertors; + + convertors = cs; + + // track all of the known conversion + for(final Convertor convertor : convertors) + { + final Set currencies; + Map possible; + + if(convertor == null) + { + throw new IllegalArgumentException("cs cannot contain null"); + } + + currencies = convertor.getCurrencies(); + + for(final Currency currency : currencies) + { + possible = possibleConversions.get(currency); + + if(possible == null) + { + possible = new HashMap(); + possibleConversions.put(currency, possible); + } + + for(final Currency c : currencies) + { + possible.put(c, convertor); + } + } + } + + // make up conversions that can be derived... eg: + // we have: + // USD <-> CAD + // CAD <-> CZK + // SSK <-> GBP + // we can derive: + // USD <-> CZK + // we cannot derive: + // USD <-> GBP + // CAD <-> GBP + // CZK <-> GBP + do + { + newConvertors = 0; + + // todo... need to loop this until all the ones that can be handled are done. + for(final Currency from : getCurrencies()) + { + for(final Currency to : getCurrencies()) + { + if(!(canConvert(from, to))) + { + final Set fromCurrencies; + final Set toCurrencies; + final Set common; + + fromCurrencies = possibleConversions.get(from).keySet(); + toCurrencies = possibleConversions.get(to).keySet(); + common = new HashSet(); + + for(final Currency currency : fromCurrencies) + { + if(toCurrencies.contains(currency)) + { + common.add(currency); + } + } + + for(final Currency currency : common) + { + final Convertor convertor; + + convertor = createConvertor(from, to, currency); + possibleConversions.get(from).put(to, convertor); + possibleConversions.get(to).put(from, convertor); + newConvertors++; + } + } + } + } + } + while(newConvertors > 0); + } + + /** + * Check to see if converting between the two currencies is possible. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return true if the conversion is possible. + * @throws IllegalArgumentException if either from or to are null. + */ + public boolean canConvert(final Currency from, final Currency to) + { + final Map possible; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + possible = possibleConversions.get(from); + + if(possible.containsKey(to)) + { + return (true); + } + + return (false); + } + + /** + * Get the currencies that the convertor supports. Just because a currency is + * supported does not mean that canConvert will return true. + * + * @return the supported currencies. + */ + public Set getCurrencies() + { + final Set currencies; + + currencies = possibleConversions.keySet(); + + return (Collections.unmodifiableSet(currencies)); + } + + /** + * Get the conversion rate between two currencies. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return the conversion rate between the two currencies. + * @throws IllegalArgumentException if either from or to is null. + * @throws InvalidConversionException if canConvert would return false. + */ + public BigDecimal getConversionRate(final Currency from, final Currency to) + throws InvalidConversionException + { + final Map possible; + Convertor convertor; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + if(!(canConvert(from, to))) + { + throw new InvalidConversionException("cannot convert", to); + } + + possible = possibleConversions.get(from); + convertor = possible.get(to); + + if(convertor == null) + { + throw new Error(); + } + + return (convertor.getConversionRate(from, to)); + } + + private Convertor getConvertor(final Currency from, final Currency to) + throws InvalidConversionException + { + final Map possible; + Convertor convertor; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + if(!(canConvert(from, to))) + { + throw new InvalidConversionException("cannot convert", to); + } + + possible = possibleConversions.get(from); + convertor = possible.get(to); + + if(convertor == null) + { + throw new Error(); + } + + return (convertor); + } + + /** + * Convert an amount from one currency to another. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @param amount the amount to convert. + * @return the converted amount. + * @throws IllegalArgumentException if any of the arguments are null. + * @throws InvalidConversionException if either from or to are not valid for the convertor. + */ + public BigDecimal convert(final Currency from, + final Currency to, + final BigDecimal amount) + throws InvalidConversionException + { + final BigDecimal result; + final Convertor convertor; + + if(amount == null) + { + throw new IllegalArgumentException("amount cannot be null"); + } + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + // fixed a bug from Task2 that showed up in Task3... before we did the conversion here, + // but that meant that the underlying covnerter convert method never got called... which + // meant that in Task3 the exchange rate never changed. + convertor = getConvertor(from, to); + result = convertor.convert(from, to, amount); + + return (result.setScale(2, RoundingMode.HALF_DOWN)); + } + + /** + * Create a convertor between two currencies using another currency that is able to convert between both. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @param intermediary the currency to use as a go-between. + * @return a Convertor that is able to convert between from an to. + * @throws IllegalArgumentException if any of the arguments are null. + */ + private Convertor createConvertor(final Currency from, + final Currency to, + final Currency intermediary) + { + final Convertor fromIntermediary; + final Convertor toIntermediary; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + if(intermediary == null) + { + throw new IllegalArgumentException("intermediary cannot be null"); + } + + fromIntermediary = possibleConversions.get(from).get(intermediary); + toIntermediary = possibleConversions.get(to).get(intermediary); + + try + { + final BigDecimal fromRate; + final BigDecimal toRate; + final BigDecimal rate; + final Convertor convertor; + + fromRate = fromIntermediary.getConversionRate(from, intermediary); + toRate = toIntermediary.getConversionRate(intermediary, to); + rate = fromRate.multiply(toRate); + convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate); + + return (convertor); + } + catch (InvalidConversionException ex) + { + throw new Error(); + } + } +}