# HG changeset patch # User japod@localhost # Date 1223463112 -7200 # Node ID de033c457beda55679e5be8bddd27c02511a2283 # Parent 79a576394dd7a7417d53065a414d0f266fbc4975 adding solution14 for task2 diff -r 79a576394dd7 -r de033c457bed task2/solution14/src/org/apidesign/apifest08/currency/Convertor.java --- a/task2/solution14/src/org/apidesign/apifest08/currency/Convertor.java Tue Oct 07 11:19:36 2008 +0200 +++ b/task2/solution14/src/org/apidesign/apifest08/currency/Convertor.java Wed Oct 08 12:51:52 2008 +0200 @@ -1,5 +1,12 @@ package org.apidesign.apifest08.currency; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + /** This is the skeleton class for your API. You need to make it public, so * it is accessible to your client code (currently in Task1Test.java) file. *

@@ -8,12 +15,21 @@ * to other packages. */ public final class Convertor { - private String currency1; - private String currency2; - private Rate rate; - + //version 1 fields + + private String currency1 = null; + private String currency2 = null; + private Rate rate = null; + + //version 2 field + private List currencyRates = null; + + //version - for compatible mode + private int instanceVersion = 0; //compatible mode because of problem with empty currency and CZE -> CZE (1:2) rate + Convertor(String currency1, String currency2, Rate rate) { - if ((currency1 == null)||(currency2 == null)||(rate == null)) { + instanceVersion = 1; + if ((currency1 == null) || (currency2 == null) || (rate == null)) { throw new IllegalArgumentException("All arguments have to be non-null."); } this.currency1 = currency1; @@ -21,68 +37,165 @@ this.rate = rate; } + Convertor(final CurrencyRate ... currencyRate) { + instanceVersion = 2; + + if (currencyRate == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + if (currencyRate.length == 0) { + throw new IllegalArgumentException("CurrencyRates cannot be empty."); + } + Set> currencies = new HashSet>(); + List curRates = new ArrayList(); + for (int i = 0; i < currencyRate.length; i++) { + CurrencyRate curRat = currencyRate[i]; + if (curRat == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + //check that currencyRate is not defined twice + Pair curPair= new Pair(curRat.getCurrency1(), curRat.getCurrency2()); + if (currencies.contains(curPair)) { + throw new IllegalArgumentException("Pair of currencies in a currency rate cannot be defined twice"); + } + currencies.add(curPair); + + curRates.add(curRat); + } + this.currencyRates = Collections.unmodifiableList(curRates); + } + public double convert(String fromCurrency, String toCurrency, int amount) { - if ((fromCurrency == null)||(toCurrency == null)) { - throw new IllegalArgumentException("All arguments have to be non-null."); - } - - if (currency1.equals(fromCurrency) && currency2.equals(toCurrency)) { - return rate.convertAtoB(amount); - } else if (currency2.equals(fromCurrency) && currency1.equals(toCurrency)) { - return rate.convertBtoA(amount); - } else { - throw new IllegalArgumentException("Convertor " + this.toString() + - " cannot work with currencies " + fromCurrency + " and " + toCurrency + "."); + if (instanceVersion == 1) { + if ((fromCurrency == null) || (toCurrency == null)) { + throw new IllegalArgumentException("All arguments have to be non-null."); + } + + if (currency1.equals(fromCurrency) && currency2.equals(toCurrency)) { + return rate.convertAtoB(amount); + } else if (currency2.equals(fromCurrency) && currency1.equals(toCurrency)) { + return rate.convertBtoA(amount); + } else { + throw new IllegalArgumentException("Convertor " + this.toString() + + " cannot work with currencies " + fromCurrency + " and " + toCurrency + "."); + } + } else { //instanceVersion == 2 + //find suitable convertor + for (CurrencyRate curRate : currencyRates) { + if ((curRate.getCurrency1().equals(fromCurrency))&& + (curRate.getCurrency2().equals(toCurrency))) + { + return curRate.getRate().convertAtoB(amount); + } + } + //suitable convertor not found, try to find inverse convertor + for (CurrencyRate curRate : currencyRates) { + if ((curRate.getCurrency2().equals(fromCurrency))&& + (curRate.getCurrency1().equals(toCurrency))) + { + return curRate.getRate().convertBtoA(amount); + } + } + //even inverse convertor not found + throw new IllegalArgumentException("Cannot work with selected currencies."); } } public double convert(String fromCurrency, String toCurrency, double amount) { - if ((fromCurrency == null)||(toCurrency == null)) { - throw new IllegalArgumentException("All arguments have to be non-null."); - } - - if (currency1.equals(fromCurrency) && currency2.equals(toCurrency)) { - return rate.convertAtoB(amount); - } else if (currency2.equals(fromCurrency) && currency1.equals(toCurrency)) { - return rate.convertBtoA(amount); - } else { - throw new IllegalArgumentException("Convertor " + this.toString() + - " cannot work with currencies " + fromCurrency + " and " + toCurrency + "."); + if (instanceVersion == 1) { + if ((fromCurrency == null) || (toCurrency == null)) { + throw new IllegalArgumentException("All arguments have to be non-null."); + } + + if (currency1.equals(fromCurrency) && currency2.equals(toCurrency)) { + return rate.convertAtoB(amount); + } else if (currency2.equals(fromCurrency) && currency1.equals(toCurrency)) { + return rate.convertBtoA(amount); + } else { + throw new IllegalArgumentException("Convertor " + this.toString() + + " cannot work with currencies " + fromCurrency + " and " + toCurrency + "."); + } + } else { //instanceVersion == 2 + //find suitable convertor + for (CurrencyRate curRate : currencyRates) { + if ((curRate.getCurrency1().equals(fromCurrency))&& + (curRate.getCurrency2().equals(toCurrency))) + { + return curRate.getRate().convertAtoB(amount); + } + } + //suitable convertor not found, try to find inverse convertor + for (CurrencyRate curRate : currencyRates) { + if ((curRate.getCurrency2().equals(fromCurrency))&& + (curRate.getCurrency1().equals(toCurrency))) + { + return curRate.getRate().convertBtoA(amount); + } + } + //even inverse convertor not found + throw new IllegalArgumentException("Cannot work with selected currencies."); } } - + + /** + * Returns currency rates. If instantiated with constructor from vesion 1 + * it creates new collection with one CurrencyRate. + * Note, it can cause exception because of empty currencies or same currencies. + */ + public Collection getCurrencyRates() { + if (instanceVersion == 1) { + List ret = new ArrayList(); + ret.add(new CurrencyRateImpl(currency1, currency2, rate)); + return Collections.unmodifiableCollection(ret); + } else { //instanceVersion == 2 + return currencyRates; + } + } + @Override public String toString() { - return currency1+currency2; + if (instanceVersion == 1) { + return currency1 + currency2; + } else { //instanceVersion == 2 + return super.toString(); //better be compatible in future :-) + } } - + @Override public boolean equals(Object obj) { - if (obj == null) { - return false; + if (instanceVersion == 1) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Convertor other = (Convertor) obj; + if (this.currency1 != other.currency1 && (this.currency1 == null || !this.currency1.equals(other.currency1))) { + return false; + } + if (this.currency2 != other.currency2 && (this.currency2 == null || !this.currency2.equals(other.currency2))) { + return false; + } + if (this.rate != other.rate && (this.rate == null || !this.rate.equals(other.rate))) { + return false; + } + return true; + } else { //instanceVersion == 2 + return super.equals(obj); //better be compatible in future :-) } - if (getClass() != obj.getClass()) { - return false; - } - final Convertor other = (Convertor) obj; - if (this.currency1 != other.currency1 && (this.currency1 == null || !this.currency1.equals(other.currency1))) { - return false; - } - if (this.currency2 != other.currency2 && (this.currency2 == null || !this.currency2.equals(other.currency2))) { - return false; - } - if (this.rate != other.rate && (this.rate == null || !this.rate.equals(other.rate))) { - return false; - } - return true; } @Override public int hashCode() { - int hash = 5; - hash = 67 * hash + (this.currency1 != null ? this.currency1.hashCode() : 0); - hash = 67 * hash + (this.currency2 != null ? this.currency2.hashCode() : 0); - hash = 67 * hash + (this.rate != null ? this.rate.hashCode() : 0); + if (instanceVersion == 1) { + int hash = 5; + hash = 67 * hash + (this.currency1 != null ? this.currency1.hashCode() : 0); + hash = 67 * hash + (this.currency2 != null ? this.currency2.hashCode() : 0); + hash = 67 * hash + (this.rate != null ? this.rate.hashCode() : 0); return hash; + } else { //instanceVersion == 2 + return super.hashCode(); //better be compatible in future :-) + } } } diff -r 79a576394dd7 -r de033c457bed task2/solution14/src/org/apidesign/apifest08/currency/ConvertorFactory.java --- a/task2/solution14/src/org/apidesign/apifest08/currency/ConvertorFactory.java Tue Oct 07 11:19:36 2008 +0200 +++ b/task2/solution14/src/org/apidesign/apifest08/currency/ConvertorFactory.java Wed Oct 08 12:51:52 2008 +0200 @@ -1,12 +1,15 @@ package org.apidesign.apifest08.currency; +import java.util.ArrayList; +import java.util.List; + public final class ConvertorFactory { //Singleton private static ConvertorFactory thisFactory = new ConvertorFactory(); private ConvertorFactory() {}; - public static ConvertorFactory newInstance() { + public static ConvertorFactory newInstance() { //ehm, mistake - it should be named getInstance return thisFactory; } @@ -25,4 +28,39 @@ public Convertor createConvertor(String currency1, String currency2, double rate) { return new Convertor(currency1, currency2, new Rate(rate)); } + + public Convertor createConvertor(CurrencyRate currencyRate) { + return new Convertor(currencyRate); + } + + public Convertor createConvertor(CurrencyRate ... currencyRates) { + return new Convertor(currencyRates); + } + + public Convertor mergeConvertors(Convertor ... convertors) { + if (convertors == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + if (convertors.length == 0) { + throw new IllegalArgumentException("Convertors cannot be empty."); + } + List currRates = new ArrayList(); + List> currPairs = new ArrayList>(); + for (Convertor convertor : convertors) { + if (convertor == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + for (CurrencyRate currRate : convertor.getCurrencyRates()) { + Pair currPair = new Pair(currRate.getCurrency1(), currRate.getCurrency2()); + if (currPairs.contains(currPair)) { + throw new IllegalArgumentException("Cannot merge - convertors contain same currency rates."); + } + currPairs.add(currPair); + currRates.add(currRate); + } + } + + return new Convertor(currRates.toArray(new CurrencyRate[0])); + } + } diff -r 79a576394dd7 -r de033c457bed task2/solution14/src/org/apidesign/apifest08/currency/CurrencyRate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task2/solution14/src/org/apidesign/apifest08/currency/CurrencyRate.java Wed Oct 08 12:51:52 2008 +0200 @@ -0,0 +1,8 @@ +package org.apidesign.apifest08.currency; + + +public interface CurrencyRate { + public String getCurrency1(); + public String getCurrency2(); + public Rate getRate(); +} diff -r 79a576394dd7 -r de033c457bed task2/solution14/src/org/apidesign/apifest08/currency/CurrencyRateFactory.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task2/solution14/src/org/apidesign/apifest08/currency/CurrencyRateFactory.java Wed Oct 08 12:51:52 2008 +0200 @@ -0,0 +1,25 @@ +package org.apidesign.apifest08.currency; + + +public final class CurrencyRateFactory { + + //Singleton + private static CurrencyRateFactory thisFactory = new CurrencyRateFactory(); + private CurrencyRateFactory() {}; + public static CurrencyRateFactory getInstance() { + return thisFactory; + } + + public CurrencyRate createCurrencyRate(final String currency1, final String currency2, final Rate rate) { + return new CurrencyRateImpl(currency1, currency2, rate); + } + + public CurrencyRate createCurrencyRate(final String currency1, final String currency2, int amount1, int amount2) { + return new CurrencyRateImpl(currency1, currency2, new Rate(amount1, amount2)); + } + + public CurrencyRate createCurrencyRate(final String currency1, final String currency2, double amount1, double amount2) { + return new CurrencyRateImpl(currency1, currency2, new Rate(amount1, amount2)); + } + +} diff -r 79a576394dd7 -r de033c457bed task2/solution14/src/org/apidesign/apifest08/currency/CurrencyRateImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task2/solution14/src/org/apidesign/apifest08/currency/CurrencyRateImpl.java Wed Oct 08 12:51:52 2008 +0200 @@ -0,0 +1,37 @@ + +package org.apidesign.apifest08.currency; + +public final class CurrencyRateImpl implements CurrencyRate { + private String currency1; + private String currency2; + private Rate rate; + + CurrencyRateImpl(final String currency1, final String currency2, final Rate rate) { + if ((currency1 == null)||(currency2 == null) || (rate == null)) { + throw new IllegalArgumentException("Argument cannot be null."); + } + if ("".equals(currency1) || "".equals(currency2)) { + throw new IllegalArgumentException("Name of currency cannot be empty string"); + } + if (currency1.equals(currency2)) { + throw new IllegalArgumentException("Currencies in rate cannot be the same"); + } + + this.currency1 = currency1; + this.currency2 = currency2; + this.rate = rate; + } + + public String getCurrency1() { + return currency1; + } + + public String getCurrency2() { + return currency2; + } + + public Rate getRate(){ + return rate; + } + +} diff -r 79a576394dd7 -r de033c457bed task2/solution14/src/org/apidesign/apifest08/currency/Pair.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task2/solution14/src/org/apidesign/apifest08/currency/Pair.java Wed Oct 08 12:51:52 2008 +0200 @@ -0,0 +1,53 @@ + +package org.apidesign.apifest08.currency; + + +public final class Pair { + + private final A first; + private final B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + + public A getFirst() { return first; } + public B getSecond() { return second; } + + @Override + public String toString() { + return "(" + first + ", " + second + ")"; + } + + private static boolean equals(Object x, Object y) { + return (x == null && y == null) || (x != null && x.equals(y)); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 59 * hash + (this.first != null ? this.first.hashCode() : 0); + hash = 59 * hash + (this.second != null ? this.second.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Pair other = (Pair) obj; + if (this.first != other.first && (this.first == null || !this.first.equals(other.first))) { + return false; + } + if (this.second != other.second && (this.second == null || !this.second.equals(other.second))) { + return false; + } + return true; + } + +} diff -r 79a576394dd7 -r de033c457bed task2/solution14/test/org/apidesign/apifest08/test/Task2Test.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task2/solution14/test/org/apidesign/apifest08/test/Task2Test.java Wed Oct 08 12:51:52 2008 +0200 @@ -0,0 +1,169 @@ +package org.apidesign.apifest08.test; + +import junit.framework.TestCase; +import org.apidesign.apifest08.currency.Convertor; +import org.apidesign.apifest08.currency.ConvertorFactory; +import org.apidesign.apifest08.currency.CurrencyRate; +import org.apidesign.apifest08.currency.CurrencyRateFactory; + +/** 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 + CurrencyRate usdCzk = CurrencyRateFactory.getInstance().createCurrencyRate("USD", "CZK", 1, 15); + CurrencyRate usdSkk = CurrencyRateFactory.getInstance().createCurrencyRate("USD", "SKK", 1, 20); + CurrencyRate czkSkk = CurrencyRateFactory.getInstance().createCurrencyRate("CZK", "SKK", 75, 100); + return ConvertorFactory.newInstance().createConvertor(usdCzk, usdSkk, czkSkk); + } + + /** 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"); + assertEquals("Result is 75 CZK", 75.0, c.convert("USD", "CZK", 5)); + + // convert $5 to SKK using c: + // assertEquals("Result is 100 SKK"); + assertEquals("Result is 100 SKK", 100.0, c.convert("USD", "SKK", 5)); + + // convert 200SKK to CZK using c: + // assertEquals("Result is 150 CZK"); + assertEquals("Result is 150 CZK", 150.0, c.convert("SKK", "CZK", 200)); + + // convert 200SKK to USD using c: + // assertEquals("Result is 10 USD"); + assertEquals("Result is 10 USD", 10.0, c.convert("SKK", "USD", 200)); + } + + /** 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 ConvertorFactory.newInstance().mergeConvertors(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"); + assertEquals("Result is 85 CZK", 85.0, c.convert("USD", "CZK", 5)); + + // convert $8 to CZK using c: + // assertEquals("Result is 136 CZK"); + assertEquals("Result is 136 CZK", 136.0, c.convert("USD", "CZK", 8)); + + // convert 1003CZK to USD using c: + // assertEquals("Result is 59 USD"); + assertEquals("Result is 59 USD", 59.0, c.convert("CZK", "USD", 1003)); + + // convert 16CZK using c: + // assertEquals("Result is 20 SKK"); + assertEquals("Result is 20 SKK", 20.0, c.convert("CZK", "SKK", 16)); + + // convert 500SKK to CZK using c: + // assertEquals("Result is 400 CZK"); + assertEquals("Result is 400 CZK", 400.0, c.convert("SKK", "CZK", 500)); + + //test exceptions + Convertor one = Task1Test.createCZKtoUSD(); + Convertor two = Task1Test.createSKKtoCZK(); + Convertor three = Task1Test.createSKKtoCZK(); + try { + ConvertorFactory.newInstance().mergeConvertors(one,two,three); + fail(); + } catch (IllegalArgumentException e) { + //ok + } + + //test exceptions + try { + ConvertorFactory.newInstance().mergeConvertors(c, two); + fail(); + } catch (IllegalArgumentException e) { + //ok + } + + //try convertors from version 1 + Convertor v1one = ConvertorFactory.newInstance().createConvertor("CZE", "CZE", 1, 2); + assertEquals("CZE->CZE 1:2 10 expects 20", 20.0, v1one.convert("CZE", "CZE", 10)); + try { + ConvertorFactory.newInstance().mergeConvertors(v1one, two); + fail(); + } catch (IllegalArgumentException e) { + //ok + } + + Convertor v1two = ConvertorFactory.newInstance().createConvertor("EUR", "", 1, 2); + assertEquals("EUR->'' 1:2 10 expects 20", 20.0, v1two.convert("EUR", "", 10)); + try { + ConvertorFactory.newInstance().mergeConvertors(v1two, two); + fail(); + } catch (IllegalArgumentException e) { + //ok + } + + Convertor v1three = ConvertorFactory.newInstance().createConvertor("EUR", "", 1, 2); + assertEquals("''->EUR 1:2 10 expects 5", 5.0, v1three.convert("", "EUR", 10)); + try { + ConvertorFactory.newInstance().mergeConvertors(v1three, two); + fail(); + } catch (IllegalArgumentException e) { + //ok + } + + } +}