adding solution11 for task 2
authorjapod@localhost
Tue, 07 Oct 2008 00:25:53 +0200
changeset 37d333e45f6df1
parent 36 6102a569c7fa
child 38 5838e0aaa81f
child 39 daec35cd96e8
adding solution11 for task 2
task2/solution11/nbproject/build-impl.xml
task2/solution11/nbproject/genfiles.properties
task2/solution11/src/org/apidesign/apifest08/currency/Convertor.java
task2/solution11/src/org/apidesign/apifest08/currency/ExchangeRateValue.java
task2/solution11/test/org/apidesign/apifest08/test/Task2Test.java
     1.1 --- a/task2/solution11/nbproject/build-impl.xml	Tue Oct 07 00:24:22 2008 +0200
     1.2 +++ b/task2/solution11/nbproject/build-impl.xml	Tue Oct 07 00:25:53 2008 +0200
     1.3 @@ -218,13 +218,13 @@
     1.4              </sequential>
     1.5          </macrodef>
     1.6      </target>
     1.7 -    <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
     1.8 +    <target name="-init-macrodef-nbjpda">
     1.9          <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
    1.10              <attribute default="${main.class}" name="name"/>
    1.11              <attribute default="${debug.classpath}" name="classpath"/>
    1.12              <attribute default="" name="stopclassname"/>
    1.13              <sequential>
    1.14 -                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
    1.15 +                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="dt_socket">
    1.16                      <classpath>
    1.17                          <path path="@{classpath}"/>
    1.18                      </classpath>
    1.19 @@ -255,12 +255,6 @@
    1.20          <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
    1.21              <istrue value="${have-jdk-older-than-1.4}"/>
    1.22          </condition>
    1.23 -        <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
    1.24 -            <os family="windows"/>
    1.25 -        </condition>
    1.26 -        <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
    1.27 -            <isset property="debug.transport"/>
    1.28 -        </condition>
    1.29      </target>
    1.30      <target depends="-init-debug-args" name="-init-macrodef-debug">
    1.31          <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
    1.32 @@ -270,7 +264,7 @@
    1.33              <sequential>
    1.34                  <java classname="@{classname}" dir="${work.dir}" fork="true">
    1.35                      <jvmarg line="${debug-args-line}"/>
    1.36 -                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
    1.37 +                    <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
    1.38                      <jvmarg line="${run.jvmargs}"/>
    1.39                      <classpath>
    1.40                          <path path="@{classpath}"/>
    1.41 @@ -317,13 +311,6 @@
    1.42                  ===================
    1.43              -->
    1.44      <target depends="init" name="deps-jar" unless="no.deps"/>
    1.45 -    <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
    1.46 -    <target depends="init" name="-check-automatic-build">
    1.47 -        <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
    1.48 -    </target>
    1.49 -    <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
    1.50 -        <antcall target="clean"/>
    1.51 -    </target>
    1.52      <target depends="init,deps-jar" name="-pre-pre-compile">
    1.53          <mkdir dir="${build.classes.dir}"/>
    1.54      </target>
    1.55 @@ -344,7 +331,7 @@
    1.56          <!-- Empty placeholder for easier customization. -->
    1.57          <!-- You can override this target in the ../build.xml file. -->
    1.58      </target>
    1.59 -    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
    1.60 +    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
    1.61      <target name="-pre-compile-single">
    1.62          <!-- Empty placeholder for easier customization. -->
    1.63          <!-- You can override this target in the ../build.xml file. -->
    1.64 @@ -358,7 +345,7 @@
    1.65          <!-- Empty placeholder for easier customization. -->
    1.66          <!-- You can override this target in the ../build.xml file. -->
    1.67      </target>
    1.68 -    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
    1.69 +    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
    1.70      <!--
    1.71                  ====================
    1.72                  JAR BUILDING SECTION
     2.1 --- a/task2/solution11/nbproject/genfiles.properties	Tue Oct 07 00:24:22 2008 +0200
     2.2 +++ b/task2/solution11/nbproject/genfiles.properties	Tue Oct 07 00:25:53 2008 +0200
     2.3 @@ -4,5 +4,5 @@
     2.4  # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
     2.5  # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
     2.6  nbproject/build-impl.xml.data.CRC32=0e1e702f
     2.7 -nbproject/build-impl.xml.script.CRC32=6cbb076a
     2.8 -nbproject/build-impl.xml.stylesheet.CRC32=e55b27f5
     2.9 +nbproject/build-impl.xml.script.CRC32=c899f2cf
    2.10 +nbproject/build-impl.xml.stylesheet.CRC32=487672f9
     3.1 --- a/task2/solution11/src/org/apidesign/apifest08/currency/Convertor.java	Tue Oct 07 00:24:22 2008 +0200
     3.2 +++ b/task2/solution11/src/org/apidesign/apifest08/currency/Convertor.java	Tue Oct 07 00:25:53 2008 +0200
     3.3 @@ -1,5 +1,10 @@
     3.4  package org.apidesign.apifest08.currency;
     3.5  
     3.6 +import java.util.ArrayList;
     3.7 +import java.util.Collection;
     3.8 +import java.util.HashSet;
     3.9 +import java.util.List;
    3.10 +import java.util.Set;
    3.11  import org.apidesign.apifest08.currency.Computer.ComputerRequest;
    3.12  import org.apidesign.apifest08.currency.Computer.ComputerResponse;
    3.13  
    3.14 @@ -10,31 +15,50 @@
    3.15   * with amount stored in integer or double, that are identified
    3.16   * with string value. Exchange rates are immutable.
    3.17   * 
    3.18 + * In Task2's version provides support for multiple exchange rates
    3.19 + * between different currencies & merging exchange rates from
    3.20 + * existing convertors into new convertor's instance.
    3.21 + * No time for javadoc these features, sorry.
    3.22 + * 
    3.23   * @author ked
    3.24   */
    3.25  public final class Convertor<AmountType, IdentifierType> {
    3.26  
    3.27      Computer<AmountType> computer;
    3.28 -    CurrencyValue<AmountType, IdentifierType> firstCurrencyExchangeRate;
    3.29 -    CurrencyValue<AmountType, IdentifierType> secondCurrencyExchangeRate;
    3.30 +    List<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates = new ArrayList<ExchangeRateValue<AmountType, IdentifierType>>();
    3.31  
    3.32      Convertor(
    3.33              Computer<AmountType> computer,
    3.34 -            CurrencyValue<AmountType, IdentifierType> firstCurrencyExchangeRate,
    3.35 -            CurrencyValue<AmountType, IdentifierType> secondCurrencyExchangeRate) {
    3.36 -        if (firstCurrencyExchangeRate.getIdentifier() == null ||
    3.37 -            secondCurrencyExchangeRate.getIdentifier() == null ||
    3.38 -            firstCurrencyExchangeRate.getIdentifier().equals(secondCurrencyExchangeRate.getIdentifier())) {
    3.39 -                throw new IllegalArgumentException("Inappropriate exchange rates' identifiers!");
    3.40 +            Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates) {
    3.41 +        this.computer = computer;
    3.42 +
    3.43 +        for (ExchangeRateValue<AmountType, IdentifierType> exchangeRate : exchangeRates) {
    3.44 +            if (findExchangeRate(
    3.45 +                    this.exchangeRates,
    3.46 +                    exchangeRate.getCurrencyA().getIdentifier(),
    3.47 +                    exchangeRate.getCurrencyB().getIdentifier()) != null) {
    3.48 +                throw new IllegalArgumentException("Duplicate exchange rate!");
    3.49 +            }
    3.50 +            this.exchangeRates.add(exchangeRate);
    3.51          }
    3.52 -        this.computer = computer;
    3.53 -        this.firstCurrencyExchangeRate = firstCurrencyExchangeRate;
    3.54 -        this.secondCurrencyExchangeRate = secondCurrencyExchangeRate;
    3.55 +    }
    3.56 +
    3.57 +    private ExchangeRateValue<AmountType, IdentifierType> findExchangeRate(
    3.58 +            Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates,
    3.59 +            IdentifierType currencyA,
    3.60 +            IdentifierType currencyB) {
    3.61 +        for (ExchangeRateValue<AmountType, IdentifierType> exchangeRate : exchangeRates) {
    3.62 +            if ((exchangeRate.getCurrencyA().getIdentifier().equals(currencyA) && exchangeRate.getCurrencyB().getIdentifier().equals(currencyB)) ||
    3.63 +                    (exchangeRate.getCurrencyA().getIdentifier().equals(currencyB) && exchangeRate.getCurrencyB().getIdentifier().equals(currencyA))) {
    3.64 +                return exchangeRate;
    3.65 +            }
    3.66 +        }
    3.67 +        return null;
    3.68      }
    3.69  
    3.70      /**
    3.71       * Convert an amount of the one currency to an amount of the another one currency
    3.72 -     * with respect to previously specified exchange rate.
    3.73 +     * with respect to previously specified exchange rates.
    3.74       * 
    3.75       * @param targetCurrency an identifier of the requested currency
    3.76       * @param currencyValue an amount of the another one currency
    3.77 @@ -43,52 +67,117 @@
    3.78      public CurrencyValue<AmountType, IdentifierType> convert(
    3.79              IdentifierType targetCurrency,
    3.80              CurrencyValue<AmountType, IdentifierType> currencyValue) {
    3.81 -        if (firstCurrencyExchangeRate.getIdentifier().equals(targetCurrency) &&
    3.82 -            secondCurrencyExchangeRate.getIdentifier().equals(currencyValue.getIdentifier())) {
    3.83 -            ComputerRequest<AmountType> computerRequest = new ComputerRequest<AmountType>();
    3.84 -            computerRequest.setInput(currencyValue.getAmount());
    3.85 -            computerRequest.setInputCurrencyRatio(secondCurrencyExchangeRate.getAmount());
    3.86 -            computerRequest.setOutputCurrencyRatio(firstCurrencyExchangeRate.getAmount());
    3.87 -            ComputerResponse<AmountType> computerResponse = computer.compute(computerRequest);
    3.88 -
    3.89 -            return CurrencyValue.getCurrencyValue(
    3.90 -                    computerResponse.getResult(),
    3.91 -                    firstCurrencyExchangeRate.getIdentifier());
    3.92 -        } else if (secondCurrencyExchangeRate.getIdentifier().equals(targetCurrency) &&
    3.93 -                   firstCurrencyExchangeRate.getIdentifier().equals(currencyValue.getIdentifier())) {
    3.94 -            ComputerRequest<AmountType> computerRequest = new ComputerRequest<AmountType>();
    3.95 -            computerRequest.setInput(currencyValue.getAmount());
    3.96 -            computerRequest.setInputCurrencyRatio(firstCurrencyExchangeRate.getAmount());
    3.97 -            computerRequest.setOutputCurrencyRatio(secondCurrencyExchangeRate.getAmount());
    3.98 -            ComputerResponse<AmountType> computerResponse = computer.compute(computerRequest);
    3.99 -
   3.100 -            return CurrencyValue.getCurrencyValue(
   3.101 -                    computerResponse.getResult(),
   3.102 -                    secondCurrencyExchangeRate.getIdentifier());
   3.103 -        } else {
   3.104 +        ExchangeRateValue<AmountType, IdentifierType> exchangeRate =
   3.105 +                findExchangeRate(exchangeRates, currencyValue.getIdentifier(), targetCurrency);
   3.106 +        if (exchangeRate == null) {
   3.107              throw new IllegalArgumentException("Inappropriate currencies to convert!");
   3.108          }
   3.109 +
   3.110 +        ComputerRequest<AmountType> computerRequest = new ComputerRequest<AmountType>();
   3.111 +        computerRequest.setInput(currencyValue.getAmount());
   3.112 +
   3.113 +        IdentifierType targetCurrencyRef; // just for backward compatibility :-(
   3.114 +        if (exchangeRate.getCurrencyA().getIdentifier().equals(targetCurrency)) {
   3.115 +            computerRequest.setInputCurrencyRatio(exchangeRate.getCurrencyB().getAmount());
   3.116 +            computerRequest.setOutputCurrencyRatio(exchangeRate.getCurrencyA().getAmount());
   3.117 +            targetCurrencyRef = exchangeRate.getCurrencyA().getIdentifier();
   3.118 +        } else {
   3.119 +            computerRequest.setInputCurrencyRatio(exchangeRate.getCurrencyA().getAmount());
   3.120 +            computerRequest.setOutputCurrencyRatio(exchangeRate.getCurrencyB().getAmount());
   3.121 +            targetCurrencyRef = exchangeRate.getCurrencyB().getIdentifier();
   3.122 +        }
   3.123 +
   3.124 +        ComputerResponse<AmountType> computerResponse = computer.compute(computerRequest);
   3.125 +        return CurrencyValue.getCurrencyValue(
   3.126 +                computerResponse.getResult(),
   3.127 +                targetCurrencyRef);
   3.128 +    }
   3.129 +
   3.130 +    // ---
   3.131 +    // MERGING
   3.132 +    // ---
   3.133 +    static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> mergeConvertors(
   3.134 +            Computer<AmountType> computer,
   3.135 +            Collection<Convertor<AmountType, IdentifierType>> convertors) {
   3.136 +        Set<ExchangeRateValue<AmountType, IdentifierType>> exchangeRatesSet = new HashSet<ExchangeRateValue<AmountType, IdentifierType>>();
   3.137 +        for (Convertor<AmountType, IdentifierType> convertor : convertors) {
   3.138 +            exchangeRatesSet.addAll(convertor.exchangeRates);
   3.139 +        }
   3.140 +        return getConvertor(computer, exchangeRatesSet);
   3.141 +    }
   3.142 +
   3.143 +    static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> mergeConvertors(
   3.144 +            Computer<AmountType> computer,
   3.145 +            Convertor<AmountType, IdentifierType> convertorA,
   3.146 +            Convertor<AmountType, IdentifierType> convertorB) {
   3.147 +        Collection<Convertor<AmountType, IdentifierType>> convertors =
   3.148 +                new ArrayList<Convertor<AmountType, IdentifierType>>();
   3.149 +        convertors.add(convertorA);
   3.150 +        convertors.add(convertorB);
   3.151 +        return mergeConvertors(computer, convertors);
   3.152 +    }
   3.153 +
   3.154 +    public static Convertor<Double, String> mergeConvertorsDoubleString(
   3.155 +            Collection<Convertor<Double, String>> convertors) {
   3.156 +        return mergeConvertors(DoubleComputer, convertors);
   3.157 +    }
   3.158 +
   3.159 +    public static Convertor<Double, String> mergeConvertorsDoubleString(
   3.160 +            Convertor<Double, String> convertorA,
   3.161 +            Convertor<Double, String> convertorB) {
   3.162 +        return mergeConvertors(DoubleComputer, convertorA, convertorB);
   3.163 +    }
   3.164 +
   3.165 +    public static Convertor<Integer, String> mergeConvertorsIntegerString(
   3.166 +            Collection<Convertor<Integer, String>> convertors) {
   3.167 +        return mergeConvertors(IntegerComputer, convertors);
   3.168 +    }
   3.169 +
   3.170 +    public static Convertor<Integer, String> mergeConvertorsIntegerString(
   3.171 +            Convertor<Integer, String> convertorA,
   3.172 +            Convertor<Integer, String> convertorB) {
   3.173 +        return mergeConvertors(IntegerComputer, convertorA, convertorB);
   3.174 +    }
   3.175 +
   3.176 +    // ---
   3.177 +    // CREATION
   3.178 +    // ---
   3.179 +    static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertor(
   3.180 +            Computer<AmountType> computer, Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates) {
   3.181 +        return new Convertor<AmountType, IdentifierType>(computer, exchangeRates);
   3.182      }
   3.183  
   3.184      static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertor(
   3.185 -            Computer<AmountType> computer,
   3.186 -            CurrencyValue<AmountType, IdentifierType> firstCurrencyExchangeRate,
   3.187 -            CurrencyValue<AmountType, IdentifierType> secondCurrencyExchangeRate) {
   3.188 -        return new Convertor<AmountType, IdentifierType>(
   3.189 -                computer,
   3.190 -                firstCurrencyExchangeRate,
   3.191 -                secondCurrencyExchangeRate);
   3.192 +            Computer<AmountType> computer, ExchangeRateValue<AmountType, IdentifierType> exchangeRate) {
   3.193 +        Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates =
   3.194 +                new ArrayList<ExchangeRateValue<AmountType, IdentifierType>>();
   3.195 +        exchangeRates.add(exchangeRate);
   3.196 +        return getConvertor(computer, exchangeRates);
   3.197      }
   3.198      
   3.199 -    static final Computer<Double> DoubleComputer = new Computer<Double>() {
   3.200 +    public static Convertor<Double, String> getConvertorDoubleString(
   3.201 +            Collection<ExchangeRateValue<Double, String>> exchangeRates) {
   3.202 +        return getConvertor(DoubleComputer, exchangeRates);
   3.203 +    }
   3.204  
   3.205 -        public ComputerResponse<Double> compute(ComputerRequest<Double> request) {
   3.206 -            ComputerResponse<Double> response = new ComputerResponse<Double>();
   3.207 -            response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());
   3.208 -            return response;
   3.209 -        }
   3.210 -    };
   3.211 +    public static Convertor<Double, String> getConvertorDoubleString(
   3.212 +            ExchangeRateValue<Double, String> exchangeRate) {
   3.213 +        return getConvertor(DoubleComputer, exchangeRate);
   3.214 +    }
   3.215  
   3.216 +    public static Convertor<Integer, String> getConvertorIntegerString(
   3.217 +            Collection<ExchangeRateValue<Integer, String>> exchangeRates) {
   3.218 +        return getConvertor(IntegerComputer, exchangeRates);
   3.219 +    }
   3.220 +
   3.221 +    public static Convertor<Integer, String> getConvertorIntegerString(
   3.222 +            ExchangeRateValue<Integer, String> exchangeRate) {
   3.223 +        return getConvertor(IntegerComputer, exchangeRate);
   3.224 +    }
   3.225 +
   3.226 +    // ---
   3.227 +    // BACKWARD COMPATIBILITY - CREATION
   3.228 +    // ---
   3.229      /**
   3.230       * Creates convertor for Double|String values with specified exchange rate
   3.231       * between two currencies.
   3.232 @@ -97,21 +186,11 @@
   3.233       * @param secondCurrencyExchangeRate second currency
   3.234       * @return convertor
   3.235       */
   3.236 -
   3.237      public static Convertor<Double, String> getConvertorDoubleString(
   3.238              CurrencyValue<Double, String> firstCurrencyExchangeRate,
   3.239              CurrencyValue<Double, String> secondCurrencyExchangeRate) {
   3.240 -        return getConvertor(DoubleComputer, firstCurrencyExchangeRate, secondCurrencyExchangeRate);
   3.241 +        return getConvertorDoubleString(ExchangeRateValue.getExchangeRate(firstCurrencyExchangeRate, secondCurrencyExchangeRate));
   3.242      }
   3.243 -    
   3.244 -    static final Computer<Integer> IntegerComputer = new Computer<Integer>() {
   3.245 -
   3.246 -        public ComputerResponse<Integer> compute(ComputerRequest<Integer> request) {
   3.247 -            ComputerResponse<Integer> response = new ComputerResponse<Integer>();
   3.248 -            response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());
   3.249 -            return response;
   3.250 -        }
   3.251 -    };
   3.252  
   3.253      /**
   3.254       * Creates convertor for Integer|String values with specified exchange rate
   3.255 @@ -124,6 +203,26 @@
   3.256      public static Convertor<Integer, String> getConvertorIntegerString(
   3.257              CurrencyValue<Integer, String> firstCurrencyExchangeRate,
   3.258              CurrencyValue<Integer, String> secondCurrencyExchangeRate) {
   3.259 -        return getConvertor(IntegerComputer, firstCurrencyExchangeRate, secondCurrencyExchangeRate);
   3.260 +        return getConvertorIntegerString(ExchangeRateValue.getExchangeRate(firstCurrencyExchangeRate, secondCurrencyExchangeRate));
   3.261      }
   3.262 +    
   3.263 +    // ---
   3.264 +    // COMPUTERS
   3.265 +    // ---
   3.266 +    static final Computer<Double> DoubleComputer = new Computer<Double>() {
   3.267 +
   3.268 +        public ComputerResponse<Double> compute(ComputerRequest<Double> request) {
   3.269 +            ComputerResponse<Double> response = new ComputerResponse<Double>();
   3.270 +            response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());
   3.271 +            return response;
   3.272 +        }
   3.273 +    };
   3.274 +    static final Computer<Integer> IntegerComputer = new Computer<Integer>() {
   3.275 +
   3.276 +        public ComputerResponse<Integer> compute(ComputerRequest<Integer> request) {
   3.277 +            ComputerResponse<Integer> response = new ComputerResponse<Integer>();
   3.278 +            response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());
   3.279 +            return response;
   3.280 +        }
   3.281 +    };
   3.282  }
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/task2/solution11/src/org/apidesign/apifest08/currency/ExchangeRateValue.java	Tue Oct 07 00:25:53 2008 +0200
     4.3 @@ -0,0 +1,78 @@
     4.4 +package org.apidesign.apifest08.currency;
     4.5 +
     4.6 +import java.io.Serializable;
     4.7 +
     4.8 +/**
     4.9 + * Value class, holding an exchange rate between two currencies.
    4.10 + * Designed to be an immutable.
    4.11 + * 
    4.12 + * @author ked
    4.13 + */
    4.14 +public final class ExchangeRateValue<AmountType, IdentifierType> implements Serializable {
    4.15 +
    4.16 +    private final CurrencyValue<AmountType, IdentifierType> currencyA;
    4.17 +    private final CurrencyValue<AmountType, IdentifierType> currencyB;
    4.18 +
    4.19 +    private ExchangeRateValue(
    4.20 +            CurrencyValue<AmountType, IdentifierType> currencyA,
    4.21 +            CurrencyValue<AmountType, IdentifierType> currencyB) {
    4.22 +        if (currencyA.getIdentifier() == null ||
    4.23 +            currencyB.getIdentifier() == null ||
    4.24 +            currencyA.getIdentifier().equals(currencyB)) {
    4.25 +                throw new IllegalArgumentException("Inappropriate exchange rates' identifiers!");
    4.26 +        }
    4.27 +
    4.28 +        this.currencyA = currencyA;
    4.29 +        this.currencyB = currencyB;
    4.30 +    }
    4.31 +
    4.32 +    public CurrencyValue<AmountType, IdentifierType> getCurrencyA() {
    4.33 +        return currencyA;
    4.34 +    }
    4.35 +
    4.36 +    public CurrencyValue<AmountType, IdentifierType> getCurrencyB() {
    4.37 +        return currencyB;
    4.38 +    }
    4.39 +
    4.40 +    @Override
    4.41 +    public boolean equals(Object obj) {
    4.42 +        if (obj == null) {
    4.43 +            return false;
    4.44 +        }
    4.45 +        if (getClass() != obj.getClass()) {
    4.46 +            return false;
    4.47 +        }
    4.48 +        final ExchangeRateValue other = (ExchangeRateValue) obj;
    4.49 +        if (this.currencyA != other.currencyA && (this.currencyA == null || !this.currencyA.equals(other.currencyA))) {
    4.50 +            return false;
    4.51 +        }
    4.52 +        if (this.currencyB != other.currencyB && (this.currencyB == null || !this.currencyB.equals(other.currencyB))) {
    4.53 +            return false;
    4.54 +        }
    4.55 +        return true;
    4.56 +    }
    4.57 +
    4.58 +    @Override
    4.59 +    public int hashCode() {
    4.60 +        int hash = 3;
    4.61 +        hash = 71 * hash + (this.currencyA != null ? this.currencyA.hashCode() : 0);
    4.62 +        hash = 71 * hash + (this.currencyB != null ? this.currencyB.hashCode() : 0);
    4.63 +        return hash;
    4.64 +    }
    4.65 +
    4.66 +    /**
    4.67 +     * Creates new instance.
    4.68 +     * Generic types of the new instance are derived from types of the parameters.
    4.69 +     * 
    4.70 +     * @param <AmountType> type of the currency amount
    4.71 +     * @param <IdentifierType> type of the currency identifier
    4.72 +     * @param currencyA one currency of the exchange rate
    4.73 +     * @param currencyB another currency of the exchange rate
    4.74 +     * @return new instance
    4.75 +     */
    4.76 +    public static <AmountType, IdentifierType> ExchangeRateValue<AmountType, IdentifierType> getExchangeRate(
    4.77 +            CurrencyValue<AmountType, IdentifierType> currencyA,
    4.78 +            CurrencyValue<AmountType, IdentifierType> currencyB) {
    4.79 +        return new ExchangeRateValue<AmountType, IdentifierType>(currencyA, currencyB);
    4.80 +    }
    4.81 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/task2/solution11/test/org/apidesign/apifest08/test/Task2Test.java	Tue Oct 07 00:25:53 2008 +0200
     5.3 @@ -0,0 +1,140 @@
     5.4 +package org.apidesign.apifest08.test;
     5.5 +
     5.6 +import java.util.ArrayList;
     5.7 +import java.util.Collection;
     5.8 +import junit.framework.TestCase;
     5.9 +import org.apidesign.apifest08.currency.Convertor;
    5.10 +import org.apidesign.apifest08.currency.CurrencyValue;
    5.11 +import org.apidesign.apifest08.currency.ExchangeRateValue;
    5.12 +
    5.13 +/** There are many currencies around the world and many banks manipulate
    5.14 + * with more than one or two at the same time. As banks are usually the
    5.15 + * best paying clients, which is true even in case of your Convertor API,
    5.16 + * it is reasonable to listen to their requests.
    5.17 + * <p>
    5.18 + * The quest for today is to enhance your existing convertor API to hold
    5.19 + * information about many currencies and allow conversions between any of them.
    5.20 + * Also, as conversion rates for diferent currencies usually arise from various
    5.21 + * bank departments, there is another important need. There is a need to
    5.22 + * compose two convertors into one by merging all the information about
    5.23 + * currencies they know about.
    5.24 + */
    5.25 +public class Task2Test extends TestCase {
    5.26 +
    5.27 +    public Task2Test(String testName) {
    5.28 +        super(testName);
    5.29 +    }
    5.30 +
    5.31 +    @Override
    5.32 +    protected void setUp() throws Exception {
    5.33 +    }
    5.34 +
    5.35 +    @Override
    5.36 +    protected void tearDown() throws Exception {
    5.37 +    }
    5.38 +
    5.39 +    // As in Task1Test, keep in mind, that there are three parts
    5.40 +    // of the whole system:
    5.41 +    // 1. there is someone who knows the current exchange rate
    5.42 +    // 2. there is someone who wants to do the conversion
    5.43 +    // 3. there is the API between 1. and 2. which allows them to communicate
    5.44 +    // 
    5.45 +    // Please backward compatibly enhance your existing API to support following
    5.46 +    // usecases:
    5.47 +    //
    5.48 +    /** Create convertor that understands two currencies, CZK and
    5.49 +     *  SKK. Make 100 SKK == 75 CZK. This is method for the group of users that
    5.50 +     *  knows the exchange rate, and needs to use the API to create objects
    5.51 +     *  with the exchange rate. Anyone shall be ready to call this method without
    5.52 +     *  any other method being called previously. The API itself shall know
    5.53 +     *  nothing about any rates, before this method is called.
    5.54 +     */
    5.55 +    public static Convertor<Integer, String> createTripleConvertor() {
    5.56 +        // Rates: 1USD = 15CZK
    5.57 +        // Rates: 1USD = 20SKK
    5.58 +        // Rates: 75CZK = 100SKK
    5.59 +        Collection<ExchangeRateValue<Integer, String>> exchangeRates =
    5.60 +                new ArrayList<ExchangeRateValue<Integer, String>>();
    5.61 +        exchangeRates.add(ExchangeRateValue.getExchangeRate(
    5.62 +                CurrencyValue.getCurrencyValue(1, "USD"),
    5.63 +                CurrencyValue.getCurrencyValue(15, "CZK")));
    5.64 +        exchangeRates.add(ExchangeRateValue.getExchangeRate(
    5.65 +                CurrencyValue.getCurrencyValue(1, "USD"),
    5.66 +                CurrencyValue.getCurrencyValue(20, "SKK")));
    5.67 +        exchangeRates.add(ExchangeRateValue.getExchangeRate(
    5.68 +                CurrencyValue.getCurrencyValue(75, "CZK"),
    5.69 +                CurrencyValue.getCurrencyValue(100, "SKK")));
    5.70 +        return Convertor.getConvertorIntegerString(exchangeRates);
    5.71 +    }
    5.72 +
    5.73 +    /** Define convertor that understands three currencies. Use it.
    5.74 +     */
    5.75 +    public void testConvertorForUSDandCZKandSKK() throws Exception {
    5.76 +        Convertor<Integer, String> c = createTripleConvertor();
    5.77 +
    5.78 +        CurrencyValue<Integer, String> result;
    5.79 +        // convert $5 to CZK using c:
    5.80 +        // assertEquals("Result is 75 CZK");
    5.81 +        result = c.convert("CZK", CurrencyValue.getCurrencyValue(5, "USD"));
    5.82 +        assertEquals(CurrencyValue.getCurrencyValue(75, "CZK"), result);
    5.83 +
    5.84 +        // convert $5 to SKK using c:
    5.85 +        // assertEquals("Result is 100 SKK");
    5.86 +        result = c.convert("SKK", CurrencyValue.getCurrencyValue(5, "USD"));
    5.87 +        assertEquals(CurrencyValue.getCurrencyValue(100, "SKK"), result);
    5.88 +
    5.89 +        // convert 200SKK to CZK using c:
    5.90 +        // assertEquals("Result is 150 CZK");
    5.91 +        result = c.convert("CZK", CurrencyValue.getCurrencyValue(200, "SKK"));
    5.92 +        assertEquals(CurrencyValue.getCurrencyValue(150, "CZK"), result);
    5.93 +
    5.94 +        // convert 200SKK to USK using c:
    5.95 +        // assertEquals("Result is 10 USD");
    5.96 +        result = c.convert("USD", CurrencyValue.getCurrencyValue(200, "SKK"));
    5.97 +        assertEquals(CurrencyValue.getCurrencyValue(10, "USD"), result);
    5.98 +    }
    5.99 +
   5.100 +    /** Merge all currency rates of convertor 1 with convertor 2.
   5.101 +     * Implement this using your API, preferably this method just delegates
   5.102 +     * into some API method which does the actual work, without requiring
   5.103 +     * API clients to code anything complex.
   5.104 +     */
   5.105 +    public static Convertor<Integer, String> merge(Convertor<Integer, String> one, Convertor<Integer, String> two) {
   5.106 +        return Convertor.mergeConvertorsIntegerString(one, two);
   5.107 +    }
   5.108 +
   5.109 +    /** Join the convertors from previous task, Task1Test and show that it
   5.110 +     * can be used to do reasonable conversions.
   5.111 +     */
   5.112 +    public void testConvertorComposition() throws Exception {
   5.113 +        Convertor<Integer, String> c = merge(
   5.114 +                Task1Test.createCZKtoUSD(),
   5.115 +                Task1Test.createSKKtoCZK());
   5.116 +
   5.117 +        CurrencyValue<Integer, String> result;
   5.118 +        // convert $5 to CZK using c:
   5.119 +        // assertEquals("Result is 85 CZK");
   5.120 +        result = c.convert("CZK", CurrencyValue.getCurrencyValue(5, "USD"));
   5.121 +        assertEquals(CurrencyValue.getCurrencyValue(85, "CZK"), result);
   5.122 +
   5.123 +        // convert $8 to CZK using c:
   5.124 +        // assertEquals("Result is 136 CZK");
   5.125 +        result = c.convert("CZK", CurrencyValue.getCurrencyValue(8, "USD"));
   5.126 +        assertEquals(CurrencyValue.getCurrencyValue(136, "CZK"), result);
   5.127 +
   5.128 +        // convert 1003CZK to USD using c:
   5.129 +        // assertEquals("Result is 59 USD");
   5.130 +        result = c.convert("USD", CurrencyValue.getCurrencyValue(1003, "CZK"));
   5.131 +        assertEquals(CurrencyValue.getCurrencyValue(59, "USD"), result);
   5.132 +
   5.133 +        // convert 16CZK using c:
   5.134 +        // assertEquals("Result is 20 SKK");
   5.135 +        result = c.convert("SKK", CurrencyValue.getCurrencyValue(16, "CZK"));
   5.136 +        assertEquals(CurrencyValue.getCurrencyValue(20, "SKK"), result);
   5.137 +
   5.138 +        // convert 500SKK to CZK using c:
   5.139 +        // assertEquals("Result is 400 CZK");
   5.140 +        result = c.convert("CZK", CurrencyValue.getCurrencyValue(500, "SKK"));
   5.141 +        assertEquals(CurrencyValue.getCurrencyValue(400, "CZK"), result);
   5.142 +    }
   5.143 +}