001 /*
002 * Copyright 2009-2013 Stephen Colebourne
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.joda.money;
017
018 import java.io.InvalidObjectException;
019 import java.io.ObjectInputStream;
020 import java.io.Serializable;
021 import java.util.ArrayList;
022 import java.util.Collections;
023 import java.util.Currency;
024 import java.util.HashSet;
025 import java.util.List;
026 import java.util.Locale;
027 import java.util.Map.Entry;
028 import java.util.Set;
029 import java.util.concurrent.ConcurrentHashMap;
030 import java.util.concurrent.ConcurrentMap;
031
032 import org.joda.convert.FromString;
033 import org.joda.convert.ToString;
034
035 /**
036 * A unit of currency.
037 * <p>
038 * This class represents a unit of currency such as the British Pound, Euro
039 * or US Dollar.
040 * <p>
041 * The set of loaded currencies is provided by an instance of {@link CurrencyUnitDataProvider}.
042 * The provider used is determined by the system property {@code org.joda.money.CurrencyUnitDataProvider}
043 * which should be the fully qualified class name of the provider. The default provider loads the first
044 * resource named {@code /org/joda/money/MoneyData.csv} on the classpath.
045 * <p>
046 * This class is immutable and thread-safe.
047 */
048 public final class CurrencyUnit implements Comparable<CurrencyUnit>, Serializable {
049
050 /**
051 * The serialisation version.
052 */
053 private static final long serialVersionUID = 327835287287L;
054 /**
055 * Map of registered currencies by text code.
056 */
057 private static final ConcurrentMap<String, CurrencyUnit> currenciesByCode = new ConcurrentHashMap<String, CurrencyUnit>();
058 /**
059 * Map of registered currencies by numeric code.
060 */
061 private static final ConcurrentMap<Integer, CurrencyUnit> currenciesByNumericCode = new ConcurrentHashMap<Integer, CurrencyUnit>();
062 /**
063 * Map of registered currencies by country.
064 */
065 private static final ConcurrentMap<String, CurrencyUnit> currenciesByCountry = new ConcurrentHashMap<String, CurrencyUnit>();
066 static {
067 // load one data provider by system property
068 try {
069 try {
070 String clsName = System.getProperty(
071 "org.joda.money.CurrencyUnitDataProvider", "org.joda.money.DefaultCurrencyUnitDataProvider");
072 Class<? extends CurrencyUnitDataProvider> cls =
073 CurrencyUnit.class.getClassLoader().loadClass(clsName).asSubclass(CurrencyUnitDataProvider.class);
074 cls.newInstance().registerCurrencies();
075 } catch (SecurityException ex) {
076 new DefaultCurrencyUnitDataProvider().registerCurrencies();
077 }
078 } catch (RuntimeException ex) {
079 throw ex;
080 } catch (Exception ex) {
081 throw new RuntimeException(ex.toString(), ex);
082 }
083 }
084
085 // a selection of commonly traded, stable currencies
086 /**
087 * The currency 'USD' - United States Dollar.
088 */
089 public static final CurrencyUnit USD = of("USD");
090 /**
091 * The currency 'EUR' - Euro.
092 */
093 public static final CurrencyUnit EUR = of("EUR");
094 /**
095 * The currency 'JPY' - Japanese Yen.
096 */
097 public static final CurrencyUnit JPY = of("JPY");
098 /**
099 * The currency 'GBP' - British pound.
100 */
101 public static final CurrencyUnit GBP = of("GBP");
102 /**
103 * The currency 'CHF' - Swiss Franc.
104 */
105 public static final CurrencyUnit CHF = of("CHF");
106 /**
107 * The currency 'AUD' - Australian Dollar.
108 */
109 public static final CurrencyUnit AUD = of("AUD");
110 /**
111 * The currency 'CAD' - Canadian Dollar.
112 */
113 public static final CurrencyUnit CAD = of("CAD");
114
115 /**
116 * The currency code, not null.
117 */
118 private final String code;
119 /**
120 * The numeric currency code.
121 */
122 private final short numericCode;
123 /**
124 * The number of decimal places.
125 */
126 private final short decimalPlaces;
127
128 //-----------------------------------------------------------------------
129 /**
130 * Registers a currency allowing it to be used.
131 * <p>
132 * This class only permits known currencies to be returned.
133 * To achieve this, all currencies have to be registered in advance.
134 * <p>
135 * Since this method is public, it is possible to add currencies in
136 * application code. It is recommended to do this only at startup, however
137 * it is safe to do so later as the internal implementation is thread-safe.
138 *
139 * @param currencyCode the currency code, not null
140 * @param numericCurrencyCode the numeric currency code, -1 if none
141 * @param decimalPlaces the number of decimal places that the currency
142 * normally has, from 0 to 9 (normally 0, 2 or 3), or -1 for a pseudo-currency
143 * @param countryCodes the country codes to register the currency under, not null
144 * @return the new instance, never null
145 * @throws IllegalArgumentException if the code is already registered, or the
146 * specified data is invalid
147 */
148 public static synchronized CurrencyUnit registerCurrency(
149 String currencyCode, int numericCurrencyCode, int decimalPlaces, List<String> countryCodes) {
150 return registerCurrency(currencyCode, numericCurrencyCode, decimalPlaces, countryCodes, false);
151 }
152
153 /**
154 * Registers a currency allowing it to be used, allowing replacement.
155 * <p>
156 * This class only permits known currencies to be returned.
157 * To achieve this, all currencies have to be registered in advance.
158 * <p>
159 * Since this method is public, it is possible to add currencies in
160 * application code. It is recommended to do this only at startup, however
161 * it is safe to do so later as the internal implementation is thread-safe.
162 * <p>
163 * This method uses a flag to determine whether the registered currency
164 * must be new, or can replace an existing currency.
165 *
166 * @param currencyCode the currency code, not null
167 * @param numericCurrencyCode the numeric currency code, -1 if none
168 * @param decimalPlaces the number of decimal places that the currency
169 * normally has, from 0 to 9 (normally 0, 2 or 3), or -1 for a pseudo-currency
170 * @param countryCodes the country codes to register the currency under,
171 * use of ISO-3166 is recommended, not null
172 * @param force true to register forcefully, replacing any existing matching currency,
173 * false to validate that there is no existing matching currency
174 * @return the new instance, never null
175 * @throws IllegalArgumentException if the code is already registered and {@code force} is false;
176 * or if the specified data is invalid
177 */
178 public static synchronized CurrencyUnit registerCurrency(
179 String currencyCode, int numericCurrencyCode, int decimalPlaces, List<String> countryCodes, boolean force) {
180 MoneyUtils.checkNotNull(currencyCode, "Currency code must not be null");
181 if (numericCurrencyCode < -1 || numericCurrencyCode > 999) {
182 throw new IllegalArgumentException("Invalid numeric code");
183 }
184 if (decimalPlaces < -1 || decimalPlaces > 9) {
185 throw new IllegalArgumentException("Invalid number of decimal places");
186 }
187 MoneyUtils.checkNotNull(countryCodes, "Country codes must not be null");
188
189 CurrencyUnit currency = new CurrencyUnit(currencyCode, (short) numericCurrencyCode, (short) decimalPlaces);
190 if (force) {
191 currenciesByCode.remove(currencyCode);
192 currenciesByNumericCode.remove(numericCurrencyCode);
193 for (String countryCode : countryCodes) {
194 currenciesByCountry.remove(countryCode);
195 }
196 } else {
197 if (currenciesByCode.containsKey(currencyCode) || currenciesByNumericCode.containsKey(numericCurrencyCode)) {
198 throw new IllegalArgumentException("Currency already registered: " + currencyCode);
199 }
200 for (String countryCode : countryCodes) {
201 if (currenciesByCountry.containsKey(countryCode)) {
202 throw new IllegalArgumentException("Currency already registered for country: " + countryCode);
203 }
204 }
205 }
206 currenciesByCode.putIfAbsent(currencyCode, currency);
207 if (numericCurrencyCode >= 0) {
208 currenciesByNumericCode.putIfAbsent(numericCurrencyCode, currency);
209 }
210 for (String countryCode : countryCodes) {
211 currenciesByCountry.put(countryCode, currency);
212 }
213 return currenciesByCode.get(currencyCode);
214 }
215
216 /**
217 * Gets the list of all registered currencies.
218 * <p>
219 * This class only permits known currencies to be returned, thus this list is
220 * the complete list of valid singleton currencies. The list may change after
221 * application startup, however this isn't recommended.
222 *
223 * @return the sorted, independent, list of all registered currencies, never null
224 */
225 public static List<CurrencyUnit> registeredCurrencies() {
226 ArrayList<CurrencyUnit> list = new ArrayList<CurrencyUnit>(currenciesByCode.values());
227 Collections.sort(list);
228 return list;
229 }
230
231 //-----------------------------------------------------------------------
232 /**
233 * Obtains an instance of {@code CurrencyUnit} matching the specified JDK currency.
234 * <p>
235 * This converts the JDK currency instance to a currency unit using the code.
236 *
237 * @param currency the currency, not null
238 * @return the singleton instance, never null
239 * @throws IllegalCurrencyException if the currency is unknown
240 */
241 public static CurrencyUnit of(Currency currency) {
242 MoneyUtils.checkNotNull(currency, "Currency must not be null");
243 return of(currency.getCurrencyCode());
244 }
245
246 /**
247 * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 three letter currency code.
248 * <p>
249 * A currency is uniquely identified by ISO-4217 three letter code.
250 *
251 * @param currencyCode the currency code, not null
252 * @return the singleton instance, never null
253 * @throws IllegalCurrencyException if the currency is unknown
254 */
255 @FromString
256 public static CurrencyUnit of(String currencyCode) {
257 MoneyUtils.checkNotNull(currencyCode, "Currency code must not be null");
258 CurrencyUnit currency = currenciesByCode.get(currencyCode);
259 if (currency == null) {
260 throw new IllegalCurrencyException("Unknown currency '" + currencyCode + '\'');
261 }
262 return currency;
263 }
264
265 /**
266 * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 numeric currency code.
267 * <p>
268 * The numeric code is an alternative to the three letter code.
269 * This method is lenient and does not require the string to be left padded with zeroes.
270 *
271 * @param numericCurrencyCode the currency code, not null
272 * @return the singleton instance, never null
273 * @throws IllegalCurrencyException if the currency is unknown
274 */
275 public static CurrencyUnit ofNumericCode(String numericCurrencyCode) {
276 MoneyUtils.checkNotNull(numericCurrencyCode, "Currency code must not be null");
277 switch (numericCurrencyCode.length()) {
278 case 1:
279 return ofNumericCode(numericCurrencyCode.charAt(0) - '0');
280 case 2:
281 return ofNumericCode((numericCurrencyCode.charAt(0) - '0') * 10 +
282 numericCurrencyCode.charAt(1) - '0');
283 case 3:
284 return ofNumericCode((numericCurrencyCode.charAt(0) - '0') * 100 +
285 (numericCurrencyCode.charAt(1) - '0') * 10 +
286 numericCurrencyCode.charAt(2) - '0');
287 default:
288 throw new IllegalCurrencyException("Unknown currency '" + numericCurrencyCode + '\'');
289 }
290 }
291
292 /**
293 * Obtains an instance of {@code CurrencyUnit} for the specified ISO-4217 numeric currency code.
294 * <p>
295 * The numeric code is an alternative to the three letter code.
296 *
297 * @param numericCurrencyCode the numeric currency code, not null
298 * @return the singleton instance, never null
299 * @throws IllegalCurrencyException if the currency is unknown
300 */
301 public static CurrencyUnit ofNumericCode(int numericCurrencyCode) {
302 CurrencyUnit currency = currenciesByNumericCode.get(numericCurrencyCode);
303 if (currency == null) {
304 throw new IllegalCurrencyException("Unknown currency '" + numericCurrencyCode + '\'');
305 }
306 return currency;
307 }
308
309 /**
310 * Obtains an instance of {@code CurrencyUnit} for the specified locale.
311 * <p>
312 * Only the country is used from the locale.
313 *
314 * @param locale the locale, not null
315 * @return the singleton instance, never null
316 * @throws IllegalCurrencyException if the currency is unknown
317 */
318 public static CurrencyUnit of(Locale locale) {
319 MoneyUtils.checkNotNull(locale, "Locale must not be null");
320 CurrencyUnit currency = currenciesByCountry.get(locale.getCountry());
321 if (currency == null) {
322 throw new IllegalCurrencyException("Unknown currency for locale '" + locale + '\'');
323 }
324 return currency;
325 }
326
327 /**
328 * Obtains an instance of {@code CurrencyUnit} for the specified ISO-3166 country code.
329 * <p>
330 * Country codes should generally be in upper case.
331 * This method is case sensitive.
332 *
333 * @param countryCode the country code, typically ISO-3166, not null
334 * @return the singleton instance, never null
335 * @throws IllegalCurrencyException if the currency is unknown
336 */
337 public static CurrencyUnit ofCountry(String countryCode) {
338 MoneyUtils.checkNotNull(countryCode, "Country code must not be null");
339 CurrencyUnit currency = currenciesByCountry.get(countryCode);
340 if (currency == null) {
341 throw new IllegalCurrencyException("Unknown currency for country '" + countryCode + '\'');
342 }
343 return currency;
344 }
345
346 //-----------------------------------------------------------------------
347 /**
348 * Obtains an instance of {@code CurrencyUnit} for the specified currency code.
349 * <p>
350 * This method exists to match the API of {@link Currency}.
351 *
352 * @param currencyCode the currency code, not null
353 * @return the singleton instance, never null
354 * @throws IllegalCurrencyException if the currency is unknown
355 */
356 public static CurrencyUnit getInstance(String currencyCode) {
357 return CurrencyUnit.of(currencyCode);
358 }
359
360 /**
361 * Obtains an instance of {@code CurrencyUnit} for the specified locale.
362 * <p>
363 * This method exists to match the API of {@link Currency}.
364 *
365 * @param locale the locale, not null
366 * @return the singleton instance, never null
367 * @throws IllegalCurrencyException if the currency is unknown
368 */
369 public static CurrencyUnit getInstance(Locale locale) {
370 return CurrencyUnit.of(locale);
371 }
372
373 //-----------------------------------------------------------------------
374 /**
375 * Constructor, creating a new currency instance.
376 *
377 * @param code the currency code, not null
378 * @param numericCurrencyCode the numeric currency code, -1 if none
379 * @param decimalPlaces the decimal places, not null
380 */
381 CurrencyUnit(String code, short numericCurrencyCode, short decimalPlaces) {
382 assert code != null : "Joda-Money bug: Currency code must not be null";
383 this.code = code;
384 this.numericCode = numericCurrencyCode;
385 this.decimalPlaces = decimalPlaces;
386 }
387
388 /**
389 * Block malicious data streams.
390 *
391 * @param ois the input stream, not null
392 * @throws InvalidObjectException
393 */
394 private void readObject(ObjectInputStream ois) throws InvalidObjectException {
395 throw new InvalidObjectException("Serialization delegate required");
396 }
397
398 /**
399 * Uses a serialization delegate.
400 *
401 * @return the replacing object, never null
402 */
403 private Object writeReplace() {
404 return new Ser(Ser.CURRENCY_UNIT, this);
405 }
406
407 //-----------------------------------------------------------------------
408 /**
409 * Gets the ISO-4217 three letter currency code.
410 * <p>
411 * Each currency is uniquely identified by a three-letter code.
412 *
413 * @return the three letter ISO-4217 currency code, never null
414 */
415 public String getCode() {
416 return code;
417 }
418
419 /**
420 * Gets the ISO-4217 numeric currency code.
421 * <p>
422 * The numeric code is an alternative to the standard three letter code.
423 *
424 * @return the numeric currency code
425 */
426 public int getNumericCode() {
427 return numericCode;
428 }
429
430 /**
431 * Gets the ISO-4217 numeric currency code as a three digit string.
432 * <p>
433 * This formats the numeric code as a three digit string prefixed by zeroes if necessary.
434 * If there is no valid code, then an empty string is returned.
435 *
436 * @return the three digit numeric currency code, empty is no code, never null
437 */
438 public String getNumeric3Code() {
439 if (numericCode < 0) {
440 return "";
441 }
442 String str = Integer.toString(numericCode);
443 if (str.length() == 1) {
444 return "00" + str;
445 }
446 if (str.length() == 2) {
447 return "0" + str;
448 }
449 return str;
450 }
451
452 /**
453 * Gets the country codes applicable to this currency.
454 * <p>
455 * A currency is typically valid in one or more countries.
456 * The codes are typically defined by ISO-3166.
457 * An empty set indicates that no the currency is not associated with a country code.
458 *
459 * @return the country codes, may be empty, not null
460 */
461 public Set<String> getCountryCodes() {
462 Set<String> countryCodes = new HashSet<String>();
463 for (Entry<String, CurrencyUnit> entry : currenciesByCountry.entrySet()) {
464 if (this.equals(entry.getValue())) {
465 countryCodes.add(entry.getKey());
466 }
467 }
468 return countryCodes;
469 }
470
471 //-----------------------------------------------------------------------
472 /**
473 * Gets the number of decimal places typically used by this currency.
474 * <p>
475 * Different currencies have different numbers of decimal places by default.
476 * For example, 'GBP' has 2 decimal places, but 'JPY' has zero.
477 * Pseudo-currencies will return zero.
478 * <p>
479 * See also {@link #getDefaultFractionDigits()}.
480 *
481 * @return the decimal places, from 0 to 9 (normally 0, 2 or 3)
482 */
483 public int getDecimalPlaces() {
484 return decimalPlaces < 0 ? 0 : decimalPlaces;
485 }
486
487 /**
488 * Checks if this is a pseudo-currency.
489 *
490 * @return true if this is a pseudo-currency
491 */
492 public boolean isPseudoCurrency() {
493 return decimalPlaces < 0;
494 }
495
496 //-----------------------------------------------------------------------
497 /**
498 * Gets the ISO-4217 three-letter currency code.
499 * <p>
500 * This method matches the API of {@link Currency}.
501 *
502 * @return the currency code, never null
503 */
504 public String getCurrencyCode() {
505 return code;
506 }
507
508 /**
509 * Gets the number of fractional digits typically used by this currency.
510 * <p>
511 * Different currencies have different numbers of fractional digits by default.
512 * For example, 'GBP' has 2 fractional digits, but 'JPY' has zero.
513 * Pseudo-currencies are indicated by -1.
514 * <p>
515 * This method matches the API of {@link Currency}.
516 * The alternative method {@link #getDecimalPlaces()} may be more useful.
517 *
518 * @return the fractional digits, from 0 to 9 (normally 0, 2 or 3), or -1 for pseudo-currencies
519 */
520 public int getDefaultFractionDigits() {
521 return decimalPlaces;
522 }
523
524 //-----------------------------------------------------------------------
525 /**
526 * Gets the symbol for this locale from the JDK.
527 * <p>
528 * If this currency doesn't have a JDK equivalent, then the currency code
529 * is returned.
530 * <p>
531 * This method matches the API of {@link Currency}.
532 *
533 * @return the JDK currency instance, never null
534 */
535 public String getSymbol() {
536 try {
537 return Currency.getInstance(code).getSymbol();
538 } catch (IllegalArgumentException ex) {
539 return code;
540 }
541 }
542
543 /**
544 * Gets the symbol for this locale from the JDK.
545 * <p>
546 * If this currency doesn't have a JDK equivalent, then the currency code
547 * is returned.
548 * <p>
549 * This method matches the API of {@link Currency}.
550 *
551 * @param locale the locale to get the symbol for, not null
552 * @return the JDK currency instance, never null
553 */
554 public String getSymbol(Locale locale) {
555 MoneyUtils.checkNotNull(locale, "Locale must not be null");
556 try {
557 return Currency.getInstance(code).getSymbol(locale);
558 } catch (IllegalArgumentException ex) {
559 return code;
560 }
561 }
562
563 //-----------------------------------------------------------------------
564 /**
565 * Gets the JDK currency instance equivalent to this currency.
566 * <p>
567 * This attempts to convert a {@code CurrencyUnit} to a JDK {@code Currency}.
568 *
569 * @return the JDK currency instance, never null
570 * @throws IllegalArgumentException if no matching currency exists in the JDK
571 */
572 public Currency toCurrency() {
573 return Currency.getInstance(code);
574 }
575
576 //-----------------------------------------------------------------------
577 /**
578 * Compares this currency to another by alphabetical comparison of the code.
579 *
580 * @param other the other currency, not null
581 * @return negative if earlier alphabetically, 0 if equal, positive if greater alphabetically
582 */
583 public int compareTo(CurrencyUnit other) {
584 return code.compareTo(other.code);
585 }
586
587 /**
588 * Checks if this currency equals another currency.
589 * <p>
590 * The comparison checks the 3 letter currency code.
591 *
592 * @param obj the other currency, null returns false
593 * @return true if equal
594 */
595 @Override
596 public boolean equals(Object obj) {
597 if (obj == this) {
598 return true;
599 }
600 if (obj instanceof CurrencyUnit) {
601 return code.equals(((CurrencyUnit) obj).code);
602 }
603 return false;
604 }
605
606 /**
607 * Returns a suitable hash code for the currency.
608 *
609 * @return the hash code
610 */
611 @Override
612 public int hashCode() {
613 return code.hashCode();
614 }
615
616 //-----------------------------------------------------------------------
617 /**
618 * Gets the currency code as a string.
619 *
620 * @return the currency code, never null
621 */
622 @Override
623 @ToString
624 public String toString() {
625 return code;
626 }
627
628 }