1 package org.apidesign.apifest08.currency;
3 import static org.apidesign.apifest08.currency.Assert.notNull;
5 import java.math.BigDecimal;
6 import java.math.RoundingMode;
7 import java.util.ArrayList;
8 import java.util.Calendar;
9 import java.util.Currency;
10 import java.util.Date;
11 import java.util.GregorianCalendar;
12 import java.util.List;
13 import java.util.TimeZone;
18 public final class Convertor {
20 private List<ConvertorDelegate> convertorDelegates = new ArrayList<ConvertorDelegate>();
24 * Create new instance of the converter for the given currencies and its rate.
26 * @param rateValue the rate between the first and the second currency
27 * @param currencyFirst the first currency
28 * @param currencySecond the second currency
30 public Convertor(BigDecimal rateValue, Currency currencyFirst, Currency currencySecond) {
31 notNull(currencyFirst, "currencyFirst");
32 notNull(currencySecond, "currencySecond");
33 notNull(rateValue, "rateValue");
34 convertorDelegates.add(new ConvertorDelegate(new StaticRateProvider(rateValue), currencyFirst, currencySecond, null, null));
38 * Create new instance of the converter for the given currencies and its rate.
39 * A rate value is provided by {@link RateProvider}.
41 * @param rateProvider the rate provider
42 * @param currencyFirst the first currency
43 * @param currencySecond the second currency
45 public Convertor(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond) {
46 notNull(currencyFirst, "currencyFirst");
47 notNull(currencySecond, "currencySecond");
48 notNull(rateProvider, "rateProvider");
49 convertorDelegates.add(new ConvertorDelegate(rateProvider, currencyFirst, currencySecond, null, null));
53 * Create new instance of the convertor. The associated rate(s) is timely limited
54 * by a given 'from' and 'till'.
59 public Convertor(Convertor convertor, Date from, Date till) {
60 notNull(convertor, "convertor");
61 notNull(from, "from");
62 notNull(till, "till");
63 for(ConvertorDelegate delegate: convertor.convertorDelegates) {
64 convertorDelegates.add(new ConvertorDelegate(delegate.rateProvider, delegate.first, delegate.second, from, till));
69 * Create new instance of the convertor from the given convertors.
70 * @param convertors the convertors
72 public Convertor(Convertor... convertors) {
73 notNull(convertors, "convertors");
74 if(convertors.length == 0) {
75 throw new IllegalArgumentException("There must be at least one converter.");
78 for(Convertor convertor: convertors) {
79 if(convertor != null) {
80 for(ConvertorDelegate delegate: convertor.convertorDelegates) {
81 convertorDelegates.add(new ConvertorDelegate(delegate.rateProvider, delegate.first, delegate.second, delegate.from, delegate.till));
88 * Converts an amount value between the two currencies of this converter. The rate is taken
91 * @param amount an amount
92 * @param fromCurrency an amount currency
93 * @param toCurrency to a target currency
94 * @return a converted amount value
96 * @throws ConversionException if the conversion fails
97 * @throws UnsupportedConversionException if the conversion between a given currencies is not supported.
99 public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
100 return convert(amount, fromCurrency, toCurrency, new Date(System.currentTimeMillis()));
104 * Converts an amount value between the two currencies of this converter and its
105 * associated rate for a given time.
107 * @param amount an amount
108 * @param fromCurrency an amount currency
109 * @param toCurrency to a target currency
111 * @return a converted amount value
113 * @throws ConversionException if the conversion fails
114 * @throws UnsupportedConversionException if the conversion between a given currencies and time is not supported.
116 public Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency, Date time) throws ConversionException {
117 notNull(amount, "amount");
118 notNull(fromCurrency, "fromCurrency");
119 notNull(toCurrency, "toCurrency");
120 notNull(time, "time");
122 ConvertorDelegate appropriateDelegate = null;
123 //try find an appropriate delegate for conversion
124 for(ConvertorDelegate delegate : convertorDelegates) {
125 if(delegate.isConversionSupported(fromCurrency, toCurrency) && delegate.isConversionSupported(time)) {
126 appropriateDelegate = delegate;
129 if(appropriateDelegate == null) {
130 throw new UnsupportedConversionException(fromCurrency, toCurrency, time);
133 return appropriateDelegate.convert(amount, fromCurrency, toCurrency);
137 * Internal delegate implements a logic for conversion between two currencies
138 * and vice versa. There could be time limitation for the associated rate.
140 * @see #isConversionSupported(Currency, Currency)
142 private static class ConvertorDelegate {
144 private final Currency first;
145 private final Currency second;
146 private final RateProvider rateProvider;
147 private final Date from;
148 private final Date till;
149 public static final BigDecimal one = new BigDecimal(1);
152 private ConvertorDelegate(RateProvider rateProvider, Currency currencyFirst, Currency currencySecond, Date from, Date till) {
153 this.rateProvider = rateProvider;
154 this.first = currencyFirst;
155 this.second = currencySecond;
160 private Amount convert(BigDecimal amount, Currency fromCurrency, Currency toCurrency) throws ConversionException {
161 BigDecimal rateValue = getRateValue(fromCurrency, toCurrency);
162 BigDecimal result = rateValue.multiply(amount);
163 return new Amount(result, toCurrency);
166 private BigDecimal getRateValue(Currency fromCurrency, Currency toCurrency) {
170 if(first == fromCurrency) {
171 BigDecimal rateValue = rateProvider.getRate();
172 if(rateValue == null) {
173 throw new NullPointerException("Rate cannot be null!");
177 BigDecimal rateValue = rateProvider.getRate();
178 if(rateValue == null) {
179 throw new NullPointerException("Rate cannot be null!");
182 retVal = one.divide(rateValue, 10 ,RoundingMode.HALF_UP);
189 * @return <code>true</code> if the delegate is able to convert from the given currency
190 * to the given currency and vice versa otherwise <code>false</code>.
192 private boolean isConversionSupported(Currency fromCurrency, Currency toCurrency) {
193 return ((fromCurrency == first || fromCurrency == second) && (toCurrency == first || toCurrency == second));
197 * @return <code>true</code> if the delegate is able to convert in a given
200 private boolean isConversionSupported(Date time) {
202 if(this.from != null && this.till != null) {
203 retVal = getDateInGMT(from).getTime() <= getDateInGMT(time).getTime() && getDateInGMT(till).getTime() >= getDateInGMT(time).getTime();
205 retVal = true; //delegate is applicable "for eternity"
210 private Date getDateInGMT(Date currentDate) {
211 TimeZone tz = TimeZone.getTimeZone("GMT");
213 Calendar mbCal = new GregorianCalendar(tz);
214 mbCal.setTimeInMillis(currentDate.getTime());
216 Calendar cal = Calendar.getInstance();
217 cal.set(Calendar.YEAR, mbCal.get(Calendar.YEAR));
218 cal.set(Calendar.MONTH, mbCal.get(Calendar.MONTH));
219 cal.set(Calendar.DAY_OF_MONTH, mbCal.get(Calendar.DAY_OF_MONTH));
220 cal.set(Calendar.HOUR_OF_DAY, mbCal.get(Calendar.HOUR_OF_DAY));
221 cal.set(Calendar.MINUTE, mbCal.get(Calendar.MINUTE));
222 cal.set(Calendar.SECOND, mbCal.get(Calendar.SECOND));
223 cal.set(Calendar.MILLISECOND, mbCal.get(Calendar.MILLISECOND));
225 return cal.getTime();
230 * A rate provider. This class represents a way how could be "static" convertor
231 * extended in order converts according to current rate.
233 public static abstract class RateProvider {
236 * @return a rate between the from currency and the to currency associated with
239 public abstract BigDecimal getRate();
242 private static class StaticRateProvider extends RateProvider{
243 private final BigDecimal rateValue;
245 private StaticRateProvider(BigDecimal rateValue){
246 this.rateValue = rateValue;
249 public BigDecimal getRate() {
250 return this.rateValue;