1/** 2 ******************************************************************************* 3 * Copyright (C) 2001-2015, International Business Machines Corporation and 4 * others. All Rights Reserved. 5 ******************************************************************************* 6 */ 7package com.ibm.icu.util; 8 9import java.io.ObjectStreamException; 10import java.lang.ref.SoftReference; 11import java.text.ParsePosition; 12import java.util.ArrayList; 13import java.util.Collections; 14import java.util.Date; 15import java.util.HashMap; 16import java.util.HashSet; 17import java.util.Iterator; 18import java.util.List; 19import java.util.Locale; 20import java.util.Map; 21import java.util.MissingResourceException; 22import java.util.Set; 23 24import com.ibm.icu.impl.ICUCache; 25import com.ibm.icu.impl.ICUDebug; 26import com.ibm.icu.impl.ICUResourceBundle; 27import com.ibm.icu.impl.SimpleCache; 28import com.ibm.icu.impl.TextTrieMap; 29import com.ibm.icu.text.CurrencyDisplayNames; 30import com.ibm.icu.text.CurrencyMetaInfo; 31import com.ibm.icu.text.CurrencyMetaInfo.CurrencyDigits; 32import com.ibm.icu.text.CurrencyMetaInfo.CurrencyFilter; 33import com.ibm.icu.util.ULocale.Category; 34 35/** 36 * A class encapsulating a currency, as defined by ISO 4217. A 37 * <tt>Currency</tt> object can be created given a <tt>Locale</tt> or 38 * given an ISO 4217 code. Once created, the <tt>Currency</tt> object 39 * can return various data necessary to its proper display: 40 * 41 * <ul><li>A display symbol, for a specific locale 42 * <li>The number of fraction digits to display 43 * <li>A rounding increment 44 * </ul> 45 * 46 * The <tt>DecimalFormat</tt> class uses these data to display 47 * currencies. 48 * 49 * <p>Note: This class deliberately resembles 50 * <tt>java.util.Currency</tt> but it has a completely independent 51 * implementation, and adds features not present in the JDK. 52 * @author Alan Liu 53 * @stable ICU 2.2 54 */ 55public class Currency extends MeasureUnit { 56 private static final long serialVersionUID = -5839973855554750484L; 57 private static final boolean DEBUG = ICUDebug.enabled("currency"); 58 59 // Cache to save currency name trie 60 private static ICUCache<ULocale, List<TextTrieMap<CurrencyStringInfo>>> CURRENCY_NAME_CACHE = 61 new SimpleCache<ULocale, List<TextTrieMap<CurrencyStringInfo>>>(); 62 63 /** 64 * Selector for getName() indicating a symbolic name for a 65 * currency, such as "$" for USD. 66 * @stable ICU 2.6 67 */ 68 public static final int SYMBOL_NAME = 0; 69 70 /** 71 * Selector for getName() indicating the long name for a 72 * currency, such as "US Dollar" for USD. 73 * @stable ICU 2.6 74 */ 75 public static final int LONG_NAME = 1; 76 77 /** 78 * Selector for getName() indicating the plural long name for a 79 * currency, such as "US dollar" for USD in "1 US dollar", 80 * and "US dollars" for USD in "2 US dollars". 81 * @stable ICU 4.2 82 */ 83 public static final int PLURAL_LONG_NAME = 2; 84 85 private static final EquivalenceRelation<String> EQUIVALENT_CURRENCY_SYMBOLS = 86 new EquivalenceRelation<String>() 87 .add("\u00a5", "\uffe5") 88 .add("$", "\ufe69", "\uff04") 89 .add("\u20a8", "\u20b9") 90 .add("\u00a3", "\u20a4"); 91 92 /** 93 * Currency Usage used for Decimal Format 94 * @stable ICU 54 95 */ 96 public enum CurrencyUsage{ 97 /** 98 * a setting to specify currency usage which determines currency digit and rounding 99 * for standard usage, for example: "50.00 NT$" 100 * @stable ICU 54 101 */ 102 STANDARD, 103 104 /** 105 * a setting to specify currency usage which determines currency digit and rounding 106 * for cash usage, for example: "50 NT$" 107 * @stable ICU 54 108 */ 109 CASH 110 } 111 112 // begin registry stuff 113 114 // shim for service code 115 /* package */ static abstract class ServiceShim { 116 abstract ULocale[] getAvailableULocales(); 117 abstract Locale[] getAvailableLocales(); 118 abstract Currency createInstance(ULocale l); 119 abstract Object registerInstance(Currency c, ULocale l); 120 abstract boolean unregister(Object f); 121 } 122 123 private static ServiceShim shim; 124 private static ServiceShim getShim() { 125 // Note: this instantiation is safe on loose-memory-model configurations 126 // despite lack of synchronization, since the shim instance has no state-- 127 // it's all in the class init. The worst problem is we might instantiate 128 // two shim instances, but they'll share the same state so that's ok. 129 if (shim == null) { 130 try { 131 Class<?> cls = Class.forName("com.ibm.icu.util.CurrencyServiceShim"); 132 shim = (ServiceShim)cls.newInstance(); 133 } 134 catch (Exception e) { 135 if(DEBUG){ 136 e.printStackTrace(); 137 } 138 throw new RuntimeException(e.getMessage()); 139 } 140 } 141 return shim; 142 } 143 144 /** 145 * Returns a currency object for the default currency in the given 146 * locale. 147 * @param locale the locale 148 * @return the currency object for this locale 149 * @stable ICU 2.2 150 */ 151 public static Currency getInstance(Locale locale) { 152 return getInstance(ULocale.forLocale(locale)); 153 } 154 155 /** 156 * Returns a currency object for the default currency in the given 157 * locale. 158 * @stable ICU 3.2 159 */ 160 public static Currency getInstance(ULocale locale) { 161 String currency = locale.getKeywordValue("currency"); 162 if (currency != null) { 163 return getInstance(currency); 164 } 165 166 if (shim == null) { 167 return createCurrency(locale); 168 } 169 170 return shim.createInstance(locale); 171 } 172 173 /** 174 * Returns an array of Strings which contain the currency 175 * identifiers that are valid for the given locale on the 176 * given date. If there are no such identifiers, returns null. 177 * Returned identifiers are in preference order. 178 * @param loc the locale for which to retrieve currency codes. 179 * @param d the date for which to retrieve currency codes for the given locale. 180 * @return The array of ISO currency codes. 181 * @stable ICU 4.0 182 */ 183 public static String[] getAvailableCurrencyCodes(ULocale loc, Date d) { 184 CurrencyFilter filter = CurrencyFilter.onDate(d).withRegion(loc.getCountry()); 185 List<String> list = getTenderCurrencies(filter); 186 // Note: Prior to 4.4 the spec didn't say that we return null if there are no results, but 187 // the test assumed it did. Kept the behavior and amended the spec. 188 if (list.isEmpty()) { 189 return null; 190 } 191 return list.toArray(new String[list.size()]); 192 } 193 194 /** 195 * Returns an array of Strings which contain the currency 196 * identifiers that are valid for the given {@link java.util.Locale} on the 197 * given date. If there are no such identifiers, returns null. 198 * Returned identifiers are in preference order. 199 * @param loc the {@link java.util.Locale} for which to retrieve currency codes. 200 * @param d the date for which to retrieve currency codes for the given locale. 201 * @return The array of ISO currency codes. 202 * @stable ICU 54 203 */ 204 public static String[] getAvailableCurrencyCodes(Locale loc, Date d) { 205 return getAvailableCurrencyCodes(ULocale.forLocale(loc), d); 206 } 207 208 /** 209 * Returns the set of available currencies. The returned set of currencies contains all of the 210 * available currencies, including obsolete ones. The result set can be modified without 211 * affecting the available currencies in the runtime. 212 * 213 * @return The set of available currencies. The returned set could be empty if there is no 214 * currency data available. 215 * 216 * @stable ICU 49 217 */ 218 public static Set<Currency> getAvailableCurrencies() { 219 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance(); 220 List<String> list = info.currencies(CurrencyFilter.all()); 221 HashSet<Currency> resultSet = new HashSet<Currency>(list.size()); 222 for (String code : list) { 223 resultSet.add(getInstance(code)); 224 } 225 return resultSet; 226 } 227 228 private static final String EUR_STR = "EUR"; 229 private static final ICUCache<ULocale, String> currencyCodeCache = new SimpleCache<ULocale, String>(); 230 231 /** 232 * Instantiate a currency from resource data. 233 */ 234 /* package */ static Currency createCurrency(ULocale loc) { 235 236 String variant = loc.getVariant(); 237 if ("EURO".equals(variant)) { 238 return getInstance(EUR_STR); 239 } 240 241 String code = currencyCodeCache.get(loc); 242 if (code == null) { 243 String country = loc.getCountry(); 244 245 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance(); 246 List<String> list = info.currencies(CurrencyFilter.onRegion(country)); 247 if (list.size() > 0) { 248 code = list.get(0); 249 boolean isPreEuro = "PREEURO".equals(variant); 250 if (isPreEuro && EUR_STR.equals(code)) { 251 if (list.size() < 2) { 252 return null; 253 } 254 code = list.get(1); 255 } 256 } else { 257 return null; 258 } 259 currencyCodeCache.put(loc, code); 260 } 261 return getInstance(code); 262 } 263 264 /** 265 * Returns a currency object given an ISO 4217 3-letter code. 266 * @param theISOCode the iso code 267 * @return the currency for this iso code 268 * @throws NullPointerException if <code>theISOCode</code> is null. 269 * @throws IllegalArgumentException if <code>theISOCode</code> is not a 270 * 3-letter alpha code. 271 * @stable ICU 2.2 272 */ 273 public static Currency getInstance(String theISOCode) { 274 if (theISOCode == null) { 275 throw new NullPointerException("The input currency code is null."); 276 } 277 if (!isAlpha3Code(theISOCode)) { 278 throw new IllegalArgumentException( 279 "The input currency code is not 3-letter alphabetic code."); 280 } 281 return (Currency) MeasureUnit.internalGetInstance("currency", theISOCode.toUpperCase(Locale.ENGLISH)); 282 } 283 284 285 private static boolean isAlpha3Code(String code) { 286 if (code.length() != 3) { 287 return false; 288 } else { 289 for (int i = 0; i < 3; i++) { 290 char ch = code.charAt(i); 291 if (ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') { 292 return false; 293 } 294 } 295 } 296 return true; 297 } 298 299 /** 300 * Registers a new currency for the provided locale. The returned object 301 * is a key that can be used to unregister this currency object. 302 * 303 * <p>Because ICU may choose to cache Currency objects internally, this must 304 * be called at application startup, prior to any calls to 305 * Currency.getInstance to avoid undefined behavior. 306 * 307 * @param currency the currency to register 308 * @param locale the ulocale under which to register the currency 309 * @return a registry key that can be used to unregister this currency 310 * @see #unregister 311 * @stable ICU 3.2 312 */ 313 public static Object registerInstance(Currency currency, ULocale locale) { 314 return getShim().registerInstance(currency, locale); 315 } 316 317 /** 318 * Unregister the currency associated with this key (obtained from 319 * registerInstance). 320 * @param registryKey the registry key returned from registerInstance 321 * @see #registerInstance 322 * @stable ICU 2.6 323 */ 324 public static boolean unregister(Object registryKey) { 325 if (registryKey == null) { 326 throw new IllegalArgumentException("registryKey must not be null"); 327 } 328 if (shim == null) { 329 return false; 330 } 331 return shim.unregister(registryKey); 332 } 333 334 /** 335 * Return an array of the locales for which a currency 336 * is defined. 337 * @return an array of the available locales 338 * @stable ICU 2.2 339 */ 340 public static Locale[] getAvailableLocales() { 341 if (shim == null) { 342 return ICUResourceBundle.getAvailableLocales(); 343 } else { 344 return shim.getAvailableLocales(); 345 } 346 } 347 348 /** 349 * Return an array of the ulocales for which a currency 350 * is defined. 351 * @return an array of the available ulocales 352 * @stable ICU 3.2 353 */ 354 public static ULocale[] getAvailableULocales() { 355 if (shim == null) { 356 return ICUResourceBundle.getAvailableULocales(); 357 } else { 358 return shim.getAvailableULocales(); 359 } 360 } 361 362 // end registry stuff 363 364 /** 365 * Given a key and a locale, returns an array of values for the key for which data 366 * exists. If commonlyUsed is true, these are the values that typically are used 367 * with this locale, otherwise these are all values for which data exists. 368 * This is a common service API. 369 * <p> 370 * The only supported key is "currency", other values return an empty array. 371 * <p> 372 * Currency information is based on the region of the locale. If the locale does not 373 * indicate a region, {@link ULocale#addLikelySubtags(ULocale)} is used to infer a region, 374 * except for the 'und' locale. 375 * <p> 376 * If commonlyUsed is true, only the currencies known to be in use as of the current date 377 * are returned. When there are more than one, these are returned in preference order 378 * (typically, this occurs when a country is transitioning to a new currency, and the 379 * newer currency is preferred), see 380 * <a href="http://unicode.org/reports/tr35/#Supplemental_Currency_Data">Unicode TR#35 Sec. C1</a>. 381 * If commonlyUsed is false, all currencies ever used in any locale are returned, in no 382 * particular order. 383 * 384 * @param key key whose values to look up. the only recognized key is "currency" 385 * @param locale the locale 386 * @param commonlyUsed if true, return only values that are currently used in the locale. 387 * Otherwise returns all values. 388 * @return an array of values for the given key and the locale. If there is no data, the 389 * array will be empty. 390 * @stable ICU 4.2 391 */ 392 public static final String[] getKeywordValuesForLocale(String key, ULocale locale, 393 boolean commonlyUsed) { 394 395 // The only keyword we recognize is 'currency' 396 if (!"currency".equals(key)) { 397 return EMPTY_STRING_ARRAY; 398 } 399 400 if (!commonlyUsed) { 401 // Behavior change from 4.3.3, no longer sort the currencies 402 return getAllTenderCurrencies().toArray(new String[0]); 403 } 404 405 // Don't resolve region if the requested locale is 'und', it will resolve to US 406 // which we don't want. 407 String prefRegion = locale.getCountry(); 408 if (prefRegion.length() == 0) { 409 if (UND.equals(locale)) { 410 return EMPTY_STRING_ARRAY; 411 } 412 ULocale loc = ULocale.addLikelySubtags(locale); 413 prefRegion = loc.getCountry(); 414 } 415 416 CurrencyFilter filter = CurrencyFilter.now().withRegion(prefRegion); 417 418 // currencies are in region's preferred order when we're filtering on region, which 419 // matches our spec 420 List<String> result = getTenderCurrencies(filter); 421 422 // No fallback anymore (change from 4.3.3) 423 if (result.size() == 0) { 424 return EMPTY_STRING_ARRAY; 425 } 426 427 return result.toArray(new String[result.size()]); 428 } 429 430 private static final ULocale UND = new ULocale("und"); 431 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 432 433 /** 434 * Returns the ISO 4217 3-letter code for this currency object. 435 * @stable ICU 2.2 436 */ 437 public String getCurrencyCode() { 438 return subType; 439 } 440 441 /** 442 * Returns the ISO 4217 numeric code for this currency object. 443 * <p>Note: If the ISO 4217 numeric code is not assigned for the currency or 444 * the currency is unknown, this method returns 0.</p> 445 * @return The ISO 4217 numeric code of this currency. 446 * @stable ICU 49 447 */ 448 public int getNumericCode() { 449 int result = 0; 450 try { 451 UResourceBundle bundle = UResourceBundle.getBundleInstance( 452 ICUResourceBundle.ICU_BASE_NAME, 453 "currencyNumericCodes", 454 ICUResourceBundle.ICU_DATA_CLASS_LOADER); 455 UResourceBundle codeMap = bundle.get("codeMap"); 456 UResourceBundle numCode = codeMap.get(subType); 457 result = numCode.getInt(); 458 } catch (MissingResourceException e) { 459 // fall through 460 } 461 return result; 462 } 463 464 /** 465 * Convenience and compatibility override of getName that 466 * requests the symbol name for the default <code>DISPLAY</code> locale. 467 * @see #getName 468 * @see Category#DISPLAY 469 * @stable ICU 3.4 470 */ 471 public String getSymbol() { 472 return getSymbol(ULocale.getDefault(Category.DISPLAY)); 473 } 474 475 /** 476 * Convenience and compatibility override of getName that 477 * requests the symbol name. 478 * @param loc the Locale for the symbol 479 * @see #getName 480 * @stable ICU 3.4 481 */ 482 public String getSymbol(Locale loc) { 483 return getSymbol(ULocale.forLocale(loc)); 484 } 485 486 /** 487 * Convenience and compatibility override of getName that 488 * requests the symbol name. 489 * @param uloc the ULocale for the symbol 490 * @see #getName 491 * @stable ICU 3.4 492 */ 493 public String getSymbol(ULocale uloc) { 494 return getName(uloc, SYMBOL_NAME, new boolean[1]); 495 } 496 497 /** 498 * Returns the display name for the given currency in the 499 * given locale. 500 * This is a convenient method for 501 * getName(ULocale, int, boolean[]); 502 * @stable ICU 3.2 503 */ 504 public String getName(Locale locale, 505 int nameStyle, 506 boolean[] isChoiceFormat) { 507 return getName(ULocale.forLocale(locale), nameStyle, isChoiceFormat); 508 } 509 510 /** 511 * Returns the display name for the given currency in the 512 * given locale. For example, the display name for the USD 513 * currency object in the en_US locale is "$". 514 * @param locale locale in which to display currency 515 * @param nameStyle selector for which kind of name to return. 516 * The nameStyle should be either SYMBOL_NAME or 517 * LONG_NAME. Otherwise, throw IllegalArgumentException. 518 * @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true 519 * if the returned value is a ChoiceFormat pattern; otherwise it 520 * is set to false 521 * @return display string for this currency. If the resource data 522 * contains no entry for this currency, then the ISO 4217 code is 523 * returned. If isChoiceFormat[0] is true, then the result is a 524 * ChoiceFormat pattern. Otherwise it is a static string. <b>Note:</b> 525 * as of ICU 4.4, choice formats are not used, and the value returned 526 * in isChoiceFormat is always false. 527 * <p> 528 * @throws IllegalArgumentException if the nameStyle is not SYMBOL_NAME 529 * or LONG_NAME. 530 * @see #getName(ULocale, int, String, boolean[]) 531 * @stable ICU 3.2 532 */ 533 public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) { 534 if (!(nameStyle == SYMBOL_NAME || nameStyle == LONG_NAME)) { 535 throw new IllegalArgumentException("bad name style: " + nameStyle); 536 } 537 538 // We no longer support choice format data in names. Data should not contain 539 // choice patterns. 540 if (isChoiceFormat != null) { 541 isChoiceFormat[0] = false; 542 } 543 544 CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale); 545 return nameStyle == SYMBOL_NAME ? names.getSymbol(subType) : names.getName(subType); 546 } 547 548 /** 549 * Returns the display name for the given currency in the given locale. 550 * This is a convenience overload of getName(ULocale, int, String, boolean[]); 551 * @stable ICU 4.2 552 */ 553 public String getName(Locale locale, int nameStyle, String pluralCount, 554 boolean[] isChoiceFormat) { 555 return getName(ULocale.forLocale(locale), nameStyle, pluralCount, isChoiceFormat); 556 } 557 558 /** 559 * Returns the display name for the given currency in the 560 * given locale. For example, the SYMBOL_NAME for the USD 561 * currency object in the en_US locale is "$". 562 * The PLURAL_LONG_NAME for the USD currency object when the currency 563 * amount is plural is "US dollars", such as in "3.00 US dollars"; 564 * while the PLURAL_LONG_NAME for the USD currency object when the currency 565 * amount is singular is "US dollar", such as in "1.00 US dollar". 566 * @param locale locale in which to display currency 567 * @param nameStyle selector for which kind of name to return 568 * @param pluralCount plural count string for this locale 569 * @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true 570 * if the returned value is a ChoiceFormat pattern; otherwise it 571 * is set to false 572 * @return display string for this currency. If the resource data 573 * contains no entry for this currency, then the ISO 4217 code is 574 * returned. If isChoiceFormat[0] is true, then the result is a 575 * ChoiceFormat pattern. Otherwise it is a static string. <b>Note:</b> 576 * as of ICU 4.4, choice formats are not used, and the value returned 577 * in isChoiceFormat is always false. 578 * @throws IllegalArgumentException if the nameStyle is not SYMBOL_NAME, 579 * LONG_NAME, or PLURAL_LONG_NAME. 580 * @stable ICU 4.2 581 */ 582 public String getName(ULocale locale, int nameStyle, String pluralCount, 583 boolean[] isChoiceFormat) { 584 if (nameStyle != PLURAL_LONG_NAME) { 585 return getName(locale, nameStyle, isChoiceFormat); 586 } 587 588 // We no longer support choice format 589 if (isChoiceFormat != null) { 590 isChoiceFormat[0] = false; 591 } 592 593 CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale); 594 return names.getPluralName(subType, pluralCount); 595 } 596 597 /** 598 * Returns the display name for this currency in the default locale. 599 * If the resource data for the default locale contains no entry for this currency, 600 * then the ISO 4217 code is returned. 601 * <p> 602 * Note: This method is a convenience equivalent for 603 * {@link java.util.Currency#getDisplayName()} and is equivalent to 604 * <code>getName(Locale.getDefault(), LONG_NAME, null)</code>. 605 * 606 * @return The display name of this currency 607 * @see #getDisplayName(Locale) 608 * @see #getName(Locale, int, boolean[]) 609 * @stable ICU 49 610 */ 611 public String getDisplayName() { 612 return getName(Locale.getDefault(), LONG_NAME, null); 613 } 614 615 /** 616 * Returns the display name for this currency in the given locale. 617 * If the resource data for the given locale contains no entry for this currency, 618 * then the ISO 4217 code is returned. 619 * <p> 620 * Note: This method is a convenience equivalent for 621 * {@link java.util.Currency#getDisplayName(java.util.Locale)} and is equivalent 622 * to <code>getName(locale, LONG_NAME, null)</code>. 623 * 624 * @param locale locale in which to display currency 625 * @return The display name of this currency for the specified locale 626 * @see #getDisplayName(Locale) 627 * @see #getName(Locale, int, boolean[]) 628 * @stable ICU 49 629 */ 630 public String getDisplayName(Locale locale) { 631 return getName(locale, LONG_NAME, null); 632 } 633 634 /** 635 * Attempt to parse the given string as a currency, either as a 636 * display name in the given locale, or as a 3-letter ISO 4217 637 * code. If multiple display names match, then the longest one is 638 * selected. If both a display name and a 3-letter ISO code 639 * match, then the display name is preferred, unless it's length 640 * is less than 3. 641 * 642 * @param locale the locale of the display names to match 643 * @param text the text to parse 644 * @param type parse against currency type: LONG_NAME only or not 645 * @param pos input-output position; on input, the position within 646 * text to match; must have 0 <= pos.getIndex() < text.length(); 647 * on output, the position after the last matched character. If 648 * the parse fails, the position in unchanged upon output. 649 * @return the ISO 4217 code, as a string, of the best match, or 650 * null if there is no match 651 * 652 * @internal 653 * @deprecated This API is ICU internal only. 654 */ 655 @Deprecated 656 public static String parse(ULocale locale, String text, int type, ParsePosition pos) { 657 List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = CURRENCY_NAME_CACHE.get(locale); 658 if (currencyTrieVec == null) { 659 TextTrieMap<CurrencyStringInfo> currencyNameTrie = 660 new TextTrieMap<CurrencyStringInfo>(true); 661 TextTrieMap<CurrencyStringInfo> currencySymbolTrie = 662 new TextTrieMap<CurrencyStringInfo>(false); 663 currencyTrieVec = new ArrayList<TextTrieMap<CurrencyStringInfo>>(); 664 currencyTrieVec.add(currencySymbolTrie); 665 currencyTrieVec.add(currencyNameTrie); 666 setupCurrencyTrieVec(locale, currencyTrieVec); 667 CURRENCY_NAME_CACHE.put(locale, currencyTrieVec); 668 } 669 670 int maxLength = 0; 671 String isoResult = null; 672 673 // look for the names 674 TextTrieMap<CurrencyStringInfo> currencyNameTrie = currencyTrieVec.get(1); 675 CurrencyNameResultHandler handler = new CurrencyNameResultHandler(); 676 currencyNameTrie.find(text, pos.getIndex(), handler); 677 isoResult = handler.getBestCurrencyISOCode(); 678 maxLength = handler.getBestMatchLength(); 679 680 if (type != Currency.LONG_NAME) { // not long name only 681 TextTrieMap<CurrencyStringInfo> currencySymbolTrie = currencyTrieVec.get(0); 682 handler = new CurrencyNameResultHandler(); 683 currencySymbolTrie.find(text, pos.getIndex(), handler); 684 if (handler.getBestMatchLength() > maxLength) { 685 isoResult = handler.getBestCurrencyISOCode(); 686 maxLength = handler.getBestMatchLength(); 687 } 688 } 689 int start = pos.getIndex(); 690 pos.setIndex(start + maxLength); 691 return isoResult; 692 } 693 694 private static void setupCurrencyTrieVec(ULocale locale, 695 List<TextTrieMap<CurrencyStringInfo>> trieVec) { 696 697 TextTrieMap<CurrencyStringInfo> symTrie = trieVec.get(0); 698 TextTrieMap<CurrencyStringInfo> trie = trieVec.get(1); 699 700 CurrencyDisplayNames names = CurrencyDisplayNames.getInstance(locale); 701 for (Map.Entry<String, String> e : names.symbolMap().entrySet()) { 702 String symbol = e.getKey(); 703 String isoCode = e.getValue(); 704 // Register under not just symbol, but under every equivalent symbol as well 705 // e.g short width yen and long width yen. 706 for (String equivalentSymbol : EQUIVALENT_CURRENCY_SYMBOLS.get(symbol)) { 707 symTrie.put(equivalentSymbol, new CurrencyStringInfo(isoCode, symbol)); 708 } 709 } 710 for (Map.Entry<String, String> e : names.nameMap().entrySet()) { 711 String name = e.getKey(); 712 String isoCode = e.getValue(); 713 trie.put(name, new CurrencyStringInfo(isoCode, name)); 714 } 715 } 716 717 private static final class CurrencyStringInfo { 718 private String isoCode; 719 private String currencyString; 720 721 public CurrencyStringInfo(String isoCode, String currencyString) { 722 this.isoCode = isoCode; 723 this.currencyString = currencyString; 724 } 725 726 public String getISOCode() { 727 return isoCode; 728 } 729 730 @SuppressWarnings("unused") 731 public String getCurrencyString() { 732 return currencyString; 733 } 734 } 735 736 private static class CurrencyNameResultHandler 737 implements TextTrieMap.ResultHandler<CurrencyStringInfo> { 738 // The length of longest matching key 739 private int bestMatchLength; 740 // The currency ISO code of longest matching key 741 private String bestCurrencyISOCode; 742 743 // As the trie is traversed, handlePrefixMatch is called at each node. matchLength is the 744 // length length of the key at the current node; values is the list of all the values mapped to 745 // that key. matchLength increases with each call as trie is traversed. 746 public boolean handlePrefixMatch(int matchLength, Iterator<CurrencyStringInfo> values) { 747 if (values.hasNext()) { 748 // Since the best match criteria is only based on length of key in trie and since all the 749 // values are mapped to the same key, we only need to examine the first value. 750 bestCurrencyISOCode = values.next().getISOCode(); 751 bestMatchLength = matchLength; 752 } 753 return true; 754 } 755 756 public String getBestCurrencyISOCode() { 757 return bestCurrencyISOCode; 758 } 759 760 public int getBestMatchLength() { 761 return bestMatchLength; 762 } 763 } 764 765 /** 766 * Returns the number of the number of fraction digits that should 767 * be displayed for this currency. 768 * This is equivalent to getDefaultFractionDigits(CurrencyUsage.STANDARD); 769 * @return a non-negative number of fraction digits to be 770 * displayed 771 * @stable ICU 2.2 772 */ 773 public int getDefaultFractionDigits() { 774 return getDefaultFractionDigits(CurrencyUsage.STANDARD); 775 } 776 777 /** 778 * Returns the number of the number of fraction digits that should 779 * be displayed for this currency with Usage. 780 * @param Usage the usage of currency(Standard or Cash) 781 * @return a non-negative number of fraction digits to be 782 * displayed 783 * @stable ICU 54 784 */ 785 public int getDefaultFractionDigits(CurrencyUsage Usage) { 786 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance(); 787 CurrencyDigits digits = info.currencyDigits(subType, Usage); 788 return digits.fractionDigits; 789 } 790 791 /** 792 * Returns the rounding increment for this currency, or 0.0 if no 793 * rounding is done by this currency. 794 * This is equivalent to getRoundingIncrement(CurrencyUsage.STANDARD); 795 * @return the non-negative rounding increment, or 0.0 if none 796 * @stable ICU 2.2 797 */ 798 public double getRoundingIncrement() { 799 return getRoundingIncrement(CurrencyUsage.STANDARD); 800 } 801 802 /** 803 * Returns the rounding increment for this currency, or 0.0 if no 804 * rounding is done by this currency with the Usage. 805 * @param Usage the usage of currency(Standard or Cash) 806 * @return the non-negative rounding increment, or 0.0 if none 807 * @stable ICU 54 808 */ 809 public double getRoundingIncrement(CurrencyUsage Usage) { 810 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance(); 811 CurrencyDigits digits = info.currencyDigits(subType, Usage); 812 813 int data1 = digits.roundingIncrement; 814 815 // If there is no rounding return 0.0 to indicate no rounding. 816 // This is the high-runner case, by far. 817 if (data1 == 0) { 818 return 0.0; 819 } 820 821 int data0 = digits.fractionDigits; 822 823 // If the meta data is invalid, return 0.0 to indicate no rounding. 824 if (data0 < 0 || data0 >= POW10.length) { 825 return 0.0; 826 } 827 828 // Return data[1] / 10^(data[0]). The only actual rounding data, 829 // as of this writing, is CHF { 2, 25 }. 830 return (double) data1 / POW10[data0]; 831 } 832 833 /** 834 * Returns the ISO 4217 code for this currency. 835 * @stable ICU 2.2 836 */ 837 public String toString() { 838 return subType; 839 } 840 841 /** 842 * Constructs a currency object for the given ISO 4217 3-letter 843 * code. This constructor assumes that the code is valid. 844 * 845 * @param theISOCode The iso code used to construct the currency. 846 * @stable ICU 3.4 847 */ 848 protected Currency(String theISOCode) { 849 super("currency", theISOCode); 850 851 // isoCode is kept for readResolve() and Currency class no longer 852 // use it. So this statement actually does not have any effect. 853 isoCode = theISOCode; 854 } 855 856 // POW10[i] = 10^i 857 private static final int[] POW10 = { 858 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 859 }; 860 861 862 private static SoftReference<List<String>> ALL_TENDER_CODES; 863 private static SoftReference<Set<String>> ALL_CODES_AS_SET; 864 /* 865 * Returns an unmodifiable String list including all known tender currency codes. 866 */ 867 private static synchronized List<String> getAllTenderCurrencies() { 868 List<String> all = (ALL_TENDER_CODES == null) ? null : ALL_TENDER_CODES.get(); 869 if (all == null) { 870 // Filter out non-tender currencies which have "from" date set to 9999-12-31 871 // CurrencyFilter has "to" value set to 9998-12-31 in order to exclude them 872 //CurrencyFilter filter = CurrencyFilter.onDateRange(null, new Date(253373299200000L)); 873 CurrencyFilter filter = CurrencyFilter.all(); 874 all = Collections.unmodifiableList(getTenderCurrencies(filter)); 875 ALL_TENDER_CODES = new SoftReference<List<String>>(all); 876 } 877 return all; 878 } 879 880 private static synchronized Set<String> getAllCurrenciesAsSet() { 881 Set<String> all = (ALL_CODES_AS_SET == null) ? null : ALL_CODES_AS_SET.get(); 882 if (all == null) { 883 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance(); 884 all = Collections.unmodifiableSet( 885 new HashSet<String>(info.currencies(CurrencyFilter.all()))); 886 ALL_CODES_AS_SET = new SoftReference<Set<String>>(all); 887 } 888 return all; 889 } 890 891 /** 892 * Queries if the given ISO 4217 3-letter code is available on the specified date range. 893 * <p> 894 * Note: For checking availability of a currency on a specific date, specify the date on both <code>from</code> and 895 * <code>to</code>. When both <code>from</code> and <code>to</code> are null, this method checks if the specified 896 * currency is available all time. 897 * 898 * @param code 899 * The ISO 4217 3-letter code. 900 * @param from 901 * The lower bound of the date range, inclusive. When <code>from</code> is null, check the availability 902 * of the currency any date before <code>to</code> 903 * @param to 904 * The upper bound of the date range, inclusive. When <code>to</code> is null, check the availability of 905 * the currency any date after <code>from</code> 906 * @return true if the given ISO 4217 3-letter code is supported on the specified date range. 907 * @throws IllegalArgumentException when <code>to</code> is before <code>from</code>. 908 * 909 * @stable ICU 4.6 910 */ 911 public static boolean isAvailable(String code, Date from, Date to) { 912 if (!isAlpha3Code(code)) { 913 return false; 914 } 915 916 if (from != null && to != null && from.after(to)) { 917 throw new IllegalArgumentException("To is before from"); 918 } 919 920 code = code.toUpperCase(Locale.ENGLISH); 921 boolean isKnown = getAllCurrenciesAsSet().contains(code); 922 if (isKnown == false) { 923 return false; 924 } else if (from == null && to == null) { 925 return true; 926 } 927 928 // If caller passed a date range, we cannot rely solely on the cache 929 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance(); 930 List<String> allActive = info.currencies( 931 CurrencyFilter.onDateRange(from, to).withCurrency(code)); 932 return allActive.contains(code); 933 } 934 935 /** 936 * Returns the list of remaining tender currencies after a filter is applied. 937 * @param filter the filter to apply to the tender currencies 938 * @return a list of tender currencies 939 */ 940 private static List<String> getTenderCurrencies(CurrencyFilter filter) { 941 CurrencyMetaInfo info = CurrencyMetaInfo.getInstance(); 942 return info.currencies(filter.withTender()); 943 } 944 945 private static final class EquivalenceRelation<T> { 946 947 private Map<T, Set<T>> data = new HashMap<T, Set<T>>(); 948 949 @SuppressWarnings("unchecked") // See ticket #11395, this is safe. 950 public EquivalenceRelation<T> add(T... items) { 951 Set<T> group = new HashSet<T>(); 952 for (T item : items) { 953 if (data.containsKey(item)) { 954 throw new IllegalArgumentException("All groups passed to add must be disjoint."); 955 } 956 group.add(item); 957 } 958 for (T item : items) { 959 data.put(item, group); 960 } 961 return this; 962 } 963 964 public Set<T> get(T item) { 965 Set<T> result = data.get(item); 966 if (result == null) { 967 return Collections.singleton(item); 968 } 969 return Collections.unmodifiableSet(result); 970 } 971 } 972 973 private Object writeReplace() throws ObjectStreamException { 974 return new MeasureUnitProxy(type, subType); 975 } 976 977 // For backward compatibility only 978 /** 979 * ISO 4217 3-letter code. 980 */ 981 private final String isoCode; 982 983 private Object readResolve() throws ObjectStreamException { 984 // The old isoCode field used to determine the currency. 985 return Currency.getInstance(isoCode); 986 } 987} 988//eof 989