privatestatic ConcurrentMap<String, Currency> instances = new ConcurrentHashMap<>(7); privatestatic 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
// handy constants - must match definitions in GenerateCurrencyData // magic number privatestaticfinalint MAGIC_NUMBER = 0x43757244; // number of characters from A to Z privatestaticfinalint A_TO_Z = ('Z' - 'A') + 1; // entry for invalid country codes privatestaticfinalint INVALID_COUNTRY_ENTRY = 0x0000007F; // entry for countries without currency privatestaticfinalint COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x00000200; // mask for simple case country entries privatestaticfinalint SIMPLE_CASE_COUNTRY_MASK = 0x00000000; // mask for simple case country entry final character privatestaticfinalint SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x0000001F; // mask for simple case country entry default currency digits privatestaticfinalint SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x000001E0; // shift count for simple case country entry default currency digits privatestaticfinalint SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5; // maximum number for simple case country entry default currency digits privatestaticfinalint SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS = 9; // mask for special case country entries privatestaticfinalint SPECIAL_CASE_COUNTRY_MASK = 0x00000200; // mask for special case country index privatestaticfinalint SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x0000001F; // delta from entry index component in main table to index into special case tables privatestaticfinalint SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1; // mask for distinguishing simple and special case countries privatestaticfinalint COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK; // mask for the numeric code of the currency privatestaticfinalint NUMERIC_CODE_MASK = 0x000FFC00; // shift count for the numeric code of the currency privatestaticfinalint NUMERIC_CODE_SHIFT = 10;
// Currency data format version privatestaticfinalint VALID_FORMAT_VERSION = 3;
static {
initStatic();
}
@SuppressWarnings("removal") privatestaticvoid initStatic() {
AccessController.doPrivileged(new PrivilegedAction<>() {
@Override publicVoid run() { try { try (InputStream in = getClass().getResourceAsStream("/java/util/currency.data")) { if (in == null) { thrownew InternalError("Currency data not found");
}
DataInputStream dis = new DataInputStream(new BufferedInputStream(in)); if (dis.readInt() != MAGIC_NUMBER) { thrownew InternalError("Currency data is possibly corrupted");
}
formatVersion = dis.readInt(); if (formatVersion != VALID_FORMAT_VERSION) { thrownew 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) { thrownew InternalError(e);
}
// look for the properties file for overrides
String propsFile = System.getProperty("java.util.currency.data"); if (propsFile == null) {
propsFile = StaticProperty.javaHome() + 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);
}
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})?");
List<CurrencyProperty> currencyEntries
= getValidCurrencyData(props, propertiesPattern);
currencyEntries.forEach(Currency::replaceCurrencyData);
}
} catch (IOException e) {
CurrencyProperty.info("currency.properties is ignored"
+ " because of an IOException", e);
} returnnull;
}
});
}
privatestatic 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) { thrownew 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;
}
}
@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: assertfalse; // shouldn't happen
}
returnnull;
}
}
privatestaticint[] readIntArray(DataInputStream dis, int count) throws IOException { int[] ret = newint[count]; for (int i = 0; i < count; i++) {
ret[i] = dis.readInt();
}
return ret;
}
privatestatic 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;
Set<String> keys = props.stringPropertyNames();
List<CurrencyProperty> propertyEntries = new ArrayList<>();
// remove all invalid entries and parse all valid currency properties // entries to a group of CurrencyProperty, classified by currency code
Map<String, List<CurrencyProperty>> currencyCodeGroup = keys.stream()
.map(k -> CurrencyProperty
.getValidEntry(k.toUpperCase(Locale.ROOT),
props.getProperty(k).toUpperCase(Locale.ROOT),
pattern)).flatMap(o -> o.stream())
.collect(Collectors.groupingBy(entry -> entry.currencyCode));
// check each group for inconsistencies
currencyCodeGroup.forEach((curCode, list) -> { boolean inconsistent = CurrencyProperty
.containsInconsistentInstances(list); if (inconsistent) {
list.forEach(prop -> CurrencyProperty.info("The property"
+ " entry for " + prop.country + " is inconsistent."
+ " Ignored.", null));
} else {
propertyEntries.addAll(list);
}
});
String ctry = prop.country;
String code = prop.currencyCode; int numeric = prop.numericCode; int fraction = prop.fraction; int entry = numeric << NUMERIC_CODE_SHIFT;
int index = SpecialCaseEntry.indexOf(code, fraction, numeric);
// If a new entry changes the numeric code/dfd of an existing // currency code, update it in the sc list at the respective // index and also change it in the other currencies list and // main table (if that currency code is also used as a // simple case).
// If all three components do not match with the new entry, // but the currency code exists in the special case list // update the sc entry with the new entry int scCurrencyCodeIndex = -1; if (index == -1) {
scCurrencyCodeIndex = SpecialCaseEntry.currencyCodeIndex(code); if (scCurrencyCodeIndex != -1) { //currency code exists in sc list, then update the old entry
specialCasesList.set(scCurrencyCodeIndex, new SpecialCaseEntry(code, fraction, numeric));
// also update the entry in other currencies list
OtherCurrencyEntry oe = OtherCurrencyEntry.findEntry(code); if (oe != null) { int oIndex = otherCurrenciesList.indexOf(oe);
otherCurrenciesList.set(oIndex, new OtherCurrencyEntry(
code, fraction, numeric));
}
}
}
/* If a country switches from simple case to special case or *onespecialcasetootherspecialcasewhichisnotpresent *inthescarraystheninsertthenewentryinspecialcasearrays. *Ifanentrywithgivencurrencycodeexists,updatewiththenew *entry.
*/ if (index == -1 && (ctry.charAt(0) != code.charAt(0)
|| ctry.charAt(1) != code.charAt(1))) {
if(scCurrencyCodeIndex == -1) {
specialCasesList.add(new SpecialCaseEntry(code, fraction,
numeric));
index = specialCasesList.size() - 1;
} else {
index = scCurrencyCodeIndex;
}
// update the entry in main table if it exists as a simple case
updateMainTableEntry(code, fraction, numeric);
}
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);
}
// update the entry in maintable for any simple case found, if a new // entry as a special case updates the entry in sc list with // existing currency code privatestaticvoid updateMainTableEntry(String code, int fraction, int numeric) { // checking the existence of currency code in mainTable int tableEntry = getMainTableEntry(code.charAt(0), code.charAt(1)); int entry = numeric << NUMERIC_CODE_SHIFT; if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
&& tableEntry != INVALID_COUNTRY_ENTRY
&& code.charAt(2) - 'A' == (tableEntry
& SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) {
int numericCode = (tableEntry & NUMERIC_CODE_MASK)
>> NUMERIC_CODE_SHIFT; int defaultFractionDigits = (tableEntry
& SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK)
>> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; if (numeric != numericCode || fraction != defaultFractionDigits) { // update the entry in main table
entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT)
| (code.charAt(2) - 'A');
setMainTableEntry(code.charAt(0), code.charAt(1), entry);
}
}
}
/* Used to represent a special case currency entry *-cutOverTime:cut-overtimeinmillisasreturnedby *System.currentTimeMillisforspecialcasecountriesthatarechanging *currencies;Long.MAX_VALUEforcountriesthatarenotchangingcurrencies *-oldCurrency:oldcurrenciesforspecialcasecountries *-newCurrency:newcurrenciesforspecialcasecountriesthatare *changingcurrencies;nullforothers *-oldCurrencyFraction:defaultfractiondigitsforoldcurrencies *-newCurrencyFraction:defaultfractiondigitsfornewcurrencies,0for *countriesthatarenotchangingcurrencies *-oldCurrencyNumericCode:numericcodeforoldcurrencies *-newCurrencyNumericCode:numericcodefornewcurrencies,0forcountries *thatarenotchangingcurrencies
*/ privatestaticclass SpecialCaseEntry {
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 privatestaticint 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 privatestaticint[] 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 = newint[2];
fractionAndNumericCode[0] = scEntry.oldCurrencyFraction;
fractionAndNumericCode[1] = scEntry.oldCurrencyNumericCode; break;
} elseif (scEntry.newCurrency.equals(code)
&& System.currentTimeMillis() >= scEntry.cutOverTime) { //consider only if the cutover time is passed
fractionAndNumericCode = newint[2];
fractionAndNumericCode[0] = scEntry.newCurrencyFraction;
fractionAndNumericCode[1] = scEntry.newCurrencyNumericCode; break;
}
} return fractionAndNumericCode;
}
// get the index based on currency code privatestaticint currencyCodeIndex(String code) { 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 return index;
} elseif (scEntry.newCurrency.equals(code)
&& System.currentTimeMillis() >= scEntry.cutOverTime) { //consider only if the cutover time is passed return index;
}
} return -1;
}
// convert the special case entry to sc arrays index privatestaticint toIndex(int tableEntry) { return (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA;
}
}
/* Used to represent Other currencies *-currencyCode:currencycodesthatarenotthemaincurrency *ofasimplecountry *-otherCurrenciesDFD:decimalformatdigitsforothercurrencies *-otherCurrenciesNumericCode:numericcodeforothercurrencies
*/ privatestaticclass OtherCurrencyEntry {
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 privatestatic 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;
}
} returnnull;
}
if (ctry.length() != 2) { // Invalid country code. Ignore the entry.
} else {
prop = parseProperty(ctry, curData, pattern); // if the property entry failed any of the below checked // criteria it is ignored if (prop == null
|| (prop.date == null && curData.chars()
.map(c -> c == ',' ? 1 : 0).sum() >= 3)) { // format is not recognized. ignore the data if date // string is null and we've 4 values, bad date value
prop = null;
} elseif (prop.fraction
> SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS) {
prop = null;
} else { try { if (prop.date != null
&& !isPastCutoverDate(prop.date)) {
prop = null;
}
} catch (ParseException ex) {
prop = null;
}
}
}
if (prop == null) {
info("The property entry for " + ctry + " is invalid."
+ " Ignored.", null);
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.32Bemerkung:
(vorverarbeitet am 2026-10-06)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.