japod@35
|
1 |
package org.apidesign.apifest08.currency;
|
japod@35
|
2 |
|
japod@35
|
3 |
|
japod@35
|
4 |
import java.math.BigDecimal;
|
japod@35
|
5 |
import java.math.RoundingMode;
|
japod@35
|
6 |
import java.util.Collections;
|
japod@35
|
7 |
import java.util.Currency;
|
japod@35
|
8 |
import java.util.HashMap;
|
japod@35
|
9 |
import java.util.HashSet;
|
japod@35
|
10 |
import java.util.Map;
|
japod@35
|
11 |
import java.util.Set;
|
japod@35
|
12 |
|
japod@35
|
13 |
|
japod@35
|
14 |
/**
|
japod@35
|
15 |
* A composite convertor allows conversions between many currencies by forwarding conversion requests to stored convertors.
|
japod@35
|
16 |
* A composite convertor will build all possible conversions that are allowed by the underlying set of convertors.
|
japod@35
|
17 |
*
|
japod@35
|
18 |
* @author D'Arcy Smith
|
japod@55
|
19 |
* @verson 1.1
|
japod@35
|
20 |
*/
|
japod@35
|
21 |
final class CompositeConvertorImpl
|
japod@35
|
22 |
implements Convertor
|
japod@35
|
23 |
{
|
japod@35
|
24 |
/**
|
japod@35
|
25 |
* The convertors that are supported.
|
japod@35
|
26 |
*/
|
japod@35
|
27 |
private final Convertor[] convertors;
|
japod@35
|
28 |
|
japod@35
|
29 |
/**
|
japod@35
|
30 |
* Keeps track of what convertors to use to convert between currencies.
|
japod@35
|
31 |
*/
|
japod@35
|
32 |
private final Map<Currency, Map<Currency, Convertor>> possibleConversions;
|
japod@35
|
33 |
|
japod@35
|
34 |
{
|
japod@35
|
35 |
possibleConversions = new HashMap<Currency, Map<Currency, Convertor>>();
|
japod@35
|
36 |
}
|
japod@35
|
37 |
|
japod@35
|
38 |
/**
|
japod@35
|
39 |
* Construct a ComositeConvertorImpl with the specified convertors.
|
japod@35
|
40 |
* This will result in all possible conversions between the supplied currencies being made.
|
japod@35
|
41 |
*
|
japod@35
|
42 |
* @param cs the convertors to use.
|
japod@35
|
43 |
* @throws IllegalArgumentException if any of the items in cs are null.
|
japod@35
|
44 |
*/
|
japod@35
|
45 |
CompositeConvertorImpl(Convertor ... cs)
|
japod@35
|
46 |
{
|
japod@35
|
47 |
int newConvertors;
|
japod@35
|
48 |
|
japod@35
|
49 |
convertors = cs;
|
japod@35
|
50 |
|
japod@35
|
51 |
// track all of the known conversion
|
japod@35
|
52 |
for(final Convertor convertor : convertors)
|
japod@35
|
53 |
{
|
japod@35
|
54 |
final Set<Currency> currencies;
|
japod@35
|
55 |
Map<Currency, Convertor> possible;
|
japod@35
|
56 |
|
japod@35
|
57 |
if(convertor == null)
|
japod@35
|
58 |
{
|
japod@35
|
59 |
throw new IllegalArgumentException("cs cannot contain null");
|
japod@35
|
60 |
}
|
japod@35
|
61 |
|
japod@35
|
62 |
currencies = convertor.getCurrencies();
|
japod@35
|
63 |
|
japod@35
|
64 |
for(final Currency currency : currencies)
|
japod@35
|
65 |
{
|
japod@35
|
66 |
possible = possibleConversions.get(currency);
|
japod@35
|
67 |
|
japod@35
|
68 |
if(possible == null)
|
japod@35
|
69 |
{
|
japod@35
|
70 |
possible = new HashMap<Currency, Convertor>();
|
japod@35
|
71 |
possibleConversions.put(currency, possible);
|
japod@35
|
72 |
}
|
japod@35
|
73 |
|
japod@35
|
74 |
for(final Currency c : currencies)
|
japod@35
|
75 |
{
|
japod@35
|
76 |
possible.put(c, convertor);
|
japod@35
|
77 |
}
|
japod@35
|
78 |
}
|
japod@35
|
79 |
}
|
japod@35
|
80 |
|
japod@35
|
81 |
// make up conversions that can be derived... eg:
|
japod@35
|
82 |
// we have:
|
japod@35
|
83 |
// USD <-> CAD
|
japod@35
|
84 |
// CAD <-> CZK
|
japod@35
|
85 |
// SSK <-> GBP
|
japod@35
|
86 |
// we can derive:
|
japod@35
|
87 |
// USD <-> CZK
|
japod@35
|
88 |
// we cannot derive:
|
japod@35
|
89 |
// USD <-> GBP
|
japod@35
|
90 |
// CAD <-> GBP
|
japod@35
|
91 |
// CZK <-> GBP
|
japod@35
|
92 |
do
|
japod@35
|
93 |
{
|
japod@35
|
94 |
newConvertors = 0;
|
japod@35
|
95 |
|
japod@35
|
96 |
// todo... need to loop this until all the ones that can be handled are done.
|
japod@35
|
97 |
for(final Currency from : getCurrencies())
|
japod@35
|
98 |
{
|
japod@35
|
99 |
for(final Currency to : getCurrencies())
|
japod@35
|
100 |
{
|
japod@35
|
101 |
if(!(canConvert(from, to)))
|
japod@35
|
102 |
{
|
japod@35
|
103 |
final Set<Currency> fromCurrencies;
|
japod@35
|
104 |
final Set<Currency> toCurrencies;
|
japod@35
|
105 |
final Set<Currency> common;
|
japod@35
|
106 |
|
japod@35
|
107 |
fromCurrencies = possibleConversions.get(from).keySet();
|
japod@35
|
108 |
toCurrencies = possibleConversions.get(to).keySet();
|
japod@35
|
109 |
common = new HashSet<Currency>();
|
japod@35
|
110 |
|
japod@35
|
111 |
for(final Currency currency : fromCurrencies)
|
japod@35
|
112 |
{
|
japod@35
|
113 |
if(toCurrencies.contains(currency))
|
japod@35
|
114 |
{
|
japod@35
|
115 |
common.add(currency);
|
japod@35
|
116 |
}
|
japod@35
|
117 |
}
|
japod@35
|
118 |
|
japod@35
|
119 |
for(final Currency currency : common)
|
japod@35
|
120 |
{
|
japod@35
|
121 |
final Convertor convertor;
|
japod@35
|
122 |
|
japod@35
|
123 |
convertor = createConvertor(from, to, currency);
|
japod@35
|
124 |
possibleConversions.get(from).put(to, convertor);
|
japod@35
|
125 |
possibleConversions.get(to).put(from, convertor);
|
japod@35
|
126 |
newConvertors++;
|
japod@35
|
127 |
}
|
japod@35
|
128 |
}
|
japod@35
|
129 |
}
|
japod@35
|
130 |
}
|
japod@35
|
131 |
}
|
japod@35
|
132 |
while(newConvertors > 0);
|
japod@35
|
133 |
}
|
japod@35
|
134 |
|
japod@35
|
135 |
/**
|
japod@35
|
136 |
* Check to see if converting between the two currencies is possible.
|
japod@35
|
137 |
*
|
japod@35
|
138 |
* @param from the currency to convert from.
|
japod@35
|
139 |
* @param to the currency to convert to.
|
japod@35
|
140 |
* @return true if the conversion is possible.
|
japod@35
|
141 |
* @throws IllegalArgumentException if either from or to are null.
|
japod@35
|
142 |
*/
|
japod@35
|
143 |
public boolean canConvert(final Currency from, final Currency to)
|
japod@35
|
144 |
{
|
japod@35
|
145 |
final Map<Currency, Convertor> possible;
|
japod@35
|
146 |
|
japod@35
|
147 |
if(from == null)
|
japod@35
|
148 |
{
|
japod@35
|
149 |
throw new IllegalArgumentException("from cannot be null");
|
japod@35
|
150 |
}
|
japod@35
|
151 |
|
japod@35
|
152 |
if(to == null)
|
japod@35
|
153 |
{
|
japod@35
|
154 |
throw new IllegalArgumentException("to cannot be null");
|
japod@35
|
155 |
}
|
japod@35
|
156 |
|
japod@35
|
157 |
possible = possibleConversions.get(from);
|
japod@35
|
158 |
|
japod@35
|
159 |
if(possible.containsKey(to))
|
japod@35
|
160 |
{
|
japod@35
|
161 |
return (true);
|
japod@35
|
162 |
}
|
japod@35
|
163 |
|
japod@35
|
164 |
return (false);
|
japod@35
|
165 |
}
|
japod@35
|
166 |
|
japod@35
|
167 |
/**
|
japod@35
|
168 |
* Get the currencies that the convertor supports. Just because a currency is
|
japod@35
|
169 |
* supported does not mean that canConvert will return true.
|
japod@35
|
170 |
*
|
japod@35
|
171 |
* @return the supported currencies.
|
japod@35
|
172 |
*/
|
japod@35
|
173 |
public Set<Currency> getCurrencies()
|
japod@35
|
174 |
{
|
japod@35
|
175 |
final Set<Currency> currencies;
|
japod@35
|
176 |
|
japod@35
|
177 |
currencies = possibleConversions.keySet();
|
japod@35
|
178 |
|
japod@35
|
179 |
return (Collections.unmodifiableSet(currencies));
|
japod@35
|
180 |
}
|
japod@35
|
181 |
|
japod@35
|
182 |
/**
|
japod@35
|
183 |
* Get the conversion rate between two currencies.
|
japod@35
|
184 |
*
|
japod@35
|
185 |
* @param from the currency to convert from.
|
japod@35
|
186 |
* @param to the currency to convert to.
|
japod@35
|
187 |
* @return the conversion rate between the two currencies.
|
japod@35
|
188 |
* @throws IllegalArgumentException if either from or to is null.
|
japod@35
|
189 |
* @throws InvalidConversionException if canConvert would return false.
|
japod@35
|
190 |
*/
|
japod@35
|
191 |
public BigDecimal getConversionRate(final Currency from, final Currency to)
|
japod@35
|
192 |
throws InvalidConversionException
|
japod@35
|
193 |
{
|
japod@35
|
194 |
final Map<Currency, Convertor> possible;
|
japod@35
|
195 |
Convertor convertor;
|
japod@35
|
196 |
|
japod@35
|
197 |
if(from == null)
|
japod@35
|
198 |
{
|
japod@35
|
199 |
throw new IllegalArgumentException("from cannot be null");
|
japod@35
|
200 |
}
|
japod@35
|
201 |
|
japod@35
|
202 |
if(to == null)
|
japod@35
|
203 |
{
|
japod@35
|
204 |
throw new IllegalArgumentException("to cannot be null");
|
japod@35
|
205 |
}
|
japod@35
|
206 |
|
japod@35
|
207 |
if(!(canConvert(from, to)))
|
japod@35
|
208 |
{
|
japod@35
|
209 |
throw new InvalidConversionException("cannot convert", to);
|
japod@35
|
210 |
}
|
japod@35
|
211 |
|
japod@35
|
212 |
possible = possibleConversions.get(from);
|
japod@35
|
213 |
convertor = possible.get(to);
|
japod@35
|
214 |
|
japod@35
|
215 |
if(convertor == null)
|
japod@35
|
216 |
{
|
japod@35
|
217 |
throw new Error();
|
japod@35
|
218 |
}
|
japod@35
|
219 |
|
japod@35
|
220 |
return (convertor.getConversionRate(from, to));
|
japod@35
|
221 |
}
|
japod@35
|
222 |
|
japod@55
|
223 |
private Convertor getConvertor(final Currency from, final Currency to)
|
japod@55
|
224 |
throws InvalidConversionException
|
japod@55
|
225 |
{
|
japod@55
|
226 |
final Map<Currency, Convertor> possible;
|
japod@55
|
227 |
Convertor convertor;
|
japod@55
|
228 |
|
japod@55
|
229 |
if(from == null)
|
japod@55
|
230 |
{
|
japod@55
|
231 |
throw new IllegalArgumentException("from cannot be null");
|
japod@55
|
232 |
}
|
japod@55
|
233 |
|
japod@55
|
234 |
if(to == null)
|
japod@55
|
235 |
{
|
japod@55
|
236 |
throw new IllegalArgumentException("to cannot be null");
|
japod@55
|
237 |
}
|
japod@55
|
238 |
|
japod@55
|
239 |
if(!(canConvert(from, to)))
|
japod@55
|
240 |
{
|
japod@55
|
241 |
throw new InvalidConversionException("cannot convert", to);
|
japod@55
|
242 |
}
|
japod@55
|
243 |
|
japod@55
|
244 |
possible = possibleConversions.get(from);
|
japod@55
|
245 |
convertor = possible.get(to);
|
japod@55
|
246 |
|
japod@55
|
247 |
if(convertor == null)
|
japod@55
|
248 |
{
|
japod@55
|
249 |
throw new Error();
|
japod@55
|
250 |
}
|
japod@55
|
251 |
|
japod@55
|
252 |
return (convertor);
|
japod@55
|
253 |
}
|
japod@55
|
254 |
|
japod@35
|
255 |
/**
|
japod@35
|
256 |
* Convert an amount from one currency to another.
|
japod@35
|
257 |
*
|
japod@35
|
258 |
* @param from the currency to convert from.
|
japod@35
|
259 |
* @param to the currency to convert to.
|
japod@35
|
260 |
* @param amount the amount to convert.
|
japod@35
|
261 |
* @return the converted amount.
|
japod@35
|
262 |
* @throws IllegalArgumentException if any of the arguments are null.
|
japod@35
|
263 |
* @throws InvalidConversionException if either from or to are not valid for the convertor.
|
japod@35
|
264 |
*/
|
japod@35
|
265 |
public BigDecimal convert(final Currency from,
|
japod@35
|
266 |
final Currency to,
|
japod@35
|
267 |
final BigDecimal amount)
|
japod@35
|
268 |
throws InvalidConversionException
|
japod@35
|
269 |
{
|
japod@35
|
270 |
final BigDecimal result;
|
japod@55
|
271 |
final Convertor convertor;
|
japod@35
|
272 |
|
japod@35
|
273 |
if(amount == null)
|
japod@35
|
274 |
{
|
japod@35
|
275 |
throw new IllegalArgumentException("amount cannot be null");
|
japod@35
|
276 |
}
|
japod@35
|
277 |
|
japod@35
|
278 |
if(from == null)
|
japod@35
|
279 |
{
|
japod@35
|
280 |
throw new IllegalArgumentException("from cannot be null");
|
japod@35
|
281 |
}
|
japod@35
|
282 |
|
japod@35
|
283 |
if(to == null)
|
japod@35
|
284 |
{
|
japod@35
|
285 |
throw new IllegalArgumentException("to cannot be null");
|
japod@35
|
286 |
}
|
japod@35
|
287 |
|
japod@55
|
288 |
// fixed a bug from Task2 that showed up in Task3... before we did the conversion here,
|
japod@55
|
289 |
// but that meant that the underlying covnerter convert method never got called... which
|
japod@55
|
290 |
// meant that in Task3 the exchange rate never changed.
|
japod@55
|
291 |
convertor = getConvertor(from, to);
|
japod@55
|
292 |
result = convertor.convert(from, to, amount);
|
japod@35
|
293 |
|
japod@35
|
294 |
return (result.setScale(2, RoundingMode.HALF_DOWN));
|
japod@35
|
295 |
}
|
japod@35
|
296 |
|
japod@35
|
297 |
/**
|
japod@35
|
298 |
* Create a convertor between two currencies using another currency that is able to convert between both.
|
japod@35
|
299 |
*
|
japod@35
|
300 |
* @param from the currency to convert from.
|
japod@35
|
301 |
* @param to the currency to convert to.
|
japod@35
|
302 |
* @param intermediary the currency to use as a go-between.
|
japod@35
|
303 |
* @return a Convertor that is able to convert between from an to.
|
japod@35
|
304 |
* @throws IllegalArgumentException if any of the arguments are null.
|
japod@35
|
305 |
*/
|
japod@35
|
306 |
private Convertor createConvertor(final Currency from,
|
japod@35
|
307 |
final Currency to,
|
japod@35
|
308 |
final Currency intermediary)
|
japod@35
|
309 |
{
|
japod@35
|
310 |
final Convertor fromIntermediary;
|
japod@35
|
311 |
final Convertor toIntermediary;
|
japod@35
|
312 |
|
japod@35
|
313 |
if(from == null)
|
japod@35
|
314 |
{
|
japod@35
|
315 |
throw new IllegalArgumentException("from cannot be null");
|
japod@35
|
316 |
}
|
japod@35
|
317 |
|
japod@35
|
318 |
if(to == null)
|
japod@35
|
319 |
{
|
japod@35
|
320 |
throw new IllegalArgumentException("to cannot be null");
|
japod@35
|
321 |
}
|
japod@35
|
322 |
|
japod@35
|
323 |
if(intermediary == null)
|
japod@35
|
324 |
{
|
japod@35
|
325 |
throw new IllegalArgumentException("intermediary cannot be null");
|
japod@35
|
326 |
}
|
japod@35
|
327 |
|
japod@35
|
328 |
fromIntermediary = possibleConversions.get(from).get(intermediary);
|
japod@35
|
329 |
toIntermediary = possibleConversions.get(to).get(intermediary);
|
japod@35
|
330 |
|
japod@35
|
331 |
try
|
japod@35
|
332 |
{
|
japod@35
|
333 |
final BigDecimal fromRate;
|
japod@35
|
334 |
final BigDecimal toRate;
|
japod@35
|
335 |
final BigDecimal rate;
|
japod@35
|
336 |
final Convertor convertor;
|
japod@35
|
337 |
|
japod@35
|
338 |
fromRate = fromIntermediary.getConversionRate(from, intermediary);
|
japod@35
|
339 |
toRate = toIntermediary.getConversionRate(intermediary, to);
|
japod@55
|
340 |
rate = fromRate.multiply(toRate);
|
japod@35
|
341 |
convertor = ConvertorFactory.getConvertor(from, BigDecimal.ONE, to, rate);
|
japod@35
|
342 |
|
japod@35
|
343 |
return (convertor);
|
japod@35
|
344 |
}
|
japod@35
|
345 |
catch (InvalidConversionException ex)
|
japod@35
|
346 |
{
|
japod@35
|
347 |
throw new Error();
|
japod@35
|
348 |
}
|
japod@35
|
349 |
}
|
japod@35
|
350 |
}
|