japod@23: package org.apidesign.apifest08.currency; japod@23: japod@23: import java.math.BigDecimal; japod@23: import java.math.MathContext; japod@23: import java.math.RoundingMode; japod@23: japod@23: /** Convertor able to convert amount from one currency to other currency. japod@41: *

japod@41: * Conversion method are: japod@41: *

japod@23: * japod@23: * Exchange rate is provided by {@link ExchangeRateProvider}. japod@23: */ japod@23: public class Convertor { japod@41: private Convertor[] convertors; japod@41: japod@41: /** Create new Convertor as merge of provided convertors. Merged convertor will use japod@41: * provided convertors to convert between currencies. japod@41: *

japod@41: * Only one should be able to provide conversion between currencies. If more than one convertos japod@41: * are able to convert currency, one of conversions will be used (it is not defined which). japod@41: * japod@41: * @since version 2 japod@41: * @param convertors Convertor used to create merge-convertor. japod@41: * @return Returns new convertor instance. japod@41: */ japod@41: public static Convertor createConvertorAsMerge(Convertor[] convertors) { japod@41: return new Convertor(convertors); japod@41: } japod@41: japod@41: boolean remainderAllowed = true; //if false, remained is not allowed (should be true ideally, but can't handle it now) japod@23: ExchangeRateProvider exchangeRateProvider; japod@23: japod@41: /** Create simle convertor. japod@41: */ japod@23: private Convertor() { japod@41: this.convertors=new Convertor[0]; japod@41: } japod@41: japod@41: /** Create merge convertor. japod@41: */ japod@41: private Convertor(Convertor[] convertors) { japod@41: this.convertors = convertors; japod@23: } japod@23: japod@23: /** japod@41: * Create new Convertor using ExchangeRateProvider. japod@23: * japod@23: * @param exchangeRateProvider {@link ExchangeRateProvider} used to get exchange rate. japod@23: * japod@23: * @return Returns Convertor which can be used to convert money. japod@41: * @since version1 japod@23: */ japod@23: public static Convertor createConvertor(ExchangeRateProvider exchangeRateProvider) { japod@23: Convertor c = new Convertor(); japod@23: japod@23: c.exchangeRateProvider = exchangeRateProvider; japod@23: return c; japod@23: } japod@23: japod@23: /** japod@23: * Convert amount from fromCurrency to toCurrency as specified japod@23: * in ExchangeRateProvider. japod@23: * japod@23: * @param amount Amount which should be converted. Can't be negative value (can be zero or positive). japod@23: * @return Return ConversionResult which holds conversion result. japod@41: * @since version1 japod@41: * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies. japod@23: */ japod@23: public ConversionResult convert(BigDecimal amount) { japod@41: return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, false,false); japod@23: } japod@23: japod@23: /** japod@23: * Convert amount from toCurrency to fromCurrency as specified japod@23: * in ExchangeRateProvider. This is reverted order than suggested by names of currency fields in ExchangeRate. japod@23: * japod@23: * @param amount Amount which should be converted. Can't be negative value (can be zero or positive). japod@23: * @return Return ConversionResult which holds conversion result. japod@41: * @since version1 japod@41: * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies. japod@23: */ japod@23: public ConversionResult convertBack(BigDecimal amount) { japod@41: return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, true,false); japod@23: } japod@23: japod@41: private ConversionResult convertUsingSimpleConvertor(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, boolean reversibleExRate, BigDecimal amount, boolean convertBack) throws ConversionNotSupportedException, RuntimeException { japod@23: ConversionResult result = new ConversionResult(); japod@41: japod@41: //ExchangeRate rate = exchangeRateProvider.getExchangeRate(); japod@41: ExchangeRate rate; japod@41: if (reversibleExRate) { japod@41: rate = exchangeRateProvider.getReversibleExchangeRate(fromCurrency, toCurrency); japod@41: } else { japod@41: rate = exchangeRateProvider.getExchangeRate(fromCurrency, toCurrency); japod@23: } japod@41: if (rate == null) { japod@41: return null; japod@23: } japod@23: japod@41: int fromFranctionDigits = fromCurrency.getDefaultFractionDigits(); japod@41: int toFractionDigits = toCurrency.getDefaultFractionDigits(); japod@41: japod@41: if (toFractionDigits != 2) { japod@41: throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + exchangeRateProvider.getToCurrency() + " has " + toFractionDigits + " defaultFractionDigits"); japod@23: } japod@41: if (fromFranctionDigits != 2) { japod@41: throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + exchangeRateProvider.getFromCurrency() + " has " + fromFranctionDigits + " defaultFractionDigits"); japod@41: } japod@41: japod@41: if (amount.signum() == -1) { japod@41: throw new RuntimeException("Can convert only non-negative value, current value is " + amount); japod@41: } japod@41: japod@41: japod@23: MathContext context = new MathContext(0, RoundingMode.DOWN); japod@41: japod@23: BigDecimal from; japod@23: BigDecimal to; japod@23: if (convertBack) { japod@23: //converting in reverted way japod@23: to = rate.getFromValue(); japod@23: from = rate.getToValue(); japod@23: } else { japod@41: //converting in normal way japod@23: from = rate.getFromValue(); japod@23: to = rate.getToValue(); japod@23: } japod@23: japod@23: BigDecimal amountCent = amount.movePointRight(2); japod@41: japod@41: final BigDecimal multiplied = amountCent.multiply(to, context); japod@41: BigDecimal[] division = multiplied.divideAndRemainder(from, context); japod@23: japod@23: if (!remainderAllowed && !(BigDecimal.ZERO.equals(division[1]))) { japod@41: throw new RuntimeException("Remained is not allowed - remaining amount is " + division[1] + " cents"); japod@23: } else { japod@23: result.setRemainder(BigDecimal.ZERO); japod@23: } japod@41: japod@41: final BigDecimal converted = division[0].movePointLeft(2); japod@23: result.setConverted(converted); japod@23: //result.setRemainder(...); japod@23: return result; japod@23: } japod@41: japod@41: japod@41: private ConversionResult convertValue(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency,BigDecimal amount, boolean convertBack,boolean reversibleExRate) throws RuntimeException { japod@41: //result.setRemainder(...); japod@41: if (convertors.length==0) { japod@41: return convertUsingSimpleConvertor(fromCurrency, toCurrency, reversibleExRate, amount, convertBack); japod@41: } else { japod@41: ConversionResult result = null; japod@41: for (int i = 0;ivalue from fromCurrency to toCurrency. japod@41: *

japod@41: * Exchange rate is provided by exchange rate provider which was specified when Convertor was created. japod@41: * This method is using only exchange rate from->to and not trying to use reverted excange rate to->from. japod@41: * japod@41: * @param fromCurrency Source currency to convert from. japod@41: * @param toCurrency Target currency to convert to. japod@41: * @param value Value in source currency which should be converted. japod@41: * @return Return conversion result. japod@41: * @since version2 japod@41: * @throws ConversionNotSupportedException If conversion from fromCurrency to toCurrency is not supported. japod@41: */ japod@41: public ConversionResult convert(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) { japod@41: ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,false); japod@41: if (result==null) { japod@41: //throw new ConversionNotSupportedException("Conversion from " + fromCurrency + " to " + toCurrency + " is not supported"); japod@41: throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),false); japod@41: } japod@41: return result; japod@41: } japod@41: japod@41: /** japod@41: * Convert value from fromCurrency to toCurrency. japod@41: * Exchange rate is provided by exchange rate provider which was specified when Convertor was created. japod@41: *

japod@41: * This method is using only exchange rate from->to and if not found, it is trying to use reverted excange rate to->from. japod@41: * japod@41: * @param fromCurrency Source currency to convert from. japod@41: * @param toCurrency Target currency to convert to. japod@41: * @param value Value in source currency which should be converted. japod@41: * @return Return conversion result. japod@41: * @since version2 japod@41: * @throws ConversionNotSupportedException If conversion from fromCurrency to toCurrency japod@41: * is not supported and neither conversion from toCurrency to fromCurrency is not supported. japod@41: */ japod@41: public ConversionResult convertWithReversibleRates(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) { japod@41: ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,true); japod@41: if (result==null) { japod@41: //throw new ConversionNotSupportedException("Neither onversion nor reverted conversion from " + fromCurrency + " to " + toCurrency + " is not supported,"); japod@41: throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),true); japod@41: } japod@41: return result; japod@41: } japod@41: japod@23: }