• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 package java.util;
28 
29 import java.io.BufferedInputStream;
30 import java.io.DataInputStream;
31 import java.io.File;
32 import java.io.FileReader;
33 import java.io.InputStream;
34 import java.io.IOException;
35 import java.io.Serializable;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ConcurrentMap;
38 import java.util.regex.Pattern;
39 import java.util.regex.Matcher;
40 import java.util.stream.Collectors;
41 
42 import sun.util.logging.PlatformLogger;
43 
44 import libcore.icu.ICU;
45 
46 // BEGIN Android-changed: Removed docs about superseding runtime currency data.
47 // Doing so via a properties file is not supported on Android.
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} instance for any given currency. Therefore, there's
55  * no public constructor. You obtain a {@code Currency} instance using
56  * the {@code getInstance} methods.
57  *
58  * <p>
59  * It is recommended to use {@link java.math.BigDecimal} class while dealing
60  * with {@code Currency} or monetary values as it provides better handling of floating
61  * point numbers and their operations.
62  *
63  * @spec http://www.iso.org/iso/home/standards/currency_codes.htm ISO - ISO 4217 - Currency codes
64  * @see java.math.BigDecimal
65  * @since 1.4
66  */
67 // END Android-changed: Removed docs about superseding runtime currency data.
68 @SuppressWarnings("removal")
69 public final class Currency implements Serializable {
70 
71     @java.io.Serial
72     private static final long serialVersionUID = -158308464356906721L;
73 
74     /**
75      * ISO 4217 currency code for this currency.
76      *
77      * @serial
78      */
79     private final String currencyCode;
80 
81     // BEGIN Android-changed: Use ICU.
82     // We do not keep track of defaultFractionDigits and numericCode separately.
83     /*
84     /**
85      * Default fraction digits for this currency.
86      * Set from currency data tables.
87      *
88     private final transient int defaultFractionDigits;
89      */
90 
91     /*
92      * ISO 4217 numeric code for this currency.
93      * Set from currency data tables.
94      *
95      private final transient int numericCode;
96      */
97     private transient final android.icu.util.Currency icuCurrency;
98     // END Android-changed: Use ICU.
99 
100 
101     // class data: instance map
102 
103     private static ConcurrentMap<String, Currency> instances = new ConcurrentHashMap<>(7);
104     private static HashSet<Currency> available;
105 
106     // BEGIN Android-removed: Use ICU.
107     // We don't need any of these static fields nor the static initializer.
108     /*
109     // Class data: currency data obtained from currency.data file.
110     // Purpose:
111     // - determine valid country codes
112     // - determine valid currency codes
113     // - map country codes to currency codes
114     // - obtain default fraction digits for currency codes
115     //
116     // sc = special case; dfd = default fraction digits
117     // Simple countries are those where the country code is a prefix of the
118     // currency code, and there are no known plans to change the currency.
119     //
120     // table formats:
121     // - mainTable:
122     //   - maps country code to 32-bit int
123     //   - 26*26 entries, corresponding to [A-Z]*[A-Z]
124     //   - \u007F -> not valid country
125     //   - bits 20-31: unused
126     //   - bits 10-19: numeric code (0 to 1023)
127     //   - bit 9: 1 - special case, bits 0-4 indicate which one
128     //            0 - simple country, bits 0-4 indicate final char of currency code
129     //   - bits 5-8: fraction digits for simple countries, 0 for special cases
130     //   - bits 0-4: final char for currency code for simple country, or ID of special case
131     // - special case IDs:
132     //   - 0: country has no currency
133     //   - other: index into specialCasesList
134 
135     static int formatVersion;
136     static int dataVersion;
137     static int[] mainTable;
138     static List<SpecialCaseEntry> specialCasesList;
139     static List<OtherCurrencyEntry> otherCurrenciesList;
140 
141     // handy constants - must match definitions in GenerateCurrencyData
142     // magic number
143     private static final int MAGIC_NUMBER = 0x43757244;
144     // number of characters from A to Z
145     private static final int A_TO_Z = ('Z' - 'A') + 1;
146     // entry for invalid country codes
147     private static final int INVALID_COUNTRY_ENTRY = 0x0000007F;
148     // entry for countries without currency
149     private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x00000200;
150     // mask for simple case country entries
151     private static final int SIMPLE_CASE_COUNTRY_MASK = 0x00000000;
152     // mask for simple case country entry final character
153     private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x0000001F;
154     // mask for simple case country entry default currency digits
155     private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x000001E0;
156     // shift count for simple case country entry default currency digits
157     private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5;
158     // maximum number for simple case country entry default currency digits
159     private static final int SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS = 9;
160     // mask for special case country entries
161     private static final int SPECIAL_CASE_COUNTRY_MASK = 0x00000200;
162     // mask for special case country index
163     private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x0000001F;
164     // delta from entry index component in main table to index into special case tables
165     private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1;
166     // mask for distinguishing simple and special case countries
167     private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK;
168     // mask for the numeric code of the currency
169     private static final int NUMERIC_CODE_MASK = 0x000FFC00;
170     // shift count for the numeric code of the currency
171     private static final int NUMERIC_CODE_SHIFT = 10;
172 
173     // Currency data format version
174     private static final int VALID_FORMAT_VERSION = 3;
175 
176     static {
177         initStatic();
178     }
179 
180     @SuppressWarnings("removal")
181     private static void initStatic() {
182         AccessController.doPrivileged(new PrivilegedAction<>() {
183             @Override
184             public Void run() {
185                 try {
186                     try (InputStream in = getClass().getResourceAsStream("/java/util/currency.data")) {
187                         if (in == null) {
188                             throw new InternalError("Currency data not found");
189                         }
190                         DataInputStream dis = new DataInputStream(new BufferedInputStream(in));
191                         if (dis.readInt() != MAGIC_NUMBER) {
192                             throw new InternalError("Currency data is possibly corrupted");
193                         }
194                         formatVersion = dis.readInt();
195                         if (formatVersion != VALID_FORMAT_VERSION) {
196                             throw new InternalError("Currency data format is incorrect");
197                         }
198                         dataVersion = dis.readInt();
199                         mainTable = readIntArray(dis, A_TO_Z * A_TO_Z);
200                         int scCount = dis.readInt();
201                         specialCasesList = readSpecialCases(dis, scCount);
202                         int ocCount = dis.readInt();
203                         otherCurrenciesList = readOtherCurrencies(dis, ocCount);
204                     }
205                 } catch (IOException e) {
206                     throw new InternalError(e);
207                 }
208 
209                 // look for the properties file for overrides
210                 String propsFile = System.getProperty("java.util.currency.data");
211                 if (propsFile == null) {
212                     propsFile = StaticProperty.javaHome() + File.separator + "lib" +
213                         File.separator + "currency.properties";
214                 }
215                 try {
216                     File propFile = new File(propsFile);
217                     if (propFile.exists()) {
218                         Properties props = new Properties();
219                         try (FileReader fr = new FileReader(propFile)) {
220                             props.load(fr);
221                         }
222                         Pattern propertiesPattern =
223                                 Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*" +
224                                         "(\\d+)\\s*,?\\s*(\\d{4}-\\d{2}-\\d{2}T\\d{2}:" +
225                                         "\\d{2}:\\d{2})?");
226                         List<CurrencyProperty> currencyEntries
227                                 = getValidCurrencyData(props, propertiesPattern);
228                         currencyEntries.forEach(Currency::replaceCurrencyData);
229                     }
230                 } catch (IOException e) {
231                     CurrencyProperty.info("currency.properties is ignored"
232                             + " because of an IOException", e);
233                 }
234                 return null;
235             }
236         });
237     }
238 
239     /**
240      * Constants for retrieving localized names from the name providers.
241      *
242     private static final int SYMBOL = 0;
243     private static final int DISPLAYNAME = 1;
244     */
245     // END Android-removed: Use ICU.
246 
247     /**
248      * Constructs a {@code Currency} instance. The constructor is private
249      * so that we can ensure that there's never more than one instance for a
250      * given currency.
251      */
252     // BEGIN Android-changed: Use ICU.
253     // We do not keep track of defaultFractionDigits and numericCode separately.
254     /*
255     private Currency(String currencyCode, int defaultFractionDigits, int numericCode) {
256         this.currencyCode = currencyCode;
257         this.defaultFractionDigits = defaultFractionDigits;
258         this.numericCode = numericCode;
259     }
260     */
Currency(android.icu.util.Currency icuCurrency)261     private Currency(android.icu.util.Currency icuCurrency) {
262         this.icuCurrency = icuCurrency;
263         this.currencyCode = icuCurrency.getCurrencyCode();
264     }
265     // END Android-changed: Use ICU.
266 
267     /**
268      * Returns the {@code Currency} instance for the given currency code.
269      *
270      * @param currencyCode the ISO 4217 code of the currency
271      * @return the {@code Currency} instance for the given currency code
272      * @throws    NullPointerException if {@code currencyCode} is null
273      * @throws    IllegalArgumentException if {@code currencyCode} is not
274      * a supported ISO 4217 code.
275      */
getInstance(String currencyCode)276     public static Currency getInstance(String currencyCode) {
277         // BEGIN Android-changed: Use ICU.
278         // Upstream uses a private static helper method, implemented differently.
279         Currency instance = instances.get(currencyCode);
280         if (instance != null) {
281             return instance;
282         }
283         android.icu.util.Currency icuInstance =
284                   android.icu.util.Currency.getInstance(currencyCode);
285         if (icuInstance == null) {
286             return null;
287         }
288         /*
289         if (defaultFractionDigits == Integer.MIN_VALUE) {
290             // Currency code not internally generated, need to verify first
291             // A currency code must have 3 characters and exist in the main table
292             // or in the list of other currencies.
293             boolean found = false;
294             if (currencyCode.length() != 3) {
295                 throw new IllegalArgumentException();
296             }
297             char char1 = currencyCode.charAt(0);
298             char char2 = currencyCode.charAt(1);
299             int tableEntry = getMainTableEntry(char1, char2);
300             if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
301                     && tableEntry != INVALID_COUNTRY_ENTRY
302                     && currencyCode.charAt(2) - 'A' == (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) {
303                 defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
304                 numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
305                 found = true;
306             } else { //special case
307                 int[] fractionAndNumericCode = SpecialCaseEntry.findEntry(currencyCode);
308                 if (fractionAndNumericCode != null) {
309                     defaultFractionDigits = fractionAndNumericCode[0];
310                     numericCode = fractionAndNumericCode[1];
311                     found = true;
312                 }
313             }
314 
315             if (!found) {
316                 OtherCurrencyEntry ocEntry = OtherCurrencyEntry.findEntry(currencyCode);
317                 if (ocEntry == null) {
318                     throw new IllegalArgumentException();
319                 }
320                 defaultFractionDigits = ocEntry.fraction;
321                 numericCode = ocEntry.numericCode;
322             }
323         }
324         */
325 
326         Currency currencyVal = new Currency(icuInstance);
327         // END Android-changed: Use ICU.
328         instance = instances.putIfAbsent(currencyCode, currencyVal);
329         return (instance != null ? instance : currencyVal);
330     }
331 
332     // Android-changed: Remove "rg" support in the javadoc. See http://b/228322300.
333     /**
334      * Returns the {@code Currency} instance for the country of the
335      * given locale. The language and variant components of the locale
336      * are ignored. The result may vary over time, as countries change their
337      * currencies. For example, for the original member countries of the
338      * European Monetary Union, the method returns the old national currencies
339      * until December 31, 2001, and the Euro from January 1, 2002, local time
340      * of the respective countries.
341      * <p>
342      * If the specified {@code locale} contains "cu"
343      * {@linkplain Locale##def_locale_extension Unicode extensions},
344      * the instance returned from this method reflects
345      * the values specified with those extensions.
346      * <p>
347      * The method returns {@code null} for territories that don't
348      * have a currency, such as Antarctica.
349      *
350      * @param locale the locale for whose country a {@code Currency}
351      * instance is needed
352      * @return the {@code Currency} instance for the country of the given
353      * locale, or {@code null}
354      * @throws    NullPointerException if {@code locale}
355      * is {@code null}
356      * @throws    IllegalArgumentException if the country of the given {@code locale}
357      * is not a supported ISO 3166 country code.
358      */
getInstance(Locale locale)359     public static Currency getInstance(Locale locale) {
360         // check for locale overrides
361         String override = locale.getUnicodeLocaleType("cu");
362         if (override != null) {
363             try {
364                 return getInstance(override.toUpperCase(Locale.ROOT));
365             } catch (IllegalArgumentException iae) {
366                 // override currency is invalid. Fall through.
367             }
368         }
369 
370         // BEGIN Android-changed: Use ICU.
371         /*
372         String country = CalendarDataUtility.findRegionOverride(locale).getCountry();
373 
374         if (country == null || !country.matches("^[a-zA-Z]{2}$")) {
375             throw new IllegalArgumentException();
376         }
377 
378         char char1 = country.charAt(0);
379         char char2 = country.charAt(1);
380         int tableEntry = getMainTableEntry(char1, char2);
381         if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
382                     && tableEntry != INVALID_COUNTRY_ENTRY) {
383             char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A');
384             int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
385             int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
386             StringBuilder sb = new StringBuilder(country);
387             sb.append(finalChar);
388             return getInstance(sb.toString(), defaultFractionDigits, numericCode);
389         } else {
390             // special cases
391             if (tableEntry == INVALID_COUNTRY_ENTRY) {
392                 throw new IllegalArgumentException();
393             }
394             if (tableEntry == COUNTRY_WITHOUT_CURRENCY_ENTRY) {
395                 return null;
396             } else {
397                 int index = SpecialCaseEntry.toIndex(tableEntry);
398                 SpecialCaseEntry scEntry = specialCasesList.get(index);
399                 if (scEntry.cutOverTime == Long.MAX_VALUE
400                         || System.currentTimeMillis() < scEntry.cutOverTime) {
401                     return getInstance(scEntry.oldCurrency,
402                             scEntry.oldCurrencyFraction,
403                             scEntry.oldCurrencyNumericCode);
404                 } else {
405                     return getInstance(scEntry.newCurrency,
406                             scEntry.newCurrencyFraction,
407                             scEntry.newCurrencyNumericCode);
408                 }
409             }
410         }
411         */
412         String country = locale.getCountry();
413         android.icu.util.Currency icuInstance =
414                 android.icu.util.Currency.getInstance(locale);
415         // Unknown historical reason to append variant to country code. The API documentation
416         // does not mention the effect of locale variant. The actual effect here is throwing
417         // IllegalArgumentException because the code like FR_EURO is not a valid country code.
418         String variant = locale.getVariant();
419         if (!variant.isEmpty() && (variant.equals("EURO") || variant.equals("HK") ||
420                 variant.equals("PREEURO"))) {
421             country = country + "_" + variant;
422         }
423         if (!ICU.isIsoCountry(country)) {
424             // Throws IllegalArgumentException as required by the API documentation.
425             throw new IllegalArgumentException("Unsupported ISO 3166 country: " + locale);
426         }
427         String currencyCode = ICU.getCurrencyCode(country);
428         if (currencyCode == null || icuInstance == null ||
429                 icuInstance.getCurrencyCode().equals("XXX")) { // XXX is not a real currency.
430             return null;
431         }
432         return getInstance(currencyCode);
433         // END Android-changed: Use ICU.
434     }
435 
436     /**
437      * Gets the set of available currencies.  The returned set of currencies
438      * contains all of the available currencies, which may include currencies
439      * that represent obsolete ISO 4217 codes.  The set can be modified
440      * without affecting the available currencies in the runtime.
441      *
442      * @return the set of available currencies.  If there is no currency
443      *    available in the runtime, the returned set is empty.
444      * @since 1.7
445      */
getAvailableCurrencies()446     public static Set<Currency> getAvailableCurrencies() {
447         synchronized(Currency.class) {
448             if (available == null) {
449                 // BEGIN Android-changed: Use ICU.
450                 /*
451                 available = new HashSet<>(256);
452 
453                 // Add simple currencies first
454                 for (char c1 = 'A'; c1 <= 'Z'; c1 ++) {
455                     for (char c2 = 'A'; c2 <= 'Z'; c2 ++) {
456                         int tableEntry = getMainTableEntry(c1, c2);
457                         if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
458                              && tableEntry != INVALID_COUNTRY_ENTRY) {
459                             char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A');
460                             int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
461                             int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT;
462                             StringBuilder sb = new StringBuilder();
463                             sb.append(c1);
464                             sb.append(c2);
465                             sb.append(finalChar);
466                             available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode));
467                         } else if ((tableEntry & COUNTRY_TYPE_MASK) == SPECIAL_CASE_COUNTRY_MASK
468                                 && tableEntry != INVALID_COUNTRY_ENTRY
469                                 && tableEntry != COUNTRY_WITHOUT_CURRENCY_ENTRY) {
470                             int index = SpecialCaseEntry.toIndex(tableEntry);
471                             SpecialCaseEntry scEntry = specialCasesList.get(index);
472 
473                             if (scEntry.cutOverTime == Long.MAX_VALUE
474                                     || System.currentTimeMillis() < scEntry.cutOverTime) {
475                                 available.add(getInstance(scEntry.oldCurrency,
476                                         scEntry.oldCurrencyFraction,
477                                         scEntry.oldCurrencyNumericCode));
478                             } else {
479                                 available.add(getInstance(scEntry.newCurrency,
480                                         scEntry.newCurrencyFraction,
481                                         scEntry.newCurrencyNumericCode));
482                             }
483                         }
484                     }
485                 }
486 
487                 // Now add other currencies
488                 for (OtherCurrencyEntry entry : otherCurrenciesList) {
489                     available.add(getInstance(entry.currencyCode));
490                 }
491                 */
492                 available = new HashSet<>();
493                 Set<android.icu.util.Currency> icuAvailableCurrencies
494                         = android.icu.util.Currency.getAvailableCurrencies();
495                 for (android.icu.util.Currency icuCurrency : icuAvailableCurrencies) {
496                     Currency currency = getInstance(icuCurrency.getCurrencyCode());
497                     if (currency == null) {
498                         currency = new Currency(icuCurrency);
499                         instances.put(currency.currencyCode, currency);
500                     }
501                     available.add(currency);
502                 }
503                 // END Android-changed: Use ICU.
504             }
505         }
506 
507         @SuppressWarnings("unchecked")
508         Set<Currency> result = (Set<Currency>) available.clone();
509         return result;
510     }
511 
512     /**
513      * Gets the ISO 4217 currency code of this currency.
514      *
515      * @return the ISO 4217 currency code of this currency.
516      */
getCurrencyCode()517     public String getCurrencyCode() {
518         return currencyCode;
519     }
520 
521     // Android-changed: Remove "rg" support in the javadoc. See http://b/228322300.
522     /**
523      * Gets the symbol of this currency for the default
524      * {@link Locale.Category#DISPLAY DISPLAY} locale.
525      * For example, for the US Dollar, the symbol is "$" if the default
526      * locale is the US, while for other locales it may be "US$". If no
527      * symbol can be determined, the ISO 4217 currency code is returned.
528      * <p>
529      * This is equivalent to calling
530      * {@link #getSymbol(Locale)
531      *     getSymbol(Locale.getDefault(Locale.Category.DISPLAY))}.
532      *
533      * @return the symbol of this currency for the default
534      *     {@link Locale.Category#DISPLAY DISPLAY} locale
535      */
getSymbol()536     public String getSymbol() {
537         return getSymbol(Locale.getDefault(Locale.Category.DISPLAY));
538     }
539 
540     // Android-changed: Remove "rg" support in the javadoc. See http://b/228322300.
541     /**
542      * Gets the symbol of this currency for the specified locale.
543      * For example, for the US Dollar, the symbol is "$" if the specified
544      * locale is the US, while for other locales it may be "US$". If no
545      * symbol can be determined, the ISO 4217 currency code is returned.
546      *
547      * @param locale the locale for which a display name for this currency is
548      * needed
549      * @return the symbol of this currency for the specified locale
550      * @throws    NullPointerException if {@code locale} is null
551      */
getSymbol(Locale locale)552     public String getSymbol(Locale locale) {
553         // BEGIN Android-changed: Use ICU.
554         /*
555         LocaleServiceProviderPool pool =
556             LocaleServiceProviderPool.getPool(CurrencyNameProvider.class);
557         locale = CalendarDataUtility.findRegionOverride(locale);
558         String symbol = pool.getLocalizedObject(
559                                 CurrencyNameGetter.INSTANCE,
560                                 locale, currencyCode, SYMBOL);
561         if (symbol != null) {
562             return symbol;
563         }
564 
565         // use currency code as symbol of last resort
566         return currencyCode;
567         */
568         if (locale == null) {
569             throw new NullPointerException("locale == null");
570         }
571         return icuCurrency.getSymbol(locale);
572         // END Android-changed: Use ICU.
573     }
574 
575     /**
576      * Gets the default number of fraction digits used with this currency.
577      * Note that the number of fraction digits is the same as ISO 4217's
578      * minor unit for the currency.
579      * For example, the default number of fraction digits for the Euro is 2,
580      * while for the Japanese Yen it's 0.
581      * In the case of pseudo-currencies, such as IMF Special Drawing Rights,
582      * -1 is returned.
583      *
584      * @return the default number of fraction digits used with this currency
585      */
getDefaultFractionDigits()586     public int getDefaultFractionDigits() {
587         // BEGIN Android-changed: Use ICU.
588         // return defaultFractionDigits;
589         if (icuCurrency.getCurrencyCode().equals("XXX")) {
590             return -1;
591         }
592         return icuCurrency.getDefaultFractionDigits();
593         // END Android-changed: Use ICU.
594     }
595 
596     /**
597      * Returns the ISO 4217 numeric code of this currency.
598      *
599      * @return the ISO 4217 numeric code of this currency
600      * @since 1.7
601      */
getNumericCode()602     public int getNumericCode() {
603         // Android-changed: Use ICU.
604         // return numericCode;
605         return icuCurrency.getNumericCode();
606     }
607 
608     /**
609      * Returns the 3 digit ISO 4217 numeric code of this currency as a {@code String}.
610      * Unlike {@link #getNumericCode()}, which returns the numeric code as {@code int},
611      * this method always returns the numeric code as a 3 digit string.
612      * e.g. a numeric value of 32 would be returned as "032",
613      * and a numeric value of 6 would be returned as "006".
614      *
615      * @return the 3 digit ISO 4217 numeric code of this currency as a {@code String}
616      * @since 9
617      */
getNumericCodeAsString()618     public String getNumericCodeAsString() {
619         // Android-added: We don't store the code as a field. Call getNumericCode().
620         int numericCode = getNumericCode();
621         /* numeric code could be returned as a 3 digit string simply by using
622            String.format("%03d",numericCode); which uses regex to parse the format,
623            "%03d" in this case. Parsing a regex gives an extra performance overhead,
624            so String.format() approach is avoided in this scenario.
625         */
626         if (numericCode < 100) {
627             StringBuilder sb = new StringBuilder();
628             sb.append('0');
629             if (numericCode < 10) {
630                 sb.append('0');
631             }
632             return sb.append(numericCode).toString();
633         }
634         return String.valueOf(numericCode);
635     }
636 
637     /**
638      * Gets the name that is suitable for displaying this currency for
639      * the default {@link Locale.Category#DISPLAY DISPLAY} locale.
640      * If there is no suitable display name found
641      * for the default locale, the ISO 4217 currency code is returned.
642      * <p>
643      * This is equivalent to calling
644      * {@link #getDisplayName(Locale)
645      *     getDisplayName(Locale.getDefault(Locale.Category.DISPLAY))}.
646      *
647      * @return the display name of this currency for the default
648      *     {@link Locale.Category#DISPLAY DISPLAY} locale
649      * @since 1.7
650      */
getDisplayName()651     public String getDisplayName() {
652         return getDisplayName(Locale.getDefault(Locale.Category.DISPLAY));
653     }
654 
655     /**
656      * Gets the name that is suitable for displaying this currency for
657      * the specified locale.  If there is no suitable display name found
658      * for the specified locale, the ISO 4217 currency code is returned.
659      *
660      * @param locale the locale for which a display name for this currency is
661      * needed
662      * @return the display name of this currency for the specified locale
663      * @throws    NullPointerException if {@code locale} is null
664      * @since 1.7
665      */
getDisplayName(Locale locale)666     public String getDisplayName(Locale locale) {
667         // Android-changed: Use ICU.
668         /*
669         LocaleServiceProviderPool pool =
670             LocaleServiceProviderPool.getPool(CurrencyNameProvider.class);
671         String result = pool.getLocalizedObject(
672                                 CurrencyNameGetter.INSTANCE,
673                                 locale, currencyCode, DISPLAYNAME);
674         if (result != null) {
675             return result;
676         }
677 
678         // use currency code as symbol of last resort
679         return currencyCode;
680         */
681         return icuCurrency.getDisplayName(Objects.requireNonNull(locale));
682     }
683 
684     /**
685      * Returns the ISO 4217 currency code of this currency.
686      *
687      * @return the ISO 4217 currency code of this currency
688      */
689     @Override
toString()690     public String toString() {
691         // Android-changed: Use ICU.
692         // return currencyCode;
693         return icuCurrency.toString();
694     }
695 
696     /**
697      * Resolves instances being deserialized to a single instance per currency.
698      */
699     @java.io.Serial
readResolve()700     private Object readResolve() {
701         return getInstance(currencyCode);
702     }
703 
704     // Android-removed: Use ICU.
705     // Removed a bunch of private helper methods that are unused on Android.
706     /**
707      * Gets the main table entry for the country whose country code consists
708      * of char1 and char2.
709      *
710     private static int getMainTableEntry(char char1, char char2) {
711         if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') {
712             throw new IllegalArgumentException();
713         }
714         return mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')];
715     }
716 
717     /**
718      * Sets the main table entry for the country whose country code consists
719      * of char1 and char2.
720      *
721     private static void setMainTableEntry(char char1, char char2, int entry) {
722         if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') {
723             throw new IllegalArgumentException();
724         }
725         mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')] = entry;
726     }
727 
728     /**
729      * Obtains a localized currency names from a CurrencyNameProvider
730      * implementation.
731      *
732     private static class CurrencyNameGetter
733         implements LocaleServiceProviderPool.LocalizedObjectGetter<CurrencyNameProvider,
734                                                                    String> {
735         private static final CurrencyNameGetter INSTANCE = new CurrencyNameGetter();
736 
737         @Override
738         public String getObject(CurrencyNameProvider currencyNameProvider,
739                                 Locale locale,
740                                 String key,
741                                 Object... params) {
742             assert params.length == 1;
743             int type = (Integer)params[0];
744 
745             switch(type) {
746             case SYMBOL:
747                 return currencyNameProvider.getSymbol(key, locale);
748             case DISPLAYNAME:
749                 return currencyNameProvider.getDisplayName(key, locale);
750             default:
751                 assert false; // shouldn't happen
752             }
753 
754             return null;
755         }
756     }
757 
758     private static int[] readIntArray(DataInputStream dis, int count) throws IOException {
759         int[] ret = new int[count];
760         for (int i = 0; i < count; i++) {
761             ret[i] = dis.readInt();
762         }
763 
764         return ret;
765     }
766 
767     private static List<SpecialCaseEntry> readSpecialCases(DataInputStream dis,
768             int count)
769             throws IOException {
770 
771         List<SpecialCaseEntry> list = new ArrayList<>(count);
772         long cutOverTime;
773         String oldCurrency;
774         String newCurrency;
775         int oldCurrencyFraction;
776         int newCurrencyFraction;
777         int oldCurrencyNumericCode;
778         int newCurrencyNumericCode;
779 
780         for (int i = 0; i < count; i++) {
781             cutOverTime = dis.readLong();
782             oldCurrency = dis.readUTF();
783             newCurrency = dis.readUTF();
784             oldCurrencyFraction = dis.readInt();
785             newCurrencyFraction = dis.readInt();
786             oldCurrencyNumericCode = dis.readInt();
787             newCurrencyNumericCode = dis.readInt();
788             SpecialCaseEntry sc = new SpecialCaseEntry(cutOverTime,
789                     oldCurrency, newCurrency,
790                     oldCurrencyFraction, newCurrencyFraction,
791                     oldCurrencyNumericCode, newCurrencyNumericCode);
792             list.add(sc);
793         }
794         return list;
795     }
796 
797     private static List<OtherCurrencyEntry> readOtherCurrencies(DataInputStream dis,
798             int count)
799             throws IOException {
800 
801         List<OtherCurrencyEntry> list = new ArrayList<>(count);
802         String currencyCode;
803         int fraction;
804         int numericCode;
805 
806         for (int i = 0; i < count; i++) {
807             currencyCode = dis.readUTF();
808             fraction = dis.readInt();
809             numericCode = dis.readInt();
810             OtherCurrencyEntry oc = new OtherCurrencyEntry(currencyCode,
811                     fraction,
812                     numericCode);
813             list.add(oc);
814         }
815         return list;
816     }
817 
818     /**
819      * Parse currency data found in the properties file (that
820      * java.util.currency.data designates) to a List of CurrencyProperty
821      * instances. Also, remove invalid entries and the multiple currency
822      * code inconsistencies.
823      *
824      * @param props properties containing currency data
825      * @param pattern regex pattern for the properties entry
826      * @return list of parsed property entries
827      *
828     private static List<CurrencyProperty> getValidCurrencyData(Properties props,
829             Pattern pattern) {
830 
831         Set<String> keys = props.stringPropertyNames();
832         List<CurrencyProperty> propertyEntries = new ArrayList<>();
833 
834         // remove all invalid entries and parse all valid currency properties
835         // entries to a group of CurrencyProperty, classified by currency code
836         Map<String, List<CurrencyProperty>> currencyCodeGroup = keys.stream()
837                 .map(k -> CurrencyProperty
838                 .getValidEntry(k.toUpperCase(Locale.ROOT),
839                         props.getProperty(k).toUpperCase(Locale.ROOT),
840                         pattern)).flatMap(o -> o.stream())
841                 .collect(Collectors.groupingBy(entry -> entry.currencyCode));
842 
843         // check each group for inconsistencies
844         currencyCodeGroup.forEach((curCode, list) -> {
845             boolean inconsistent = CurrencyProperty
846                     .containsInconsistentInstances(list);
847             if (inconsistent) {
848                 list.forEach(prop -> CurrencyProperty.info("The property"
849                         + " entry for " + prop.country + " is inconsistent."
850                         + " Ignored.", null));
851             } else {
852                 propertyEntries.addAll(list);
853             }
854         });
855 
856         return propertyEntries;
857     }
858 
859     /**
860      * Replaces currency data found in the properties file that
861      * java.util.currency.data designates. This method is invoked for
862      * each valid currency entry.
863      *
864      * @param prop CurrencyProperty instance of the valid property entry
865      *
866     private static void replaceCurrencyData(CurrencyProperty prop) {
867 
868 
869         String ctry = prop.country;
870         String code = prop.currencyCode;
871         int numeric = prop.numericCode;
872         int fraction = prop.fraction;
873         int entry = numeric << NUMERIC_CODE_SHIFT;
874 
875         int index = SpecialCaseEntry.indexOf(code, fraction, numeric);
876 
877 
878         // If a new entry changes the numeric code/dfd of an existing
879         // currency code, update it in the sc list at the respective
880         // index and also change it in the other currencies list and
881         // main table (if that currency code is also used as a
882         // simple case).
883 
884         // If all three components do not match with the new entry,
885         // but the currency code exists in the special case list
886         // update the sc entry with the new entry
887         int scCurrencyCodeIndex = -1;
888         if (index == -1) {
889             scCurrencyCodeIndex = SpecialCaseEntry.currencyCodeIndex(code);
890             if (scCurrencyCodeIndex != -1) {
891                 //currency code exists in sc list, then update the old entry
892                 specialCasesList.set(scCurrencyCodeIndex,
893                         new SpecialCaseEntry(code, fraction, numeric));
894 
895                 // also update the entry in other currencies list
896                 OtherCurrencyEntry oe = OtherCurrencyEntry.findEntry(code);
897                 if (oe != null) {
898                     int oIndex = otherCurrenciesList.indexOf(oe);
899                     otherCurrenciesList.set(oIndex, new OtherCurrencyEntry(
900                             code, fraction, numeric));
901                 }
902             }
903         }
904 
905         /* If a country switches from simple case to special case or
906          * one special case to other special case which is not present
907          * in the sc arrays then insert the new entry in special case arrays.
908          * If an entry with given currency code exists, update with the new
909          * entry.
910          *
911         if (index == -1 && (ctry.charAt(0) != code.charAt(0)
912                 || ctry.charAt(1) != code.charAt(1))) {
913 
914             if(scCurrencyCodeIndex == -1) {
915                 specialCasesList.add(new SpecialCaseEntry(code, fraction,
916                         numeric));
917                 index = specialCasesList.size() - 1;
918             } else {
919                 index = scCurrencyCodeIndex;
920             }
921 
922             // update the entry in main table if it exists as a simple case
923             updateMainTableEntry(code, fraction, numeric);
924         }
925 
926         if (index == -1) {
927             // simple case
928             entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT)
929                     | (code.charAt(2) - 'A');
930         } else {
931             // special case
932             entry = SPECIAL_CASE_COUNTRY_MASK
933                     | (index + SPECIAL_CASE_COUNTRY_INDEX_DELTA);
934         }
935         setMainTableEntry(ctry.charAt(0), ctry.charAt(1), entry);
936     }
937 
938     // update the entry in maintable for any simple case found, if a new
939     // entry as a special case updates the entry in sc list with
940     // existing currency code
941     private static void updateMainTableEntry(String code, int fraction,
942             int numeric) {
943         // checking the existence of currency code in mainTable
944         int tableEntry = getMainTableEntry(code.charAt(0), code.charAt(1));
945         int entry = numeric << NUMERIC_CODE_SHIFT;
946         if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK
947                 && tableEntry != INVALID_COUNTRY_ENTRY
948                 && code.charAt(2) - 'A' == (tableEntry
949                 & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) {
950 
951             int numericCode = (tableEntry & NUMERIC_CODE_MASK)
952                     >> NUMERIC_CODE_SHIFT;
953             int defaultFractionDigits = (tableEntry
954                     & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK)
955                     >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT;
956             if (numeric != numericCode || fraction != defaultFractionDigits) {
957                 // update the entry in main table
958                 entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT)
959                         | (code.charAt(2) - 'A');
960                 setMainTableEntry(code.charAt(0), code.charAt(1), entry);
961             }
962         }
963     }
964 
965     /* Used to represent a special case currency entry
966      * - cutOverTime: cut-over time in millis as returned by
967      *   System.currentTimeMillis for special case countries that are changing
968      *   currencies; Long.MAX_VALUE for countries that are not changing currencies
969      * - oldCurrency: old currencies for special case countries
970      * - newCurrency: new currencies for special case countries that are
971      *   changing currencies; null for others
972      * - oldCurrencyFraction: default fraction digits for old currencies
973      * - newCurrencyFraction: default fraction digits for new currencies, 0 for
974      *   countries that are not changing currencies
975      * - oldCurrencyNumericCode: numeric code for old currencies
976      * - newCurrencyNumericCode: numeric code for new currencies, 0 for countries
977      *   that are not changing currencies
978     *
979     private static class SpecialCaseEntry {
980 
981         private final long cutOverTime;
982         private final String oldCurrency;
983         private final String newCurrency;
984         private final int oldCurrencyFraction;
985         private final int newCurrencyFraction;
986         private final int oldCurrencyNumericCode;
987         private final int newCurrencyNumericCode;
988 
989         private SpecialCaseEntry(long cutOverTime, String oldCurrency, String newCurrency,
990                 int oldCurrencyFraction, int newCurrencyFraction,
991                 int oldCurrencyNumericCode, int newCurrencyNumericCode) {
992             this.cutOverTime = cutOverTime;
993             this.oldCurrency = oldCurrency;
994             this.newCurrency = newCurrency;
995             this.oldCurrencyFraction = oldCurrencyFraction;
996             this.newCurrencyFraction = newCurrencyFraction;
997             this.oldCurrencyNumericCode = oldCurrencyNumericCode;
998             this.newCurrencyNumericCode = newCurrencyNumericCode;
999         }
1000 
1001         private SpecialCaseEntry(String currencyCode, int fraction,
1002                 int numericCode) {
1003             this(Long.MAX_VALUE, currencyCode, "", fraction, 0, numericCode, 0);
1004         }
1005 
1006         //get the index of the special case entry
1007         private static int indexOf(String code, int fraction, int numeric) {
1008             int size = specialCasesList.size();
1009             for (int index = 0; index < size; index++) {
1010                 SpecialCaseEntry scEntry = specialCasesList.get(index);
1011                 if (scEntry.oldCurrency.equals(code)
1012                         && scEntry.oldCurrencyFraction == fraction
1013                         && scEntry.oldCurrencyNumericCode == numeric
1014                         && scEntry.cutOverTime == Long.MAX_VALUE) {
1015                     return index;
1016                 }
1017             }
1018             return -1;
1019         }
1020 
1021         // get the fraction and numericCode of the sc currencycode
1022         private static int[] findEntry(String code) {
1023             int[] fractionAndNumericCode = null;
1024             int size = specialCasesList.size();
1025             for (int index = 0; index < size; index++) {
1026                 SpecialCaseEntry scEntry = specialCasesList.get(index);
1027                 if (scEntry.oldCurrency.equals(code) && (scEntry.cutOverTime == Long.MAX_VALUE
1028                         || System.currentTimeMillis() < scEntry.cutOverTime)) {
1029                     //consider only when there is no new currency or cutover time is not passed
1030                     fractionAndNumericCode = new int[2];
1031                     fractionAndNumericCode[0] = scEntry.oldCurrencyFraction;
1032                     fractionAndNumericCode[1] = scEntry.oldCurrencyNumericCode;
1033                     break;
1034                 } else if (scEntry.newCurrency.equals(code)
1035                         && System.currentTimeMillis() >= scEntry.cutOverTime) {
1036                     //consider only if the cutover time is passed
1037                     fractionAndNumericCode = new int[2];
1038                     fractionAndNumericCode[0] = scEntry.newCurrencyFraction;
1039                     fractionAndNumericCode[1] = scEntry.newCurrencyNumericCode;
1040                     break;
1041                 }
1042             }
1043             return fractionAndNumericCode;
1044         }
1045 
1046         // get the index based on currency code
1047         private static int currencyCodeIndex(String code) {
1048             int size = specialCasesList.size();
1049             for (int index = 0; index < size; index++) {
1050                 SpecialCaseEntry scEntry = specialCasesList.get(index);
1051                 if (scEntry.oldCurrency.equals(code) && (scEntry.cutOverTime == Long.MAX_VALUE
1052                         || System.currentTimeMillis() < scEntry.cutOverTime)) {
1053                     //consider only when there is no new currency or cutover time is not passed
1054                     return index;
1055                 } else if (scEntry.newCurrency.equals(code)
1056                         && System.currentTimeMillis() >= scEntry.cutOverTime) {
1057                     //consider only if the cutover time is passed
1058                     return index;
1059                 }
1060             }
1061             return -1;
1062         }
1063 
1064 
1065         // convert the special case entry to sc arrays index
1066         private static int toIndex(int tableEntry) {
1067             return (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA;
1068         }
1069 
1070     }
1071 
1072     /* Used to represent Other currencies
1073      * - currencyCode: currency codes that are not the main currency
1074      *   of a simple country
1075      * - otherCurrenciesDFD: decimal format digits for other currencies
1076      * - otherCurrenciesNumericCode: numeric code for other currencies
1077      *
1078     private static class OtherCurrencyEntry {
1079 
1080         private final String currencyCode;
1081         private final int fraction;
1082         private final int numericCode;
1083 
1084         private OtherCurrencyEntry(String currencyCode, int fraction,
1085                 int numericCode) {
1086             this.currencyCode = currencyCode;
1087             this.fraction = fraction;
1088             this.numericCode = numericCode;
1089         }
1090 
1091         //get the instance of the other currency code
1092         private static OtherCurrencyEntry findEntry(String code) {
1093             int size = otherCurrenciesList.size();
1094             for (int index = 0; index < size; index++) {
1095                 OtherCurrencyEntry ocEntry = otherCurrenciesList.get(index);
1096                 if (ocEntry.currencyCode.equalsIgnoreCase(code)) {
1097                     return ocEntry;
1098                 }
1099             }
1100             return null;
1101         }
1102 
1103     }
1104 
1105 
1106     /*
1107      * Used to represent an entry of the properties file that
1108      * java.util.currency.data designates
1109      *
1110      * - country: country representing the currency entry
1111      * - currencyCode: currency code
1112      * - fraction: default fraction digit
1113      * - numericCode: numeric code
1114      * - date: cutover date
1115      *
1116     private static class CurrencyProperty {
1117         private final String country;
1118         private final String currencyCode;
1119         private final int fraction;
1120         private final int numericCode;
1121         private final String date;
1122 
1123         private CurrencyProperty(String country, String currencyCode,
1124                 int fraction, int numericCode, String date) {
1125             this.country = country;
1126             this.currencyCode = currencyCode;
1127             this.fraction = fraction;
1128             this.numericCode = numericCode;
1129             this.date = date;
1130         }
1131 
1132         /**
1133          * Check the valid currency data and create/return an Optional instance
1134          * of CurrencyProperty
1135          *
1136          * @param ctry    country representing the currency data
1137          * @param curData currency data of the given {@code ctry}
1138          * @param pattern regex pattern for the properties entry
1139          * @return Optional containing CurrencyProperty instance, If valid;
1140          *         empty otherwise
1141          *
1142         private static Optional<CurrencyProperty> getValidEntry(String ctry,
1143                 String curData,
1144                 Pattern pattern) {
1145 
1146             CurrencyProperty prop = null;
1147 
1148             if (ctry.length() != 2) {
1149                 // Invalid country code. Ignore the entry.
1150             } else {
1151 
1152                 prop = parseProperty(ctry, curData, pattern);
1153                 // if the property entry failed any of the below checked
1154                 // criteria it is ignored
1155                 if (prop == null
1156                         || (prop.date == null && curData.chars()
1157                                 .map(c -> c == ',' ? 1 : 0).sum() >= 3)) {
1158                     // format is not recognized.  ignore the data if date
1159                     // string is null and we've 4 values, bad date value
1160                     prop = null;
1161                 } else if (prop.fraction
1162                         > SIMPLE_CASE_COUNTRY_MAX_DEFAULT_DIGITS) {
1163                     prop = null;
1164                 } else {
1165                     try {
1166                         if (prop.date != null
1167                                 && !isPastCutoverDate(prop.date)) {
1168                             prop = null;
1169                         }
1170                     } catch (ParseException ex) {
1171                         prop = null;
1172                     }
1173                 }
1174             }
1175 
1176             if (prop == null) {
1177                 info("The property entry for " + ctry + " is invalid."
1178                         + " Ignored.", null);
1179             }
1180 
1181             return Optional.ofNullable(prop);
1182         }
1183 
1184         /*
1185          * Parse properties entry and return CurrencyProperty instance
1186          *
1187         private static CurrencyProperty parseProperty(String ctry,
1188                 String curData, Pattern pattern) {
1189             Matcher m = pattern.matcher(curData);
1190             if (!m.find()) {
1191                 return null;
1192             } else {
1193                 return new CurrencyProperty(ctry, m.group(1),
1194                         Integer.parseInt(m.group(3)),
1195                         Integer.parseInt(m.group(2)), m.group(4));
1196             }
1197         }
1198 
1199         /**
1200          * Checks if the given list contains multiple inconsistent currency instances
1201          *
1202         private static boolean containsInconsistentInstances(
1203                 List<CurrencyProperty> list) {
1204             int numCode = list.get(0).numericCode;
1205             int fractionDigit = list.get(0).fraction;
1206             return list.stream().anyMatch(prop -> prop.numericCode != numCode
1207                     || prop.fraction != fractionDigit);
1208         }
1209 
1210         private static boolean isPastCutoverDate(String s)
1211                 throws ParseException {
1212             SimpleDateFormat format = new SimpleDateFormat(
1213                     "yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT);
1214             format.setTimeZone(TimeZone.getTimeZone("UTC"));
1215             format.setLenient(false);
1216             long time = format.parse(s.trim()).getTime();
1217             return System.currentTimeMillis() > time;
1218 
1219         }
1220 
1221         private static void info(String message, Throwable t) {
1222             PlatformLogger logger = PlatformLogger
1223                     .getLogger("java.util.Currency");
1224             if (logger.isLoggable(PlatformLogger.Level.INFO)) {
1225                 if (t != null) {
1226                     logger.info(message, t);
1227                 } else {
1228                     logger.info(message);
1229                 }
1230             }
1231         }
1232 
1233     }
1234     */
1235 }
1236