2 *******************************************************************************
\r
3 * Copyright (C) 2001-2009, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.util;
\r
9 import java.io.Serializable;
\r
10 import java.text.ChoiceFormat;
\r
11 import java.text.ParsePosition;
\r
12 import java.util.ArrayList;
\r
13 import java.util.Date;
\r
14 import java.util.Enumeration;
\r
15 import java.util.HashMap;
\r
16 import java.util.HashSet;
\r
17 import java.util.Iterator;
\r
18 import java.util.LinkedList;
\r
19 import java.util.List;
\r
20 import java.util.Locale;
\r
21 import java.util.MissingResourceException;
\r
22 import java.util.Vector;
\r
24 import com.ibm.icu.impl.ICUCache;
\r
25 import com.ibm.icu.impl.ICUDebug;
\r
26 import com.ibm.icu.impl.ICUResourceBundle;
\r
27 import com.ibm.icu.impl.SimpleCache;
\r
28 import com.ibm.icu.impl.TextTrieMap;
\r
31 * A class encapsulating a currency, as defined by ISO 4217. A
\r
32 * <tt>Currency</tt> object can be created given a <tt>Locale</tt> or
\r
33 * given an ISO 4217 code. Once created, the <tt>Currency</tt> object
\r
34 * can return various data necessary to its proper display:
\r
36 * <ul><li>A display symbol, for a specific locale
\r
37 * <li>The number of fraction digits to display
\r
38 * <li>A rounding increment
\r
41 * The <tt>DecimalFormat</tt> class uses these data to display
\r
44 * <p>Note: This class deliberately resembles
\r
45 * <tt>java.util.Currency</tt> but it has a completely independent
\r
46 * implementation, and adds features not present in the JDK.
\r
50 public class Currency extends MeasureUnit implements Serializable {
\r
51 // using serialver from jdk1.4.2_05
\r
52 private static final long serialVersionUID = -5839973855554750484L;
\r
53 private static final boolean DEBUG = ICUDebug.enabled("currency");
\r
55 // Cache to save currency name trie
\r
56 private static ICUCache CURRENCY_NAME_CACHE = new SimpleCache();
\r
59 * ISO 4217 3-letter code.
\r
61 private String isoCode;
\r
64 * Selector for getName() indicating a symbolic name for a
\r
65 * currency, such as "$" for USD.
\r
68 public static final int SYMBOL_NAME = 0;
\r
71 * Selector for ucurr_getName indicating the long name for a
\r
72 * currency, such as "US Dollar" for USD.
\r
75 public static final int LONG_NAME = 1;
\r
78 * Selector for getName() indicating the plural long name for a
\r
79 * currency, such as "US dollar" for USD in "1 US dollar",
\r
80 * and "US dollars" for USD in "2 US dollars".
\r
82 * @provisional This API might change or be removed in a future release.
\r
84 public static final int PLURAL_LONG_NAME = 2;
\r
86 // begin registry stuff
\r
88 // shim for service code
\r
89 /* package */ static abstract class ServiceShim {
\r
90 abstract ULocale[] getAvailableULocales();
\r
91 abstract Locale[] getAvailableLocales();
\r
92 abstract Currency createInstance(ULocale l);
\r
93 abstract Object registerInstance(Currency c, ULocale l);
\r
94 abstract boolean unregister(Object f);
\r
97 private static ServiceShim shim;
\r
98 private static ServiceShim getShim() {
\r
99 // Note: this instantiation is safe on loose-memory-model configurations
\r
100 // despite lack of synchronization, since the shim instance has no state--
\r
101 // it's all in the class init. The worst problem is we might instantiate
\r
102 // two shim instances, but they'll share the same state so that's ok.
\r
103 if (shim == null) {
\r
105 Class cls = Class.forName("com.ibm.icu.util.CurrencyServiceShim");
\r
106 shim = (ServiceShim)cls.newInstance();
\r
108 catch (Exception e) {
\r
110 e.printStackTrace();
\r
112 throw new RuntimeException(e.getMessage());
\r
119 * Returns a currency object for the default currency in the given
\r
121 * @param locale the locale
\r
122 * @return the currency object for this locale
\r
125 public static Currency getInstance(Locale locale) {
\r
126 return getInstance(ULocale.forLocale(locale));
\r
130 * Returns a currency object for the default currency in the given
\r
134 public static Currency getInstance(ULocale locale) {
\r
135 String currency = locale.getKeywordValue("currency");
\r
136 if (currency != null) {
\r
137 return getInstance(currency);
\r
140 if (shim == null) {
\r
141 return createCurrency(locale);
\r
144 return shim.createInstance(locale);
\r
148 * Returns an array of Strings which contain the currency
\r
149 * identifiers which are valid for the given locale on the
\r
151 * @param loc the locale for which to retrieve currency codes.
\r
152 * @param d the date for which to retrieve currency codes for the given locale.
\r
153 * @return The array of ISO currency codes.
\r
156 public static String[] getAvailableCurrencyCodes(ULocale loc, Date d)
\r
159 String country = loc.getCountry();
\r
160 long dateL = d.getTime();
\r
161 long mask = 4294967295L;
\r
163 Vector currCodeVector = new Vector();
\r
165 // Get supplementalData
\r
166 ICUResourceBundle bundle = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
\r
167 "supplementalData",
\r
168 ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
169 if (bundle == null)
\r
175 // Work with the supplementalData
\r
178 // Process each currency to see which one is valid for the given date.
\r
179 // Some regions can have more than one current currency in use for
\r
181 UResourceBundle cm = bundle.get("CurrencyMap");
\r
182 UResourceBundle countryArray = cm.get(country);
\r
184 // Get valid currencies
\r
185 for (int i = 0; i < countryArray.getSize(); i++)
\r
187 // get the currency resource
\r
188 UResourceBundle currencyReq = countryArray.get(i);
\r
189 String curriso = null;
\r
190 curriso = currencyReq.getString("id");
\r
192 // get the from date
\r
194 UResourceBundle fromRes = currencyReq.get("from");
\r
195 int[] fromArray = fromRes.getIntVector();
\r
196 fromDate = (long)fromArray[0] << 32;
\r
197 fromDate |= ((long)fromArray[1] & mask);
\r
199 // get the to date and check the date range
\r
200 if (currencyReq.getSize() > 2)
\r
203 UResourceBundle toRes = currencyReq.get("to");
\r
204 int[] toArray = toRes.getIntVector();
\r
205 toDate = (long)toArray[0] << 32;
\r
206 toDate |= ((long)toArray[1] & mask);
\r
208 if ((fromDate <= dateL) && (dateL < toDate))
\r
210 currCodeVector.addElement(curriso);
\r
215 if (fromDate <= dateL)
\r
217 currCodeVector.addElement(curriso);
\r
223 // return the String array if we have matches
\r
224 currCodeVector.trimToSize();
\r
225 if (currCodeVector.size() != 0)
\r
227 return ((String[])currCodeVector.toArray(new String[0]));
\r
231 catch (MissingResourceException ex)
\r
233 // We don't know about this region.
\r
234 // As of CLDR 1.5.1, the data includes deprecated region history too.
\r
235 // So if we get here, either the region doesn't exist, or the data is really bad.
\r
236 // Deprecated regions should return the last valid currency for that region in the data.
\r
237 // We don't try to resolve it to a new region.
\r
240 // if we get this far, return nothing
\r
244 private static final String EUR_STR = "EUR";
\r
246 * Instantiate a currency from a resource bundle found in Locale loc.
\r
248 /* package */ static Currency createCurrency(ULocale loc) {
\r
249 String country = loc.getCountry();
\r
250 String variant = loc.getVariant();
\r
251 boolean isPreEuro = variant.equals("PREEURO");
\r
252 boolean isEuro = variant.equals("EURO");
\r
253 // TODO: ICU4C has service registration, and the currency is requested from the service here.
\r
254 ICUResourceBundle bundle = (ICUResourceBundle) ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,"supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
256 //throw new MissingResourceException()
\r
260 UResourceBundle cm = bundle.get("CurrencyMap");
\r
261 String curriso = null;
\r
262 UResourceBundle countryArray = cm.get(country);
\r
263 // Some regions can have more than one current currency in use.
\r
264 // The latest default currency is always the first one.
\r
265 UResourceBundle currencyReq = countryArray.get(0);
\r
266 curriso = currencyReq.getString("id");
\r
267 if (isPreEuro && curriso.equals(EUR_STR)) {
\r
268 currencyReq = countryArray.get(1);
\r
269 curriso = currencyReq.getString("id");
\r
274 if (curriso != null) {
\r
275 return new Currency(curriso);
\r
277 } catch (MissingResourceException ex) {
\r
278 // We don't know about this region.
\r
279 // As of CLDR 1.5.1, the data includes deprecated region history too.
\r
280 // So if we get here, either the region doesn't exist, or the data is really bad.
\r
281 // Deprecated regions should return the last valid currency for that region in the data.
\r
282 // We don't try to resolve it to a new region.
\r
288 * Returns a currency object given an ISO 4217 3-letter code.
\r
289 * @param theISOCode the iso code
\r
290 * @return the currency for this iso code
\r
291 * @throws NullPoninterException if <code>theISOCode</code> is null.
\r
292 * @throws IllegalArgumentException if <code>theISOCode</code> is not a
\r
293 * 3-letter alpha code.
\r
296 public static Currency getInstance(String theISOCode) {
\r
297 if (theISOCode == null) {
\r
298 throw new NullPointerException("The input currency code is null.");
\r
300 boolean is3alpha = true;
\r
301 if (theISOCode.length() != 3) {
\r
304 for (int i = 0; i < 3; i++) {
\r
305 char ch = theISOCode.charAt(i);
\r
306 if (ch < 'A' || (ch > 'Z' && ch < 'a') || ch > 'z') {
\r
313 throw new IllegalArgumentException(
\r
314 "The input currency code is not 3-letter alphabetic code.");
\r
316 return new Currency(theISOCode.toUpperCase(Locale.US));
\r
320 * Registers a new currency for the provided locale. The returned object
\r
321 * is a key that can be used to unregister this currency object.
\r
322 * @param currency the currency to register
\r
323 * @param locale the ulocale under which to register the currency
\r
324 * @return a registry key that can be used to unregister this currency
\r
328 public static Object registerInstance(Currency currency, ULocale locale) {
\r
329 return getShim().registerInstance(currency, locale);
\r
333 * Unregister the currency associated with this key (obtained from
\r
334 * registerInstance).
\r
335 * @param registryKey the registry key returned from registerInstance
\r
336 * @see #registerInstance
\r
339 public static boolean unregister(Object registryKey) {
\r
340 if (registryKey == null) {
\r
341 throw new IllegalArgumentException("registryKey must not be null");
\r
343 if (shim == null) {
\r
346 return shim.unregister(registryKey);
\r
350 * Return an array of the locales for which a currency
\r
352 * @return an array of the available locales
\r
355 public static Locale[] getAvailableLocales() {
\r
356 if (shim == null) {
\r
357 return ICUResourceBundle.getAvailableLocales(ICUResourceBundle.ICU_BASE_NAME);
\r
359 return shim.getAvailableLocales();
\r
364 * Return an array of the ulocales for which a currency
\r
366 * @return an array of the available ulocales
\r
369 public static ULocale[] getAvailableULocales() {
\r
370 if (shim == null) {
\r
371 return ICUResourceBundle.getAvailableULocales(ICUResourceBundle.ICU_BASE_NAME);
\r
373 return shim.getAvailableULocales();
\r
377 // end registry stuff
\r
380 * Given a key and a locale, returns an array of string values in a preferred
\r
381 * order that would make a difference. These are all and only those values where
\r
382 * the open (creation) of the service with the locale formed from the input locale
\r
383 * plus input keyword and that value has different behavior than creation with the
\r
384 * input locale alone.
\r
385 * @param key one of the keys supported by this service. For now, only
\r
386 * "currency" is supported.
\r
387 * @param locale the locale
\r
388 * @param commonlyUsed if set to true it will return only commonly used values
\r
389 * with the given locale in preferred order. Otherwise,
\r
390 * it will return all the available values for the locale.
\r
391 * @return an array of string values for the given key and the locale.
\r
393 * @provisional This API might change or be removed in a future release.
\r
395 public static final String[] getKeywordValuesForLocale(String key, ULocale locale, boolean commonlyUsed) {
\r
397 String prefRegion = locale.getCountry();
\r
398 if (prefRegion.length() == 0){
\r
399 ULocale loc = ULocale.addLikelySubtags(locale);
\r
400 prefRegion = loc.getCountry();
\r
403 // Read values from supplementalData
\r
404 LinkedList values = new LinkedList();
\r
405 LinkedList otherValues = new LinkedList();
\r
407 UResourceBundle bundle = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "supplementalData");
\r
408 bundle = bundle.get("CurrencyMap");
\r
409 Enumeration keyEnum = bundle.getKeys();
\r
410 boolean done = false;
\r
411 while (keyEnum.hasMoreElements() && !done) {
\r
412 String region = (String)keyEnum.nextElement();
\r
413 boolean isPrefRegion = prefRegion.equals(region);
\r
414 if (!isPrefRegion && commonlyUsed) {
\r
415 // With commonlyUsed=true, we do not put
\r
416 // currencies for other regions in the
\r
420 UResourceBundle regbndl = bundle.get(region);
\r
421 for (int i = 0; i < regbndl.getSize(); i++) {
\r
422 UResourceBundle curbndl = regbndl.get(i);
\r
423 if (curbndl.getType() != UResourceBundle.TABLE) {
\r
424 // Currently, an empty ARRAY is mixed in..
\r
427 String curID = curbndl.getString("id");
\r
428 boolean hasTo = false;
\r
430 UResourceBundle to = curbndl.get("to");
\r
434 } catch (MissingResourceException e) {
\r
435 // Do nothing here...
\r
437 if (isPrefRegion && !hasTo && !values.contains(curID)) {
\r
438 // Currently active currency for the target country
\r
440 } else if (!otherValues.contains(curID) && !commonlyUsed){
\r
441 otherValues.add(curID);
\r
445 if (commonlyUsed) {
\r
446 if (values.size() == 0) {
\r
447 // This could happen if no valid region is supplied in the input
\r
448 // locale. In this case, we use the CLDR's default.
\r
449 return getKeywordValuesForLocale(key, new ULocale("und"), true);
\r
452 // Consolidate the list
\r
453 Iterator itr = otherValues.iterator();
\r
454 while (itr.hasNext()) {
\r
455 String curID = (String)itr.next();
\r
456 if (!values.contains(curID)) {
\r
461 return (String[]) values.toArray(new String[values.size()]);
\r
465 * Return a hashcode for this currency.
\r
468 public int hashCode() {
\r
469 return isoCode.hashCode();
\r
473 * Return true if rhs is a Currency instance,
\r
474 * is non-null, and has the same currency code.
\r
477 public boolean equals(Object rhs) {
\r
478 if (rhs == null) return false;
\r
479 if (rhs == this) return true;
\r
481 Currency c = (Currency) rhs;
\r
482 return isoCode.equals(c.isoCode);
\r
484 catch (ClassCastException e) {
\r
490 * Returns the ISO 4217 3-letter code for this currency object.
\r
493 public String getCurrencyCode() {
\r
498 * Convenience and compatibility override of getName that
\r
499 * requests the symbol name.
\r
503 public String getSymbol() {
\r
504 return getSymbol(ULocale.getDefault());
\r
508 * Convenience and compatibility override of getName that
\r
509 * requests the symbol name.
\r
510 * @param loc the Locale for the symbol
\r
514 public String getSymbol(Locale loc) {
\r
515 return getSymbol(ULocale.forLocale(loc));
\r
519 * Convenience and compatibility override of getName that
\r
520 * requests the symbol name.
\r
521 * @param uloc the ULocale for the symbol
\r
525 public String getSymbol(ULocale uloc) {
\r
526 return getName(uloc, SYMBOL_NAME, new boolean[1]);
\r
530 * Returns the display name for the given currency in the
\r
532 * This is a convenient method for
\r
533 * getName(ULocale, int, boolean[]);
\r
536 public String getName(Locale locale,
\r
538 boolean[] isChoiceFormat) {
\r
539 return getName(ULocale.forLocale(locale), nameStyle, isChoiceFormat);
\r
543 * Returns the display name for the given currency in the
\r
544 * given locale. For example, the display name for the USD
\r
545 * currency object in the en_US locale is "$".
\r
546 * @param locale locale in which to display currency
\r
547 * @param nameStyle selector for which kind of name to return.
\r
548 * The nameStyle should be either SYMBOL_NAME or
\r
549 * LONG_NAME. Otherwise, throw IllegalArgumentException.
\r
550 * @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true
\r
551 * if the returned value is a ChoiceFormat pattern; otherwise it
\r
553 * @return display string for this currency. If the resource data
\r
554 * contains no entry for this currency, then the ISO 4217 code is
\r
555 * returned. If isChoiceFormat[0] is true, then the result is a
\r
556 * ChoiceFormat pattern. Otherwise it is a static string.
\r
557 * @throws IllegalArgumentException if the nameStyle is not SYMBOL_NAME
\r
561 public String getName(ULocale locale,
\r
563 boolean[] isChoiceFormat) {
\r
565 // Look up the Currencies resource for the given locale. The
\r
566 // Currencies locale data looks like this:
\r
569 //| USD { "US$", "US Dollar" }
\r
570 //| CHF { "Sw F", "Swiss Franc" }
\r
571 //| INR { "=0#Rs|1#Re|1<Rs", "=0#Rupees|1#Rupee|1<Rupees" }
\r
576 if (nameStyle < 0 || nameStyle > 1) {
\r
577 throw new IllegalArgumentException();
\r
583 UResourceBundle rb = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);
\r
584 ICUResourceBundle currencies = (ICUResourceBundle)rb.get("Currencies");
\r
586 // Fetch resource with multi-level resource inheritance fallback
\r
587 s = currencies.getWithFallback(isoCode).getString(nameStyle);
\r
588 }catch (MissingResourceException e) {
\r
589 //TODO what should be done here?
\r
592 // Determine if this is a ChoiceFormat pattern. One leading mark
\r
593 // indicates a ChoiceFormat. Two indicates a static string that
\r
594 // starts with a mark. In either case, the first mark is ignored,
\r
595 // if present. Marks in the rest of the string have no special
\r
597 isChoiceFormat[0] = false;
\r
600 while (i < s.length() && s.charAt(i) == '=' && i < 2) {
\r
603 isChoiceFormat[0]= (i == 1);
\r
605 // Skip over first mark
\r
606 s = s.substring(1);
\r
611 // If we fail to find a match, use the ISO 4217 code
\r
616 * Returns the display name for the given currency in the
\r
618 * This is a convenient method of
\r
619 * getName(ULocale, int, String, boolean[]);
\r
621 * @provisional This API might change or be removed in a future release.
\r
623 public String getName(Locale locale,
\r
625 String pluralCount,
\r
626 boolean[] isChoiceFormat) {
\r
627 return getName(ULocale.forLocale(locale), nameStyle,
\r
628 pluralCount, isChoiceFormat);
\r
632 * Returns the display name for the given currency in the
\r
633 * given locale. For example, the SYMBOL_NAME for the USD
\r
634 * currency object in the en_US locale is "$".
\r
635 * The PLURAL_LONG_NAME for the USD currency object when the currency
\r
636 * amount is plural is "US dollars", such as in "3.00 US dollars";
\r
637 * while the PLURAL_LONG_NAME for the USD currency object when the currency
\r
638 * amount is singular is "US dollar", such as in "1.00 US dollar".
\r
639 * @param locale locale in which to display currency
\r
640 * @param nameStyle selector for which kind of name to return
\r
641 * @param pluralCount plural count string for this locale
\r
642 * @param isChoiceFormat fill-in; isChoiceFormat[0] is set to true
\r
643 * if the returned value is a ChoiceFormat pattern; otherwise it
\r
645 * @return display string for this currency. If the resource data
\r
646 * contains no entry for this currency, then the ISO 4217 code is
\r
647 * returned. If isChoiceFormat[0] is true, then the result is a
\r
648 * ChoiceFormat pattern. Otherwise it is a static string.
\r
649 * @throws IllegalArgumentException if the nameStyle is not SYMBOL_NAME
\r
650 * or LONG_NAME, or PLURAL_LONG_NAME.
\r
652 * @provisional This API might change or be removed in a future release.
\r
654 public String getName(ULocale locale,
\r
656 String pluralCount,
\r
657 boolean[] isChoiceFormat) {
\r
658 if (nameStyle != PLURAL_LONG_NAME) {
\r
659 return getName(locale, nameStyle, isChoiceFormat);
\r
662 // Look up the CurrencyPlurals resource for the given locale. The
\r
663 // CurrencyPlurals locale data looks like this:
\r
665 //| CurrencyPlurals {
\r
667 //| one{"US dollar"}
\r
668 //| other{"US dollars"}
\r
674 // Algorithm detail: http://unicode.org/reports/tr35/#Currencies
\r
675 // especially the fallback rule.
\r
677 ICUResourceBundle isoCodeBundle;
\r
678 // search at run time, not saved in initialization
\r
680 UResourceBundle rb = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);
\r
681 // get handles fallback
\r
682 ICUResourceBundle currencies = (ICUResourceBundle)rb.get("CurrencyPlurals");
\r
684 // Fetch resource with multi-level resource inheritance fallback
\r
685 isoCodeBundle = currencies.getWithFallback(isoCode);
\r
686 } catch (MissingResourceException e) {
\r
687 // if there is no CurrencyPlurals defined or no plural long names
\r
688 // defined in the locale chain, fall back to long name.
\r
689 return getName(locale, LONG_NAME, isChoiceFormat);
\r
692 s = isoCodeBundle.getStringWithFallback(pluralCount);
\r
693 } catch (MissingResourceException e1) {
\r
695 // if there is no name corresponding to 'pluralCount' defined,
\r
696 // fall back to name corresponding to "other".
\r
697 s = isoCodeBundle.getStringWithFallback("other");
\r
698 } catch (MissingResourceException e) {
\r
699 // if there is no name corresponding to plural count "other",
\r
700 // fall back to long name.
\r
701 return getName(locale, LONG_NAME, isChoiceFormat);
\r
704 // No support for choice format for getting plural currency names.
\r
708 // If we fail to find a match, use the ISO 4217 code
\r
713 * Attempt to parse the given string as a currency, either as a
\r
714 * display name in the given locale, or as a 3-letter ISO 4217
\r
715 * code. If multiple display names match, then the longest one is
\r
716 * selected. If both a display name and a 3-letter ISO code
\r
717 * match, then the display name is preferred, unless it's length
\r
720 * @param locale the locale of the display names to match
\r
721 * @param text the text to parse
\r
722 * @param type parse against currency type: LONG_NAME only or not
\r
723 * @param pos input-output position; on input, the position within
\r
724 * text to match; must have 0 <= pos.getIndex() < text.length();
\r
725 * on output, the position after the last matched character. If
\r
726 * the parse fails, the position in unchanged upon output.
\r
727 * @return the ISO 4217 code, as a string, of the best match, or
\r
728 * null if there is no match
\r
731 * @deprecated This API is ICU internal only.
\r
733 public static String parse(ULocale locale, String text, int type, ParsePosition pos) {
\r
734 //TextTrieMap currencyNameTrie = (TextTrieMap)CURRENCY_NAME_CACHE.get(locale);
\r
735 Vector currencyTrieVec = (Vector)CURRENCY_NAME_CACHE.get(locale);
\r
736 if (currencyTrieVec == null) {
\r
737 TextTrieMap currencyNameTrie = new TextTrieMap(true);
\r
738 TextTrieMap currencySymbolTrie = new TextTrieMap(false);
\r
739 currencyTrieVec = new Vector();
\r
740 currencyTrieVec.addElement(currencySymbolTrie);
\r
741 currencyTrieVec.addElement(currencyNameTrie);
\r
742 setupCurrencyTrieVec(locale, currencyTrieVec);
\r
743 CURRENCY_NAME_CACHE.put(locale, currencyTrieVec);
\r
747 String isoResult = null;
\r
749 // look for the names
\r
750 TextTrieMap currencyNameTrie = (TextTrieMap)currencyTrieVec.elementAt(1);
\r
751 CurrencyNameResultHandler handler = new CurrencyNameResultHandler();
\r
752 currencyNameTrie.find(text, pos.getIndex(), handler);
\r
753 List list = handler.getMatchedCurrencyNames();
\r
754 if (list != null && list.size() != 0) {
\r
755 Iterator it = list.iterator();
\r
756 while (it.hasNext()) {
\r
757 CurrencyStringInfo info = (CurrencyStringInfo)it.next();
\r
758 String isoCode = info.getISOCode();
\r
759 String currencyString = info.getCurrencyString();
\r
760 if (currencyString.length() > maxLength) {
\r
761 maxLength = currencyString.length();
\r
762 isoResult = isoCode;
\r
767 if (type != Currency.LONG_NAME) { // not long name only
\r
768 TextTrieMap currencySymbolTrie = (TextTrieMap)currencyTrieVec.elementAt(0);
\r
769 handler = new CurrencyNameResultHandler();
\r
770 currencySymbolTrie.find(text, pos.getIndex(), handler);
\r
771 list = handler.getMatchedCurrencyNames();
\r
772 if (list != null && list.size() != 0) {
\r
773 Iterator it = list.iterator();
\r
774 while (it.hasNext()) {
\r
775 CurrencyStringInfo info = (CurrencyStringInfo)it.next();
\r
776 String isoCode = info.getISOCode();
\r
777 String currencyString = info.getCurrencyString();
\r
778 if (currencyString.length() > maxLength) {
\r
779 maxLength = currencyString.length();
\r
780 isoResult = isoCode;
\r
786 int start = pos.getIndex();
\r
787 pos.setIndex(start + maxLength);
\r
791 private static void setupCurrencyTrieVec(ULocale locale, Vector trieVec) {
\r
792 // Look up the Currencies resource for the given locale. The
\r
793 // Currencies locale data looks like this:
\r
796 //| USD { "US$", "US Dollar" }
\r
797 //| CHF { "Sw F", "Swiss Franc" }
\r
798 //| INR { "=0#Rs|1#Re|1<Rs", "=0#Rupees|1#Rupee|1<Rupees" }
\r
803 // In the future, resource bundles may implement multi-level
\r
804 // fallback. That is, if a currency is not found in the en_US
\r
805 // Currencies data, then the en Currencies data will be searched.
\r
806 // Currently, if a Currencies datum exists in en_US and en, the
\r
807 // en_US entry hides that in en.
\r
809 // We want multi-level fallback for this resource, so we implement
\r
812 // Multi-level resource inheritance fallback loop
\r
815 1. Look at the Currencies array from the locale
\r
816 1a. Iterate through it, and check each row to see if row[1] matches
\r
817 1a1. If row[1] is a pattern, use ChoiceFormat to attempt a parse
\r
818 1b. Upon a match, return the ISO code stored at row[0]
\r
819 2. If there is no match, fall back to "en" and try again
\r
820 3. If there is no match, fall back to root and try again
\r
821 4. If still no match, parse 3-letter ISO {this code is probably unchanged}.
\r
824 TextTrieMap symTrie = (TextTrieMap)trieVec.elementAt(0);
\r
825 TextTrieMap trie = (TextTrieMap)trieVec.elementAt(1);
\r
827 HashSet visited = new HashSet();
\r
828 ULocale parentLocale = locale;
\r
829 while (parentLocale != null) {
\r
830 UResourceBundle rb = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,parentLocale);
\r
831 // We can't cast this to String[][]; the cast has to happen later
\r
833 UResourceBundle currencies = rb.get("Currencies");
\r
834 // Do a linear search
\r
835 for (int i=0; i<currencies.getSize(); ++i) {
\r
836 UResourceBundle item = currencies.get(i);
\r
837 String ISOCode = item.getKey();
\r
838 if (!visited.contains(ISOCode)) {
\r
839 CurrencyStringInfo info = new CurrencyStringInfo(ISOCode, ISOCode);
\r
840 symTrie.put(ISOCode, info);
\r
842 String name = item.getString(0);
\r
843 if (name.length() > 1 && name.charAt(0) == '=' &&
\r
844 name.charAt(1) != '=') {
\r
845 // handle choice format here
\r
846 name = name.substring(1);
\r
847 ChoiceFormat choice = new ChoiceFormat(name);
\r
848 Object[] names = choice.getFormats();
\r
849 for (int nameIndex = 0; nameIndex < names.length;
\r
851 info = new CurrencyStringInfo(ISOCode,
\r
852 (String)names[nameIndex]);
\r
853 symTrie.put((String)names[nameIndex], info);
\r
856 info = new CurrencyStringInfo(ISOCode, name);
\r
857 symTrie.put(name, info);
\r
860 info = new CurrencyStringInfo(ISOCode, item.getString(1));
\r
861 trie.put(item.getString(1), info);
\r
862 visited.add(ISOCode);
\r
866 catch (MissingResourceException e) {}
\r
868 parentLocale = parentLocale.getFallback();
\r
870 // Look up the CurrencyPlurals resource for the given locale. The
\r
871 // CurrencyPlurals locale data looks like this:
\r
873 //| CurrencyPlurals {
\r
875 //| one{"US Dollar"}
\r
876 //| other{"US dollars"}
\r
882 HashMap visitedInMap = new HashMap();
\r
883 parentLocale = locale;
\r
884 while (parentLocale != null) {
\r
885 UResourceBundle rb = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,parentLocale);
\r
887 UResourceBundle currencies;
\r
888 currencies = rb.get("CurrencyPlurals");
\r
889 for (int i=0; i<currencies.getSize(); ++i) {
\r
890 UResourceBundle item = currencies.get(i);
\r
891 String ISOCode = item.getKey();
\r
892 HashSet visitPluralCount = (HashSet)visitedInMap.get(ISOCode);
\r
893 if (visitPluralCount == null) {
\r
894 visitPluralCount = new HashSet();
\r
895 visitedInMap.put(ISOCode, visitPluralCount);
\r
897 for (int j=0; j<item.getSize(); ++j) {
\r
898 String count = item.get(j).getKey();
\r
899 if (!visitPluralCount.contains(count)) {
\r
900 CurrencyStringInfo info = new CurrencyStringInfo(ISOCode, item.get(j).getString());
\r
902 trie.put(item.get(j).getString(), info);
\r
903 visitPluralCount.add(count);
\r
908 catch (MissingResourceException e) {}
\r
910 parentLocale = parentLocale.getFallback();
\r
914 private static final class CurrencyStringInfo {
\r
915 private String isoCode;
\r
916 private String currencyString;
\r
918 public CurrencyStringInfo(String isoCode, String currencyString) {
\r
919 this.isoCode = isoCode;
\r
920 this.currencyString = currencyString;
\r
923 private String getISOCode() {
\r
927 private String getCurrencyString() {
\r
928 return currencyString;
\r
932 private static class CurrencyNameResultHandler implements TextTrieMap.ResultHandler {
\r
933 private ArrayList resultList;
\r
935 public boolean handlePrefixMatch(int matchLength, Iterator values) {
\r
936 if (resultList == null) {
\r
937 resultList = new ArrayList();
\r
939 while (values.hasNext()) {
\r
940 CurrencyStringInfo item = (CurrencyStringInfo)values.next();
\r
941 if (item == null) {
\r
945 for (; i < resultList.size(); i++) {
\r
946 CurrencyStringInfo tmp = (CurrencyStringInfo)resultList.get(i);
\r
947 if (item.getISOCode() == tmp.getISOCode()) {
\r
948 if (matchLength > tmp.getCurrencyString().length()) {
\r
949 resultList.set(i, item);
\r
954 if (i == resultList.size()) {
\r
955 // not found in the current list
\r
956 resultList.add(item);
\r
962 List getMatchedCurrencyNames() {
\r
963 if (resultList == null || resultList.size() == 0) {
\r
971 * Returns the number of the number of fraction digits that should
\r
972 * be displayed for this currency.
\r
973 * @return a non-negative number of fraction digits to be
\r
977 public int getDefaultFractionDigits() {
\r
978 return (findData())[0];
\r
982 * Returns the rounding increment for this currency, or 0.0 if no
\r
983 * rounding is done by this currency.
\r
984 * @return the non-negative rounding increment, or 0.0 if none
\r
987 public double getRoundingIncrement() {
\r
988 int[] data = findData();
\r
990 int data1 = data[1]; // rounding increment
\r
992 // If there is no rounding return 0.0 to indicate no rounding.
\r
993 // This is the high-runner case, by far.
\r
998 int data0 = data[0]; // fraction digits
\r
1000 // If the meta data is invalid, return 0.0 to indicate no rounding.
\r
1001 if (data0 < 0 || data0 >= POW10.length) {
\r
1005 // Return data[1] / 10^(data[0]). The only actual rounding data,
\r
1006 // as of this writing, is CHF { 2, 25 }.
\r
1007 return (double) data1 / POW10[data0];
\r
1011 * Returns the ISO 4217 code for this currency.
\r
1014 public String toString() {
\r
1019 * Constructs a currency object for the given ISO 4217 3-letter
\r
1020 * code. This constructor assumes that the code is valid.
\r
1022 * @param theISOCode The iso code used to construct the currency.
\r
1025 protected Currency(String theISOCode) {
\r
1026 isoCode = theISOCode;
\r
1030 * Internal function to look up currency data. Result is an array of
\r
1031 * two Integers. The first is the fraction digits. The second is the
\r
1032 * rounding increment, or 0 if none. The rounding increment is in
\r
1033 * units of 10^(-fraction_digits).
\r
1035 private int[] findData() {
\r
1038 // Get CurrencyMeta resource out of root locale file. [This may
\r
1039 // move out of the root locale file later; if it does, update this
\r
1041 UResourceBundle root = ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
\r
1042 UResourceBundle currencyMeta = root.get("CurrencyMeta");
\r
1044 //Integer[] i = null;
\r
1045 //int defaultPos = -1;
\r
1046 int[] i = currencyMeta.get(isoCode).getIntVector();
\r
1048 // Do a linear search for isoCode. At the same time,
\r
1049 // record the position of the DEFAULT meta data. If the
\r
1050 // meta data becomes large, make this faster.
\r
1051 /*for (int j=0; j<currencyMeta.length; ++j) {
\r
1052 Object[] row = currencyMeta[j];
\r
1053 String s = (String) row[0];
\r
1054 int c = isoCode.compareToIgnoreCase(s);
\r
1056 i = (Integer[]) row[1];
\r
1059 if ("DEFAULT".equalsIgnoreCase(s)) {
\r
1062 if (c < 0 && defaultPos >= 0) {
\r
1068 i = currencyMeta.get("DEFAULT").getIntVector();
\r
1071 if (i != null && i.length >= 2) {
\r
1075 catch (MissingResourceException e) {}
\r
1077 // Config/build error; return hard-coded defaults
\r
1078 return LAST_RESORT_DATA;
\r
1081 // Default currency meta data of last resort. We try to use the
\r
1082 // defaults encoded in the meta data resource bundle. If there is a
\r
1083 // configuration/build error and these are not available, we use these
\r
1084 // hard-coded defaults (which should be identical).
\r
1085 private static final int[] LAST_RESORT_DATA = new int[] { 2, 0 };
\r
1087 // POW10[i] = 10^i
\r
1088 private static final int[] POW10 = { 1, 10, 100, 1000, 10000, 100000,
\r
1089 1000000, 10000000, 100000000, 1000000000 };
\r
1091 // -------- BEGIN ULocale boilerplate --------
\r
1094 * Return the locale that was used to create this object, or null.
\r
1095 * This may may differ from the locale requested at the time of
\r
1096 * this object's creation. For example, if an object is created
\r
1097 * for locale <tt>en_US_CALIFORNIA</tt>, the actual data may be
\r
1098 * drawn from <tt>en</tt> (the <i>actual</i> locale), and
\r
1099 * <tt>en_US</tt> may be the most specific locale that exists (the
\r
1100 * <i>valid</i> locale).
\r
1102 * <p>Note: This method will be obsoleted. The implementation is
\r
1103 * no longer locale-specific and so there is no longer a valid or
\r
1104 * actual locale associated with the Currency object. Until
\r
1105 * it is removed, this method will return the root locale.
\r
1106 * @param type type of information requested, either {@link
\r
1107 * com.ibm.icu.util.ULocale#VALID_LOCALE} or {@link
\r
1108 * com.ibm.icu.util.ULocale#ACTUAL_LOCALE}.
\r
1109 * @return the information specified by <i>type</i>, or null if
\r
1110 * this object was not constructed from locale data.
\r
1111 * @see com.ibm.icu.util.ULocale
\r
1112 * @see com.ibm.icu.util.ULocale#VALID_LOCALE
\r
1113 * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE
\r
1114 * @obsolete ICU 3.2 to be removed
\r
1115 * @deprecated This API is obsolete.
\r
1117 public final ULocale getLocale(ULocale.Type type) {
\r
1118 ULocale result = (type == ULocale.ACTUAL_LOCALE) ? actualLocale : validLocale;
\r
1119 if (result == null) {
\r
1120 return ULocale.ROOT;
\r
1126 * Set information about the locales that were used to create this
\r
1127 * object. If the object was not constructed from locale data,
\r
1128 * both arguments should be set to null. Otherwise, neither
\r
1129 * should be null. The actual locale must be at the same level or
\r
1130 * less specific than the valid locale. This method is intended
\r
1131 * for use by factories or other entities that create objects of
\r
1133 * @param valid the most specific locale containing any resource
\r
1135 * @param actual the locale containing data used to construct this
\r
1137 * @see com.ibm.icu.util.ULocale
\r
1138 * @see com.ibm.icu.util.ULocale#VALID_LOCALE
\r
1139 * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE
\r
1141 * @deprecated This API is ICU internal only.
\r
1143 final void setLocale(ULocale valid, ULocale actual) {
\r
1144 // Change the following to an assertion later
\r
1145 if ((valid == null) != (actual == null)) {
\r
1147 throw new IllegalArgumentException();
\r
1150 // Another check we could do is that the actual locale is at
\r
1151 // the same level or less specific than the valid locale.
\r
1152 this.validLocale = valid;
\r
1153 this.actualLocale = actual;
\r
1157 * The most specific locale containing any resource data, or null.
\r
1159 private ULocale validLocale;
\r
1162 * The locale containing data used to construct this object, or null.
\r
1164 private ULocale actualLocale;
\r
1166 // -------- END ULocale boilerplate --------
\r