mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 06:14:49 +02:00
8187443: Forest Consolidation: Move files to unified layout
Reviewed-by: darcy, ihse
This commit is contained in:
parent
270fe13182
commit
3789983e89
56923 changed files with 3 additions and 15727 deletions
978
src/java.base/share/classes/java/util/Currency.java
Normal file
978
src/java.base/share/classes/java/util/Currency.java
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue