solution 4, task4
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Fri, 17 Oct 2008 17:40:14 +0200
changeset 69420baec87dc5
parent 68 4de3a4b5445a
child 70 6f4b6952f988
solution 4, task4
task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java
task4/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java
task4/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java
task4/solution04/src/org/apidesign/apifest08/currency/DateRange.java
task4/solution04/src/org/apidesign/apifest08/currency/DatedCompositeConvertorImpl.java
task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertor.java
task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertorImpl.java
task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java
task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRateConvertor.java
task4/solution04/src/org/apidesign/apifest08/currency/TimedConvertor.java
task4/solution04/test/org/apidesign/apifest08/test/Task1Test.java
task4/solution04/test/org/apidesign/apifest08/test/Task4Test.java
     1.1 --- a/task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java	Fri Oct 17 17:39:18 2008 +0200
     1.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java	Fri Oct 17 17:40:14 2008 +0200
     1.3 @@ -16,7 +16,7 @@
     1.4   * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors.
     1.5   *
     1.6   * @author D'Arcy Smith
     1.7 - * @verson 1.1
     1.8 + * @verson 1.2
     1.9   */
    1.10  final class CompositeConvertorImpl
    1.11      implements Convertor
    1.12 @@ -88,7 +88,9 @@
    1.13          //   we cannot derive:
    1.14          //      USD <-> GBP
    1.15          //      CAD <-> GBP
    1.16 -        //      CZK <-> GBP
    1.17 +        //      CZK <-> GBP        
    1.18 +        // 
    1.19 +        // NOTE: no attempt is made to deal with dates for DatedConvertors... nothing we can do about it.
    1.20          do
    1.21          {
    1.22              newConvertors = 0;
     2.1 --- a/task4/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java	Fri Oct 17 17:39:18 2008 +0200
     2.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java	Fri Oct 17 17:40:14 2008 +0200
     2.3 @@ -2,11 +2,7 @@
     2.4  
     2.5  
     2.6  import java.math.BigDecimal;
     2.7 -import java.math.MathContext;
     2.8 -import java.math.RoundingMode;
     2.9 -import java.util.Collections;
    2.10  import java.util.Currency;
    2.11 -import java.util.HashSet;
    2.12  import java.util.Set;
    2.13  
    2.14  
    2.15 @@ -14,32 +10,16 @@
    2.16   * Convert between two currencies.
    2.17   *
    2.18   * @author D'Arcy Smith
    2.19 - * @version 1.0
    2.20 + * @version 1.1
    2.21   */
    2.22 -final class ConvertorImpl
    2.23 -    implements Convertor
    2.24 +class ConvertorImpl
    2.25 +    implements ExchangeRateConvertor
    2.26  {
    2.27      /**
    2.28 -     * The currency to convert from.
    2.29       */
    2.30 -    private final Currency currencyA;
    2.31 +    private final ExchangeRate rate;
    2.32  
    2.33      /**
    2.34 -     * The currency to convert to.
    2.35 -     */
    2.36 -    private final Currency currencyB;
    2.37 -
    2.38 -    /**
    2.39 -     * The echange rate between a and b.
    2.40 -     */
    2.41 -    private final BigDecimal currencyARate;
    2.42 -
    2.43 -    /**
    2.44 -     * The echange rate between b and a.
    2.45 -     */
    2.46 -    private final BigDecimal currencyBRate;
    2.47 -    
    2.48 -    /**
    2.49       * Constructs a convertor with the specified currencies.
    2.50       * 
    2.51       * @param a the currency to convert from.
    2.52 @@ -48,45 +28,14 @@
    2.53       * @param bRate the exchage rage between to and from.
    2.54       * @throws IllegalArgumentException if either any of the arguments are null or if either rate <= 0.
    2.55       */
    2.56 -    public ConvertorImpl(final Currency   a,
    2.57 -                         final BigDecimal aRate,
    2.58 -                         final Currency   b,
    2.59 -                         final BigDecimal bRate)
    2.60 +    public ConvertorImpl(final ExchangeRate r)
    2.61      {
    2.62 -        if(a == null)
    2.63 +        if(r == null)
    2.64          {
    2.65 -            throw new IllegalArgumentException("a cannot be null");
    2.66 +            throw new IllegalArgumentException("r cannot be null");
    2.67          }
    2.68  
    2.69 -        if(b == null)
    2.70 -        {
    2.71 -            throw new IllegalArgumentException("b cannot be null");
    2.72 -        }
    2.73 -
    2.74 -        if(aRate == null)
    2.75 -        {
    2.76 -            throw new IllegalArgumentException("aRate cannot be null");
    2.77 -        }
    2.78 -
    2.79 -        if(bRate == null)
    2.80 -        {
    2.81 -            throw new IllegalArgumentException("bRate cannot be null");
    2.82 -        }
    2.83 -                
    2.84 -        if(aRate.compareTo(BigDecimal.ZERO) <= 0)
    2.85 -        {
    2.86 -            throw new IllegalArgumentException("aRate must be > 0, was: " + aRate);
    2.87 -        }
    2.88 -                
    2.89 -        if(bRate.compareTo(BigDecimal.ZERO) <= 0)
    2.90 -        {
    2.91 -            throw new IllegalArgumentException("bRate must be > 0, was: " + bRate);
    2.92 -        }
    2.93 -        
    2.94 -        currencyA     = a;
    2.95 -        currencyB     = b;
    2.96 -        currencyARate = aRate;
    2.97 -        currencyBRate = bRate;
    2.98 +        rate = r;
    2.99      }
   2.100      
   2.101      /**
   2.102 @@ -104,36 +53,7 @@
   2.103                                final BigDecimal amount)
   2.104          throws InvalidConversionException
   2.105      {
   2.106 -        final BigDecimal result;
   2.107 -        
   2.108 -        if(amount == null)
   2.109 -        {
   2.110 -            throw new IllegalArgumentException("amount cannot be null");
   2.111 -        }
   2.112 -        
   2.113 -        if(from == null)
   2.114 -        {
   2.115 -            throw new IllegalArgumentException("from cannot be null");
   2.116 -        }
   2.117 -        
   2.118 -        if(to == null)
   2.119 -        {
   2.120 -            throw new IllegalArgumentException("to cannot be null");
   2.121 -        }
   2.122 -        
   2.123 -        if(!(from.equals(currencyA)) && (!(from.equals(currencyB))))
   2.124 -        {
   2.125 -            throw new InvalidConversionException("cannot convert from: " + from.getCurrencyCode(), from, currencyA, currencyB);
   2.126 -        }
   2.127 -        
   2.128 -        if(!(to.equals(currencyA)) && (!(to.equals(currencyB))))
   2.129 -        {
   2.130 -            throw new InvalidConversionException("cannot convert to: " + to.getCurrencyCode(), to, currencyA, currencyB);
   2.131 -        }
   2.132 -
   2.133 -        result = amount.multiply(getConversionRate(from, to));
   2.134 -
   2.135 -        return (result.setScale(2, RoundingMode.HALF_DOWN));
   2.136 +        return (rate.convert(from, to, amount));
   2.137      }
   2.138  
   2.139      /**
   2.140 @@ -146,18 +66,7 @@
   2.141       */
   2.142      public boolean canConvert(final Currency from, final Currency to)
   2.143      {
   2.144 -        if(from == null)
   2.145 -        {
   2.146 -            throw new IllegalArgumentException("from cannot be null");
   2.147 -        }
   2.148 -        
   2.149 -        if(to == null)
   2.150 -        {
   2.151 -            throw new IllegalArgumentException("to cannot be null");
   2.152 -        }
   2.153 -        
   2.154 -        return ((from.equals(currencyA) || from.equals(currencyB)) &&
   2.155 -                (to.equals(currencyA)   || to.equals(currencyB)));
   2.156 +        return (rate.canConvert(from, to));
   2.157      }
   2.158  
   2.159      /**
   2.160 @@ -167,13 +76,7 @@
   2.161       */
   2.162      public Set<Currency> getCurrencies()
   2.163      {
   2.164 -        final Set<Currency> currencies;
   2.165 -        
   2.166 -        currencies = new HashSet<Currency>();
   2.167 -        currencies.add(currencyA);
   2.168 -        currencies.add(currencyB);
   2.169 -
   2.170 -        return (Collections.unmodifiableSet(currencies));
   2.171 +        return (rate.getCurrencies());
   2.172      }
   2.173  
   2.174      /**
   2.175 @@ -189,44 +92,7 @@
   2.176                                          final Currency to)
   2.177          throws InvalidConversionException
   2.178      {                
   2.179 -        final BigDecimal rate;
   2.180 -        
   2.181 -        if(from == null)
   2.182 -        {
   2.183 -            throw new IllegalArgumentException("from cannot be null");
   2.184 -        }
   2.185 -        
   2.186 -        if(to == null)
   2.187 -        {
   2.188 -            throw new IllegalArgumentException("to cannot be null");
   2.189 -        }
   2.190 -
   2.191 -        if(from.equals(to))
   2.192 -        {
   2.193 -            rate = BigDecimal.ONE;
   2.194 -        }
   2.195 -        else 
   2.196 -        {
   2.197 -            final BigDecimal rateX;
   2.198 -            final BigDecimal rateY;
   2.199 -            final BigDecimal temp;
   2.200 -            
   2.201 -            if(from.equals(currencyA))
   2.202 -            {
   2.203 -                rateX = currencyARate;
   2.204 -                rateY = currencyBRate;
   2.205 -            }
   2.206 -            else
   2.207 -            {
   2.208 -                rateX = currencyBRate;
   2.209 -                rateY = currencyARate;
   2.210 -            }
   2.211 -
   2.212 -            temp = BigDecimal.ONE.divide(rateX, MathContext.DECIMAL64);
   2.213 -            rate = temp.multiply(rateY);
   2.214 -        }
   2.215 -        
   2.216 -        return (rate.setScale(20, RoundingMode.HALF_EVEN));
   2.217 +        return (rate.getConversionRate(from, to));
   2.218      }
   2.219  
   2.220      /**
   2.221 @@ -250,29 +116,7 @@
   2.222  
   2.223          final ConvertorImpl other = (ConvertorImpl) obj;
   2.224  
   2.225 -        // it would be nice if NetBeans could chck to see if the variable is final and guaranteed not to be null... but that
   2.226 -        // would likely be tricky... but in a NetBeans engineer reads that see if you can do it :-)
   2.227 -        if (this.currencyA != other.currencyA && (this.currencyA == null || !this.currencyA.equals(other.currencyA)))
   2.228 -        {
   2.229 -            return false;
   2.230 -        }
   2.231 -        
   2.232 -        if (this.currencyB != other.currencyB && (this.currencyB == null || !this.currencyB.equals(other.currencyB)))
   2.233 -        {
   2.234 -            return false;
   2.235 -        }
   2.236 -        
   2.237 -        if (this.currencyARate != other.currencyARate && (this.currencyARate == null || !this.currencyARate.equals(other.currencyARate)))
   2.238 -        {
   2.239 -            return false;
   2.240 -        }
   2.241 -        
   2.242 -        if (this.currencyBRate != other.currencyBRate && (this.currencyBRate == null || !this.currencyBRate.equals(other.currencyBRate)))
   2.243 -        {
   2.244 -            return false;
   2.245 -        }
   2.246 -
   2.247 -        return true;
   2.248 +        return (rate.equals(other.rate));
   2.249      }
   2.250  
   2.251      /**
   2.252 @@ -283,12 +127,7 @@
   2.253      @Override
   2.254      public int hashCode()
   2.255      {
   2.256 -        int hash = 7;
   2.257 -        hash = 37 * hash + (this.currencyA != null ? this.currencyA.hashCode() : 0);
   2.258 -        hash = 37 * hash + (this.currencyB != null ? this.currencyB.hashCode() : 0);
   2.259 -        hash = 37 * hash + (this.currencyARate != null ? this.currencyARate.hashCode() : 0);
   2.260 -        hash = 37 * hash + (this.currencyBRate != null ? this.currencyBRate.hashCode() : 0);
   2.261 -        return hash;
   2.262 +        return (rate.hashCode());
   2.263      }
   2.264  
   2.265      /**
   2.266 @@ -299,6 +138,11 @@
   2.267      @Override
   2.268      public String toString()
   2.269      {
   2.270 -        return (currencyA.getCurrencyCode() + " to " + currencyB.getCurrencyCode());
   2.271 +        return (rate.getCurrencyA().getCurrencyCode() + " to " + rate.getCurrencyB().getCurrencyCode());
   2.272 +    }
   2.273 +
   2.274 +    public ExchangeRate getExchangeRate()
   2.275 +    {
   2.276 +        return (rate);
   2.277      }
   2.278  }
     3.1 --- a/task4/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java	Fri Oct 17 17:39:18 2008 +0200
     3.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java	Fri Oct 17 17:40:14 2008 +0200
     3.3 @@ -2,26 +2,17 @@
     3.4  
     3.5  import java.math.BigDecimal;
     3.6  import java.util.Currency;
     3.7 +import java.util.Date;
     3.8  
     3.9  
    3.10  /**
    3.11   * Create convertors using a flyweight to reduce the number of repetative creations of the same convertor.
    3.12   * 
    3.13   * @author D'Arcy Smith
    3.14 - * @version 1.0
    3.15 + * @version 1.2
    3.16   */
    3.17  public final class ConvertorFactory
    3.18  {
    3.19 -    /*
    3.20 -     * flyweight so that only one vestion of each converter is created at a time.
    3.21 -    private final static Map<String, WeakReference<Convertor>> convertors;
    3.22 -    
    3.23 -    static
    3.24 -    {
    3.25 -        convertors = new WeakHashMap<String, WeakReference<Convertor>>();
    3.26 -    }
    3.27 -    */
    3.28 -    
    3.29      /** 
    3.30       * Prevent accidental construction.
    3.31       */
    3.32 @@ -71,8 +62,8 @@
    3.33                                           final Currency   b,
    3.34                                           final BigDecimal bRate)
    3.35      {
    3.36 -        // final String key;        
    3.37 -        Convertor    convertor;
    3.38 +        Convertor          convertor;
    3.39 +        final ExchangeRate rate;
    3.40  
    3.41          if(a == null)
    3.42          {
    3.43 @@ -94,27 +85,69 @@
    3.44              throw new IllegalArgumentException("bRate cannot be null");
    3.45          }
    3.46  
    3.47 -        /*
    3.48 -        key = a.getCurrencyCode() + aRate + b.getCurrencyCode() + bRate;
    3.49 -
    3.50 -        // make sure that we don't try to overwrite one
    3.51 -        synchronized(convertors)
    3.52 -        {
    3.53 -            if(!(convertors.containsKey(key)))
    3.54 -            {        
    3.55 -                convertor = new ConvertorImpl(a, aRate, b, bRate);
    3.56 -                convertors.put(key, new WeakReference(convertor));
    3.57 -            }
    3.58 -
    3.59 -            convertor = convertors.get(key).get();
    3.60 -        }
    3.61 -        */
    3.62 -        
    3.63 -        convertor = new ConvertorImpl(a, aRate, b, bRate);
    3.64 +        rate      = ExchangeRate.getExchangeRate(a, b, aRate, bRate);
    3.65 +        convertor = getConvertor(rate);
    3.66                  
    3.67          return (convertor);
    3.68      }
    3.69      
    3.70 +    public static Convertor getConvertor(final ExchangeRate rate)
    3.71 +    {
    3.72 +        final ConvertorImpl convertor;
    3.73 +
    3.74 +        if(rate == null)
    3.75 +        {
    3.76 +            throw new IllegalArgumentException("rate cannot be null");
    3.77 +        }
    3.78 +        
    3.79 +        convertor = new ConvertorImpl(rate);
    3.80 +                
    3.81 +        return (convertor);        
    3.82 +    }
    3.83 +
    3.84 +    public static DatedConvertor getConvertor(final Date      from,
    3.85 +                                              final Date      till,
    3.86 +                                              final Convertor convertor)
    3.87 +    {
    3.88 +        final DateRange      range;
    3.89 +        final ExchangeRate   rate;
    3.90 +        final DatedConvertor datedConvertor;
    3.91 +
    3.92 +        if(from == null)
    3.93 +        {
    3.94 +            throw new IllegalArgumentException("from cannot be null");
    3.95 +        }
    3.96 +
    3.97 +        if(till == null)
    3.98 +        {
    3.99 +            throw new IllegalArgumentException("till cannot be null");
   3.100 +        }
   3.101 +
   3.102 +        if(convertor == null)
   3.103 +        {
   3.104 +            throw new IllegalArgumentException("convertor cannot be null");
   3.105 +        }
   3.106 +        
   3.107 +        if(from.after(till))
   3.108 +        {
   3.109 +            throw new IllegalArgumentException(from + " cannot be after " + till);
   3.110 +        }
   3.111 +        
   3.112 +        if(convertor instanceof ExchangeRateConvertor)
   3.113 +        {
   3.114 +            rate = ((ExchangeRateConvertor)convertor).getExchangeRate();
   3.115 +        }
   3.116 +        else
   3.117 +        {
   3.118 +            throw new Error();
   3.119 +        }
   3.120 +
   3.121 +        range          = new DateRange(from, till);
   3.122 +        datedConvertor = new DatedConvertorImpl(range, rate);
   3.123 +        
   3.124 +        return (datedConvertor);
   3.125 +    }
   3.126 +
   3.127      /**
   3.128       * 
   3.129       * @param cs
   3.130 @@ -123,55 +156,38 @@
   3.131      public static Convertor mergeConvertors(final Convertor ... cs)
   3.132      {
   3.133          Convertor convertor;
   3.134 -        
   3.135 -        /*
   3.136 -        final String key;
   3.137 +        int       dated;
   3.138 +        int       nonDated;
   3.139  
   3.140 -        // ISSUE: only takes into account the names... not the rates...
   3.141 -        key = getKey(cs);
   3.142 +        dated    = 0;
   3.143 +        nonDated = 0;
   3.144  
   3.145 -        // make sure that we don't try to overwrite one
   3.146 -        synchronized(convertors)
   3.147 +        for(final Convertor c : cs)
   3.148          {
   3.149 -            if(!(convertors.containsKey(key)))
   3.150 -            {        
   3.151 -                convertor = new CompositeConvertorImpl(cs);
   3.152 -                convertors.put(key, new WeakReference(convertor));
   3.153 +            if(c instanceof DatedConvertor)
   3.154 +            {
   3.155 +                dated++;
   3.156              }
   3.157 +            else
   3.158 +            {
   3.159 +                nonDated++;
   3.160 +            }
   3.161 +        }
   3.162  
   3.163 -            convertor = convertors.get(key).get();
   3.164 +        if(dated != 0 && nonDated != 0)
   3.165 +        {
   3.166 +            throw new IllegalArgumentException("cannot mix DatedConvertors and non-DatedConvertors");
   3.167          }
   3.168 -        */
   3.169 -        
   3.170 -        convertor = new CompositeConvertorImpl(cs);
   3.171 -        
   3.172 +
   3.173 +        if(dated != 0)
   3.174 +        {
   3.175 +            convertor = new DatedCompositeConvertorImpl(cs);
   3.176 +        }
   3.177 +        else
   3.178 +        {
   3.179 +            convertor = new CompositeConvertorImpl(cs);
   3.180 +        }
   3.181 +
   3.182          return (convertor);
   3.183      }
   3.184 -
   3.185 -    /*
   3.186 -    private static String getKey(final Convertor ... cs)
   3.187 -    {
   3.188 -        final Set<Currency> currencies;
   3.189 -        final StringBuilder builder;
   3.190 -        
   3.191 -        currencies = new HashSet<Currency>();
   3.192 -        
   3.193 -        for(final Convertor convertor : cs)
   3.194 -        {
   3.195 -            final Set<Currency> c;
   3.196 -            
   3.197 -            c = convertor.getCurrencies();
   3.198 -            currencies.addAll(c);
   3.199 -        }
   3.200 -        
   3.201 -        builder = new StringBuilder();
   3.202 -
   3.203 -        for(final Currency currency : currencies)
   3.204 -        {
   3.205 -            builder.append(currency.getCurrencyCode());
   3.206 -        }
   3.207 -        
   3.208 -        return (builder.toString());
   3.209 -    }
   3.210 -    */
   3.211  }
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DateRange.java	Fri Oct 17 17:40:14 2008 +0200
     4.3 @@ -0,0 +1,91 @@
     4.4 +package org.apidesign.apifest08.currency;
     4.5 +
     4.6 +
     4.7 +import java.util.Date;
     4.8 +
     4.9 +
    4.10 +public final class DateRange
    4.11 +{
    4.12 +    private final Date from;
    4.13 +    private final Date till;
    4.14 +    
    4.15 +    DateRange(final Date f,
    4.16 +              final Date t)
    4.17 +    {
    4.18 +        if(f == null && t != null)
    4.19 +        {
    4.20 +            throw new IllegalArgumentException("f was null but t was not");
    4.21 +        }
    4.22 +        
    4.23 +        if(f != null && t == null)
    4.24 +        {
    4.25 +            throw new IllegalArgumentException("f was null but t was not");
    4.26 +        }
    4.27 +        
    4.28 +        from = f;
    4.29 +        till = t;
    4.30 +    }
    4.31 +    
    4.32 +    public Date getFrom()
    4.33 +    {
    4.34 +        return (from);
    4.35 +    }
    4.36 +    
    4.37 +    public Date getTill()
    4.38 +    {
    4.39 +        return (from);
    4.40 +    }
    4.41 +    
    4.42 +    public boolean isInRange(final Date date)
    4.43 +    {
    4.44 +        final boolean retVal;
    4.45 +
    4.46 +        if(date.equals(from) || date.equals(till))
    4.47 +        {
    4.48 +            retVal = true;
    4.49 +        }
    4.50 +        else if(date.after(from) && date.before(till))
    4.51 +        {
    4.52 +            retVal = true;
    4.53 +        }
    4.54 +        else
    4.55 +        {
    4.56 +            retVal = false;
    4.57 +        }
    4.58 +
    4.59 +        return (retVal);
    4.60 +    }
    4.61 +
    4.62 +    @Override
    4.63 +    public boolean equals(Object obj) {
    4.64 +        if (obj == null) {
    4.65 +            return false;
    4.66 +        }
    4.67 +        if (getClass() != obj.getClass()) {
    4.68 +            return false;
    4.69 +        }
    4.70 +        final DateRange other = (DateRange) obj;
    4.71 +        if (this.from != other.from && (this.from == null || !this.from.equals(other.from))) {
    4.72 +            return false;
    4.73 +        }
    4.74 +        if (this.till != other.till && (this.till == null || !this.till.equals(other.till))) {
    4.75 +            return false;
    4.76 +        }
    4.77 +        return true;
    4.78 +    }
    4.79 +
    4.80 +    @Override
    4.81 +    public int hashCode() {
    4.82 +        int hash = 7;
    4.83 +        hash = 89 * hash + (this.from != null ? this.from.hashCode() : 0);
    4.84 +        hash = 89 * hash + (this.till != null ? this.till.hashCode() : 0);
    4.85 +        return hash;
    4.86 +    }
    4.87 +
    4.88 +    @Override
    4.89 +    public String toString()
    4.90 +    {
    4.91 +        return (from + " until " + till);
    4.92 +    }
    4.93 +
    4.94 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DatedCompositeConvertorImpl.java	Fri Oct 17 17:40:14 2008 +0200
     5.3 @@ -0,0 +1,319 @@
     5.4 +package org.apidesign.apifest08.currency;
     5.5 +
     5.6 +import java.math.BigDecimal;
     5.7 +import java.util.Currency;
     5.8 +import java.util.Date;
     5.9 +import java.util.HashMap;
    5.10 +import java.util.Map;
    5.11 +import java.util.Set;
    5.12 +
    5.13 +
    5.14 +final class DatedCompositeConvertorImpl
    5.15 +    implements TimedConvertor
    5.16 +{
    5.17 +    /**
    5.18 +     * The convertors that are supported.
    5.19 +     */
    5.20 +    private final DatedConvertor[] convertors;
    5.21 +    
    5.22 +    /**
    5.23 +     * Keeps track of what convertors to use to convert between currencies.
    5.24 +     */
    5.25 +    private final Map<DateRange, Map<Currency, Map<Currency, Convertor>>> datedConversions;
    5.26 +    
    5.27 +    {
    5.28 +        datedConversions = new HashMap<DateRange, Map<Currency, Map<Currency, Convertor>>>();
    5.29 +    }
    5.30 +
    5.31 +    /**
    5.32 +     * Construct a ComositeConvertorImpl with the specified convertors.
    5.33 +     * This will result in all possible conversions between the supplied currencies being made.
    5.34 +     * 
    5.35 +     * @param cs the convertors to use.
    5.36 +     * @throws IllegalArgumentException if any of the items in cs are null.
    5.37 +     */
    5.38 +    DatedCompositeConvertorImpl(Convertor ... cs)
    5.39 +    {
    5.40 +        int i;
    5.41 +
    5.42 +        convertors = new DatedConvertor[cs.length];
    5.43 +        i          = 0;
    5.44 +
    5.45 +        for(final Convertor c : cs)
    5.46 +        {
    5.47 +            if(!(c instanceof DatedConvertor))
    5.48 +            {
    5.49 +                throw new IllegalArgumentException("cs must only contain DatedConvertors");
    5.50 +            }
    5.51 +
    5.52 +            convertors[i] = (DatedConvertor)c;
    5.53 +            i++;
    5.54 +        }
    5.55 +
    5.56 +        // track all of the known conversion
    5.57 +        for(final DatedConvertor convertor : convertors)
    5.58 +        {
    5.59 +            final Set<Currency>      currencies;
    5.60 +            Map<Currency, Convertor> possible;
    5.61 +            DateRange                range;
    5.62 +            
    5.63 +            if(convertor == null)
    5.64 +            {
    5.65 +                throw new IllegalArgumentException("cs cannot contain null");
    5.66 +            }
    5.67 +            
    5.68 +            currencies = convertor.getCurrencies();
    5.69 +            range      = convertor.getDateRange();
    5.70 +            
    5.71 +            for(final Currency currency : currencies)
    5.72 +            {
    5.73 +                Map<Currency, Map<Currency, Convertor>> possibleConversions;
    5.74 +                
    5.75 +                possibleConversions = datedConversions.get(range);
    5.76 +                
    5.77 +                if(possibleConversions == null)
    5.78 +                {
    5.79 +                    possibleConversions = new HashMap<Currency, Map<Currency, Convertor>>();
    5.80 +                    datedConversions.put(range, possibleConversions);
    5.81 +                }
    5.82 +
    5.83 +                possible = possibleConversions.get(currency);
    5.84 +
    5.85 +                if(possible == null)
    5.86 +                {
    5.87 +                    possible = new HashMap<Currency, Convertor>();
    5.88 +                    possibleConversions.put(currency, possible);
    5.89 +                }
    5.90 +
    5.91 +                for(final Currency c : currencies)
    5.92 +                {
    5.93 +                    possible.put(c, convertor);
    5.94 +                }
    5.95 +            }
    5.96 +        }
    5.97 +
    5.98 +
    5.99 +        /*
   5.100 +        // make up conversions that can be derived... eg:
   5.101 +        //   we have:
   5.102 +        //      USD <-> CAD
   5.103 +        //      CAD <-> CZK
   5.104 +        //      SSK <-> GBP
   5.105 +        //   we can derive:
   5.106 +        //      USD <-> CZK
   5.107 +        //   we cannot derive:
   5.108 +        //      USD <-> GBP
   5.109 +        //      CAD <-> GBP
   5.110 +        //      CZK <-> GBP        
   5.111 +        // 
   5.112 +        // NOTE: no attempt is made to deal with dates for DatedConvertors... nothing we can do about it.
   5.113 +        do
   5.114 +        {
   5.115 +            newConvertors = 0;
   5.116 +
   5.117 +            // todo... need to loop this until all the ones that can be handled are done.
   5.118 +            for(final Currency from : getCurrencies())
   5.119 +            {
   5.120 +                for(final Currency to : getCurrencies())
   5.121 +                {
   5.122 +                    if(!(canConvert(from, to)))
   5.123 +                    {
   5.124 +                        final Set<Currency> fromCurrencies;
   5.125 +                        final Set<Currency> toCurrencies;
   5.126 +                        final Set<Currency> common;
   5.127 +                        Map<Currency, Map<Currency, Convertor>> possibleConversions;
   5.128 +                
   5.129 +                        possibleConversions.get(range);
   5.130 +                        fromCurrencies = possibleConversions.get(from).keySet();
   5.131 +                        toCurrencies   = possibleConversions.get(to).keySet();
   5.132 +                        common         = new HashSet<Currency>();
   5.133 +
   5.134 +                        for(final Currency currency : fromCurrencies)
   5.135 +                        {
   5.136 +                            if(toCurrencies.contains(currency))
   5.137 +                            {
   5.138 +                                common.add(currency);
   5.139 +                            }
   5.140 +                        }
   5.141 +
   5.142 +                        for(final Currency currency : common)
   5.143 +                        {
   5.144 +                            final Convertor convertor;
   5.145 +
   5.146 +                            convertor = createConvertor(from, to, currency);
   5.147 +                            possibleConversions.get(from).put(to, convertor);
   5.148 +                            possibleConversions.get(to).put(from, convertor);
   5.149 +                            newConvertors++;
   5.150 +                        }
   5.151 +                    }
   5.152 +                }
   5.153 +            }
   5.154 +        }
   5.155 +        while(newConvertors > 0);
   5.156 +        */
   5.157 +    }
   5.158 +
   5.159 +    /**
   5.160 +     * Check to see if converting between the two currencies is possible.
   5.161 +     *
   5.162 +     * @param from the currency to convert from.
   5.163 +     * @param to the currency to convert to.
   5.164 +     * @return true if the conversion is possible.
   5.165 +     * @throws IllegalArgumentException if either from or to are null.
   5.166 +     */
   5.167 +    public boolean canConvert(final Currency from, final Currency to)
   5.168 +    {
   5.169 +        throw new UnsupportedOperationException();
   5.170 +    }
   5.171 +
   5.172 +    /**
   5.173 +     * Get the currencies that the convertor supports.  Just because a currency is
   5.174 +     * supported does not mean that canConvert will return true.
   5.175 +     * 
   5.176 +     * @return the supported currencies.
   5.177 +     */
   5.178 +    public Set<Currency> getCurrencies()
   5.179 +    {
   5.180 +        throw new UnsupportedOperationException();
   5.181 +    }
   5.182 +    
   5.183 +    /**
   5.184 +     * Get the conversion rate between two currencies.
   5.185 +     * 
   5.186 +     * @param from the currency to convert from.
   5.187 +     * @param to the currency to convert to.
   5.188 +     * @return the conversion rate between the two currencies.
   5.189 +     * @throws IllegalArgumentException if either from or to is null.
   5.190 +     * @throws InvalidConversionException if canConvert would return false.
   5.191 +     */
   5.192 +    public BigDecimal getConversionRate(final Currency from, final Currency to)
   5.193 +        throws InvalidConversionException
   5.194 +    {
   5.195 +        throw new UnsupportedOperationException();
   5.196 +    }
   5.197 +
   5.198 +    private Convertor getConvertor(final Currency from, final Currency to, final Date date)
   5.199 +    {
   5.200 +        Map<Currency, Map<Currency, Convertor>> possibleConversions;
   5.201 +        final Map<Currency, Convertor> possible;
   5.202 +        Convertor                      convertor;
   5.203 +
   5.204 +        if(from == null)
   5.205 +        {
   5.206 +            throw new IllegalArgumentException("from cannot be null");
   5.207 +        }
   5.208 +        
   5.209 +        if(to == null)
   5.210 +        {
   5.211 +            throw new IllegalArgumentException("to cannot be null");
   5.212 +        }
   5.213 +        
   5.214 +        possibleConversions = null;
   5.215 +
   5.216 +        for(final DateRange range : datedConversions.keySet())
   5.217 +        {
   5.218 +            if(range.isInRange(date))
   5.219 +            {
   5.220 +                possibleConversions = datedConversions.get(range);
   5.221 +                break;
   5.222 +            }
   5.223 +        }
   5.224 +
   5.225 +        if(possibleConversions == null)
   5.226 +        {
   5.227 +            return (null);
   5.228 +        }
   5.229 +
   5.230 +        possible  = possibleConversions.get(from);
   5.231 +        
   5.232 +                
   5.233 +        if(possible == null)
   5.234 +        {
   5.235 +            return (null);
   5.236 +        }
   5.237 +
   5.238 +
   5.239 +        convertor = possible.get(to);
   5.240 +
   5.241 +        
   5.242 +        if(convertor == null)
   5.243 +        {
   5.244 +            return (null);
   5.245 +        }
   5.246 +
   5.247 +        return (convertor);
   5.248 +    }
   5.249 +
   5.250 +    /**
   5.251 +     * Convert an amount from one currency to another.
   5.252 +     * 
   5.253 +     * @param from the currency to convert from.
   5.254 +     * @param to the currency to convert to.
   5.255 +     * @param amount the amount to convert.
   5.256 +     * @return the converted amount.
   5.257 +     * @throws IllegalArgumentException if any of the arguments are null.
   5.258 +     * @throws InvalidConversionException if either from or to are not valid for the convertor.
   5.259 +     */
   5.260 +    public BigDecimal convert(final Currency   from,
   5.261 +                              final Currency   to,
   5.262 +                              final BigDecimal amount)
   5.263 +        throws InvalidConversionException
   5.264 +    {
   5.265 +        throw new InvalidConversionException("No date for the conversion", from);
   5.266 +    }
   5.267 +
   5.268 +    public BigDecimal convert(final Currency   from, 
   5.269 +                              final Currency   to, 
   5.270 +                              final BigDecimal amount, 
   5.271 +                              final Date       date) 
   5.272 +        throws InvalidConversionException 
   5.273 +    {
   5.274 +        final Convertor  convertor;
   5.275 +        final BigDecimal total;
   5.276 +        
   5.277 +        convertor = getConvertor(from, to, date);
   5.278 +
   5.279 +        if(convertor == null)
   5.280 +        {
   5.281 +            throw new InvalidConversionException("cannot convert", from);
   5.282 +        }
   5.283 +        
   5.284 +        if(canConvert(from, to, date))
   5.285 +        {
   5.286 +            final TimedConvertor timeConvertor;
   5.287 +            
   5.288 +            timeConvertor = (TimedConvertor)convertor;
   5.289 +            total         = timeConvertor.convert(from, to, amount, date);
   5.290 +        }
   5.291 +        else
   5.292 +        {
   5.293 +            throw new InvalidConversionException("cannot convert", from);
   5.294 +        }
   5.295 +        
   5.296 +        return (total);
   5.297 +    }
   5.298 +
   5.299 +    public boolean canConvert(final Currency from,
   5.300 +                              final Currency to,
   5.301 +                              final Date     date)
   5.302 +    {
   5.303 +        Convertor     convertor;
   5.304 +        final boolean retVal;
   5.305 +
   5.306 +        convertor = getConvertor(from, to, date);
   5.307 +
   5.308 +        if(convertor != null)
   5.309 +        {
   5.310 +            final TimedConvertor timeConvertor;
   5.311 +
   5.312 +            timeConvertor = (TimedConvertor)convertor;
   5.313 +            retVal        = timeConvertor.canConvert(from, to, date);
   5.314 +        }
   5.315 +        else
   5.316 +        {
   5.317 +            retVal = false;
   5.318 +        }
   5.319 +
   5.320 +        return (retVal);
   5.321 +    }
   5.322 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertor.java	Fri Oct 17 17:40:14 2008 +0200
     6.3 @@ -0,0 +1,9 @@
     6.4 +package org.apidesign.apifest08.currency;
     6.5 +
     6.6 +
     6.7 +public interface DatedConvertor
     6.8 +    extends ExchangeRateConvertor,
     6.9 +            TimedConvertor
    6.10 +{
    6.11 +    DateRange getDateRange();
    6.12 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/DatedConvertorImpl.java	Fri Oct 17 17:40:14 2008 +0200
     7.3 @@ -0,0 +1,82 @@
     7.4 +package org.apidesign.apifest08.currency;
     7.5 +
     7.6 +import java.math.BigDecimal;
     7.7 +import java.util.Currency;
     7.8 +import java.util.Date;
     7.9 +
    7.10 +
    7.11 +final class DatedConvertorImpl
    7.12 +    extends    ConvertorImpl
    7.13 +    implements DatedConvertor
    7.14 +{
    7.15 +    private final DateRange range;
    7.16 +    
    7.17 +    DatedConvertorImpl(final DateRange rng,
    7.18 +                       final ExchangeRate r)
    7.19 +    {
    7.20 +        super(r);
    7.21 +        
    7.22 +        if(rng == null)
    7.23 +        {
    7.24 +            throw new IllegalArgumentException("rng cannot be null");
    7.25 +        }
    7.26 +
    7.27 +        range = rng;
    7.28 +    }
    7.29 +    
    7.30 +    public DateRange getDateRange()
    7.31 +    {
    7.32 +        return (range);
    7.33 +    }
    7.34 +
    7.35 +    @Override
    7.36 +    public BigDecimal convert(final Currency   from,
    7.37 +                              final Currency   to,
    7.38 +                              final BigDecimal amount)
    7.39 +        throws InvalidConversionException
    7.40 +    {
    7.41 +        final BigDecimal total;
    7.42 +        
    7.43 +        total = convert(from, to, amount, new Date(System.currentTimeMillis()));
    7.44 +        
    7.45 +        return (total);
    7.46 +    }
    7.47 +
    7.48 +    public BigDecimal convert(final Currency   from,
    7.49 +                              final Currency   to,
    7.50 +                              final BigDecimal amount,
    7.51 +                              final Date       date)
    7.52 +        throws InvalidConversionException
    7.53 +    {
    7.54 +        final BigDecimal total;
    7.55 +
    7.56 +        if(range.isInRange(date))
    7.57 +        {
    7.58 +            total = super.convert(from, to, amount);
    7.59 +        }
    7.60 +        else
    7.61 +        {
    7.62 +            throw new InvalidConversionException("cannot convert for date", from);
    7.63 +        }
    7.64 +
    7.65 +        return (total);
    7.66 +    }
    7.67 +
    7.68 +    public boolean canConvert(final Currency from,
    7.69 +                              final Currency to,
    7.70 +                              final Date     date)
    7.71 +    {
    7.72 +        final boolean retVal;
    7.73 +
    7.74 +        if(canConvert(from, to))
    7.75 +        {
    7.76 +            retVal = range.isInRange(date);
    7.77 +        }
    7.78 +        else
    7.79 +        {
    7.80 +            retVal = false;
    7.81 +        }
    7.82 +
    7.83 +        return (retVal);
    7.84 +    }
    7.85 +}
     8.1 --- a/task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java	Fri Oct 17 17:39:18 2008 +0200
     8.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRate.java	Fri Oct 17 17:40:14 2008 +0200
     8.3 @@ -2,14 +2,18 @@
     8.4  
     8.5  
     8.6  import java.math.BigDecimal;
     8.7 +import java.math.RoundingMode;
     8.8 +import java.util.Collections;
     8.9  import java.util.Currency;
    8.10 +import java.util.HashSet;
    8.11 +import java.util.Set;
    8.12  
    8.13  
    8.14  /**
    8.15   * The exchange rate between two currencies.
    8.16   *
    8.17   * @author D'Arcy Smith
    8.18 - * @version 1.0
    8.19 + * @version 1.1
    8.20   */
    8.21  public final class ExchangeRate
    8.22  {
    8.23 @@ -122,4 +126,201 @@
    8.24      {
    8.25          return rateBtoA;
    8.26      }
    8.27 +    
    8.28 +    public static ExchangeRate getExchangeRate(final Currency   a,
    8.29 +                                               final Currency   b,
    8.30 +                                               final BigDecimal va,
    8.31 +                                               final BigDecimal vb)
    8.32 +    {
    8.33 +        final BigDecimal   rateAtoB;
    8.34 +        final BigDecimal   rateBtoA;
    8.35 +        final ExchangeRate rate;    
    8.36 +        
    8.37 +        if(a == null)
    8.38 +        {
    8.39 +            throw new IllegalArgumentException("a cannot be null");
    8.40 +        }
    8.41 +        
    8.42 +        if(b == null)
    8.43 +        {
    8.44 +            throw new IllegalArgumentException("b cannot be null");
    8.45 +        }
    8.46 +
    8.47 +        if(a.equals(b))
    8.48 +        {
    8.49 +            rateAtoB = BigDecimal.ONE;
    8.50 +            rateBtoA = BigDecimal.ONE;
    8.51 +        }
    8.52 +        else 
    8.53 +        {
    8.54 +            rateAtoB = vb.divide(va, 20, RoundingMode.HALF_DOWN);
    8.55 +            rateBtoA = va.divide(vb, 20, RoundingMode.HALF_DOWN);
    8.56 +        }
    8.57 +        
    8.58 +        rate = new ExchangeRate(a, 
    8.59 +                                b,
    8.60 +                                rateAtoB.setScale(20, RoundingMode.HALF_EVEN),
    8.61 +                                rateBtoA.setScale(20, RoundingMode.HALF_EVEN));
    8.62 +        
    8.63 +        return (rate);
    8.64 +    }
    8.65 +
    8.66 +    public BigDecimal convert(final Currency   from,
    8.67 +                              final Currency   to,
    8.68 +                              final BigDecimal amount)
    8.69 +        throws InvalidConversionException
    8.70 +    {
    8.71 +        final BigDecimal result;
    8.72 +        
    8.73 +        if(amount == null)
    8.74 +        {
    8.75 +            throw new IllegalArgumentException("amount cannot be null");
    8.76 +        }
    8.77 +        
    8.78 +        if(from == null)
    8.79 +        {
    8.80 +            throw new IllegalArgumentException("from cannot be null");
    8.81 +        }
    8.82 +        
    8.83 +        if(to == null)
    8.84 +        {
    8.85 +            throw new IllegalArgumentException("to cannot be null");
    8.86 +        }
    8.87 +        
    8.88 +        if(!(from.equals(currencyA)) && (!(from.equals(currencyB))))
    8.89 +        {
    8.90 +            throw new InvalidConversionException("cannot convert from: " + from.getCurrencyCode(), from, currencyA, currencyB);
    8.91 +        }
    8.92 +        
    8.93 +        if(!(to.equals(currencyA)) && (!(to.equals(currencyB))))
    8.94 +        {
    8.95 +            throw new InvalidConversionException("cannot convert to: " + to.getCurrencyCode(), to, currencyA, currencyB);
    8.96 +        }
    8.97 +
    8.98 +        result = amount.multiply(getConversionRate(from, to));
    8.99 +
   8.100 +        return (result.setScale(2, RoundingMode.HALF_DOWN));
   8.101 +    }
   8.102 +    
   8.103 +    /**
   8.104 +     * Check to see if converting between the two currencies is possible.
   8.105 +     * 
   8.106 +     * @param from the currency to convert from.
   8.107 +     * @param to the currency to convert to.
   8.108 +     * @return true if the conversion is possible.
   8.109 +     * @throws IllegalArgumentException if either from or to are null.
   8.110 +     */
   8.111 +    public boolean canConvert(final Currency from, final Currency to)
   8.112 +    {
   8.113 +        if(from == null)
   8.114 +        {
   8.115 +            throw new IllegalArgumentException("from cannot be null");
   8.116 +        }
   8.117 +        
   8.118 +        if(to == null)
   8.119 +        {
   8.120 +            throw new IllegalArgumentException("to cannot be null");
   8.121 +        }
   8.122 +        
   8.123 +        return ((from.equals(currencyA) || from.equals(currencyB)) &&
   8.124 +                (to.equals(currencyA)   || to.equals(currencyB)));
   8.125 +    }
   8.126 +    /**
   8.127 +     * Get the currencies that the convertor supports.
   8.128 +     * 
   8.129 +     * @return the supported currencies.
   8.130 +     */
   8.131 +    public Set<Currency> getCurrencies()
   8.132 +    {
   8.133 +        final Set<Currency> currencies;
   8.134 +        
   8.135 +        currencies = new HashSet<Currency>();
   8.136 +        currencies.add(currencyA);
   8.137 +        currencies.add(currencyB);
   8.138 +
   8.139 +        return (Collections.unmodifiableSet(currencies));
   8.140 +    }
   8.141 +
   8.142 +    /**
   8.143 +     * Get the conversion rate between two currencies.
   8.144 +     * 
   8.145 +     * @param from the currency to convert from.
   8.146 +     * @param to the currency to convert to.
   8.147 +     * @return the conversion rate between the two currencies.
   8.148 +     * @throws InvalidConversionException if canConvert would return false.
   8.149 +     * @throws IllegalArgumentException if either from or to are null.
   8.150 +     */
   8.151 +    public BigDecimal getConversionRate(final Currency from, 
   8.152 +                                        final Currency to)
   8.153 +        throws InvalidConversionException
   8.154 +    {                
   8.155 +        final BigDecimal rate;
   8.156 +        
   8.157 +        if(from == null)
   8.158 +        {
   8.159 +            throw new IllegalArgumentException("from cannot be null");
   8.160 +        }
   8.161 +        
   8.162 +        if(to == null)
   8.163 +        {
   8.164 +            throw new IllegalArgumentException("to cannot be null");
   8.165 +        }
   8.166 +
   8.167 +        if(from.equals(to))
   8.168 +        {
   8.169 +            rate = BigDecimal.ONE;
   8.170 +        }
   8.171 +        else 
   8.172 +        {
   8.173 +            if(from.equals(currencyA))
   8.174 +            {
   8.175 +                rate = rateAtoB;
   8.176 +            }
   8.177 +            else
   8.178 +            {
   8.179 +                rate = rateBtoA;
   8.180 +            }
   8.181 +        }
   8.182 +        
   8.183 +        return (rate);
   8.184 +    }
   8.185 +
   8.186 +    public String toString()
   8.187 +    {
   8.188 +        return (rateAtoB + " : " + rateBtoA);
   8.189 +    }
   8.190 +
   8.191 +    @Override
   8.192 +    public boolean equals(Object obj) {
   8.193 +        if (obj == null) {
   8.194 +            return false;
   8.195 +        }
   8.196 +        if (getClass() != obj.getClass()) {
   8.197 +            return false;
   8.198 +        }
   8.199 +        final ExchangeRate other = (ExchangeRate) obj;
   8.200 +        if (this.currencyA != other.currencyA && (this.currencyA == null || !this.currencyA.equals(other.currencyA))) {
   8.201 +            return false;
   8.202 +        }
   8.203 +        if (this.currencyB != other.currencyB && (this.currencyB == null || !this.currencyB.equals(other.currencyB))) {
   8.204 +            return false;
   8.205 +        }
   8.206 +        if (this.rateAtoB != other.rateAtoB && (this.rateAtoB == null || !this.rateAtoB.equals(other.rateAtoB))) {
   8.207 +            return false;
   8.208 +        }
   8.209 +        if (this.rateBtoA != other.rateBtoA && (this.rateBtoA == null || !this.rateBtoA.equals(other.rateBtoA))) {
   8.210 +            return false;
   8.211 +        }
   8.212 +        return true;
   8.213 +    }
   8.214 +
   8.215 +    @Override
   8.216 +    public int hashCode() {
   8.217 +        int hash = 7;
   8.218 +        hash = 97 * hash + (this.currencyA != null ? this.currencyA.hashCode() : 0);
   8.219 +        hash = 97 * hash + (this.currencyB != null ? this.currencyB.hashCode() : 0);
   8.220 +        hash = 97 * hash + (this.rateAtoB != null ? this.rateAtoB.hashCode() : 0);
   8.221 +        hash = 97 * hash + (this.rateBtoA != null ? this.rateBtoA.hashCode() : 0);
   8.222 +        return hash;
   8.223 +    }
   8.224  }
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/ExchangeRateConvertor.java	Fri Oct 17 17:40:14 2008 +0200
     9.3 @@ -0,0 +1,8 @@
     9.4 +package org.apidesign.apifest08.currency;
     9.5 +
     9.6 +
     9.7 +public interface ExchangeRateConvertor
     9.8 +    extends Convertor
     9.9 +{
    9.10 +    ExchangeRate getExchangeRate();
    9.11 +}
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/task4/solution04/src/org/apidesign/apifest08/currency/TimedConvertor.java	Fri Oct 17 17:40:14 2008 +0200
    10.3 @@ -0,0 +1,17 @@
    10.4 +package org.apidesign.apifest08.currency;
    10.5 +
    10.6 +import java.math.BigDecimal;
    10.7 +import java.util.Currency;
    10.8 +import java.util.Date;
    10.9 +
   10.10 +
   10.11 +public interface TimedConvertor
   10.12 +    extends Convertor
   10.13 +{
   10.14 +    BigDecimal convert(Currency   from, 
   10.15 +                       Currency   to, 
   10.16 +                       BigDecimal amount,
   10.17 +                       Date       date)
   10.18 +        throws InvalidConversionException;
   10.19 +    boolean canConvert(Currency from, Currency to, Date date);
   10.20 +}
   10.21 \ No newline at end of file
    11.1 --- a/task4/solution04/test/org/apidesign/apifest08/test/Task1Test.java	Fri Oct 17 17:39:18 2008 +0200
    11.2 +++ b/task4/solution04/test/org/apidesign/apifest08/test/Task1Test.java	Fri Oct 17 17:40:14 2008 +0200
    11.3 @@ -18,9 +18,9 @@
    11.4   */
    11.5  public class Task1Test extends TestCase {
    11.6      
    11.7 -    private final static Currency CZK;
    11.8 -    private final static Currency SKK;
    11.9 -    private final static Currency USD;
   11.10 +    public final static Currency CZK;
   11.11 +    public final static Currency SKK;
   11.12 +    public final static Currency USD;
   11.13  
   11.14      static
   11.15      {
    12.1 --- a/task4/solution04/test/org/apidesign/apifest08/test/Task4Test.java	Fri Oct 17 17:39:18 2008 +0200
    12.2 +++ b/task4/solution04/test/org/apidesign/apifest08/test/Task4Test.java	Fri Oct 17 17:40:14 2008 +0200
    12.3 @@ -1,8 +1,15 @@
    12.4  package org.apidesign.apifest08.test;
    12.5  
    12.6 +import java.math.BigDecimal;
    12.7 +import java.util.Calendar;
    12.8  import java.util.Date;
    12.9 +import java.util.TimeZone;
   12.10  import junit.framework.TestCase;
   12.11  import org.apidesign.apifest08.currency.Convertor;
   12.12 +import org.apidesign.apifest08.currency.ConvertorFactory;
   12.13 +import org.apidesign.apifest08.currency.ExchangeRate;
   12.14 +import org.apidesign.apifest08.currency.InvalidConversionException;
   12.15 +import org.apidesign.apifest08.currency.TimedConvertor;
   12.16  
   12.17  /** The exchange rates are not always the same. They are changing. However
   12.18   * as in order to predict the future, one needs to understand own past. That is
   12.19 @@ -22,8 +29,11 @@
   12.20          super(testName);
   12.21      }
   12.22  
   12.23 +    private Calendar gmtCalendar;
   12.24 +    
   12.25      @Override
   12.26      protected void setUp() throws Exception {
   12.27 +        gmtCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
   12.28      }
   12.29  
   12.30      @Override
   12.31 @@ -45,57 +55,133 @@
   12.32       * @return new convertor
   12.33       */
   12.34      public static Convertor limitTo(Convertor old, Date from, Date till) {
   12.35 -        return null;
   12.36 +        final Convertor convertor;
   12.37 +
   12.38 +        convertor = ConvertorFactory.getConvertor(from, till, old);
   12.39 +        
   12.40 +        return convertor;
   12.41      }
   12.42  
   12.43  
   12.44      public void testCompositionOfLimitedConvertors() throws Exception {
   12.45 -        if (Boolean.getBoolean("ignore.failing")) {
   12.46 -            // implement me! then delete this if statement
   12.47 -            return;
   12.48 -        }
   12.49  
   12.50 -        Date d1 = null; // 2008-10-01 0:00 GMT
   12.51 -        Date d2 = null; // 2008-10-02 0:00 GMT
   12.52 -        Date d3 = null; // 2008-10-03 0:00 GMT
   12.53 +        gmtCalendar.set(2008, Calendar.OCTOBER, 1, 0, 0, 0);
   12.54 +        Date d1 = gmtCalendar.getTime(); // 2008-10-01 0:00 GMT
   12.55 +        gmtCalendar.set(2008, Calendar.OCTOBER, 2, 0, 0, 0);
   12.56 +        Date d2 = gmtCalendar.getTime(); // 2008-10-02 0:00 GMT
   12.57 +        gmtCalendar.set(2008, Calendar.OCTOBER, 3, 0, 0, 0);
   12.58 +        Date d3 = gmtCalendar.getTime(); // 2008-10-03 0:00 GMT
   12.59          
   12.60          Convertor c = Task2Test.merge(
   12.61              limitTo(Task1Test.createCZKtoUSD(), d1, d2),
   12.62              limitTo(Task1Test.createSKKtoCZK(), d2, d3)
   12.63          );
   12.64  
   12.65 +        Date date;
   12.66 +        BigDecimal amount;
   12.67 +
   12.68          // convert $5 to CZK using c:
   12.69          // cannot convert as no rate is applicable to current date
   12.70 +        try
   12.71 +        {
   12.72 +            c.convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("5.00"));
   12.73 +            fail("test A");
   12.74 +        }
   12.75 +        catch(final InvalidConversionException ex)
   12.76 +        {
   12.77 +        }
   12.78  
   12.79          // convert $8 to CZK using c:
   12.80          // cannot convert as no rate is applicable to current date
   12.81 +        try
   12.82 +        {
   12.83 +            c.convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("8.00"));
   12.84 +            fail("test B");
   12.85 +        }
   12.86 +        catch(final InvalidConversionException ex)
   12.87 +        {
   12.88 +        }
   12.89  
   12.90          // convert 1003CZK to USD using c:
   12.91          // cannot convert as no rate is applicable to current date
   12.92 +        try
   12.93 +        {
   12.94 +            c.convert(Task1Test.CZK, Task1Test.USD, new BigDecimal("1003.00"));
   12.95 +            fail("test C");
   12.96 +        }
   12.97 +        catch(final InvalidConversionException ex)
   12.98 +        {
   12.99 +        }
  12.100  
  12.101          // convert 16CZK using c:
  12.102          // cannot convert as no rate is applicable to current date
  12.103 +        try
  12.104 +        {
  12.105 +            c.convert(Task1Test.CZK, Task1Test.USD, new BigDecimal("16.00"));
  12.106 +            fail("test D");
  12.107 +        }
  12.108 +        catch(final InvalidConversionException ex)
  12.109 +        {
  12.110 +        }
  12.111  
  12.112          // convert 500SKK to CZK using c:
  12.113          // cannot convert as no rate is applicable to current date
  12.114 +        try
  12.115 +        {
  12.116 +            c.convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"));
  12.117 +            fail("test C");
  12.118 +        }
  12.119 +        catch(final InvalidConversionException ex)
  12.120 +        {
  12.121 +        }
  12.122  
  12.123          // convert $5 to CZK using c at 2008-10-01 6:00 GMT:
  12.124          // assertEquals("Result is 85 CZK");
  12.125 +        gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0);
  12.126 +        date = gmtCalendar.getTime();
  12.127 +        amount = ((TimedConvertor)c).convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("5.00"), date);
  12.128 +        assertEquals(new BigDecimal("85.00"), amount);
  12.129  
  12.130          // convert $8 to CZK using c at 2008-10-01 6:00 GMT:
  12.131          // assertEquals("Result is 136 CZK");
  12.132 +        gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0);
  12.133 +        date = gmtCalendar.getTime();
  12.134 +        amount = ((TimedConvertor)c).convert(Task1Test.USD, Task1Test.CZK, new BigDecimal("8.00"), date);
  12.135 +        assertEquals(new BigDecimal("136.00"), amount);
  12.136  
  12.137          // convert 1003CZK to USD using c at 2008-10-01 6:00 GMT:
  12.138          // assertEquals("Result is 59 USD");
  12.139 +        gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0);
  12.140 +        date = gmtCalendar.getTime();
  12.141 +        amount = ((TimedConvertor)c).convert(Task1Test.CZK, Task1Test.USD, new BigDecimal("1003.00"), date);
  12.142 +        assertEquals(new BigDecimal("59.00"), amount);
  12.143  
  12.144          // convert 16CZK using c at 2008-10-02 9:00 GMT:
  12.145          // assertEquals("Result is 20 SKK");
  12.146 +        gmtCalendar.set(2008, Calendar.OCTOBER, 2, 9, 0, 0);
  12.147 +        date = gmtCalendar.getTime();
  12.148 +        amount = ((TimedConvertor)c).convert(Task1Test.CZK, Task1Test.SKK, new BigDecimal("16.00"), date);
  12.149 +        assertEquals(new BigDecimal("20.00"), amount);
  12.150  
  12.151          // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT:
  12.152          // assertEquals("Result is 400 CZK");
  12.153 +        gmtCalendar.set(2008, Calendar.OCTOBER, 2, 9, 0, 0);
  12.154 +        date = gmtCalendar.getTime();
  12.155 +        amount = ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date);
  12.156 +        assertEquals(new BigDecimal("400.00"), amount);
  12.157  
  12.158          // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT:
  12.159          // cannot convert as no rate is applicable to current date
  12.160 +        try
  12.161 +        {
  12.162 +            gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0);
  12.163 +            date = gmtCalendar.getTime();
  12.164 +            ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date);
  12.165 +            fail("test D");
  12.166 +        }
  12.167 +        catch(final InvalidConversionException ex)
  12.168 +        {
  12.169 +        }
  12.170      }
  12.171  
  12.172      /** Create convertor that understands two currencies, CZK and
  12.173 @@ -103,30 +189,49 @@
  12.174       *
  12.175       * @return prepared convertor ready for converting SKK to CZK and CZK to SKK
  12.176       */
  12.177 -    public static Convertor createSKKtoCZK2() {
  12.178 -        return null;
  12.179 +    public static Convertor createSKKtoCZK2()
  12.180 +    {
  12.181 +        final ExchangeRate rate;
  12.182 +        final Convertor    convertor;
  12.183 +        
  12.184 +        rate = ExchangeRate.getExchangeRate(Task1Test.SKK,
  12.185 +                                            Task1Test.CZK,
  12.186 +                                            new BigDecimal("100.00"),
  12.187 +                                            new BigDecimal("90.00"));
  12.188 +        convertor = ConvertorFactory.getConvertor(rate);
  12.189 +
  12.190 +        return (convertor);
  12.191      }
  12.192  
  12.193      public void testDateConvetorWithTwoDifferentRates() throws Exception {
  12.194 -        if (Boolean.getBoolean("ignore.failing")) {
  12.195 -            // implement me! then delete this if statement
  12.196 -            return;
  12.197 -        }
  12.198 -
  12.199 -        Date d1 = null; // 2008-10-01 0:00 GMT
  12.200 -        Date d2 = null; // 2008-10-02 0:00 GMT
  12.201 -        Date d3 = null; // 2008-10-03 0:00 GMT
  12.202 +        gmtCalendar.set(2008, Calendar.OCTOBER, 1, 0, 0, 0);
  12.203 +        Date d1 = gmtCalendar.getTime(); // 2008-10-01 0:00 GMT
  12.204 +        gmtCalendar.set(2008, Calendar.OCTOBER, 2, 0, 0, 0);
  12.205 +        Date d2 = gmtCalendar.getTime(); // 2008-10-02 0:00 GMT
  12.206 +        gmtCalendar.set(2008, Calendar.OCTOBER, 3, 0, 0, 0);
  12.207 +        Date d3 = gmtCalendar.getTime(); // 2008-10-03 0:00 GMT
  12.208  
  12.209          Convertor c = Task2Test.merge(
  12.210              limitTo(createSKKtoCZK2(), d1, d2),
  12.211              limitTo(Task1Test.createSKKtoCZK(), d2, d3)
  12.212          );
  12.213  
  12.214 +        Date date;
  12.215 +        BigDecimal amount;
  12.216 +        
  12.217          // convert 500SKK to CZK using c at 2008-10-02 9:00 GMT:
  12.218          // assertEquals("Result is 400 CZK");
  12.219 +        gmtCalendar.set(2008, Calendar.OCTOBER, 2, 6, 0, 0);
  12.220 +        date = gmtCalendar.getTime();
  12.221 +        amount = ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date);
  12.222 +        assertEquals(new BigDecimal("400.00"), amount);
  12.223  
  12.224          // convert 500SKK to CZK using c at 2008-10-01 6:00 GMT:
  12.225          // assertEquals("Result is 450 CZK");
  12.226 +        gmtCalendar.set(2008, Calendar.OCTOBER, 1, 6, 0, 0);
  12.227 +        date = gmtCalendar.getTime();
  12.228 +        amount = ((TimedConvertor)c).convert(Task1Test.SKK, Task1Test.CZK, new BigDecimal("500.00"), date);
  12.229 +        assertEquals(new BigDecimal("450.00"), amount);
  12.230      }
  12.231  
  12.232  }