1 package org.apidesign.apifest08.currency;
3 import java.util.ArrayList;
4 import java.util.Collection;
6 import java.util.HashSet;
9 import org.apidesign.apifest08.currency.Computer.ComputerRequest;
10 import org.apidesign.apifest08.currency.Computer.ComputerResponse;
15 * In Task 1's version provides conversion between currency values
16 * with amount stored in integer or double, that are identified
17 * with string value. Exchange rates are immutable.
19 * In Task2's version provides support for multiple exchange rates
20 * between different currencies & merging exchange rates from
21 * existing convertors into new convertor's instance.
22 * No time for javadoc these features, sorry.
24 * In Task3's version supports reading of current exchange rates
25 * from data sources. Data sources are merged during convertors' merging
26 * as well as static exchange rates.
27 * No time for javadoc, again.
29 * In Task4's version takes into account validity range of data sources,
30 * can convert using an exchange rate value according to the specified instant
31 * of the time and provides a method for creating a new convertor with the same
32 * data sources as the old one, but with their validity ranges limited
33 * to the specified range.
34 * As usual, no time for javadoc.
38 public final class Convertor<AmountType, IdentifierType> {
40 Computer<AmountType> computer;
41 // each static exchange rate is a special case of an exchange rate data source
42 // historically separated
43 List<ExchangeRateDataSource<AmountType, IdentifierType>> staticExchangeRateDataSources =
44 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
45 List<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources =
46 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
51 Convertor(Computer<AmountType> computer) {
52 this.computer = computer;
55 void addExchangeRateDataSources(
56 List<ExchangeRateDataSource<AmountType, IdentifierType>> target,
57 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources) {
58 for (ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource : exchangeRateDataSources) {
59 if (isOverlappingExchangeRate(
60 exchangeRateDataSource.getCurrencyAIdentifier(),
61 exchangeRateDataSource.getCurrencyBIdentifier(),
62 exchangeRateDataSource.getValidFrom(),
63 exchangeRateDataSource.getValidTill())) {
64 throw new IllegalArgumentException("Duplicate exchange rate!");
66 target.add(exchangeRateDataSource);
70 ExchangeRateValue<AmountType, IdentifierType> findExchangeRate(
71 IdentifierType currencyA,
72 IdentifierType currencyB,
74 ExchangeRateValue<AmountType, IdentifierType> result = null;
75 result = findExchangeRateInternal(staticExchangeRateDataSources, currencyA, currencyB, instant);
79 result = findExchangeRateInternal(exchangeRateDataSources, currencyA, currencyB, instant);
83 ExchangeRateValue<AmountType, IdentifierType> findExchangeRateInternal(
84 List<ExchangeRateDataSource<AmountType, IdentifierType>> where,
85 IdentifierType currencyA,
86 IdentifierType currencyB,
88 for (ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource : where) {
89 if (((exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyA) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyB)) ||
90 (exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyB) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyA))) &&
91 DateUtil.isInRange(instant, exchangeRateDataSource.getValidFrom(), exchangeRateDataSource.getValidTill())) {
92 return exchangeRateDataSource.getExchangeRate();
98 boolean isOverlappingExchangeRate(
99 IdentifierType currencyA,
100 IdentifierType currencyB,
103 boolean result = false;
104 result = isOverlappingExchangeRateInternal(staticExchangeRateDataSources, currencyA, currencyB, from, to);
105 if (result == true) {
108 result = isOverlappingExchangeRateInternal(exchangeRateDataSources, currencyA, currencyB, from, to);
112 boolean isOverlappingExchangeRateInternal(
113 List<ExchangeRateDataSource<AmountType, IdentifierType>> where,
114 IdentifierType currencyA,
115 IdentifierType currencyB,
118 for (ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource : where) {
119 if (((exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyA) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyB)) ||
120 (exchangeRateDataSource.getCurrencyAIdentifier().equals(currencyB) && exchangeRateDataSource.getCurrencyBIdentifier().equals(currencyA))) &&
121 DateUtil.isRangesOverlapping(from, to, exchangeRateDataSource.getValidFrom(), exchangeRateDataSource.getValidTill())) {
129 * Convert an amount of the one currency to an amount of the another one currency
130 * with respect to previously specified exchange rates.
132 * @param targetCurrency an identifier of the requested currency
133 * @param currencyValue an amount of the another one currency
134 * @return an amount of the requested currency
136 public CurrencyValue<AmountType, IdentifierType> convert(
137 IdentifierType targetCurrency,
138 CurrencyValue<AmountType, IdentifierType> currencyValue) {
139 return convert(targetCurrency, currencyValue, new Date()); // System.currentTimeMillis()
142 public CurrencyValue<AmountType, IdentifierType> convert(
143 IdentifierType targetCurrency,
144 CurrencyValue<AmountType, IdentifierType> currencyValue,
146 ExchangeRateValue<AmountType, IdentifierType> exchangeRate =
147 findExchangeRate(currencyValue.getIdentifier(), targetCurrency, instant);
148 if (exchangeRate == null) {
149 throw new IllegalArgumentException("Inappropriate currencies to convert!");
152 ComputerRequest<AmountType> computerRequest = new ComputerRequest<AmountType>();
153 computerRequest.setInput(currencyValue.getAmount());
155 IdentifierType targetCurrencyRef; // just for backward compatibility :-(
156 if (exchangeRate.getCurrencyA().getIdentifier().equals(targetCurrency)) {
157 computerRequest.setInputCurrencyRatio(exchangeRate.getCurrencyB().getAmount());
158 computerRequest.setOutputCurrencyRatio(exchangeRate.getCurrencyA().getAmount());
159 targetCurrencyRef = exchangeRate.getCurrencyA().getIdentifier();
161 computerRequest.setInputCurrencyRatio(exchangeRate.getCurrencyA().getAmount());
162 computerRequest.setOutputCurrencyRatio(exchangeRate.getCurrencyB().getAmount());
163 targetCurrencyRef = exchangeRate.getCurrencyB().getIdentifier();
166 ComputerResponse<AmountType> computerResponse = new ComputerResponse<AmountType>();
167 computer.compute(computerRequest, computerResponse);
169 return CurrencyValue.getCurrencyValue(
170 computerResponse.getResult(),
177 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> limitDataSources(
178 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> source,
179 Date from, Date till) {
180 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> result =
181 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
183 for (ExchangeRateDataSource<AmountType, IdentifierType> dataSource : source) {
184 result.add(ExchangeRateDataSource.getExchangeRateDataSource(
185 dataSource.getCurrencyAIdentifier(), dataSource.getCurrencyBIdentifier(),
186 dataSource.getExchangeRateProvider(),
187 DateUtil.getRangesIntersectionBottom(dataSource.getValidFrom(), from),
188 DateUtil.getRangesIntersectionTop(dataSource.getValidTill(), till)));
194 public Convertor<AmountType, IdentifierType> limitConvertor(Date from, Date till) {
195 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> limitedStatic =
196 limitDataSources(staticExchangeRateDataSources, from, till);
197 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> limited =
198 limitDataSources(exchangeRateDataSources, from, till);
200 Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
201 c.addExchangeRateDataSources(c.staticExchangeRateDataSources, limitedStatic);
202 c.addExchangeRateDataSources(c.exchangeRateDataSources, limited);
209 static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> mergeConvertors(
210 Computer<AmountType> computer,
211 Collection<Convertor<AmountType, IdentifierType>> convertors) {
212 Set<ExchangeRateDataSource<AmountType, IdentifierType>> mergedStatic =
213 new HashSet<ExchangeRateDataSource<AmountType, IdentifierType>>();
214 Set<ExchangeRateDataSource<AmountType, IdentifierType>> merged =
215 new HashSet<ExchangeRateDataSource<AmountType, IdentifierType>>();
216 for (Convertor<AmountType, IdentifierType> convertor : convertors) {
217 mergedStatic.addAll(convertor.staticExchangeRateDataSources);
219 for (Convertor<AmountType, IdentifierType> convertor : convertors) {
220 merged.addAll(convertor.exchangeRateDataSources);
223 Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
224 c.addExchangeRateDataSources(c.staticExchangeRateDataSources, mergedStatic);
225 c.addExchangeRateDataSources(c.exchangeRateDataSources, merged);
229 static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> mergeConvertors(
230 Computer<AmountType> computer,
231 Convertor<AmountType, IdentifierType> convertorA,
232 Convertor<AmountType, IdentifierType> convertorB) {
233 Collection<Convertor<AmountType, IdentifierType>> convertors =
234 new ArrayList<Convertor<AmountType, IdentifierType>>();
235 convertors.add(convertorA);
236 convertors.add(convertorB);
237 return mergeConvertors(computer, convertors);
240 public static Convertor<Double, String> mergeConvertorsDoubleString(
241 Collection<Convertor<Double, String>> convertors) {
242 return mergeConvertors(DoubleComputer, convertors);
245 public static Convertor<Double, String> mergeConvertorsDoubleString(
246 Convertor<Double, String> convertorA,
247 Convertor<Double, String> convertorB) {
248 return mergeConvertors(DoubleComputer, convertorA, convertorB);
251 public static Convertor<Integer, String> mergeConvertorsIntegerString(
252 Collection<Convertor<Integer, String>> convertors) {
253 return mergeConvertors(IntegerComputer, convertors);
256 public static Convertor<Integer, String> mergeConvertorsIntegerString(
257 Convertor<Integer, String> convertorA,
258 Convertor<Integer, String> convertorB) {
259 return mergeConvertors(IntegerComputer, convertorA, convertorB);
265 static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertor(
266 Computer<AmountType> computer, Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates) {
267 Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
268 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources =
269 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
270 for (ExchangeRateValue<AmountType, IdentifierType> exchangeRate : exchangeRates) {
271 exchangeRateDataSources.add(
272 ExchangeRateDataSource.getExchangeRateDataSource(
273 exchangeRate.getCurrencyA().getIdentifier(), exchangeRate.getCurrencyB().getIdentifier(),
274 StaticExchangeRateProvider.getStaticExchangeRateProvider(exchangeRate)));
276 c.addExchangeRateDataSources(c.staticExchangeRateDataSources, exchangeRateDataSources);
280 static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertorDataSource(
281 Computer<AmountType> computer, Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources) {
282 Convertor<AmountType, IdentifierType> c = new Convertor<AmountType, IdentifierType>(computer);
283 c.addExchangeRateDataSources(c.exchangeRateDataSources, exchangeRateDataSources);
287 static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertor(
288 Computer<AmountType> computer, ExchangeRateValue<AmountType, IdentifierType> exchangeRate) {
289 Collection<ExchangeRateValue<AmountType, IdentifierType>> exchangeRates =
290 new ArrayList<ExchangeRateValue<AmountType, IdentifierType>>();
291 exchangeRates.add(exchangeRate);
292 return getConvertor(computer, exchangeRates);
295 static <AmountType, IdentifierType> Convertor<AmountType, IdentifierType> getConvertorDataSource(
296 Computer<AmountType> computer, ExchangeRateDataSource<AmountType, IdentifierType> exchangeRateDataSource) {
297 Collection<ExchangeRateDataSource<AmountType, IdentifierType>> exchangeRateDataSources =
298 new ArrayList<ExchangeRateDataSource<AmountType, IdentifierType>>();
299 exchangeRateDataSources.add(exchangeRateDataSource);
300 return getConvertorDataSource(computer, exchangeRateDataSources);
303 public static Convertor<Double, String> getConvertorDoubleString(
304 Collection<ExchangeRateValue<Double, String>> exchangeRates) {
305 return getConvertor(DoubleComputer, exchangeRates);
308 public static Convertor<Double, String> getConvertorDoubleString(
309 ExchangeRateValue<Double, String> exchangeRate) {
310 return getConvertor(DoubleComputer, exchangeRate);
313 public static Convertor<Double, String> getConvertorDataSourceDoubleString(
314 Collection<ExchangeRateDataSource<Double, String>> exchangeRateDataSources) {
315 return getConvertorDataSource(DoubleComputer, exchangeRateDataSources);
318 public static Convertor<Double, String> getConvertorDataSourceDoubleString(
319 ExchangeRateDataSource<Double, String> exchangeRateDataSource) {
320 return getConvertorDataSource(DoubleComputer, exchangeRateDataSource);
323 public static Convertor<Integer, String> getConvertorIntegerString(
324 Collection<ExchangeRateValue<Integer, String>> exchangeRates) {
325 return getConvertor(IntegerComputer, exchangeRates);
328 public static Convertor<Integer, String> getConvertorIntegerString(
329 ExchangeRateValue<Integer, String> exchangeRate) {
330 return getConvertor(IntegerComputer, exchangeRate);
333 public static Convertor<Integer, String> getConvertorDataSourceIntegerString(
334 Collection<ExchangeRateDataSource<Integer, String>> exchangeRateDataSources) {
335 return getConvertorDataSource(IntegerComputer, exchangeRateDataSources);
338 public static Convertor<Integer, String> getConvertorDataSourceIntegerString(
339 ExchangeRateDataSource<Integer, String> exchangeRateDataSource) {
340 return getConvertorDataSource(IntegerComputer, exchangeRateDataSource);
344 // BACKWARD COMPATIBILITY - CREATION
347 * Creates convertor for Double|String values with specified exchange rate
348 * between two currencies.
350 * @param firstCurrencyExchangeRate first currency
351 * @param secondCurrencyExchangeRate second currency
354 public static Convertor<Double, String> getConvertorDoubleString(
355 CurrencyValue<Double, String> firstCurrencyExchangeRate,
356 CurrencyValue<Double, String> secondCurrencyExchangeRate) {
357 return getConvertorDoubleString(ExchangeRateValue.getExchangeRate(firstCurrencyExchangeRate, secondCurrencyExchangeRate));
361 * Creates convertor for Integer|String values with specified exchange rate
362 * between two currencies.
364 * @param firstCurrencyExchangeRate first currency
365 * @param secondCurrencyExchangeRate second currency
368 public static Convertor<Integer, String> getConvertorIntegerString(
369 CurrencyValue<Integer, String> firstCurrencyExchangeRate,
370 CurrencyValue<Integer, String> secondCurrencyExchangeRate) {
371 return getConvertorIntegerString(ExchangeRateValue.getExchangeRate(firstCurrencyExchangeRate, secondCurrencyExchangeRate));
377 static final Computer<Double> DoubleComputer = new Computer<Double>() {
379 public void compute(ComputerRequest<Double> request, ComputerResponse<Double> response) {
380 response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());
383 static final Computer<Integer> IntegerComputer = new Computer<Integer>() {
385 public void compute(ComputerRequest<Integer> request, ComputerResponse<Integer> response) {
386 response.setResult(request.getInput() * request.getOutputCurrencyRatio() / request.getInputCurrencyRatio());