# 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
+ }
+
+ }
+}