task2/solution13/src/org/apidesign/apifest08/currency/Convertor.java
author japod@localhost
Tue, 07 Oct 2008 01:18:23 +0200
changeset 41 a7e6f84fb078
parent 29 f6073056b9fe
permissions -rw-r--r--
adding solution13 for task2
     1 package org.apidesign.apifest08.currency;
     2 
     3 import java.math.BigDecimal;
     4 import java.math.MathContext;
     5 import java.math.RoundingMode;
     6 
     7 /** Convertor able to convert amount from one currency to other currency.
     8  * <p>
     9  * Conversion method are:
    10  * <ul>
    11  * <li>{@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal)} - convert using exchange rate specified in exchange rate provide. <em>Do not try use reverted excahnage rate</em>. 
    12  * <li>{@link #convertWithReversibleRates(ConvertorCurrency, ConvertorCurrency, BigDecimal)} - convert using exchange rate specified in exchange rate provide. <em>Try use reverted excahnage rate</em>. 
    13  * </ul>
    14  * 
    15  * Exchange rate is provided by {@link ExchangeRateProvider}.
    16  */
    17 public class Convertor {
    18     private Convertor[] convertors;
    19 
    20     /** Create new <code>Convertor</code> as merge of provided convertors. Merged convertor will use
    21      * provided convertors to convert between currencies.
    22      * <p>
    23      * Only one should be able to provide conversion between currencies. If more than one convertos
    24      * are able to convert currency, one of conversions will be used (it is not defined which).
    25      * 
    26      * @since version 2
    27      * @param convertors Convertor used to create merge-convertor.
    28      * @return Returns new convertor instance.
    29      */
    30     public static Convertor createConvertorAsMerge(Convertor[] convertors) {
    31         return new Convertor(convertors);
    32     }
    33     
    34     boolean remainderAllowed = true; //if false, remained is not allowed (should be true ideally, but can't handle it now)
    35     ExchangeRateProvider exchangeRateProvider; 
    36     
    37     /** Create simle convertor.
    38      */
    39     private Convertor() {
    40         this.convertors=new Convertor[0];
    41     }
    42     
    43     /** Create merge convertor.
    44      */            
    45     private Convertor(Convertor[] convertors) {
    46         this.convertors = convertors;       
    47     }
    48     
    49     /**
    50      * Create new <code>Convertor</code> using <code>ExchangeRateProvider</code>.
    51      * 
    52      * @param exchangeRateProvider {@link ExchangeRateProvider} used to get exchange rate.
    53      * 
    54      * @return Returns <code>Convertor</code> which can be used to convert money.
    55      * @since version1
    56      */
    57     public static Convertor createConvertor(ExchangeRateProvider exchangeRateProvider) {
    58         Convertor c = new Convertor();
    59 
    60         c.exchangeRateProvider = exchangeRateProvider;
    61         return c;
    62     }
    63     
    64     /**
    65      * Convert <code>amount</code> from <code>fromCurrency</code> to <code>toCurrency</code> as specified
    66      * in <code>ExchangeRateProvider</code>.
    67      * 
    68      * @param amount Amount which should be converted. Can't be negative value (can be zero or positive).
    69      * @return Return <code>ConversionResult</code> which holds conversion result.
    70      * @since version1
    71      * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies.
    72      */
    73     public ConversionResult convert(BigDecimal amount) {
    74         return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, false,false);
    75     }
    76     
    77     /**
    78      * Convert <code>amount</code> from <code>toCurrency</code> to <code>fromCurrency</code> as specified
    79      * in <code>ExchangeRateProvider</code>. This is <em>reverted</em> order than suggested by  names of currency fields in <code>ExchangeRate</code>.
    80      * 
    81      * @param amount Amount which should be converted. Can't be negative value (can be zero or positive).
    82      * @return Return <code>ConversionResult</code> which holds conversion result.
    83      * @since version1
    84      * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies.
    85      */
    86     public ConversionResult convertBack(BigDecimal amount) {
    87         return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, true,false);
    88     }
    89 
    90     private ConversionResult convertUsingSimpleConvertor(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, boolean reversibleExRate, BigDecimal amount, boolean convertBack) throws ConversionNotSupportedException, RuntimeException {
    91         ConversionResult result = new ConversionResult();
    92 
    93         //ExchangeRate rate = exchangeRateProvider.getExchangeRate();
    94         ExchangeRate rate;
    95         if (reversibleExRate) {
    96             rate = exchangeRateProvider.getReversibleExchangeRate(fromCurrency, toCurrency);
    97         } else {
    98             rate = exchangeRateProvider.getExchangeRate(fromCurrency, toCurrency);
    99         }
   100         if (rate == null) {
   101             return null;
   102         }
   103         
   104         int fromFranctionDigits = fromCurrency.getDefaultFractionDigits();
   105         int toFractionDigits = toCurrency.getDefaultFractionDigits();
   106 
   107         if (toFractionDigits != 2) {
   108             throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + exchangeRateProvider.getToCurrency() + " has " + toFractionDigits + " defaultFractionDigits");
   109         }
   110         if (fromFranctionDigits != 2) {
   111             throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + exchangeRateProvider.getFromCurrency() + " has " + fromFranctionDigits + " defaultFractionDigits");
   112         }
   113 
   114         if (amount.signum() == -1) {
   115             throw new RuntimeException("Can convert only non-negative value, current value is " + amount);
   116         }
   117 
   118 
   119         MathContext context = new MathContext(0, RoundingMode.DOWN);
   120 
   121         BigDecimal from;
   122         BigDecimal to;
   123         if (convertBack) {
   124             //converting in reverted way
   125             to = rate.getFromValue();
   126             from = rate.getToValue();
   127         } else {
   128             //converting in normal way
   129             from = rate.getFromValue();
   130             to = rate.getToValue();
   131         }
   132 
   133         BigDecimal amountCent = amount.movePointRight(2);
   134 
   135         final BigDecimal multiplied = amountCent.multiply(to, context);
   136         BigDecimal[] division = multiplied.divideAndRemainder(from, context);
   137 
   138         if (!remainderAllowed && !(BigDecimal.ZERO.equals(division[1]))) {
   139             throw new RuntimeException("Remained is not allowed - remaining amount is " + division[1] + " cents");
   140         } else {
   141             result.setRemainder(BigDecimal.ZERO);
   142         }
   143 
   144         final BigDecimal converted = division[0].movePointLeft(2);
   145         result.setConverted(converted);
   146         //result.setRemainder(...);
   147         return result;
   148     }
   149     
   150 
   151     private ConversionResult convertValue(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency,BigDecimal amount, boolean convertBack,boolean reversibleExRate) throws RuntimeException {
   152         //result.setRemainder(...);
   153         if (convertors.length==0) {
   154             return convertUsingSimpleConvertor(fromCurrency, toCurrency, reversibleExRate, amount, convertBack);
   155         } else {
   156             ConversionResult result = null;
   157             for (int i = 0;i<convertors.length;i++) {
   158                 Convertor subConvertor = convertors[i];
   159                 result = subConvertor.convertValue(fromCurrency, toCurrency, amount, convertBack, reversibleExRate);
   160                 if (result!=null) {
   161                     break;
   162                 }
   163             }
   164             return result;
   165         }
   166     }
   167     
   168     /**
   169      * Convert <code>value</code> from <code>fromCurrency</code> to <code>toCurrency</code>.
   170      * <p>
   171      * Exchange rate is provided by exchange rate provider which was specified when Convertor was created.
   172      * This method is using only exchange rate from->to and not trying to use reverted excange rate to->from.
   173      * 
   174      * @param fromCurrency Source currency to convert from.
   175      * @param toCurrency Target currency to convert to.
   176      * @param value Value in source currency which should be converted.
   177      * @return Return conversion result.
   178      * @since version2
   179      * @throws ConversionNotSupportedException If conversion from <code>fromCurrency</code> to <code>toCurrency</code> is not supported.
   180      */
   181     public ConversionResult convert(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) {
   182         ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,false);
   183         if (result==null) {
   184             //throw new ConversionNotSupportedException("Conversion from " + fromCurrency + " to " + toCurrency + " is not supported");
   185             throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),false);
   186         }
   187         return result;
   188     }
   189     
   190     /**
   191      * Convert <code>value</code> from <code>fromCurrency</code> to <code>toCurrency</code>.
   192      * Exchange rate is provided by exchange rate provider which was specified when Convertor was created.
   193      * <p>
   194      *  This method is using only exchange rate from->to and if not found, it is trying to use reverted excange rate to->from.
   195      * 
   196      * @param fromCurrency Source currency to convert from.
   197      * @param toCurrency Target currency to convert to.
   198      * @param value Value in source currency which should be converted.
   199      * @return Return conversion result.
   200      * @since version2
   201      * @throws ConversionNotSupportedException If conversion from <code>fromCurrency</code> to <code>toCurrency</code> 
   202      *         is not supported and neither conversion from <code>toCurrency</code> to <code>fromCurrency</code> is not supported.
   203      */
   204     public ConversionResult convertWithReversibleRates(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) {
   205         ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,true);
   206         if (result==null) {
   207             //throw new ConversionNotSupportedException("Neither onversion nor reverted conversion from " + fromCurrency + " to " + toCurrency + "  is not supported,");
   208             throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),true);
   209         }
   210         return result;
   211     }
   212     
   213 }