src/java.base/share/classes/java/util/Currency.java
changeset 47216 71c04702a3d5
parent 38440 9e77c5b81def
child 48251 57148c79bd75
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/java/util/Currency.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,978 @@
+/*
+ * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.util;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.spi.CurrencyNameProvider;
+import sun.util.locale.provider.LocaleServiceProviderPool;
+import sun.util.logging.PlatformLogger;
+
+
+/**
+ * Represents a currency. Currencies are identified by their ISO 4217 currency
+ * codes. Visit the <a href="http://www.iso.org/iso/home/standards/currency_codes.htm">
+ * ISO web site</a> for more information.
+ * <p>
+ * The class is designed so that there's never more than one
+ * <code>Currency</code> instance for any given currency. Therefore, there's
+ * no public constructor. You obtain a <code>Currency</code> instance using
+ * the <code>getInstance</code> methods.
+ * <p>
+ * Users can supersede the Java runtime currency data by means of the system
+ * property {@code java.util.currency.data}. If this system property is
+ * defined then its value is the location of a properties file, the contents of
+ * which are key/value pairs of the ISO 3166 country codes and the ISO 4217
+ * currency data respectively.  The value part consists of three ISO 4217 values
+ * of a currency, i.e., an alphabetic code, a numeric code, and a minor unit.
+ * Those three ISO 4217 values are separated by commas.
+ * The lines which start with '#'s are considered comment lines. An optional UTC
+ * timestamp may be specified per currency entry if users need to specify a
+ * cutover date indicating when the new data comes into effect. The timestamp is
+ * appended to the end of the currency properties and uses a comma as a separator.
+ * If a UTC datestamp is present and valid, the JRE will only use the new currency
+ * properties if the current UTC date is later than the date specified at class
+ * loading time. The format of the timestamp must be of ISO 8601 format :
+ * {@code 'yyyy-MM-dd'T'HH:mm:ss'}. For example,
+ * <p>
+ * <code>
+ * #Sample currency properties<br>
+ * JP=JPZ,999,0
+ * </code>
+ * <p>
+ * will supersede the currency data for Japan.
+ *
+ * <p>
+ * <code>
+ * #Sample currency properties with cutover date<br>
+ * JP=JPZ,999,0,2014-01-01T00:00:00
+ * </code>
+ * <p>
+ * will supersede the currency data for Japan if {@code Currency} class is loaded after
+ * 1st January 2014 00:00:00 GMT.
+ * <p>
+ * Where syntactically malformed entries are encountered, the entry is ignored
+ * and the remainder of entries in file are processed. For instances where duplicate
+ * country code entries exist, the behavior of the Currency information for that
+ * {@code Currency} is undefined and the remainder of entries in file are processed.
+ * <p>
+ * It is recommended to use {@link java.math.BigDecimal} class while dealing
+ * with {@code Currency} or monetary values as it provides better handling of floating
+ * point numbers and their operations.
+ *
+ * @see java.math.BigDecimal
+ * @since 1.4
+ */
+public final class Currency implements Serializable {
+
+    private static final long serialVersionUID = -158308464356906721L;
+
+    /**
+     * ISO 4217 currency code for this currency.
+     *
+     * @serial
+     */
+    private final String currencyCode;
+
+    /**
+     * Default fraction digits for this currency.
+     * Set from currency data tables.
+     */
+    private final transient int defaultFractionDigits;
+
+    /**
+     * ISO 4217 numeric code for this currency.
+     * Set from currency data tables.
+     */
+    private final transient int numericCode;
+
+
+    // class data: instance map
+
+    private static ConcurrentMap<String, Currency> instances = new ConcurrentHashMap<>(7);
+    private static HashSet<Currency> available;
+
+    // Class data: currency data obtained from currency.data file.
+    // Purpose:
+    // - determine valid country codes
+    // - determine valid currency codes
+    // - map country codes to currency codes
+    // - obtain default fraction digits for currency codes
+    //
+    // sc = special case; dfd = default fraction digits
+    // Simple countries are those where the country code is a prefix of the
+    // currency code, and there are no known plans to change the currency.
+    //
+    // table formats:
+    // - mainTable:
+    //   - maps country code to 32-bit int
+    //   - 26*26 entries, corresponding to [A-Z]*[A-Z]
+    //   - \u007F -> not valid country
+    //   - bits 20-31: unused
+    //   - bits 10-19: numeric code (0 to 1023)
+    //   - bit 9: 1 - special case, bits 0-4 indicate which one
+    //            0 - simple country, bits 0-4 indicate final char of currency code
+    //   - bits 5-8: fraction digits for simple countries, 0 for special cases
+    //   - bits 0-4: final char for currency code for simple country, or ID of special case
+    // - special case IDs:
+    //   - 0: country has no currency
+    //   - other: index into specialCasesList
+
+    static int formatVersion;
+    static int dataVersion;
+    static int[] mainTable;
+    static List<SpecialCaseEntry> specialCasesList;
+    static List<OtherCurrencyEntry> otherCurrenciesList;
+
+    // handy constants - must match definitions in GenerateCurrencyData
+    // magic number
+    private static final int MAGIC_NUMBER = 0x43757244;
+    // number of characters from A to Z
+    private static final int A_TO_Z = ('Z' - 'A') + 1;
+    // entry for invalid country codes
+    private static final int INVALID_COUNTRY_ENTRY = 0x0000007F;
+    // entry for countries without currency
+    private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x00000200;
+    // mask for simple case country entries
+    private static final int SIMPLE_CASE_COUNTRY_MASK = 0x00000000;
+    // mask for simple case country entry final character
+    private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x0000001F;
+    // mask for simple case country entry default currency digits
+    private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x000001E0;
+    // shift count for simple case country entry default currency digits
+    private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5;
+    // maximum number for simple case country entry default currency digits
+    private static final int SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS = 9;
+    // mask for special case country entries
+    private static final int SPECIAL_CASE_COUNTRY_MASK = 0x00000200;
+    // mask for special case country index
+    private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x0000001F;
+    // delta from entry index component in main table to index into special case tables
+    private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1;
+    // mask for distinguishing simple and special case countries
+    private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK;
+    // mask for the numeric code of the currency
+    private static final int NUMERIC_CODE_MASK = 0x000FFC00;
+    // shift count for the numeric code of the currency
+    private static final int NUMERIC_CODE_SHIFT = 10;
+
+    // Currency data format version
+    private static final int VALID_FORMAT_VERSION = 3;
+
+    static {
+        AccessController.doPrivileged(new PrivilegedAction<>() {
+            @Override
+            public Void run() {
+                try {
+                    try (InputStream in = getClass().getResourceAsStream("/java/util/currency.data")) {
+                        if (in == null) {
+                            throw new InternalError("Currency data not found");
+                        }
+                        DataInputStream dis = new DataInputStream(new BufferedInputStream(in));
+                        if (dis.readInt() != MAGIC_NUMBER) {
+                            throw new InternalError("Currency data is possibly corrupted");
+                        }
+                        formatVersion = dis.readInt();
+                        if (formatVersion != VALID_FORMAT_VERSION) {
+                            throw new InternalError("Currency data format is incorrect");
+                        }
+                        dataVersion = dis.readInt();
+                        mainTable = readIntArray(dis, A_TO_Z * A_TO_Z);
+                        int scCount = dis.readInt();
+                        specialCasesList = readSpecialCases(dis, scCount);
+                        int ocCount = dis.readInt();
+                        otherCurrenciesList = readOtherCurrencies(dis, ocCount);
+                    }
+                } catch (IOException e) {
+                    throw new InternalError(e);
+                }
+
+                // look for the properties file for overrides
+                String propsFile = System.getProperty("java.util.currency.data");
+                if (propsFile == null) {
+                    propsFile = System.getProperty("java.home") + File.separator + "lib" +
+                        File.separator + "currency.properties";
+                }
+                try {
+                    File propFile = new File(propsFile);
+                    if (propFile.exists()) {
+                        Properties props = new Properties();
+                        try (FileReader fr = new FileReader(propFile)) {
+                            props.load(fr);
+                        }
+                        Set<String> keys = props.stringPropertyNames();
+                        Pattern propertiesPattern =
+                            Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" +
+                                "(\\d+)\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" +
+                                "\\d{2}:\\d{2})?");
+                        for (String key : keys) {
+                           replaceCurrencyData(propertiesPattern,
+                               key.toUpperCase(Locale.ROOT),
+                               props.getProperty(key).toUpperCase(Locale.ROOT));
+                        }
+                    }
+                } catch (IOException e) {
+                    info("currency.properties is ignored because of an IOException", e);
+                }
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Constants for retrieving localized names from the name providers.
+     */
+    private static final int SYMBOL = 0;
+    private static final int DISPLAYNAME = 1;
+
+
+    /**
+     * Constructs a <code>Currency</code> instance. The constructor is private
+     * so that we can insure that there's never more than one instance for a
+     * given currency.
+     */
+    private Currency(String currencyCode, int defaultFractionDigits, int numericCode) {
+        this.currencyCode = currencyCode;
+        this.defaultFractionDigits = defaultFractionDigits;
+        this.numericCode = numericCode;
+    }
+
+    /**
+     * Returns the <code>Currency</code> instance for the given currency code.
+     *
+     * @param currencyCode the ISO 4217 code of the currency
+     * @return the <code>Currency</code> instance for the given currency code
+     * @exception NullPointerException if <code>currencyCode</code> is null
+     * @exception IllegalArgumentException if <code>currencyCode</code> is not
+     * a supported ISO 4217 code.
+     */
+    public static Currency getInstance(String currencyCode) {
+        return getInstance(currencyCode, Integer.MIN_VALUE, 0);
+    }
+
+    private static Currency getInstance(String currencyCode, int defaultFractionDigits,
+        int numericCode) {
+        // Try to look up the currency code in the instances table.
+        // This does the null pointer check as a side effect.
+        // Also, if there already is an entry, the currencyCode must be valid.
+        Currency instance = instances.get(currencyCode);
+        if (instance != null) {
+            return instance;
+        }
+
+        if (defaultFractionDigits == Integer.MIN_VALUE) {
+            // Currency code not internally generated, need to verify first
+            // A currency code must have 3 characters and exist in the main table
+            // or in the list of other currencies.
+            boolean found = false;
+            if (currencyCode.length() != 3) {
+                throw new IllegalArgumentException();
+            }
+            char char1 = currencyCode.charAt(0);
+            char char2 = currencyCode.charAt(1);
+            int tableEntry = getMainTableEntry(char1, char2);
+            if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
+                    && tableEntry != INVALID_COUNTRY_ENTRY
+                    && currencyCode.charAt(2) - 'A' == (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) {
+                defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
+                numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
+                found = true;
+            } else { //special case
+                int[] fractionAndNumericCode = SpecialCaseEntry.findEntry(currencyCode);
+                if (fractionAndNumericCode != null) {
+                    defaultFractionDigits = fractionAndNumericCode[0];
+                    numericCode = fractionAndNumericCode[1];
+                    found = true;
+                }
+            }
+
+            if (!found) {
+                OtherCurrencyEntry ocEntry = OtherCurrencyEntry.findEntry(currencyCode);
+                if (ocEntry == null) {
+                    throw new IllegalArgumentException();
+                }
+                defaultFractionDigits = ocEntry.fraction;
+                numericCode = ocEntry.numericCode;
+            }
+        }
+
+        Currency currencyVal =
+            new Currency(currencyCode, defaultFractionDigits, numericCode);
+        instance = instances.putIfAbsent(currencyCode, currencyVal);
+        return (instance != null ? instance : currencyVal);
+    }
+
+    /**
+     * Returns the <code>Currency</code> instance for the country of the
+     * given locale. The language and variant components of the locale
+     * are ignored. The result may vary over time, as countries change their
+     * currencies. For example, for the original member countries of the
+     * European Monetary Union, the method returns the old national currencies
+     * until December 31, 2001, and the Euro from January 1, 2002, local time
+     * of the respective countries.
+     * <p>
+     * The method returns <code>null</code> for territories that don't
+     * have a currency, such as Antarctica.
+     *
+     * @param locale the locale for whose country a <code>Currency</code>
+     * instance is needed
+     * @return the <code>Currency</code> instance for the country of the given
+     * locale, or {@code null}
+     * @exception NullPointerException if <code>locale</code>
+     * is {@code null}
+     * @exception IllegalArgumentException if the country of the given {@code locale}
+     * is not a supported ISO 3166 country code.
+     */
+    public static Currency getInstance(Locale locale) {
+        String country = locale.getCountry();
+        if (country == null) {
+            throw new NullPointerException();
+        }
+
+        if (country.length() != 2) {
+            throw new IllegalArgumentException();
+        }
+
+        char char1 = country.charAt(0);
+        char char2 = country.charAt(1);
+        int tableEntry = getMainTableEntry(char1, char2);
+        if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
+                    && tableEntry != INVALID_COUNTRY_ENTRY) {
+            char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A');
+            int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
+            int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
+            StringBuilder sb = new StringBuilder(country);
+            sb.append(finalChar);
+            return getInstance(sb.toString(), defaultFractionDigits, numericCode);
+        } else {
+            // special cases
+            if (tableEntry == INVALID_COUNTRY_ENTRY) {
+                throw new IllegalArgumentException();
+            }
+            if (tableEntry == COUNTRY_WITHOUT_CURRENCY_ENTRY) {
+                return null;
+            } else {
+                int index = SpecialCaseEntry.toIndex(tableEntry);
+                SpecialCaseEntry scEntry = specialCasesList.get(index);
+                if (scEntry.cutOverTime == Long.MAX_VALUE
+                        || System.currentTimeMillis() < scEntry.cutOverTime) {
+                    return getInstance(scEntry.oldCurrency,
+                            scEntry.oldCurrencyFraction,
+                            scEntry.oldCurrencyNumericCode);
+                } else {
+                    return getInstance(scEntry.newCurrency,
+                            scEntry.newCurrencyFraction,
+                            scEntry.newCurrencyNumericCode);
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the set of available currencies.  The returned set of currencies
+     * contains all of the available currencies, which may include currencies
+     * that represent obsolete ISO 4217 codes.  The set can be modified
+     * without affecting the available currencies in the runtime.
+     *
+     * @return the set of available currencies.  If there is no currency
+     *    available in the runtime, the returned set is empty.
+     * @since 1.7
+     */
+    public static Set<Currency> getAvailableCurrencies() {
+        synchronized(Currency.class) {
+            if (available == null) {
+                available = new HashSet<>(256);
+
+                // Add simple currencies first
+                for (char c1 = 'A'; c1 <= 'Z'; c1 ++) {
+                    for (char c2 = 'A'; c2 <= 'Z'; c2 ++) {
+                        int tableEntry = getMainTableEntry(c1, c2);
+                        if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
+                             && tableEntry != INVALID_COUNTRY_ENTRY) {
+                            char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A');
+                            int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
+                            int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
+                            StringBuilder sb = new StringBuilder();
+                            sb.append(c1);
+                            sb.append(c2);
+                            sb.append(finalChar);
+                            available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode));
+                        } else if ((tableEntry & COUNTRY_TYPE_MASK) == SPECIAL_CASE_COUNTRY_MASK
+                                && tableEntry != INVALID_COUNTRY_ENTRY
+                                && tableEntry != COUNTRY_WITHOUT_CURRENCY_ENTRY) {
+                            int index = SpecialCaseEntry.toIndex(tableEntry);
+                            SpecialCaseEntry scEntry = specialCasesList.get(index);
+
+                            if (scEntry.cutOverTime == Long.MAX_VALUE
+                                    || System.currentTimeMillis() < scEntry.cutOverTime) {
+                                available.add(getInstance(scEntry.oldCurrency,
+                                        scEntry.oldCurrencyFraction,
+                                        scEntry.oldCurrencyNumericCode));
+                            } else {
+                                available.add(getInstance(scEntry.newCurrency,
+                                        scEntry.newCurrencyFraction,
+                                        scEntry.newCurrencyNumericCode));
+                            }
+                        }
+                    }
+                }
+
+                // Now add other currencies
+                for (OtherCurrencyEntry entry : otherCurrenciesList) {
+                    available.add(getInstance(entry.currencyCode));
+                }
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        Set<Currency> result = (Set<Currency>) available.clone();
+        return result;
+    }
+
+    /**
+     * Gets the ISO 4217 currency code of this currency.
+     *
+     * @return the ISO 4217 currency code of this currency.
+     */
+    public String getCurrencyCode() {
+        return currencyCode;
+    }
+
+    /**
+     * Gets the symbol of this currency for the default
+     * {@link Locale.Category#DISPLAY DISPLAY} locale.
+     * For example, for the US Dollar, the symbol is "$" if the default
+     * locale is the US, while for other locales it may be "US$". If no
+     * symbol can be determined, the ISO 4217 currency code is returned.
+     * <p>
+     * This is equivalent to calling
+     * {@link #getSymbol(Locale)
+     *     getSymbol(Locale.getDefault(Locale.Category.DISPLAY))}.
+     *
+     * @return the symbol of this currency for the default
+     *     {@link Locale.Category#DISPLAY DISPLAY} locale
+     */
+    public String getSymbol() {
+        return getSymbol(Locale.getDefault(Locale.Category.DISPLAY));
+    }
+
+    /**
+     * Gets the symbol of this currency for the specified locale.
+     * For example, for the US Dollar, the symbol is "$" if the specified
+     * locale is the US, while for other locales it may be "US$". If no
+     * symbol can be determined, the ISO 4217 currency code is returned.
+     *
+     * @param locale the locale for which a display name for this currency is
+     * needed
+     * @return the symbol of this currency for the specified locale
+     * @exception NullPointerException if <code>locale</code> is null
+     */
+    public String getSymbol(Locale locale) {
+        LocaleServiceProviderPool pool =
+            LocaleServiceProviderPool.getPool(CurrencyNameProvider.class);
+        String symbol = pool.getLocalizedObject(
+                                CurrencyNameGetter.INSTANCE,
+                                locale, currencyCode, SYMBOL);
+        if (symbol != null) {
+            return symbol;
+        }
+
+        // use currency code as symbol of last resort
+        return currencyCode;
+    }
+
+    /**
+     * Gets the default number of fraction digits used with this currency.
+     * Note that the number of fraction digits is the same as ISO 4217's
+     * minor unit for the currency.
+     * For example, the default number of fraction digits for the Euro is 2,
+     * while for the Japanese Yen it's 0.
+     * In the case of pseudo-currencies, such as IMF Special Drawing Rights,
+     * -1 is returned.
+     *
+     * @return the default number of fraction digits used with this currency
+    */
+    public int getDefaultFractionDigits() {
+        return defaultFractionDigits;
+    }
+
+    /**
+     * Returns the ISO 4217 numeric code of this currency.
+     *
+     * @return the ISO 4217 numeric code of this currency
+     * @since 1.7
+     */
+    public int getNumericCode() {
+        return numericCode;
+    }
+
+    /**
+     * Returns the 3 digit ISO 4217 numeric code of this currency as a {@code String}.
+     * Unlike {@link getNumericCode()}, which returns the numeric code as {@code int},
+     * this method always returns the numeric code as a 3 digit string.
+     * e.g. a numeric value of 32 would be returned as "032",
+     * and a numeric value of 6 would be returned as "006".
+     *
+     * @return the 3 digit ISO 4217 numeric code of this currency as a {@code String}
+     * @since 9
+     */
+    public String getNumericCodeAsString() {
+        /* numeric code could be returned as a 3 digit string simply by using
+           String.format("%03d",numericCode); which uses regex to parse the format,
+           "%03d" in this case. Parsing a regex gives an extra performance overhead,
+           so String.format() approach is avoided in this scenario.
+        */
+        if (numericCode < 100) {
+            StringBuilder sb = new StringBuilder();
+            sb.append('0');
+            if (numericCode < 10) {
+                sb.append('0');
+            }
+            return sb.append(numericCode).toString();
+        }
+        return String.valueOf(numericCode);
+    }
+
+    /**
+     * Gets the name that is suitable for displaying this currency for
+     * the default {@link Locale.Category#DISPLAY DISPLAY} locale.
+     * If there is no suitable display name found
+     * for the default locale, the ISO 4217 currency code is returned.
+     * <p>
+     * This is equivalent to calling
+     * {@link #getDisplayName(Locale)
+     *     getDisplayName(Locale.getDefault(Locale.Category.DISPLAY))}.
+     *
+     * @return the display name of this currency for the default
+     *     {@link Locale.Category#DISPLAY DISPLAY} locale
+     * @since 1.7
+     */
+    public String getDisplayName() {
+        return getDisplayName(Locale.getDefault(Locale.Category.DISPLAY));
+    }
+
+    /**
+     * Gets the name that is suitable for displaying this currency for
+     * the specified locale.  If there is no suitable display name found
+     * for the specified locale, the ISO 4217 currency code is returned.
+     *
+     * @param locale the locale for which a display name for this currency is
+     * needed
+     * @return the display name of this currency for the specified locale
+     * @exception NullPointerException if <code>locale</code> is null
+     * @since 1.7
+     */
+    public String getDisplayName(Locale locale) {
+        LocaleServiceProviderPool pool =
+            LocaleServiceProviderPool.getPool(CurrencyNameProvider.class);
+        String result = pool.getLocalizedObject(
+                                CurrencyNameGetter.INSTANCE,
+                                locale, currencyCode, DISPLAYNAME);
+        if (result != null) {
+            return result;
+        }
+
+        // use currency code as symbol of last resort
+        return currencyCode;
+    }
+
+    /**
+     * Returns the ISO 4217 currency code of this currency.
+     *
+     * @return the ISO 4217 currency code of this currency
+     */
+    @Override
+    public String toString() {
+        return currencyCode;
+    }
+
+    /**
+     * Resolves instances being deserialized to a single instance per currency.
+     */
+    private Object readResolve() {
+        return getInstance(currencyCode);
+    }
+
+    /**
+     * Gets the main table entry for the country whose country code consists
+     * of char1 and char2.
+     */
+    private static int getMainTableEntry(char char1, char char2) {
+        if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') {
+            throw new IllegalArgumentException();
+        }
+        return mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')];
+    }
+
+    /**
+     * Sets the main table entry for the country whose country code consists
+     * of char1 and char2.
+     */
+    private static void setMainTableEntry(char char1, char char2, int entry) {
+        if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') {
+            throw new IllegalArgumentException();
+        }
+        mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')] = entry;
+    }
+
+    /**
+     * Obtains a localized currency names from a CurrencyNameProvider
+     * implementation.
+     */
+    private static class CurrencyNameGetter
+        implements LocaleServiceProviderPool.LocalizedObjectGetter<CurrencyNameProvider,
+                                                                   String> {
+        private static final CurrencyNameGetter INSTANCE = new CurrencyNameGetter();
+
+        @Override
+        public String getObject(CurrencyNameProvider currencyNameProvider,
+                                Locale locale,
+                                String key,
+                                Object... params) {
+            assert params.length == 1;
+            int type = (Integer)params[0];
+
+            switch(type) {
+            case SYMBOL:
+                return currencyNameProvider.getSymbol(key, locale);
+            case DISPLAYNAME:
+                return currencyNameProvider.getDisplayName(key, locale);
+            default:
+                assert false; // shouldn't happen
+            }
+
+            return null;
+        }
+    }
+
+    private static int[] readIntArray(DataInputStream dis, int count) throws IOException {
+        int[] ret = new int[count];
+        for (int i = 0; i < count; i++) {
+            ret[i] = dis.readInt();
+        }
+
+        return ret;
+    }
+
+    private static List<SpecialCaseEntry> readSpecialCases(DataInputStream dis,
+            int count)
+            throws IOException {
+
+        List<SpecialCaseEntry> list = new ArrayList<>(count);
+        long cutOverTime;
+        String oldCurrency;
+        String newCurrency;
+        int oldCurrencyFraction;
+        int newCurrencyFraction;
+        int oldCurrencyNumericCode;
+        int newCurrencyNumericCode;
+
+        for (int i = 0; i < count; i++) {
+            cutOverTime = dis.readLong();
+            oldCurrency = dis.readUTF();
+            newCurrency = dis.readUTF();
+            oldCurrencyFraction = dis.readInt();
+            newCurrencyFraction = dis.readInt();
+            oldCurrencyNumericCode = dis.readInt();
+            newCurrencyNumericCode = dis.readInt();
+            SpecialCaseEntry sc = new SpecialCaseEntry(cutOverTime,
+                    oldCurrency, newCurrency,
+                    oldCurrencyFraction, newCurrencyFraction,
+                    oldCurrencyNumericCode, newCurrencyNumericCode);
+            list.add(sc);
+        }
+        return list;
+    }
+
+    private static List<OtherCurrencyEntry> readOtherCurrencies(DataInputStream dis,
+            int count)
+            throws IOException {
+
+        List<OtherCurrencyEntry> list = new ArrayList<>(count);
+        String currencyCode;
+        int fraction;
+        int numericCode;
+
+        for (int i = 0; i < count; i++) {
+            currencyCode = dis.readUTF();
+            fraction = dis.readInt();
+            numericCode = dis.readInt();
+            OtherCurrencyEntry oc = new OtherCurrencyEntry(currencyCode,
+                    fraction,
+                    numericCode);
+            list.add(oc);
+        }
+        return list;
+    }
+
+    /**
+     * Replaces currency data found in the currencydata.properties file
+     *
+     * @param pattern regex pattern for the properties
+     * @param ctry country code
+     * @param curdata currency data.  This is a comma separated string that
+     *    consists of "three-letter alphabet code", "three-digit numeric code",
+     *    and "one-digit (0-9) default fraction digit".
+     *    For example, "JPZ,392,0".
+     *    An optional UTC date can be appended to the string (comma separated)
+     *    to allow a currency change take effect after date specified.
+     *    For example, "JP=JPZ,999,0,2014-01-01T00:00:00" has no effect unless
+     *    UTC time is past 1st January 2014 00:00:00 GMT.
+     */
+    private static void replaceCurrencyData(Pattern pattern, String ctry, String curdata) {
+
+        if (ctry.length() != 2) {
+            // ignore invalid country code
+            info("currency.properties entry for " + ctry +
+                    " is ignored because of the invalid country code.", null);
+            return;
+        }
+
+        Matcher m = pattern.matcher(curdata);
+        if (!m.find() || (m.group(4) == null && countOccurrences(curdata, ',') >= 3)) {
+            // format is not recognized.  ignore the data
+            // if group(4) date string is null and we've 4 values, bad date value
+            info("currency.properties entry for " + ctry +
+                    " ignored because the value format is not recognized.", null);
+            return;
+        }
+
+        try {
+            if (m.group(4) != null && !isPastCutoverDate(m.group(4))) {
+                info("currency.properties entry for " + ctry +
+                        " ignored since cutover date has not passed :" + curdata, null);
+                return;
+            }
+        } catch (ParseException ex) {
+            info("currency.properties entry for " + ctry +
+                        " ignored since exception encountered :" + ex.getMessage(), null);
+            return;
+        }
+
+        String code = m.group(1);
+        int numeric = Integer.parseInt(m.group(2));
+        int entry = numeric << NUMERIC_CODE_SHIFT;
+        int fraction = Integer.parseInt(m.group(3));
+        if (fraction > SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS) {
+            info("currency.properties entry for " + ctry +
+                " ignored since the fraction is more than " +
+                SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS + ":" + curdata, null);
+            return;
+        }
+
+        int index = SpecialCaseEntry.indexOf(code, fraction, numeric);
+
+        /* if a country switches from simple case to special case or
+         * one special case to other special case which is not present
+         * in the sc arrays then insert the new entry in special case arrays
+         */
+        if (index == -1 && (ctry.charAt(0) != code.charAt(0)
+                || ctry.charAt(1) != code.charAt(1))) {
+
+            specialCasesList.add(new SpecialCaseEntry(code, fraction, numeric));
+            index = specialCasesList.size() - 1;
+        }
+
+        if (index == -1) {
+            // simple case
+            entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT)
+                    | (code.charAt(2) - 'A');
+        } else {
+            // special case
+            entry = SPECIAL_CASE_COUNTRY_MASK
+                    | (index + SPECIAL_CASE_COUNTRY_INDEX_DELTA);
+        }
+        setMainTableEntry(ctry.charAt(0), ctry.charAt(1), entry);
+    }
+
+    private static boolean isPastCutoverDate(String s) throws ParseException {
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT);
+        format.setTimeZone(TimeZone.getTimeZone("UTC"));
+        format.setLenient(false);
+        long time = format.parse(s.trim()).getTime();
+        return System.currentTimeMillis() > time;
+
+    }
+
+    private static int countOccurrences(String value, char match) {
+        int count = 0;
+        for (char c : value.toCharArray()) {
+            if (c == match) {
+               ++count;
+            }
+        }
+        return count;
+    }
+
+    private static void info(String message, Throwable t) {
+        PlatformLogger logger = PlatformLogger.getLogger("java.util.Currency");
+        if (logger.isLoggable(PlatformLogger.Level.INFO)) {
+            if (t != null) {
+                logger.info(message, t);
+            } else {
+                logger.info(message);
+            }
+        }
+    }
+
+    /* Used to represent a special case currency entry
+     * - cutOverTime: cut-over time in millis as returned by
+     *   System.currentTimeMillis for special case countries that are changing
+     *   currencies; Long.MAX_VALUE for countries that are not changing currencies
+     * - oldCurrency: old currencies for special case countries
+     * - newCurrency: new currencies for special case countries that are
+     *   changing currencies; null for others
+     * - oldCurrencyFraction: default fraction digits for old currencies
+     * - newCurrencyFraction: default fraction digits for new currencies, 0 for
+     *   countries that are not changing currencies
+     * - oldCurrencyNumericCode: numeric code for old currencies
+     * - newCurrencyNumericCode: numeric code for new currencies, 0 for countries
+     *   that are not changing currencies
+    */
+    private static class SpecialCaseEntry {
+
+        final private long cutOverTime;
+        final private String oldCurrency;
+        final private String newCurrency;
+        final private int oldCurrencyFraction;
+        final private int newCurrencyFraction;
+        final private int oldCurrencyNumericCode;
+        final private int newCurrencyNumericCode;
+
+        private SpecialCaseEntry(long cutOverTime, String oldCurrency, String newCurrency,
+                int oldCurrencyFraction, int newCurrencyFraction,
+                int oldCurrencyNumericCode, int newCurrencyNumericCode) {
+            this.cutOverTime = cutOverTime;
+            this.oldCurrency = oldCurrency;
+            this.newCurrency = newCurrency;
+            this.oldCurrencyFraction = oldCurrencyFraction;
+            this.newCurrencyFraction = newCurrencyFraction;
+            this.oldCurrencyNumericCode = oldCurrencyNumericCode;
+            this.newCurrencyNumericCode = newCurrencyNumericCode;
+        }
+
+        private SpecialCaseEntry(String currencyCode, int fraction,
+                int numericCode) {
+            this(Long.MAX_VALUE, currencyCode, "", fraction, 0, numericCode, 0);
+        }
+
+        //get the index of the special case entry
+        private static int indexOf(String code, int fraction, int numeric) {
+            int size = specialCasesList.size();
+            for (int index = 0; index < size; index++) {
+                SpecialCaseEntry scEntry = specialCasesList.get(index);
+                if (scEntry.oldCurrency.equals(code)
+                        && scEntry.oldCurrencyFraction == fraction
+                        && scEntry.oldCurrencyNumericCode == numeric
+                        && scEntry.cutOverTime == Long.MAX_VALUE) {
+                    return index;
+                }
+            }
+            return -1;
+        }
+
+        // get the fraction and numericCode of the sc currencycode
+        private static int[] findEntry(String code) {
+            int[] fractionAndNumericCode = null;
+            int size = specialCasesList.size();
+            for (int index = 0; index < size; index++) {
+                SpecialCaseEntry scEntry = specialCasesList.get(index);
+                if (scEntry.oldCurrency.equals(code) && (scEntry.cutOverTime == Long.MAX_VALUE
+                        || System.currentTimeMillis() < scEntry.cutOverTime)) {
+                    //consider only when there is no new currency or cutover time is not passed
+                    fractionAndNumericCode = new int[2];
+                    fractionAndNumericCode[0] = scEntry.oldCurrencyFraction;
+                    fractionAndNumericCode[1] = scEntry.oldCurrencyNumericCode;
+                    break;
+                } else if (scEntry.newCurrency.equals(code)
+                        && System.currentTimeMillis() >= scEntry.cutOverTime) {
+                    //consider only if the cutover time is passed
+                    fractionAndNumericCode = new int[2];
+                    fractionAndNumericCode[0] = scEntry.newCurrencyFraction;
+                    fractionAndNumericCode[1] = scEntry.newCurrencyNumericCode;
+                    break;
+                }
+            }
+            return fractionAndNumericCode;
+        }
+
+        // convert the special case entry to sc arrays index
+        private static int toIndex(int tableEntry) {
+            return (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA;
+        }
+
+    }
+
+    /* Used to represent Other currencies
+     * - currencyCode: currency codes that are not the main currency
+     *   of a simple country
+     * - otherCurrenciesDFD: decimal format digits for other currencies
+     * - otherCurrenciesNumericCode: numeric code for other currencies
+     */
+    private static class OtherCurrencyEntry {
+
+        final private String currencyCode;
+        final private int fraction;
+        final private int numericCode;
+
+        private OtherCurrencyEntry(String currencyCode, int fraction,
+                int numericCode) {
+            this.currencyCode = currencyCode;
+            this.fraction = fraction;
+            this.numericCode = numericCode;
+        }
+
+        //get the instance of the other currency code
+        private static OtherCurrencyEntry findEntry(String code) {
+            int size = otherCurrenciesList.size();
+            for (int index = 0; index < size; index++) {
+                OtherCurrencyEntry ocEntry = otherCurrenciesList.get(index);
+                if (ocEntry.currencyCode.equalsIgnoreCase(code)) {
+                    return ocEntry;
+                }
+            }
+            return null;
+        }
+
+    }
+
+}
+
+