# HG changeset patch # User japod@localhost # Date 1223669115 -7200 # Node ID 14e78f48ac2b995e756b13a346792ecb76a4ebd0 # Parent 1b300c79f4cef3d1deed200a0287feb0a2b9af9c solution04 task3 diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java --- a/task3/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java Fri Oct 10 22:02:31 2008 +0200 +++ b/task3/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java Fri Oct 10 22:05:15 2008 +0200 @@ -16,7 +16,7 @@ * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors. * * @author D'Arcy Smith - * @verson 1.0 + * @verson 1.1 */ final class CompositeConvertorImpl implements Convertor @@ -220,6 +220,38 @@ return (convertor.getConversionRate(from, to)); } + private Convertor getConvertor(final Currency from, final Currency to) + throws InvalidConversionException + { + final Map possible; + Convertor convertor; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + if(!(canConvert(from, to))) + { + throw new InvalidConversionException("cannot convert", to); + } + + possible = possibleConversions.get(from); + convertor = possible.get(to); + + if(convertor == null) + { + throw new Error(); + } + + return (convertor); + } + /** * Convert an amount from one currency to another. * @@ -236,6 +268,7 @@ throws InvalidConversionException { final BigDecimal result; + final Convertor convertor; if(amount == null) { @@ -252,7 +285,11 @@ throw new IllegalArgumentException("to cannot be null"); } - result = amount.multiply(getConversionRate(from, to)); + // fixed a bug from Task2 that showed up in Task3... before we did the conversion here, + // but that meant that the underlying covnerter convert method never got called... which + // meant that in Task3 the exchange rate never changed. + convertor = getConvertor(from, to); + result = convertor.convert(from, to, amount); return (result.setScale(2, RoundingMode.HALF_DOWN)); } @@ -300,8 +337,7 @@ fromRate = fromIntermediary.getConversionRate(from, intermediary); toRate = toIntermediary.getConversionRate(intermediary, to); - rate = fromRate.multiply(toRate); - + rate = fromRate.multiply(toRate); convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate); return (convertor); diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java --- a/task3/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java Fri Oct 10 22:02:31 2008 +0200 +++ b/task3/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java Fri Oct 10 22:05:15 2008 +0200 @@ -20,12 +20,12 @@ implements Convertor { /** - * The currency to cvonvert from. + * The currency to convert from. */ private final Currency currencyA; /** - * The currency to cvonvert from. + * The currency to convert to. */ private final Currency currencyB; @@ -142,9 +142,20 @@ * @param from the currency to convert from. * @param to the currency to convert to. * @return true if the conversion is possible. + * @throws IllegalArgumentException if either from or to are null. */ public boolean canConvert(final Currency from, final Currency to) { + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + return ((from.equals(currencyA) || from.equals(currencyB)) && (to.equals(currencyA) || to.equals(currencyB))); } @@ -172,12 +183,23 @@ * @param to the currency to convert to. * @return the conversion rate between the two currencies. * @throws InvalidConversionException if canConvert would return false. + * @throws IllegalArgumentException if either from or to are null. */ public BigDecimal getConversionRate(final Currency from, final Currency to) throws InvalidConversionException - { + { final BigDecimal rate; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } if(from.equals(to)) { diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java --- a/task3/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java Fri Oct 10 22:02:31 2008 +0200 +++ b/task3/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java Fri Oct 10 22:05:15 2008 +0200 @@ -115,6 +115,11 @@ return (convertor); } + /** + * + * @param cs + * @return + */ public static Convertor mergeConvertors(final Convertor ... cs) { Convertor convertor; diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task3/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java Fri Oct 10 22:05:15 2008 +0200 @@ -0,0 +1,125 @@ +package org.apidesign.apifest08.currency; + + +import java.math.BigDecimal; +import java.util.Currency; + + +/** + * The exchange rate between two currencies. + * + * @author D'Arcy Smith + * @version 1.0 + */ +public final class ExchangeRate +{ + /** + * + */ + private final Currency currencyA; + + /** + * + */ + private final Currency currencyB; + + /** + * + */ + private final BigDecimal rateAtoB; + + /** + * + */ + private final BigDecimal rateBtoA; + + /** + * Construct an ExchangeRate with the specified values. + * + * @param a the first currency + * @param b the second currency + * @param ra the rate to convert a to b + * @param rb the rate to covertt b to a + * @throws IllegalArgumentException if any parameter is null. + */ + public ExchangeRate(final Currency a, + final Currency b, + final BigDecimal ra, + final BigDecimal rb) + { + if(a == null) + { + throw new IllegalArgumentException("a cannot be null"); + } + + if(b == null) + { + throw new IllegalArgumentException("b cannot be null"); + } + + if(ra == null) + { + throw new IllegalArgumentException("ra cannot be null"); + } + + if(rb == null) + { + throw new IllegalArgumentException("rb cannot be null"); + } + + if(ra.compareTo(BigDecimal.ZERO) <= 0) + { + throw new IllegalArgumentException("ra cannot be <= 0, was: " + ra); + } + + if(rb.compareTo(BigDecimal.ZERO) <= 0) + { + throw new IllegalArgumentException("rb cannot be <= 0, was: " + ra); + } + + currencyA = a; + currencyB = b; + rateAtoB = ra; + rateBtoA = rb; + } + + /** + * Get the first currency. + * + * @return the first currency. + */ + public Currency getCurrencyA() + { + return currencyA; + } + + /** + * Get the second currency. + * + * @return the second currency. + */ + public Currency getCurrencyB() + { + return currencyB; + } + + /** + * Get the conversion rate from currencyA to currencyB. + * + * @return the conversion rate from currencyA to currencyB. + */ + public BigDecimal getRateAtoB() + { + return rateAtoB; + } + + /** + * Get the conversion rate from currencyB to currencyA. + * + * @return the conversion rate from currencyB to currencyA. + */ + public BigDecimal getRateBtoA() + { + return rateBtoA; + } +} diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/src/org/apidesign/apifest08/currency/ExchangeRateFinder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task3/solution04/src/org/apidesign/apifest08/currency/ExchangeRateFinder.java Fri Oct 10 22:05:15 2008 +0200 @@ -0,0 +1,22 @@ +package org.apidesign.apifest08.currency; + +import java.util.Currency; + + +/** + * Used to look up the exchange rate between two currencies. + * + * @author D'Arcy Smith + * @version 1.0 + */ +public interface ExchangeRateFinder +{ + /** + * Find the exchange rate between two currencies. + * + * @param a the currency to convert from. + * @param b the currency to convert to. + * @return the exchange rate for conversions between the two currencies. + */ + ExchangeRate findRate(Currency a, Currency b); +} diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/src/org/apidesign/apifest08/currency/OnlineConvertor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task3/solution04/src/org/apidesign/apifest08/currency/OnlineConvertor.java Fri Oct 10 22:05:15 2008 +0200 @@ -0,0 +1,175 @@ +package org.apidesign.apifest08.currency; + + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.Set; + + +/** + * A Convertor that looks up the exchange rate with each call to "convert". + * + * @author D'Arcy Smith + * @version 1.0 + */ +public class OnlineConvertor + implements Convertor +{ + /** + * The currency to convert from. + */ + private final Currency currencyA; + + /** + * The currency to convert to. + */ + private final Currency currencyB; + + /** + * Used to find the current exchange rate. + */ + private final ExchangeRateFinder finder; + + /** + * The convertor to perform the conversion. + */ + private Convertor realConvertor; + + /** + * Constructs an OnlinConvertor with the specified currencies. + * + * @param a the currency to convert from. + * @param b the currency to convert to. + * @param f the finder used to obtanin the current exchange rate. + * @throws IllegalArgumentException if either a or b are null. + */ + public OnlineConvertor(final Currency a, + final Currency b, + final ExchangeRateFinder f) + { + if(a == null) + { + throw new IllegalArgumentException("a cannot be null"); + } + + if(b == null) + { + throw new IllegalArgumentException("b cannot be null"); + } + + if(f == null) + { + throw new IllegalArgumentException("f cannot be null"); + } + + currencyA = a; + currencyB = b; + finder = f; + realConvertor = lookupRate(); + } + + /** + * Convert an amount from one currency to another. Before the conversion takes place + * the current exchange rate is looked up. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @param amount the amount to convert. + * @return the converted amount. + * @throws IllegalArgumentException if any of the arguments are null. + * @throws InvalidConversionException if either from or to are not equal to the currencies passed to the constructor. + */ + public BigDecimal convert(final Currency from, + final Currency to, + final BigDecimal amount) + throws InvalidConversionException + { + final BigDecimal value; + + synchronized(this) + { + realConvertor = lookupRate(); + value = realConvertor.convert(from, to, amount); + } + + return (value); + } + + /** + * Lookup the current exchange rate. + * + * @return + */ + private final Convertor lookupRate() + { + final Convertor convertor; + final ExchangeRate rate; + + rate = finder.findRate(currencyA, currencyB); + convertor = ConvertorFactory.getConvertor(rate.getCurrencyA(), rate.getRateAtoB(), rate.getCurrencyB(), rate.getRateBtoA()); + + return (convertor); + } + + /** + * Check to see if converting between the two currencies is possible. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return true if the conversion is possible. + * @throws IllegalArgumentException if either from or to are null. + */ + public boolean canConvert(final Currency from, + final Currency to) + { + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + return (realConvertor.canConvert(from, to)); + } + + /** + * Get the currencies that the convertor supports. + * + * @return the supported currencies. + */ + public Set getCurrencies() + { + return (realConvertor.getCurrencies()); + } + + /** + * Get the conversion rate between two currencies. This does not lookup the current + * conversion rate (it probably should, but given the way the contest works that might + * not be a good idea - if it were the real world way it might be a good idea). + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return the conversion rate between the two currencies. + * @throws InvalidConversionException if canConvert would return false. + * @throws IllegalArgumentException if either from or to are null. + */ + public BigDecimal getConversionRate(final Currency from, + final Currency to) + throws InvalidConversionException + { + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + return (realConvertor.getConversionRate(from, to)); + } +} diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/test/org/apidesign/apifest08/test/Task3Test.java --- a/task3/solution04/test/org/apidesign/apifest08/test/Task3Test.java Fri Oct 10 22:02:31 2008 +0200 +++ b/task3/solution04/test/org/apidesign/apifest08/test/Task3Test.java Fri Oct 10 22:05:15 2008 +0200 @@ -1,7 +1,11 @@ package org.apidesign.apifest08.test; +import java.math.BigDecimal; +import java.util.Currency; import junit.framework.TestCase; import org.apidesign.apifest08.currency.Convertor; +import org.apidesign.apifest08.currency.InvalidConversionException; +import org.apidesign.apifest08.currency.OnlineConvertor; /** The exchange rates are not always the same. They are changing. Day by day, * hour by hour, minute by minute. For every bank it is important to always @@ -15,17 +19,33 @@ * he does not know how the whole system looks and how the convertor is supposed * to be used. */ -public class Task3Test extends TestCase { - public Task3Test(String testName) { +public class Task3Test + extends TestCase +{ + private final static Currency CZK; + private final static Currency SKK; + private final static Currency USD; + + static + { + CZK = Currency.getInstance("CZK"); + SKK = Currency.getInstance("SKK"); + USD = Currency.getInstance("USD"); + } + + public Task3Test(String testName) + { super(testName); } @Override - protected void setUp() throws Exception { + protected void setUp() throws Exception + { } @Override - protected void tearDown() throws Exception { + protected void tearDown() throws Exception + { } // Backward compatibly enhance your existing API to support following @@ -41,8 +61,21 @@ * until you reach 1USD = 16CZK * * @return new instance of "online" USD and CZK convertor starting with rate 1USD = 16CZK + * @throws InvalidConversionException */ - public static Convertor createOnlineCZKUSDConvertor() { + public static Convertor createOnlineCZKUSDConvertor() + throws InvalidConversionException + { + final Convertor convertor; + + convertor = new OnlineConvertor(USD, + CZK, + new TestExchangeRateFinder(new BigDecimal("15.00"), + new BigDecimal("16.00"), + new BigDecimal("16.00"), + new BigDecimal("0.01"), + new BigDecimal("-0.01"))); + // initial rate: 1USD = 16CZK // 2nd query 1USD = 15.99CZK // 3rd query 1USD = 15.98CZK @@ -51,10 +84,12 @@ // then 1USD = 15.02CZK // and so on and on up to 1USD = 16CZK // and then another round to 15, etc. - return null; + return convertor; } - public void testFewQueriesForOnlineConvertor() { + public void testFewQueriesForOnlineConvertor() + throws InvalidConversionException + { if (Boolean.getBoolean("ignore.failing")) { // implement me! return; @@ -64,25 +99,37 @@ doFewQueriesForOnlineConvertor(c); } - static void doFewQueriesForOnlineConvertor(Convertor c) { + static void doFewQueriesForOnlineConvertor(Convertor c) + throws InvalidConversionException + { + BigDecimal amount; + // convert $5 to CZK using c: //assertEquals("Result is 80 CZK"); + amount = c.convert(USD, CZK, new BigDecimal("5.00")); + assertEquals(new BigDecimal("80.00"), amount); // convert $8 to CZK using c: //assertEquals("Result is 127.92 CZK"); + amount = c.convert(USD, CZK, new BigDecimal("8.00")); + assertEquals(new BigDecimal("127.92"), amount); // convert $1 to CZK using c: //assertEquals("Result is 15.98 CZK"); + amount = c.convert(USD, CZK, new BigDecimal("1.00")); + assertEquals(new BigDecimal("15.98"), amount); // convert 15.97CZK to USD using c: //assertEquals("Result is 1$"); - - fail("Implement me!"); + amount = c.convert(CZK, USD, new BigDecimal("15.97")); + assertEquals(new BigDecimal("1.00"), amount); } /** Join the convertors and show they behave sane. */ public void testOnlineConvertorComposition() throws Exception { + BigDecimal amount; + if (Boolean.getBoolean("ignore.failing")) { // implement me! return; @@ -95,9 +142,13 @@ // convert 16CZK to SKK using c: // assertEquals("Result is 20 SKK"); - + amount = c.convert(CZK, SKK, new BigDecimal("16.00")); + assertEquals(new BigDecimal("20.00"), amount); + // convert 500SKK to CZK using c: // assertEquals("Result is 400 CZK"); + amount = c.convert(SKK, CZK, new BigDecimal("500.00")); + assertEquals(new BigDecimal("400.00"), amount); doFewQueriesForOnlineConvertor(c); } diff -r 1b300c79f4ce -r 14e78f48ac2b task3/solution04/test/org/apidesign/apifest08/test/TestExchangeRateFinder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task3/solution04/test/org/apidesign/apifest08/test/TestExchangeRateFinder.java Fri Oct 10 22:05:15 2008 +0200 @@ -0,0 +1,61 @@ +package org.apidesign.apifest08.test; + + +import java.math.BigDecimal; +import java.util.Currency; +import org.apidesign.apifest08.currency.ExchangeRate; +import org.apidesign.apifest08.currency.ExchangeRateFinder; + + +class TestExchangeRateFinder + implements ExchangeRateFinder +{ + private final BigDecimal min; + private final BigDecimal max; + private final BigDecimal stepUp; + private final BigDecimal stepDown; + private BigDecimal step; + private BigDecimal rate; + private boolean firstCall; + + TestExchangeRateFinder(final BigDecimal mn, + final BigDecimal mx, + final BigDecimal start, + final BigDecimal up, + final BigDecimal down) + { + min = mn; + max = mx; + rate = start; + stepUp = up; + stepDown = down; + firstCall = true; + } + + public ExchangeRate findRate(Currency a, Currency b) + { + final ExchangeRate value; + + if(rate.equals(max)) + { + step = stepDown; + } + else if(rate.equals(min)) + { + step = stepUp; + } + + value = new ExchangeRate(a, b, BigDecimal.ONE, rate); + + if(firstCall) + { + firstCall = false; + } + else + { + rate = rate.add(step); + } + + return (value); + } +}