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