]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/util/Currency.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / util / Currency.java
1 /**\r
2  *******************************************************************************\r
3  * Copyright (C) 2001-2009, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 package com.ibm.icu.util;\r
8 \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
23 \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
29 \r
30 /**\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
35  *\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
39  * </ul>\r
40  *\r
41  * The <tt>DecimalFormat</tt> class uses these data to display\r
42  * currencies.\r
43  *\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
47  * @author Alan Liu\r
48  * @stable ICU 2.2\r
49  */\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
54 \r
55     // Cache to save currency name trie\r
56     private static ICUCache CURRENCY_NAME_CACHE =  new SimpleCache();\r
57 \r
58     /**\r
59      * ISO 4217 3-letter code.\r
60      */\r
61     private String isoCode;\r
62 \r
63     /**\r
64      * Selector for getName() indicating a symbolic name for a\r
65      * currency, such as "$" for USD.\r
66      * @stable ICU 2.6\r
67      */\r
68     public static final int SYMBOL_NAME = 0;\r
69 \r
70     /**\r
71      * Selector for ucurr_getName indicating the long name for a\r
72      * currency, such as "US Dollar" for USD.\r
73      * @stable ICU 2.6\r
74      */\r
75     public static final int LONG_NAME = 1;\r
76    \r
77     /**\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
81      * @draft ICU 4.2\r
82      * @provisional This API might change or be removed in a future release.\r
83      */\r
84     public static final int PLURAL_LONG_NAME = 2;\r
85 \r
86     // begin registry stuff\r
87 \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
95     }\r
96 \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
104             try {\r
105                 Class cls = Class.forName("com.ibm.icu.util.CurrencyServiceShim");\r
106                 shim = (ServiceShim)cls.newInstance();\r
107             }\r
108             catch (Exception e) {\r
109                 if(DEBUG){\r
110                     e.printStackTrace();\r
111                 }\r
112                 throw new RuntimeException(e.getMessage());\r
113             }\r
114         }\r
115         return shim;\r
116     }\r
117 \r
118     /**\r
119      * Returns a currency object for the default currency in the given\r
120      * locale.\r
121      * @param locale the locale\r
122      * @return the currency object for this locale\r
123      * @stable ICU 2.2\r
124      */\r
125     public static Currency getInstance(Locale locale) {\r
126         return getInstance(ULocale.forLocale(locale));\r
127     }\r
128 \r
129     /**\r
130      * Returns a currency object for the default currency in the given\r
131      * locale.\r
132      * @stable ICU 3.2\r
133      */\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
138         }\r
139 \r
140         if (shim == null) {\r
141             return createCurrency(locale);\r
142         }\r
143 \r
144         return shim.createInstance(locale);\r
145     }\r
146 \r
147     /**\r
148      * Returns an array of Strings which contain the currency\r
149      * identifiers which are valid for the given locale on the \r
150      * given date.\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
154      * @stable ICU 4.0\r
155      */\r
156     public static String[] getAvailableCurrencyCodes(ULocale loc, Date d) \r
157     {\r
158         // local variables\r
159         String country = loc.getCountry();\r
160         long dateL = d.getTime();\r
161         long mask = 4294967295L;\r
162 \r
163             Vector currCodeVector = new Vector();\r
164 \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
170         {\r
171             // no data\r
172             return null;\r
173         }\r
174 \r
175         // Work with the supplementalData\r
176         try\r
177         {\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
180             // a given date.\r
181             UResourceBundle cm = bundle.get("CurrencyMap");\r
182             UResourceBundle countryArray = cm.get(country);\r
183 \r
184             // Get valid currencies\r
185             for (int i = 0; i < countryArray.getSize(); i++)\r
186             {\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
191 \r
192                 // get the from date\r
193                 long fromDate = 0;\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
198 \r
199                 // get the to date and check the date range\r
200                 if (currencyReq.getSize() > 2)\r
201                 {\r
202                     long toDate = 0;\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
207 \r
208                     if ((fromDate <= dateL) && (dateL < toDate))\r
209                     {\r
210                         currCodeVector.addElement(curriso);\r
211                     }\r
212                 }\r
213                 else\r
214                 {\r
215                     if (fromDate <= dateL)\r
216                     {\r
217                         currCodeVector.addElement(curriso);\r
218                     }\r
219                 }\r
220 \r
221             }  // end For loop\r
222 \r
223             // return the String array if we have matches\r
224             currCodeVector.trimToSize();\r
225             if (currCodeVector.size() != 0)\r
226             {\r
227                 return ((String[])currCodeVector.toArray(new String[0]));\r
228             }\r
229 \r
230         }\r
231         catch (MissingResourceException ex)\r
232         {\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
238         }\r
239 \r
240         // if we get this far, return nothing\r
241         return null;\r
242     }\r
243 \r
244     private static final String EUR_STR = "EUR";\r
245     /**\r
246      * Instantiate a currency from a resource bundle found in Locale loc.\r
247      */\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
255         if(bundle==null){\r
256             //throw new MissingResourceException()\r
257             return null;\r
258         }\r
259         try {\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
270             }\r
271             else if (isEuro) {\r
272                 curriso = EUR_STR;\r
273             }\r
274             if (curriso != null) {\r
275                 return new Currency(curriso);\r
276             }\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
283         }\r
284         return null;\r
285     }\r
286 \r
287     /**\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
294      * @stable ICU 2.2\r
295      */\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
299         }\r
300         boolean is3alpha = true;\r
301         if (theISOCode.length() != 3) {\r
302             is3alpha = false;\r
303         } else {\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
307                     is3alpha = false;\r
308                     break;\r
309                 }\r
310             }\r
311         }\r
312         if (!is3alpha) {\r
313             throw new IllegalArgumentException(\r
314                     "The input currency code is not 3-letter alphabetic code.");\r
315         }\r
316         return new Currency(theISOCode.toUpperCase(Locale.US));\r
317     }\r
318 \r
319     /**\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
325      * @see #unregister\r
326      * @stable ICU 3.2\r
327      */\r
328     public static Object registerInstance(Currency currency, ULocale locale) {\r
329         return getShim().registerInstance(currency, locale);\r
330     }\r
331 \r
332     /**\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
337      * @stable ICU 2.6\r
338      */\r
339     public static boolean unregister(Object registryKey) {\r
340         if (registryKey == null) {\r
341             throw new IllegalArgumentException("registryKey must not be null");\r
342         }\r
343         if (shim == null) {\r
344             return false;\r
345         }\r
346         return shim.unregister(registryKey);\r
347     }\r
348 \r
349     /**\r
350      * Return an array of the locales for which a currency\r
351      * is defined.\r
352      * @return an array of the available locales\r
353      * @stable ICU 2.2\r
354      */\r
355     public static Locale[] getAvailableLocales() {\r
356         if (shim == null) {\r
357             return ICUResourceBundle.getAvailableLocales(ICUResourceBundle.ICU_BASE_NAME);\r
358         } else {\r
359             return shim.getAvailableLocales();\r
360         }\r
361     }\r
362 \r
363     /**\r
364      * Return an array of the ulocales for which a currency\r
365      * is defined.\r
366      * @return an array of the available ulocales\r
367      * @stable ICU 3.2\r
368      */\r
369     public static ULocale[] getAvailableULocales() {\r
370         if (shim == null) {\r
371             return ICUResourceBundle.getAvailableULocales(ICUResourceBundle.ICU_BASE_NAME);\r
372         } else {\r
373             return shim.getAvailableULocales();\r
374         }\r
375     }\r
376 \r
377     // end registry stuff\r
378 \r
379     /**\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
392      * @draft ICU 4.2\r
393      * @provisional This API might change or be removed in a future release.\r
394      */\r
395     public static final String[] getKeywordValuesForLocale(String key, ULocale locale, boolean commonlyUsed) {\r
396         // Resolve region\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
401         }\r
402 \r
403         // Read values from supplementalData\r
404         LinkedList values = new LinkedList();\r
405         LinkedList otherValues = new LinkedList();\r
406 \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
417                 // result list.\r
418                 continue;\r
419             }\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
425                     continue;\r
426                 }\r
427                 String curID = curbndl.getString("id");\r
428                 boolean hasTo = false;\r
429                 try {\r
430                     UResourceBundle to = curbndl.get("to");\r
431                     if (to != null) {\r
432                         hasTo = true;\r
433                     }\r
434                 } catch (MissingResourceException e) {\r
435                     // Do nothing here...\r
436                 }\r
437                 if (isPrefRegion && !hasTo && !values.contains(curID)) {\r
438                     // Currently active currency for the target country\r
439                     values.add(curID);\r
440                 } else if (!otherValues.contains(curID) && !commonlyUsed){\r
441                     otherValues.add(curID);\r
442                 }\r
443             }\r
444         }\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
450             }\r
451         } else {\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
457                     values.add(curID);\r
458                 }\r
459             }\r
460         }\r
461         return (String[]) values.toArray(new String[values.size()]);\r
462     }\r
463 \r
464     /**\r
465      * Return a hashcode for this currency.\r
466      * @stable ICU 2.2\r
467      */\r
468     public int hashCode() {\r
469         return isoCode.hashCode();\r
470     }\r
471 \r
472     /**\r
473      * Return true if rhs is a Currency instance,\r
474      * is non-null, and has the same currency code.\r
475      * @stable ICU 2.2\r
476      */\r
477     public boolean equals(Object rhs) {\r
478         if (rhs == null) return false;\r
479         if (rhs == this) return true;\r
480         try {\r
481             Currency c = (Currency) rhs;\r
482             return isoCode.equals(c.isoCode);\r
483         }\r
484         catch (ClassCastException e) {\r
485             return false;\r
486         }\r
487     }\r
488 \r
489     /**\r
490      * Returns the ISO 4217 3-letter code for this currency object.\r
491      * @stable ICU 2.2\r
492      */\r
493     public String getCurrencyCode() {\r
494         return isoCode;\r
495     }\r
496 \r
497     /**\r
498      * Convenience and compatibility override of getName that\r
499      * requests the symbol name.\r
500      * @see #getName\r
501      * @stable ICU 3.4\r
502      */\r
503     public String getSymbol() {\r
504         return getSymbol(ULocale.getDefault());\r
505     }\r
506 \r
507     /**\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
511      * @see #getName\r
512      * @stable ICU 3.4\r
513      */\r
514     public String getSymbol(Locale loc) {\r
515         return getSymbol(ULocale.forLocale(loc));\r
516     }\r
517 \r
518     /**\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
522      * @see #getName\r
523      * @stable ICU 3.4\r
524      */\r
525     public String getSymbol(ULocale uloc) {\r
526         return getName(uloc, SYMBOL_NAME, new boolean[1]);\r
527     }\r
528 \r
529     /**\r
530      * Returns the display name for the given currency in the\r
531      * given locale.  \r
532      * This is a convenient method for \r
533      * getName(ULocale, int, boolean[]); \r
534      * @stable ICU 3.2\r
535      */\r
536     public String getName(Locale locale,\r
537                           int nameStyle,\r
538                           boolean[] isChoiceFormat) {\r
539         return getName(ULocale.forLocale(locale), nameStyle, isChoiceFormat);\r
540     }\r
541 \r
542     /**\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
552      * is set to false\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
558      *                                    or LONG_NAME.\r
559      * @stable ICU 3.2\r
560      */\r
561     public String getName(ULocale locale,\r
562                           int nameStyle,\r
563                           boolean[] isChoiceFormat) {\r
564 \r
565         // Look up the Currencies resource for the given locale.  The\r
566         // Currencies locale data looks like this:\r
567         //|en {\r
568         //|  Currencies {\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
572         //|    //...\r
573         //|  }\r
574         //|}\r
575 \r
576         if (nameStyle < 0 || nameStyle > 1) {\r
577             throw new IllegalArgumentException();\r
578         }\r
579 \r
580         String s = null;\r
581 \r
582          try {\r
583             UResourceBundle rb = UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);\r
584             ICUResourceBundle currencies = (ICUResourceBundle)rb.get("Currencies");\r
585 \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
590         }\r
591 \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
596         // meaning.\r
597         isChoiceFormat[0] = false;\r
598         if (s != null) {\r
599             int i=0;\r
600             while (i < s.length() && s.charAt(i) == '=' && i < 2) {\r
601                 ++i;\r
602             }\r
603             isChoiceFormat[0]= (i == 1);\r
604             if (i != 0) {\r
605                 // Skip over first mark\r
606                 s = s.substring(1);\r
607             }\r
608             return s;\r
609         }\r
610 \r
611         // If we fail to find a match, use the ISO 4217 code\r
612         return isoCode;\r
613     }\r
614 \r
615     /**\r
616      * Returns the display name for the given currency in the\r
617      * given locale.  \r
618      * This is a convenient method of \r
619      * getName(ULocale, int, String, boolean[]);\r
620      * @draft ICU 4.2\r
621      * @provisional This API might change or be removed in a future release.\r
622      */\r
623     public String getName(Locale locale,\r
624                           int nameStyle,\r
625                           String pluralCount,\r
626                           boolean[] isChoiceFormat) {\r
627         return getName(ULocale.forLocale(locale), nameStyle, \r
628                        pluralCount, isChoiceFormat);\r
629     }\r
630 \r
631     /**\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
644      * is set to false\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
651      * @draft ICU 4.2\r
652      * @provisional This API might change or be removed in a future release.\r
653      */\r
654     public String getName(ULocale locale,\r
655                           int nameStyle,\r
656                           String pluralCount,\r
657                           boolean[] isChoiceFormat) {\r
658         if (nameStyle != PLURAL_LONG_NAME) {\r
659             return getName(locale, nameStyle, isChoiceFormat);\r
660         }\r
661 \r
662         // Look up the CurrencyPlurals resource for the given locale.  The\r
663         // CurrencyPlurals locale data looks like this:\r
664         //|en {\r
665         //|  CurrencyPlurals {\r
666         //|    USD{\r
667         //|      one{"US dollar"}\r
668         //|      other{"US dollars"}\r
669         //|    }\r
670         //|    ...\r
671         //|  }\r
672         //|}\r
673         // \r
674         // Algorithm detail: http://unicode.org/reports/tr35/#Currencies\r
675         // especially the fallback rule.\r
676         String s = null;\r
677         ICUResourceBundle isoCodeBundle;\r
678         // search at run time, not saved in initialization\r
679         try {\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
683 \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
690         }\r
691         try {\r
692             s = isoCodeBundle.getStringWithFallback(pluralCount);\r
693         } catch (MissingResourceException e1) {\r
694             try {\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
702             }\r
703         }\r
704         // No support for choice format for getting plural currency names.\r
705         if (s != null) {\r
706             return s;\r
707         }\r
708         // If we fail to find a match, use the ISO 4217 code\r
709         return isoCode;\r
710     }\r
711 \r
712     /**\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
718      * is less than 3.\r
719      *\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
729      *\r
730      * @internal\r
731      * @deprecated This API is ICU internal only.\r
732      */\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
744         }\r
745         \r
746         int maxLength = 0;\r
747         String isoResult = null;\r
748 \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
763                 }\r
764             }\r
765         }\r
766 \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
781                 }\r
782             }\r
783           }\r
784         }\r
785 \r
786         int start = pos.getIndex();\r
787         pos.setIndex(start + maxLength);\r
788         return isoResult;\r
789     }\r
790 \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
794         //|en {\r
795         //|  Currencies {\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
799         //|    //...\r
800         //|  }\r
801         //|}\r
802 \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
808 \r
809         // We want multi-level fallback for this resource, so we implement\r
810         // it manually.\r
811 \r
812         // Multi-level resource inheritance fallback loop\r
813 \r
814         /*\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
822         */\r
823 \r
824         TextTrieMap symTrie = (TextTrieMap)trieVec.elementAt(0);\r
825         TextTrieMap trie = (TextTrieMap)trieVec.elementAt(1);\r
826 \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
832             try {\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
841 \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
850                                  ++nameIndex) {\r
851                                 info = new CurrencyStringInfo(ISOCode, \r
852                                                       (String)names[nameIndex]);\r
853                                 symTrie.put((String)names[nameIndex], info);\r
854                             }\r
855                         } else {\r
856                             info = new CurrencyStringInfo(ISOCode, name);\r
857                             symTrie.put(name, info);\r
858                         }\r
859 \r
860                         info = new CurrencyStringInfo(ISOCode, item.getString(1));\r
861                         trie.put(item.getString(1), info);\r
862                         visited.add(ISOCode);\r
863                     }\r
864                 }\r
865             }\r
866             catch (MissingResourceException e) {}\r
867 \r
868             parentLocale = parentLocale.getFallback();\r
869         }\r
870         // Look up the CurrencyPlurals resource for the given locale.  The\r
871         // CurrencyPlurals locale data looks like this:\r
872         //|en {\r
873         //|  CurrencyPlurals {\r
874         //|    USD { \r
875         //|      one{"US Dollar"}  \r
876         //|      other{"US dollars"} \r
877         //|    }\r
878         //|    //...\r
879         //|  }\r
880         //|}\r
881 \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
886             try {\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
896                     }\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
901 \r
902                             trie.put(item.get(j).getString(), info);\r
903                             visitPluralCount.add(count);\r
904                         }\r
905                     }\r
906                 }\r
907             }\r
908             catch (MissingResourceException e) {}\r
909 \r
910             parentLocale = parentLocale.getFallback();\r
911         }\r
912     }\r
913 \r
914     private static final class CurrencyStringInfo {\r
915         private String isoCode;\r
916         private String currencyString;\r
917 \r
918         public CurrencyStringInfo(String isoCode, String currencyString) {\r
919             this.isoCode = isoCode;\r
920             this.currencyString = currencyString;\r
921         }\r
922 \r
923         private String getISOCode() {\r
924             return isoCode;\r
925         }\r
926 \r
927         private String getCurrencyString() {\r
928             return currencyString;\r
929         }\r
930     }\r
931 \r
932     private static class CurrencyNameResultHandler implements TextTrieMap.ResultHandler {\r
933         private ArrayList resultList;\r
934     \r
935         public boolean handlePrefixMatch(int matchLength, Iterator values) {\r
936             if (resultList == null) {\r
937                 resultList = new ArrayList();\r
938             }\r
939             while (values.hasNext()) {\r
940                 CurrencyStringInfo item = (CurrencyStringInfo)values.next();\r
941                 if (item == null) {\r
942                     break;\r
943                 }\r
944                 int i = 0;\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
950                         }\r
951                         break;\r
952                     }\r
953                 }\r
954                 if (i == resultList.size()) {\r
955                     // not found in the current list\r
956                     resultList.add(item);\r
957                 }\r
958             }\r
959             return true;\r
960         }\r
961 \r
962         List getMatchedCurrencyNames() {\r
963             if (resultList == null || resultList.size() == 0) {\r
964                 return null;\r
965             }\r
966             return resultList;\r
967         }\r
968     }\r
969 \r
970     /**\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
974      * displayed\r
975      * @stable ICU 2.2\r
976      */\r
977     public int getDefaultFractionDigits() {\r
978         return (findData())[0];\r
979     }\r
980 \r
981     /**\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
985      * @stable ICU 2.2\r
986      */\r
987     public double getRoundingIncrement() {\r
988         int[] data = findData();\r
989 \r
990         int data1 = data[1]; // rounding increment\r
991 \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
994         if (data1 == 0) {\r
995             return 0.0;\r
996         }\r
997 \r
998         int data0 = data[0]; // fraction digits\r
999 \r
1000         // If the meta data is invalid, return 0.0 to indicate no rounding.\r
1001         if (data0 < 0 || data0 >= POW10.length) {\r
1002             return 0.0;\r
1003         }\r
1004 \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
1008     }\r
1009 \r
1010     /**\r
1011      * Returns the ISO 4217 code for this currency.\r
1012      * @stable ICU 2.2\r
1013      */\r
1014     public String toString() {\r
1015         return isoCode;\r
1016     }\r
1017 \r
1018     /**\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
1021      * \r
1022      * @param theISOCode The iso code used to construct the currency.\r
1023      * @stable ICU 3.4\r
1024      */\r
1025     protected Currency(String theISOCode) {\r
1026         isoCode = theISOCode;\r
1027     }\r
1028 \r
1029     /**\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
1034      */\r
1035     private int[] findData() {\r
1036 \r
1037         try {\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
1040             // code.]\r
1041             UResourceBundle root = ICUResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
1042             UResourceBundle currencyMeta = root.get("CurrencyMeta");\r
1043 \r
1044             //Integer[] i = null;\r
1045             //int defaultPos = -1;\r
1046             int[] i = currencyMeta.get(isoCode).getIntVector();\r
1047 \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
1055                 if (c == 0) {\r
1056                     i = (Integer[]) row[1];\r
1057                     break;\r
1058                 }\r
1059                 if ("DEFAULT".equalsIgnoreCase(s)) {\r
1060                     defaultPos = j;\r
1061                 }\r
1062                 if (c < 0 && defaultPos >= 0) {\r
1063                     break;\r
1064                 }\r
1065             }\r
1066             */\r
1067             if (i == null) {\r
1068                 i = currencyMeta.get("DEFAULT").getIntVector();\r
1069             }\r
1070 \r
1071             if (i != null && i.length >= 2) {\r
1072                 return i;\r
1073             }\r
1074         }\r
1075         catch (MissingResourceException e) {}\r
1076 \r
1077         // Config/build error; return hard-coded defaults\r
1078         return LAST_RESORT_DATA;\r
1079     }\r
1080 \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
1086 \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
1090 \r
1091     // -------- BEGIN ULocale boilerplate --------\r
1092 \r
1093     /**\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
1101      *\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
1116      */\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
1121         }\r
1122         return result;\r
1123     }\r
1124 \r
1125     /**\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
1132      * this class.\r
1133      * @param valid the most specific locale containing any resource\r
1134      * data, or null\r
1135      * @param actual the locale containing data used to construct this\r
1136      * object, or null\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
1140      * @internal\r
1141      * @deprecated This API is ICU internal only.\r
1142      */\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
1146             ///CLOVER:OFF\r
1147             throw new IllegalArgumentException();\r
1148             ///CLOVER:ON\r
1149         }\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
1154     }\r
1155 \r
1156     /*\r
1157      * The most specific locale containing any resource data, or null.\r
1158      */\r
1159     private ULocale validLocale;\r
1160 \r
1161     /*\r
1162      * The locale containing data used to construct this object, or null.\r
1163      */\r
1164     private ULocale actualLocale;\r
1165 \r
1166     // -------- END ULocale boilerplate --------\r
1167 }\r
1168 \r
1169 //eof\r