2 *******************************************************************************
3 * Copyright (C) 2001-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.util;
9 import java.io.ObjectStreamException;
10 import java.io.Serializable;
11 import java.lang.ref.SoftReference;
12 import java.text.ParsePosition;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.Date;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Locale;
22 import java.util.MissingResourceException;
25 import com.ibm.icu.impl.ICUCache;
26 import com.ibm.icu.impl.ICUDebug;
27 import com.ibm.icu.impl.ICUResourceBundle;
28 import com.ibm.icu.impl.SimpleCache;
29 import com.ibm.icu.impl.TextTrieMap;
30 import com.ibm.icu.text.CurrencyDisplayNames;
31 import com.ibm.icu.text.CurrencyMetaInfo;
32 import com.ibm.icu.text.CurrencyMetaInfo.CurrencyDigits;
33 import com.ibm.icu.text.CurrencyMetaInfo.CurrencyFilter;
34 import com.ibm.icu.util.ULocale.Category;
37 * A class encapsulating a currency, as defined by ISO 4217. A
38 * <tt>Currency</tt> object can be created given a <tt>Locale</tt> or
39 * given an ISO 4217 code. Once created, the <tt>Currency</tt> object
40 * can return various data necessary to its proper display:
42 * <ul><li>A display symbol, for a specific locale
43 * <li>The number of fraction digits to display
44 * <li>A rounding increment
47 * The <tt>DecimalFormat</tt> class uses these data to display
50 * <p>Note: This class deliberately resembles
51 * <tt>java.util.Currency</tt> but it has a completely independent
52 * implementation, and adds features not present in the JDK.
56 public class Currency extends MeasureUnit implements Serializable {
57 private static final long serialVersionUID = -5839973855554750484L;
58 private static final boolean DEBUG = ICUDebug.enabled("currency");
60 // Cache to save currency name trie
61 private static ICUCache<ULocale, List<TextTrieMap<CurrencyStringInfo>>> CURRENCY_NAME_CACHE =
62 new SimpleCache<ULocale, List<TextTrieMap<CurrencyStringInfo>>>();
65 * Selector for getName() indicating a symbolic name for a
66 * currency, such as "$" for USD.
69 public static final int SYMBOL_NAME = 0;
72 * Selector for getName() indicating the long name for a
73 * currency, such as "US Dollar" for USD.
76 public static final int LONG_NAME = 1;
79 * Selector for getName() indicating the plural long name for a
80 * currency, such as "US dollar" for USD in "1 US dollar",
81 * and "US dollars" for USD in "2 US dollars".
84 public static final int PLURAL_LONG_NAME = 2;
86 private static final EquivalenceRelation<String> EQUIVALENT_CURRENCY_SYMBOLS =
87 new EquivalenceRelation<String>()
88 .add("\u00a5", "\uffe5")
89 .add("$", "\ufe69", "\uff04")
90 .add("\u20a8", "\u20b9")
91 .add("\u00a3", "\u20a4");
93 // begin registry stuff
95 // shim for service code
96 /* package */ static abstract class ServiceShim {
97 abstract ULocale[] getAvailableULocales();
98 abstract Locale[] getAvailableLocales();
99 abstract Currency createInstance(ULocale l);
100 abstract Object registerInstance(Currency c, ULocale l);
101 abstract boolean unregister(Object f);
104 private static ServiceShim shim;
105 private static ServiceShim getShim() {
106 // Note: this instantiation is safe on loose-memory-model configurations
107 // despite lack of synchronization, since the shim instance has no state--
108 // it's all in the class init. The worst problem is we might instantiate
109 // two shim instances, but they'll share the same state so that's ok.
112 Class<?> cls = Class.forName("com.ibm.icu.util.CurrencyServiceShim");
113 shim = (ServiceShim)cls.newInstance();
115 catch (Exception e) {
119 throw new RuntimeException(e.getMessage());
126 * Returns a currency object for the default currency in the given
128 * @param locale the locale
129 * @return the currency object for this locale
132 public static Currency getInstance(Locale locale) {
133 return getInstance(ULocale.forLocale(locale));
137 * Returns a currency object for the default currency in the given
141 public static Currency getInstance(ULocale locale) {
142 String currency = locale.getKeywordValue("currency");
143 if (currency != null) {
144 return getInstance(currency);
148 return createCurrency(locale);
151 return shim.createInstance(locale);
155 * Returns an array of Strings which contain the currency
156 * identifiers that are valid for the given locale on the
157 * given date. If there are no such identifiers, returns null.
158 * Returned identifiers are in preference order.
159 * @param loc the locale for which to retrieve currency codes.
160 * @param d the date for which to retrieve currency codes for the given locale.
161 * @return The array of ISO currency codes.
164 public static String[] getAvailableCurrencyCodes(ULocale loc, Date d) {
165 CurrencyFilter filter = CurrencyFilter.onDate(d).withRegion(loc.getCountry());
166 List<String> list = getTenderCurrencies(filter);
167 // Note: Prior to 4.4 the spec didn't say that we return null if there are no results, but
168 // the test assumed it did. Kept the behavior and amended the spec.
169 if (list.isEmpty()) {
172 return list.toArray(new String[list.size()]);
176 * Returns the set of available currencies. The returned set of currencies contains all of the
177 * available currencies, including obsolete ones. The result set can be modified without
178 * affecting the available currencies in the runtime.
180 * @return The set of available currencies. The returned set could be empty if there is no
181 * currency data available.
185 public static Set<Currency> getAvailableCurrencies() {
186 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
187 List<String> list = info.currencies(CurrencyFilter.all());
188 HashSet<Currency> resultSet = new HashSet<Currency>(list.size());
189 for (String code : list) {
190 resultSet.add(getInstance(code));
195 private static final String EUR_STR = "EUR";
196 private static final ICUCache<ULocale, String> currencyCodeCache = new SimpleCache<ULocale, String>();
199 * Instantiate a currency from resource data.
201 /* package */ static Currency createCurrency(ULocale loc) {
203 String variant = loc.getVariant();
204 if ("EURO".equals(variant)) {
205 return getInstance(EUR_STR);
208 String code = currencyCodeCache.get(loc);
210 String country = loc.getCountry();
212 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
213 List<String> list = info.currencies(CurrencyFilter.onRegion(country));
214 if (list.size() > 0) {
216 boolean isPreEuro = "PREEURO".equals(variant);
217 if (isPreEuro && EUR_STR.equals(code)) {
218 if (list.size() < 2) {
226 currencyCodeCache.put(loc, code);
228 return getInstance(code);
232 * Returns a currency object given an ISO 4217 3-letter code.
233 * @param theISOCode the iso code
234 * @return the currency for this iso code
235 * @throws NullPointerException if <code>theISOCode</code> is null.
236 * @throws IllegalArgumentException if <code>theISOCode</code> is not a
237 * 3-letter alpha code.
240 public static Currency getInstance(String theISOCode) {
241 if (theISOCode == null) {
242 throw new NullPointerException("The input currency code is null.");
244 if (!isAlpha3Code(theISOCode)) {
245 throw new IllegalArgumentException(
246 "The input currency code is not 3-letter alphabetic code.");
248 return (Currency) MeasureUnit.addUnit("currency", theISOCode.toUpperCase(Locale.ENGLISH), CURRENCY_FACTORY);
252 private static boolean isAlpha3Code(String code) {
253 if (code.length() != 3) {
256 for (int i = 0; i < 3; i++) {
257 char ch = code.charAt(i);
258 if (ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') {
267 * Registers a new currency for the provided locale. The returned object
268 * is a key that can be used to unregister this currency object.
269 * @param currency the currency to register
270 * @param locale the ulocale under which to register the currency
271 * @return a registry key that can be used to unregister this currency
275 public static Object registerInstance(Currency currency, ULocale locale) {
276 return getShim().registerInstance(currency, locale);
280 * Unregister the currency associated with this key (obtained from
282 * @param registryKey the registry key returned from registerInstance
283 * @see #registerInstance
286 public static boolean unregister(Object registryKey) {
287 if (registryKey == null) {
288 throw new IllegalArgumentException("registryKey must not be null");
293 return shim.unregister(registryKey);
297 * Return an array of the locales for which a currency
299 * @return an array of the available locales
302 public static Locale[] getAvailableLocales() {
304 return ICUResourceBundle.getAvailableLocales();
306 return shim.getAvailableLocales();
311 * Return an array of the ulocales for which a currency
313 * @return an array of the available ulocales
316 public static ULocale[] getAvailableULocales() {
318 return ICUResourceBundle.getAvailableULocales();
320 return shim.getAvailableULocales();
324 // end registry stuff
327 * Given a key and a locale, returns an array of values for the key for which data
328 * exists. If commonlyUsed is true, these are the values that typically are used
329 * with this locale, otherwise these are all values for which data exists.
330 * This is a common service API.
332 * The only supported key is "currency", other values return an empty array.
334 * Currency information is based on the region of the locale. If the locale does not
335 * indicate a region, {@link ULocale#addLikelySubtags(ULocale)} is used to infer a region,
336 * except for the 'und' locale.
338 * If commonlyUsed is true, only the currencies known to be in use as of the current date
339 * are returned. When there are more than one, these are returned in preference order
340 * (typically, this occurs when a country is transitioning to a new currency, and the
341 * newer currency is preferred), see
342 * <a href="http://unicode.org/reports/tr35/#Supplemental_Currency_Data">Unicode TR#35 Sec. C1</a>.
343 * If commonlyUsed is false, all currencies ever used in any locale are returned, in no
346 * @param key key whose values to look up. the only recognized key is "currency"
347 * @param locale the locale
348 * @param commonlyUsed if true, return only values that are currently used in the locale.
349 * Otherwise returns all values.
350 * @return an array of values for the given key and the locale. If there is no data, the
351 * array will be empty.
354 public static final String[] getKeywordValuesForLocale(String key, ULocale locale,
355 boolean commonlyUsed) {
357 // The only keyword we recognize is 'currency'
358 if (!"currency".equals(key)) {
359 return EMPTY_STRING_ARRAY;
363 // Behavior change from 4.3.3, no longer sort the currencies
364 return getAllTenderCurrencies().toArray(new String[0]);
367 // Don't resolve region if the requested locale is 'und', it will resolve to US
368 // which we don't want.
369 String prefRegion = locale.getCountry();
370 if (prefRegion.length() == 0) {
371 if (UND.equals(locale)) {
372 return EMPTY_STRING_ARRAY;
374 ULocale loc = ULocale.addLikelySubtags(locale);
375 prefRegion = loc.getCountry();
378 CurrencyFilter filter = CurrencyFilter.now().withRegion(prefRegion);
380 // currencies are in region's preferred order when we're filtering on region, which
382 List<String> result = getTenderCurrencies(filter);
384 // No fallback anymore (change from 4.3.3)
385 if (result.size() == 0) {
386 return EMPTY_STRING_ARRAY;
389 return result.toArray(new String[result.size()]);
392 private static final ULocale UND = new ULocale("und");
393 private static final String[] EMPTY_STRING_ARRAY = new String[0];
396 * Returns the ISO 4217 3-letter code for this currency object.
399 public String getCurrencyCode() {
404 * Returns the ISO 4217 numeric code for this currency object.
405 * <p>Note: If the ISO 4217 numeric code is not assigned for the currency or
406 * the currency is unknown, this method returns 0.</p>
407 * @return The ISO 4217 numeric code of this currency.
410 public int getNumericCode() {
413 UResourceBundle bundle = UResourceBundle.getBundleInstance(
414 ICUResourceBundle.ICU_BASE_NAME,
415 "currencyNumericCodes",
416 ICUResourceBundle.ICU_DATA_CLASS_LOADER);
417 UResourceBundle codeMap = bundle.get("codeMap");
418 UResourceBundle numCode = codeMap.get(code);
419 result = numCode.getInt();
420 } catch (MissingResourceException e) {
427 * Convenience and compatibility override of getName that
428 * requests the symbol name for the default <code>DISPLAY</code> locale.
430 * @see Category#DISPLAY
433 public String getSymbol() {
434 return getSymbol(ULocale.getDefault(Category.DISPLAY));
438 * Convenience and compatibility override of getName that
439 * requests the symbol name.
440 * @param loc the Locale for the symbol
444 public String getSymbol(Locale loc) {
445 return getSymbol(ULocale.forLocale(loc));
449 * Convenience and compatibility override of getName that
450 * requests the symbol name.
451 * @param uloc the ULocale for the symbol
455 public String getSymbol(ULocale uloc) {
456 return getName(uloc, SYMBOL_NAME, new boolean[1]);
460 * Returns the display name for the given currency in the
462 * This is a convenient method for
463 * getName(ULocale, int, boolean[]);
466 public String getName(Locale locale,
468 boolean[] isChoiceFormat) {
469 return getName(ULocale.forLocale(locale), nameStyle, isChoiceFormat);
473 * Returns the display name for the given currency in the
474 * given locale. For example, the display name for the USD
475 * currency object in the en_US locale is "$".
476 * @param locale locale in which to display currency
477 * @param nameStyle selector for which kind of name to return.
478 * The nameStyle should be either SYMBOL_NAME or
479 * LONG_NAME. Otherwise, throw IllegalArgumentException.
480 * @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true
481 * if the returned value is a ChoiceFormat pattern; otherwise it
483 * @return display string for this currency. If the resource data
484 * contains no entry for this currency, then the ISO 4217 code is
485 * returned. If isChoiceFormat[0] is true, then the result is a
486 * ChoiceFormat pattern. Otherwise it is a static string. <b>Note:</b>
487 * as of ICU 4.4, choice formats are not used, and the value returned
488 * in isChoiceFormat is always false.
490 * @throws IllegalArgumentException if the nameStyle is not SYMBOL_NAME
492 * @see #getName(ULocale, int, String, boolean[])
495 public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) {
496 if (!(nameStyle == SYMBOL_NAME || nameStyle == LONG_NAME)) {
497 throw new IllegalArgumentException("bad name style: " + nameStyle);
500 // We no longer support choice format data in names. Data should not contain
502 if (isChoiceFormat != null) {
503 isChoiceFormat[0] = false;
506 CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale);
507 return nameStyle == SYMBOL_NAME ? names.getSymbol(code) : names.getName(code);
511 * Returns the display name for the given currency in the given locale.
512 * This is a convenience overload of getName(ULocale, int, String, boolean[]);
515 public String getName(Locale locale, int nameStyle, String pluralCount,
516 boolean[] isChoiceFormat) {
517 return getName(ULocale.forLocale(locale), nameStyle, pluralCount, isChoiceFormat);
521 * Returns the display name for the given currency in the
522 * given locale. For example, the SYMBOL_NAME for the USD
523 * currency object in the en_US locale is "$".
524 * The PLURAL_LONG_NAME for the USD currency object when the currency
525 * amount is plural is "US dollars", such as in "3.00 US dollars";
526 * while the PLURAL_LONG_NAME for the USD currency object when the currency
527 * amount is singular is "US dollar", such as in "1.00 US dollar".
528 * @param locale locale in which to display currency
529 * @param nameStyle selector for which kind of name to return
530 * @param pluralCount plural count string for this locale
531 * @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true
532 * if the returned value is a ChoiceFormat pattern; otherwise it
534 * @return display string for this currency. If the resource data
535 * contains no entry for this currency, then the ISO 4217 code is
536 * returned. If isChoiceFormat[0] is true, then the result is a
537 * ChoiceFormat pattern. Otherwise it is a static string. <b>Note:</b>
538 * as of ICU 4.4, choice formats are not used, and the value returned
539 * in isChoiceFormat is always false.
540 * @throws IllegalArgumentException if the nameStyle is not SYMBOL_NAME,
541 * LONG_NAME, or PLURAL_LONG_NAME.
544 public String getName(ULocale locale, int nameStyle, String pluralCount,
545 boolean[] isChoiceFormat) {
546 if (nameStyle != PLURAL_LONG_NAME) {
547 return getName(locale, nameStyle, isChoiceFormat);
550 // We no longer support choice format
551 if (isChoiceFormat != null) {
552 isChoiceFormat[0] = false;
555 CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale);
556 return names.getPluralName(code, pluralCount);
560 * Returns the display name for this currency in the default locale.
561 * If the resource data for the default locale contains no entry for this currency,
562 * then the ISO 4217 code is returned.
564 * Note: This method was added for JDK compatibility support and equivalent to
565 * <code>getName(Locale.getDefault(), LONG_NAME, null)</code>.
567 * @return The display name of this currency
568 * @see #getDisplayName(Locale)
569 * @see #getName(Locale, int, boolean[])
572 public String getDisplayName() {
573 return getName(Locale.getDefault(), LONG_NAME, null);
577 * Returns the display name for this currency in the given locale.
578 * If the resource data for the given locale contains no entry for this currency,
579 * then the ISO 4217 code is returned.
581 * Note: This method was added for JDK compatibility support and equivalent to
582 * <code>getName(locale, LONG_NAME, null)</code>.
584 * @param locale locale in which to display currency
585 * @return The display name of this currency for the specified locale
586 * @see #getDisplayName(Locale)
587 * @see #getName(Locale, int, boolean[])
590 public String getDisplayName(Locale locale) {
591 return getName(locale, LONG_NAME, null);
595 * Attempt to parse the given string as a currency, either as a
596 * display name in the given locale, or as a 3-letter ISO 4217
597 * code. If multiple display names match, then the longest one is
598 * selected. If both a display name and a 3-letter ISO code
599 * match, then the display name is preferred, unless it's length
602 * @param locale the locale of the display names to match
603 * @param text the text to parse
604 * @param type parse against currency type: LONG_NAME only or not
605 * @param pos input-output position; on input, the position within
606 * text to match; must have 0 <= pos.getIndex() < text.length();
607 * on output, the position after the last matched character. If
608 * the parse fails, the position in unchanged upon output.
609 * @return the ISO 4217 code, as a string, of the best match, or
610 * null if there is no match
613 * @deprecated This API is ICU internal only.
615 public static String parse(ULocale locale, String text, int type, ParsePosition pos) {
616 List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = CURRENCY_NAME_CACHE.get(locale);
617 if (currencyTrieVec == null) {
618 TextTrieMap<CurrencyStringInfo> currencyNameTrie =
619 new TextTrieMap<CurrencyStringInfo>(true);
620 TextTrieMap<CurrencyStringInfo> currencySymbolTrie =
621 new TextTrieMap<CurrencyStringInfo>(false);
622 currencyTrieVec = new ArrayList<TextTrieMap<CurrencyStringInfo>>();
623 currencyTrieVec.add(currencySymbolTrie);
624 currencyTrieVec.add(currencyNameTrie);
625 setupCurrencyTrieVec(locale, currencyTrieVec);
626 CURRENCY_NAME_CACHE.put(locale, currencyTrieVec);
630 String isoResult = null;
632 // look for the names
633 TextTrieMap<CurrencyStringInfo> currencyNameTrie = currencyTrieVec.get(1);
634 CurrencyNameResultHandler handler = new CurrencyNameResultHandler();
635 currencyNameTrie.find(text, pos.getIndex(), handler);
636 isoResult = handler.getBestCurrencyISOCode();
637 maxLength = handler.getBestMatchLength();
639 if (type != Currency.LONG_NAME) { // not long name only
640 TextTrieMap<CurrencyStringInfo> currencySymbolTrie = currencyTrieVec.get(0);
641 handler = new CurrencyNameResultHandler();
642 currencySymbolTrie.find(text, pos.getIndex(), handler);
643 if (handler.getBestMatchLength() > maxLength) {
644 isoResult = handler.getBestCurrencyISOCode();
645 maxLength = handler.getBestMatchLength();
648 int start = pos.getIndex();
649 pos.setIndex(start + maxLength);
653 private static void setupCurrencyTrieVec(ULocale locale,
654 List<TextTrieMap<CurrencyStringInfo>> trieVec) {
656 TextTrieMap<CurrencyStringInfo> symTrie = trieVec.get(0);
657 TextTrieMap<CurrencyStringInfo> trie = trieVec.get(1);
659 CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale);
660 for (Map.Entry<String, String> e : names.symbolMap().entrySet()) {
661 String symbol = e.getKey();
662 String isoCode = e.getValue();
663 // Register under not just symbol, but under every equivalent symbol as well
664 // e.g short width yen and long width yen.
665 for (String equivalentSymbol : EQUIVALENT_CURRENCY_SYMBOLS.get(symbol)) {
666 symTrie.put(equivalentSymbol, new CurrencyStringInfo(isoCode, symbol));
669 for (Map.Entry<String, String> e : names.nameMap().entrySet()) {
670 String name = e.getKey();
671 String isoCode = e.getValue();
672 trie.put(name, new CurrencyStringInfo(isoCode, name));
676 private static final class CurrencyStringInfo {
677 private String isoCode;
678 private String currencyString;
680 public CurrencyStringInfo(String isoCode, String currencyString) {
681 this.isoCode = isoCode;
682 this.currencyString = currencyString;
685 public String getISOCode() {
689 @SuppressWarnings("unused")
690 public String getCurrencyString() {
691 return currencyString;
695 private static class CurrencyNameResultHandler
696 implements TextTrieMap.ResultHandler<CurrencyStringInfo> {
697 // The length of longest matching key
698 private int bestMatchLength;
699 // The currency ISO code of longest matching key
700 private String bestCurrencyISOCode;
702 // As the trie is traversed, handlePrefixMatch is called at each node. matchLength is the
703 // length length of the key at the current node; values is the list of all the values mapped to
704 // that key. matchLength increases with each call as trie is traversed.
705 public boolean handlePrefixMatch(int matchLength, Iterator<CurrencyStringInfo> values) {
706 if (values.hasNext()) {
707 // Since the best match criteria is only based on length of key in trie and since all the
708 // values are mapped to the same key, we only need to examine the first value.
709 bestCurrencyISOCode = values.next().getISOCode();
710 bestMatchLength = matchLength;
715 public String getBestCurrencyISOCode() {
716 return bestCurrencyISOCode;
719 public int getBestMatchLength() {
720 return bestMatchLength;
725 * Returns the number of the number of fraction digits that should
726 * be displayed for this currency.
727 * @return a non-negative number of fraction digits to be
731 public int getDefaultFractionDigits() {
732 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
733 CurrencyDigits digits = info.currencyDigits(code);
734 return digits.fractionDigits;
738 * Returns the rounding increment for this currency, or 0.0 if no
739 * rounding is done by this currency.
740 * @return the non-negative rounding increment, or 0.0 if none
743 public double getRoundingIncrement() {
744 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
745 CurrencyDigits digits = info.currencyDigits(code);
747 int data1 = digits.roundingIncrement;
749 // If there is no rounding return 0.0 to indicate no rounding.
750 // This is the high-runner case, by far.
755 int data0 = digits.fractionDigits;
757 // If the meta data is invalid, return 0.0 to indicate no rounding.
758 if (data0 < 0 || data0 >= POW10.length) {
762 // Return data[1] / 10^(data[0]). The only actual rounding data,
763 // as of this writing, is CHF { 2, 25 }.
764 return (double) data1 / POW10[data0];
768 * Returns the ISO 4217 code for this currency.
771 public String toString() {
776 * Constructs a currency object for the given ISO 4217 3-letter
777 * code. This constructor assumes that the code is valid.
779 * @param theISOCode The iso code used to construct the currency.
782 protected Currency(String theISOCode) {
783 super("currency", theISOCode);
785 // isoCode is kept for readResolve() and Currency class no longer
786 // use it. So this statement actually does not have any effect.
791 private static final int[] POW10 = {
792 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
796 private static SoftReference<List<String>> ALL_TENDER_CODES;
797 private static SoftReference<Set<String>> ALL_CODES_AS_SET;
799 * Returns an unmodifiable String list including all known tender currency codes.
801 private static synchronized List<String> getAllTenderCurrencies() {
802 List<String> all = (ALL_TENDER_CODES == null) ? null : ALL_TENDER_CODES.get();
804 // Filter out non-tender currencies which have "from" date set to 9999-12-31
805 // CurrencyFilter has "to" value set to 9998-12-31 in order to exclude them
806 //CurrencyFilter filter = CurrencyFilter.onDateRange(null, new Date(253373299200000L));
807 CurrencyFilter filter = CurrencyFilter.all();
808 all = Collections.unmodifiableList(getTenderCurrencies(filter));
809 ALL_TENDER_CODES = new SoftReference<List<String>>(all);
814 private static synchronized Set<String> getAllCurrenciesAsSet() {
815 Set<String> all = (ALL_CODES_AS_SET == null) ? null : ALL_CODES_AS_SET.get();
817 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
818 all = Collections.unmodifiableSet(
819 new HashSet<String>(info.currencies(CurrencyFilter.all())));
820 ALL_CODES_AS_SET = new SoftReference<Set<String>>(all);
826 * Queries if the given ISO 4217 3-letter code is available on the specified date range.
828 * Note: For checking availability of a currency on a specific date, specify the date on both <code>from</code> and
829 * <code>to</code>. When both <code>from</code> and <code>to</code> are null, this method checks if the specified
830 * currency is available all time.
833 * The ISO 4217 3-letter code.
835 * The lower bound of the date range, inclusive. When <code>from</code> is null, check the availability
836 * of the currency any date before <code>to</code>
838 * The upper bound of the date range, inclusive. When <code>to</code> is null, check the availability of
839 * the currency any date after <code>from</code>
840 * @return true if the given ISO 4217 3-letter code is supported on the specified date range.
841 * @throws IllegalArgumentException when <code>to</code> is before <code>from</code>.
845 public static boolean isAvailable(String code, Date from, Date to) {
846 if (!isAlpha3Code(code)) {
850 if (from != null && to != null && from.after(to)) {
851 throw new IllegalArgumentException("To is before from");
854 code = code.toUpperCase(Locale.ENGLISH);
855 boolean isKnown = getAllCurrenciesAsSet().contains(code);
856 if (isKnown == false) {
858 } else if (from == null && to == null) {
862 // If caller passed a date range, we cannot rely solely on the cache
863 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
864 List<String> allActive = info.currencies(
865 CurrencyFilter.onDateRange(from, to).withCurrency(code));
866 return allActive.contains(code);
870 * Returns the list of remaining tender currencies after a filter is applied.
871 * @param filter the filter to apply to the tender currencies
872 * @return a list of tender currencies
874 private static List<String> getTenderCurrencies(CurrencyFilter filter) {
875 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance();
876 return info.currencies(filter.withTender());
879 private static final class EquivalenceRelation<T> {
881 private Map<T, Set<T>> data = new HashMap<T, Set<T>>();
883 public EquivalenceRelation<T> add(T... items) {
884 Set<T> group = new HashSet<T>();
885 for (T item : items) {
886 if (data.containsKey(item)) {
887 throw new IllegalArgumentException("All groups passed to add must be disjoint.");
891 for (T item : items) {
892 data.put(item, group);
897 public Set<T> get(T item) {
898 Set<T> result = data.get(item);
899 if (result == null) {
900 return Collections.singleton(item);
902 return Collections.unmodifiableSet(result);
906 private Object writeReplace() throws ObjectStreamException {
907 return new MeasureUnitProxy(type, code);
910 // For backward compatibility only
912 * ISO 4217 3-letter code.
914 private final String isoCode;
916 private Object readResolve() throws ObjectStreamException {
917 return Currency.getInstance(isoCode);