KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > util > Currency


1 /*
2  * @(#)Currency.java 1.8 04/03/16
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.util;
9
10 import java.io.Serializable JavaDoc;
11 import java.security.AccessController JavaDoc;
12 import java.security.PrivilegedAction JavaDoc;
13 import sun.text.resources.LocaleData;
14
15
16 /**
17  * Represents a currency. Currencies are identified by their ISO 4217 currency
18  * codes. See the
19  * <a HREF="http://www.bsi-global.com/iso4217currency">
20  * ISO 4217 maintenance agency</a> for more information, including a table of
21  * currency codes.
22  * <p>
23  * The class is designed so that there's never more than one
24  * <code>Currency</code> instance for any given currency. Therefore, there's
25  * no public constructor. You obtain a <code>Currency</code> instance using
26  * the <code>getInstance</code> methods.
27  *
28  * @since 1.4
29  */

30 public final class Currency implements Serializable JavaDoc {
31
32     private static final long serialVersionUID = -158308464356906721L;
33     
34     /**
35      * ISO 4217 currency code for this currency.
36      *
37      * @serial
38      */

39     private final String JavaDoc currencyCode;
40     
41     /**
42      * Default fraction digits for this currency.
43      * Set from currency data tables.
44      */

45     transient private final int defaultFractionDigits;
46
47
48     // class data: instance map
49

50     private static HashMap JavaDoc instances = new HashMap JavaDoc(7);
51
52     
53     // Class data: currency data obtained from java.util.CurrencyData.
54
// Purpose:
55
// - determine valid country codes
56
// - determine valid currency codes
57
// - map country codes to currency codes
58
// - obtain default fraction digits for currency codes
59
//
60
// sc = special case; dfd = default fraction digits
61
// Simple countries are those where the country code is a prefix of the
62
// currency code, and there are no known plans to change the currency.
63
//
64
// table formats:
65
// - mainTable:
66
// - maps country code to 8-bit char
67
// - 26*26 entries, corresponding to [A-Z]*[A-Z]
68
// - \u007F -> not valid country
69
// - bit 7 - 1: special case, bits 0-4 indicate which one
70
// 0: simple country, bits 0-4 indicate final char of currency code
71
// - bits 5-6: fraction digits for simple countries, 0 for special cases
72
// - bits 0-4: final char for currency code for simple country, or ID of special case
73
// - special case IDs:
74
// - 0: country has no currency
75
// - other: index into sc* arrays + 1
76
// - scCutOverTimes: cut-over time in millis as returned by
77
// System.currentTimeMillis for special case countries that are changing
78
// currencies; Long.MAX_VALUE for countries that are not changing currencies
79
// - scOldCurrencies: old currencies for special case countries
80
// - scNewCurrencies: new currencies for special case countries that are
81
// changing currencies; null for others
82
// - scOldCurrenciesDFD: default fraction digits for old currencies
83
// - scNewCurrenciesDFD: default fraction digits for new currencies, 0 for
84
// countries that are not changing currencies
85
// - otherCurrencies: concatenation of all currency codes that are not the
86
// main currency of a simple country, separated by "-"
87
// - otherCurrenciesDFD: decimal format digits for currencies in otherCurrencies, same order
88

89     static String JavaDoc mainTable;
90     static long[] scCutOverTimes;
91     static String JavaDoc[] scOldCurrencies;
92     static String JavaDoc[] scNewCurrencies;
93     static int[] scOldCurrenciesDFD;
94     static int[] scNewCurrenciesDFD;
95     static String JavaDoc otherCurrencies;
96     static int[] otherCurrenciesDFD;
97
98     // handy constants - must match definitions in GenerateCurrencyData
99
// number of characters from A to Z
100
private static final int A_TO_Z = ('Z' - 'A') + 1;
101     // entry for invalid country codes
102
private static final int INVALID_COUNTRY_ENTRY = 0x007F;
103     // entry for countries without currency
104
private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x0080;
105     // mask for simple case country entries
106
private static final int SIMPLE_CASE_COUNTRY_MASK = 0x0000;
107     // mask for simple case country entry final character
108
private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x001F;
109     // mask for simple case country entry default currency digits
110
private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x0060;
111     // shift count for simple case country entry default currency digits
112
private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5;
113     // mask for special case country entries
114
private static final int SPECIAL_CASE_COUNTRY_MASK = 0x0080;
115     // mask for special case country index
116
private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x001F;
117     // delta from entry index component in main table to index into special case tables
118
private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1;
119     // mask for distinguishing simple and special case countries
120
private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK;
121
122     static {
123         AccessController.doPrivileged(new PrivilegedAction JavaDoc() {
124             public Object JavaDoc run() {
125                 try {
126                     Class JavaDoc data = Class.forName("java.util.CurrencyData");
127                     mainTable = (String JavaDoc) data.getDeclaredField("mainTable").get(data);
128                     scCutOverTimes = (long[]) data.getDeclaredField("scCutOverTimes").get(data);
129                     scOldCurrencies = (String JavaDoc[]) data.getDeclaredField("scOldCurrencies").get(data);
130                     scNewCurrencies = (String JavaDoc[]) data.getDeclaredField("scNewCurrencies").get(data);
131                     scOldCurrenciesDFD = (int[]) data.getDeclaredField("scOldCurrenciesDFD").get(data);
132                     scNewCurrenciesDFD = (int[]) data.getDeclaredField("scNewCurrenciesDFD").get(data);
133                     otherCurrencies = (String JavaDoc) data.getDeclaredField("otherCurrencies").get(data);
134                     otherCurrenciesDFD = (int[]) data.getDeclaredField("otherCurrenciesDFD").get(data);
135                 } catch (ClassNotFoundException JavaDoc e) {
136                     throw new InternalError JavaDoc();
137                 } catch (NoSuchFieldException JavaDoc e) {
138                     throw new InternalError JavaDoc();
139                 } catch (IllegalAccessException JavaDoc e) {
140                     throw new InternalError JavaDoc();
141                 }
142                 return null;
143             }
144         });
145     }
146     
147
148     /**
149      * Constructs a <code>Currency</code> instance. The constructor is private
150      * so that we can insure that there's never more than one instance for a
151      * given currency.
152      */

153     private Currency(String JavaDoc currencyCode, int defaultFractionDigits) {
154         this.currencyCode = currencyCode;
155         this.defaultFractionDigits = defaultFractionDigits;
156     }
157     
158     /**
159      * Returns the <code>Currency</code> instance for the given currency code.
160      *
161      * @param currencyCode the ISO 4217 code of the currency
162      * @return the <code>Currency</code> instance for the given currency code
163      * @exception NullPointerException if <code>currencyCode</code> is null
164      * @exception IllegalArgumentException if <code>currencyCode</code> is not
165      * a supported ISO 4217 code.
166      */

167     public static Currency JavaDoc getInstance(String JavaDoc currencyCode) {
168         return getInstance(currencyCode, Integer.MIN_VALUE);
169     }
170     
171     private static Currency JavaDoc getInstance(String JavaDoc currencyCode, int defaultFractionDigits) {
172         synchronized (instances) {
173             // Try to look up the currency code in the instances table.
174
// This does the null pointer check as a side effect.
175
// Also, if there already is an entry, the currencyCode must be valid.
176
Currency JavaDoc instance = (Currency JavaDoc) instances.get(currencyCode);
177             if (instance != null) {
178                 return instance;
179             }
180         
181             if (defaultFractionDigits == Integer.MIN_VALUE) {
182                 // Currency code not internally generated, need to verify first
183
// A currency code must have 3 characters and exist in the main table
184
// or in the list of other currencies.
185
if (currencyCode.length() != 3) {
186                     throw new IllegalArgumentException JavaDoc();
187                 }
188                 char char1 = currencyCode.charAt(0);
189                 char char2 = currencyCode.charAt(1);
190                 int tableEntry = getMainTableEntry(char1, char2);
191                 if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
192                         && tableEntry != INVALID_COUNTRY_ENTRY
193                         && currencyCode.charAt(2) - 'A' == (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) {
194                     defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
195                 } else {
196                     // Check for '-' separately so we don't get false hits in the table.
197
if (currencyCode.charAt(2) == '-') {
198                         throw new IllegalArgumentException JavaDoc();
199                     }
200                     int index = otherCurrencies.indexOf(currencyCode);
201                     if (index == -1) {
202                         throw new IllegalArgumentException JavaDoc();
203                     }
204                     defaultFractionDigits = otherCurrenciesDFD[index / 4];
205                 }
206             }
207         
208             instance = new Currency JavaDoc(currencyCode, defaultFractionDigits);
209             instances.put(currencyCode, instance);
210             return instance;
211         }
212     }
213     
214     /**
215      * Returns the <code>Currency</code> instance for the country of the
216      * given locale. The language and variant components of the locale
217      * are ignored. The result may vary over time, as countries change their
218      * currencies. For example, for the original member countries of the
219      * European Monetary Union, the method returns the old national currencies
220      * until December 31, 2001, and the Euro from January 1, 2002, local time
221      * of the respective countries.
222      * <p>
223      * The method returns <code>null</code> for territories that don't
224      * have a currency, such as Antarctica.
225      *
226      * @param locale the locale for whose country a <code>Currency</code>
227      * instance is needed
228      * @return the <code>Currency</code> instance for the country of the given
229      * locale, or null
230      * @exception NullPointerException if <code>locale</code> or its country
231      * code is null
232      * @exception IllegalArgumentException if the country of the given locale
233      * is not a supported ISO 3166 country code.
234      */

235     public static Currency JavaDoc getInstance(Locale JavaDoc locale) {
236         String JavaDoc country = locale.getCountry();
237         if (country == null) {
238             throw new NullPointerException JavaDoc();
239         }
240
241         if (country.length() != 2) {
242             throw new IllegalArgumentException JavaDoc();
243         }
244         
245         char char1 = country.charAt(0);
246         char char2 = country.charAt(1);
247         int tableEntry = getMainTableEntry(char1, char2);
248         if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
249                     && tableEntry != INVALID_COUNTRY_ENTRY) {
250             char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A');
251             int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
252             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(country);
253             sb.append(finalChar);
254             return getInstance(sb.toString(), defaultFractionDigits);
255         } else {
256             // special cases
257
if (tableEntry == INVALID_COUNTRY_ENTRY) {
258                 throw new IllegalArgumentException JavaDoc();
259             }
260             if (tableEntry == COUNTRY_WITHOUT_CURRENCY_ENTRY) {
261                 return null;
262             } else {
263                 int index = (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA;
264                 if (scCutOverTimes[index] == Long.MAX_VALUE || System.currentTimeMillis() < scCutOverTimes[index]) {
265                     return getInstance(scOldCurrencies[index], scOldCurrenciesDFD[index]);
266                 } else {
267                     return getInstance(scNewCurrencies[index], scNewCurrenciesDFD[index]);
268                 }
269             }
270         }
271     }
272     
273     /**
274      * Gets the ISO 4217 currency code of this currency.
275      *
276      * @return the ISO 4217 currency code of this currency.
277      */

278     public String JavaDoc getCurrencyCode() {
279         return currencyCode;
280     }
281     
282     /**
283      * Gets the symbol of this currency for the default locale.
284      * For example, for the US Dollar, the symbol is "$" if the default
285      * locale is the US, while for other locales it may be "US$". If no
286      * symbol can be determined, the ISO 4217 currency code is returned.
287      *
288      * @return the symbol of this currency for the default locale
289      */

290     public String JavaDoc getSymbol() {
291         return getSymbol(Locale.getDefault());
292     }
293     
294     /**
295      * Gets the symbol of this currency for the specified locale.
296      * For example, for the US Dollar, the symbol is "$" if the specified
297      * locale is the US, while for other locales it may be "US$". If no
298      * symbol can be determined, the ISO 4217 currency code is returned.
299      *
300      * @param locale the locale for which a display name for this currency is
301      * needed
302      * @return the symbol of this currency for the specified locale
303      * @exception NullPointerException if <code>locale</code> is null
304      */

305     public String JavaDoc getSymbol(Locale JavaDoc locale) {
306         ResourceBundle JavaDoc bundle;
307         try {
308             bundle = LocaleData.getLocaleElements(locale);
309         } catch (MissingResourceException JavaDoc e) {
310             // use currency code as symbol of last resort
311
return currencyCode;
312         }
313         String JavaDoc[][] symbols =
314                 (String JavaDoc[][]) bundle.getObject("CurrencySymbols");
315         if (symbols != null) {
316             for (int i = 0; i < symbols.length; i++) {
317                 if (symbols[i][0].equals(currencyCode)) {
318                     return symbols[i][1];
319                 }
320             }
321         }
322         // use currency code as symbol of last resort
323
return currencyCode;
324     }
325     
326     /**
327      * Gets the default number of fraction digits used with this currency.
328      * For example, the default number of fraction digits for the Euro is 2,
329      * while for the Japanese Yen it's 0.
330      * In the case of pseudo-currencies, such as IMF Special Drawing Rights,
331      * -1 is returned.
332      *
333      * @return the default number of fraction digits used with this currency
334      */

335     public int getDefaultFractionDigits() {
336         return defaultFractionDigits;
337     }
338     
339     /**
340      * Returns the ISO 4217 currency code of this currency.
341      *
342      * @return the ISO 4217 currency code of this currency
343      */

344     public String JavaDoc toString() {
345         return currencyCode;
346     }
347
348     /**
349      * Resolves instances being deserialized to a single instance per currency.
350      */

351     private Object JavaDoc readResolve() {
352         return getInstance(currencyCode);
353     }
354     
355     /**
356      * Gets the main table entry for the country whose country code consists
357      * of char1 and char2.
358      */

359     private static int getMainTableEntry(char char1, char char2) {
360         if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') {
361             throw new IllegalArgumentException JavaDoc();
362         }
363         return mainTable.charAt((char1 - 'A') * A_TO_Z + (char2 - 'A'));
364     }
365 }
366
Popular Tags