adding solution04 for task2
authorjapod@localhost
Tue, 07 Oct 2008 00:21:03 +0200
changeset 358898c620fe96
parent 34 3a18aae85c9e
child 36 6102a569c7fa
adding solution04 for task2
task2/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java
task2/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java
task2/solution04/src/org/apidesign/apifest08/currency/Convertor.java
task2/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java
task2/solution04/src/org/apidesign/apifest08/currency/InvalidConversionException.java
task2/solution04/test/org/apidesign/apifest08/test/Task1Test.java
task2/solution04/test/org/apidesign/apifest08/test/Task2Test.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/task2/solution04/src/org/apidesign/apifest08/currency/CompositeConvertorImpl.java	Tue Oct 07 00:21:03 2008 +0200
     1.3 @@ -0,0 +1,314 @@
     1.4 +package org.apidesign.apifest08.currency;
     1.5 +
     1.6 +
     1.7 +import java.math.BigDecimal;
     1.8 +import java.math.RoundingMode;
     1.9 +import java.util.Collections;
    1.10 +import java.util.Currency;
    1.11 +import java.util.HashMap;
    1.12 +import java.util.HashSet;
    1.13 +import java.util.Map;
    1.14 +import java.util.Set;
    1.15 +
    1.16 +
    1.17 +/**
    1.18 + * A composite convertor allows conversions between many currencies by forwarding conversion requests to stored convertors.
    1.19 + * A composite convertor will build all possible conversions that are allowed by the underlying set of convertors.
    1.20 + *
    1.21 + * @author D'Arcy Smith
    1.22 + * @verson 1.0
    1.23 + */
    1.24 +final class CompositeConvertorImpl
    1.25 +    implements Convertor
    1.26 +{
    1.27 +    /**
    1.28 +     * The convertors that are supported.
    1.29 +     */
    1.30 +    private final Convertor[] convertors;
    1.31 +    
    1.32 +    /**
    1.33 +     * Keeps track of what convertors to use to convert between currencies.
    1.34 +     */
    1.35 +    private final Map<Currency, Map<Currency, Convertor>> possibleConversions;
    1.36 +    
    1.37 +    {
    1.38 +        possibleConversions = new HashMap<Currency, Map<Currency, Convertor>>();
    1.39 +    }
    1.40 +
    1.41 +    /**
    1.42 +     * Construct a ComositeConvertorImpl with the specified convertors.
    1.43 +     * This will result in all possible conversions between the supplied currencies being made.
    1.44 +     * 
    1.45 +     * @param cs the convertors to use.
    1.46 +     * @throws IllegalArgumentException if any of the items in cs are null.
    1.47 +     */
    1.48 +    CompositeConvertorImpl(Convertor ... cs)
    1.49 +    {
    1.50 +        int newConvertors;
    1.51 +        
    1.52 +        convertors = cs;
    1.53 +
    1.54 +        // track all of the known conversion
    1.55 +        for(final Convertor convertor : convertors)
    1.56 +        {
    1.57 +            final Set<Currency>      currencies;
    1.58 +            Map<Currency, Convertor> possible;
    1.59 +
    1.60 +            if(convertor == null)
    1.61 +            {
    1.62 +                throw new IllegalArgumentException("cs cannot contain null");
    1.63 +            }
    1.64 +            
    1.65 +            currencies = convertor.getCurrencies();
    1.66 +
    1.67 +            for(final Currency currency : currencies)
    1.68 +            {
    1.69 +                possible = possibleConversions.get(currency);
    1.70 +
    1.71 +                if(possible == null)
    1.72 +                {
    1.73 +                    possible = new HashMap<Currency, Convertor>();
    1.74 +                    possibleConversions.put(currency, possible);
    1.75 +                }
    1.76 +
    1.77 +                for(final Currency c : currencies)
    1.78 +                {
    1.79 +                    possible.put(c, convertor);
    1.80 +                }
    1.81 +            }
    1.82 +        }
    1.83 +
    1.84 +        // make up conversions that can be derived... eg:
    1.85 +        //   we have:
    1.86 +        //      USD <-> CAD
    1.87 +        //      CAD <-> CZK
    1.88 +        //      SSK <-> GBP
    1.89 +        //   we can derive:
    1.90 +        //      USD <-> CZK
    1.91 +        //   we cannot derive:
    1.92 +        //      USD <-> GBP
    1.93 +        //      CAD <-> GBP
    1.94 +        //      CZK <-> GBP
    1.95 +        do
    1.96 +        {
    1.97 +            newConvertors = 0;
    1.98 +
    1.99 +            // todo... need to loop this until all the ones that can be handled are done.
   1.100 +            for(final Currency from : getCurrencies())
   1.101 +            {
   1.102 +                for(final Currency to : getCurrencies())
   1.103 +                {
   1.104 +                    if(!(canConvert(from, to)))
   1.105 +                    {
   1.106 +                        final Set<Currency> fromCurrencies;
   1.107 +                        final Set<Currency> toCurrencies;
   1.108 +                        final Set<Currency> common;
   1.109 +
   1.110 +                        fromCurrencies = possibleConversions.get(from).keySet();
   1.111 +                        toCurrencies   = possibleConversions.get(to).keySet();
   1.112 +                        common         = new HashSet<Currency>();
   1.113 +
   1.114 +                        for(final Currency currency : fromCurrencies)
   1.115 +                        {
   1.116 +                            if(toCurrencies.contains(currency))
   1.117 +                            {
   1.118 +                                common.add(currency);
   1.119 +                            }
   1.120 +                        }
   1.121 +
   1.122 +                        for(final Currency currency : common)
   1.123 +                        {
   1.124 +                            final Convertor convertor;
   1.125 +
   1.126 +                            convertor = createConvertor(from, to, currency);
   1.127 +                            possibleConversions.get(from).put(to, convertor);
   1.128 +                            possibleConversions.get(to).put(from, convertor);
   1.129 +                            newConvertors++;
   1.130 +                        }
   1.131 +                    }
   1.132 +                }
   1.133 +            }
   1.134 +        }
   1.135 +        while(newConvertors > 0);
   1.136 +    }
   1.137 +
   1.138 +    /**
   1.139 +     * Check to see if converting between the two currencies is possible.
   1.140 +     *
   1.141 +     * @param from the currency to convert from.
   1.142 +     * @param to the currency to convert to.
   1.143 +     * @return true if the conversion is possible.
   1.144 +     * @throws IllegalArgumentException if either from or to are null.
   1.145 +     */
   1.146 +    public boolean canConvert(final Currency from, final Currency to)
   1.147 +    {
   1.148 +        final Map<Currency, Convertor> possible;
   1.149 +
   1.150 +        if(from == null)
   1.151 +        {
   1.152 +            throw new IllegalArgumentException("from cannot be null");
   1.153 +        }
   1.154 +        
   1.155 +        if(to == null)
   1.156 +        {
   1.157 +            throw new IllegalArgumentException("to cannot be null");
   1.158 +        }
   1.159 +                
   1.160 +        possible = possibleConversions.get(from);
   1.161 +
   1.162 +        if(possible.containsKey(to))
   1.163 +        {
   1.164 +            return (true);
   1.165 +        }
   1.166 +
   1.167 +        return (false);
   1.168 +    }
   1.169 +
   1.170 +    /**
   1.171 +     * Get the currencies that the convertor supports.  Just because a currency is
   1.172 +     * supported does not mean that canConvert will return true.
   1.173 +     * 
   1.174 +     * @return the supported currencies.
   1.175 +     */
   1.176 +    public Set<Currency> getCurrencies()
   1.177 +    {
   1.178 +        final Set<Currency> currencies;
   1.179 +
   1.180 +        currencies = possibleConversions.keySet();
   1.181 +
   1.182 +        return (Collections.unmodifiableSet(currencies));
   1.183 +    }
   1.184 +    
   1.185 +    /**
   1.186 +     * Get the conversion rate between two currencies.
   1.187 +     * 
   1.188 +     * @param from the currency to convert from.
   1.189 +     * @param to the currency to convert to.
   1.190 +     * @return the conversion rate between the two currencies.
   1.191 +     * @throws IllegalArgumentException if either from or to is null.
   1.192 +     * @throws InvalidConversionException if canConvert would return false.
   1.193 +     */
   1.194 +    public BigDecimal getConversionRate(final Currency from, final Currency to)
   1.195 +        throws InvalidConversionException
   1.196 +    {
   1.197 +        final Map<Currency, Convertor> possible;
   1.198 +        Convertor                      convertor;
   1.199 +
   1.200 +        if(from == null)
   1.201 +        {
   1.202 +            throw new IllegalArgumentException("from cannot be null");
   1.203 +        }
   1.204 +        
   1.205 +        if(to == null)
   1.206 +        {
   1.207 +            throw new IllegalArgumentException("to cannot be null");
   1.208 +        }
   1.209 +        
   1.210 +        if(!(canConvert(from, to)))
   1.211 +        {
   1.212 +            throw new InvalidConversionException("cannot convert", to);
   1.213 +        }
   1.214 +
   1.215 +        possible  = possibleConversions.get(from);
   1.216 +        convertor = possible.get(to);
   1.217 +
   1.218 +        if(convertor == null)
   1.219 +        {
   1.220 +            throw new Error();
   1.221 +        }
   1.222 +
   1.223 +        return (convertor.getConversionRate(from, to));
   1.224 +    }
   1.225 +
   1.226 +    /**
   1.227 +     * Convert an amount from one currency to another.
   1.228 +     * 
   1.229 +     * @param from the currency to convert from.
   1.230 +     * @param to the currency to convert to.
   1.231 +     * @param amount the amount to convert.
   1.232 +     * @return the converted amount.
   1.233 +     * @throws IllegalArgumentException if any of the arguments are null.
   1.234 +     * @throws InvalidConversionException if either from or to are not valid for the convertor.
   1.235 +     */
   1.236 +    public BigDecimal convert(final Currency   from,
   1.237 +                              final Currency   to,
   1.238 +                              final BigDecimal amount)
   1.239 +        throws InvalidConversionException
   1.240 +    {
   1.241 +        final BigDecimal result;
   1.242 +        
   1.243 +        if(amount == null)
   1.244 +        {
   1.245 +            throw new IllegalArgumentException("amount cannot be null");
   1.246 +        }
   1.247 +        
   1.248 +        if(from == null)
   1.249 +        {
   1.250 +            throw new IllegalArgumentException("from cannot be null");
   1.251 +        }
   1.252 +        
   1.253 +        if(to == null)
   1.254 +        {
   1.255 +            throw new IllegalArgumentException("to cannot be null");
   1.256 +        }
   1.257 +
   1.258 +        result = amount.multiply(getConversionRate(from, to));
   1.259 +
   1.260 +        return (result.setScale(2, RoundingMode.HALF_DOWN));
   1.261 +    }
   1.262 +
   1.263 +    /**
   1.264 +     * Create a convertor between two currencies using another currency that is able to convert between both.
   1.265 +     * 
   1.266 +     * @param from the currency to convert from.
   1.267 +     * @param to the currency to convert to.
   1.268 +     * @param intermediary the currency to use as a go-between.
   1.269 +     * @return a Convertor that is able to convert between from an to.
   1.270 +     * @throws IllegalArgumentException if any of the arguments are null.
   1.271 +     */
   1.272 +    private Convertor createConvertor(final Currency from,
   1.273 +                                      final Currency to,
   1.274 +                                      final Currency intermediary) 
   1.275 +    {
   1.276 +        final Convertor fromIntermediary;
   1.277 +        final Convertor toIntermediary;
   1.278 +
   1.279 +        if(from == null)
   1.280 +        {
   1.281 +            throw new IllegalArgumentException("from cannot be null");
   1.282 +        }
   1.283 +        
   1.284 +        if(to == null)
   1.285 +        {
   1.286 +            throw new IllegalArgumentException("to cannot be null");
   1.287 +        }
   1.288 +        
   1.289 +        if(intermediary == null)
   1.290 +        {
   1.291 +            throw new IllegalArgumentException("intermediary cannot be null");
   1.292 +        }
   1.293 +        
   1.294 +        fromIntermediary = possibleConversions.get(from).get(intermediary);
   1.295 +        toIntermediary   = possibleConversions.get(to).get(intermediary);
   1.296 +
   1.297 +        try
   1.298 +        {
   1.299 +            final BigDecimal fromRate;
   1.300 +            final BigDecimal toRate;
   1.301 +            final BigDecimal rate;
   1.302 +            final Convertor  convertor;
   1.303 +            
   1.304 +            fromRate  = fromIntermediary.getConversionRate(from, intermediary);
   1.305 +            toRate    = toIntermediary.getConversionRate(intermediary, to);
   1.306 +            rate      = fromRate.multiply(toRate);
   1.307 +            
   1.308 +            convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
   1.309 +            
   1.310 +            return (convertor);
   1.311 +        }
   1.312 +        catch (InvalidConversionException ex)
   1.313 +        {
   1.314 +            throw new Error();
   1.315 +        }
   1.316 +    }
   1.317 +}
     2.1 --- a/task2/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java	Tue Oct 07 00:19:37 2008 +0200
     2.2 +++ b/task2/solution04/src/org/apidesign/apifest08/currency/ConverterImpl.java	Tue Oct 07 00:21:03 2008 +0200
     2.3 @@ -4,7 +4,10 @@
     2.4  import java.math.BigDecimal;
     2.5  import java.math.MathContext;
     2.6  import java.math.RoundingMode;
     2.7 +import java.util.Collections;
     2.8  import java.util.Currency;
     2.9 +import java.util.HashSet;
    2.10 +import java.util.Set;
    2.11  
    2.12  
    2.13  /**
    2.14 @@ -128,12 +131,59 @@
    2.15              throw new InvalidConversionException("cannot convert to: " + to.getCurrencyCode(), to, currencyA, currencyB);
    2.16          }
    2.17  
    2.18 -        // converting between the same currency is no converstion at all.
    2.19 +        result = amount.multiply(getConversionRate(from, to));
    2.20 +
    2.21 +        return (result.setScale(2, RoundingMode.HALF_DOWN));
    2.22 +    }
    2.23 +
    2.24 +    /**
    2.25 +     * Check to see if converting between the two currencies is possible.
    2.26 +     * 
    2.27 +     * @param from the currency to convert from.
    2.28 +     * @param to the currency to convert to.
    2.29 +     * @return true if the conversion is possible.
    2.30 +     */
    2.31 +    public boolean canConvert(final Currency from, final Currency to)
    2.32 +    {
    2.33 +        return ((from.equals(currencyA) || from.equals(currencyB)) &&
    2.34 +                (to.equals(currencyA)   || to.equals(currencyB)));
    2.35 +    }
    2.36 +
    2.37 +    /**
    2.38 +     * Get the currencies that the convertor supports.
    2.39 +     * 
    2.40 +     * @return the supported currencies.
    2.41 +     */
    2.42 +    public Set<Currency> getCurrencies()
    2.43 +    {
    2.44 +        final Set<Currency> currencies;
    2.45 +        
    2.46 +        currencies = new HashSet<Currency>();
    2.47 +        currencies.add(currencyA);
    2.48 +        currencies.add(currencyB);
    2.49 +
    2.50 +        return (Collections.unmodifiableSet(currencies));
    2.51 +    }
    2.52 +
    2.53 +    /**
    2.54 +     * Get the conversion rate between two currencies.
    2.55 +     * 
    2.56 +     * @param from the currency to convert from.
    2.57 +     * @param to the currency to convert to.
    2.58 +     * @return the conversion rate between the two currencies.
    2.59 +     * @throws InvalidConversionException if canConvert would return false.
    2.60 +     */
    2.61 +    public BigDecimal getConversionRate(final Currency from, 
    2.62 +                                        final Currency to)
    2.63 +        throws InvalidConversionException
    2.64 +    {
    2.65 +        final BigDecimal rate;
    2.66 +
    2.67          if(from.equals(to))
    2.68          {
    2.69 -            result = amount;
    2.70 +            rate = BigDecimal.ONE;
    2.71          }
    2.72 -        else
    2.73 +        else 
    2.74          {
    2.75              final BigDecimal rateX;
    2.76              final BigDecimal rateY;
    2.77 @@ -150,10 +200,83 @@
    2.78                  rateY = currencyARate;
    2.79              }
    2.80  
    2.81 -            temp   = amount.divide(rateX, MathContext.DECIMAL32);
    2.82 -            result = temp.multiply(rateY);
    2.83 +            temp = BigDecimal.ONE.divide(rateX, MathContext.DECIMAL64);
    2.84 +            rate = temp.multiply(rateY);
    2.85 +        }
    2.86 +        
    2.87 +        return (rate.setScale(20, RoundingMode.HALF_EVEN));
    2.88 +    }
    2.89 +
    2.90 +    /**
    2.91 +     * Check to see if two ConvertorImpls are equal.
    2.92 +     *
    2.93 +     * @param obj the object to check
    2.94 +     * @return if the ConvertorImpls are not the same (cuyrrencies and rates).
    2.95 +     */
    2.96 +    @Override
    2.97 +    public boolean equals(Object obj)
    2.98 +    {
    2.99 +        if (obj == null)
   2.100 +        {
   2.101 +            return false;
   2.102 +        }
   2.103 +        
   2.104 +        if (getClass() != obj.getClass())
   2.105 +        {
   2.106 +            return false;
   2.107          }
   2.108  
   2.109 -        return (result.setScale(2, RoundingMode.HALF_DOWN));
   2.110 +        final ConvertorImpl other = (ConvertorImpl) obj;
   2.111 +
   2.112 +        // it would be nice if NetBeans could chck to see if the variable is final and guaranteed not to be null... but that
   2.113 +        // would likely be tricky... but in a NetBeans engineer reads that see if you can do it :-)
   2.114 +        if (this.currencyA != other.currencyA && (this.currencyA == null || !this.currencyA.equals(other.currencyA)))
   2.115 +        {
   2.116 +            return false;
   2.117 +        }
   2.118 +        
   2.119 +        if (this.currencyB != other.currencyB && (this.currencyB == null || !this.currencyB.equals(other.currencyB)))
   2.120 +        {
   2.121 +            return false;
   2.122 +        }
   2.123 +        
   2.124 +        if (this.currencyARate != other.currencyARate && (this.currencyARate == null || !this.currencyARate.equals(other.currencyARate)))
   2.125 +        {
   2.126 +            return false;
   2.127 +        }
   2.128 +        
   2.129 +        if (this.currencyBRate != other.currencyBRate && (this.currencyBRate == null || !this.currencyBRate.equals(other.currencyBRate)))
   2.130 +        {
   2.131 +            return false;
   2.132 +        }
   2.133 +
   2.134 +        return true;
   2.135 +    }
   2.136 +
   2.137 +    /**
   2.138 +     * Get the hashCode of the Convertor.
   2.139 +     *
   2.140 +     * @return the hashCode of the convertor.
   2.141 +     */
   2.142 +    @Override
   2.143 +    public int hashCode()
   2.144 +    {
   2.145 +        int hash = 7;
   2.146 +        hash = 37 * hash + (this.currencyA != null ? this.currencyA.hashCode() : 0);
   2.147 +        hash = 37 * hash + (this.currencyB != null ? this.currencyB.hashCode() : 0);
   2.148 +        hash = 37 * hash + (this.currencyARate != null ? this.currencyARate.hashCode() : 0);
   2.149 +        hash = 37 * hash + (this.currencyBRate != null ? this.currencyBRate.hashCode() : 0);
   2.150 +        return hash;
   2.151 +    }
   2.152 +
   2.153 +    /**
   2.154 +     * Get the currencyCode of both currencies.
   2.155 +     *
   2.156 +     * @return the currency codes of both currencies.
   2.157 +     */
   2.158 +    @Override
   2.159 +    public String toString()
   2.160 +    {
   2.161 +        return (currencyA.getCurrencyCode() + " to " + currencyB.getCurrencyCode());
   2.162      }
   2.163  }
     3.1 --- a/task2/solution04/src/org/apidesign/apifest08/currency/Convertor.java	Tue Oct 07 00:19:37 2008 +0200
     3.2 +++ b/task2/solution04/src/org/apidesign/apifest08/currency/Convertor.java	Tue Oct 07 00:21:03 2008 +0200
     3.3 @@ -3,6 +3,7 @@
     3.4  
     3.5  import java.math.BigDecimal;
     3.6  import java.util.Currency;
     3.7 +import java.util.Set;
     3.8  
     3.9  
    3.10  /**
    3.11 @@ -27,4 +28,32 @@
    3.12                         Currency   to, 
    3.13                         BigDecimal amount)
    3.14          throws InvalidConversionException;
    3.15 +
    3.16 +    /**
    3.17 +     * Check to see if converting between the two currencies is possible.
    3.18 +     *
    3.19 +     * @param from the currency to convert from.
    3.20 +     * @param to the currency to convert to.
    3.21 +     * @return true if the conversion is possible.
    3.22 +     */
    3.23 +    boolean canConvert(Currency from, Currency to);
    3.24 +
    3.25 +    /**
    3.26 +     * Get the currencies that the convertor supports.  Just because a currency is
    3.27 +     * supported does not mean that canConvert will return true.
    3.28 +     * 
    3.29 +     * @return the supported currencies.
    3.30 +     */
    3.31 +    Set<Currency> getCurrencies();
    3.32 +
    3.33 +    /**
    3.34 +     * Get the conversion rate between two currencies.
    3.35 +     * 
    3.36 +     * @param from the currency to convert from.
    3.37 +     * @param to the currency to convert to.
    3.38 +     * @return the conversion rate between the two currencies.
    3.39 +     * @throws InvalidConversionException if canConvert would return false.
    3.40 +     */
    3.41 +    BigDecimal getConversionRate(final Currency from, final Currency to)
    3.42 +        throws InvalidConversionException;
    3.43  }
     4.1 --- a/task2/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java	Tue Oct 07 00:19:37 2008 +0200
     4.2 +++ b/task2/solution04/src/org/apidesign/apifest08/currency/ConvertorFactory.java	Tue Oct 07 00:21:03 2008 +0200
     4.3 @@ -1,10 +1,7 @@
     4.4  package org.apidesign.apifest08.currency;
     4.5  
     4.6 -import java.lang.ref.WeakReference;
     4.7  import java.math.BigDecimal;
     4.8  import java.util.Currency;
     4.9 -import java.util.Map;
    4.10 -import java.util.WeakHashMap;
    4.11  
    4.12  
    4.13  /**
    4.14 @@ -15,15 +12,15 @@
    4.15   */
    4.16  public final class ConvertorFactory
    4.17  {
    4.18 -    /**
    4.19 +    /*
    4.20       * flyweight so that only one vestion of each converter is created at a time.
    4.21 -     */
    4.22      private final static Map<String, WeakReference<Convertor>> convertors;
    4.23      
    4.24      static
    4.25      {
    4.26          convertors = new WeakHashMap<String, WeakReference<Convertor>>();
    4.27      }
    4.28 +    */
    4.29      
    4.30      /** 
    4.31       * Prevent accidental construction.
    4.32 @@ -74,7 +71,7 @@
    4.33                                           final Currency   b,
    4.34                                           final BigDecimal bRate)
    4.35      {
    4.36 -        final String key;        
    4.37 +        // final String key;        
    4.38          Convertor    convertor;
    4.39  
    4.40          if(a == null)
    4.41 @@ -97,6 +94,7 @@
    4.42              throw new IllegalArgumentException("bRate cannot be null");
    4.43          }
    4.44  
    4.45 +        /*
    4.46          key = a.getCurrencyCode() + aRate + b.getCurrencyCode() + bRate;
    4.47  
    4.48          // make sure that we don't try to overwrite one
    4.49 @@ -107,10 +105,68 @@
    4.50                  convertor = new ConvertorImpl(a, aRate, b, bRate);
    4.51                  convertors.put(key, new WeakReference(convertor));
    4.52              }
    4.53 +
    4.54 +            convertor = convertors.get(key).get();
    4.55          }
    4.56 +        */
    4.57 +        
    4.58 +        convertor = new ConvertorImpl(a, aRate, b, bRate);
    4.59 +                
    4.60 +        return (convertor);
    4.61 +    }
    4.62 +    
    4.63 +    public static Convertor mergeConvertors(final Convertor ... cs)
    4.64 +    {
    4.65 +        Convertor convertor;
    4.66 +        
    4.67 +        /*
    4.68 +        final String key;
    4.69  
    4.70 -        convertor = convertors.get(key).get();
    4.71 +        // ISSUE: only takes into account the names... not the rates...
    4.72 +        key = getKey(cs);
    4.73 +
    4.74 +        // make sure that we don't try to overwrite one
    4.75 +        synchronized(convertors)
    4.76 +        {
    4.77 +            if(!(convertors.containsKey(key)))
    4.78 +            {        
    4.79 +                convertor = new CompositeConvertorImpl(cs);
    4.80 +                convertors.put(key, new WeakReference(convertor));
    4.81 +            }
    4.82 +
    4.83 +            convertor = convertors.get(key).get();
    4.84 +        }
    4.85 +        */
    4.86 +        
    4.87 +        convertor = new CompositeConvertorImpl(cs);
    4.88          
    4.89          return (convertor);
    4.90      }
    4.91 +
    4.92 +    /*
    4.93 +    private static String getKey(final Convertor ... cs)
    4.94 +    {
    4.95 +        final Set<Currency> currencies;
    4.96 +        final StringBuilder builder;
    4.97 +        
    4.98 +        currencies = new HashSet<Currency>();
    4.99 +        
   4.100 +        for(final Convertor convertor : cs)
   4.101 +        {
   4.102 +            final Set<Currency> c;
   4.103 +            
   4.104 +            c = convertor.getCurrencies();
   4.105 +            currencies.addAll(c);
   4.106 +        }
   4.107 +        
   4.108 +        builder = new StringBuilder();
   4.109 +
   4.110 +        for(final Currency currency : currencies)
   4.111 +        {
   4.112 +            builder.append(currency.getCurrencyCode());
   4.113 +        }
   4.114 +        
   4.115 +        return (builder.toString());
   4.116 +    }
   4.117 +    */
   4.118  }
     5.1 --- a/task2/solution04/src/org/apidesign/apifest08/currency/InvalidConversionException.java	Tue Oct 07 00:19:37 2008 +0200
     5.2 +++ b/task2/solution04/src/org/apidesign/apifest08/currency/InvalidConversionException.java	Tue Oct 07 00:21:03 2008 +0200
     5.3 @@ -28,18 +28,31 @@
     5.4       */
     5.5      private final Currency currencyB;
     5.6      
     5.7 +    
     5.8      /**
     5.9 -     * Construct a new InvalidConversionException wit the specified message.
    5.10 +     * Construct a new InvalidConversionException with the specified message.
    5.11 +     * 
    5.12 +     * @param msg the message for getMessage.
    5.13 +     * @param bad the currency that is not valid.
    5.14 +     */
    5.15 +    public InvalidConversionException(final String   msg,
    5.16 +                                      final Currency bad)
    5.17 +    {
    5.18 +        this(msg, bad, null, null);
    5.19 +    }
    5.20 +
    5.21 +    /**
    5.22 +     * Construct a new InvalidConversionException with the specified message.
    5.23       * 
    5.24       * @param msg the message for getMessage.
    5.25       * @param bad the currency that is not valid.
    5.26       * @param a a valid currency.
    5.27       * @param b a valid currency.
    5.28       */
    5.29 -    public InvalidConversionException(final String    msg,
    5.30 -                                       final Currency bad,
    5.31 -                                       final Currency a,
    5.32 -                                       final Currency b)
    5.33 +    public InvalidConversionException(final String   msg,
    5.34 +                                      final Currency bad,
    5.35 +                                      final Currency a,
    5.36 +                                      final Currency b)
    5.37      {
    5.38          super(msg);
    5.39  
     6.1 --- a/task2/solution04/test/org/apidesign/apifest08/test/Task1Test.java	Tue Oct 07 00:19:37 2008 +0200
     6.2 +++ b/task2/solution04/test/org/apidesign/apifest08/test/Task1Test.java	Tue Oct 07 00:21:03 2008 +0200
     6.3 @@ -3,6 +3,7 @@
     6.4  
     6.5  import java.math.BigDecimal;
     6.6  import java.util.Currency;
     6.7 +import java.util.Set;
     6.8  import junit.framework.TestCase;
     6.9  import org.apidesign.apifest08.currency.Convertor;
    6.10  import org.apidesign.apifest08.currency.ConvertorFactory;
    6.11 @@ -50,7 +51,8 @@
    6.12       */
    6.13      public static Convertor createCZKtoUSD()
    6.14      {
    6.15 -        return (ConvertorFactory.getConvertor("CZK", BigDecimal.valueOf(17.0), "USD", BigDecimal.valueOf(1)));
    6.16 +        return (ConvertorFactory.getConvertor("CZK", BigDecimal.valueOf(17.0),
    6.17 +                                              "USD", BigDecimal.valueOf(1)));
    6.18      }
    6.19  
    6.20      /** Create convertor that understands two currencies, CZK and
    6.21 @@ -63,7 +65,8 @@
    6.22       */
    6.23      public static Convertor createSKKtoCZK()
    6.24      {
    6.25 -        return (ConvertorFactory.getConvertor(Currency.getInstance("SKK"), BigDecimal.valueOf(100), Currency.getInstance("CZK"), BigDecimal.valueOf(80)));
    6.26 +        return (ConvertorFactory.getConvertor(Currency.getInstance("SKK"), BigDecimal.valueOf(100),
    6.27 +                                              Currency.getInstance("CZK"), BigDecimal.valueOf(80)));
    6.28      }
    6.29      
    6.30      /** Use the convertor from <code>createCZKtoUSD</code> method and do few conversions
    6.31 @@ -180,5 +183,41 @@
    6.32              assertEquals(CZK, ex.getCurrencyB());
    6.33          }
    6.34      }
    6.35 +    
    6.36 +    public void testGetCurrencies()
    6.37 +    {
    6.38 +        Convertor           c;
    6.39 +        Set<Currency> currencies;
    6.40 +        
    6.41 +        c          = createSKKtoCZK();
    6.42 +        currencies = c.getCurrencies();        
    6.43 +        assertEquals(2, currencies.size());
    6.44 +        assertTrue(currencies.contains(Currency.getInstance("SKK")));
    6.45 +        assertTrue(currencies.contains(Currency.getInstance("CZK")));
    6.46 +
    6.47 +        c          = createCZKtoUSD();
    6.48 +        currencies = c.getCurrencies();        
    6.49 +        assertEquals(2, currencies.size());
    6.50 +        assertTrue(currencies.contains(Currency.getInstance("USD")));
    6.51 +        assertTrue(currencies.contains(Currency.getInstance("CZK")));
    6.52 +    }
    6.53 +    
    6.54 +    public void testGetConverstionRate()
    6.55 +        throws InvalidConversionException
    6.56 +    {
    6.57 +        Convertor c;
    6.58 +       
    6.59 +        c = createSKKtoCZK();
    6.60 +        assertEquals(1.0,  c.getConversionRate(Currency.getInstance("CZK"), Currency.getInstance("CZK")).doubleValue());
    6.61 +        assertEquals(1.0,  c.getConversionRate(Currency.getInstance("SKK"), Currency.getInstance("SKK")).doubleValue());
    6.62 +        assertEquals(0.80, c.getConversionRate(Currency.getInstance("SKK"), Currency.getInstance("CZK")).doubleValue());
    6.63 +        assertEquals(1.25, c.getConversionRate(Currency.getInstance("CZK"), Currency.getInstance("SKK")).doubleValue());
    6.64 +
    6.65 +        c = createCZKtoUSD();
    6.66 +        assertEquals(1.0,      c.getConversionRate(Currency.getInstance("CZK"), Currency.getInstance("CZK")).doubleValue());
    6.67 +        assertEquals(1.0,      c.getConversionRate(Currency.getInstance("USD"), Currency.getInstance("USD")).doubleValue());
    6.68 +        assertEquals(1.0/17.0, c.getConversionRate(Currency.getInstance("CZK"), Currency.getInstance("USD")).doubleValue(), 0.00000000000000001);
    6.69 +        assertEquals(17.0,     c.getConversionRate(Currency.getInstance("USD"), Currency.getInstance("CZK")).doubleValue(), 0.00000000000000001);
    6.70 +    }
    6.71  }
    6.72  
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/task2/solution04/test/org/apidesign/apifest08/test/Task2Test.java	Tue Oct 07 00:21:03 2008 +0200
     7.3 @@ -0,0 +1,179 @@
     7.4 +package org.apidesign.apifest08.test;
     7.5 +
     7.6 +import java.math.BigDecimal;
     7.7 +import java.util.Currency;
     7.8 +import java.util.Set;
     7.9 +import junit.framework.TestCase;
    7.10 +import org.apidesign.apifest08.currency.Convertor;
    7.11 +import org.apidesign.apifest08.currency.ConvertorFactory;
    7.12 +import org.apidesign.apifest08.currency.InvalidConversionException;
    7.13 +
    7.14 +
    7.15 +/** There are many currencies around the world and many banks manipulate
    7.16 + * with more than one or two at the same time. As banks are usually the
    7.17 + * best paying clients, which is true even in case of your Convertor API,
    7.18 + * it is reasonable to listen to their requests.
    7.19 + * <p>
    7.20 + * The quest for today is to enhance your existing convertor API to hold
    7.21 + * information about many currencies and allow conversions between any of them.
    7.22 + * Also, as conversion rates for diferent currencies usually arise from various
    7.23 + * bank departments, there is another important need. There is a need to
    7.24 + * compose two convertors into one by merging all the information about
    7.25 + * currencies they know about.
    7.26 + */
    7.27 +public class Task2Test extends TestCase
    7.28 +{
    7.29 +    private final static Currency CZK;
    7.30 +    private final static Currency SKK;
    7.31 +    private final static Currency USD;
    7.32 +
    7.33 +    static
    7.34 +    {
    7.35 +        CZK = Currency.getInstance("CZK");
    7.36 +        SKK = Currency.getInstance("SKK");
    7.37 +        USD = Currency.getInstance("USD");
    7.38 +    }
    7.39 +
    7.40 +    public Task2Test(String testName)
    7.41 +    {
    7.42 +        super(testName);
    7.43 +    }
    7.44 +
    7.45 +    @Override
    7.46 +    protected void setUp() 
    7.47 +        throws Exception
    7.48 +    {
    7.49 +    }
    7.50 +
    7.51 +    @Override
    7.52 +    protected void tearDown() 
    7.53 +        throws Exception
    7.54 +    {
    7.55 +    }
    7.56 +
    7.57 +    // As in Task1Test, keep in mind, that there are three parts
    7.58 +    // of the whole system:
    7.59 +    // 1. there is someone who knows the current exchange rate
    7.60 +    // 2. there is someone who wants to do the conversion
    7.61 +    // 3. there is the API between 1. and 2. which allows them to communicate
    7.62 +    // 
    7.63 +    // Please backward compatibly enhance your existing API to support following
    7.64 +    // usecases:
    7.65 +    //
    7.66 +    
    7.67 +    /** Create convertor that understands two currencies, CZK and
    7.68 +     *  SKK. Make 100 SKK == 75 CZK. This is method for the group of users that
    7.69 +     *  knows the exchange rate, and needs to use the API to create objects
    7.70 +     *  with the exchange rate. Anyone shall be ready to call this method without
    7.71 +     *  any other method being called previously. The API itself shall know
    7.72 +     *  nothing about any rates, before this method is called.
    7.73 +     */
    7.74 +    public static Convertor createTripleConvertor() {
    7.75 +        // Rates: 1USD = 15CZK
    7.76 +        // Rates: 1USD = 20SKK
    7.77 +        // Rates: 75CZK = 100SKK
    7.78 +        Convertor c = ConvertorFactory.mergeConvertors(
    7.79 +            ConvertorFactory.getConvertor(USD, BigDecimal.ONE, CZK, BigDecimal.valueOf(15.00)),
    7.80 +            ConvertorFactory.getConvertor(USD, BigDecimal.ONE, SKK, BigDecimal.valueOf(20.00))
    7.81 +        );
    7.82 +        
    7.83 +        return c;
    7.84 +    }
    7.85 +
    7.86 +    /** Define convertor that understands three currencies. Use it.
    7.87 +     */
    7.88 +    public void testConvertorForUSDandCZKandSKK() throws Exception {
    7.89 +        Convertor c = createTripleConvertor();
    7.90 +
    7.91 +        // convert $5 to CZK using c:
    7.92 +        // assertEquals("Result is 75 CZK");
    7.93 +        assertEquals(new BigDecimal("75.00"), c.convert(USD, CZK, BigDecimal.valueOf(5.00)));
    7.94 +
    7.95 +        // convert $5 to SKK using c:
    7.96 +        // assertEquals("Result is 100 SKK");
    7.97 +        assertEquals(new BigDecimal("100.00"), c.convert(USD, SKK, BigDecimal.valueOf(5.00)));
    7.98 +
    7.99 +        // convert 200SKK to CZK using c:
   7.100 +        // assertEquals("Result is 150 CZK");
   7.101 +        assertEquals(new BigDecimal("150.00"), c.convert(SKK, CZK, BigDecimal.valueOf(200.00)));
   7.102 +
   7.103 +        // convert 200SKK to USK using c:
   7.104 +        // assertEquals("Result is 10 USD");
   7.105 +        assertEquals(new BigDecimal("10.00"), c.convert(SKK, USD, BigDecimal.valueOf(200.00)));
   7.106 +    }
   7.107 +
   7.108 +    /** Merge all currency rates of convertor 1 with convertor 2.
   7.109 +     * Implement this using your API, preferably this method just delegates
   7.110 +     * into some API method which does the actual work, without requiring
   7.111 +     * API clients to code anything complex.
   7.112 +     */
   7.113 +    public static Convertor merge(Convertor one, Convertor two) {
   7.114 +        return ConvertorFactory.mergeConvertors(one, two);
   7.115 +    }
   7.116 +
   7.117 +    /** Join the convertors from previous task, Task1Test and show that it
   7.118 +     * can be used to do reasonable conversions.
   7.119 +     */
   7.120 +    public void testConvertorComposition() throws Exception {
   7.121 +        Convertor c = merge(
   7.122 +            Task1Test.createCZKtoUSD(),
   7.123 +            Task1Test.createSKKtoCZK()
   7.124 +        );
   7.125 +
   7.126 +        // convert $5 to CZK using c:
   7.127 +        // assertEquals("Result is 85 CZK");
   7.128 +        assertEquals(new BigDecimal("85.00"), c.convert(USD, CZK, BigDecimal.valueOf(5.00)));
   7.129 +
   7.130 +        // convert $8 to CZK using c:
   7.131 +        // assertEquals("Result is 136 CZK");
   7.132 +        assertEquals(new BigDecimal("136.00"), c.convert(USD, CZK, BigDecimal.valueOf(8.00)));
   7.133 +
   7.134 +        // convert 1003CZK to USD using c:
   7.135 +        // assertEquals("Result is 59 USD");
   7.136 +        assertEquals(new BigDecimal("59.00"), c.convert(CZK, USD, BigDecimal.valueOf(1003.00)));
   7.137 +
   7.138 +        // convert 16CZK using c:
   7.139 +        // assertEquals("Result is 20 SKK");
   7.140 +        assertEquals(new BigDecimal("20.00"), c.convert(CZK, SKK, BigDecimal.valueOf(16.00)));
   7.141 +
   7.142 +        // convert 500SKK to CZK using c:
   7.143 +        // assertEquals("Result is 400 CZK");
   7.144 +        assertEquals(new BigDecimal("400.00"), c.convert(SKK, CZK, BigDecimal.valueOf(500.00)));
   7.145 +    }
   7.146 +    
   7.147 +    public void testGetCurrencies()
   7.148 +    {
   7.149 +        Convertor c = merge(
   7.150 +            Task1Test.createCZKtoUSD(),
   7.151 +            Task1Test.createSKKtoCZK()
   7.152 +        );
   7.153 +        Set<Currency> currencies;
   7.154 +        
   7.155 +        currencies = c.getCurrencies();        
   7.156 +        assertEquals(3, currencies.size());
   7.157 +        assertTrue(currencies.contains(Currency.getInstance("SKK")));
   7.158 +        assertTrue(currencies.contains(Currency.getInstance("CZK")));
   7.159 +        assertTrue(currencies.contains(Currency.getInstance("USD")));
   7.160 +    }
   7.161 +    
   7.162 +    public void testGetConverstionRate()
   7.163 +        throws InvalidConversionException
   7.164 +    {
   7.165 +        Convertor c = merge(
   7.166 +            Task1Test.createCZKtoUSD(),
   7.167 +            Task1Test.createSKKtoCZK()
   7.168 +        );
   7.169 +
   7.170 +        assertEquals(1.0,        c.getConversionRate(USD, USD).doubleValue(), 0.0000000000000001);
   7.171 +        assertEquals(17.0,       c.getConversionRate(USD, CZK).doubleValue(), 0.0000000000000001);
   7.172 +        assertEquals(21.25,      c.getConversionRate(USD, SKK).doubleValue(), 0.0000000000000001);
   7.173 +
   7.174 +        assertEquals(1.0 / 17.0, c.getConversionRate(CZK, USD).doubleValue(), 0.0000000000000001);
   7.175 +        assertEquals(1.0,        c.getConversionRate(CZK, CZK).doubleValue(), 0.0000000000000001);
   7.176 +        assertEquals(1.25,       c.getConversionRate(CZK, SKK).doubleValue(), 0.0000000000000001);
   7.177 +
   7.178 +        assertEquals(0.04705882352941176, c.getConversionRate(SKK, USD).doubleValue(), 0.0000000000000001);
   7.179 +        assertEquals(0.8,                 c.getConversionRate(SKK, CZK).doubleValue(), 0.0000000000000001);
   7.180 +        assertEquals(1.0,                 c.getConversionRate(SKK, SKK).doubleValue(), 0.0000000000000001);
   7.181 +    }
   7.182 +}