1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2020, 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 /*
28  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
29  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
30  *
31  *   The original version of this source code and documentation is copyrighted
32  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
33  * materials are provided under terms of a License Agreement between Taligent
34  * and Sun. This technology is protected by multiple US and International
35  * patents. This notice and attribution to Taligent may not be removed.
36  *   Taligent is a registered trademark of Taligent, Inc.
37  *
38  */
39 
40 package java.text;
41 
42 import java.io.InvalidObjectException;
43 import java.io.IOException;
44 import java.io.ObjectInputStream;
45 import java.io.ObjectOutputStream;
46 import java.io.ObjectStreamField;
47 import java.io.Serializable;
48 import java.util.Currency;
49 import java.util.Locale;
50 import java.util.Objects;
51 
52 import libcore.icu.DecimalFormatData;
53 import libcore.icu.ICU;
54 import libcore.icu.LocaleData;
55 
56 // Android-removed: Remove javadoc related to "rg" Locale extension.
57 // The "rg" extension isn't supported until https://unicode-org.atlassian.net/browse/ICU-21831
58 // is resolved, because java.text.* stack relies on ICU on resource resolution.
59 /**
60  * This class represents the set of symbols (such as the decimal separator,
61  * the grouping separator, and so on) needed by {@code DecimalFormat}
62  * to format numbers. {@code DecimalFormat} creates for itself an instance of
63  * {@code DecimalFormatSymbols} from its locale data.  If you need to change any
64  * of these symbols, you can get the {@code DecimalFormatSymbols} object from
65  * your {@code DecimalFormat} and modify it.
66  *
67  * @see          java.util.Locale
68  * @see          DecimalFormat
69  * @author       Mark Davis
70  * @author       Alan Liu
71  * @since 1.1
72  */
73 
74 public class DecimalFormatSymbols implements Cloneable, Serializable {
75 
76     // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
77     /**
78      * Create a DecimalFormatSymbols object for the default
79      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
80      * It is recommended that the {@link #getInstance(Locale) getInstance} method is used
81      * instead.
82      * <p>This is equivalent to calling
83      * {@link #DecimalFormatSymbols(Locale)
84      *     DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT))}.
85      * @see java.util.Locale#getDefault(java.util.Locale.Category)
86      * @see java.util.Locale.Category#FORMAT
87      */
DecimalFormatSymbols()88     public DecimalFormatSymbols() {
89         initialize( Locale.getDefault(Locale.Category.FORMAT) );
90     }
91 
92     // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
93     /**
94      * Create a DecimalFormatSymbols object for the given locale.
95      * It is recommended that the {@link #getInstance(Locale) getInstance} method is used
96      * instead.
97      * If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
98      * for the numbering system, the instance is initialized with the specified numbering
99      * system if the JRE implementation supports it. For example,
100      * <pre>
101      * NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
102      * </pre>
103      * This may return a {@code NumberFormat} instance with the Thai numbering system,
104      * instead of the Latin numbering system.
105      *
106      * @param locale the desired locale
107      * @throws    NullPointerException if {@code locale} is null
108      */
DecimalFormatSymbols( Locale locale )109     public DecimalFormatSymbols( Locale locale ) {
110         initialize( locale );
111     }
112 
113     // Android-changed: Removed reference to DecimalFormatSymbolsProvider.
114     /**
115      * Returns an array of all locales for which the
116      * {@code getInstance} methods of this class can return
117      * localized instances.
118      *
119      * It must contain at least a {@code Locale}
120      * instance equal to {@link java.util.Locale#US Locale.US}.
121      *
122      * @return an array of locales for which localized
123      *         {@code DecimalFormatSymbols} instances are available.
124      * @since 1.6
125      */
getAvailableLocales()126     public static Locale[] getAvailableLocales() {
127         // Android-changed: Removed used of DecimalFormatSymbolsProvider. Switched to use ICU.
128         return ICU.getAvailableLocales();
129     }
130 
131     // Android-changed: Removed reference to DecimalFormatSymbolsProvider.
132     /**
133      * Gets the {@code DecimalFormatSymbols} instance for the default
134      * locale.
135      * <p>This is equivalent to calling
136      * {@link #getInstance(Locale)
137      *     getInstance(Locale.getDefault(Locale.Category.FORMAT))}.
138      * @see java.util.Locale#getDefault(java.util.Locale.Category)
139      * @see java.util.Locale.Category#FORMAT
140      * @return a {@code DecimalFormatSymbols} instance.
141      * @since 1.6
142      */
getInstance()143     public static final DecimalFormatSymbols getInstance() {
144         return getInstance(Locale.getDefault(Locale.Category.FORMAT));
145     }
146 
147     // Android-changed: Removed reference to DecimalFormatSymbolsProvider.
148     /**
149      * Gets the {@code DecimalFormatSymbols} instance for the specified
150      * locale.
151      * If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
152      * for the numbering system, the instance is initialized with the specified numbering
153      * system if the JRE implementation supports it. For example,
154      * <pre>
155      * NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
156      * </pre>
157      * This may return a {@code NumberFormat} instance with the Thai numbering system,
158      * instead of the Latin numbering system.
159      *
160      * @param locale the desired locale.
161      * @return a {@code DecimalFormatSymbols} instance.
162      * @throws    NullPointerException if {@code locale} is null
163      * @since 1.6
164      */
getInstance(Locale locale)165     public static final DecimalFormatSymbols getInstance(Locale locale) {
166         // Android-changed: Removed used of DecimalFormatSymbolsProvider.
167         return new DecimalFormatSymbols(locale);
168     }
169 
170     /**
171      * Gets the character used for zero. Different for Arabic, etc.
172      *
173      * @return the character used for zero
174      */
getZeroDigit()175     public char getZeroDigit() {
176         return zeroDigit;
177     }
178 
179     /**
180      * Sets the character used for zero. Different for Arabic, etc.
181      *
182      * @param zeroDigit the character used for zero
183      */
setZeroDigit(char zeroDigit)184     public void setZeroDigit(char zeroDigit) {
185         hashCode = 0;
186         this.zeroDigit = zeroDigit;
187         // Android-added: reset cachedIcuDFS.
188         cachedIcuDFS = null;
189     }
190 
191     /**
192      * Gets the character used for grouping separator. Different for French, etc.
193      *
194      * @return the grouping separator
195      */
getGroupingSeparator()196     public char getGroupingSeparator() {
197         return groupingSeparator;
198     }
199 
200     /**
201      * Sets the character used for grouping separator. Different for French, etc.
202      *
203      * @param groupingSeparator the grouping separator
204      */
setGroupingSeparator(char groupingSeparator)205     public void setGroupingSeparator(char groupingSeparator) {
206         hashCode = 0;
207         this.groupingSeparator = groupingSeparator;
208         // Android-added: reset cachedIcuDFS.
209         cachedIcuDFS = null;
210     }
211 
212     /**
213      * Gets the character used for decimal sign. Different for French, etc.
214      *
215      * @return the character used for decimal sign
216      */
getDecimalSeparator()217     public char getDecimalSeparator() {
218         return decimalSeparator;
219     }
220 
221     /**
222      * Sets the character used for decimal sign. Different for French, etc.
223      *
224      * @param decimalSeparator the character used for decimal sign
225      */
setDecimalSeparator(char decimalSeparator)226     public void setDecimalSeparator(char decimalSeparator) {
227         hashCode = 0;
228         this.decimalSeparator = decimalSeparator;
229         // Android-added: reset cachedIcuDFS.
230         cachedIcuDFS = null;
231     }
232 
233     /**
234      * Gets the character used for per mille sign. Different for Arabic, etc.
235      *
236      * @return the character used for per mille sign
237      */
getPerMill()238     public char getPerMill() {
239         return perMill;
240     }
241 
242     /**
243      * Sets the character used for per mille sign. Different for Arabic, etc.
244      *
245      * @param perMill the character used for per mille sign
246      */
setPerMill(char perMill)247     public void setPerMill(char perMill) {
248         hashCode = 0;
249         this.perMill = perMill;
250         this.perMillText = Character.toString(perMill);
251         // Android-added: reset cachedIcuDFS.
252         cachedIcuDFS = null;
253     }
254 
255     /**
256      * Gets the character used for percent sign. Different for Arabic, etc.
257      *
258      * @return the character used for percent sign
259      */
getPercent()260     public char getPercent() {
261         return percent;
262     }
263 
264     // Android-added: getPercentString() for @UnsupportedAppUsage. Use getPercentText() otherwise.
265     /**
266      * Gets the string used for percent sign. Different for Arabic, etc.
267      *
268      * @hide
269      */
getPercentString()270     public String getPercentString() {
271         return getPercentText();
272     }
273 
274     /**
275      * Sets the character used for percent sign. Different for Arabic, etc.
276      *
277      * @param percent the character used for percent sign
278      */
setPercent(char percent)279     public void setPercent(char percent) {
280         hashCode = 0;
281         this.percent = percent;
282         this.percentText = Character.toString(percent);
283         // Android-added: reset cachedIcuDFS.
284         cachedIcuDFS = null;
285     }
286 
287     /**
288      * Gets the character used for a digit in a pattern.
289      *
290      * @return the character used for a digit in a pattern
291      */
getDigit()292     public char getDigit() {
293         return digit;
294     }
295 
296     /**
297      * Sets the character used for a digit in a pattern.
298      *
299      * @param digit the character used for a digit in a pattern
300      */
setDigit(char digit)301     public void setDigit(char digit) {
302         hashCode = 0;
303         this.digit = digit;
304         // Android-added: reset cachedIcuDFS.
305         cachedIcuDFS = null;
306     }
307 
308     /**
309      * Gets the character used to separate positive and negative subpatterns
310      * in a pattern.
311      *
312      * @return the pattern separator
313      */
getPatternSeparator()314     public char getPatternSeparator() {
315         return patternSeparator;
316     }
317 
318     /**
319      * Sets the character used to separate positive and negative subpatterns
320      * in a pattern.
321      *
322      * @param patternSeparator the pattern separator
323      */
setPatternSeparator(char patternSeparator)324     public void setPatternSeparator(char patternSeparator) {
325         hashCode = 0;
326         this.patternSeparator = patternSeparator;
327         // Android-added: reset cachedIcuDFS.
328         cachedIcuDFS = null;
329     }
330 
331     /**
332      * Gets the string used to represent infinity. Almost always left
333      * unchanged.
334      *
335      * @return the string representing infinity
336      */
getInfinity()337     public String getInfinity() {
338         return infinity;
339     }
340 
341     /**
342      * Sets the string used to represent infinity. Almost always left
343      * unchanged.
344      *
345      * @param infinity the string representing infinity
346      */
setInfinity(String infinity)347     public void setInfinity(String infinity) {
348         hashCode = 0;
349         this.infinity = infinity;
350         // Android-added: reset cachedIcuDFS.
351         cachedIcuDFS = null;
352     }
353 
354     /**
355      * Gets the string used to represent "not a number". Almost always left
356      * unchanged.
357      *
358      * @return the string representing "not a number"
359      */
getNaN()360     public String getNaN() {
361         return NaN;
362     }
363 
364     /**
365      * Sets the string used to represent "not a number". Almost always left
366      * unchanged.
367      *
368      * @param NaN the string representing "not a number"
369      */
setNaN(String NaN)370     public void setNaN(String NaN) {
371         hashCode = 0;
372         this.NaN = NaN;
373         // Android-added: reset cachedIcuDFS.
374         cachedIcuDFS = null;
375     }
376 
377     /**
378      * Gets the character used to represent minus sign. If no explicit
379      * negative format is specified, one is formed by prefixing
380      * minusSign to the positive format.
381      *
382      * @return the character representing minus sign
383      */
getMinusSign()384     public char getMinusSign() {
385         return minusSign;
386     }
387 
388     /**
389      * Sets the character used to represent minus sign. If no explicit
390      * negative format is specified, one is formed by prefixing
391      * minusSign to the positive format.
392      *
393      * @param minusSign the character representing minus sign
394      */
setMinusSign(char minusSign)395     public void setMinusSign(char minusSign) {
396         hashCode = 0;
397         this.minusSign = minusSign;
398         this.minusSignText = Character.toString(minusSign);
399         // Android-added: reset cachedIcuDFS.
400         cachedIcuDFS = null;
401     }
402 
403     /**
404      * Returns the currency symbol for the currency of these
405      * DecimalFormatSymbols in their locale.
406      *
407      * @return the currency symbol
408      * @since 1.2
409      */
getCurrencySymbol()410     public String getCurrencySymbol()
411     {
412         initializeCurrency(locale);
413         return currencySymbol;
414     }
415 
416     /**
417      * Sets the currency symbol for the currency of these
418      * DecimalFormatSymbols in their locale.
419      *
420      * @param currency the currency symbol
421      * @since 1.2
422      */
setCurrencySymbol(String currency)423     public void setCurrencySymbol(String currency)
424     {
425         initializeCurrency(locale);
426         hashCode = 0;
427         currencySymbol = currency;
428         // Android-added: reset cachedIcuDFS.
429         cachedIcuDFS = null;
430     }
431 
432     /**
433      * Returns the ISO 4217 currency code of the currency of these
434      * DecimalFormatSymbols.
435      *
436      * @return the currency code
437      * @since 1.2
438      */
getInternationalCurrencySymbol()439     public String getInternationalCurrencySymbol()
440     {
441         initializeCurrency(locale);
442         return intlCurrencySymbol;
443     }
444 
445     /**
446      * Sets the ISO 4217 currency code of the currency of these
447      * DecimalFormatSymbols.
448      * If the currency code is valid (as defined by
449      * {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}),
450      * this also sets the currency attribute to the corresponding Currency
451      * instance and the currency symbol attribute to the currency's symbol
452      * in the DecimalFormatSymbols' locale. If the currency code is not valid,
453      * then the currency attribute is set to null and the currency symbol
454      * attribute is not modified.
455      *
456      * @param currencyCode the currency code
457      * @see #setCurrency
458      * @see #setCurrencySymbol
459      * @since 1.2
460      */
setInternationalCurrencySymbol(String currencyCode)461     public void setInternationalCurrencySymbol(String currencyCode)
462     {
463         initializeCurrency(locale);
464         hashCode = 0;
465         intlCurrencySymbol = currencyCode;
466         currency = null;
467         if (currencyCode != null) {
468             try {
469                 currency = Currency.getInstance(currencyCode);
470                 // Android-changed: get currencySymbol for locale.
471                 currencySymbol = currency.getSymbol(locale);
472             } catch (IllegalArgumentException e) {
473             }
474         }
475         // Android-added: reset cachedIcuDFS.
476         cachedIcuDFS = null;
477     }
478 
479     /**
480      * Gets the currency of these DecimalFormatSymbols. May be null if the
481      * currency symbol attribute was previously set to a value that's not
482      * a valid ISO 4217 currency code.
483      *
484      * @return the currency used, or null
485      * @since 1.4
486      */
getCurrency()487     public Currency getCurrency() {
488         initializeCurrency(locale);
489         return currency;
490     }
491 
492     /**
493      * Sets the currency of these DecimalFormatSymbols.
494      * This also sets the currency symbol attribute to the currency's symbol
495      * in the DecimalFormatSymbols' locale, and the international currency
496      * symbol attribute to the currency's ISO 4217 currency code.
497      *
498      * @param currency the new currency to be used
499      * @throws    NullPointerException if {@code currency} is null
500      * @since 1.4
501      * @see #setCurrencySymbol
502      * @see #setInternationalCurrencySymbol
503      */
setCurrency(Currency currency)504     public void setCurrency(Currency currency) {
505         if (currency == null) {
506             throw new NullPointerException();
507         }
508         initializeCurrency(locale);
509         hashCode = 0;
510         this.currency = currency;
511         intlCurrencySymbol = currency.getCurrencyCode();
512         currencySymbol = currency.getSymbol(locale);
513         // Android-added: reset cachedIcuDFS.
514         cachedIcuDFS = null;
515     }
516 
517 
518     /**
519      * Returns the monetary decimal separator.
520      *
521      * @return the monetary decimal separator
522      * @since 1.2
523      */
getMonetaryDecimalSeparator()524     public char getMonetaryDecimalSeparator()
525     {
526         return monetarySeparator;
527     }
528 
529     /**
530      * Sets the monetary decimal separator.
531      *
532      * @param sep the monetary decimal separator
533      * @since 1.2
534      */
setMonetaryDecimalSeparator(char sep)535     public void setMonetaryDecimalSeparator(char sep)
536     {
537         hashCode = 0;
538         monetarySeparator = sep;
539         // Android-added: reset cachedIcuDFS.
540         cachedIcuDFS = null;
541     }
542 
543     /**
544      * Returns the string used to separate the mantissa from the exponent.
545      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
546      *
547      * @return the exponent separator string
548      * @see #setExponentSeparator(java.lang.String)
549      * @since 1.6
550      */
getExponentSeparator()551     public String getExponentSeparator()
552     {
553         return exponentialSeparator;
554     }
555 
556     /**
557      * Sets the string used to separate the mantissa from the exponent.
558      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
559      *
560      * @param exp the exponent separator string
561      * @throws    NullPointerException if {@code exp} is null
562      * @see #getExponentSeparator()
563      * @since 1.6
564      */
setExponentSeparator(String exp)565     public void setExponentSeparator(String exp)
566     {
567         if (exp == null) {
568             throw new NullPointerException();
569         }
570         hashCode = 0;
571         exponentialSeparator = exp;
572         // Android-added: reset cachedIcuDFS.
573         cachedIcuDFS = null;
574     }
575 
576     /**
577      * Gets the character used for grouping separator for currencies.
578      * May be different from {@code grouping separator} in some locales,
579      * e.g, German in Austria.
580      *
581      * @return the monetary grouping separator
582      * @since 15
583      */
getMonetaryGroupingSeparator()584     public char getMonetaryGroupingSeparator() {
585         return monetaryGroupingSeparator;
586     }
587 
588     /**
589      * Sets the character used for grouping separator for currencies.
590      * Invocation of this method will not affect the normal
591      * {@code grouping separator}.
592      *
593      * @param monetaryGroupingSeparator the monetary grouping separator
594      * @see #setGroupingSeparator(char)
595      * @since 15
596      */
setMonetaryGroupingSeparator(char monetaryGroupingSeparator)597     public void setMonetaryGroupingSeparator(char monetaryGroupingSeparator)
598     {
599         hashCode = 0;
600         this.monetaryGroupingSeparator = monetaryGroupingSeparator;
601         // Android-added: reset cachedIcuDFS.
602         cachedIcuDFS = null;
603     }
604 
605     //------------------------------------------------------------
606     // BEGIN   Package Private methods ... to be made public later
607     //------------------------------------------------------------
608 
609     /**
610      * Returns the character used to separate the mantissa from the exponent.
611      */
getExponentialSymbol()612     char getExponentialSymbol()
613     {
614         return exponential;
615     }
616 
617     /**
618      * Sets the character used to separate the mantissa from the exponent.
619      */
setExponentialSymbol(char exp)620     void setExponentialSymbol(char exp)
621     {
622         exponential = exp;
623         // Android-added: reset cachedIcuDFS.
624         cachedIcuDFS = null;
625     }
626 
627     /**
628      * Gets the string used for per mille sign. Different for Arabic, etc.
629      *
630      * @return the string used for per mille sign
631      * @since 13
632      */
getPerMillText()633     String getPerMillText() {
634         return perMillText;
635     }
636 
637     /**
638      * Sets the string used for per mille sign. Different for Arabic, etc.
639      *
640      * Setting the {@code perMillText} affects the return value of
641      * {@link #getPerMill()}, in which the first non-format character of
642      * {@code perMillText} is returned.
643      *
644      * @param perMillText the string used for per mille sign
645      * @throws NullPointerException if {@code perMillText} is null
646      * @throws IllegalArgumentException if {@code perMillText} is an empty string
647      * @see #getPerMill()
648      * @see #getPerMillText()
649      * @since 13
650      */
setPerMillText(String perMillText)651     void setPerMillText(String perMillText) {
652         Objects.requireNonNull(perMillText);
653         if (perMillText.isEmpty()) {
654             throw new IllegalArgumentException("Empty argument string");
655         }
656 
657         hashCode = 0;
658         this.perMillText = perMillText;
659         this.perMill = findNonFormatChar(perMillText, '\u2030');
660         // Android-added: reset cachedIcuDFS.
661         cachedIcuDFS = null;
662     }
663 
664     /**
665      * Gets the string used for percent sign. Different for Arabic, etc.
666      *
667      * @return the string used for percent sign
668      * @since 13
669      */
getPercentText()670     String getPercentText() {
671         return percentText;
672     }
673 
674     /**
675      * Sets the string used for percent sign. Different for Arabic, etc.
676      *
677      * Setting the {@code percentText} affects the return value of
678      * {@link #getPercent()}, in which the first non-format character of
679      * {@code percentText} is returned.
680      *
681      * @param percentText the string used for percent sign
682      * @throws NullPointerException if {@code percentText} is null
683      * @throws IllegalArgumentException if {@code percentText} is an empty string
684      * @see #getPercent()
685      * @see #getPercentText()
686      * @since 13
687      */
setPercentText(String percentText)688     void setPercentText(String percentText) {
689         Objects.requireNonNull(percentText);
690         if (percentText.isEmpty()) {
691             throw new IllegalArgumentException("Empty argument string");
692         }
693 
694         hashCode = 0;
695         this.percentText = percentText;
696         this.percent = findNonFormatChar(percentText, '%');
697         // Android-added: reset cachedIcuDFS.
698         cachedIcuDFS = null;
699     }
700 
701     /**
702      * Gets the string used to represent minus sign. If no explicit
703      * negative format is specified, one is formed by prefixing
704      * minusSignText to the positive format.
705      *
706      * @return the string representing minus sign
707      * @since 13
708      */
getMinusSignText()709     String getMinusSignText() {
710         return minusSignText;
711     }
712 
713     /**
714      * Sets the string used to represent minus sign. If no explicit
715      * negative format is specified, one is formed by prefixing
716      * minusSignText to the positive format.
717      *
718      * Setting the {@code minusSignText} affects the return value of
719      * {@link #getMinusSign()}, in which the first non-format character of
720      * {@code minusSignText} is returned.
721      *
722      * @param minusSignText the character representing minus sign
723      * @throws NullPointerException if {@code minusSignText} is null
724      * @throws IllegalArgumentException if {@code minusSignText} is an
725      *  empty string
726      * @see #getMinusSign()
727      * @see #getMinusSignText()
728      * @since 13
729      */
setMinusSignText(String minusSignText)730     void setMinusSignText(String minusSignText) {
731         Objects.requireNonNull(minusSignText);
732         if (minusSignText.isEmpty()) {
733             throw new IllegalArgumentException("Empty argument string");
734         }
735 
736         hashCode = 0;
737         this.minusSignText = minusSignText;
738         this.minusSign = findNonFormatChar(minusSignText, '-');
739         // Android-added: reset cachedIcuDFS.
740         cachedIcuDFS = null;
741     }
742 
743     //------------------------------------------------------------
744     // END     Package Private methods ... to be made public later
745     //------------------------------------------------------------
746 
747     /**
748      * Standard override.
749      */
750     @Override
clone()751     public Object clone() {
752         try {
753             return (DecimalFormatSymbols)super.clone();
754             // other fields are bit-copied
755         } catch (CloneNotSupportedException e) {
756             throw new InternalError(e);
757         }
758     }
759 
760     /**
761      * Override equals.
762      */
763     @Override
equals(Object obj)764     public boolean equals(Object obj) {
765         if (obj == null) return false;
766         if (this == obj) return true;
767         if (getClass() != obj.getClass()) return false;
768         DecimalFormatSymbols other = (DecimalFormatSymbols) obj;
769         return (zeroDigit == other.zeroDigit &&
770             groupingSeparator == other.groupingSeparator &&
771             decimalSeparator == other.decimalSeparator &&
772             percent == other.percent &&
773             percentText.equals(other.percentText) &&
774             perMill == other.perMill &&
775             perMillText.equals(other.perMillText) &&
776             digit == other.digit &&
777             minusSign == other.minusSign &&
778             minusSignText.equals(other.minusSignText) &&
779             patternSeparator == other.patternSeparator &&
780             infinity.equals(other.infinity) &&
781             NaN.equals(other.NaN) &&
782             getCurrencySymbol().equals(other.getCurrencySymbol()) && // possible currency init occurs here
783             intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
784             currency == other.currency &&
785             monetarySeparator == other.monetarySeparator &&
786             monetaryGroupingSeparator == other.monetaryGroupingSeparator &&
787             exponentialSeparator.equals(other.exponentialSeparator) &&
788             locale.equals(other.locale));
789     }
790 
791     /**
792      * Override hashCode.
793      */
794     @Override
hashCode()795     public int hashCode() {
796         if (hashCode == 0) {
797             hashCode = Objects.hash(
798                 zeroDigit,
799                 groupingSeparator,
800                 decimalSeparator,
801                 percent,
802                 percentText,
803                 perMill,
804                 perMillText,
805                 digit,
806                 minusSign,
807                 minusSignText,
808                 patternSeparator,
809                 infinity,
810                 NaN,
811                 getCurrencySymbol(), // possible currency init occurs here
812                 intlCurrencySymbol,
813                 currency,
814                 monetarySeparator,
815                 monetaryGroupingSeparator,
816                 exponentialSeparator,
817                 locale);
818         }
819         return hashCode;
820     }
821 
822     /**
823      * Initializes the symbols from the FormatData resource bundle.
824      */
initialize( Locale locale )825     private void initialize( Locale locale ) {
826         this.locale = locale;
827 
828         // BEGIN Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
829         /*
830         // check for region override
831         Locale override = locale.getUnicodeLocaleType("nu") == null ?
832             CalendarDataUtility.findRegionOverride(locale) :
833             locale;
834 
835         // get resource bundle data
836         LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, override);
837         // Avoid potential recursions
838         if (!(adapter instanceof ResourceBundleBasedAdapter)) {
839             adapter = LocaleProviderAdapter.getResourceBundleBased();
840         }
841         Object[] data = adapter.getLocaleResources(override).getDecimalFormatSymbolsData();
842         String[] numberElements = (String[]) data[0];
843         */
844         if (locale == null) {
845             throw new NullPointerException("locale");
846         }
847         locale = LocaleData.mapInvalidAndNullLocales(locale);
848         DecimalFormatData decimalFormatData = DecimalFormatData.getInstance(locale);
849         String[] values = new String[13];
850         values[0] = String.valueOf(decimalFormatData.getDecimalSeparator());
851         values[1] = String.valueOf(decimalFormatData.getGroupingSeparator());
852         values[2] = String.valueOf(decimalFormatData.getPatternSeparator());
853         values[3] = decimalFormatData.getPercent();
854         values[4] = String.valueOf(decimalFormatData.getZeroDigit());
855         values[5] = "#";
856         values[6] = decimalFormatData.getMinusSign();
857         values[7] = decimalFormatData.getExponentSeparator();
858         values[8] = decimalFormatData.getPerMill();
859         values[9] = decimalFormatData.getInfinity();
860         values[10] = decimalFormatData.getNaN();
861         values[11] = decimalFormatData.getMonetaryDecimalSeparator();
862         values[12] = decimalFormatData.getMonetaryGroupSeparator();
863         String[] numberElements = values;
864         // END Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
865 
866         decimalSeparator = numberElements[0].charAt(0);
867         groupingSeparator = numberElements[1].charAt(0);
868         patternSeparator = numberElements[2].charAt(0);
869         // Android-changed: For app compat, use single char for  percent, per mill and minus sign.
870         // TODO: Support 2-char percent, per mill and minus sign.
871         // percentText = numberElements[3];
872         // percent = findNonFormatChar(percentText, '%');
873         percent = findNonFormatChar(numberElements[3], '%');
874         percentText = Character.toString(percent);
875         zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc.
876         digit = numberElements[5].charAt(0);
877         // minusSignText = numberElements[6];
878         // minusSign = findNonFormatChar(minusSignText, '-');
879         minusSign = findNonFormatChar(numberElements[6], '-');
880         minusSignText = Character.toString(minusSign);
881         exponential = numberElements[7].charAt(0);
882         exponentialSeparator = numberElements[7]; //string representation new since 1.6
883         // perMillText = numberElements[8];
884         // perMill = findNonFormatChar(perMillText, '\u2030');
885         perMill = findNonFormatChar(numberElements[8], '\u2030');
886         perMillText = Character.toString(perMill);
887         infinity  = numberElements[9];
888         NaN = numberElements[10];
889 
890         // monetary decimal/grouping separators may be missing in resource bundles
891         monetarySeparator = numberElements.length < 12 || numberElements[11].isEmpty() ?
892             decimalSeparator : numberElements[11].charAt(0);
893         monetaryGroupingSeparator = numberElements.length < 13 || numberElements[12].isEmpty() ?
894             groupingSeparator : numberElements[12].charAt(0);
895 
896         // Android-removed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
897         // Upstream tries to re-use the strings from the cache, but Android doesn't have
898         // LocaleProviderAdapter to cache the strings.
899         // maybe filled with previously cached values, or null.
900         // intlCurrencySymbol = (String) data[1];
901         // currencySymbol = (String) data[2];
902     }
903 
904     /**
905      * Obtains non-format single character from String
906      */
907     private char findNonFormatChar(String src, char defChar) {
908         // Android-changed: Use maybeStripMarkers for backward compatibility.
909         // TODO: Consider using the OpenJDK implementation on Android U.
910         /*
911         return (char)src.chars()
912             .filter(c -> Character.getType(c) != Character.FORMAT)
913             .findFirst()
914             .orElse(defChar);
915         */
916         return maybeStripMarkers(src, defChar);
917     }
918 
919     /**
920      * Lazy initialization for currency related fields
921      */
922     private void initializeCurrency(Locale locale) {
923         if (currencyInitialized) {
924             return;
925         }
926 
927         // Try to obtain the currency used in the locale's country.
928         // Check for empty country string separately because it's a valid
929         // country ID for Locale (and used for the C locale), but not a valid
930         // ISO 3166 country code, and exceptions are expensive.
931         if (!locale.getCountry().isEmpty()) {
932             try {
933                 currency = Currency.getInstance(locale);
934             } catch (IllegalArgumentException e) {
935                 // use default values below for compatibility
936             }
937         }
938 
939         if (currency != null) {
940             // BEGIN Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
941             // Android doesn't have DecimalFormatSymbolsProvider to cache the values.
942             // Thus, simplify the code not loading from the cache.
943             /*
944             // get resource bundle data
945             LocaleProviderAdapter adapter =
946                 LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
947             // Avoid potential recursions
948             if (!(adapter instanceof ResourceBundleBasedAdapter)) {
949                 adapter = LocaleProviderAdapter.getResourceBundleBased();
950             }
951             Object[] data = adapter.getLocaleResources(locale).getDecimalFormatSymbolsData();
952             intlCurrencySymbol = currency.getCurrencyCode();
953             if (data[1] != null && data[1] == intlCurrencySymbol) {
954                 currencySymbol = (String) data[2];
955             } else {
956                 currencySymbol = currency.getSymbol(locale);
957                 data[1] = intlCurrencySymbol;
958                 data[2] = currencySymbol;
959             }
960             */
961             intlCurrencySymbol = currency.getCurrencyCode();
962             currencySymbol = currency.getSymbol(locale);
963             // END Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
964         } else {
965             // default values
966             intlCurrencySymbol = "XXX";
967             try {
968                 currency = Currency.getInstance(intlCurrencySymbol);
969             } catch (IllegalArgumentException e) {
970             }
971             currencySymbol = "\u00A4";
972         }
973 
974         currencyInitialized = true;
975     }
976 
977     // Android-changed: maybeStripMarkers added in b/26207216, fixed in b/32465689.
978     /**
979      * Attempts to strip RTL, LTR and Arabic letter markers from {@code symbol}.
980      * If the string contains a single non-marker character (and any number of marker characters),
981      * then that character is returned, otherwise {@code fallback} is returned.
982      *
983      * @hide
984      */
985     // VisibleForTesting
986     public static char maybeStripMarkers(String symbol, char fallback) {
987         final int length = symbol.length();
988         if (length >= 1) {
989             boolean sawNonMarker = false;
990             char nonMarker = 0;
991             for (int i = 0; i < length; i++) {
992                 final char c = symbol.charAt(i);
993                 if (c == '\u200E' || c == '\u200F' || c == '\u061C') {
994                     continue;
995                 }
996                 if (sawNonMarker) {
997                     // More than one non-marker character.
998                     return fallback;
999                 }
1000                 sawNonMarker = true;
1001                 nonMarker = c;
1002             }
1003             if (sawNonMarker) {
1004                 return nonMarker;
1005             }
1006         }
1007         return fallback;
1008     }
1009 
1010     // BEGIN Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
1011     /**
1012      * Convert an instance of this class to the ICU version so that it can be used with ICU4J.
1013      * @hide
1014      */
1015     protected android.icu.text.DecimalFormatSymbols getIcuDecimalFormatSymbols() {
1016         if (cachedIcuDFS != null) {
1017             return cachedIcuDFS;
1018         }
1019 
1020         initializeCurrency(this.locale);
1021         cachedIcuDFS = new android.icu.text.DecimalFormatSymbols(this.locale);
1022         // Do not localize plus sign. See "Special Pattern Characters" section in DecimalFormat.
1023         // http://b/67034519
1024         cachedIcuDFS.setPlusSign('+');
1025         cachedIcuDFS.setZeroDigit(zeroDigit);
1026         cachedIcuDFS.setDigit(digit);
1027         cachedIcuDFS.setDecimalSeparator(decimalSeparator);
1028         cachedIcuDFS.setGroupingSeparator(groupingSeparator);
1029         cachedIcuDFS.setPatternSeparator(patternSeparator);
1030         cachedIcuDFS.setPercentString(percentText);
1031         cachedIcuDFS.setPerMillString(perMillText);
1032         cachedIcuDFS.setMonetaryDecimalSeparator(monetarySeparator);
1033         cachedIcuDFS.setMinusSignString(minusSignText);
1034         cachedIcuDFS.setInfinity(infinity);
1035         cachedIcuDFS.setNaN(NaN);
1036         cachedIcuDFS.setExponentSeparator(exponentialSeparator);
1037         cachedIcuDFS.setMonetaryGroupingSeparator(monetaryGroupingSeparator);
1038         // j.t.DecimalFormatSymbols doesn't insert whitespace before/after currency by default.
1039         // Override ICU default value to retain historic Android behavior.
1040         // http://b/112127077
1041         cachedIcuDFS.setPatternForCurrencySpacing(
1042             android.icu.text.DecimalFormatSymbols.CURRENCY_SPC_INSERT,
1043             false /* beforeCurrency */, "");
1044         cachedIcuDFS.setPatternForCurrencySpacing(
1045             android.icu.text.DecimalFormatSymbols.CURRENCY_SPC_INSERT,
1046             true /* beforeCurrency */, "");
1047 
1048         try {
1049             cachedIcuDFS.setCurrency(
1050                     android.icu.util.Currency.getInstance(getCurrency().getCurrencyCode()));
1051         } catch (NullPointerException e) {
1052             currency = Currency.getInstance("XXX");
1053         }
1054 
1055         cachedIcuDFS.setCurrencySymbol(currencySymbol);
1056         cachedIcuDFS.setInternationalCurrencySymbol(intlCurrencySymbol);
1057 
1058         return cachedIcuDFS;
1059     }
1060 
1061     /**
1062      * Create an instance of DecimalFormatSymbols using the ICU equivalent of this class.
1063      * @hide
1064      */
1065     protected static DecimalFormatSymbols fromIcuInstance(
1066             android.icu.text.DecimalFormatSymbols dfs) {
1067         DecimalFormatSymbols result = new DecimalFormatSymbols(dfs.getLocale());
1068         result.setZeroDigit(dfs.getZeroDigit());
1069         result.setDigit(dfs.getDigit());
1070         result.setDecimalSeparator(dfs.getDecimalSeparator());
1071         result.setGroupingSeparator(dfs.getGroupingSeparator());
1072         result.setPatternSeparator(dfs.getPatternSeparator());
1073         // TODO: Remove findNonFormatChar filter to support 2-char percent, per mill and minus sign.
1074         result.setPercent(result.findNonFormatChar(dfs.getPercentString(), '%'));
1075         result.setPerMill(result.findNonFormatChar(dfs.getPerMillString(), '\u2030'));
1076         result.setMonetaryDecimalSeparator(dfs.getMonetaryDecimalSeparator());
1077         result.setMinusSign(result.findNonFormatChar(dfs.getMinusSignString(), '-'));
1078         result.setInfinity(dfs.getInfinity());
1079         result.setNaN(dfs.getNaN());
1080         result.setExponentSeparator(dfs.getExponentSeparator());
1081         result.setMonetaryGroupingSeparator(dfs.getMonetaryGroupingSeparator());
1082 
1083         try {
1084             if (dfs.getCurrency() != null) {
1085                 result.setCurrency(Currency.getInstance(dfs.getCurrency().getCurrencyCode()));
1086             } else {
1087                 result.setCurrency(Currency.getInstance("XXX"));
1088             }
1089         } catch (IllegalArgumentException e) {
1090             result.setCurrency(Currency.getInstance("XXX"));
1091         }
1092 
1093         result.setInternationalCurrencySymbol(dfs.getInternationalCurrencySymbol());
1094         result.setCurrencySymbol(dfs.getCurrencySymbol());
1095         return result;
1096     }
1097     // END Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
1098 
1099     // BEGIN Android-added: Android specific serialization code.
1100     private static final ObjectStreamField[] serialPersistentFields = {
1101             new ObjectStreamField("currencySymbol", String.class),
1102             new ObjectStreamField("decimalSeparator", char.class),
1103             new ObjectStreamField("digit", char.class),
1104             new ObjectStreamField("exponential", char.class),
1105             new ObjectStreamField("exponentialSeparator", String.class),
1106             new ObjectStreamField("groupingSeparator", char.class),
1107             new ObjectStreamField("infinity", String.class),
1108             new ObjectStreamField("intlCurrencySymbol", String.class),
1109             new ObjectStreamField("minusSign", char.class),
1110             new ObjectStreamField("monetarySeparator", char.class),
1111             new ObjectStreamField("NaN", String.class),
1112             new ObjectStreamField("patternSeparator", char.class),
1113             new ObjectStreamField("percent", char.class),
1114             new ObjectStreamField("perMill", char.class),
1115             new ObjectStreamField("serialVersionOnStream", int.class),
1116             new ObjectStreamField("zeroDigit", char.class),
1117             new ObjectStreamField("locale", Locale.class),
1118             new ObjectStreamField("minusSignStr", String.class),
1119             new ObjectStreamField("percentStr", String.class),
1120             new ObjectStreamField("perMillText", String.class),
1121             new ObjectStreamField("percentText", String.class),
1122             new ObjectStreamField("minusSignText", String.class),
1123             new ObjectStreamField("monetaryGroupingSeparator", char.class),
1124     };
1125 
1126     private void writeObject(ObjectOutputStream stream) throws IOException {
1127         ObjectOutputStream.PutField fields = stream.putFields();
1128         fields.put("currencySymbol", currencySymbol);
1129         fields.put("decimalSeparator", getDecimalSeparator());
1130         fields.put("digit", getDigit());
1131         fields.put("exponential", exponentialSeparator.charAt(0));
1132         fields.put("exponentialSeparator", exponentialSeparator);
1133         fields.put("groupingSeparator", getGroupingSeparator());
1134         fields.put("infinity", infinity);
1135         fields.put("intlCurrencySymbol", intlCurrencySymbol);
1136         fields.put("monetarySeparator", getMonetaryDecimalSeparator());
1137         fields.put("NaN", NaN);
1138         fields.put("patternSeparator", getPatternSeparator());
1139         fields.put("perMill", getPerMill());
1140         fields.put("serialVersionOnStream", serialVersionOnStream);
1141         fields.put("zeroDigit", getZeroDigit());
1142         fields.put("locale", locale);
1143 
1144         // Hardcode values here for backwards compatibility. These values will only be used
1145         // if we're de-serializing this object on an earlier version of android.
1146         fields.put("minusSign", minusSign);
1147         fields.put("percent", percent);
1148 
1149         // minusSignStr is a single-char string.
1150         fields.put("minusSignStr", String.valueOf(minusSign));
1151         fields.put("percentStr", getPercentString());
1152 
1153         // Fields added when serialVersionOnStream increased from 3 to 5 on ART U module.
1154         fields.put("perMillText", getPerMillText());
1155         fields.put("percentText", getPercentText());
1156         fields.put("minusSignText", getMinusSignText());
1157         fields.put("monetaryGroupingSeparator", getMonetaryGroupingSeparator());
1158         stream.writeFields();
1159     }
1160     // END Android-added: Android specific serialization code.
1161 
1162     /**
1163      * Reads the default serializable fields, provides default values for objects
1164      * in older serial versions, and initializes non-serializable fields.
1165      * If {@code serialVersionOnStream}
1166      * is less than 1, initializes {@code monetarySeparator} to be
1167      * the same as {@code decimalSeparator} and {@code exponential}
1168      * to be 'E'.
1169      * If {@code serialVersionOnStream} is less than 2,
1170      * initializes {@code locale} to the root locale, and initializes
1171      * If {@code serialVersionOnStream} is less than 3, it initializes
1172      * {@code exponentialSeparator} using {@code exponential}.
1173      * If {@code serialVersionOnStream} is less than 4, it initializes
1174      * {@code perMillText}, {@code percentText}, and
1175      * {@code minusSignText} using {@code perMill}, {@code percent}, and
1176      * {@code minusSign} respectively.
1177      * If {@code serialVersionOnStream} is less than 5, it initializes
1178      * {@code monetaryGroupingSeparator} using {@code groupingSeparator}.
1179      * Sets {@code serialVersionOnStream} back to the maximum allowed value so that
1180      * default serialization will work properly if this object is streamed out again.
1181      * Initializes the currency from the intlCurrencySymbol field.
1182      *
1183      * @throws InvalidObjectException if {@code char} and {@code String}
1184      *      representations of either percent, per mille, and/or minus sign disagree.
1185      * @since  1.1.6
1186      */
1187     @java.io.Serial
1188     private void readObject(ObjectInputStream stream)
1189             throws IOException, ClassNotFoundException {
1190         // BEGIN Android-changed: Android specific serialization code.
1191         ObjectInputStream.GetField fields = stream.readFields();
1192         final int serialVersionOnStream = fields.get("serialVersionOnStream", 0);
1193         currencySymbol = (String) fields.get("currencySymbol", "");
1194         setDecimalSeparator(fields.get("decimalSeparator", '.'));
1195         setDigit(fields.get("digit", '#'));
1196         setGroupingSeparator(fields.get("groupingSeparator", ','));
1197         infinity = (String) fields.get("infinity", "");
1198         intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", "");
1199         NaN = (String) fields.get("NaN", "");
1200         setPatternSeparator(fields.get("patternSeparator", ';'));
1201 
1202         // Special handling for minusSign and percent. If we've serialized the string versions of
1203         // these fields, use them. If not, fall back to the single character versions. This can
1204         // only happen if we're de-serializing an object that was written by an older version of
1205         // android (something that's strongly discouraged anyway).
1206         final String minusSignStr = (String) fields.get("minusSignStr", null);
1207         if (minusSignStr != null) {
1208             minusSign = minusSignStr.charAt(0);
1209         } else {
1210             setMinusSign(fields.get("minusSign", '-'));
1211         }
1212         final String percentStr = (String) fields.get("percentStr", null);
1213         if (percentStr != null) {
1214             percent = percentStr.charAt(0);
1215         } else {
1216             setPercent(fields.get("percent", '%'));
1217         }
1218 
1219         setPerMill(fields.get("perMill", '\u2030'));
1220         setZeroDigit(fields.get("zeroDigit", '0'));
1221         locale = (Locale) fields.get("locale", null);
1222         if (serialVersionOnStream == 0) {
1223             setMonetaryDecimalSeparator(getDecimalSeparator());
1224         } else {
1225             setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.'));
1226         }
1227 
1228         if (serialVersionOnStream == 0) {
1229             // Prior to Java 1.1.6, the exponent separator wasn't configurable.
1230             exponentialSeparator = "E";
1231         } else if (serialVersionOnStream < 3) {
1232             // In Javas 1.1.6 and 1.4, there was a character field "exponential".
1233             setExponentSeparator(String.valueOf(fields.get("exponential", 'E')));
1234         } else {
1235             // In Java 6, there's a new "exponentialSeparator" field.
1236             setExponentSeparator((String) fields.get("exponentialSeparator", "E"));
1237         }
1238         if (serialVersionOnStream < 4) {
1239             // didn't have perMillText, percentText, and minusSignText.
1240             // Create one using corresponding char variations.
1241             perMillText = Character.toString(perMill);
1242             percentText = Character.toString(percent);
1243             minusSignText = Character.toString(minusSign);
1244         } else {
1245             // Android-changed: Read the fields manually.
1246             perMillText = (String) fields.get("perMillText", Character.toString(perMill));
1247             percentText = (String) fields.get("percentText", Character.toString(percent));
1248             minusSignText = (String) fields.get("minusSignText", Character.toString(minusSign));
1249             // Check whether char and text fields agree
1250             if (findNonFormatChar(perMillText, '\uFFFF') != perMill ||
1251                 findNonFormatChar(percentText, '\uFFFF') != percent ||
1252                 findNonFormatChar(minusSignText, '\uFFFF') != minusSign) {
1253                 throw new InvalidObjectException(
1254                     "'char' and 'String' representations of either percent, " +
1255                     "per mille, and/or minus sign disagree.");
1256             }
1257         }
1258         if (serialVersionOnStream < 5) {
1259             // didn't have monetaryGroupingSeparator. Create one using groupingSeparator
1260             monetaryGroupingSeparator = groupingSeparator;
1261         }
1262         // Android-changed: Read the monetaryGroupingSeparator field manually.
1263         else {
1264             monetaryGroupingSeparator = fields.get("monetaryGroupingSeparator", groupingSeparator);
1265         }
1266 
1267         // Android-changed: Add `this` to avoid conflict with the local variable.
1268         // serialVersionOnStream = currentSerialVersion;
1269         this.serialVersionOnStream = currentSerialVersion;
1270 
1271         if (intlCurrencySymbol != null) {
1272             try {
1273                 currency = Currency.getInstance(intlCurrencySymbol);
1274                 currencyInitialized = true;
1275             } catch (IllegalArgumentException e) {
1276                 currency = null;
1277             }
1278         }
1279         // END Android-changed: Android specific serialization code.
1280     }
1281 
1282     /**
1283      * Character used for zero.
1284      *
1285      * @serial
1286      * @see #getZeroDigit
1287      */
1288     private  char    zeroDigit;
1289 
1290     /**
1291      * Character used for grouping separator.
1292      *
1293      * @serial
1294      * @see #getGroupingSeparator
1295      */
1296     private  char    groupingSeparator;
1297 
1298     /**
1299      * Character used for decimal sign.
1300      *
1301      * @serial
1302      * @see #getDecimalSeparator
1303      */
1304     private  char    decimalSeparator;
1305 
1306     /**
1307      * Character used for per mille sign.
1308      *
1309      * @serial
1310      * @see #getPerMill
1311      */
1312     private  char    perMill;
1313 
1314     /**
1315      * Character used for percent sign.
1316      * @serial
1317      * @see #getPercent
1318      */
1319     private  char    percent;
1320 
1321     /**
1322      * Character used for a digit in a pattern.
1323      *
1324      * @serial
1325      * @see #getDigit
1326      */
1327     private  char    digit;
1328 
1329     /**
1330      * Character used to separate positive and negative subpatterns
1331      * in a pattern.
1332      *
1333      * @serial
1334      * @see #getPatternSeparator
1335      */
1336     private  char    patternSeparator;
1337 
1338     /**
1339      * String used to represent infinity.
1340      * @serial
1341      * @see #getInfinity
1342      */
1343     private  String  infinity;
1344 
1345     /**
1346      * String used to represent "not a number".
1347      * @serial
1348      * @see #getNaN
1349      */
1350     private  String  NaN;
1351 
1352     /**
1353      * Character used to represent minus sign.
1354      * @serial
1355      * @see #getMinusSign
1356      */
1357     private  char    minusSign;
1358 
1359     /**
1360      * String denoting the local currency, e.g. "$".
1361      * @serial
1362      * @see #getCurrencySymbol
1363      */
1364     private  String  currencySymbol;
1365 
1366     /**
1367      * ISO 4217 currency code denoting the local currency, e.g. "USD".
1368      * @serial
1369      * @see #getInternationalCurrencySymbol
1370      */
1371     private  String  intlCurrencySymbol;
1372 
1373     /**
1374      * The decimal separator used when formatting currency values.
1375      * @serial
1376      * @since  1.1.6
1377      * @see #getMonetaryDecimalSeparator
1378      */
1379     private  char    monetarySeparator; // Field new in JDK 1.1.6
1380 
1381     /**
1382      * The character used to distinguish the exponent in a number formatted
1383      * in exponential notation, e.g. 'E' for a number such as "1.23E45".
1384      * <p>
1385      * Note that the public API provides no way to set this field,
1386      * even though it is supported by the implementation and the stream format.
1387      * The intent is that this will be added to the API in the future.
1388      *
1389      * @serial
1390      * @since  1.1.6
1391      */
1392     private  char    exponential;       // Field new in JDK 1.1.6
1393 
1394     /**
1395      * The string used to separate the mantissa from the exponent.
1396      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
1397      * <p>
1398      * If both {@code exponential} and {@code exponentialSeparator}
1399      * exist, this {@code exponentialSeparator} has the precedence.
1400      *
1401      * @serial
1402      * @since 1.6
1403      */
1404     private  String    exponentialSeparator;       // Field new in JDK 1.6
1405 
1406     /**
1407      * The locale of these currency format symbols.
1408      *
1409      * @serial
1410      * @since 1.4
1411      */
1412     private Locale locale;
1413 
1414     /**
1415      * String representation of per mille sign, which may include
1416      * formatting characters, such as BiDi control characters.
1417      * The first non-format character of this string is the same as
1418      * {@code perMill}.
1419      *
1420      * @serial
1421      * @since 13
1422      */
1423     private  String perMillText;
1424 
1425     /**
1426      * String representation of percent sign, which may include
1427      * formatting characters, such as BiDi control characters.
1428      * The first non-format character of this string is the same as
1429      * {@code percent}.
1430      *
1431      * @serial
1432      * @since 13
1433      */
1434     private  String percentText;
1435 
1436     /**
1437      * String representation of minus sign, which may include
1438      * formatting characters, such as BiDi control characters.
1439      * The first non-format character of this string is the same as
1440      * {@code minusSign}.
1441      *
1442      * @serial
1443      * @since 13
1444      */
1445     private  String minusSignText;
1446 
1447     /**
1448      * The grouping separator used when formatting currency values.
1449      *
1450      * @serial
1451      * @since 15
1452      */
1453     private  char    monetaryGroupingSeparator;
1454 
1455     // currency; only the ISO code is serialized.
1456     private transient Currency currency;
1457     private transient volatile boolean currencyInitialized;
1458 
1459     /**
1460      * Cached hash code.
1461      */
1462     private transient volatile int hashCode;
1463 
1464     // Proclaim JDK 1.1 FCS compatibility
1465     @java.io.Serial
1466     static final long serialVersionUID = 5772796243397350300L;
1467 
1468     // The internal serial version which says which version was written
1469     // - 0 (default) for version up to JDK 1.1.5
1470     // - 1 for version from JDK 1.1.6, which includes two new fields:
1471     //     monetarySeparator and exponential.
1472     // - 2 for version from J2SE 1.4, which includes locale field.
1473     // - 3 for version from J2SE 1.6, which includes exponentialSeparator field.
1474     // - 4 for version from Java SE 13, which includes perMillText, percentText,
1475     //      and minusSignText field.
1476     // - 5 for version from Java SE 15, which includes monetaryGroupingSeparator.
1477     private static final int currentSerialVersion = 5;
1478 
1479     /**
1480      * Describes the version of {@code DecimalFormatSymbols} present on the stream.
1481      * Possible values are:
1482      * <ul>
1483      * <li><b>0</b> (or uninitialized): versions prior to JDK 1.1.6.
1484      *
1485      * <li><b>1</b>: Versions written by JDK 1.1.6 or later, which include
1486      *      two new fields: {@code monetarySeparator} and {@code exponential}.
1487      * <li><b>2</b>: Versions written by J2SE 1.4 or later, which include a
1488      *      new {@code locale} field.
1489      * <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a
1490      *      new {@code exponentialSeparator} field.
1491      * <li><b>4</b>: Versions written by Java SE 13 or later, which include
1492      *      new {@code perMillText}, {@code percentText}, and
1493      *      {@code minusSignText} field.
1494      * <li><b>5</b>: Versions written by Java SE 15 or later, which include
1495      *      new {@code monetaryGroupingSeparator} field.
1496      * * </ul>
1497      * When streaming out a {@code DecimalFormatSymbols}, the most recent format
1498      * (corresponding to the highest allowable {@code serialVersionOnStream})
1499      * is always written.
1500      *
1501      * @serial
1502      * @since  1.1.6
1503      */
1504     private int serialVersionOnStream = currentSerialVersion;
1505 
1506     // BEGIN Android-added: cache for cachedIcuDFS.
1507     /**
1508      * Lazily created cached instance of an ICU DecimalFormatSymbols that's equivalent to this one.
1509      * This field is reset to null whenever any of the relevant fields of this class are modified
1510      * and will be re-created by {@link #getIcuDecimalFormatSymbols()} as necessary.
1511      */
1512     private transient android.icu.text.DecimalFormatSymbols cachedIcuDFS = null;
1513     // END Android-added: cache for cachedIcuDFS.
1514 }
1515