Solution 6, task 4
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Fri, 17 Oct 2008 17:32:34 +0200
changeset 6451f7b894eba6
parent 63 20d332739f60
child 65 8482e36a7ad2
Solution 6, task 4
task4/solution06/src/org/apidesign/apifest08/currency/Convertor.java
task4/solution06/src/org/apidesign/apifest08/currency/UnsupportedConversionException.java
task4/solution06/test/org/apidesign/apifest08/test/Task4Test.java
     1.1 --- a/task4/solution06/src/org/apidesign/apifest08/currency/Convertor.java	Fri Oct 17 17:31:48 2008 +0200
     1.2 +++ b/task4/solution06/src/org/apidesign/apifest08/currency/Convertor.java	Fri Oct 17 17:32:34 2008 +0200
     1.3 @@ -5,8 +5,12 @@
     1.4  import java.math.BigDecimal;
     1.5  import java.math.RoundingMode;
     1.6  import java.util.ArrayList;
     1.7 +import java.util.Calendar;
     1.8  import java.util.Currency;
     1.9 +import java.util.Date;
    1.10 +import java.util.GregorianCalendar;
    1.11  import java.util.List;
    1.12 +import java.util.TimeZone;
    1.13  
    1.14  /**
    1.15   * Currency covertor.
    1.16 @@ -27,7 +31,7 @@
    1.17  		notNull(currencyFirst, "currencyFirst");
    1.18  		notNull(currencySecond, "currencySecond");		
    1.19  		notNull(rateValue, "rateValue");	
    1.20 -		convertorDelegates.add(new ConvertorDelegate(new StaticRateProvider(rateValue), currencyFirst, currencySecond));
    1.21 +		convertorDelegates.add(new ConvertorDelegate(new StaticRateProvider(rateValue), currencyFirst, currencySecond, null, null));
    1.22  	}
    1.23  	
    1.24  	/**
    1.25 @@ -42,7 +46,23 @@
    1.26  		notNull(currencyFirst, "currencyFirst");
    1.27  		notNull(currencySecond, "currencySecond");		
    1.28  		notNull(rateProvider, "rateProvider");
    1.29 -		convertorDelegates.add(new ConvertorDelegate(rateProvider, currencyFirst, currencySecond));
    1.30 +		convertorDelegates.add(new ConvertorDelegate(rateProvider, currencyFirst, currencySecond, null, null));
    1.31 +	}
    1.32 +	
    1.33 +	/**
    1.34 +	 * Create new instance of the convertor. The associated rate(s) is timely limited
    1.35 +	 * by a given 'from' and 'till'. 
    1.36 +	 * @param convertor
    1.37 +	 * @param from
    1.38 +	 * @param till
    1.39 +	 */
    1.40 +	public Convertor(Convertor convertor, Date from, Date till) {
    1.41 +	    notNull(convertor, "convertor");
    1.42 +		notNull(from, "from");
    1.43 +		notNull(till, "till");				
    1.44 +		for(ConvertorDelegate delegate: convertor.convertorDelegates) {
    1.45 +			convertorDelegates.add(new ConvertorDelegate(delegate.rateProvider, delegate.first, delegate.second, from, till));
    1.46 +		}
    1.47  	}
    1.48  	
    1.49  	/**
    1.50 @@ -57,13 +77,16 @@
    1.51  		
    1.52  		for(Convertor convertor: convertors) {
    1.53  			if(convertor != null) {
    1.54 -				convertorDelegates.addAll(convertor.convertorDelegates);
    1.55 +				for(ConvertorDelegate delegate: convertor.convertorDelegates) {
    1.56 +					convertorDelegates.add(new ConvertorDelegate(delegate.rateProvider, delegate.first, delegate.second, delegate.from, delegate.till));
    1.57 +				}
    1.58  			}
    1.59  		}
    1.60  	}
    1.61      	
    1.62  	/**
    1.63 -	 * Converts an amount value between the two currencies of this converter.
    1.64 +	 * Converts an amount value between the two currencies of this converter. The rate is taken 
    1.65 +	 * for current time.
    1.66  	 *  
    1.67  	 * @param amount an amount
    1.68  	 * @param fromCurrency an amount currency
    1.69 @@ -74,42 +97,64 @@
    1.70  	 * @throws UnsupportedConversionException if the conversion between a given currencies is not supported.
    1.71  	 */
    1.72  	public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
    1.73 +		return convert(amount, fromCurrency, toCurrency, new Date(System.currentTimeMillis()));
    1.74 +	}
    1.75 +
    1.76 +	/**
    1.77 +	 * Converts an amount value between the two currencies of this converter and its 
    1.78 +	 * associated rate for a given time.
    1.79 +	 *  
    1.80 +	 * @param amount an amount
    1.81 +	 * @param fromCurrency an amount currency
    1.82 +	 * @param toCurrency to a target currency
    1.83 +	 * @param time time
    1.84 +	 * @return a converted amount value
    1.85 +	 * 
    1.86 +	 * @throws ConversionException if the conversion fails
    1.87 +	 * @throws UnsupportedConversionException if the conversion between a given currencies and time is not supported.
    1.88 +	 */
    1.89 +	public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency, Date time) throws ConversionException {
    1.90  		notNull(amount, "amount");
    1.91  		notNull(fromCurrency, "fromCurrency");
    1.92 -		notNull(toCurrency, "toCurrency");
    1.93 +		notNull(toCurrency, "toCurrency");		
    1.94 +		notNull(time, "time");
    1.95 +		
    1.96  		ConvertorDelegate appropriateDelegate = null;
    1.97 -		
    1.98  		//try find an appropriate delegate for conversion
    1.99  		for(ConvertorDelegate delegate : convertorDelegates) {
   1.100 -			if(delegate.isConversionSupported(fromCurrency, toCurrency)) {
   1.101 -				appropriateDelegate = delegate;
   1.102 -				break;
   1.103 +			if(delegate.isConversionSupported(fromCurrency, toCurrency) && delegate.isConversionSupported(time)) {				
   1.104 +				appropriateDelegate = delegate;				
   1.105  			}
   1.106  		}
   1.107 -		
   1.108  		if(appropriateDelegate == null) {
   1.109 -			throw new UnsupportedConversionException(fromCurrency, toCurrency);
   1.110 +			throw new UnsupportedConversionException(fromCurrency, toCurrency, time);
   1.111  		}
   1.112  		
   1.113 -		return appropriateDelegate.convert(amount, fromCurrency, toCurrency);	
   1.114 +		return appropriateDelegate.convert(amount, fromCurrency, toCurrency);
   1.115  	}
   1.116  	
   1.117  	/**
   1.118  	 * Internal delegate implements a logic for conversion between two currencies
   1.119 -	 * and vice versa.
   1.120 +	 * and vice versa. There could be time limitation for the associated rate.
   1.121 +	 * 
   1.122  	 * @see #isConversionSupported(Currency, Currency)
   1.123  	 */
   1.124  	private static class ConvertorDelegate {
   1.125 +		
   1.126  		private final Currency first;
   1.127  		private final Currency second;
   1.128  		private final RateProvider rateProvider;
   1.129 +		private final Date from;
   1.130 +		private final Date till;
   1.131  		public static final BigDecimal one = new BigDecimal(1);
   1.132  		
   1.133  		
   1.134 -		private ConvertorDelegate(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond) {
   1.135 +		private ConvertorDelegate(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond, Date from, Date till) {
   1.136  			this.rateProvider = rateProvider;
   1.137  			this.first = currencyFirst;
   1.138  			this.second = currencySecond;
   1.139 +			this.from = from;
   1.140 +			this.till = till;
   1.141  		}
   1.142  		
   1.143  		private Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
   1.144 @@ -147,6 +192,38 @@
   1.145  		private boolean isConversionSupported(Currency fromCurrency, Currency toCurrency) {
   1.146  			return ((fromCurrency == first || fromCurrency == second) && (toCurrency == first || toCurrency == second));
   1.147  		}
   1.148 +		
   1.149 +		/**
   1.150 +		 * @return <code>true</code> if the delegate is able to convert in a given
   1.151 +		 * time.
   1.152 +		 */
   1.153 +		private boolean isConversionSupported(Date time) {
   1.154 +			boolean retVal;
   1.155 +			if(this.from != null && this.till != null) {
   1.156 +				retVal = getDateInGMT(from).getTime() <= getDateInGMT(time).getTime() && getDateInGMT(till).getTime() >= getDateInGMT(time).getTime();  
   1.157 +		    } else {
   1.158 +				retVal = true; //delegate is applicable "for eternity"				
   1.159 +			}
   1.160 +			return retVal;
   1.161 +		}
   1.162 +		
   1.163 +		private Date getDateInGMT(Date currentDate) {
   1.164 +			TimeZone tz = TimeZone.getTimeZone("GMT");
   1.165 +			
   1.166 +			Calendar mbCal = new GregorianCalendar(tz);
   1.167 +			mbCal.setTimeInMillis(currentDate.getTime());
   1.168 +
   1.169 +			Calendar cal = Calendar.getInstance();
   1.170 +			cal.set(Calendar.YEAR, mbCal.get(Calendar.YEAR));
   1.171 +			cal.set(Calendar.MONTH, mbCal.get(Calendar.MONTH));
   1.172 +			cal.set(Calendar.DAY_OF_MONTH, mbCal.get(Calendar.DAY_OF_MONTH));
   1.173 +			cal.set(Calendar.HOUR_OF_DAY, mbCal.get(Calendar.HOUR_OF_DAY));
   1.174 +			cal.set(Calendar.MINUTE, mbCal.get(Calendar.MINUTE));
   1.175 +			cal.set(Calendar.SECOND, mbCal.get(Calendar.SECOND));
   1.176 +			cal.set(Calendar.MILLISECOND, mbCal.get(Calendar.MILLISECOND));
   1.177 +
   1.178 +			return cal.getTime();
   1.179 +		}
   1.180  	}
   1.181  	
   1.182  	/**
     2.1 --- a/task4/solution06/src/org/apidesign/apifest08/currency/UnsupportedConversionException.java	Fri Oct 17 17:31:48 2008 +0200
     2.2 +++ b/task4/solution06/src/org/apidesign/apifest08/currency/UnsupportedConversionException.java	Fri Oct 17 17:32:34 2008 +0200
     2.3 @@ -1,6 +1,7 @@
     2.4  package org.apidesign.apifest08.currency;
     2.5  
     2.6  import java.util.Currency;
     2.7 +import java.util.Date;
     2.8  
     2.9  public final class UnsupportedConversionException extends ConversionException{
    2.10  
    2.11 @@ -14,6 +15,12 @@
    2.12  		this.from = from;
    2.13  		this.to = to;
    2.14  	}
    2.15 +	
    2.16 +	public UnsupportedConversionException(Currency from, Currency to, Date time) {
    2.17 +		super("Conversion from  the currency " + from + " to the currency " + to + " or vice versa in not supported for time " + time );
    2.18 +		this.from = from;
    2.19 +		this.to = to;
    2.20 +	}
    2.21  
    2.22  	public Currency getFrom() {
    2.23  		return from;
     3.1 --- a/task4/solution06/test/org/apidesign/apifest08/test/Task4Test.java	Fri Oct 17 17:31:48 2008 +0200
     3.2 +++ b/task4/solution06/test/org/apidesign/apifest08/test/Task4Test.java	Fri Oct 17 17:32:34 2008 +0200
     3.3 @@ -1,8 +1,13 @@
     3.4  package org.apidesign.apifest08.test;
     3.5  
     3.6 +import java.math.BigDecimal;
     3.7 +import java.text.SimpleDateFormat;
     3.8  import java.util.Date;
     3.9  import junit.framework.TestCase;
    3.10  import org.apidesign.apifest08.currency.Convertor;
    3.11 +import org.apidesign.apifest08.currency.UnsupportedConversionException;
    3.12 +
    3.13 +import static org.apidesign.apifest08.test.Currencies.*;
    3.14  
    3.15  /** The exchange rates are not always the same. They are changing. However
    3.16   * as in order to predict the future, one needs to understand own past. That is
    3.17 @@ -45,7 +50,7 @@
    3.18       * @return new convertor
    3.19       */
    3.20      public static Convertor limitTo(Convertor old, Date from, Date till) {
    3.21 -        return null;
    3.22 +        return new Convertor(old, from, till);
    3.23      }
    3.24  
    3.25  
    3.26 @@ -54,10 +59,11 @@
    3.27              // implement me! then delete this if statement
    3.28              return;
    3.29          }
    3.30 -
    3.31 -        Date d1 = null; // 2008-10-01 0:00 GMT
    3.32 -        Date d2 = null; // 2008-10-02 0:00 GMT
    3.33 -        Date d3 = null; // 2008-10-03 0:00 GMT
    3.34 +        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm zzzz");
    3.35 +        
    3.36 +        Date d1 = df.parse("2008-10-01 0:00 GMT"); 
    3.37 +        Date d2 = df.parse("2008-10-02 0:00 GMT"); 
    3.38 +        Date d3 = df.parse("2008-10-03 0:00 GMT");
    3.39          
    3.40          Convertor c = Task2Test.merge(
    3.41              limitTo(Task1Test.createCZKtoUSD(), d1, d2),
    3.42 @@ -65,37 +71,67 @@
    3.43          );
    3.44  
    3.45          // convert $5 to CZK using c:
    3.46 -        // cannot convert as no rate is applicable to current date
    3.47 +        try {
    3.48 +        	c.convert(new BigDecimal(5), USD , CZK);
    3.49 +        	fail("cannot convert as no rate is applicable to current date");
    3.50 +        } catch(UnsupportedConversionException e) {
    3.51 +        	//expected
    3.52 +        }
    3.53  
    3.54          // convert $8 to CZK using c:
    3.55 -        // cannot convert as no rate is applicable to current date
    3.56 +        try {
    3.57 +        	c.convert(new BigDecimal(8), USD , CZK);
    3.58 +        	fail("cannot convert as no rate is applicable to current date");
    3.59 +        } catch(UnsupportedConversionException e) {
    3.60 +        	//expected
    3.61 +        }
    3.62  
    3.63          // convert 1003CZK to USD using c:
    3.64 -        // cannot convert as no rate is applicable to current date
    3.65 +        try {
    3.66 +        	c.convert(new BigDecimal(1003), CZK, USD);
    3.67 +        	fail("cannot convert as no rate is applicable to current date");
    3.68 +        } catch(UnsupportedConversionException e) {
    3.69 +        	//expected
    3.70 +        }
    3.71  
    3.72          // convert 16CZK using c:
    3.73 -        // cannot convert as no rate is applicable to current date
    3.74 +        try {
    3.75 +        	c.convert(new BigDecimal(16), CZK, USD);
    3.76 +        	fail("cannot convert as no rate is applicable to current date");
    3.77 +        } catch(UnsupportedConversionException e) {
    3.78 +        	//expected
    3.79 +        }
    3.80  
    3.81          // convert 500SKK to CZK using c:
    3.82 -        // cannot convert as no rate is applicable to current date
    3.83 +        try {
    3.84 +        	c.convert(new BigDecimal(500), SKK, CZK);
    3.85 +        	fail("cannot convert as no rate is applicable to current date");
    3.86 +        } catch(UnsupportedConversionException e) {
    3.87 +        	//expected
    3.88 +        }
    3.89  
    3.90 -        // convert $5 to CZK using c at 2008-10-01 6:00 GMT:
    3.91 -        // assertEquals("Result is 85 CZK");
    3.92 +        // convert $5 to CZK using c at 2008-10-01 6:00 GMT:         
    3.93 +        assertEquals("Result is 85 CZK", 85, c.convert(new BigDecimal(5), USD, CZK, df.parse("2008-10-01 6:00 GMT")).getValue().intValue());
    3.94  
    3.95          // convert $8 to CZK using c at 2008-10-01 6:00 GMT:
    3.96 -        // assertEquals("Result is 136 CZK");
    3.97 -
    3.98 +        assertEquals("Result is 136 CZK", 136, c.convert(new BigDecimal(8), USD, CZK, df.parse("2008-10-01 6:00 GMT")).getValue().intValue());
    3.99 +     
   3.100          // convert 1003CZK to USD using c at 2008-10-01 6:00 GMT:
   3.101 -        // assertEquals("Result is 59 USD");
   3.102 +        assertEquals("Result is 59 USD", 59, c.convert(new BigDecimal(1003), CZK, USD, df.parse("2008-10-01 6:00 GMT")).getValue().intValue());
   3.103  
   3.104          // convert 16CZK using c at 2008-10-02 9:00 GMT:
   3.105 -        // assertEquals("Result is 20 SKK");
   3.106 +        assertEquals("Result is 20 SKK", 20, c.convert(new BigDecimal(16), CZK, SKK, df.parse("2008-10-02 9:00 GMT")).getValue().intValue());
   3.107  
   3.108          // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT:
   3.109 -        // assertEquals("Result is 400 CZK");
   3.110 +        assertEquals("Result is 400 CZK", 400, c.convert(new BigDecimal(500), SKK, CZK, df.parse("2008-10-02 9:00 GMT")).getValue().intValue());
   3.111  
   3.112          // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT:
   3.113 -        // cannot convert as no rate is applicable to current date
   3.114 +        try {
   3.115 +        	c.convert(new BigDecimal(500), SKK, CZK, df.parse("2008-10-01 6:00 GMT"));
   3.116 +        	fail("cannot convert as no rate is applicable to current date");
   3.117 +        } catch(UnsupportedConversionException e) {
   3.118 +        	//expected
   3.119 +        }
   3.120      }
   3.121  
   3.122      /** Create convertor that understands two currencies, CZK and
   3.123 @@ -104,7 +140,7 @@
   3.124       * @return prepared convertor ready for converting SKK to CZK and CZK to SKK
   3.125       */
   3.126      public static Convertor createSKKtoCZK2() {
   3.127 -        return null;
   3.128 +    	return new Convertor(new BigDecimal("0.9"), SKK, CZK);
   3.129      }
   3.130  
   3.131      public void testDateConvetorWithTwoDifferentRates() throws Exception {
   3.132 @@ -112,10 +148,11 @@
   3.133              // implement me! then delete this if statement
   3.134              return;
   3.135          }
   3.136 -
   3.137 -        Date d1 = null; // 2008-10-01 0:00 GMT
   3.138 -        Date d2 = null; // 2008-10-02 0:00 GMT
   3.139 -        Date d3 = null; // 2008-10-03 0:00 GMT
   3.140 +        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm zzzz");
   3.141 +        
   3.142 +        Date d1 = df.parse("2008-10-01 0:00 GMT"); 
   3.143 +        Date d2 = df.parse("2008-10-02 0:00 GMT"); 
   3.144 +        Date d3 = df.parse("2008-10-03 0:00 GMT");
   3.145  
   3.146          Convertor c = Task2Test.merge(
   3.147              limitTo(createSKKtoCZK2(), d1, d2),
   3.148 @@ -123,10 +160,10 @@
   3.149          );
   3.150  
   3.151          // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT:
   3.152 -        // assertEquals("Result is 400 CZK");
   3.153 +        assertEquals("Result is 400 CZK", 400, c.convert(new BigDecimal(500), SKK, CZK, df.parse("2008-10-02 9:00 GMT")).getValue().intValue());
   3.154  
   3.155          // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT:
   3.156 -        // assertEquals("Result is 450 CZK");
   3.157 +        assertEquals("Result is 450 CZK", 450, c.convert(new BigDecimal(500), SKK, CZK, df.parse("2008-10-01 6:00 GMT")).getValue().intValue());
   3.158      }
   3.159  
   3.160  }