1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/task4/solution06/src/org/apidesign/apifest08/currency/Convertor.java Sat Oct 11 23:38:46 2008 +0200
1.3 @@ -0,0 +1,176 @@
1.4 +package org.apidesign.apifest08.currency;
1.5 +
1.6 +import static org.apidesign.apifest08.currency.Assert.notNull;
1.7 +
1.8 +import java.math.BigDecimal;
1.9 +import java.math.RoundingMode;
1.10 +import java.util.ArrayList;
1.11 +import java.util.Currency;
1.12 +import java.util.List;
1.13 +
1.14 +/**
1.15 + * Currency covertor.
1.16 + */
1.17 +public final class Convertor {
1.18 +
1.19 + private List<ConvertorDelegate> convertorDelegates = new ArrayList<ConvertorDelegate>();
1.20 +
1.21 +
1.22 + /**
1.23 + * Create new instance of the converter for the given currencies and its rate.
1.24 + *
1.25 + * @param rateValue the rate between the first and the second currency
1.26 + * @param currencyFirst the first currency
1.27 + * @param currencySecond the second currency
1.28 + */
1.29 + public Convertor(BigDecimal rateValue, Currency currencyFirst, Currency currencySecond) {
1.30 + notNull(currencyFirst, "currencyFirst");
1.31 + notNull(currencySecond, "currencySecond");
1.32 + notNull(rateValue, "rateValue");
1.33 + convertorDelegates.add(new ConvertorDelegate(new StaticRateProvider(rateValue), currencyFirst, currencySecond));
1.34 + }
1.35 +
1.36 + /**
1.37 + * Create new instance of the converter for the given currencies and its rate.
1.38 + * A rate value is provided by {@link RateProvider}.
1.39 + *
1.40 + * @param rateProvider the rate provider
1.41 + * @param currencyFirst the first currency
1.42 + * @param currencySecond the second currency
1.43 + */
1.44 + public Convertor(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond) {
1.45 + notNull(currencyFirst, "currencyFirst");
1.46 + notNull(currencySecond, "currencySecond");
1.47 + notNull(rateProvider, "rateProvider");
1.48 + convertorDelegates.add(new ConvertorDelegate(rateProvider, currencyFirst, currencySecond));
1.49 + }
1.50 +
1.51 + /**
1.52 + * Create new instance of the convertor from the given convertors.
1.53 + * @param convertors the convertors
1.54 + */
1.55 + public Convertor(Convertor... convertors) {
1.56 + notNull(convertors, "convertors");
1.57 + if(convertors.length == 0) {
1.58 + throw new IllegalArgumentException("There must be at least one converter.");
1.59 + }
1.60 +
1.61 + for(Convertor convertor: convertors) {
1.62 + if(convertor != null) {
1.63 + convertorDelegates.addAll(convertor.convertorDelegates);
1.64 + }
1.65 + }
1.66 + }
1.67 +
1.68 + /**
1.69 + * Converts an amount value between the two currencies of this converter.
1.70 + *
1.71 + * @param amount an amount
1.72 + * @param fromCurrency an amount currency
1.73 + * @param toCurrency to a target currency
1.74 + * @return a converted amount value
1.75 + *
1.76 + * @throws ConversionException if the conversion fails
1.77 + * @throws UnsupportedConversionException if the conversion between a given currencies is not supported.
1.78 + */
1.79 + public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
1.80 + notNull(amount, "amount");
1.81 + notNull(fromCurrency, "fromCurrency");
1.82 + notNull(toCurrency, "toCurrency");
1.83 + ConvertorDelegate appropriateDelegate = null;
1.84 +
1.85 + //try find an appropriate delegate for conversion
1.86 + for(ConvertorDelegate delegate : convertorDelegates) {
1.87 + if(delegate.isConversionSupported(fromCurrency, toCurrency)) {
1.88 + appropriateDelegate = delegate;
1.89 + break;
1.90 + }
1.91 + }
1.92 +
1.93 + if(appropriateDelegate == null) {
1.94 + throw new UnsupportedConversionException(fromCurrency, toCurrency);
1.95 + }
1.96 +
1.97 + return appropriateDelegate.convert(amount, fromCurrency, toCurrency);
1.98 + }
1.99 +
1.100 + /**
1.101 + * Internal delegate implements a logic for conversion between two currencies
1.102 + * and vice versa.
1.103 + * @see #isConversionSupported(Currency, Currency)
1.104 + */
1.105 + private static class ConvertorDelegate {
1.106 + private final Currency first;
1.107 + private final Currency second;
1.108 + private final RateProvider rateProvider;
1.109 + public static final BigDecimal one = new BigDecimal(1);
1.110 +
1.111 +
1.112 + private ConvertorDelegate(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond) {
1.113 + this.rateProvider = rateProvider;
1.114 + this.first = currencyFirst;
1.115 + this.second = currencySecond;
1.116 + }
1.117 +
1.118 + private Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
1.119 + BigDecimal rateValue = getRateValue(fromCurrency, toCurrency);
1.120 + BigDecimal result = rateValue.multiply(amount);
1.121 + return new Amount(result, toCurrency);
1.122 + }
1.123 +
1.124 + private BigDecimal getRateValue(Currency fromCurrency, Currency toCurrency) {
1.125 +
1.126 + BigDecimal retVal;
1.127 +
1.128 + if(first == fromCurrency) {
1.129 + BigDecimal rateValue = rateProvider.getRate();
1.130 + if(rateValue == null) {
1.131 + throw new NullPointerException("Rate cannot be null!");
1.132 + }
1.133 + retVal = rateValue;
1.134 + } else {
1.135 + BigDecimal rateValue = rateProvider.getRate();
1.136 + if(rateValue == null) {
1.137 + throw new NullPointerException("Rate cannot be null!");
1.138 + }
1.139 + //reverse rate
1.140 + retVal = one.divide(rateValue, 10 ,RoundingMode.HALF_UP);
1.141 + }
1.142 +
1.143 + return retVal;
1.144 + }
1.145 +
1.146 + /**
1.147 + * @return <code>true</code> if the delegate is able to convert from the given currency
1.148 + * to the given currency and vice versa otherwise <code>false</code>.
1.149 + */
1.150 + private boolean isConversionSupported(Currency fromCurrency, Currency toCurrency) {
1.151 + return ((fromCurrency == first || fromCurrency == second) && (toCurrency == first || toCurrency == second));
1.152 + }
1.153 + }
1.154 +
1.155 + /**
1.156 + * A rate provider. This class represents a way how could be "static" convertor
1.157 + * extended in order converts according to current rate.
1.158 + */
1.159 + public static abstract class RateProvider {
1.160 +
1.161 + /**
1.162 + * @return a rate between the from currency and the to currency associated with
1.163 + * a given convertor.
1.164 + */
1.165 + public abstract BigDecimal getRate();
1.166 + }
1.167 +
1.168 + private static class StaticRateProvider extends RateProvider{
1.169 + private final BigDecimal rateValue;
1.170 +
1.171 + private StaticRateProvider(BigDecimal rateValue){
1.172 + this.rateValue = rateValue;
1.173 + }
1.174 +
1.175 + public BigDecimal getRate() {
1.176 + return this.rateValue;
1.177 + }
1.178 + }
1.179 +}