1 package org.apidesign.apifest08.currency;
3 import java.math.BigDecimal;
4 import java.math.MathContext;
5 import java.math.RoundingMode;
8 /** Convertor able to convert amount from one currency to other currency.
10 * Conversion method are:
12 * <li>{@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal)} - convert
13 * using exchange rate specified in exchange rate provide. This method is not try
14 * use reverted excahnage rate.
15 * <li>{@link #convertWithReversibleRates(ConvertorCurrency, ConvertorCurrency, BigDecimal)} -
16 * convert using exchange rate specified in exchange rate provide. This method
17 * is not trying to use reverted exchange rate.
20 * Exchange rate is provided by {@link ExchangeRateProvider}.
22 public class Convertor {
23 private Convertor[] convertors;
24 private IDateProviderEngine dateProvider;
25 boolean remainderAllowed = true; //if false, remained is not allowed (should be true ideally, but can't handle it now)
26 private ExchangeRateProvider exchangeRateProvider;
27 private Date fromDate;
30 /** Create new <code>Convertor</code> as merge of provided convertors. Merged convertor will use
31 * provided convertors to convert between currencies.
33 * Only one should be able to provide conversion between currencies. If more than one convertos
34 * are able to convert currency, one of conversions will be used (it is not defined which).
37 * @param convertors Convertor used to create merge-convertor.
38 * @return Returns new convertor instance.
40 public static Convertor createConvertorAsMerge(Convertor[] convertors) {
41 for (int i=0;i<convertors.length;i++) {
42 if (convertors[i]==null) {
43 throw new NullPointerException("Convertor at index "+i+" can't be null");
46 return new Convertor(convertors);
50 /** Create simle convertor.
53 setDateProvider(DateProvider.createCurrentDateProvider());
54 this.convertors=new Convertor[0];
57 /** Create merge convertor.
59 private Convertor(Convertor[] convertors) {
61 this.convertors = convertors;
65 * Create new <code>Convertor</code> using <code>ExchangeRateProvider</code>.
67 * @param exchangeRateProvider {@link ExchangeRateProvider} used to get exchange rate.
69 * @return Returns <code>Convertor</code> which can be used to convert money.
72 public static Convertor createConvertor(ExchangeRateProvider exchangeRateProvider) {
73 Convertor c = new Convertor();
75 c.exchangeRateProvider = exchangeRateProvider;
80 * Convert <code>amount</code> from <code>fromCurrency</code> to <code>toCurrency</code> as specified
81 * in <code>ExchangeRateProvider</code>.
83 * @param amount Amount which should be converted. Can't be negative value (can be zero or positive).
84 * @return Return <code>ConversionResult</code> which holds conversion result.
86 * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies.
88 public ConversionResult convert(BigDecimal amount) {
89 return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, false,false,null);
93 * Convert <code>amount</code> from <code>toCurrency</code> to <code>fromCurrency</code> as specified
94 * in <code>ExchangeRateProvider</code>. This is <em>reverted</em> order than suggested by names of currency fields in <code>ExchangeRate</code>.
96 * @param amount Amount which should be converted. Can't be negative value (can be zero or positive).
97 * @return Return <code>ConversionResult</code> which holds conversion result.
99 * @deprecated since version2. Use {@link #convert(ConvertorCurrency, ConvertorCurrency, BigDecimal) } - explicitly specify conversion currencies.
101 public ConversionResult convertBack(BigDecimal amount) {
102 return convertValue(exchangeRateProvider.getFromCurrency(), exchangeRateProvider.getToCurrency(),amount, true,false,null);
105 private ConversionResult convertUsingSimpleConvertor(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, boolean reversibleExRate, BigDecimal amount, boolean convertBack,Date date) throws ConversionNotSupportedException, RuntimeException {
107 date = dateProvider.getCurrentDate();
111 if (reversibleExRate) {
112 rate = exchangeRateProvider.getReversibleExchangeRate(fromCurrency, toCurrency, date);
114 rate = exchangeRateProvider.getExchangeRate(fromCurrency, toCurrency, date);
119 ConversionResult result = new ConversionResult();
121 int fromFranctionDigits = fromCurrency.getDefaultFractionDigits();
122 int toFractionDigits = toCurrency.getDefaultFractionDigits();
124 int usedFractionDigits = Math.max(fromFranctionDigits, toFractionDigits);
126 //if (toFractionDigits != 2) {
127 // throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + toCurrency + " has " + toFractionDigits + " defaultFractionDigits");
129 //if (fromFranctionDigits != 2) {
130 // throw new ConvertorException("Can't process currency with defaultFractionDigits!=2, " + fromCurrency + " has " + fromFranctionDigits + " defaultFractionDigits");
133 if (amount.signum() == -1) {
134 throw new RuntimeException("Can convert only non-negative value, current value is " + amount);
138 MathContext context = new MathContext(0, RoundingMode.DOWN);
143 //converting in reverted way
144 to = rate.getFromValue();
145 from = rate.getToValue();
147 //converting in normal way
148 from = rate.getFromValue();
149 to = rate.getToValue();
152 BigDecimal amountCent = amount.movePointRight(usedFractionDigits);
154 final BigDecimal multiplied = amountCent.multiply(to, context);
155 BigDecimal[] division = multiplied.divideAndRemainder(from, context);
157 if (!remainderAllowed && !(BigDecimal.ZERO.equals(division[1]))) {
158 throw new RuntimeException("Remained is not allowed - remaining amount is " + division[1] + " cents");
160 result.setRemainder(BigDecimal.ZERO.setScale(fromFranctionDigits));
163 BigDecimal converted = division[0].movePointLeft(usedFractionDigits);
164 converted = converted.setScale(toFractionDigits,RoundingMode.DOWN); //XXX ugly
165 result.setConverted(converted);
166 BigDecimal[] convertedInBase = converted.multiply(from).divideAndRemainder(to);
167 BigDecimal x2 = convertedInBase[0].add(convertedInBase[1]);
168 BigDecimal remainder = amount.subtract(x2);
169 result.setRemainder(remainder);
170 //System.out.println("ConvertedInBase="+Arrays.asList(convertedInBase)+":"+x2 +" ("+amount+") ["+converted+"] <"+from+","+to+"> ->"+remainder);
175 private ConversionResult convertValue(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency,BigDecimal amount, boolean convertBack,boolean reversibleExRate,Date date) throws RuntimeException {
176 //result.setRemainder(...);
177 if (!dateIsInLimit(date)) {
180 if (convertors.length==0) {
181 return convertUsingSimpleConvertor(fromCurrency, toCurrency, reversibleExRate, amount, convertBack,date);
183 ConversionResult result = null;
184 for (int i = 0;i<convertors.length;i++) {
185 Convertor subConvertor = convertors[i];
186 result = subConvertor.convertValue(fromCurrency, toCurrency, amount, convertBack, reversibleExRate,date);
196 * Convert <code>value</code> from <code>fromCurrency</code> to <code>toCurrency</code>.
198 * Exchange rate is provided by exchange rate provider which was specified when Convertor was created.
199 * This method is using only exchange rate from->to and not trying to use reverted excange rate to->from.
201 * This method is using date from <code>IDateProviderEngine</code> to get exchange rate (see {@link #getDateProvider() }.
203 * @param fromCurrency Source currency to convert from.
204 * @param toCurrency Target currency to convert to.
205 * @param value Value in source currency which should be converted.
206 * @return Return conversion result.
208 * @throws ConversionNotSupportedException If conversion from <code>fromCurrency</code> to <code>toCurrency</code> is not supported.
210 public ConversionResult convert(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) {
211 Date defaultDate = dateProvider.getCurrentDate();
212 ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,false,defaultDate);
214 //throw new ConversionNotSupportedException("Conversion from " + fromCurrency + " to " + toCurrency + " is not supported");
215 throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),false);
220 * Same as {@link #convert(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)} but using provided rate instead of
221 * <code>IDateProviderEngine</code>.
223 * @param fromCurrency Source currency to convert from.
224 * @param toCurrency Target currency to convert to.
225 * @param value Value in source currency which should be converted.
226 * @return Return conversion result.
227 * @param date Conversion date
228 * @return Return conversion result.
230 public ConversionResult convert(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value, Date date) {
231 ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,false,date);
233 throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),false);
239 * Convert <code>value</code> from <code>fromCurrency</code> to <code>toCurrency</code>.
240 * Exchange rate is provided by exchange rate provider which was specified when Convertor was created.
242 * This method is using only exchange rate from->to and if not found, it is trying to use reverted excange rate to->from.
244 * This method is using date from <code>IDateProviderEngine</code> to get exchange rate (see {@link #getDateProvider() }.
246 * @param fromCurrency Source currency to convert from.
247 * @param toCurrency Target currency to convert to.
248 * @param value Value in source currency which should be converted.
249 * @return Return conversion result.
251 * @throws ConversionNotSupportedException If conversion from <code>fromCurrency</code> to <code>toCurrency</code>
252 * is not supported and neither conversion from <code>toCurrency</code> to <code>fromCurrency</code> is not supported.
254 public ConversionResult convertWithReversibleRates(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value) {
255 Date defaultDate = dateProvider.getCurrentDate();
256 ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,true,defaultDate);
258 //throw new ConversionNotSupportedException("Neither onversion nor reverted conversion from " + fromCurrency + " to " + toCurrency + " is not supported,");
259 throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),true);
265 * Same as {@link #convertWithReversibleRates(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)} but using provided rate instead of
266 * <code>IDateProviderEngine</code>.
268 * @param fromCurrency Source currency to convert from.
269 * @param toCurrency Target currency to convert to.
270 * @param value Value in source currency which should be converted.
271 * @return Return conversion result.
272 * @param date Conversion date
273 * @return Return conversion result.
275 public ConversionResult convertWithReversibleRates(ConvertorCurrency fromCurrency, ConvertorCurrency toCurrency, BigDecimal value, Date date) {
276 ConversionResult result = convertValue(fromCurrency, toCurrency, value, false,true,date);
278 throw new ConversionNotSupportedException(fromCurrency.getCurrencyCode(),toCurrency.getCurrencyCode(),true);
283 private boolean dateIsInLimit(Date date) {
288 if (fromDate == null && toDate == null) {
290 } else if (fromDate.getTime()<=date.getTime() && date.getTime()<=toDate.getTime()) {
300 public void limitAllowedDates(Date from, Date till) {
302 throw new NullPointerException("from Date can't be null");
305 throw new NullPointerException("till Date can't be null");
307 if (from.getTime()>till.getTime()) {
308 throw new IllegalArgumentException("From date "+from+" must be before tii date "+till);
310 this.fromDate = from;
315 /** Return current date provider.
316 * @return Returns current date provider.
318 public IDateProviderEngine getDateProvider() {
324 * Set date provider. Date provider is used to get "current date". Current date
325 * is used by methods {@link #convert(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)} and
326 * {@link #convertWithReversibleRates(ConvertorCurrency, ConvertorCurrency, java.math.BigDecimal)}.
328 * @param dateProvider Date provider which should be used by Convertor.
330 public void setDateProvider(IDateProviderEngine dateProvider) {
331 this.dateProvider = dateProvider;