Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/util/Currency.java
38829 views
1
/*
2
* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package java.util;
27
28
import java.io.BufferedInputStream;
29
import java.io.DataInputStream;
30
import java.io.File;
31
import java.io.FileInputStream;
32
import java.io.FileReader;
33
import java.io.IOException;
34
import java.io.Serializable;
35
import java.security.AccessController;
36
import java.security.PrivilegedAction;
37
import java.text.ParseException;
38
import java.text.SimpleDateFormat;
39
import java.util.concurrent.ConcurrentHashMap;
40
import java.util.concurrent.ConcurrentMap;
41
import java.util.regex.Pattern;
42
import java.util.regex.Matcher;
43
import java.util.spi.CurrencyNameProvider;
44
import sun.util.locale.provider.LocaleServiceProviderPool;
45
import sun.util.logging.PlatformLogger;
46
47
48
/**
49
* Represents a currency. Currencies are identified by their ISO 4217 currency
50
* codes. Visit the <a href="http://www.iso.org/iso/home/standards/currency_codes.htm">
51
* ISO web site</a> for more information.
52
* <p>
53
* The class is designed so that there's never more than one
54
* <code>Currency</code> instance for any given currency. Therefore, there's
55
* no public constructor. You obtain a <code>Currency</code> instance using
56
* the <code>getInstance</code> methods.
57
* <p>
58
* Users can supersede the Java runtime currency data by means of the system
59
* property {@code java.util.currency.data}. If this system property is
60
* defined then its value is the location of a properties file, the contents of
61
* which are key/value pairs of the ISO 3166 country codes and the ISO 4217
62
* currency data respectively. The value part consists of three ISO 4217 values
63
* of a currency, i.e., an alphabetic code, a numeric code, and a minor unit.
64
* Those three ISO 4217 values are separated by commas.
65
* The lines which start with '#'s are considered comment lines. An optional UTC
66
* timestamp may be specified per currency entry if users need to specify a
67
* cutover date indicating when the new data comes into effect. The timestamp is
68
* appended to the end of the currency properties and uses a comma as a separator.
69
* If a UTC datestamp is present and valid, the JRE will only use the new currency
70
* properties if the current UTC date is later than the date specified at class
71
* loading time. The format of the timestamp must be of ISO 8601 format :
72
* {@code 'yyyy-MM-dd'T'HH:mm:ss'}. For example,
73
* <p>
74
* <code>
75
* #Sample currency properties<br>
76
* JP=JPZ,999,0
77
* </code>
78
* <p>
79
* will supersede the currency data for Japan.
80
*
81
* <p>
82
* <code>
83
* #Sample currency properties with cutover date<br>
84
* JP=JPZ,999,0,2014-01-01T00:00:00
85
* </code>
86
* <p>
87
* will supersede the currency data for Japan if {@code Currency} class is loaded after
88
* 1st January 2014 00:00:00 GMT.
89
* <p>
90
* Where syntactically malformed entries are encountered, the entry is ignored
91
* and the remainder of entries in file are processed. For instances where duplicate
92
* country code entries exist, the behavior of the Currency information for that
93
* {@code Currency} is undefined and the remainder of entries in file are processed.
94
*
95
* @since 1.4
96
*/
97
public final class Currency implements Serializable {
98
99
private static final long serialVersionUID = -158308464356906721L;
100
101
/**
102
* ISO 4217 currency code for this currency.
103
*
104
* @serial
105
*/
106
private final String currencyCode;
107
108
/**
109
* Default fraction digits for this currency.
110
* Set from currency data tables.
111
*/
112
transient private final int defaultFractionDigits;
113
114
/**
115
* ISO 4217 numeric code for this currency.
116
* Set from currency data tables.
117
*/
118
transient private final int numericCode;
119
120
121
// class data: instance map
122
123
private static ConcurrentMap<String, Currency> instances = new ConcurrentHashMap<>(7);
124
private static HashSet<Currency> available;
125
126
// Class data: currency data obtained from currency.data file.
127
// Purpose:
128
// - determine valid country codes
129
// - determine valid currency codes
130
// - map country codes to currency codes
131
// - obtain default fraction digits for currency codes
132
//
133
// sc = special case; dfd = default fraction digits
134
// Simple countries are those where the country code is a prefix of the
135
// currency code, and there are no known plans to change the currency.
136
//
137
// table formats:
138
// - mainTable:
139
// - maps country code to 32-bit int
140
// - 26*26 entries, corresponding to [A-Z]*[A-Z]
141
// - \u007F -> not valid country
142
// - bits 20-31: unused
143
// - bits 10-19: numeric code (0 to 1023)
144
// - bit 9: 1 - special case, bits 0-4 indicate which one
145
// 0 - simple country, bits 0-4 indicate final char of currency code
146
// - bits 5-8: fraction digits for simple countries, 0 for special cases
147
// - bits 0-4: final char for currency code for simple country, or ID of special case
148
// - special case IDs:
149
// - 0: country has no currency
150
// - other: index into sc* arrays + 1
151
// - scCutOverTimes: cut-over time in millis as returned by
152
// System.currentTimeMillis for special case countries that are changing
153
// currencies; Long.MAX_VALUE for countries that are not changing currencies
154
// - scOldCurrencies: old currencies for special case countries
155
// - scNewCurrencies: new currencies for special case countries that are
156
// changing currencies; null for others
157
// - scOldCurrenciesDFD: default fraction digits for old currencies
158
// - scNewCurrenciesDFD: default fraction digits for new currencies, 0 for
159
// countries that are not changing currencies
160
// - otherCurrencies: concatenation of all currency codes that are not the
161
// main currency of a simple country, separated by "-"
162
// - otherCurrenciesDFD: decimal format digits for currencies in otherCurrencies, same order
163
164
static int formatVersion;
165
static int dataVersion;
166
static int[] mainTable;
167
static long[] scCutOverTimes;
168
static String[] scOldCurrencies;
169
static String[] scNewCurrencies;
170
static int[] scOldCurrenciesDFD;
171
static int[] scNewCurrenciesDFD;
172
static int[] scOldCurrenciesNumericCode;
173
static int[] scNewCurrenciesNumericCode;
174
static String otherCurrencies;
175
static int[] otherCurrenciesDFD;
176
static int[] otherCurrenciesNumericCode;
177
178
// handy constants - must match definitions in GenerateCurrencyData
179
// magic number
180
private static final int MAGIC_NUMBER = 0x43757244;
181
// number of characters from A to Z
182
private static final int A_TO_Z = ('Z' - 'A') + 1;
183
// entry for invalid country codes
184
private static final int INVALID_COUNTRY_ENTRY = 0x0000007F;
185
// entry for countries without currency
186
private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x00000200;
187
// mask for simple case country entries
188
private static final int SIMPLE_CASE_COUNTRY_MASK = 0x00000000;
189
// mask for simple case country entry final character
190
private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x0000001F;
191
// mask for simple case country entry default currency digits
192
private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x000001E0;
193
// shift count for simple case country entry default currency digits
194
private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5;
195
// maximum number for simple case country entry default currency digits
196
private static final int SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS = 9;
197
// mask for special case country entries
198
private static final int SPECIAL_CASE_COUNTRY_MASK = 0x00000200;
199
// mask for special case country index
200
private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x0000001F;
201
// delta from entry index component in main table to index into special case tables
202
private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1;
203
// mask for distinguishing simple and special case countries
204
private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK;
205
// mask for the numeric code of the currency
206
private static final int NUMERIC_CODE_MASK = 0x000FFC00;
207
// shift count for the numeric code of the currency
208
private static final int NUMERIC_CODE_SHIFT = 10;
209
210
// Currency data format version
211
private static final int VALID_FORMAT_VERSION = 2;
212
213
static {
214
AccessController.doPrivileged(new PrivilegedAction<Void>() {
215
@Override
216
public Void run() {
217
String homeDir = System.getProperty("java.home");
218
try {
219
String dataFile = homeDir + File.separator +
220
"lib" + File.separator + "currency.data";
221
try (DataInputStream dis = new DataInputStream(
222
new BufferedInputStream(
223
new FileInputStream(dataFile)))) {
224
if (dis.readInt() != MAGIC_NUMBER) {
225
throw new InternalError("Currency data is possibly corrupted");
226
}
227
formatVersion = dis.readInt();
228
if (formatVersion != VALID_FORMAT_VERSION) {
229
throw new InternalError("Currency data format is incorrect");
230
}
231
dataVersion = dis.readInt();
232
mainTable = readIntArray(dis, A_TO_Z * A_TO_Z);
233
int scCount = dis.readInt();
234
scCutOverTimes = readLongArray(dis, scCount);
235
scOldCurrencies = readStringArray(dis, scCount);
236
scNewCurrencies = readStringArray(dis, scCount);
237
scOldCurrenciesDFD = readIntArray(dis, scCount);
238
scNewCurrenciesDFD = readIntArray(dis, scCount);
239
scOldCurrenciesNumericCode = readIntArray(dis, scCount);
240
scNewCurrenciesNumericCode = readIntArray(dis, scCount);
241
int ocCount = dis.readInt();
242
otherCurrencies = dis.readUTF();
243
otherCurrenciesDFD = readIntArray(dis, ocCount);
244
otherCurrenciesNumericCode = readIntArray(dis, ocCount);
245
}
246
} catch (IOException e) {
247
throw new InternalError(e);
248
}
249
250
// look for the properties file for overrides
251
String propsFile = System.getProperty("java.util.currency.data");
252
if (propsFile == null) {
253
propsFile = homeDir + File.separator + "lib" +
254
File.separator + "currency.properties";
255
}
256
try {
257
File propFile = new File(propsFile);
258
if (propFile.exists()) {
259
Properties props = new Properties();
260
try (FileReader fr = new FileReader(propFile)) {
261
props.load(fr);
262
}
263
Set<String> keys = props.stringPropertyNames();
264
Pattern propertiesPattern =
265
Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" +
266
"(\\d+)\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" +
267
"\\d{2}:\\d{2})?");
268
for (String key : keys) {
269
replaceCurrencyData(propertiesPattern,
270
key.toUpperCase(Locale.ROOT),
271
props.getProperty(key).toUpperCase(Locale.ROOT));
272
}
273
}
274
} catch (IOException e) {
275
info("currency.properties is ignored because of an IOException", e);
276
}
277
return null;
278
}
279
});
280
}
281
282
/**
283
* Constants for retrieving localized names from the name providers.
284
*/
285
private static final int SYMBOL = 0;
286
private static final int DISPLAYNAME = 1;
287
288
289
/**
290
* Constructs a <code>Currency</code> instance. The constructor is private
291
* so that we can insure that there's never more than one instance for a
292
* given currency.
293
*/
294
private Currency(String currencyCode, int defaultFractionDigits, int numericCode) {
295
this.currencyCode = currencyCode;
296
this.defaultFractionDigits = defaultFractionDigits;
297
this.numericCode = numericCode;
298
}
299
300
/**
301
* Returns the <code>Currency</code> instance for the given currency code.
302
*
303
* @param currencyCode the ISO 4217 code of the currency
304
* @return the <code>Currency</code> instance for the given currency code
305
* @exception NullPointerException if <code>currencyCode</code> is null
306
* @exception IllegalArgumentException if <code>currencyCode</code> is not
307
* a supported ISO 4217 code.
308
*/
309
public static Currency getInstance(String currencyCode) {
310
return getInstance(currencyCode, Integer.MIN_VALUE, 0);
311
}
312
313
private static Currency getInstance(String currencyCode, int defaultFractionDigits,
314
int numericCode) {
315
// Try to look up the currency code in the instances table.
316
// This does the null pointer check as a side effect.
317
// Also, if there already is an entry, the currencyCode must be valid.
318
Currency instance = instances.get(currencyCode);
319
if (instance != null) {
320
return instance;
321
}
322
323
if (defaultFractionDigits == Integer.MIN_VALUE) {
324
// Currency code not internally generated, need to verify first
325
// A currency code must have 3 characters and exist in the main table
326
// or in the list of other currencies.
327
if (currencyCode.length() != 3) {
328
throw new IllegalArgumentException();
329
}
330
char char1 = currencyCode.charAt(0);
331
char char2 = currencyCode.charAt(1);
332
int tableEntry = getMainTableEntry(char1, char2);
333
if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
334
&& tableEntry != INVALID_COUNTRY_ENTRY
335
&& currencyCode.charAt(2) - 'A' == (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) {
336
defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
337
numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
338
} else {
339
// Check for '-' separately so we don't get false hits in the table.
340
if (currencyCode.charAt(2) == '-') {
341
throw new IllegalArgumentException();
342
}
343
int index = otherCurrencies.indexOf(currencyCode);
344
if (index == -1) {
345
throw new IllegalArgumentException();
346
}
347
defaultFractionDigits = otherCurrenciesDFD[index / 4];
348
numericCode = otherCurrenciesNumericCode[index / 4];
349
}
350
}
351
352
Currency currencyVal =
353
new Currency(currencyCode, defaultFractionDigits, numericCode);
354
instance = instances.putIfAbsent(currencyCode, currencyVal);
355
return (instance != null ? instance : currencyVal);
356
}
357
358
/**
359
* Returns the <code>Currency</code> instance for the country of the
360
* given locale. The language and variant components of the locale
361
* are ignored. The result may vary over time, as countries change their
362
* currencies. For example, for the original member countries of the
363
* European Monetary Union, the method returns the old national currencies
364
* until December 31, 2001, and the Euro from January 1, 2002, local time
365
* of the respective countries.
366
* <p>
367
* The method returns <code>null</code> for territories that don't
368
* have a currency, such as Antarctica.
369
*
370
* @param locale the locale for whose country a <code>Currency</code>
371
* instance is needed
372
* @return the <code>Currency</code> instance for the country of the given
373
* locale, or {@code null}
374
* @exception NullPointerException if <code>locale</code> or its country
375
* code is {@code null}
376
* @exception IllegalArgumentException if the country of the given {@code locale}
377
* is not a supported ISO 3166 country code.
378
*/
379
public static Currency getInstance(Locale locale) {
380
String country = locale.getCountry();
381
if (country == null) {
382
throw new NullPointerException();
383
}
384
385
if (country.length() != 2) {
386
throw new IllegalArgumentException();
387
}
388
389
char char1 = country.charAt(0);
390
char char2 = country.charAt(1);
391
int tableEntry = getMainTableEntry(char1, char2);
392
if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
393
&& tableEntry != INVALID_COUNTRY_ENTRY) {
394
char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A');
395
int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
396
int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
397
StringBuilder sb = new StringBuilder(country);
398
sb.append(finalChar);
399
return getInstance(sb.toString(), defaultFractionDigits, numericCode);
400
} else {
401
// special cases
402
if (tableEntry == INVALID_COUNTRY_ENTRY) {
403
throw new IllegalArgumentException();
404
}
405
if (tableEntry == COUNTRY_WITHOUT_CURRENCY_ENTRY) {
406
return null;
407
} else {
408
int index = (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA;
409
if (scCutOverTimes[index] == Long.MAX_VALUE || System.currentTimeMillis() < scCutOverTimes[index]) {
410
return getInstance(scOldCurrencies[index], scOldCurrenciesDFD[index],
411
scOldCurrenciesNumericCode[index]);
412
} else {
413
return getInstance(scNewCurrencies[index], scNewCurrenciesDFD[index],
414
scNewCurrenciesNumericCode[index]);
415
}
416
}
417
}
418
}
419
420
/**
421
* Gets the set of available currencies. The returned set of currencies
422
* contains all of the available currencies, which may include currencies
423
* that represent obsolete ISO 4217 codes. The set can be modified
424
* without affecting the available currencies in the runtime.
425
*
426
* @return the set of available currencies. If there is no currency
427
* available in the runtime, the returned set is empty.
428
* @since 1.7
429
*/
430
public static Set<Currency> getAvailableCurrencies() {
431
synchronized(Currency.class) {
432
if (available == null) {
433
available = new HashSet<>(256);
434
435
// Add simple currencies first
436
for (char c1 = 'A'; c1 <= 'Z'; c1 ++) {
437
for (char c2 = 'A'; c2 <= 'Z'; c2 ++) {
438
int tableEntry = getMainTableEntry(c1, c2);
439
if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
440
&& tableEntry != INVALID_COUNTRY_ENTRY) {
441
char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A');
442
int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
443
int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
444
StringBuilder sb = new StringBuilder();
445
sb.append(c1);
446
sb.append(c2);
447
sb.append(finalChar);
448
available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode));
449
}
450
}
451
}
452
453
// Now add other currencies
454
StringTokenizer st = new StringTokenizer(otherCurrencies, "-");
455
while (st.hasMoreElements()) {
456
available.add(getInstance((String)st.nextElement()));
457
}
458
}
459
}
460
461
@SuppressWarnings("unchecked")
462
Set<Currency> result = (Set<Currency>) available.clone();
463
return result;
464
}
465
466
/**
467
* Gets the ISO 4217 currency code of this currency.
468
*
469
* @return the ISO 4217 currency code of this currency.
470
*/
471
public String getCurrencyCode() {
472
return currencyCode;
473
}
474
475
/**
476
* Gets the symbol of this currency for the default
477
* {@link Locale.Category#DISPLAY DISPLAY} locale.
478
* For example, for the US Dollar, the symbol is "$" if the default
479
* locale is the US, while for other locales it may be "US$". If no
480
* symbol can be determined, the ISO 4217 currency code is returned.
481
* <p>
482
* This is equivalent to calling
483
* {@link #getSymbol(Locale)
484
* getSymbol(Locale.getDefault(Locale.Category.DISPLAY))}.
485
*
486
* @return the symbol of this currency for the default
487
* {@link Locale.Category#DISPLAY DISPLAY} locale
488
*/
489
public String getSymbol() {
490
return getSymbol(Locale.getDefault(Locale.Category.DISPLAY));
491
}
492
493
/**
494
* Gets the symbol of this currency for the specified locale.
495
* For example, for the US Dollar, the symbol is "$" if the specified
496
* locale is the US, while for other locales it may be "US$". If no
497
* symbol can be determined, the ISO 4217 currency code is returned.
498
*
499
* @param locale the locale for which a display name for this currency is
500
* needed
501
* @return the symbol of this currency for the specified locale
502
* @exception NullPointerException if <code>locale</code> is null
503
*/
504
public String getSymbol(Locale locale) {
505
LocaleServiceProviderPool pool =
506
LocaleServiceProviderPool.getPool(CurrencyNameProvider.class);
507
String symbol = pool.getLocalizedObject(
508
CurrencyNameGetter.INSTANCE,
509
locale, currencyCode, SYMBOL);
510
if (symbol != null) {
511
return symbol;
512
}
513
514
// use currency code as symbol of last resort
515
return currencyCode;
516
}
517
518
/**
519
* Gets the default number of fraction digits used with this currency.
520
* For example, the default number of fraction digits for the Euro is 2,
521
* while for the Japanese Yen it's 0.
522
* In the case of pseudo-currencies, such as IMF Special Drawing Rights,
523
* -1 is returned.
524
*
525
* @return the default number of fraction digits used with this currency
526
*/
527
public int getDefaultFractionDigits() {
528
return defaultFractionDigits;
529
}
530
531
/**
532
* Returns the ISO 4217 numeric code of this currency.
533
*
534
* @return the ISO 4217 numeric code of this currency
535
* @since 1.7
536
*/
537
public int getNumericCode() {
538
return numericCode;
539
}
540
541
/**
542
* Gets the name that is suitable for displaying this currency for
543
* the default {@link Locale.Category#DISPLAY DISPLAY} locale.
544
* If there is no suitable display name found
545
* for the default locale, the ISO 4217 currency code is returned.
546
* <p>
547
* This is equivalent to calling
548
* {@link #getDisplayName(Locale)
549
* getDisplayName(Locale.getDefault(Locale.Category.DISPLAY))}.
550
*
551
* @return the display name of this currency for the default
552
* {@link Locale.Category#DISPLAY DISPLAY} locale
553
* @since 1.7
554
*/
555
public String getDisplayName() {
556
return getDisplayName(Locale.getDefault(Locale.Category.DISPLAY));
557
}
558
559
/**
560
* Gets the name that is suitable for displaying this currency for
561
* the specified locale. If there is no suitable display name found
562
* for the specified locale, the ISO 4217 currency code is returned.
563
*
564
* @param locale the locale for which a display name for this currency is
565
* needed
566
* @return the display name of this currency for the specified locale
567
* @exception NullPointerException if <code>locale</code> is null
568
* @since 1.7
569
*/
570
public String getDisplayName(Locale locale) {
571
LocaleServiceProviderPool pool =
572
LocaleServiceProviderPool.getPool(CurrencyNameProvider.class);
573
String result = pool.getLocalizedObject(
574
CurrencyNameGetter.INSTANCE,
575
locale, currencyCode, DISPLAYNAME);
576
if (result != null) {
577
return result;
578
}
579
580
// use currency code as symbol of last resort
581
return currencyCode;
582
}
583
584
/**
585
* Returns the ISO 4217 currency code of this currency.
586
*
587
* @return the ISO 4217 currency code of this currency
588
*/
589
@Override
590
public String toString() {
591
return currencyCode;
592
}
593
594
/**
595
* Resolves instances being deserialized to a single instance per currency.
596
*/
597
private Object readResolve() {
598
return getInstance(currencyCode);
599
}
600
601
/**
602
* Gets the main table entry for the country whose country code consists
603
* of char1 and char2.
604
*/
605
private static int getMainTableEntry(char char1, char char2) {
606
if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') {
607
throw new IllegalArgumentException();
608
}
609
return mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')];
610
}
611
612
/**
613
* Sets the main table entry for the country whose country code consists
614
* of char1 and char2.
615
*/
616
private static void setMainTableEntry(char char1, char char2, int entry) {
617
if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') {
618
throw new IllegalArgumentException();
619
}
620
mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')] = entry;
621
}
622
623
/**
624
* Obtains a localized currency names from a CurrencyNameProvider
625
* implementation.
626
*/
627
private static class CurrencyNameGetter
628
implements LocaleServiceProviderPool.LocalizedObjectGetter<CurrencyNameProvider,
629
String> {
630
private static final CurrencyNameGetter INSTANCE = new CurrencyNameGetter();
631
632
@Override
633
public String getObject(CurrencyNameProvider currencyNameProvider,
634
Locale locale,
635
String key,
636
Object... params) {
637
assert params.length == 1;
638
int type = (Integer)params[0];
639
640
switch(type) {
641
case SYMBOL:
642
return currencyNameProvider.getSymbol(key, locale);
643
case DISPLAYNAME:
644
return currencyNameProvider.getDisplayName(key, locale);
645
default:
646
assert false; // shouldn't happen
647
}
648
649
return null;
650
}
651
}
652
653
private static int[] readIntArray(DataInputStream dis, int count) throws IOException {
654
int[] ret = new int[count];
655
for (int i = 0; i < count; i++) {
656
ret[i] = dis.readInt();
657
}
658
659
return ret;
660
}
661
662
private static long[] readLongArray(DataInputStream dis, int count) throws IOException {
663
long[] ret = new long[count];
664
for (int i = 0; i < count; i++) {
665
ret[i] = dis.readLong();
666
}
667
668
return ret;
669
}
670
671
private static String[] readStringArray(DataInputStream dis, int count) throws IOException {
672
String[] ret = new String[count];
673
for (int i = 0; i < count; i++) {
674
ret[i] = dis.readUTF();
675
}
676
677
return ret;
678
}
679
680
/**
681
* Replaces currency data found in the currencydata.properties file
682
*
683
* @param pattern regex pattern for the properties
684
* @param ctry country code
685
* @param curdata currency data. This is a comma separated string that
686
* consists of "three-letter alphabet code", "three-digit numeric code",
687
* and "one-digit (0-9) default fraction digit".
688
* For example, "JPZ,392,0".
689
* An optional UTC date can be appended to the string (comma separated)
690
* to allow a currency change take effect after date specified.
691
* For example, "JP=JPZ,999,0,2014-01-01T00:00:00" has no effect unless
692
* UTC time is past 1st January 2014 00:00:00 GMT.
693
*/
694
private static void replaceCurrencyData(Pattern pattern, String ctry, String curdata) {
695
696
if (ctry.length() != 2) {
697
// ignore invalid country code
698
info("currency.properties entry for " + ctry +
699
" is ignored because of the invalid country code.", null);
700
return;
701
}
702
703
Matcher m = pattern.matcher(curdata);
704
if (!m.find() || (m.group(4) == null && countOccurrences(curdata, ',') >= 3)) {
705
// format is not recognized. ignore the data
706
// if group(4) date string is null and we've 4 values, bad date value
707
info("currency.properties entry for " + ctry +
708
" ignored because the value format is not recognized.", null);
709
return;
710
}
711
712
try {
713
if (m.group(4) != null && !isPastCutoverDate(m.group(4))) {
714
info("currency.properties entry for " + ctry +
715
" ignored since cutover date has not passed :" + curdata, null);
716
return;
717
}
718
} catch (ParseException ex) {
719
info("currency.properties entry for " + ctry +
720
" ignored since exception encountered :" + ex.getMessage(), null);
721
return;
722
}
723
724
String code = m.group(1);
725
int numeric = Integer.parseInt(m.group(2));
726
int entry = numeric << NUMERIC_CODE_SHIFT;
727
int fraction = Integer.parseInt(m.group(3));
728
if (fraction > SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS) {
729
info("currency.properties entry for " + ctry +
730
" ignored since the fraction is more than " +
731
SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS + ":" + curdata, null);
732
return;
733
}
734
735
int index;
736
for (index = 0; index < scOldCurrencies.length; index++) {
737
if (scOldCurrencies[index].equals(code)) {
738
break;
739
}
740
}
741
742
if (index == scOldCurrencies.length) {
743
// simple case
744
entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT) |
745
(code.charAt(2) - 'A');
746
} else {
747
// special case
748
entry |= SPECIAL_CASE_COUNTRY_MASK |
749
(index + SPECIAL_CASE_COUNTRY_INDEX_DELTA);
750
}
751
setMainTableEntry(ctry.charAt(0), ctry.charAt(1), entry);
752
}
753
754
private static boolean isPastCutoverDate(String s) throws ParseException {
755
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT);
756
format.setTimeZone(TimeZone.getTimeZone("UTC"));
757
format.setLenient(false);
758
long time = format.parse(s.trim()).getTime();
759
return System.currentTimeMillis() > time;
760
761
}
762
763
private static int countOccurrences(String value, char match) {
764
int count = 0;
765
for (char c : value.toCharArray()) {
766
if (c == match) {
767
++count;
768
}
769
}
770
return count;
771
}
772
773
private static void info(String message, Throwable t) {
774
PlatformLogger logger = PlatformLogger.getLogger("java.util.Currency");
775
if (logger.isLoggable(PlatformLogger.Level.INFO)) {
776
if (t != null) {
777
logger.info(message, t);
778
} else {
779
logger.info(message);
780
}
781
}
782
}
783
}
784
785