adding solution02 for task2
authorjapod@localhost
Tue, 07 Oct 2008 00:19:37 +0200
changeset 343a18aae85c9e
parent 33 80f2d8751f35
child 35 8898c620fe96
adding solution02 for task2
task2/solution02/src/org/apidesign/apifest08/currency/CompositeConvertor.java
task2/solution02/src/org/apidesign/apifest08/currency/ConvertorFactory.java
task2/solution02/src/org/apidesign/apifest08/currency/DefaultConvertor.java
task2/solution02/src/org/apidesign/apifest08/currency/ExtendedConvertor.java
task2/solution02/src/org/apidesign/apifest08/currency/MoneyImpl.java
task2/solution02/test/org/apidesign/apifest08/currency/CompositeConvertorTest.java
task2/solution02/test/org/apidesign/apifest08/test/ConvertorTest.java
task2/solution02/test/org/apidesign/apifest08/test/Task2Test.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/task2/solution02/src/org/apidesign/apifest08/currency/CompositeConvertor.java	Tue Oct 07 00:19:37 2008 +0200
     1.3 @@ -0,0 +1,75 @@
     1.4 +package org.apidesign.apifest08.currency;
     1.5 +
     1.6 +import java.io.Serializable;
     1.7 +import java.util.Arrays;
     1.8 +import java.util.Currency;
     1.9 +
    1.10 +/**
    1.11 + * Convertor that is composed from other convertors. 
    1.12 + * @author lukas
    1.13 + *
    1.14 + */
    1.15 +class CompositeConvertor implements ExtendedConvertor, Serializable {
    1.16 +
    1.17 +	private static final long serialVersionUID = -2702026568249130055L;
    1.18 +	
    1.19 +	private final ExtendedConvertor[] convertors;
    1.20 +
    1.21 +
    1.22 +	public CompositeConvertor(ExtendedConvertor... convertors) {
    1.23 +		this.convertors = convertors.clone();
    1.24 +		for (ExtendedConvertor convertor: this.convertors)
    1.25 +		{
    1.26 +			if (convertor==null)
    1.27 +			{
    1.28 +				throw new NullPointerException("One of the convertors to be merged is null");
    1.29 +			}
    1.30 +		}
    1.31 +	}
    1.32 +
    1.33 +	/**
    1.34 +	 * Returns true if the composite contains convertor that supports given conversion.
    1.35 +	 */
    1.36 +	public boolean isConversionSupported(Currency from, Currency to) {
    1.37 +		return findConvertorFor(from, to)!=null;
    1.38 +	}
    1.39 +
    1.40 +
    1.41 +	/**
    1.42 +	 * If the composite contains convertor that supports given conversion, delegates to its convert method.
    1.43 +	 * Throws {@link IllegalArgumentException} convertor supporting given conversion is not found. 
    1.44 +	 */
    1.45 +	public Money convert(Money amount, Currency destinationCurrency) throws IllegalArgumentException {
    1.46 +		ExtendedConvertor convertor = findConvertorFor(amount.getCurrency(), destinationCurrency);
    1.47 +		if (convertor!=null)
    1.48 +		{
    1.49 +			return convertor.convert(amount, destinationCurrency);
    1.50 +		}
    1.51 +		throw new IllegalArgumentException("Conversion not supported. Can not convert to "+destinationCurrency+".");
    1.52 +	}
    1.53 +	
    1.54 +	/**
    1.55 +	 * Finds convertor for given currencies. If not found, returns null. 
    1.56 +	 * @param from
    1.57 +	 * @param to
    1.58 +	 * @return
    1.59 +	 */
    1.60 +	ExtendedConvertor findConvertorFor(Currency from, Currency to) {
    1.61 +		//does linear search, in the future can cache the results.
    1.62 +		for (ExtendedConvertor convertor:convertors)
    1.63 +		{
    1.64 +			if (convertor.isConversionSupported(from, to))
    1.65 +			{
    1.66 +				return convertor;
    1.67 +			}
    1.68 +		}
    1.69 +		return null;
    1.70 +	}
    1.71 +	
    1.72 +
    1.73 +	@Override
    1.74 +	public String toString() {
    1.75 +		return getClass().getName()+" converts "+Arrays.toString(convertors);
    1.76 +	}
    1.77 +
    1.78 +}
     2.1 --- a/task2/solution02/src/org/apidesign/apifest08/currency/ConvertorFactory.java	Tue Oct 07 00:17:07 2008 +0200
     2.2 +++ b/task2/solution02/src/org/apidesign/apifest08/currency/ConvertorFactory.java	Tue Oct 07 00:19:37 2008 +0200
     2.3 @@ -22,9 +22,24 @@
     2.4  	 * @param destinationEquivalent
     2.5  	 * @return
     2.6  	 */
     2.7 -	public static final Convertor createConvertor(Money sourceEquivalent, Money destinationEquivalent)
     2.8 +	public static final ExtendedConvertor createConvertor(Money sourceEquivalent, Money destinationEquivalent)
     2.9  	{
    2.10 -		return new DefaultConvertor(sourceEquivalent.getAmount(), destinationEquivalent.getAmount(), sourceEquivalent.getCurrency(), destinationEquivalent.getCurrency());
    2.11 +		return mergeConvertors(
    2.12 +				new DefaultConvertor(sourceEquivalent, destinationEquivalent),
    2.13 +				new DefaultConvertor(destinationEquivalent ,sourceEquivalent)
    2.14 +		);
    2.15 +	}
    2.16 +	
    2.17 +	/**
    2.18 +	 * Merges convertors. The resulting convertor has ability to do all conversions that its underlying 
    2.19 +	 * convertors could do. No consistency validation is done, inconsistent input will result in a convertor with
    2.20 +	 * inconsistent behavior.
    2.21 +	 * @param convertors
    2.22 +	 * @return
    2.23 +	 */
    2.24 +	public static final ExtendedConvertor mergeConvertors(ExtendedConvertor... convertors)
    2.25 +	{
    2.26 +		return new CompositeConvertor(convertors);
    2.27  	}
    2.28  	
    2.29  
     3.1 --- a/task2/solution02/src/org/apidesign/apifest08/currency/DefaultConvertor.java	Tue Oct 07 00:17:07 2008 +0200
     3.2 +++ b/task2/solution02/src/org/apidesign/apifest08/currency/DefaultConvertor.java	Tue Oct 07 00:19:37 2008 +0200
     3.3 @@ -11,9 +11,9 @@
     3.4   * @author lukas
     3.5   *
     3.6   */
     3.7 -class DefaultConvertor implements Convertor, Serializable {
     3.8 +class DefaultConvertor implements ExtendedConvertor, Serializable {
     3.9  
    3.10 -	private static final long serialVersionUID = -1754789142402148099L;
    3.11 +	private static final long serialVersionUID = 6660014633685135034L;
    3.12  
    3.13  	/**
    3.14  	 * Equivalent in source currency.
    3.15 @@ -29,20 +29,20 @@
    3.16  	
    3.17  	private final Currency destinationCurrency;
    3.18  	
    3.19 -	public DefaultConvertor(BigDecimal sourceEquivalent, BigDecimal destinationEquivalent, Currency sourceCurrency, Currency destinationCurrency) {
    3.20 +	public DefaultConvertor(Money sourceEquivalent, Money destinationEquivalent) {
    3.21  		super();
    3.22 -		if (BigDecimal.ZERO.compareTo(sourceEquivalent)==0)
    3.23 +		if (BigDecimal.ZERO.compareTo(sourceEquivalent.getAmount())==0)
    3.24  		{
    3.25  			throw new IllegalArgumentException("Source equivalent amount can not be 0.");
    3.26  		}
    3.27 -		if (BigDecimal.ZERO.compareTo(destinationEquivalent)==0)
    3.28 +		if (BigDecimal.ZERO.compareTo(destinationEquivalent.getAmount())==0)
    3.29  		{
    3.30  			throw new IllegalArgumentException("Destination equivalent amount can not be 0.");
    3.31  		}
    3.32 -		this.sourceEquivalent = sourceEquivalent;
    3.33 -		this.destinationEquivalent = destinationEquivalent;
    3.34 -		this.sourceCurrency = sourceCurrency;
    3.35 -		this.destinationCurrency = destinationCurrency;
    3.36 +		this.sourceEquivalent = sourceEquivalent.getAmount();
    3.37 +		this.destinationEquivalent = destinationEquivalent.getAmount();
    3.38 +		this.sourceCurrency = sourceEquivalent.getCurrency();
    3.39 +		this.destinationCurrency = destinationEquivalent.getCurrency();
    3.40  	}
    3.41  	
    3.42  	public Money convert(Money amount, Currency destinationCurrency) {
    3.43 @@ -54,10 +54,6 @@
    3.44  		{
    3.45  			throw new NullPointerException("destionationCurrency is null");
    3.46  		}
    3.47 -		if (isConversionInOpositeDirection(amount, destinationCurrency))
    3.48 -		{
    3.49 -			return revert().convert(amount, destinationCurrency);
    3.50 -		}
    3.51  		if (!amount.getCurrency().equals(getSourceCurrency()))
    3.52  		{
    3.53  			throw new IllegalArgumentException("Can not convert from "+amount.getCurrency()+". Converts between "+getSourceCurrency()+" and "+getDestinationCurrency());
    3.54 @@ -70,20 +66,9 @@
    3.55  		BigDecimal destinationAmount = sourceAmount.multiply(destinationEquivalent).divide(sourceEquivalent, 2, RoundingMode.HALF_UP);
    3.56  		return new MoneyImpl(destinationAmount, getDestinationCurrency());
    3.57  	}
    3.58 -
    3.59 -	/**
    3.60 -	 * Returns true, if the conversion is in oposit direction.
    3.61 -	 * @param amount
    3.62 -	 * @param destinationCurrency
    3.63 -	 * @return
    3.64 -	 */
    3.65 -	private boolean isConversionInOpositeDirection(Money amount,	Currency destinationCurrency) {
    3.66 -		return amount.getCurrency().equals(getDestinationCurrency()) && destinationCurrency.equals(getSourceCurrency());
    3.67 -	}
    3.68 -
    3.69 -
    3.70 -	public Convertor revert() {
    3.71 -		return new DefaultConvertor(destinationEquivalent, sourceEquivalent, destinationCurrency, sourceCurrency);
    3.72 +	
    3.73 +	public boolean isConversionSupported(Currency from, Currency to) {
    3.74 +		return sourceCurrency.equals(from) && destinationCurrency.equals(to);
    3.75  	}
    3.76  
    3.77  	public BigDecimal getSourceEquivalent() {
    3.78 @@ -107,58 +92,4 @@
    3.79  		return getClass().getName()+" converts "+getSourceCurrency()+" to "+getDestinationCurrency()+" "
    3.80  			+getSourceCurrency()+getSourceEquivalent()+"="+getDestinationCurrency()+getDestinationEquivalent();
    3.81  	}
    3.82 -
    3.83 -	@Override
    3.84 -	public int hashCode() {
    3.85 -		final int prime = 31;
    3.86 -		int result = 1;
    3.87 -		result = prime
    3.88 -				* result
    3.89 -				+ ((destinationCurrency == null) ? 0 : destinationCurrency
    3.90 -						.hashCode());
    3.91 -		result = prime
    3.92 -				* result
    3.93 -				+ ((destinationEquivalent == null) ? 0 : destinationEquivalent
    3.94 -						.hashCode());
    3.95 -		result = prime * result
    3.96 -				+ ((sourceCurrency == null) ? 0 : sourceCurrency.hashCode());
    3.97 -		result = prime
    3.98 -				* result
    3.99 -				+ ((sourceEquivalent == null) ? 0 : sourceEquivalent.hashCode());
   3.100 -		return result;
   3.101 -	}
   3.102 -
   3.103 -	@Override
   3.104 -	public boolean equals(Object obj) {
   3.105 -		if (this == obj)
   3.106 -			return true;
   3.107 -		if (obj == null)
   3.108 -			return false;
   3.109 -		if (!(obj instanceof DefaultConvertor))
   3.110 -			return false;
   3.111 -		DefaultConvertor other = (DefaultConvertor) obj;
   3.112 -		if (destinationCurrency == null) {
   3.113 -			if (other.destinationCurrency != null)
   3.114 -				return false;
   3.115 -		} else if (!destinationCurrency.equals(other.destinationCurrency))
   3.116 -			return false;
   3.117 -		if (destinationEquivalent == null) {
   3.118 -			if (other.destinationEquivalent != null)
   3.119 -				return false;
   3.120 -		} else if (!destinationEquivalent.equals(other.destinationEquivalent))
   3.121 -			return false;
   3.122 -		if (sourceCurrency == null) {
   3.123 -			if (other.sourceCurrency != null)
   3.124 -				return false;
   3.125 -		} else if (!sourceCurrency.equals(other.sourceCurrency))
   3.126 -			return false;
   3.127 -		if (sourceEquivalent == null) {
   3.128 -			if (other.sourceEquivalent != null)
   3.129 -				return false;
   3.130 -		} else if (!sourceEquivalent.equals(other.sourceEquivalent))
   3.131 -			return false;
   3.132 -		return true;
   3.133 -	}
   3.134 -
   3.135 -
   3.136  }
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/task2/solution02/src/org/apidesign/apifest08/currency/ExtendedConvertor.java	Tue Oct 07 00:19:37 2008 +0200
     4.3 @@ -0,0 +1,18 @@
     4.4 +package org.apidesign.apifest08.currency;
     4.5 +
     4.6 +import java.util.Currency;
     4.7 +
     4.8 +/**
     4.9 + * Extended convertor interface. 
    4.10 + * @author lukas
    4.11 + *
    4.12 + */
    4.13 +public interface ExtendedConvertor extends Convertor {
    4.14 +	/**
    4.15 +	 * Returns true if given conversion is supported.
    4.16 +	 * @param from
    4.17 +	 * @param to
    4.18 +	 * @return
    4.19 +	 */
    4.20 +	public boolean isConversionSupported(Currency from, Currency to);
    4.21 +}
     5.1 --- a/task2/solution02/src/org/apidesign/apifest08/currency/MoneyImpl.java	Tue Oct 07 00:17:07 2008 +0200
     5.2 +++ b/task2/solution02/src/org/apidesign/apifest08/currency/MoneyImpl.java	Tue Oct 07 00:19:37 2008 +0200
     5.3 @@ -15,7 +15,7 @@
     5.4  	private final BigDecimal amount;
     5.5  	
     5.6  	private final Currency currency;
     5.7 -
     5.8 +	
     5.9  	public MoneyImpl(BigDecimal amount, Currency currency) {
    5.10  		if (amount==null) throw new NullPointerException("Amount is null");
    5.11  		if (currency==null) throw new NullPointerException("Currency is null"+currency);
    5.12 @@ -23,6 +23,8 @@
    5.13  		this.currency = currency;
    5.14  	}
    5.15  	
    5.16 +	
    5.17 +	
    5.18  	public MoneyImpl(long amount, Currency currency) {
    5.19  		this(BigDecimal.valueOf(amount), currency);
    5.20  	}
    5.21 @@ -30,6 +32,37 @@
    5.22  	public MoneyImpl(double amount, Currency currency) {
    5.23  		this(BigDecimal.valueOf(amount), currency);
    5.24  	}
    5.25 +	
    5.26 +	/**
    5.27 +	 * Factory method.
    5.28 +	 * @param amount
    5.29 +	 * @param currency
    5.30 +	 * @return
    5.31 +	 */
    5.32 +	public static final Money money(BigDecimal amount, Currency currency)
    5.33 +	{
    5.34 +		return new MoneyImpl(amount, currency);
    5.35 +	}
    5.36 +	/**
    5.37 +	 * Factory method.
    5.38 +	 * @param amount
    5.39 +	 * @param currency
    5.40 +	 * @return
    5.41 +	 */
    5.42 +	public static final Money money(long amount, Currency currency)
    5.43 +	{
    5.44 +		return new MoneyImpl(amount, currency);
    5.45 +	}
    5.46 +	/**
    5.47 +	 * Factory method.
    5.48 +	 * @param amount
    5.49 +	 * @param currency
    5.50 +	 * @return
    5.51 +	 */
    5.52 +	public static final Money money(double amount, Currency currency)
    5.53 +	{
    5.54 +		return new MoneyImpl(amount, currency);
    5.55 +	}
    5.56  
    5.57  	/**
    5.58  	 * Returns amount.
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/task2/solution02/test/org/apidesign/apifest08/currency/CompositeConvertorTest.java	Tue Oct 07 00:19:37 2008 +0200
     6.3 @@ -0,0 +1,56 @@
     6.4 +package org.apidesign.apifest08.currency;
     6.5 +
     6.6 +import static junit.framework.Assert.assertEquals;
     6.7 +import static junit.framework.Assert.assertFalse;
     6.8 +import static junit.framework.Assert.assertTrue;
     6.9 +import static junit.framework.Assert.fail;
    6.10 +import static org.apidesign.apifest08.test.Task1Test.CZK;
    6.11 +import static org.apidesign.apifest08.test.Task1Test.SKK;
    6.12 +import static org.apidesign.apifest08.test.Task1Test.USD;
    6.13 +
    6.14 +import org.junit.Test;
    6.15 +
    6.16 +public class CompositeConvertorTest {
    6.17 +	private static final ExtendedConvertor USD_CZK_CONVERTOR = ConvertorFactory.createConvertor(new MoneyImpl(1,USD), new MoneyImpl(17,CZK));
    6.18 +	private static final ExtendedConvertor SKK_CZK_CONVERTOR = ConvertorFactory.createConvertor(new MoneyImpl(100,SKK), new MoneyImpl(80,CZK));
    6.19 +
    6.20 +	@Test
    6.21 +	public void testCompose()
    6.22 +	{
    6.23 +		ExtendedConvertor convertor = new CompositeConvertor(USD_CZK_CONVERTOR, SKK_CZK_CONVERTOR);
    6.24 +		assertTrue(convertor.isConversionSupported(CZK, SKK));
    6.25 +		assertTrue(convertor.isConversionSupported(CZK, USD));
    6.26 +		assertFalse(convertor.isConversionSupported(SKK, USD));
    6.27 +		assertEquals(new MoneyImpl(10,SKK), convertor.convert(new MoneyImpl(8,CZK), SKK));
    6.28 +		assertEquals(new MoneyImpl(2,USD), convertor.convert(new MoneyImpl(34,CZK), USD));
    6.29 +		try
    6.30 +		{
    6.31 +			convertor.convert(new MoneyImpl(34,SKK), USD);
    6.32 +			fail("Exception expected");
    6.33 +		}
    6.34 +		catch(IllegalArgumentException e)
    6.35 +		{
    6.36 +			assertTrue("Ok", true);
    6.37 +		}
    6.38 +	}
    6.39 +	@Test
    6.40 +	public void testEmpty()
    6.41 +	{
    6.42 +		ExtendedConvertor convertor = new CompositeConvertor();
    6.43 +		assertFalse(convertor.isConversionSupported(SKK, USD));
    6.44 +		try
    6.45 +		{
    6.46 +			convertor.convert(new MoneyImpl(34,SKK), USD);
    6.47 +			fail("Exception expected");
    6.48 +		}
    6.49 +		catch(IllegalArgumentException e)
    6.50 +		{
    6.51 +			assertTrue("Ok", true);
    6.52 +		}
    6.53 +	}
    6.54 +	@Test(expected=NullPointerException.class)
    6.55 +	public void testCreateNull()
    6.56 +	{
    6.57 +		new CompositeConvertor(USD_CZK_CONVERTOR, null);
    6.58 +	}
    6.59 +}
     7.1 --- a/task2/solution02/test/org/apidesign/apifest08/test/ConvertorTest.java	Tue Oct 07 00:17:07 2008 +0200
     7.2 +++ b/task2/solution02/test/org/apidesign/apifest08/test/ConvertorTest.java	Tue Oct 07 00:19:37 2008 +0200
     7.3 @@ -1,13 +1,16 @@
     7.4  package org.apidesign.apifest08.test;
     7.5  
     7.6 +import static junit.framework.Assert.assertEquals;
     7.7 +import static junit.framework.Assert.assertFalse;
     7.8 +import static junit.framework.Assert.assertTrue;
     7.9  import static org.apidesign.apifest08.test.Task1Test.CZK;
    7.10 +import static org.apidesign.apifest08.test.Task1Test.SKK;
    7.11  import static org.apidesign.apifest08.test.Task1Test.USD;
    7.12 -import static org.junit.Assert.assertEquals;
    7.13  
    7.14  import java.math.BigDecimal;
    7.15  
    7.16 -import org.apidesign.apifest08.currency.Convertor;
    7.17  import org.apidesign.apifest08.currency.ConvertorFactory;
    7.18 +import org.apidesign.apifest08.currency.ExtendedConvertor;
    7.19  import org.apidesign.apifest08.currency.Money;
    7.20  import org.apidesign.apifest08.currency.MoneyImpl;
    7.21  import org.junit.Test;
    7.22 @@ -15,7 +18,7 @@
    7.23  
    7.24  public class ConvertorTest {
    7.25  
    7.26 -	private static final Convertor CZK_TO_USD_CONVERTOR = ConvertorFactory.createConvertor(new MoneyImpl(17,CZK), new MoneyImpl(1,USD));
    7.27 +	private static final ExtendedConvertor CZK_TO_USD_CONVERTOR = ConvertorFactory.createConvertor(new MoneyImpl(17,CZK), new MoneyImpl(1,USD));
    7.28  	@Test
    7.29  	public void testConvertSmall()
    7.30  	{
    7.31 @@ -29,4 +32,13 @@
    7.32  		Money converted = CZK_TO_USD_CONVERTOR.convert(new MoneyImpl(0.01,USD),CZK);
    7.33  		assertEquals(new MoneyImpl(new BigDecimal("0.17"),CZK),converted);
    7.34  	}
    7.35 +	@Test
    7.36 +	public void testSupports()
    7.37 +	{
    7.38 +		assertTrue(CZK_TO_USD_CONVERTOR.isConversionSupported(USD,CZK));
    7.39 +		assertTrue(CZK_TO_USD_CONVERTOR.isConversionSupported(CZK,USD));
    7.40 +		assertFalse(CZK_TO_USD_CONVERTOR.isConversionSupported(CZK,CZK));
    7.41 +		assertFalse(CZK_TO_USD_CONVERTOR.isConversionSupported(CZK,SKK));
    7.42 +		
    7.43 +	}
    7.44  }
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/task2/solution02/test/org/apidesign/apifest08/test/Task2Test.java	Tue Oct 07 00:19:37 2008 +0200
     8.3 @@ -0,0 +1,119 @@
     8.4 +package org.apidesign.apifest08.test;
     8.5 +
     8.6 +import static org.apidesign.apifest08.currency.ConvertorFactory.createConvertor;
     8.7 +import static org.apidesign.apifest08.currency.ConvertorFactory.mergeConvertors;
     8.8 +import static org.apidesign.apifest08.currency.MoneyImpl.money;
     8.9 +import static org.apidesign.apifest08.test.Task1Test.CZK;
    8.10 +import static org.apidesign.apifest08.test.Task1Test.SKK;
    8.11 +import static org.apidesign.apifest08.test.Task1Test.USD;
    8.12 +import junit.framework.TestCase;
    8.13 +
    8.14 +import org.apidesign.apifest08.currency.Convertor;
    8.15 +import org.apidesign.apifest08.currency.ExtendedConvertor;
    8.16 +
    8.17 +/** There are many currencies around the world and many banks manipulate
    8.18 + * with more than one or two at the same time. As banks are usually the
    8.19 + * best paying clients, which is true even in case of your Convertor API,
    8.20 + * it is reasonable to listen to their requests.
    8.21 + * <p>
    8.22 + * The quest for today is to enhance your existing convertor API to hold
    8.23 + * information about many currencies and allow conversions between any of them.
    8.24 + * Also, as conversion rates for diferent currencies usually arise from various
    8.25 + * bank departments, there is another important need. There is a need to
    8.26 + * compose two convertors into one by merging all the information about
    8.27 + * currencies they know about.
    8.28 + */
    8.29 +public class Task2Test extends TestCase {
    8.30 +    public Task2Test(String testName) {
    8.31 +        super(testName);
    8.32 +    }
    8.33 +
    8.34 +    @Override
    8.35 +    protected void setUp() throws Exception {
    8.36 +    }
    8.37 +
    8.38 +    @Override
    8.39 +    protected void tearDown() throws Exception {
    8.40 +    }
    8.41 +
    8.42 +    // As in Task1Test, keep in mind, that there are three parts
    8.43 +    // of the whole system:
    8.44 +    // 1. there is someone who knows the current exchange rate
    8.45 +    // 2. there is someone who wants to do the conversion
    8.46 +    // 3. there is the API between 1. and 2. which allows them to communicate
    8.47 +    // 
    8.48 +    // Please backward compatibly enhance your existing API to support following
    8.49 +    // usecases:
    8.50 +    //
    8.51 +    
    8.52 +    /** Create convertor that understands two currencies, CZK and
    8.53 +     *  SKK. Make 100 SKK == 75 CZK. This is method for the group of users that
    8.54 +     *  knows the exchange rate, and needs to use the API to create objects
    8.55 +     *  with the exchange rate. Anyone shall be ready to call this method without
    8.56 +     *  any other method being called previously. The API itself shall know
    8.57 +     *  nothing about any rates, before this method is called.
    8.58 +     */
    8.59 +    public static Convertor createTripleConvertor() {
    8.60 +        // Rates: 1USD = 15CZK
    8.61 +        // Rates: 1USD = 20SKK
    8.62 +        // Rates: 75CZK = 100SKK
    8.63 +	   return mergeConvertors(
    8.64 +			   createConvertor(money(1, USD),  money(15, CZK)),
    8.65 +			   createConvertor(money(1, USD),  money(20, SKK)),
    8.66 +			   createConvertor(money(75, CZK), money(100, SKK))
    8.67 +       );
    8.68 +    }
    8.69 +
    8.70 +    /** Define convertor that understands three currencies. Use it.
    8.71 +     */
    8.72 +    public void testConvertorForUSDandCZKandSKK() throws Exception {
    8.73 +        Convertor c = createTripleConvertor();
    8.74 +
    8.75 +        // convert $5 to CZK using c:
    8.76 +        assertEquals("Result is 75 CZK", money(75, CZK),c.convert(money(5,USD), CZK));
    8.77 +
    8.78 +        // convert $5 to SKK using c:
    8.79 +        assertEquals("Result is 100 SKK", money(100, SKK),c.convert(money(5,USD), SKK));
    8.80 +
    8.81 +        // convert 200SKK to CZK using c:
    8.82 +        assertEquals("Result is 150 CZK", money(150, CZK),c.convert(money(200,SKK), CZK));
    8.83 +
    8.84 +        // convert 200SKK to USK using c:
    8.85 +        assertEquals("Result is 10 USD", money(10, USD),c.convert(money(200,SKK), USD));
    8.86 +    }
    8.87 +
    8.88 +    /** Merge all currency rates of convertor 1 with convertor 2.
    8.89 +     * Implement this using your API, preferably this method just delegates
    8.90 +     * into some API method which does the actual work, without requiring
    8.91 +     * API clients to code anything complex.
    8.92 +     */
    8.93 +    public static Convertor merge(Convertor one, Convertor two) {
    8.94 +        return mergeConvertors((ExtendedConvertor)one, (ExtendedConvertor)two);
    8.95 +    }
    8.96 +
    8.97 +    /** Join the convertors from previous task, Task1Test and show that it
    8.98 +     * can be used to do reasonable conversions.
    8.99 +     */
   8.100 +    public void testConvertorComposition() throws Exception {
   8.101 +        Convertor c = merge(
   8.102 +            Task1Test.createCZKtoUSD(),
   8.103 +            Task1Test.createSKKtoCZK()
   8.104 +        );
   8.105 +
   8.106 +        // convert $5 to CZK using c:
   8.107 +        assertEquals("Result is 85 CZK", money(85, CZK),c.convert(money(5,USD), CZK));
   8.108 +
   8.109 +        // convert $8 to CZK using c:
   8.110 +        assertEquals("Result is 136 CZK", money(136, CZK),c.convert(money(8,USD), CZK));
   8.111 +
   8.112 +        // convert 1003CZK to USD using c:
   8.113 +        assertEquals("Result is 59 USD", money(59, USD),c.convert(money(1003,CZK), USD));
   8.114 +
   8.115 +        // convert 16CZK using c:
   8.116 +        assertEquals("Result is 20 SKK", money(20, SKK),c.convert(money(16,CZK), SKK));
   8.117 +
   8.118 +        // convert 500SKK to CZK using c:
   8.119 +        assertEquals("Result is 400 CZK", money(400, CZK),c.convert(money(500,SKK), CZK));
   8.120 +
   8.121 +    }
   8.122 +}