# HG changeset patch # User japod@localhost # Date 1223333179 -7200 # Node ID daec35cd96e88d01500b6ea8be37a3671187d58f # Parent d333e45f6df14493ecefe1a0a304fae936c45e36 re-applying Dagi's patch diff -r d333e45f6df1 -r daec35cd96e8 task2/solution06/src/org/apidesign/apifest08/currency/Convertor.java --- a/task2/solution06/src/org/apidesign/apifest08/currency/Convertor.java Tue Oct 07 00:25:53 2008 +0200 +++ b/task2/solution06/src/org/apidesign/apifest08/currency/Convertor.java Tue Oct 07 00:46:19 2008 +0200 @@ -4,23 +4,44 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.Currency; +import java.util.List; public final class Convertor { - private final Currency first; - private final Currency second; - private final BigDecimal rateValue; // a rate between the first currency and the second currency - public static final BigDecimal one = new BigDecimal(1); + private List convertorDelegates = new ArrayList(); + + /** + * Create new instance of the converter for the given currencies and its rate. + * + * @param rateValue the rate between the first and the second currency + * @param currencyFirst the first currency + * @param currencySecond the second currency + */ public Convertor(BigDecimal rateValue, Currency currencyFirst, Currency currencySecond) { notNull(currencyFirst, "currencyFirst"); notNull(currencySecond, "currencySecond"); - notNull(rateValue, "rateValue"); + notNull(rateValue, "rateValue"); + convertorDelegates.add(new ConvertorDelegate(rateValue, currencyFirst, currencySecond)); + } + + /** + * Create new instance of the convertor from the given convertors. + * @param convertors the convertors + */ + public Convertor(Convertor... convertors) { + notNull(convertors, "convertors"); + if(convertors.length == 0) { + throw new IllegalArgumentException("There must be at least one converter."); + } - this.rateValue = rateValue; - this.first = currencyFirst; - this.second = currencySecond; + for(Convertor convertor: convertors) { + if(convertor != null) { + convertorDelegates.addAll(convertor.convertorDelegates); + } + } } /** @@ -38,27 +59,66 @@ notNull(amount, "amount"); notNull(fromCurrency, "fromCurrency"); notNull(toCurrency, "toCurrency"); + ConvertorDelegate appropriateDelegate = null; - if((fromCurrency != first && fromCurrency != second) || (toCurrency != first && toCurrency != second)) { + //try find an appropriate delegate for conversion + for(ConvertorDelegate delegate : convertorDelegates) { + if(delegate.isConversionSupported(fromCurrency, toCurrency)) { + appropriateDelegate = delegate; + break; + } + } + + if(appropriateDelegate == null) { throw new UnsupportedConversionException(fromCurrency, toCurrency); - } - - BigDecimal rateValue = getRateValue(fromCurrency, toCurrency); - BigDecimal result = rateValue.multiply(amount); - return new Amount(result, toCurrency); + } + + return appropriateDelegate.convert(amount, fromCurrency, toCurrency); } - private BigDecimal getRateValue(Currency fromCurrency, Currency toCurrency) { + /** + * Internal delegate implements a logic for conversion between two currencies + * and vice versa. + * @see #isConversionSupported(Currency, Currency) + */ + private static class ConvertorDelegate { + private final Currency first; + private final Currency second; + private final BigDecimal rateValue; // a rate between the first currency and the second currency + public static final BigDecimal one = new BigDecimal(1); - BigDecimal retVal; - - if(first == fromCurrency) { - retVal = rateValue; - } else { - //reverse rate - retVal = one.divide(rateValue, 10 ,RoundingMode.HALF_UP); + private ConvertorDelegate(BigDecimal rateValue, Currency currencyFirst, Currency currencySecond) { + this.rateValue = rateValue; + this.first = currencyFirst; + this.second = currencySecond; } - return retVal; + private Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException { + BigDecimal rateValue = getRateValue(fromCurrency, toCurrency); + BigDecimal result = rateValue.multiply(amount); + return new Amount(result, toCurrency); + } + + private BigDecimal getRateValue(Currency fromCurrency, Currency toCurrency) { + + BigDecimal retVal; + + if(first == fromCurrency) { + retVal = rateValue; + } else { + //reverse rate + retVal = one.divide(rateValue, 10 ,RoundingMode.HALF_UP); + } + + return retVal; + } + + /** + * @return true if the delegate is able to convert from the given currency + * to the given currency and vice versa otherwise false. + */ + private boolean isConversionSupported(Currency fromCurrency, Currency toCurrency) { + return ((fromCurrency == first || fromCurrency == second) && (toCurrency == first || toCurrency == second)); + } } } diff -r d333e45f6df1 -r daec35cd96e8 task2/solution06/test/org/apidesign/apifest08/test/Task2Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task2/solution06/test/org/apidesign/apifest08/test/Task2Test.java Tue Oct 07 00:46:19 2008 +0200 @@ -0,0 +1,117 @@ +package org.apidesign.apifest08.test; + +import static org.apidesign.apifest08.test.Currencies.CZK; +import static org.apidesign.apifest08.test.Currencies.USD; +import static org.apidesign.apifest08.test.Currencies.SKK; + +import java.math.BigDecimal; + +import junit.framework.TestCase; +import org.apidesign.apifest08.currency.Convertor; +import org.apidesign.apifest08.test.Task1Test; + +/** There are many currencies around the world and many banks manipulate + * with more than one or two at the same time. As banks are usually the + * best paying clients, which is true even in case of your Convertor API, + * it is reasonable to listen to their requests. + *

+ * The quest for today is to enhance your existing convertor API to hold + * information about many currencies and allow conversions between any of them. + * Also, as conversion rates for diferent currencies usually arise from various + * bank departments, there is another important need. There is a need to + * compose two convertors into one by merging all the information about + * currencies they know about. + */ +public class Task2Test extends TestCase { + public Task2Test(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + } + + @Override + protected void tearDown() throws Exception { + } + + // As in Task1Test, keep in mind, that there are three parts + // of the whole system: + // 1. there is someone who knows the current exchange rate + // 2. there is someone who wants to do the conversion + // 3. there is the API between 1. and 2. which allows them to communicate + // + // Please backward compatibly enhance your existing API to support following + // usecases: + // + + /** Create convertor that understands two currencies, CZK and + * SKK. Make 100 SKK == 75 CZK. This is method for the group of users that + * knows the exchange rate, and needs to use the API to create objects + * with the exchange rate. Anyone shall be ready to call this method without + * any other method being called previously. The API itself shall know + * nothing about any rates, before this method is called. + */ + public static Convertor createTripleConvertor() { + // Rates: 1USD = 15CZK + // Rates: 1USD = 20SKK + // Rates: 75CZK = 100SKK + Convertor usdCzk = new Convertor(new BigDecimal(15), USD, CZK); + Convertor usdSkk = new Convertor(new BigDecimal(20), USD, SKK); + Convertor skkCzk = new Convertor(new BigDecimal("0.75"), SKK, CZK); + return new Convertor(new Convertor[]{usdCzk, usdSkk, skkCzk}); + } + + /** Define convertor that understands three currencies. Use it. + */ + public void testConvertorForUSDandCZKandSKK() throws Exception { + Convertor c = createTripleConvertor(); + + // convert $5 to CZK using c: + assertEquals("Result is 75 CZK", 75 ,c.convert(new BigDecimal(5), USD, CZK).getValue().intValue()); + + // convert $5 to SKK using c: + assertEquals("Result is 100 SKK", 100, c.convert(new BigDecimal(5), USD, SKK).getValue().intValue()); + + // convert 200SKK to CZK using c: + assertEquals("Result is 150 CZK", 150, c.convert(new BigDecimal(200), SKK, CZK).getValue().intValue()); + + // convert 200SKK to USK using c: + assertEquals("Result is 10 USD", 10, c.convert(new BigDecimal(200), SKK, USD).getValue().intValue()); + } + + /** Merge all currency rates of convertor 1 with convertor 2. + * Implement this using your API, preferably this method just delegates + * into some API method which does the actual work, without requiring + * API clients to code anything complex. + */ + public static Convertor merge(Convertor one, Convertor two) { + return new Convertor(new Convertor[]{one, two}); + } + + /** Join the convertors from previous task, Task1Test and show that it + * can be used to do reasonable conversions. + */ + public void testConvertorComposition() throws Exception { + Convertor c = merge( + Task1Test.createCZKtoUSD(), + Task1Test.createSKKtoCZK() + ); + + // convert $5 to CZK using c: + assertEquals("Result is 85 CZK", 85, c.convert(new BigDecimal(5), USD, CZK).getValue().intValue()); + + // convert $8 to CZK using c: + assertEquals("Result is 136 CZK", 136, c.convert(new BigDecimal(8), USD, CZK).getValue().intValue()); + + // convert 1003CZK to USD using c: + assertEquals("Result is 59 USD", 59, c.convert(new BigDecimal(1003), CZK, USD).getValue().intValue()); + + // convert 16CZK using c: + assertEquals("Result is 20 SKK", 20, c.convert(new BigDecimal(16), CZK, SKK).getValue().intValue()); + + // convert 500SKK to CZK using c: + assertEquals("Result is 400 CZK", 400, c.convert(new BigDecimal(500), SKK, CZK).getValue().intValue()); + + } +}