# HG changeset patch # User Jaroslav Tulach # Date 1224257612 -7200 # Node ID 8482e36a7ad228c220af618ed0388b7efdd5b45d # Parent 51f7b894eba62798f160e9cff24e6a1b4b8233bc solution 7, task4 diff -r 51f7b894eba6 -r 8482e36a7ad2 task4/solution07/src/org/apidesign/apifest08/currency/TimeRangeSpecificConvertor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution07/src/org/apidesign/apifest08/currency/TimeRangeSpecificConvertor.java Fri Oct 17 17:33:32 2008 +0200 @@ -0,0 +1,109 @@ +package org.apidesign.apifest08.currency; + +import java.util.Currency; +import java.util.Date; +import org.apidesign.apifest08.currency.Convertor.ConversionRequest; +import org.apidesign.apifest08.currency.Convertor.ConversionResult; + +/** + * This {@link Convertor} delegates to an underlying one, provided that the request specifies time between from (included) and till (excluded). + * Otherwise it just refuses to convert. + * @author jdvorak + */ +public class TimeRangeSpecificConvertor extends DelegatingConvertor { + + private final Date from; + private final Date till; + + /** + * A new time range specific convertor. + * @param old the underlying convertor one delegates to + * @param from the beginning of the time interval + * @param till the end of the time interval + * @throws IllegalArgumentException unless from comes before till + */ + public TimeRangeSpecificConvertor( final Convertor old, final Date from, final Date till ) { + super( old ); + this.from = from; + this.till = till; + if (! from.before( till ) ) { + throw new IllegalArgumentException( "from must come before till" ); + } + } + + /** + * The beginning of the time interval. + */ + public Date getFrom() { + return from; + } + + /** + * The end of the time interval. + */ + public Date getTill() { + return till; + } + + /** + * The conversion method. + * Takes a {@link TimeSpecificConversionRequest}, other {@link ConversionRequest}s will result in an exception being thrown. + * @param req the request; must not be null; must be {@link TimeSpecificConversionRequest} or a subclass thereof + * @return the response + * @throws IllegalRequestSubtypeException iff req is not instance of {@link TimeSpecificConversionRequest} + */ + @Override + public ConversionResult convert( final ConversionRequest req ) { + if ( req instanceof TimeSpecificConversionRequest ) { + final Date time = ( (TimeSpecificConversionRequest) req ).getTime(); + if ( ( from == null || !from.after(time) ) && ( till == null || time.before(till) ) ) { + return super.convert( req ); + } else { + return new ConversionResult( null ); + } + } else { + throw new IllegalRequestSubtypeException( this.getClass().getName() + ".convert() requires a TimeSpecificConversionRequest to be passed in" ); + } + } + + /** + * The request for converting a monetary amount into another currency using the conditions that apply at a particular time. + * Immutable. + */ + public static class TimeSpecificConversionRequest extends Convertor.ConversionRequest { + + private final Date time; + + /** + * A request to convert srcAmount into tgtCurrency at the current time. + * @param srcAmount the source amount; must not be null + * @param tgtCurrency the currency we want it in afterwards; must not be null + */ + public TimeSpecificConversionRequest( final MonetaryAmount srcAmount, final Currency tgtCurrency ) { + this( srcAmount, tgtCurrency, new Date() ); + } + + /** + * A request to convert srcAmount into tgtCurrency at given time. + * @param srcAmount the source amount; must not be null + * @param tgtCurrency the currency we want it in afterwards; must not be null + * @param time the time instant when the conversion is to be carried out + */ + public TimeSpecificConversionRequest( final MonetaryAmount srcAmount, final Currency tgtCurrency, final Date time ) { + super( srcAmount, tgtCurrency ); + this.time = time; + if ( time == null ) { + throw new NullPointerException( "The time of conversion" ); + } + } + + /** + * The time as of which the conversion is to be carried out. + */ + public Date getTime() { + return time; + } + + } + +} diff -r 51f7b894eba6 -r 8482e36a7ad2 task4/solution07/test/org/apidesign/apifest08/test/Task4Test.java --- a/task4/solution07/test/org/apidesign/apifest08/test/Task4Test.java Fri Oct 17 17:32:34 2008 +0200 +++ b/task4/solution07/test/org/apidesign/apifest08/test/Task4Test.java Fri Oct 17 17:33:32 2008 +0200 @@ -1,8 +1,18 @@ package org.apidesign.apifest08.test; +import org.apidesign.apifest08.currency.TimeRangeSpecificConvertor; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Currency; import java.util.Date; import junit.framework.TestCase; +import org.apidesign.apifest08.currency.ConversionRate; import org.apidesign.apifest08.currency.Convertor; +import org.apidesign.apifest08.currency.Convertor.ConversionResult; +import org.apidesign.apifest08.currency.MonetaryAmount; +import org.apidesign.apifest08.currency.TableConvertor; +import org.apidesign.apifest08.currency.TimeRangeSpecificConvertor.TimeSpecificConversionRequest; /** The exchange rates are not always the same. They are changing. However * as in order to predict the future, one needs to understand own past. That is @@ -30,6 +40,10 @@ protected void tearDown() throws Exception { } + protected static final Currency CZK = Currency.getInstance( "CZK" ); + protected static final Currency SKK = Currency.getInstance( "SKK" ); + protected static final Currency USD = Currency.getInstance( "USD" ); + // Backward compatibly enhance your existing API to support following // usecases: // @@ -40,62 +54,110 @@ * shall call some real one in the API. * * @param old existing convertor - * @param from initial date (inclusive) - * @param till final date (exclusive) + * @param from initial date (inclusive); null means since the Big Bang + * @param till final date (exclusive); null means until the End of Universe * @return new convertor */ - public static Convertor limitTo(Convertor old, Date from, Date till) { - return null; + public static Convertor limitTo( final Convertor old, final Date from, final Date till ) { + return new TimeRangeSpecificConvertor( old, from, till ); + } + + private static final DateFormat DATE_FORMAT = new SimpleDateFormat( "yyyy-MM-dd HH:mm Z" ); + + protected static Date parseGmtDate( final String string ) { + final String zonedString = string + " GMT+0:00"; + try { + return DATE_FORMAT.parse( zonedString ); + } catch ( final ParseException ex ) { + throw new IllegalArgumentException( "Cannot parse " + zonedString, ex ); + } } - public void testCompositionOfLimitedConvertors() throws Exception { - if (Boolean.getBoolean("ignore.failing")) { - // implement me! then delete this if statement - return; - } - - Date d1 = null; // 2008-10-01 0:00 GMT - Date d2 = null; // 2008-10-02 0:00 GMT - Date d3 = null; // 2008-10-03 0:00 GMT + final Date d1 = parseGmtDate( "2008-10-01 0:00" ); + final Date d2 = parseGmtDate( "2008-10-02 0:00" ); + final Date d3 = parseGmtDate( "2008-10-03 0:00" ); - Convertor c = Task2Test.merge( + final Convertor c = Task2Test.merge( limitTo(Task1Test.createCZKtoUSD(), d1, d2), limitTo(Task1Test.createSKKtoCZK(), d2, d3) ); // convert $5 to CZK using c: + final ConversionResult r1 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 5, USD ), CZK ) ); + final MonetaryAmount a1 = r1.getNetAmount(); // cannot convert as no rate is applicable to current date + assertNull( a1 ); // convert $8 to CZK using c: + final ConversionResult r2 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 8, USD ), CZK ) ); + final MonetaryAmount a2 = r2.getNetAmount(); // cannot convert as no rate is applicable to current date + assertNull( a2 ); // convert 1003CZK to USD using c: + final ConversionResult r3 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 1003, CZK ), USD ) ); + final MonetaryAmount a3 = r3.getNetAmount(); // cannot convert as no rate is applicable to current date + assertNull( a3 ); // convert 16CZK using c: + final ConversionResult r4 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 16, CZK ), USD ) ); + final MonetaryAmount a4 = r4.getNetAmount(); // cannot convert as no rate is applicable to current date + assertNull( a4 ); // convert 500SKK to CZK using c: + final ConversionResult r5 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 500, SKK ), CZK ) ); + final MonetaryAmount a5 = r5.getNetAmount(); // cannot convert as no rate is applicable to current date + assertNull( a5 ); // convert $5 to CZK using c at 2008-10-01 6:00 GMT: + final ConversionResult r6 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 5, USD ), CZK, parseGmtDate( "2008-10-01 6:00" ) ) ); + final MonetaryAmount a6 = r6.getNetAmount(); // assertEquals("Result is 85 CZK"); + assertNotNull( a6 ); + assertEquals( 85.0, a6.getAmount().doubleValue() ); + assertEquals( CZK, a6.getCurrency() ); // convert $8 to CZK using c at 2008-10-01 6:00 GMT: + final ConversionResult r7 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 8, USD ), CZK, parseGmtDate( "2008-10-01 6:00" ) ) ); + final MonetaryAmount a7 = r7.getNetAmount(); // assertEquals("Result is 136 CZK"); + assertNotNull( a7 ); + assertEquals( 136.0, a7.getAmount().doubleValue() ); + assertEquals( CZK, a7.getCurrency() ); // convert 1003CZK to USD using c at 2008-10-01 6:00 GMT: + final ConversionResult r8 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 1003, CZK ), USD, parseGmtDate( "2008-10-01 6:00" ) ) ); + final MonetaryAmount a8 = r8.getNetAmount(); // assertEquals("Result is 59 USD"); + assertNotNull( a8 ); + assertEquals( 59.0, a8.getAmount().doubleValue() ); + assertEquals( USD, a8.getCurrency() ); // convert 16CZK using c at 2008-10-02 9:00 GMT: + final ConversionResult r9 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 16, CZK ), SKK, parseGmtDate( "2008-10-02 9:00" ) ) ); + final MonetaryAmount a9 = r9.getNetAmount(); // assertEquals("Result is 20 SKK"); + assertNotNull( a9 ); + assertEquals( 20.0, a9.getAmount().doubleValue() ); + assertEquals( SKK, a9.getCurrency() ); // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT: + final ConversionResult r10 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 500, SKK ), CZK, parseGmtDate( "2008-10-02 9:00" ) ) ); + final MonetaryAmount a10 = r10.getNetAmount(); // assertEquals("Result is 400 CZK"); + assertNotNull( a10 ); + assertEquals( 400.0, a10.getAmount().doubleValue() ); + assertEquals( CZK, a10.getCurrency() ); // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT: + final ConversionResult r11 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 500, SKK ), CZK, parseGmtDate( "2008-10-01 6:00" ) ) ); + final MonetaryAmount a11 = r11.getNetAmount(); // cannot convert as no rate is applicable to current date + assertNull( a11 ); } /** Create convertor that understands two currencies, CZK and @@ -104,29 +166,39 @@ * @return prepared convertor ready for converting SKK to CZK and CZK to SKK */ public static Convertor createSKKtoCZK2() { - return null; + final TableConvertor convertor = new TableConvertor(); + final MonetaryAmount amountInSKK = new MonetaryAmount( 100, SKK ); + final MonetaryAmount amountInCZK = new MonetaryAmount( 90, CZK ); + convertor.putIntoTable( new ConversionRate( amountInSKK, amountInCZK ) ); + convertor.putIntoTable( new ConversionRate( amountInCZK, amountInSKK ) ); + return new ContractImposingDelegatingConvertor( convertor ).test(); } public void testDateConvetorWithTwoDifferentRates() throws Exception { - if (Boolean.getBoolean("ignore.failing")) { - // implement me! then delete this if statement - return; - } + final Date d1 = parseGmtDate( "2008-10-01 0:00" ); + final Date d2 = parseGmtDate( "2008-10-02 0:00" ); + final Date d3 = parseGmtDate( "2008-10-03 0:00" ); - Date d1 = null; // 2008-10-01 0:00 GMT - Date d2 = null; // 2008-10-02 0:00 GMT - Date d3 = null; // 2008-10-03 0:00 GMT - - Convertor c = Task2Test.merge( + final Convertor c = Task2Test.merge( limitTo(createSKKtoCZK2(), d1, d2), limitTo(Task1Test.createSKKtoCZK(), d2, d3) ); // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT: + final ConversionResult r1 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 500, SKK ), CZK, parseGmtDate( "2008-10-02 9:00" ) ) ); + final MonetaryAmount a1 = r1.getNetAmount(); // assertEquals("Result is 400 CZK"); + assertNotNull( a1 ); + assertEquals( 400.0, a1.getAmount().doubleValue() ); + assertEquals( CZK, a1.getCurrency() ); // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT: + final ConversionResult r2 = c.convert( new TimeSpecificConversionRequest( new MonetaryAmount( 500, SKK ), CZK, parseGmtDate( "2008-10-01 6:00" ) ) ); + final MonetaryAmount a2 = r2.getNetAmount(); // assertEquals("Result is 450 CZK"); + assertNotNull( a2 ); + assertEquals( 450.0, a2.getAmount().doubleValue() ); + assertEquals( CZK, a2.getCurrency() ); } }