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