task4/solution06/src/org/apidesign/apifest08/currency/Convertor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Sat, 11 Oct 2008 23:38:46 +0200
changeset 61 58ec6da75f6f
parent 52 task3/solution06/src/org/apidesign/apifest08/currency/Convertor.java@c38391fb9b38
child 64 51f7b894eba6
permissions -rw-r--r--
Copying structure for task4
     1 package org.apidesign.apifest08.currency;
     2 
     3 import static org.apidesign.apifest08.currency.Assert.notNull;
     4 
     5 import java.math.BigDecimal;
     6 import java.math.RoundingMode;
     7 import java.util.ArrayList;
     8 import java.util.Currency;
     9 import java.util.List;
    10 
    11 /**
    12  * Currency covertor.
    13  */
    14 public final class Convertor {
    15 	
    16 	private List<ConvertorDelegate> convertorDelegates = new ArrayList<ConvertorDelegate>();
    17 	
    18 	
    19 	/**
    20 	 * Create new instance of the converter for the given currencies and its rate.
    21 	 * 
    22 	 * @param rateValue the rate between the first and the second currency
    23 	 * @param currencyFirst the first currency
    24 	 * @param currencySecond the second currency
    25 	 */
    26 	public Convertor(BigDecimal rateValue, Currency currencyFirst, Currency currencySecond) {
    27 		notNull(currencyFirst, "currencyFirst");
    28 		notNull(currencySecond, "currencySecond");		
    29 		notNull(rateValue, "rateValue");	
    30 		convertorDelegates.add(new ConvertorDelegate(new StaticRateProvider(rateValue), currencyFirst, currencySecond));
    31 	}
    32 	
    33 	/**
    34 	 * Create new instance of the converter for the given currencies and its rate. 
    35 	 * A rate value is provided by {@link RateProvider}.
    36 	 * 
    37 	 * @param rateProvider the rate provider
    38 	 * @param currencyFirst the first currency
    39 	 * @param currencySecond the second currency
    40 	 */
    41 	public Convertor(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond) {
    42 		notNull(currencyFirst, "currencyFirst");
    43 		notNull(currencySecond, "currencySecond");		
    44 		notNull(rateProvider, "rateProvider");
    45 		convertorDelegates.add(new ConvertorDelegate(rateProvider, currencyFirst, currencySecond));
    46 	}
    47 	
    48 	/**
    49 	 * Create new instance of the convertor from the given convertors. 
    50 	 * @param convertors the convertors
    51 	 */
    52 	public Convertor(Convertor... convertors) {
    53 		notNull(convertors, "convertors");
    54 		if(convertors.length == 0) {
    55 			throw new IllegalArgumentException("There must be at least one converter.");
    56 		}
    57 		
    58 		for(Convertor convertor: convertors) {
    59 			if(convertor != null) {
    60 				convertorDelegates.addAll(convertor.convertorDelegates);
    61 			}
    62 		}
    63 	}
    64     	
    65 	/**
    66 	 * Converts an amount value between the two currencies of this converter.
    67 	 *  
    68 	 * @param amount an amount
    69 	 * @param fromCurrency an amount currency
    70 	 * @param toCurrency to a target currency
    71 	 * @return a converted amount value
    72 	 * 
    73 	 * @throws ConversionException if the conversion fails
    74 	 * @throws UnsupportedConversionException if the conversion between a given currencies is not supported.
    75 	 */
    76 	public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
    77 		notNull(amount, "amount");
    78 		notNull(fromCurrency, "fromCurrency");
    79 		notNull(toCurrency, "toCurrency");
    80 		ConvertorDelegate appropriateDelegate = null;
    81 		
    82 		//try find an appropriate delegate for conversion
    83 		for(ConvertorDelegate delegate : convertorDelegates) {
    84 			if(delegate.isConversionSupported(fromCurrency, toCurrency)) {
    85 				appropriateDelegate = delegate;
    86 				break;
    87 			}
    88 		}
    89 		
    90 		if(appropriateDelegate == null) {
    91 			throw new UnsupportedConversionException(fromCurrency, toCurrency);
    92 		}
    93 		
    94 		return appropriateDelegate.convert(amount, fromCurrency, toCurrency);	
    95 	}
    96 	
    97 	/**
    98 	 * Internal delegate implements a logic for conversion between two currencies
    99 	 * and vice versa.
   100 	 * @see #isConversionSupported(Currency, Currency)
   101 	 */
   102 	private static class ConvertorDelegate {
   103 		private final Currency first;
   104 		private final Currency second;
   105 		private final RateProvider rateProvider;
   106 		public static final BigDecimal one = new BigDecimal(1);
   107 		
   108 		
   109 		private ConvertorDelegate(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond) {
   110 			this.rateProvider = rateProvider;
   111 			this.first = currencyFirst;
   112 			this.second = currencySecond;
   113 		}
   114 		
   115 		private Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
   116 			BigDecimal rateValue = getRateValue(fromCurrency, toCurrency);
   117 			BigDecimal result = rateValue.multiply(amount);			
   118 			return new Amount(result, toCurrency);	
   119 		}
   120 		
   121 		private BigDecimal getRateValue(Currency fromCurrency, Currency toCurrency) {		
   122 			
   123 			BigDecimal retVal;
   124 			
   125 			if(first == fromCurrency) {
   126 				BigDecimal rateValue = rateProvider.getRate();
   127 				if(rateValue == null) {
   128 					throw new NullPointerException("Rate cannot be null!");
   129 				}
   130 				retVal = rateValue;
   131 			} else {	
   132 				BigDecimal rateValue = rateProvider.getRate();
   133 				if(rateValue == null) {
   134 					throw new NullPointerException("Rate cannot be null!");
   135 				}
   136 				//reverse rate	
   137 				retVal = one.divide(rateValue, 10 ,RoundingMode.HALF_UP);
   138 			}
   139 			
   140 			return retVal;
   141 		}
   142 		
   143 		/**
   144 		 * @return <code>true</code> if the delegate is able to convert from the given currency
   145 		 * to the given currency and vice versa otherwise <code>false</code>.
   146 		 */
   147 		private boolean isConversionSupported(Currency fromCurrency, Currency toCurrency) {
   148 			return ((fromCurrency == first || fromCurrency == second) && (toCurrency == first || toCurrency == second));
   149 		}
   150 	}
   151 	
   152 	/**
   153 	 * A rate provider. This class represents a way how could be "static" convertor
   154 	 * extended in order converts according to current rate.
   155 	 */
   156 	public static abstract class RateProvider { 
   157 		
   158 		/**
   159 		 * @return a rate between the from currency and the to currency associated with
   160 		 * a given convertor.
   161 		 */
   162 		public abstract BigDecimal getRate();
   163 	}
   164 	
   165 	private static class StaticRateProvider extends RateProvider{
   166 		private final BigDecimal rateValue;
   167 		
   168 		private StaticRateProvider(BigDecimal rateValue){
   169 			this.rateValue = rateValue;
   170 		}
   171 		
   172 		public BigDecimal getRate() {
   173 			return this.rateValue;
   174 		}
   175 	}
   176 }