# HG changeset patch # User Jaroslav Tulach # Date 1224258014 -7200 # Node ID 420baec87dc5488afa0ae3b73805073702c89b54 # Parent 4de3a4b5445a7cdc89e3012f7b5b733e04e3b215 solution 4, task4 diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java --- a/task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java Fri Oct 17 17:39:18 2008 +0200 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java Fri Oct 17 17:40:14 2008 +0200 @@ -16,7 +16,7 @@ * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors. * * @author D'Arcy Smith - * @verson 1.1 + * @verson 1.2 */ final class CompositeConvertorImpl implements Convertor @@ -88,7 +88,9 @@ // we cannot derive: // USD <-> GBP // CAD <-> GBP - // CZK <-> GBP + // CZK <-> GBP + // + // NOTE: no attempt is made to deal with dates for DatedConvertors... nothing we can do about it. do { newConvertors = 0; diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java --- a/task4/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java Fri Oct 17 17:39:18 2008 +0200 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java Fri Oct 17 17:40:14 2008 +0200 @@ -2,11 +2,7 @@ import java.math.BigDecimal; -import java.math.MathContext; -import java.math.RoundingMode; -import java.util.Collections; import java.util.Currency; -import java.util.HashSet; import java.util.Set; @@ -14,32 +10,16 @@ * Convert between two currencies. * * @author D'Arcy Smith - * @version 1.0 + * @version 1.1 */ -final class ConvertorImpl - implements Convertor +class ConvertorImpl + implements ExchangeRateConvertor { /** - * The currency to convert from. */ - private final Currency currencyA; + private final ExchangeRate rate; /** - * The currency to convert to. - */ - private final Currency currencyB; - - /** - * The echange rate between a and b. - */ - private final BigDecimal currencyARate; - - /** - * The echange rate between b and a. - */ - private final BigDecimal currencyBRate; - - /** * Constructs a convertor with the specified currencies. * * @param a the currency to convert from. @@ -48,45 +28,14 @@ * @param bRate the exchage rage between to and from. * @throws IllegalArgumentException if either any of the arguments are null or if either rate <= 0. */ - public ConvertorImpl(final Currency a, - final BigDecimal aRate, - final Currency b, - final BigDecimal bRate) + public ConvertorImpl(final ExchangeRate r) { - if(a == null) + if(r == null) { - throw new IllegalArgumentException("a cannot be null"); + throw new IllegalArgumentException("r cannot be null"); } - if(b == null) - { - throw new IllegalArgumentException("b cannot be null"); - } - - if(aRate == null) - { - throw new IllegalArgumentException("aRate cannot be null"); - } - - if(bRate == null) - { - throw new IllegalArgumentException("bRate cannot be null"); - } - - if(aRate.compareTo(BigDecimal.ZERO) <= 0) - { - throw new IllegalArgumentException("aRate must be > 0, was: " + aRate); - } - - if(bRate.compareTo(BigDecimal.ZERO) <= 0) - { - throw new IllegalArgumentException("bRate must be > 0, was: " + bRate); - } - - currencyA = a; - currencyB = b; - currencyARate = aRate; - currencyBRate = bRate; + rate = r; } /** @@ -104,36 +53,7 @@ final BigDecimal amount) throws InvalidConversionException { - final BigDecimal result; - - if(amount == null) - { - throw new IllegalArgumentException("amount cannot be null"); - } - - if(from == null) - { - throw new IllegalArgumentException("from cannot be null"); - } - - if(to == null) - { - throw new IllegalArgumentException("to cannot be null"); - } - - if(!(from.equals(currencyA)) && (!(from.equals(currencyB)))) - { - throw new InvalidConversionException("cannot convert from: " + from.getCurrencyCode(), from, currencyA, currencyB); - } - - if(!(to.equals(currencyA)) && (!(to.equals(currencyB)))) - { - throw new InvalidConversionException("cannot convert to: " + to.getCurrencyCode(), to, currencyA, currencyB); - } - - result = amount.multiply(getConversionRate(from, to)); - - return (result.setScale(2, RoundingMode.HALF_DOWN)); + return (rate.convert(from, to, amount)); } /** @@ -146,18 +66,7 @@ */ public boolean canConvert(final Currency from, final Currency to) { - if(from == null) - { - throw new IllegalArgumentException("from cannot be null"); - } - - if(to == null) - { - throw new IllegalArgumentException("to cannot be null"); - } - - return ((from.equals(currencyA) || from.equals(currencyB)) && - (to.equals(currencyA) || to.equals(currencyB))); + return (rate.canConvert(from, to)); } /** @@ -167,13 +76,7 @@ */ public Set getCurrencies() { - final Set currencies; - - currencies = new HashSet(); - currencies.add(currencyA); - currencies.add(currencyB); - - return (Collections.unmodifiableSet(currencies)); + return (rate.getCurrencies()); } /** @@ -189,44 +92,7 @@ final Currency to) throws InvalidConversionException { - final BigDecimal rate; - - if(from == null) - { - throw new IllegalArgumentException("from cannot be null"); - } - - if(to == null) - { - throw new IllegalArgumentException("to cannot be null"); - } - - if(from.equals(to)) - { - rate = BigDecimal.ONE; - } - else - { - final BigDecimal rateX; - final BigDecimal rateY; - final BigDecimal temp; - - if(from.equals(currencyA)) - { - rateX = currencyARate; - rateY = currencyBRate; - } - else - { - rateX = currencyBRate; - rateY = currencyARate; - } - - temp = BigDecimal.ONE.divide(rateX, MathContext.DECIMAL64); - rate = temp.multiply(rateY); - } - - return (rate.setScale(20, RoundingMode.HALF_EVEN)); + return (rate.getConversionRate(from, to)); } /** @@ -250,29 +116,7 @@ final ConvertorImpl other = (ConvertorImpl) obj; - // it would be nice if NetBeans could chck to see if the variable is final and guaranteed not to be null... but that - // would likely be tricky... but in a NetBeans engineer reads that see if you can do it :-) - if (this.currencyA != other.currencyA && (this.currencyA == null || !this.currencyA.equals(other.currencyA))) - { - return false; - } - - if (this.currencyB != other.currencyB && (this.currencyB == null || !this.currencyB.equals(other.currencyB))) - { - return false; - } - - if (this.currencyARate != other.currencyARate && (this.currencyARate == null || !this.currencyARate.equals(other.currencyARate))) - { - return false; - } - - if (this.currencyBRate != other.currencyBRate && (this.currencyBRate == null || !this.currencyBRate.equals(other.currencyBRate))) - { - return false; - } - - return true; + return (rate.equals(other.rate)); } /** @@ -283,12 +127,7 @@ @Override public int hashCode() { - int hash = 7; - hash = 37 * hash + (this.currencyA != null ? this.currencyA.hashCode() : 0); - hash = 37 * hash + (this.currencyB != null ? this.currencyB.hashCode() : 0); - hash = 37 * hash + (this.currencyARate != null ? this.currencyARate.hashCode() : 0); - hash = 37 * hash + (this.currencyBRate != null ? this.currencyBRate.hashCode() : 0); - return hash; + return (rate.hashCode()); } /** @@ -299,6 +138,11 @@ @Override public String toString() { - return (currencyA.getCurrencyCode() + " to " + currencyB.getCurrencyCode()); + return (rate.getCurrencyA().getCurrencyCode() + " to " + rate.getCurrencyB().getCurrencyCode()); + } + + public ExchangeRate getExchangeRate() + { + return (rate); } } diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java --- a/task4/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java Fri Oct 17 17:39:18 2008 +0200 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java Fri Oct 17 17:40:14 2008 +0200 @@ -2,26 +2,17 @@ import java.math.BigDecimal; import java.util.Currency; +import java.util.Date; /** * Create convertors using a flyweight to reduce the number of repetative creations of the same convertor. * * @author D'Arcy Smith - * @version 1.0 + * @version 1.2 */ public final class ConvertorFactory { - /* - * flyweight so that only one vestion of each converter is created at a time. - private final static Map> convertors; - - static - { - convertors = new WeakHashMap>(); - } - */ - /** * Prevent accidental construction. */ @@ -71,8 +62,8 @@ final Currency b, final BigDecimal bRate) { - // final String key; - Convertor convertor; + Convertor convertor; + final ExchangeRate rate; if(a == null) { @@ -94,27 +85,69 @@ throw new IllegalArgumentException("bRate cannot be null"); } - /* - key = a.getCurrencyCode() + aRate + b.getCurrencyCode() + bRate; - - // make sure that we don't try to overwrite one - synchronized(convertors) - { - if(!(convertors.containsKey(key))) - { - convertor = new ConvertorImpl(a, aRate, b, bRate); - convertors.put(key, new WeakReference(convertor)); - } - - convertor = convertors.get(key).get(); - } - */ - - convertor = new ConvertorImpl(a, aRate, b, bRate); + rate = ExchangeRate.getExchangeRate(a, b, aRate, bRate); + convertor = getConvertor(rate); return (convertor); } + public static Convertor getConvertor(final ExchangeRate rate) + { + final ConvertorImpl convertor; + + if(rate == null) + { + throw new IllegalArgumentException("rate cannot be null"); + } + + convertor = new ConvertorImpl(rate); + + return (convertor); + } + + public static DatedConvertor getConvertor(final Date from, + final Date till, + final Convertor convertor) + { + final DateRange range; + final ExchangeRate rate; + final DatedConvertor datedConvertor; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(till == null) + { + throw new IllegalArgumentException("till cannot be null"); + } + + if(convertor == null) + { + throw new IllegalArgumentException("convertor cannot be null"); + } + + if(from.after(till)) + { + throw new IllegalArgumentException(from + " cannot be after " + till); + } + + if(convertor instanceof ExchangeRateConvertor) + { + rate = ((ExchangeRateConvertor)convertor).getExchangeRate(); + } + else + { + throw new Error(); + } + + range = new DateRange(from, till); + datedConvertor = new DatedConvertorImpl(range, rate); + + return (datedConvertor); + } + /** * * @param cs @@ -123,55 +156,38 @@ public static Convertor mergeConvertors(final Convertor ... cs) { Convertor convertor; - - /* - final String key; + int dated; + int nonDated; - // ISSUE: only takes into account the names... not the rates... - key = getKey(cs); + dated = 0; + nonDated = 0; - // make sure that we don't try to overwrite one - synchronized(convertors) + for(final Convertor c : cs) { - if(!(convertors.containsKey(key))) - { - convertor = new CompositeConvertorImpl(cs); - convertors.put(key, new WeakReference(convertor)); + if(c instanceof DatedConvertor) + { + dated++; } + else + { + nonDated++; + } + } - convertor = convertors.get(key).get(); + if(dated != 0 && nonDated != 0) + { + throw new IllegalArgumentException("cannot mix DatedConvertors and non-DatedConvertors"); } - */ - - convertor = new CompositeConvertorImpl(cs); - + + if(dated != 0) + { + convertor = new DatedCompositeConvertorImpl(cs); + } + else + { + convertor = new CompositeConvertorImpl(cs); + } + return (convertor); } - - /* - private static String getKey(final Convertor ... cs) - { - final Set currencies; - final StringBuilder builder; - - currencies = new HashSet(); - - for(final Convertor convertor : cs) - { - final Set c; - - c = convertor.getCurrencies(); - currencies.addAll(c); - } - - builder = new StringBuilder(); - - for(final Currency currency : currencies) - { - builder.append(currency.getCurrencyCode()); - } - - return (builder.toString()); - } - */ } diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/DateRange.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DateRange.java Fri Oct 17 17:40:14 2008 +0200 @@ -0,0 +1,91 @@ +package org.apidesign.apifest08.currency; + + +import java.util.Date; + + +public final class DateRange +{ + private final Date from; + private final Date till; + + DateRange(final Date f, + final Date t) + { + if(f == null && t != null) + { + throw new IllegalArgumentException("f was null but t was not"); + } + + if(f != null && t == null) + { + throw new IllegalArgumentException("f was null but t was not"); + } + + from = f; + till = t; + } + + public Date getFrom() + { + return (from); + } + + public Date getTill() + { + return (from); + } + + public boolean isInRange(final Date date) + { + final boolean retVal; + + if(date.equals(from) || date.equals(till)) + { + retVal = true; + } + else if(date.after(from) && date.before(till)) + { + retVal = true; + } + else + { + retVal = false; + } + + return (retVal); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DateRange other = (DateRange) obj; + if (this.from != other.from && (this.from == null || !this.from.equals(other.from))) { + return false; + } + if (this.till != other.till && (this.till == null || !this.till.equals(other.till))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 89 * hash + (this.from != null ? this.from.hashCode() : 0); + hash = 89 * hash + (this.till != null ? this.till.hashCode() : 0); + return hash; + } + + @Override + public String toString() + { + return (from + " until " + till); + } + +} diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/DatedCompositeConvertorImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DatedCompositeConvertorImpl.java Fri Oct 17 17:40:14 2008 +0200 @@ -0,0 +1,319 @@ +package org.apidesign.apifest08.currency; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + + +final class DatedCompositeConvertorImpl + implements TimedConvertor +{ + /** + * The convertors that are supported. + */ + private final DatedConvertor[] convertors; + + /** + * Keeps track of what convertors to use to convert between currencies. + */ + private final Map>> datedConversions; + + { + datedConversions = new HashMap>>(); + } + + /** + * Construct a ComositeConvertorImpl with the specified convertors. + * This will result in all possible conversions between the supplied currencies being made. + * + * @param cs the convertors to use. + * @throws IllegalArgumentException if any of the items in cs are null. + */ + DatedCompositeConvertorImpl(Convertor ... cs) + { + int i; + + convertors = new DatedConvertor[cs.length]; + i = 0; + + for(final Convertor c : cs) + { + if(!(c instanceof DatedConvertor)) + { + throw new IllegalArgumentException("cs must only contain DatedConvertors"); + } + + convertors[i] = (DatedConvertor)c; + i++; + } + + // track all of the known conversion + for(final DatedConvertor convertor : convertors) + { + final Set currencies; + Map possible; + DateRange range; + + if(convertor == null) + { + throw new IllegalArgumentException("cs cannot contain null"); + } + + currencies = convertor.getCurrencies(); + range = convertor.getDateRange(); + + for(final Currency currency : currencies) + { + Map> possibleConversions; + + possibleConversions = datedConversions.get(range); + + if(possibleConversions == null) + { + possibleConversions = new HashMap>(); + datedConversions.put(range, possibleConversions); + } + + possible = possibleConversions.get(currency); + + if(possible == null) + { + possible = new HashMap(); + possibleConversions.put(currency, possible); + } + + for(final Currency c : currencies) + { + possible.put(c, convertor); + } + } + } + + + /* + // make up conversions that can be derived... eg: + // we have: + // USD <-> CAD + // CAD <-> CZK + // SSK <-> GBP + // we can derive: + // USD <-> CZK + // we cannot derive: + // USD <-> GBP + // CAD <-> GBP + // CZK <-> GBP + // + // NOTE: no attempt is made to deal with dates for DatedConvertors... nothing we can do about it. + do + { + newConvertors = 0; + + // todo... need to loop this until all the ones that can be handled are done. + for(final Currency from : getCurrencies()) + { + for(final Currency to : getCurrencies()) + { + if(!(canConvert(from, to))) + { + final Set fromCurrencies; + final Set toCurrencies; + final Set common; + Map> possibleConversions; + + possibleConversions.get(range); + fromCurrencies = possibleConversions.get(from).keySet(); + toCurrencies = possibleConversions.get(to).keySet(); + common = new HashSet(); + + for(final Currency currency : fromCurrencies) + { + if(toCurrencies.contains(currency)) + { + common.add(currency); + } + } + + for(final Currency currency : common) + { + final Convertor convertor; + + convertor = createConvertor(from, to, currency); + possibleConversions.get(from).put(to, convertor); + possibleConversions.get(to).put(from, convertor); + newConvertors++; + } + } + } + } + } + while(newConvertors > 0); + */ + } + + /** + * Check to see if converting between the two currencies is possible. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return true if the conversion is possible. + * @throws IllegalArgumentException if either from or to are null. + */ + public boolean canConvert(final Currency from, final Currency to) + { + throw new UnsupportedOperationException(); + } + + /** + * Get the currencies that the convertor supports. Just because a currency is + * supported does not mean that canConvert will return true. + * + * @return the supported currencies. + */ + public Set getCurrencies() + { + throw new UnsupportedOperationException(); + } + + /** + * Get the conversion rate between two currencies. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return the conversion rate between the two currencies. + * @throws IllegalArgumentException if either from or to is null. + * @throws InvalidConversionException if canConvert would return false. + */ + public BigDecimal getConversionRate(final Currency from, final Currency to) + throws InvalidConversionException + { + throw new UnsupportedOperationException(); + } + + private Convertor getConvertor(final Currency from, final Currency to, final Date date) + { + Map> possibleConversions; + final Map possible; + Convertor convertor; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + possibleConversions = null; + + for(final DateRange range : datedConversions.keySet()) + { + if(range.isInRange(date)) + { + possibleConversions = datedConversions.get(range); + break; + } + } + + if(possibleConversions == null) + { + return (null); + } + + possible = possibleConversions.get(from); + + + if(possible == null) + { + return (null); + } + + + convertor = possible.get(to); + + + if(convertor == null) + { + return (null); + } + + return (convertor); + } + + /** + * Convert an amount from one currency to another. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @param amount the amount to convert. + * @return the converted amount. + * @throws IllegalArgumentException if any of the arguments are null. + * @throws InvalidConversionException if either from or to are not valid for the convertor. + */ + public BigDecimal convert(final Currency from, + final Currency to, + final BigDecimal amount) + throws InvalidConversionException + { + throw new InvalidConversionException("No date for the conversion", from); + } + + public BigDecimal convert(final Currency from, + final Currency to, + final BigDecimal amount, + final Date date) + throws InvalidConversionException + { + final Convertor convertor; + final BigDecimal total; + + convertor = getConvertor(from, to, date); + + if(convertor == null) + { + throw new InvalidConversionException("cannot convert", from); + } + + if(canConvert(from, to, date)) + { + final TimedConvertor timeConvertor; + + timeConvertor = (TimedConvertor)convertor; + total = timeConvertor.convert(from, to, amount, date); + } + else + { + throw new InvalidConversionException("cannot convert", from); + } + + return (total); + } + + public boolean canConvert(final Currency from, + final Currency to, + final Date date) + { + Convertor convertor; + final boolean retVal; + + convertor = getConvertor(from, to, date); + + if(convertor != null) + { + final TimedConvertor timeConvertor; + + timeConvertor = (TimedConvertor)convertor; + retVal = timeConvertor.canConvert(from, to, date); + } + else + { + retVal = false; + } + + return (retVal); + } +} diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertor.java Fri Oct 17 17:40:14 2008 +0200 @@ -0,0 +1,9 @@ +package org.apidesign.apifest08.currency; + + +public interface DatedConvertor + extends ExchangeRateConvertor, + TimedConvertor +{ + DateRange getDateRange(); +} diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertorImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertorImpl.java Fri Oct 17 17:40:14 2008 +0200 @@ -0,0 +1,82 @@ +package org.apidesign.apifest08.currency; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.Date; + + +final class DatedConvertorImpl + extends ConvertorImpl + implements DatedConvertor +{ + private final DateRange range; + + DatedConvertorImpl(final DateRange rng, + final ExchangeRate r) + { + super(r); + + if(rng == null) + { + throw new IllegalArgumentException("rng cannot be null"); + } + + range = rng; + } + + public DateRange getDateRange() + { + return (range); + } + + @Override + public BigDecimal convert(final Currency from, + final Currency to, + final BigDecimal amount) + throws InvalidConversionException + { + final BigDecimal total; + + total = convert(from, to, amount, new Date(System.currentTimeMillis())); + + return (total); + } + + public BigDecimal convert(final Currency from, + final Currency to, + final BigDecimal amount, + final Date date) + throws InvalidConversionException + { + final BigDecimal total; + + if(range.isInRange(date)) + { + total = super.convert(from, to, amount); + } + else + { + throw new InvalidConversionException("cannot convert for date", from); + } + + return (total); + } + + public boolean canConvert(final Currency from, + final Currency to, + final Date date) + { + final boolean retVal; + + if(canConvert(from, to)) + { + retVal = range.isInRange(date); + } + else + { + retVal = false; + } + + return (retVal); + } +} diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java --- a/task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java Fri Oct 17 17:39:18 2008 +0200 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java Fri Oct 17 17:40:14 2008 +0200 @@ -2,14 +2,18 @@ import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; import java.util.Currency; +import java.util.HashSet; +import java.util.Set; /** * The exchange rate between two currencies. * * @author D'Arcy Smith - * @version 1.0 + * @version 1.1 */ public final class ExchangeRate { @@ -122,4 +126,201 @@ { return rateBtoA; } + + public static ExchangeRate getExchangeRate(final Currency a, + final Currency b, + final BigDecimal va, + final BigDecimal vb) + { + final BigDecimal rateAtoB; + final BigDecimal rateBtoA; + final ExchangeRate rate; + + if(a == null) + { + throw new IllegalArgumentException("a cannot be null"); + } + + if(b == null) + { + throw new IllegalArgumentException("b cannot be null"); + } + + if(a.equals(b)) + { + rateAtoB = BigDecimal.ONE; + rateBtoA = BigDecimal.ONE; + } + else + { + rateAtoB = vb.divide(va, 20, RoundingMode.HALF_DOWN); + rateBtoA = va.divide(vb, 20, RoundingMode.HALF_DOWN); + } + + rate = new ExchangeRate(a, + b, + rateAtoB.setScale(20, RoundingMode.HALF_EVEN), + rateBtoA.setScale(20, RoundingMode.HALF_EVEN)); + + return (rate); + } + + public BigDecimal convert(final Currency from, + final Currency to, + final BigDecimal amount) + throws InvalidConversionException + { + final BigDecimal result; + + if(amount == null) + { + throw new IllegalArgumentException("amount cannot be null"); + } + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + if(!(from.equals(currencyA)) && (!(from.equals(currencyB)))) + { + throw new InvalidConversionException("cannot convert from: " + from.getCurrencyCode(), from, currencyA, currencyB); + } + + if(!(to.equals(currencyA)) && (!(to.equals(currencyB)))) + { + throw new InvalidConversionException("cannot convert to: " + to.getCurrencyCode(), to, currencyA, currencyB); + } + + result = amount.multiply(getConversionRate(from, to)); + + return (result.setScale(2, RoundingMode.HALF_DOWN)); + } + + /** + * Check to see if converting between the two currencies is possible. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return true if the conversion is possible. + * @throws IllegalArgumentException if either from or to are null. + */ + public boolean canConvert(final Currency from, final Currency to) + { + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + return ((from.equals(currencyA) || from.equals(currencyB)) && + (to.equals(currencyA) || to.equals(currencyB))); + } + /** + * Get the currencies that the convertor supports. + * + * @return the supported currencies. + */ + public Set getCurrencies() + { + final Set currencies; + + currencies = new HashSet(); + currencies.add(currencyA); + currencies.add(currencyB); + + return (Collections.unmodifiableSet(currencies)); + } + + /** + * Get the conversion rate between two currencies. + * + * @param from the currency to convert from. + * @param to the currency to convert to. + * @return the conversion rate between the two currencies. + * @throws InvalidConversionException if canConvert would return false. + * @throws IllegalArgumentException if either from or to are null. + */ + public BigDecimal getConversionRate(final Currency from, + final Currency to) + throws InvalidConversionException + { + final BigDecimal rate; + + if(from == null) + { + throw new IllegalArgumentException("from cannot be null"); + } + + if(to == null) + { + throw new IllegalArgumentException("to cannot be null"); + } + + if(from.equals(to)) + { + rate = BigDecimal.ONE; + } + else + { + if(from.equals(currencyA)) + { + rate = rateAtoB; + } + else + { + rate = rateBtoA; + } + } + + return (rate); + } + + public String toString() + { + return (rateAtoB + " : " + rateBtoA); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ExchangeRate other = (ExchangeRate) obj; + if (this.currencyA != other.currencyA && (this.currencyA == null || !this.currencyA.equals(other.currencyA))) { + return false; + } + if (this.currencyB != other.currencyB && (this.currencyB == null || !this.currencyB.equals(other.currencyB))) { + return false; + } + if (this.rateAtoB != other.rateAtoB && (this.rateAtoB == null || !this.rateAtoB.equals(other.rateAtoB))) { + return false; + } + if (this.rateBtoA != other.rateBtoA && (this.rateBtoA == null || !this.rateBtoA.equals(other.rateBtoA))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + (this.currencyA != null ? this.currencyA.hashCode() : 0); + hash = 97 * hash + (this.currencyB != null ? this.currencyB.hashCode() : 0); + hash = 97 * hash + (this.rateAtoB != null ? this.rateAtoB.hashCode() : 0); + hash = 97 * hash + (this.rateBtoA != null ? this.rateBtoA.hashCode() : 0); + return hash; + } } diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRateConvertor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRateConvertor.java Fri Oct 17 17:40:14 2008 +0200 @@ -0,0 +1,8 @@ +package org.apidesign.apifest08.currency; + + +public interface ExchangeRateConvertor + extends Convertor +{ + ExchangeRate getExchangeRate(); +} diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/src/org/apidesign/apifest08/currency/TimedConvertor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/TimedConvertor.java Fri Oct 17 17:40:14 2008 +0200 @@ -0,0 +1,17 @@ +package org.apidesign.apifest08.currency; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.Date; + + +public interface TimedConvertor + extends Convertor +{ + BigDecimal convert(Currency from, + Currency to, + BigDecimal amount, + Date date) + throws InvalidConversionException; + boolean canConvert(Currency from, Currency to, Date date); +} \ No newline at end of file diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/test/org/apidesign/apifest08/test/Task1Test.java --- a/task4/solution04/test/org/apidesign/apifest08/test/Task1Test.java Fri Oct 17 17:39:18 2008 +0200 +++ b/task4/solution04/test/org/apidesign/apifest08/test/Task1Test.java Fri Oct 17 17:40:14 2008 +0200 @@ -18,9 +18,9 @@ */ public class Task1Test extends TestCase { - private final static Currency CZK; - private final static Currency SKK; - private final static Currency USD; + public final static Currency CZK; + public final static Currency SKK; + public final static Currency USD; static { diff -r 4de3a4b5445a -r 420baec87dc5 task4/solution04/test/org/apidesign/apifest08/test/Task4Test.java --- a/task4/solution04/test/org/apidesign/apifest08/test/Task4Test.java Fri Oct 17 17:39:18 2008 +0200 +++ b/task4/solution04/test/org/apidesign/apifest08/test/Task4Test.java Fri Oct 17 17:40:14 2008 +0200 @@ -1,8 +1,15 @@ package org.apidesign.apifest08.test; +import java.math.BigDecimal; +import java.util.Calendar; import java.util.Date; +import java.util.TimeZone; import junit.framework.TestCase; import org.apidesign.apifest08.currency.Convertor; +import org.apidesign.apifest08.currency.ConvertorFactory; +import org.apidesign.apifest08.currency.ExchangeRate; +import org.apidesign.apifest08.currency.InvalidConversionException; +import org.apidesign.apifest08.currency.TimedConvertor; /** The exchange rates are not always the same. They are changing. However * as in order to predict the future, one needs to understand own past. That is @@ -22,8 +29,11 @@ super(testName); } + private Calendar gmtCalendar; + @Override protected void setUp() throws Exception { + gmtCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); } @Override @@ -45,57 +55,133 @@ * @return new convertor */ public static Convertor limitTo(Convertor old, Date from, Date till) { - return null; + final Convertor convertor; + + convertor = ConvertorFactory.getConvertor(from, till, old); + + return convertor; } public void testCompositionOfLimitedConvertors() throws Exception { - if (Boolean.getBoolean("ignore.failing")) { - // implement me! then delete this if statement - return; - } - Date d1 = null; // 2008-10-01 0:00 GMT - Date d2 = null; // 2008-10-02 0:00 GMT - Date d3 = null; // 2008-10-03 0:00 GMT + gmtCalendar.set(2008, Calendar.OCTOBER, 1, 0, 0, 0); + Date d1 = gmtCalendar.getTime(); // 2008-10-01 0:00 GMT + gmtCalendar.set(2008, Calendar.OCTOBER, 2, 0, 0, 0); + Date d2 = gmtCalendar.getTime(); // 2008-10-02 0:00 GMT + gmtCalendar.set(2008, Calendar.OCTOBER, 3, 0, 0, 0); + Date d3 = gmtCalendar.getTime(); // 2008-10-03 0:00 GMT Convertor c = Task2Test.merge( limitTo(Task1Test.createCZKtoUSD(), d1, d2), limitTo(Task1Test.createSKKtoCZK(), d2, d3) ); + Date date; + BigDecimal amount; + // convert $5 to CZK using c: // cannot convert as no rate is applicable to current date + try + { + c.convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("5.00")); + fail("test A"); + } + catch(final InvalidConversionException ex) + { + } // convert $8 to CZK using c: // cannot convert as no rate is applicable to current date + try + { + c.convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("8.00")); + fail("test B"); + } + catch(final InvalidConversionException ex) + { + } // convert 1003CZK to USD using c: // cannot convert as no rate is applicable to current date + try + { + c.convert(Task1Test.CZK, Task1Test.USD, new BigDecimal("1003.00")); + fail("test C"); + } + catch(final InvalidConversionException ex) + { + } // convert 16CZK using c: // cannot convert as no rate is applicable to current date + try + { + c.convert(Task1Test.CZK, Task1Test.USD, new BigDecimal("16.00")); + fail("test D"); + } + catch(final InvalidConversionException ex) + { + } // convert 500SKK to CZK using c: // cannot convert as no rate is applicable to current date + try + { + c.convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00")); + fail("test C"); + } + catch(final InvalidConversionException ex) + { + } // convert $5 to CZK using c at 2008-10-01 6:00 GMT: // assertEquals("Result is 85 CZK"); + gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0); + date = gmtCalendar.getTime(); + amount = ((TimedConvertor)c).convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("5.00"), date); + assertEquals(new BigDecimal("85.00"), amount); // convert $8 to CZK using c at 2008-10-01 6:00 GMT: // assertEquals("Result is 136 CZK"); + gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0); + date = gmtCalendar.getTime(); + amount = ((TimedConvertor)c).convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("8.00"), date); + assertEquals(new BigDecimal("136.00"), amount); // convert 1003CZK to USD using c at 2008-10-01 6:00 GMT: // assertEquals("Result is 59 USD"); + gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0); + date = gmtCalendar.getTime(); + amount = ((TimedConvertor)c).convert(Task1Test.CZK, Task1Test.USD, new BigDecimal("1003.00"), date); + assertEquals(new BigDecimal("59.00"), amount); // convert 16CZK using c at 2008-10-02 9:00 GMT: // assertEquals("Result is 20 SKK"); + gmtCalendar.set(2008, Calendar.OCTOBER, 2, 9, 0, 0); + date = gmtCalendar.getTime(); + amount = ((TimedConvertor)c).convert(Task1Test.CZK, Task1Test.SKK, new BigDecimal("16.00"), date); + assertEquals(new BigDecimal("20.00"), amount); // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT: // assertEquals("Result is 400 CZK"); + gmtCalendar.set(2008, Calendar.OCTOBER, 2, 9, 0, 0); + date = gmtCalendar.getTime(); + amount = ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date); + assertEquals(new BigDecimal("400.00"), amount); // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT: // cannot convert as no rate is applicable to current date + try + { + gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0); + date = gmtCalendar.getTime(); + ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date); + fail("test D"); + } + catch(final InvalidConversionException ex) + { + } } /** Create convertor that understands two currencies, CZK and @@ -103,30 +189,49 @@ * * @return prepared convertor ready for converting SKK to CZK and CZK to SKK */ - public static Convertor createSKKtoCZK2() { - return null; + public static Convertor createSKKtoCZK2() + { + final ExchangeRate rate; + final Convertor convertor; + + rate = ExchangeRate.getExchangeRate(Task1Test.SKK, + Task1Test.CZK, + new BigDecimal("100.00"), + new BigDecimal("90.00")); + convertor = ConvertorFactory.getConvertor(rate); + + return (convertor); } public void testDateConvetorWithTwoDifferentRates() throws Exception { - if (Boolean.getBoolean("ignore.failing")) { - // implement me! then delete this if statement - return; - } - - Date d1 = null; // 2008-10-01 0:00 GMT - Date d2 = null; // 2008-10-02 0:00 GMT - Date d3 = null; // 2008-10-03 0:00 GMT + gmtCalendar.set(2008, Calendar.OCTOBER, 1, 0, 0, 0); + Date d1 = gmtCalendar.getTime(); // 2008-10-01 0:00 GMT + gmtCalendar.set(2008, Calendar.OCTOBER, 2, 0, 0, 0); + Date d2 = gmtCalendar.getTime(); // 2008-10-02 0:00 GMT + gmtCalendar.set(2008, Calendar.OCTOBER, 3, 0, 0, 0); + Date d3 = gmtCalendar.getTime(); // 2008-10-03 0:00 GMT Convertor c = Task2Test.merge( limitTo(createSKKtoCZK2(), d1, d2), limitTo(Task1Test.createSKKtoCZK(), d2, d3) ); + Date date; + BigDecimal amount; + // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT: // assertEquals("Result is 400 CZK"); + gmtCalendar.set(2008, Calendar.OCTOBER, 2, 6, 0, 0); + date = gmtCalendar.getTime(); + amount = ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date); + assertEquals(new BigDecimal("400.00"), amount); // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT: // assertEquals("Result is 450 CZK"); + gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0); + date = gmtCalendar.getTime(); + amount = ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date); + assertEquals(new BigDecimal("450.00"), amount); } }