|
1 /* |
|
2 * Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Sun designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Sun in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 * CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 * have any questions. |
|
24 */ |
|
25 |
|
26 package java.util; |
|
27 |
|
28 import java.io.BufferedInputStream; |
|
29 import java.io.DataInputStream; |
|
30 import java.io.File; |
|
31 import java.io.FileInputStream; |
|
32 import java.io.FileReader; |
|
33 import java.io.IOException; |
|
34 import java.io.Serializable; |
|
35 import java.security.AccessController; |
|
36 import java.security.PrivilegedAction; |
|
37 import java.util.logging.Level; |
|
38 import java.util.logging.Logger; |
|
39 import java.util.regex.Pattern; |
|
40 import java.util.regex.Matcher; |
|
41 import java.util.spi.CurrencyNameProvider; |
|
42 import java.util.spi.LocaleServiceProvider; |
|
43 import sun.util.LocaleServiceProviderPool; |
|
44 import sun.util.resources.LocaleData; |
|
45 import sun.util.resources.OpenListResourceBundle; |
|
46 |
|
47 |
|
48 /** |
|
49 * Represents a currency. Currencies are identified by their ISO 4217 currency |
|
50 * codes. Visit the <a href="http://www.iso.org/iso/en/prods-services/popstds/currencycodes.html"> |
|
51 * ISO web site</a> for more information, including a table of |
|
52 * currency codes. |
|
53 * <p> |
|
54 * The class is designed so that there's never more than one |
|
55 * <code>Currency</code> instance for any given currency. Therefore, there's |
|
56 * no public constructor. You obtain a <code>Currency</code> instance using |
|
57 * the <code>getInstance</code> methods. |
|
58 * <p> |
|
59 * Users can supersede the Java runtime currency data by creating a properties |
|
60 * file named <code><JAVA_HOME>/lib/currency.properties</code>. The contents |
|
61 * of the properties file are key/value pairs of the ISO 3166 country codes |
|
62 * and the ISO 4217 currency data respectively. The value part consists of |
|
63 * three ISO 4217 values of a currency, i.e., an alphabetic code, a numeric |
|
64 * code, and a minor unit. Those three ISO 4217 values are separated by commas. |
|
65 * The lines which start with '#'s are considered comment lines. For example, |
|
66 * <p> |
|
67 * <code> |
|
68 * #Sample currency properties<br> |
|
69 * JP=JPZ,999,0 |
|
70 * </code> |
|
71 * <p> |
|
72 * will supersede the currency data for Japan. |
|
73 * |
|
74 * @since 1.4 |
|
75 */ |
|
76 public final class Currency implements Serializable { |
|
77 |
|
78 private static final long serialVersionUID = -158308464356906721L; |
|
79 |
|
80 /** |
|
81 * ISO 4217 currency code for this currency. |
|
82 * |
|
83 * @serial |
|
84 */ |
|
85 private final String currencyCode; |
|
86 |
|
87 /** |
|
88 * Default fraction digits for this currency. |
|
89 * Set from currency data tables. |
|
90 */ |
|
91 transient private final int defaultFractionDigits; |
|
92 |
|
93 /** |
|
94 * ISO 4217 numeric code for this currency. |
|
95 * Set from currency data tables. |
|
96 */ |
|
97 transient private final int numericCode; |
|
98 |
|
99 |
|
100 // class data: instance map |
|
101 |
|
102 private static HashMap<String, Currency> instances = new HashMap<String, Currency>(7); |
|
103 private static HashSet<Currency> available; |
|
104 |
|
105 |
|
106 // Class data: currency data obtained from currency.data file. |
|
107 // Purpose: |
|
108 // - determine valid country codes |
|
109 // - determine valid currency codes |
|
110 // - map country codes to currency codes |
|
111 // - obtain default fraction digits for currency codes |
|
112 // |
|
113 // sc = special case; dfd = default fraction digits |
|
114 // Simple countries are those where the country code is a prefix of the |
|
115 // currency code, and there are no known plans to change the currency. |
|
116 // |
|
117 // table formats: |
|
118 // - mainTable: |
|
119 // - maps country code to 32-bit int |
|
120 // - 26*26 entries, corresponding to [A-Z]*[A-Z] |
|
121 // - \u007F -> not valid country |
|
122 // - bits 18-31: unused |
|
123 // - bits 8-17: numeric code (0 to 1023) |
|
124 // - bit 7: 1 - special case, bits 0-4 indicate which one |
|
125 // 0 - simple country, bits 0-4 indicate final char of currency code |
|
126 // - bits 5-6: fraction digits for simple countries, 0 for special cases |
|
127 // - bits 0-4: final char for currency code for simple country, or ID of special case |
|
128 // - special case IDs: |
|
129 // - 0: country has no currency |
|
130 // - other: index into sc* arrays + 1 |
|
131 // - scCutOverTimes: cut-over time in millis as returned by |
|
132 // System.currentTimeMillis for special case countries that are changing |
|
133 // currencies; Long.MAX_VALUE for countries that are not changing currencies |
|
134 // - scOldCurrencies: old currencies for special case countries |
|
135 // - scNewCurrencies: new currencies for special case countries that are |
|
136 // changing currencies; null for others |
|
137 // - scOldCurrenciesDFD: default fraction digits for old currencies |
|
138 // - scNewCurrenciesDFD: default fraction digits for new currencies, 0 for |
|
139 // countries that are not changing currencies |
|
140 // - otherCurrencies: concatenation of all currency codes that are not the |
|
141 // main currency of a simple country, separated by "-" |
|
142 // - otherCurrenciesDFD: decimal format digits for currencies in otherCurrencies, same order |
|
143 |
|
144 static int formatVersion; |
|
145 static int dataVersion; |
|
146 static int[] mainTable; |
|
147 static long[] scCutOverTimes; |
|
148 static String[] scOldCurrencies; |
|
149 static String[] scNewCurrencies; |
|
150 static int[] scOldCurrenciesDFD; |
|
151 static int[] scNewCurrenciesDFD; |
|
152 static int[] scOldCurrenciesNumericCode; |
|
153 static int[] scNewCurrenciesNumericCode; |
|
154 static String otherCurrencies; |
|
155 static int[] otherCurrenciesDFD; |
|
156 static int[] otherCurrenciesNumericCode; |
|
157 |
|
158 // handy constants - must match definitions in GenerateCurrencyData |
|
159 // magic number |
|
160 private static final int MAGIC_NUMBER = 0x43757244; |
|
161 // number of characters from A to Z |
|
162 private static final int A_TO_Z = ('Z' - 'A') + 1; |
|
163 // entry for invalid country codes |
|
164 private static final int INVALID_COUNTRY_ENTRY = 0x007F; |
|
165 // entry for countries without currency |
|
166 private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x0080; |
|
167 // mask for simple case country entries |
|
168 private static final int SIMPLE_CASE_COUNTRY_MASK = 0x0000; |
|
169 // mask for simple case country entry final character |
|
170 private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x001F; |
|
171 // mask for simple case country entry default currency digits |
|
172 private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x0060; |
|
173 // shift count for simple case country entry default currency digits |
|
174 private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5; |
|
175 // mask for special case country entries |
|
176 private static final int SPECIAL_CASE_COUNTRY_MASK = 0x0080; |
|
177 // mask for special case country index |
|
178 private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x001F; |
|
179 // delta from entry index component in main table to index into special case tables |
|
180 private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1; |
|
181 // mask for distinguishing simple and special case countries |
|
182 private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK; |
|
183 // mask for the numeric code of the currency |
|
184 private static final int NUMERIC_CODE_MASK = 0x0003FF00; |
|
185 // shift count for the numeric code of the currency |
|
186 private static final int NUMERIC_CODE_SHIFT = 8; |
|
187 |
|
188 // Currency data format version |
|
189 private static final int VALID_FORMAT_VERSION = 1; |
|
190 |
|
191 static { |
|
192 AccessController.doPrivileged(new PrivilegedAction() { |
|
193 public Object run() { |
|
194 String homeDir = System.getProperty("java.home"); |
|
195 try { |
|
196 String dataFile = homeDir + File.separator + |
|
197 "lib" + File.separator + "currency.data"; |
|
198 DataInputStream dis = new DataInputStream( |
|
199 new BufferedInputStream( |
|
200 new FileInputStream(dataFile))); |
|
201 if (dis.readInt() != MAGIC_NUMBER) { |
|
202 throw new InternalError("Currency data is possibly corrupted"); |
|
203 } |
|
204 formatVersion = dis.readInt(); |
|
205 if (formatVersion != VALID_FORMAT_VERSION) { |
|
206 throw new InternalError("Currency data format is incorrect"); |
|
207 } |
|
208 dataVersion = dis.readInt(); |
|
209 mainTable = readIntArray(dis, A_TO_Z * A_TO_Z); |
|
210 int scCount = dis.readInt(); |
|
211 scCutOverTimes = readLongArray(dis, scCount); |
|
212 scOldCurrencies = readStringArray(dis, scCount); |
|
213 scNewCurrencies = readStringArray(dis, scCount); |
|
214 scOldCurrenciesDFD = readIntArray(dis, scCount); |
|
215 scNewCurrenciesDFD = readIntArray(dis, scCount); |
|
216 scOldCurrenciesNumericCode = readIntArray(dis, scCount); |
|
217 scNewCurrenciesNumericCode = readIntArray(dis, scCount); |
|
218 int ocCount = dis.readInt(); |
|
219 otherCurrencies = dis.readUTF(); |
|
220 otherCurrenciesDFD = readIntArray(dis, ocCount); |
|
221 otherCurrenciesNumericCode = readIntArray(dis, ocCount); |
|
222 dis.close(); |
|
223 } catch (IOException e) { |
|
224 InternalError ie = new InternalError(); |
|
225 ie.initCause(e); |
|
226 throw ie; |
|
227 } |
|
228 |
|
229 // look for the properties file for overrides |
|
230 try { |
|
231 File propFile = new File(homeDir + File.separator + |
|
232 "lib" + File.separator + |
|
233 "currency.properties"); |
|
234 if (propFile.exists()) { |
|
235 Properties props = new Properties(); |
|
236 props.load(new FileReader(propFile)); |
|
237 Set<String> keys = props.stringPropertyNames(); |
|
238 Pattern propertiesPattern = |
|
239 Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*([0-3])"); |
|
240 for (String key : keys) { |
|
241 replaceCurrencyData(propertiesPattern, |
|
242 key.toUpperCase(Locale.ROOT), |
|
243 props.getProperty(key).toUpperCase(Locale.ROOT)); |
|
244 } |
|
245 } |
|
246 } catch (IOException e) { |
|
247 log(Level.INFO, "currency.properties is ignored because of an IOException", e); |
|
248 } |
|
249 return null; |
|
250 } |
|
251 }); |
|
252 } |
|
253 |
|
254 /** |
|
255 * Constants for retrieving localized names from the name providers. |
|
256 */ |
|
257 private static final int SYMBOL = 0; |
|
258 private static final int DISPLAYNAME = 1; |
|
259 |
|
260 |
|
261 /** |
|
262 * Constructs a <code>Currency</code> instance. The constructor is private |
|
263 * so that we can insure that there's never more than one instance for a |
|
264 * given currency. |
|
265 */ |
|
266 private Currency(String currencyCode, int defaultFractionDigits, int numericCode) { |
|
267 this.currencyCode = currencyCode; |
|
268 this.defaultFractionDigits = defaultFractionDigits; |
|
269 this.numericCode = numericCode; |
|
270 } |
|
271 |
|
272 /** |
|
273 * Returns the <code>Currency</code> instance for the given currency code. |
|
274 * |
|
275 * @param currencyCode the ISO 4217 code of the currency |
|
276 * @return the <code>Currency</code> instance for the given currency code |
|
277 * @exception NullPointerException if <code>currencyCode</code> is null |
|
278 * @exception IllegalArgumentException if <code>currencyCode</code> is not |
|
279 * a supported ISO 4217 code. |
|
280 */ |
|
281 public static Currency getInstance(String currencyCode) { |
|
282 return getInstance(currencyCode, Integer.MIN_VALUE, 0); |
|
283 } |
|
284 |
|
285 private static Currency getInstance(String currencyCode, int defaultFractionDigits, |
|
286 int numericCode) { |
|
287 synchronized (instances) { |
|
288 // Try to look up the currency code in the instances table. |
|
289 // This does the null pointer check as a side effect. |
|
290 // Also, if there already is an entry, the currencyCode must be valid. |
|
291 Currency instance = instances.get(currencyCode); |
|
292 if (instance != null) { |
|
293 return instance; |
|
294 } |
|
295 |
|
296 if (defaultFractionDigits == Integer.MIN_VALUE) { |
|
297 // Currency code not internally generated, need to verify first |
|
298 // A currency code must have 3 characters and exist in the main table |
|
299 // or in the list of other currencies. |
|
300 if (currencyCode.length() != 3) { |
|
301 throw new IllegalArgumentException(); |
|
302 } |
|
303 char char1 = currencyCode.charAt(0); |
|
304 char char2 = currencyCode.charAt(1); |
|
305 int tableEntry = getMainTableEntry(char1, char2); |
|
306 if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK |
|
307 && tableEntry != INVALID_COUNTRY_ENTRY |
|
308 && currencyCode.charAt(2) - 'A' == (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) { |
|
309 defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; |
|
310 numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; |
|
311 } else { |
|
312 // Check for '-' separately so we don't get false hits in the table. |
|
313 if (currencyCode.charAt(2) == '-') { |
|
314 throw new IllegalArgumentException(); |
|
315 } |
|
316 int index = otherCurrencies.indexOf(currencyCode); |
|
317 if (index == -1) { |
|
318 throw new IllegalArgumentException(); |
|
319 } |
|
320 defaultFractionDigits = otherCurrenciesDFD[index / 4]; |
|
321 numericCode = otherCurrenciesNumericCode[index / 4]; |
|
322 } |
|
323 } |
|
324 |
|
325 instance = new Currency(currencyCode, defaultFractionDigits, numericCode); |
|
326 instances.put(currencyCode, instance); |
|
327 return instance; |
|
328 } |
|
329 } |
|
330 |
|
331 /** |
|
332 * Returns the <code>Currency</code> instance for the country of the |
|
333 * given locale. The language and variant components of the locale |
|
334 * are ignored. The result may vary over time, as countries change their |
|
335 * currencies. For example, for the original member countries of the |
|
336 * European Monetary Union, the method returns the old national currencies |
|
337 * until December 31, 2001, and the Euro from January 1, 2002, local time |
|
338 * of the respective countries. |
|
339 * <p> |
|
340 * The method returns <code>null</code> for territories that don't |
|
341 * have a currency, such as Antarctica. |
|
342 * |
|
343 * @param locale the locale for whose country a <code>Currency</code> |
|
344 * instance is needed |
|
345 * @return the <code>Currency</code> instance for the country of the given |
|
346 * locale, or null |
|
347 * @exception NullPointerException if <code>locale</code> or its country |
|
348 * code is null |
|
349 * @exception IllegalArgumentException if the country of the given locale |
|
350 * is not a supported ISO 3166 country code. |
|
351 */ |
|
352 public static Currency getInstance(Locale locale) { |
|
353 String country = locale.getCountry(); |
|
354 if (country == null) { |
|
355 throw new NullPointerException(); |
|
356 } |
|
357 |
|
358 if (country.length() != 2) { |
|
359 throw new IllegalArgumentException(); |
|
360 } |
|
361 |
|
362 char char1 = country.charAt(0); |
|
363 char char2 = country.charAt(1); |
|
364 int tableEntry = getMainTableEntry(char1, char2); |
|
365 if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK |
|
366 && tableEntry != INVALID_COUNTRY_ENTRY) { |
|
367 char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); |
|
368 int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; |
|
369 int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; |
|
370 StringBuffer sb = new StringBuffer(country); |
|
371 sb.append(finalChar); |
|
372 return getInstance(sb.toString(), defaultFractionDigits, numericCode); |
|
373 } else { |
|
374 // special cases |
|
375 if (tableEntry == INVALID_COUNTRY_ENTRY) { |
|
376 throw new IllegalArgumentException(); |
|
377 } |
|
378 if (tableEntry == COUNTRY_WITHOUT_CURRENCY_ENTRY) { |
|
379 return null; |
|
380 } else { |
|
381 int index = (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA; |
|
382 if (scCutOverTimes[index] == Long.MAX_VALUE || System.currentTimeMillis() < scCutOverTimes[index]) { |
|
383 return getInstance(scOldCurrencies[index], scOldCurrenciesDFD[index], |
|
384 scOldCurrenciesNumericCode[index]); |
|
385 } else { |
|
386 return getInstance(scNewCurrencies[index], scNewCurrenciesDFD[index], |
|
387 scNewCurrenciesNumericCode[index]); |
|
388 } |
|
389 } |
|
390 } |
|
391 } |
|
392 |
|
393 /** |
|
394 * Gets the set of available currencies. The returned set of currencies |
|
395 * contains all of the available currencies, which may include currencies |
|
396 * that represent obsolete ISO 4217 codes. The set can be modified |
|
397 * without affecting the available currencies in the runtime. |
|
398 * |
|
399 * @return the set of available currencies. If there is no currency |
|
400 * available in the runtime, the returned set is empty. |
|
401 * @since 1.7 |
|
402 */ |
|
403 public static Set<Currency> getAvailableCurrencies() { |
|
404 synchronized(Currency.class) { |
|
405 if (available == null) { |
|
406 available = new HashSet<Currency>(256); |
|
407 |
|
408 // Add simple currencies first |
|
409 for (char c1 = 'A'; c1 <= 'Z'; c1 ++) { |
|
410 for (char c2 = 'A'; c2 <= 'Z'; c2 ++) { |
|
411 int tableEntry = getMainTableEntry(c1, c2); |
|
412 if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK |
|
413 && tableEntry != INVALID_COUNTRY_ENTRY) { |
|
414 char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); |
|
415 int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; |
|
416 int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; |
|
417 StringBuilder sb = new StringBuilder(); |
|
418 sb.append(c1); |
|
419 sb.append(c2); |
|
420 sb.append(finalChar); |
|
421 available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode)); |
|
422 } |
|
423 } |
|
424 } |
|
425 |
|
426 // Now add other currencies |
|
427 StringTokenizer st = new StringTokenizer(otherCurrencies, "-"); |
|
428 while (st.hasMoreElements()) { |
|
429 available.add(getInstance((String)st.nextElement())); |
|
430 } |
|
431 } |
|
432 } |
|
433 |
|
434 return (Set<Currency>) available.clone(); |
|
435 } |
|
436 |
|
437 /** |
|
438 * Gets the ISO 4217 currency code of this currency. |
|
439 * |
|
440 * @return the ISO 4217 currency code of this currency. |
|
441 */ |
|
442 public String getCurrencyCode() { |
|
443 return currencyCode; |
|
444 } |
|
445 |
|
446 /** |
|
447 * Gets the symbol of this currency for the default locale. |
|
448 * For example, for the US Dollar, the symbol is "$" if the default |
|
449 * locale is the US, while for other locales it may be "US$". If no |
|
450 * symbol can be determined, the ISO 4217 currency code is returned. |
|
451 * |
|
452 * @return the symbol of this currency for the default locale |
|
453 */ |
|
454 public String getSymbol() { |
|
455 return getSymbol(Locale.getDefault()); |
|
456 } |
|
457 |
|
458 /** |
|
459 * Gets the symbol of this currency for the specified locale. |
|
460 * For example, for the US Dollar, the symbol is "$" if the specified |
|
461 * locale is the US, while for other locales it may be "US$". If no |
|
462 * symbol can be determined, the ISO 4217 currency code is returned. |
|
463 * |
|
464 * @param locale the locale for which a display name for this currency is |
|
465 * needed |
|
466 * @return the symbol of this currency for the specified locale |
|
467 * @exception NullPointerException if <code>locale</code> is null |
|
468 */ |
|
469 public String getSymbol(Locale locale) { |
|
470 try { |
|
471 // Check whether a provider can provide an implementation that's closer |
|
472 // to the requested locale than what the Java runtime itself can provide. |
|
473 LocaleServiceProviderPool pool = |
|
474 LocaleServiceProviderPool.getPool(CurrencyNameProvider.class); |
|
475 |
|
476 if (pool.hasProviders()) { |
|
477 // Assuming that all the country locales include necessary currency |
|
478 // symbols in the Java runtime's resources, so there is no need to |
|
479 // examine whether Java runtime's currency resource bundle is missing |
|
480 // names. Therefore, no resource bundle is provided for calling this |
|
481 // method. |
|
482 String symbol = pool.getLocalizedObject( |
|
483 CurrencyNameGetter.INSTANCE, |
|
484 locale, (OpenListResourceBundle)null, |
|
485 currencyCode, SYMBOL); |
|
486 if (symbol != null) { |
|
487 return symbol; |
|
488 } |
|
489 } |
|
490 |
|
491 ResourceBundle bundle = LocaleData.getCurrencyNames(locale); |
|
492 return bundle.getString(currencyCode); |
|
493 } catch (MissingResourceException e) { |
|
494 // use currency code as symbol of last resort |
|
495 return currencyCode; |
|
496 } |
|
497 } |
|
498 |
|
499 /** |
|
500 * Gets the default number of fraction digits used with this currency. |
|
501 * For example, the default number of fraction digits for the Euro is 2, |
|
502 * while for the Japanese Yen it's 0. |
|
503 * In the case of pseudo-currencies, such as IMF Special Drawing Rights, |
|
504 * -1 is returned. |
|
505 * |
|
506 * @return the default number of fraction digits used with this currency |
|
507 */ |
|
508 public int getDefaultFractionDigits() { |
|
509 return defaultFractionDigits; |
|
510 } |
|
511 |
|
512 /** |
|
513 * Returns the ISO 4217 numeric code of this currency. |
|
514 * |
|
515 * @return the ISO 4217 numeric code of this currency |
|
516 * @since 1.7 |
|
517 */ |
|
518 public int getNumericCode() { |
|
519 return numericCode; |
|
520 } |
|
521 |
|
522 /** |
|
523 * Gets the name that is suitable for displaying this currency for |
|
524 * the default locale. If there is no suitable display name found |
|
525 * for the default locale, the ISO 4217 currency code is returned. |
|
526 * |
|
527 * @return the display name of this currency for the default locale |
|
528 * @since 1.7 |
|
529 */ |
|
530 public String getDisplayName() { |
|
531 return getDisplayName(Locale.getDefault()); |
|
532 } |
|
533 |
|
534 /** |
|
535 * Gets the name that is suitable for displaying this currency for |
|
536 * the specified locale. If there is no suitable display name found |
|
537 * for the specified locale, the ISO 4217 currency code is returned. |
|
538 * |
|
539 * @param locale the locale for which a display name for this currency is |
|
540 * needed |
|
541 * @return the display name of this currency for the specified locale |
|
542 * @exception NullPointerException if <code>locale</code> is null |
|
543 * @since 1.7 |
|
544 */ |
|
545 public String getDisplayName(Locale locale) { |
|
546 try { |
|
547 OpenListResourceBundle bundle = LocaleData.getCurrencyNames(locale); |
|
548 String result = null; |
|
549 String bundleKey = currencyCode.toLowerCase(Locale.ROOT); |
|
550 |
|
551 // Check whether a provider can provide an implementation that's closer |
|
552 // to the requested locale than what the Java runtime itself can provide. |
|
553 LocaleServiceProviderPool pool = |
|
554 LocaleServiceProviderPool.getPool(CurrencyNameProvider.class); |
|
555 if (pool.hasProviders()) { |
|
556 result = pool.getLocalizedObject( |
|
557 CurrencyNameGetter.INSTANCE, |
|
558 locale, bundleKey, bundle, currencyCode, DISPLAYNAME); |
|
559 } |
|
560 |
|
561 if (result == null) { |
|
562 result = bundle.getString(bundleKey); |
|
563 } |
|
564 |
|
565 if (result != null) { |
|
566 return result; |
|
567 } |
|
568 } catch (MissingResourceException e) { |
|
569 // fall through |
|
570 } |
|
571 |
|
572 // use currency code as symbol of last resort |
|
573 return currencyCode; |
|
574 } |
|
575 |
|
576 /** |
|
577 * Returns the ISO 4217 currency code of this currency. |
|
578 * |
|
579 * @return the ISO 4217 currency code of this currency |
|
580 */ |
|
581 public String toString() { |
|
582 return currencyCode; |
|
583 } |
|
584 |
|
585 /** |
|
586 * Resolves instances being deserialized to a single instance per currency. |
|
587 */ |
|
588 private Object readResolve() { |
|
589 return getInstance(currencyCode); |
|
590 } |
|
591 |
|
592 /** |
|
593 * Gets the main table entry for the country whose country code consists |
|
594 * of char1 and char2. |
|
595 */ |
|
596 private static int getMainTableEntry(char char1, char char2) { |
|
597 if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') { |
|
598 throw new IllegalArgumentException(); |
|
599 } |
|
600 return mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')]; |
|
601 } |
|
602 |
|
603 /** |
|
604 * Sets the main table entry for the country whose country code consists |
|
605 * of char1 and char2. |
|
606 */ |
|
607 private static void setMainTableEntry(char char1, char char2, int entry) { |
|
608 if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') { |
|
609 throw new IllegalArgumentException(); |
|
610 } |
|
611 mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')] = entry; |
|
612 } |
|
613 |
|
614 /** |
|
615 * Obtains a localized currency names from a CurrencyNameProvider |
|
616 * implementation. |
|
617 */ |
|
618 private static class CurrencyNameGetter |
|
619 implements LocaleServiceProviderPool.LocalizedObjectGetter<CurrencyNameProvider, |
|
620 String> { |
|
621 private static final CurrencyNameGetter INSTANCE = new CurrencyNameGetter(); |
|
622 |
|
623 public String getObject(CurrencyNameProvider currencyNameProvider, |
|
624 Locale locale, |
|
625 String key, |
|
626 Object... params) { |
|
627 assert params.length == 1; |
|
628 int type = (Integer)params[0]; |
|
629 |
|
630 switch(type) { |
|
631 case SYMBOL: |
|
632 return currencyNameProvider.getSymbol(key, locale); |
|
633 case DISPLAYNAME: |
|
634 return currencyNameProvider.getDisplayName(key, locale); |
|
635 default: |
|
636 assert false; // shouldn't happen |
|
637 } |
|
638 |
|
639 return null; |
|
640 } |
|
641 } |
|
642 |
|
643 private static int[] readIntArray(DataInputStream dis, int count) throws IOException { |
|
644 int[] ret = new int[count]; |
|
645 for (int i = 0; i < count; i++) { |
|
646 ret[i] = dis.readInt(); |
|
647 } |
|
648 |
|
649 return ret; |
|
650 } |
|
651 |
|
652 private static long[] readLongArray(DataInputStream dis, int count) throws IOException { |
|
653 long[] ret = new long[count]; |
|
654 for (int i = 0; i < count; i++) { |
|
655 ret[i] = dis.readLong(); |
|
656 } |
|
657 |
|
658 return ret; |
|
659 } |
|
660 |
|
661 private static String[] readStringArray(DataInputStream dis, int count) throws IOException { |
|
662 String[] ret = new String[count]; |
|
663 for (int i = 0; i < count; i++) { |
|
664 ret[i] = dis.readUTF(); |
|
665 } |
|
666 |
|
667 return ret; |
|
668 } |
|
669 |
|
670 /** |
|
671 * Replaces currency data found in the currencydata.properties file |
|
672 * |
|
673 * @param pattern regex pattern for the properties |
|
674 * @param ctry country code |
|
675 * @param data currency data. This is a comma separated string that |
|
676 * consists of "three-letter alphabet code", "three-digit numeric code", |
|
677 * and "one-digit (0,1,2, or 3) default fraction digit". |
|
678 * For example, "JPZ,392,0". |
|
679 * @throws |
|
680 */ |
|
681 private static void replaceCurrencyData(Pattern pattern, String ctry, String curdata) { |
|
682 |
|
683 if (ctry.length() != 2) { |
|
684 // ignore invalid country code |
|
685 String message = new StringBuilder() |
|
686 .append("The entry in currency.properties for ") |
|
687 .append(ctry).append(" is ignored because of the invalid country code.") |
|
688 .toString(); |
|
689 log(Level.INFO, message, null); |
|
690 return; |
|
691 } |
|
692 |
|
693 Matcher m = pattern.matcher(curdata); |
|
694 if (!m.find()) { |
|
695 // format is not recognized. ignore the data |
|
696 String message = new StringBuilder() |
|
697 .append("The entry in currency.properties for ") |
|
698 .append(ctry) |
|
699 .append(" is ignored because the value format is not recognized.") |
|
700 .toString(); |
|
701 log(Level.INFO, message, null); |
|
702 return; |
|
703 } |
|
704 |
|
705 String code = m.group(1); |
|
706 int numeric = Integer.parseInt(m.group(2)); |
|
707 int fraction = Integer.parseInt(m.group(3)); |
|
708 int entry = numeric << NUMERIC_CODE_SHIFT; |
|
709 |
|
710 int index; |
|
711 for (index = 0; index < scOldCurrencies.length; index++) { |
|
712 if (scOldCurrencies[index].equals(code)) { |
|
713 break; |
|
714 } |
|
715 } |
|
716 |
|
717 if (index == scOldCurrencies.length) { |
|
718 // simple case |
|
719 entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT) | |
|
720 (code.charAt(2) - 'A'); |
|
721 } else { |
|
722 // special case |
|
723 entry |= SPECIAL_CASE_COUNTRY_MASK | |
|
724 (index + SPECIAL_CASE_COUNTRY_INDEX_DELTA); |
|
725 } |
|
726 setMainTableEntry(ctry.charAt(0), ctry.charAt(1), entry); |
|
727 } |
|
728 |
|
729 private static void log(Level level, String message, Throwable t) { |
|
730 Logger logger = Logger.getLogger("java.util.Currency"); |
|
731 if (logger.isLoggable(level)) { |
|
732 if (t != null) { |
|
733 logger.log(level, message, t); |
|
734 } else { |
|
735 logger.log(level, message); |
|
736 } |
|
737 } |
|
738 } |
|
739 } |