# HG changeset patch # User japod@localhost # Date 1222768022 -7200 # Node ID 2864c6d744c0b220358efb4bacf48df53e23dfde # Parent a861bd02a1d27cb8e68782e0bdf01d09bce440a8 solution 02 updated to 1.5 diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/src/org/apidesign/apifest08/currency/Convertor.java --- a/task1/solution02/src/org/apidesign/apifest08/currency/Convertor.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/src/org/apidesign/apifest08/currency/Convertor.java Tue Sep 30 11:47:02 2008 +0200 @@ -4,35 +4,15 @@ /** - * Converts one currency to other. The conversion is unidirectional. - * For example you can have convertor that converts USD (sourceCurrency) to CZK (destination currency). You can call the {@link Convertor#convert(Money)} method - * with amount in USD to get the equivalent in CZK. If you need convert CZK to USD you can call {@link Convertor#revert()} method to get CZK to USD - * convertor. To create a convertor instance call {@link ConvertorFactory#createConvertor(Currency, Currency)}. + * Converts currencies. To create an instance call {@link ConvertorFactory#createConvertor(Money, Money)}. */ public interface Convertor { /** - * Converts amount in source currency to amount in destination currency. The result is rounded to two decimal places. - * @param money + * Converts amount to its equivalent in the destination currency. + * @param amount + * @param destinationCurrency * @return - * @throws IllegalArgumentException if money.getCurrency is not equal to sourceCurrency. + * @throws IllegalArgumentException if currency of the amount is not supported or if it is not possible to convert it to the destination currency. */ - public Money convert(Money money); - - /** - * Returns convertor that converts from destination currency to source currency with the same exchange rate. - * @return - */ - public Convertor revert(); - - /** - * Returns source currency. - * @return - */ - public Currency getSourceCurrency(); - - /** - * Returns destination currency. - * @return - */ - public Currency getDestinationCurrency(); + public Money convert(Money amount, Currency destinationCurrency) throws IllegalArgumentException; } diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/src/org/apidesign/apifest08/currency/ConvertorFactory.java --- a/task1/solution02/src/org/apidesign/apifest08/currency/ConvertorFactory.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/src/org/apidesign/apifest08/currency/ConvertorFactory.java Tue Sep 30 11:47:02 2008 +0200 @@ -1,46 +1,27 @@ package org.apidesign.apifest08.currency; -import java.util.Currency; /** - * Creates default {@link Convertor} implementations. + * Creates {@link Convertor} implementations. * @author lukas * */ public class ConvertorFactory { - private static final DefaultConvertorFactory DEFAULT_FACTORY = new DefaultConvertorFactory(); - private ConvertorFactory() { //nothing } /** - * Creates {@link Convertor} that converts from sourceCurrency to destinationCurrency with stored exchange rate. - * @param sourceCurrency - * @param destinationCurrency - * @return - * @throws UnsupportedConversionException when exchange rate between currencies is not known. - */ - /* - * Only one of the createConveror methods is needed. The assignment is not explicit where the exchange rate should be set. - */ - public static final Convertor createConvertor(Currency sourceCurrency, Currency destinationCurrency) throws UnsupportedConversionException - { - return DEFAULT_FACTORY.getConvertor(sourceCurrency, destinationCurrency); - } - /** * Creates {@link Convertor} that converts from sourceEquivalent.currency to destinationEquivalent.currency. * Exchange rate is set as equivalents. It means if you want to create USD to CZK convertor where USD1 = CZK17 - * call createConvertor(new MoneyImpl(1, USD), new MoneyImpl(17, CZK)). + * call createConvertor(new MoneyImpl(1, USD), new MoneyImpl(17, CZK)). Convertor created by this method + * rounds the result to two decimal places. * @param sourceEquivalent * @param destinationEquivalent * @return */ - /* - * Only one of the createConveror methods is needed. The assignment is not explicit where the exchange rate should be set. - */ public static final Convertor createConvertor(Money sourceEquivalent, Money destinationEquivalent) { return new DefaultConvertor(sourceEquivalent.getAmount(), destinationEquivalent.getAmount(), sourceEquivalent.getCurrency(), destinationEquivalent.getCurrency()); diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/src/org/apidesign/apifest08/currency/DefaultConvertor.java --- a/task1/solution02/src/org/apidesign/apifest08/currency/DefaultConvertor.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/src/org/apidesign/apifest08/currency/DefaultConvertor.java Tue Sep 30 11:47:02 2008 +0200 @@ -31,26 +31,56 @@ public DefaultConvertor(BigDecimal sourceEquivalent, BigDecimal destinationEquivalent, Currency sourceCurrency, Currency destinationCurrency) { super(); + if (BigDecimal.ZERO.compareTo(sourceEquivalent)==0) + { + throw new IllegalArgumentException("Source equivalent amount can not be 0."); + } + if (BigDecimal.ZERO.compareTo(destinationEquivalent)==0) + { + throw new IllegalArgumentException("Destination equivalent amount can not be 0."); + } this.sourceEquivalent = sourceEquivalent; this.destinationEquivalent = destinationEquivalent; this.sourceCurrency = sourceCurrency; this.destinationCurrency = destinationCurrency; } - public Money convert(Money money) { - if (money==null) + public Money convert(Money amount, Currency destinationCurrency) { + if (amount==null) { throw new NullPointerException("Money is null"); } - if (!money.getCurrency().equals(getSourceCurrency())) + if (destinationCurrency==null) { - throw new IllegalArgumentException("Can not convert from "+money.getCurrency()+". Converts "+getSourceCurrency()+" to "+getDestinationCurrency()); + throw new NullPointerException("destionationCurrency is null"); } - BigDecimal sourceAmount = money.getAmount(); - BigDecimal destinationAmount = sourceAmount.multiply(destinationEquivalent).divide(sourceEquivalent, 2, RoundingMode.HALF_DOWN); + if (isConversionInOpositeDirection(amount, destinationCurrency)) + { + return revert().convert(amount, destinationCurrency); + } + if (!amount.getCurrency().equals(getSourceCurrency())) + { + throw new IllegalArgumentException("Can not convert from "+amount.getCurrency()+". Converts between "+getSourceCurrency()+" and "+getDestinationCurrency()); + } + if (!getDestinationCurrency().equals(destinationCurrency)) + { + throw new IllegalArgumentException("Can not convert to "+destinationCurrency+". Converts between "+getSourceCurrency()+" and "+getDestinationCurrency()); + } + BigDecimal sourceAmount = amount.getAmount(); + BigDecimal destinationAmount = sourceAmount.multiply(destinationEquivalent).divide(sourceEquivalent, 2, RoundingMode.HALF_UP); return new MoneyImpl(destinationAmount, getDestinationCurrency()); } + /** + * Returns true, if the conversion is in oposit direction. + * @param amount + * @param destinationCurrency + * @return + */ + private boolean isConversionInOpositeDirection(Money amount, Currency destinationCurrency) { + return amount.getCurrency().equals(getDestinationCurrency()) && destinationCurrency.equals(getSourceCurrency()); + } + public Convertor revert() { return new DefaultConvertor(destinationEquivalent, sourceEquivalent, destinationCurrency, sourceCurrency); diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/src/org/apidesign/apifest08/currency/Money.java --- a/task1/solution02/src/org/apidesign/apifest08/currency/Money.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/src/org/apidesign/apifest08/currency/Money.java Tue Sep 30 11:47:02 2008 +0200 @@ -10,7 +10,7 @@ * */ /* - * Whether we need such interface depends on context. I can imagine than in a desktop application this interface + * Whether we need such interface depends on the context. I can imagine than in a desktop application this interface * would be useless, Money could be a class. In J2EE environment it can be useful. */ public interface Money { diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/src/org/apidesign/apifest08/currency/MoneyImpl.java --- a/task1/solution02/src/org/apidesign/apifest08/currency/MoneyImpl.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/src/org/apidesign/apifest08/currency/MoneyImpl.java Tue Sep 30 11:47:02 2008 +0200 @@ -19,7 +19,7 @@ public MoneyImpl(BigDecimal amount, Currency currency) { if (amount==null) throw new NullPointerException("Amount is null"); if (currency==null) throw new NullPointerException("Currency is null"+currency); - this.amount = amount.setScale(2); + this.amount = amount; this.currency = currency; } @@ -31,15 +31,16 @@ this(BigDecimal.valueOf(amount), currency); } - /* (non-Javadoc) - * @see org.apidesign.apifest08.currency.Money#getAmount() + /** + * Returns amount. + * @return */ public BigDecimal getAmount() { return amount; } - /* (non-Javadoc) - * @see org.apidesign.apifest08.currency.Money#getCurrency() + /** + * Returns currency. */ public Currency getCurrency() { return currency; @@ -67,7 +68,7 @@ if (amount == null) { if (other.amount != null) return false; - } else if (!amount.equals(other.amount)) + } else if (amount.compareTo(other.amount)!=0) return false; if (currency == null) { if (other.currency != null) diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/test/org/apidesign/apifest08/test/ConvertorFactoryTest.java --- a/task1/solution02/test/org/apidesign/apifest08/test/ConvertorFactoryTest.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/test/org/apidesign/apifest08/test/ConvertorFactoryTest.java Tue Sep 30 11:47:02 2008 +0200 @@ -1,14 +1,15 @@ package org.apidesign.apifest08.test; -import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static org.apidesign.apifest08.test.Task1Test.CZK; import static org.apidesign.apifest08.test.Task1Test.USD; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import java.util.Currency; - +import org.apidesign.apifest08.currency.Convertor; import org.apidesign.apifest08.currency.ConvertorFactory; -import org.apidesign.apifest08.currency.UnsupportedConversionException; +import org.apidesign.apifest08.currency.MoneyImpl; import org.junit.Test; @@ -16,31 +17,36 @@ @Test(expected=NullPointerException.class) public void testNullSource() { - ConvertorFactory.createConvertor(null, USD); + ConvertorFactory.createConvertor(null, new MoneyImpl(1,USD)); } @Test(expected=NullPointerException.class) public void testNullDestination() { - ConvertorFactory.createConvertor(CZK, null); - } - @Test(expected=IllegalArgumentException.class) - public void testShortSource() - { - ConvertorFactory.createConvertor(Currency.getInstance("CZ"), USD); - } - @Test(expected=UnsupportedConversionException.class) - public void testUnknownCombination() - { - ConvertorFactory.createConvertor(CZK, Currency.getInstance("ZAR")); + ConvertorFactory.createConvertor(new MoneyImpl(17,CZK), null); } @Test public void testOk() { - assertNotNull(ConvertorFactory.createConvertor(CZK, USD)); + assertNotNull(ConvertorFactory.createConvertor(new MoneyImpl(17,CZK), new MoneyImpl(1,USD))); } @Test - public void testReverted() + public void testOkDecimalRate() { - assertEquals(ConvertorFactory.createConvertor(CZK, USD).revert(), ConvertorFactory.createConvertor(USD, CZK)); + Convertor c = ConvertorFactory.createConvertor(new MoneyImpl(1,CZK), new MoneyImpl(1d/17d,USD)); + assertNotNull(c); + assertEquals(new MoneyImpl(17,CZK),c.convert(new MoneyImpl(1,USD), CZK)); + } + @Test + public void testZeroEquivalentRate() + { + try + { + ConvertorFactory.createConvertor(new MoneyImpl(1,CZK), new MoneyImpl(0,USD)); + fail("Exception expected"); + } + catch(IllegalArgumentException e) + { + assertTrue("OK",true); + } } } diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/test/org/apidesign/apifest08/test/ConvertorTest.java --- a/task1/solution02/test/org/apidesign/apifest08/test/ConvertorTest.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/test/org/apidesign/apifest08/test/ConvertorTest.java Tue Sep 30 11:47:02 2008 +0200 @@ -6,6 +6,7 @@ import java.math.BigDecimal; +import org.apidesign.apifest08.currency.Convertor; import org.apidesign.apifest08.currency.ConvertorFactory; import org.apidesign.apifest08.currency.Money; import org.apidesign.apifest08.currency.MoneyImpl; @@ -14,17 +15,18 @@ public class ConvertorTest { + private static final Convertor CZK_TO_USD_CONVERTOR = ConvertorFactory.createConvertor(new MoneyImpl(17,CZK), new MoneyImpl(1,USD)); @Test public void testConvertSmall() { - Money converted = ConvertorFactory.createConvertor(CZK, USD).convert(new MoneyImpl(0.17,CZK)); + Money converted = CZK_TO_USD_CONVERTOR.convert(new MoneyImpl(0.17,CZK),USD); assertEquals(new MoneyImpl(new BigDecimal("0.01"),USD),converted); assertEquals(USD,converted.getCurrency()); } @Test public void testConvertSmallReverse() { - Money converted = ConvertorFactory.createConvertor(USD, CZK).convert(new MoneyImpl(0.01,USD)); + Money converted = CZK_TO_USD_CONVERTOR.convert(new MoneyImpl(0.01,USD),CZK); assertEquals(new MoneyImpl(new BigDecimal("0.17"),CZK),converted); } } diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/test/org/apidesign/apifest08/test/MoneyTest.java --- a/task1/solution02/test/org/apidesign/apifest08/test/MoneyTest.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/test/org/apidesign/apifest08/test/MoneyTest.java Tue Sep 30 11:47:02 2008 +0200 @@ -1,12 +1,12 @@ package org.apidesign.apifest08.test; import static junit.framework.Assert.assertEquals; +import static org.apidesign.apifest08.test.Task1Test.CZK; import java.math.BigDecimal; import org.apidesign.apifest08.currency.MoneyImpl; import org.junit.Test; -import static org.apidesign.apifest08.test.Task1Test.*; public class MoneyTest { @Test(expected=NullPointerException.class) @@ -19,6 +19,6 @@ } @Test public void testOk(){ - assertEquals(new BigDecimal("123.00"),new MoneyImpl(123,CZK).getAmount()); + assertEquals(0,new MoneyImpl(123,CZK).getAmount().compareTo(new BigDecimal("123"))); } } diff -r a861bd02a1d2 -r 2864c6d744c0 task1/solution02/test/org/apidesign/apifest08/test/Task1Test.java --- a/task1/solution02/test/org/apidesign/apifest08/test/Task1Test.java Mon Sep 29 13:40:19 2008 +0200 +++ b/task1/solution02/test/org/apidesign/apifest08/test/Task1Test.java Tue Sep 30 11:47:02 2008 +0200 @@ -15,9 +15,11 @@ * shall run without any runtime permissions. */ public class Task1Test extends TestCase { + public static final Currency USD = Currency.getInstance("USD"); public static final Currency CZK = Currency.getInstance("CZK"); public static final Currency SKK = Currency.getInstance("SKK"); + public Task1Test(String testName) { super(testName); } @@ -30,8 +32,19 @@ protected void tearDown() throws Exception { } + // + // Imagine 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 design such API + // + /** Create convertor that understands two currencies, CZK and - * USD. Make 1 USD == 17 CZK. + * USD. Make 1 USD == 17 CZK. This is a method provided for #1 group - + * e.g. those that know the exchange rate. They somehow need to create + * the objects from the API and tell them the exchange rate. The API itself + * knows nothing about any rates, before the createCZKtoUSD method is called. * * Creation of the convertor shall not require subclassing of any class * or interface on the client side. @@ -39,14 +52,15 @@ * @return prepared convertor ready for converting USD to CZK and CZK to USD */ public static Convertor createCZKtoUSD() { - //You can use both variants. I wrote the first one but than I realized that maybe - //you want me to write the second one. So I have written both. - return ConvertorFactory.createConvertor(CZK, USD); - //return ConvertorFactory.createConvertor(new MoneyImpl(17,CZK), new MoneyImpl(1,USD)); + return ConvertorFactory.createConvertor(new MoneyImpl(17,CZK), new MoneyImpl(1,USD)); } /** Create convertor that understands two currencies, CZK and - * SKK. Make 100 SKK == 80 CZK. + * SKK. Make 100 SKK == 80 CZK. Again this is method for the #1 group - + * it 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. * * Creation of the convertor shall not require subclassing of any class * or interface on the client side. @@ -54,38 +68,93 @@ * @return prepared convertor ready for converting SKK to CZK and CZK to SKK */ public static Convertor createSKKtoCZK() { - //You can use both variants. I wrote the first one but than I realized that maybe - //you want me to write the second one. So I have written both. - //return ConvertorFactory.createConvertor(SKK, CZK); - return ConvertorFactory.createConvertor(new MoneyImpl(100,SKK), new MoneyImpl(80,CZK)); + return ConvertorFactory.createConvertor(new MoneyImpl(100,SKK), new MoneyImpl(80,CZK)); } + + // + // now the methods for group #2 follow: + // this group knows nothing about exchange rates, but knows how to use + // the API to do conversions. It somehow (by calling one of the factory + // methods) gets objects from the API and uses them to do the conversions. + // /** Use the convertor from createCZKtoUSD method and do few conversions * with it. */ public void testCurrencyCZKUSD() throws Exception { - Convertor czkToUsdConvertor = createCZKtoUSD(); + Convertor c = createCZKtoUSD(); // convert $5 to CZK using c: - Convertor usdToCzkConvertor = czkToUsdConvertor.revert(); - assertEquals("Result is 85 CZK",new MoneyImpl(85,CZK), usdToCzkConvertor.convert(new MoneyImpl(5,USD))); + assertEquals("Result is 85 CZK",new MoneyImpl(85,CZK), c.convert(new MoneyImpl(5,USD),CZK)); // convert $8 to CZK - assertEquals("Result is 136 CZK",new MoneyImpl(136,CZK), usdToCzkConvertor.convert(new MoneyImpl(8,USD))); + assertEquals("Result is 136 CZK",new MoneyImpl(136,CZK), c.convert(new MoneyImpl(8,USD),CZK)); // convert 1003CZK to USD - assertEquals("Result is 59 USD", new MoneyImpl(59,USD), czkToUsdConvertor.convert(new MoneyImpl(1003,CZK))); + assertEquals("Result is 59 USD", new MoneyImpl(59,USD), c.convert(new MoneyImpl(1003,CZK),USD)); + } /** Use the convertor from createSKKtoCZK method and do few conversions * with it. */ public void testCurrencySKKCZK() throws Exception { - Convertor skkToCzkConvertor = createSKKtoCZK(); + Convertor c = createSKKtoCZK(); // convert 16CZK using c: - assertEquals("Result is 20 SKK", new MoneyImpl(20,SKK), skkToCzkConvertor.revert().convert(new MoneyImpl(16,CZK))); + assertEquals("Result is 20 SKK", new MoneyImpl(20,SKK), c.convert(new MoneyImpl(16,CZK),SKK)); // convert 500SKK to CZK - assertEquals("Result is 400 CZK", new MoneyImpl(400,CZK), skkToCzkConvertor.convert(new MoneyImpl(500,SKK))); + assertEquals("Result is 400 CZK", new MoneyImpl(400,CZK), c.convert(new MoneyImpl(500,SKK),CZK)); + } + + /** Verify that the CZK to USD convertor knows nothing about SKK. + */ + public void testCannotConvertToSKKwithCZKUSDConvertor() throws Exception { + Convertor c = createCZKtoUSD(); + // convert $5 to SKK, the API shall say this is not possible + try + { + c.convert(new MoneyImpl(5, USD), SKK); + fail("Exception expected"); + } + catch(IllegalArgumentException e) + { + assertTrue("Ok",true); + } + // convert 500 SKK to CZK, the API shall say this is not possible + try + { + c.convert(new MoneyImpl(500, SKK), CZK); + fail("Exception expected"); + } + catch(IllegalArgumentException e) + { + assertTrue("Ok",true); + } + } + + /** Verify that the CZK to SKK convertor knows nothing about USD. + */ + public void testCannotConvertToUSDwithCZKSKKConvertor() throws Exception { + Convertor c = createSKKtoCZK(); + // convert $5 to SKK, the API shall say this is not possible + try + { + c.convert(new MoneyImpl(5, USD), SKK); + fail("Exception expected"); + } + catch(IllegalArgumentException e) + { + assertTrue("Ok",true); + } + // convert 500 CZK to USD, the API shall say this is not possible + try + { + c.convert(new MoneyImpl(500, CZK), USD); + fail("Exception expected"); + } + catch(IllegalArgumentException e) + { + assertTrue("Ok",true); + } } } -