task4/solution06/src/org/apidesign/apifest08/currency/Convertor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Fri, 17 Oct 2008 17:32:34 +0200
changeset 64 51f7b894eba6
parent 61 58ec6da75f6f
permissions -rw-r--r--
Solution 6, task 4
japod@6
     1
package org.apidesign.apifest08.currency;
japod@6
     2
japod@21
     3
import static org.apidesign.apifest08.currency.Assert.notNull;
japod@21
     4
japod@6
     5
import java.math.BigDecimal;
japod@21
     6
import java.math.RoundingMode;
japod@38
     7
import java.util.ArrayList;
jaroslav@64
     8
import java.util.Calendar;
japod@6
     9
import java.util.Currency;
jaroslav@64
    10
import java.util.Date;
jaroslav@64
    11
import java.util.GregorianCalendar;
japod@38
    12
import java.util.List;
jaroslav@64
    13
import java.util.TimeZone;
japod@6
    14
japod@52
    15
/**
japod@52
    16
 * Currency covertor.
japod@52
    17
 */
japod@21
    18
public final class Convertor {
japod@6
    19
	
japod@38
    20
	private List<ConvertorDelegate> convertorDelegates = new ArrayList<ConvertorDelegate>();
japod@21
    21
	
japod@38
    22
	
japod@38
    23
	/**
japod@38
    24
	 * Create new instance of the converter for the given currencies and its rate.
japod@38
    25
	 * 
japod@38
    26
	 * @param rateValue the rate between the first and the second currency
japod@38
    27
	 * @param currencyFirst the first currency
japod@38
    28
	 * @param currencySecond the second currency
japod@38
    29
	 */
japod@21
    30
	public Convertor(BigDecimal rateValue, Currency currencyFirst, Currency currencySecond) {
japod@21
    31
		notNull(currencyFirst, "currencyFirst");
japod@21
    32
		notNull(currencySecond, "currencySecond");		
japod@38
    33
		notNull(rateValue, "rateValue");	
jaroslav@64
    34
		convertorDelegates.add(new ConvertorDelegate(new StaticRateProvider(rateValue), currencyFirst, currencySecond, null, null));
japod@52
    35
	}
japod@52
    36
	
japod@52
    37
	/**
japod@52
    38
	 * Create new instance of the converter for the given currencies and its rate. 
japod@52
    39
	 * A rate value is provided by {@link RateProvider}.
japod@52
    40
	 * 
japod@52
    41
	 * @param rateProvider the rate provider
japod@52
    42
	 * @param currencyFirst the first currency
japod@52
    43
	 * @param currencySecond the second currency
japod@52
    44
	 */
japod@52
    45
	public Convertor(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond) {
japod@52
    46
		notNull(currencyFirst, "currencyFirst");
japod@52
    47
		notNull(currencySecond, "currencySecond");		
japod@52
    48
		notNull(rateProvider, "rateProvider");
jaroslav@64
    49
		convertorDelegates.add(new ConvertorDelegate(rateProvider, currencyFirst, currencySecond, null, null));
jaroslav@64
    50
	}
jaroslav@64
    51
	
jaroslav@64
    52
	/**
jaroslav@64
    53
	 * Create new instance of the convertor. The associated rate(s) is timely limited
jaroslav@64
    54
	 * by a given 'from' and 'till'. 
jaroslav@64
    55
	 * @param convertor
jaroslav@64
    56
	 * @param from
jaroslav@64
    57
	 * @param till
jaroslav@64
    58
	 */
jaroslav@64
    59
	public Convertor(Convertor convertor, Date from, Date till) {
jaroslav@64
    60
	    notNull(convertor, "convertor");
jaroslav@64
    61
		notNull(from, "from");
jaroslav@64
    62
		notNull(till, "till");				
jaroslav@64
    63
		for(ConvertorDelegate delegate: convertor.convertorDelegates) {
jaroslav@64
    64
			convertorDelegates.add(new ConvertorDelegate(delegate.rateProvider, delegate.first, delegate.second, from, till));
jaroslav@64
    65
		}
japod@38
    66
	}
japod@38
    67
	
japod@38
    68
	/**
japod@38
    69
	 * Create new instance of the convertor from the given convertors. 
japod@38
    70
	 * @param convertors the convertors
japod@38
    71
	 */
japod@38
    72
	public Convertor(Convertor... convertors) {
japod@38
    73
		notNull(convertors, "convertors");
japod@38
    74
		if(convertors.length == 0) {
japod@38
    75
			throw new IllegalArgumentException("There must be at least one converter.");
japod@38
    76
		}
japod@21
    77
		
japod@38
    78
		for(Convertor convertor: convertors) {
japod@38
    79
			if(convertor != null) {
jaroslav@64
    80
				for(ConvertorDelegate delegate: convertor.convertorDelegates) {
jaroslav@64
    81
					convertorDelegates.add(new ConvertorDelegate(delegate.rateProvider, delegate.first, delegate.second, delegate.from, delegate.till));
jaroslav@64
    82
				}
japod@38
    83
			}
japod@38
    84
		}
japod@21
    85
	}
japod@21
    86
    	
japod@6
    87
	/**
jaroslav@64
    88
	 * Converts an amount value between the two currencies of this converter. The rate is taken 
jaroslav@64
    89
	 * for current time.
japod@6
    90
	 *  
japod@6
    91
	 * @param amount an amount
japod@6
    92
	 * @param fromCurrency an amount currency
japod@6
    93
	 * @param toCurrency to a target currency
japod@6
    94
	 * @return a converted amount value
japod@6
    95
	 * 
japod@6
    96
	 * @throws ConversionException if the conversion fails
japod@6
    97
	 * @throws UnsupportedConversionException if the conversion between a given currencies is not supported.
japod@6
    98
	 */
japod@21
    99
	public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
jaroslav@64
   100
		return convert(amount, fromCurrency, toCurrency, new Date(System.currentTimeMillis()));
jaroslav@64
   101
	}
jaroslav@64
   102
jaroslav@64
   103
	/**
jaroslav@64
   104
	 * Converts an amount value between the two currencies of this converter and its 
jaroslav@64
   105
	 * associated rate for a given time.
jaroslav@64
   106
	 *  
jaroslav@64
   107
	 * @param amount an amount
jaroslav@64
   108
	 * @param fromCurrency an amount currency
jaroslav@64
   109
	 * @param toCurrency to a target currency
jaroslav@64
   110
	 * @param time time
jaroslav@64
   111
	 * @return a converted amount value
jaroslav@64
   112
	 * 
jaroslav@64
   113
	 * @throws ConversionException if the conversion fails
jaroslav@64
   114
	 * @throws UnsupportedConversionException if the conversion between a given currencies and time is not supported.
jaroslav@64
   115
	 */
jaroslav@64
   116
	public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency, Date time) throws ConversionException {
japod@21
   117
		notNull(amount, "amount");
japod@21
   118
		notNull(fromCurrency, "fromCurrency");
jaroslav@64
   119
		notNull(toCurrency, "toCurrency");		
jaroslav@64
   120
		notNull(time, "time");
jaroslav@64
   121
		
japod@38
   122
		ConvertorDelegate appropriateDelegate = null;
japod@38
   123
		//try find an appropriate delegate for conversion
japod@38
   124
		for(ConvertorDelegate delegate : convertorDelegates) {
jaroslav@64
   125
			if(delegate.isConversionSupported(fromCurrency, toCurrency) && delegate.isConversionSupported(time)) {				
jaroslav@64
   126
				appropriateDelegate = delegate;				
japod@38
   127
			}
japod@38
   128
		}
japod@38
   129
		if(appropriateDelegate == null) {
jaroslav@64
   130
			throw new UnsupportedConversionException(fromCurrency, toCurrency, time);
japod@38
   131
		}
japod@38
   132
		
jaroslav@64
   133
		return appropriateDelegate.convert(amount, fromCurrency, toCurrency);
japod@21
   134
	}
japod@21
   135
	
japod@38
   136
	/**
japod@38
   137
	 * Internal delegate implements a logic for conversion between two currencies
jaroslav@64
   138
	 * and vice versa. There could be time limitation for the associated rate.
jaroslav@64
   139
	 * 
japod@38
   140
	 * @see #isConversionSupported(Currency, Currency)
japod@38
   141
	 */
japod@38
   142
	private static class ConvertorDelegate {
jaroslav@64
   143
		
japod@38
   144
		private final Currency first;
japod@38
   145
		private final Currency second;
japod@52
   146
		private final RateProvider rateProvider;
jaroslav@64
   147
		private final Date from;
jaroslav@64
   148
		private final Date till;
japod@38
   149
		public static final BigDecimal one = new BigDecimal(1);
japod@21
   150
		
japod@52
   151
		
jaroslav@64
   152
		private ConvertorDelegate(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond, Date from, Date till) {
japod@52
   153
			this.rateProvider = rateProvider;
japod@38
   154
			this.first = currencyFirst;
japod@38
   155
			this.second = currencySecond;
jaroslav@64
   156
			this.from = from;
jaroslav@64
   157
			this.till = till;
japod@21
   158
		}
japod@21
   159
		
japod@38
   160
		private Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
japod@38
   161
			BigDecimal rateValue = getRateValue(fromCurrency, toCurrency);
japod@38
   162
			BigDecimal result = rateValue.multiply(amount);			
japod@38
   163
			return new Amount(result, toCurrency);	
japod@38
   164
		}
japod@38
   165
		
japod@38
   166
		private BigDecimal getRateValue(Currency fromCurrency, Currency toCurrency) {		
japod@38
   167
			
japod@38
   168
			BigDecimal retVal;
japod@38
   169
			
japod@38
   170
			if(first == fromCurrency) {
japod@52
   171
				BigDecimal rateValue = rateProvider.getRate();
japod@52
   172
				if(rateValue == null) {
japod@52
   173
					throw new NullPointerException("Rate cannot be null!");
japod@52
   174
				}
japod@38
   175
				retVal = rateValue;
japod@38
   176
			} else {	
japod@52
   177
				BigDecimal rateValue = rateProvider.getRate();
japod@52
   178
				if(rateValue == null) {
japod@52
   179
					throw new NullPointerException("Rate cannot be null!");
japod@52
   180
				}
japod@38
   181
				//reverse rate	
japod@38
   182
				retVal = one.divide(rateValue, 10 ,RoundingMode.HALF_UP);
japod@38
   183
			}
japod@38
   184
			
japod@38
   185
			return retVal;
japod@38
   186
		}
japod@38
   187
		
japod@38
   188
		/**
japod@38
   189
		 * @return <code>true</code> if the delegate is able to convert from the given currency
japod@38
   190
		 * to the given currency and vice versa otherwise <code>false</code>.
japod@38
   191
		 */
japod@38
   192
		private boolean isConversionSupported(Currency fromCurrency, Currency toCurrency) {
japod@38
   193
			return ((fromCurrency == first || fromCurrency == second) && (toCurrency == first || toCurrency == second));
japod@38
   194
		}
jaroslav@64
   195
		
jaroslav@64
   196
		/**
jaroslav@64
   197
		 * @return <code>true</code> if the delegate is able to convert in a given
jaroslav@64
   198
		 * time.
jaroslav@64
   199
		 */
jaroslav@64
   200
		private boolean isConversionSupported(Date time) {
jaroslav@64
   201
			boolean retVal;
jaroslav@64
   202
			if(this.from != null && this.till != null) {
jaroslav@64
   203
				retVal = getDateInGMT(from).getTime() <= getDateInGMT(time).getTime() && getDateInGMT(till).getTime() >= getDateInGMT(time).getTime();  
jaroslav@64
   204
		    } else {
jaroslav@64
   205
				retVal = true; //delegate is applicable "for eternity"				
jaroslav@64
   206
			}
jaroslav@64
   207
			return retVal;
jaroslav@64
   208
		}
jaroslav@64
   209
		
jaroslav@64
   210
		private Date getDateInGMT(Date currentDate) {
jaroslav@64
   211
			TimeZone tz = TimeZone.getTimeZone("GMT");
jaroslav@64
   212
			
jaroslav@64
   213
			Calendar mbCal = new GregorianCalendar(tz);
jaroslav@64
   214
			mbCal.setTimeInMillis(currentDate.getTime());
jaroslav@64
   215
jaroslav@64
   216
			Calendar cal = Calendar.getInstance();
jaroslav@64
   217
			cal.set(Calendar.YEAR, mbCal.get(Calendar.YEAR));
jaroslav@64
   218
			cal.set(Calendar.MONTH, mbCal.get(Calendar.MONTH));
jaroslav@64
   219
			cal.set(Calendar.DAY_OF_MONTH, mbCal.get(Calendar.DAY_OF_MONTH));
jaroslav@64
   220
			cal.set(Calendar.HOUR_OF_DAY, mbCal.get(Calendar.HOUR_OF_DAY));
jaroslav@64
   221
			cal.set(Calendar.MINUTE, mbCal.get(Calendar.MINUTE));
jaroslav@64
   222
			cal.set(Calendar.SECOND, mbCal.get(Calendar.SECOND));
jaroslav@64
   223
			cal.set(Calendar.MILLISECOND, mbCal.get(Calendar.MILLISECOND));
jaroslav@64
   224
jaroslav@64
   225
			return cal.getTime();
jaroslav@64
   226
		}
japod@21
   227
	}
japod@52
   228
	
japod@52
   229
	/**
japod@52
   230
	 * A rate provider. This class represents a way how could be "static" convertor
japod@52
   231
	 * extended in order converts according to current rate.
japod@52
   232
	 */
japod@52
   233
	public static abstract class RateProvider { 
japod@52
   234
		
japod@52
   235
		/**
japod@52
   236
		 * @return a rate between the from currency and the to currency associated with
japod@52
   237
		 * a given convertor.
japod@52
   238
		 */
japod@52
   239
		public abstract BigDecimal getRate();
japod@52
   240
	}
japod@52
   241
	
japod@52
   242
	private static class StaticRateProvider extends RateProvider{
japod@52
   243
		private final BigDecimal rateValue;
japod@52
   244
		
japod@52
   245
		private StaticRateProvider(BigDecimal rateValue){
japod@52
   246
			this.rateValue = rateValue;
japod@52
   247
		}
japod@52
   248
		
japod@52
   249
		public BigDecimal getRate() {
japod@52
   250
			return this.rateValue;
japod@52
   251
		}
japod@52
   252
	}
japod@6
   253
}