1 /*
2  * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /*
27  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
28  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
29  *
30  *   The original version of this source code and documentation is copyrighted
31  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
32  * materials are provided under terms of a License Agreement between Taligent
33  * and Sun. This technology is protected by multiple US and International
34  * patents. This notice and attribution to Taligent may not be removed.
35  *   Taligent is a registered trademark of Taligent, Inc.
36  *
37  */
38 
39 package java.text;
40 
41 import java.io.InvalidObjectException;
42 import java.io.IOException;
43 import java.io.ObjectInputStream;
44 import java.io.Serializable;
45 import java.text.spi.DecimalFormatSymbolsProvider;
46 import java.util.Currency;
47 import java.util.Locale;
48 import java.util.Objects;
49 import sun.util.locale.provider.CalendarDataUtility;
50 import sun.util.locale.provider.LocaleProviderAdapter;
51 import sun.util.locale.provider.LocaleServiceProviderPool;
52 import sun.util.locale.provider.ResourceBundleBasedAdapter;
53 
54 /**
55  * This class represents the set of symbols (such as the decimal separator,
56  * the grouping separator, and so on) needed by {@code DecimalFormat}
57  * to format numbers. {@code DecimalFormat} creates for itself an instance of
58  * {@code DecimalFormatSymbols} from its locale data.  If you need to change any
59  * of these symbols, you can get the {@code DecimalFormatSymbols} object from
60  * your {@code DecimalFormat} and modify it.
61  *
62  * <p>If the locale contains "rg" (region override)
63  * <a href="../util/Locale.html#def_locale_extension">Unicode extension</a>,
64  * the symbols are overridden for the designated region.
65  *
66  * @see          java.util.Locale
67  * @see          DecimalFormat
68  * @author       Mark Davis
69  * @author       Alan Liu
70  * @since 1.1
71  */
72 
73 public class DecimalFormatSymbols implements Cloneable, Serializable {
74 
75     /**
76      * Create a DecimalFormatSymbols object for the default
77      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
78      * This constructor can only construct instances for the locales
79      * supported by the Java runtime environment, not for those
80      * supported by installed
81      * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider}
82      * implementations. For full locale coverage, use the
83      * {@link #getInstance(Locale) getInstance} method.
84      * <p>This is equivalent to calling
85      * {@link #DecimalFormatSymbols(Locale)
86      *     DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT))}.
87      * @see java.util.Locale#getDefault(java.util.Locale.Category)
88      * @see java.util.Locale.Category#FORMAT
89      */
DecimalFormatSymbols()90     public DecimalFormatSymbols() {
91         initialize( Locale.getDefault(Locale.Category.FORMAT) );
92     }
93 
94     /**
95      * Create a DecimalFormatSymbols object for the given locale.
96      * This constructor can only construct instances for the locales
97      * supported by the Java runtime environment, not for those
98      * supported by installed
99      * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider}
100      * implementations. For full locale coverage, use the
101      * {@link #getInstance(Locale) getInstance} method.
102      * If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
103      * for the numbering system, the instance is initialized with the specified numbering
104      * system if the JRE implementation supports it. For example,
105      * <pre>
106      * NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
107      * </pre>
108      * This may return a {@code NumberFormat} instance with the Thai numbering system,
109      * instead of the Latin numbering system.
110      *
111      * @param locale the desired locale
112      * @throws    NullPointerException if {@code locale} is null
113      */
DecimalFormatSymbols( Locale locale )114     public DecimalFormatSymbols( Locale locale ) {
115         initialize( locale );
116     }
117 
118     /**
119      * Returns an array of all locales for which the
120      * {@code getInstance} methods of this class can return
121      * localized instances.
122      * The returned array represents the union of locales supported by the Java
123      * runtime and by installed
124      * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider}
125      * implementations.  It must contain at least a {@code Locale}
126      * instance equal to {@link java.util.Locale#US Locale.US}.
127      *
128      * @return an array of locales for which localized
129      *         {@code DecimalFormatSymbols} instances are available.
130      * @since 1.6
131      */
getAvailableLocales()132     public static Locale[] getAvailableLocales() {
133         LocaleServiceProviderPool pool =
134             LocaleServiceProviderPool.getPool(DecimalFormatSymbolsProvider.class);
135         return pool.getAvailableLocales();
136     }
137 
138     /**
139      * Gets the {@code DecimalFormatSymbols} instance for the default
140      * locale.  This method provides access to {@code DecimalFormatSymbols}
141      * instances for locales supported by the Java runtime itself as well
142      * as for those supported by installed
143      * {@link java.text.spi.DecimalFormatSymbolsProvider
144      * DecimalFormatSymbolsProvider} implementations.
145      * <p>This is equivalent to calling
146      * {@link #getInstance(Locale)
147      *     getInstance(Locale.getDefault(Locale.Category.FORMAT))}.
148      * @see java.util.Locale#getDefault(java.util.Locale.Category)
149      * @see java.util.Locale.Category#FORMAT
150      * @return a {@code DecimalFormatSymbols} instance.
151      * @since 1.6
152      */
getInstance()153     public static final DecimalFormatSymbols getInstance() {
154         return getInstance(Locale.getDefault(Locale.Category.FORMAT));
155     }
156 
157     /**
158      * Gets the {@code DecimalFormatSymbols} instance for the specified
159      * locale.  This method provides access to {@code DecimalFormatSymbols}
160      * instances for locales supported by the Java runtime itself as well
161      * as for those supported by installed
162      * {@link java.text.spi.DecimalFormatSymbolsProvider
163      * DecimalFormatSymbolsProvider} implementations.
164      * If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
165      * for the numbering system, the instance is initialized with the specified numbering
166      * system if the JRE implementation supports it. For example,
167      * <pre>
168      * NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
169      * </pre>
170      * This may return a {@code NumberFormat} instance with the Thai numbering system,
171      * instead of the Latin numbering system.
172      *
173      * @param locale the desired locale.
174      * @return a {@code DecimalFormatSymbols} instance.
175      * @throws    NullPointerException if {@code locale} is null
176      * @since 1.6
177      */
getInstance(Locale locale)178     public static final DecimalFormatSymbols getInstance(Locale locale) {
179         LocaleProviderAdapter adapter;
180         adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
181         DecimalFormatSymbolsProvider provider = adapter.getDecimalFormatSymbolsProvider();
182         DecimalFormatSymbols dfsyms = provider.getInstance(locale);
183         if (dfsyms == null) {
184             provider = LocaleProviderAdapter.forJRE().getDecimalFormatSymbolsProvider();
185             dfsyms = provider.getInstance(locale);
186         }
187         return dfsyms;
188     }
189 
190     /**
191      * Gets the character used for zero. Different for Arabic, etc.
192      *
193      * @return the character used for zero
194      */
getZeroDigit()195     public char getZeroDigit() {
196         return zeroDigit;
197     }
198 
199     /**
200      * Sets the character used for zero. Different for Arabic, etc.
201      *
202      * @param zeroDigit the character used for zero
203      */
setZeroDigit(char zeroDigit)204     public void setZeroDigit(char zeroDigit) {
205         hashCode = 0;
206         this.zeroDigit = zeroDigit;
207     }
208 
209     /**
210      * Gets the character used for grouping separator. Different for French, etc.
211      *
212      * @return the grouping separator
213      */
getGroupingSeparator()214     public char getGroupingSeparator() {
215         return groupingSeparator;
216     }
217 
218     /**
219      * Sets the character used for grouping separator. Different for French, etc.
220      *
221      * @param groupingSeparator the grouping separator
222      */
setGroupingSeparator(char groupingSeparator)223     public void setGroupingSeparator(char groupingSeparator) {
224         hashCode = 0;
225         this.groupingSeparator = groupingSeparator;
226     }
227 
228     /**
229      * Gets the character used for decimal sign. Different for French, etc.
230      *
231      * @return the character used for decimal sign
232      */
getDecimalSeparator()233     public char getDecimalSeparator() {
234         return decimalSeparator;
235     }
236 
237     /**
238      * Sets the character used for decimal sign. Different for French, etc.
239      *
240      * @param decimalSeparator the character used for decimal sign
241      */
setDecimalSeparator(char decimalSeparator)242     public void setDecimalSeparator(char decimalSeparator) {
243         hashCode = 0;
244         this.decimalSeparator = decimalSeparator;
245     }
246 
247     /**
248      * Gets the character used for per mille sign. Different for Arabic, etc.
249      *
250      * @return the character used for per mille sign
251      */
getPerMill()252     public char getPerMill() {
253         return perMill;
254     }
255 
256     /**
257      * Sets the character used for per mille sign. Different for Arabic, etc.
258      *
259      * @param perMill the character used for per mille sign
260      */
setPerMill(char perMill)261     public void setPerMill(char perMill) {
262         hashCode = 0;
263         this.perMill = perMill;
264         this.perMillText = Character.toString(perMill);
265     }
266 
267     /**
268      * Gets the character used for percent sign. Different for Arabic, etc.
269      *
270      * @return the character used for percent sign
271      */
getPercent()272     public char getPercent() {
273         return percent;
274     }
275 
276     /**
277      * Sets the character used for percent sign. Different for Arabic, etc.
278      *
279      * @param percent the character used for percent sign
280      */
setPercent(char percent)281     public void setPercent(char percent) {
282         hashCode = 0;
283         this.percent = percent;
284         this.percentText = Character.toString(percent);
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     }
305 
306     /**
307      * Gets the character used to separate positive and negative subpatterns
308      * in a pattern.
309      *
310      * @return the pattern separator
311      */
getPatternSeparator()312     public char getPatternSeparator() {
313         return patternSeparator;
314     }
315 
316     /**
317      * Sets the character used to separate positive and negative subpatterns
318      * in a pattern.
319      *
320      * @param patternSeparator the pattern separator
321      */
setPatternSeparator(char patternSeparator)322     public void setPatternSeparator(char patternSeparator) {
323         hashCode = 0;
324         this.patternSeparator = patternSeparator;
325     }
326 
327     /**
328      * Gets the string used to represent infinity. Almost always left
329      * unchanged.
330      *
331      * @return the string representing infinity
332      */
getInfinity()333     public String getInfinity() {
334         return infinity;
335     }
336 
337     /**
338      * Sets the string used to represent infinity. Almost always left
339      * unchanged.
340      *
341      * @param infinity the string representing infinity
342      */
setInfinity(String infinity)343     public void setInfinity(String infinity) {
344         hashCode = 0;
345         this.infinity = infinity;
346     }
347 
348     /**
349      * Gets the string used to represent "not a number". Almost always left
350      * unchanged.
351      *
352      * @return the string representing "not a number"
353      */
getNaN()354     public String getNaN() {
355         return NaN;
356     }
357 
358     /**
359      * Sets the string used to represent "not a number". Almost always left
360      * unchanged.
361      *
362      * @param NaN the string representing "not a number"
363      */
setNaN(String NaN)364     public void setNaN(String NaN) {
365         hashCode = 0;
366         this.NaN = NaN;
367     }
368 
369     /**
370      * Gets the character used to represent minus sign. If no explicit
371      * negative format is specified, one is formed by prefixing
372      * minusSign to the positive format.
373      *
374      * @return the character representing minus sign
375      */
getMinusSign()376     public char getMinusSign() {
377         return minusSign;
378     }
379 
380     /**
381      * Sets the character used to represent minus sign. If no explicit
382      * negative format is specified, one is formed by prefixing
383      * minusSign to the positive format.
384      *
385      * @param minusSign the character representing minus sign
386      */
setMinusSign(char minusSign)387     public void setMinusSign(char minusSign) {
388         hashCode = 0;
389         this.minusSign = minusSign;
390         this.minusSignText = Character.toString(minusSign);
391     }
392 
393     /**
394      * Returns the currency symbol for the currency of these
395      * DecimalFormatSymbols in their locale.
396      *
397      * @return the currency symbol
398      * @since 1.2
399      */
getCurrencySymbol()400     public String getCurrencySymbol()
401     {
402         initializeCurrency(locale);
403         return currencySymbol;
404     }
405 
406     /**
407      * Sets the currency symbol for the currency of these
408      * DecimalFormatSymbols in their locale.
409      *
410      * @param currency the currency symbol
411      * @since 1.2
412      */
setCurrencySymbol(String currency)413     public void setCurrencySymbol(String currency)
414     {
415         initializeCurrency(locale);
416         hashCode = 0;
417         currencySymbol = currency;
418     }
419 
420     /**
421      * Returns the ISO 4217 currency code of the currency of these
422      * DecimalFormatSymbols.
423      *
424      * @return the currency code
425      * @since 1.2
426      */
getInternationalCurrencySymbol()427     public String getInternationalCurrencySymbol()
428     {
429         initializeCurrency(locale);
430         return intlCurrencySymbol;
431     }
432 
433     /**
434      * Sets the ISO 4217 currency code of the currency of these
435      * DecimalFormatSymbols.
436      * If the currency code is valid (as defined by
437      * {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}),
438      * this also sets the currency attribute to the corresponding Currency
439      * instance and the currency symbol attribute to the currency's symbol
440      * in the DecimalFormatSymbols' locale. If the currency code is not valid,
441      * then the currency attribute is set to null and the currency symbol
442      * attribute is not modified.
443      *
444      * @param currencyCode the currency code
445      * @see #setCurrency
446      * @see #setCurrencySymbol
447      * @since 1.2
448      */
setInternationalCurrencySymbol(String currencyCode)449     public void setInternationalCurrencySymbol(String currencyCode)
450     {
451         initializeCurrency(locale);
452         hashCode = 0;
453         intlCurrencySymbol = currencyCode;
454         currency = null;
455         if (currencyCode != null) {
456             try {
457                 currency = Currency.getInstance(currencyCode);
458                 currencySymbol = currency.getSymbol();
459             } catch (IllegalArgumentException e) {
460             }
461         }
462     }
463 
464     /**
465      * Gets the currency of these DecimalFormatSymbols. May be null if the
466      * currency symbol attribute was previously set to a value that's not
467      * a valid ISO 4217 currency code.
468      *
469      * @return the currency used, or null
470      * @since 1.4
471      */
getCurrency()472     public Currency getCurrency() {
473         initializeCurrency(locale);
474         return currency;
475     }
476 
477     /**
478      * Sets the currency of these DecimalFormatSymbols.
479      * This also sets the currency symbol attribute to the currency's symbol
480      * in the DecimalFormatSymbols' locale, and the international currency
481      * symbol attribute to the currency's ISO 4217 currency code.
482      *
483      * @param currency the new currency to be used
484      * @throws    NullPointerException if {@code currency} is null
485      * @since 1.4
486      * @see #setCurrencySymbol
487      * @see #setInternationalCurrencySymbol
488      */
setCurrency(Currency currency)489     public void setCurrency(Currency currency) {
490         if (currency == null) {
491             throw new NullPointerException();
492         }
493         initializeCurrency(locale);
494         hashCode = 0;
495         this.currency = currency;
496         intlCurrencySymbol = currency.getCurrencyCode();
497         currencySymbol = currency.getSymbol(locale);
498     }
499 
500 
501     /**
502      * Returns the monetary decimal separator.
503      *
504      * @return the monetary decimal separator
505      * @since 1.2
506      */
getMonetaryDecimalSeparator()507     public char getMonetaryDecimalSeparator()
508     {
509         return monetarySeparator;
510     }
511 
512     /**
513      * Sets the monetary decimal separator.
514      *
515      * @param sep the monetary decimal separator
516      * @since 1.2
517      */
setMonetaryDecimalSeparator(char sep)518     public void setMonetaryDecimalSeparator(char sep)
519     {
520         hashCode = 0;
521         monetarySeparator = sep;
522     }
523 
524     /**
525      * Returns the string used to separate the mantissa from the exponent.
526      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
527      *
528      * @return the exponent separator string
529      * @see #setExponentSeparator(java.lang.String)
530      * @since 1.6
531      */
getExponentSeparator()532     public String getExponentSeparator()
533     {
534         return exponentialSeparator;
535     }
536 
537     /**
538      * Sets the string used to separate the mantissa from the exponent.
539      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
540      *
541      * @param exp the exponent separator string
542      * @throws    NullPointerException if {@code exp} is null
543      * @see #getExponentSeparator()
544      * @since 1.6
545      */
setExponentSeparator(String exp)546     public void setExponentSeparator(String exp)
547     {
548         if (exp == null) {
549             throw new NullPointerException();
550         }
551         hashCode = 0;
552         exponentialSeparator = exp;
553     }
554 
555     /**
556      * Gets the character used for grouping separator for currencies.
557      * May be different from {@code grouping separator} in some locales,
558      * e.g, German in Austria.
559      *
560      * @return the monetary grouping separator
561      * @since 15
562      */
getMonetaryGroupingSeparator()563     public char getMonetaryGroupingSeparator() {
564         return monetaryGroupingSeparator;
565     }
566 
567     /**
568      * Sets the character used for grouping separator for currencies.
569      * Invocation of this method will not affect the normal
570      * {@code grouping separator}.
571      *
572      * @param monetaryGroupingSeparator the monetary grouping separator
573      * @see #setGroupingSeparator(char)
574      * @since 15
575      */
setMonetaryGroupingSeparator(char monetaryGroupingSeparator)576     public void setMonetaryGroupingSeparator(char monetaryGroupingSeparator)
577     {
578         hashCode = 0;
579         this.monetaryGroupingSeparator = monetaryGroupingSeparator;
580     }
581 
582     //------------------------------------------------------------
583     // BEGIN   Package Private methods ... to be made public later
584     //------------------------------------------------------------
585 
586     /**
587      * Returns the character used to separate the mantissa from the exponent.
588      */
getExponentialSymbol()589     char getExponentialSymbol()
590     {
591         return exponential;
592     }
593 
594     /**
595      * Sets the character used to separate the mantissa from the exponent.
596      */
setExponentialSymbol(char exp)597     void setExponentialSymbol(char exp)
598     {
599         exponential = exp;
600     }
601 
602     /**
603      * Gets the string used for per mille sign. Different for Arabic, etc.
604      *
605      * @return the string used for per mille sign
606      * @since 13
607      */
getPerMillText()608     String getPerMillText() {
609         return perMillText;
610     }
611 
612     /**
613      * Sets the string used for per mille sign. Different for Arabic, etc.
614      *
615      * Setting the {@code perMillText} affects the return value of
616      * {@link #getPerMill()}, in which the first non-format character of
617      * {@code perMillText} is returned.
618      *
619      * @param perMillText the string used for per mille sign
620      * @throws NullPointerException if {@code perMillText} is null
621      * @throws IllegalArgumentException if {@code perMillText} is an empty string
622      * @see #getPerMill()
623      * @see #getPerMillText()
624      * @since 13
625      */
setPerMillText(String perMillText)626     void setPerMillText(String perMillText) {
627         Objects.requireNonNull(perMillText);
628         if (perMillText.isEmpty()) {
629             throw new IllegalArgumentException("Empty argument string");
630         }
631 
632         hashCode = 0;
633         this.perMillText = perMillText;
634         this.perMill = findNonFormatChar(perMillText, '\u2030');
635     }
636 
637     /**
638      * Gets the string used for percent sign. Different for Arabic, etc.
639      *
640      * @return the string used for percent sign
641      * @since 13
642      */
getPercentText()643     String getPercentText() {
644         return percentText;
645     }
646 
647     /**
648      * Sets the string used for percent sign. Different for Arabic, etc.
649      *
650      * Setting the {@code percentText} affects the return value of
651      * {@link #getPercent()}, in which the first non-format character of
652      * {@code percentText} is returned.
653      *
654      * @param percentText the string used for percent sign
655      * @throws NullPointerException if {@code percentText} is null
656      * @throws IllegalArgumentException if {@code percentText} is an empty string
657      * @see #getPercent()
658      * @see #getPercentText()
659      * @since 13
660      */
setPercentText(String percentText)661     void setPercentText(String percentText) {
662         Objects.requireNonNull(percentText);
663         if (percentText.isEmpty()) {
664             throw new IllegalArgumentException("Empty argument string");
665         }
666 
667         hashCode = 0;
668         this.percentText = percentText;
669         this.percent = findNonFormatChar(percentText, '%');
670     }
671 
672     /**
673      * Gets the string used to represent minus sign. If no explicit
674      * negative format is specified, one is formed by prefixing
675      * minusSignText to the positive format.
676      *
677      * @return the string representing minus sign
678      * @since 13
679      */
getMinusSignText()680     String getMinusSignText() {
681         return minusSignText;
682     }
683 
684     /**
685      * Sets the string used to represent minus sign. If no explicit
686      * negative format is specified, one is formed by prefixing
687      * minusSignText to the positive format.
688      *
689      * Setting the {@code minusSignText} affects the return value of
690      * {@link #getMinusSign()}, in which the first non-format character of
691      * {@code minusSignText} is returned.
692      *
693      * @param minusSignText the character representing minus sign
694      * @throws NullPointerException if {@code minusSignText} is null
695      * @throws IllegalArgumentException if {@code minusSignText} is an
696      *  empty string
697      * @see #getMinusSign()
698      * @see #getMinusSignText()
699      * @since 13
700      */
setMinusSignText(String minusSignText)701     void setMinusSignText(String minusSignText) {
702         Objects.requireNonNull(minusSignText);
703         if (minusSignText.isEmpty()) {
704             throw new IllegalArgumentException("Empty argument string");
705         }
706 
707         hashCode = 0;
708         this.minusSignText = minusSignText;
709         this.minusSign = findNonFormatChar(minusSignText, '-');
710     }
711 
712     //------------------------------------------------------------
713     // END     Package Private methods ... to be made public later
714     //------------------------------------------------------------
715 
716     /**
717      * Standard override.
718      */
719     @Override
clone()720     public Object clone() {
721         try {
722             return (DecimalFormatSymbols)super.clone();
723             // other fields are bit-copied
724         } catch (CloneNotSupportedException e) {
725             throw new InternalError(e);
726         }
727     }
728 
729     /**
730      * Override equals.
731      */
732     @Override
equals(Object obj)733     public boolean equals(Object obj) {
734         if (obj == null) return false;
735         if (this == obj) return true;
736         if (getClass() != obj.getClass()) return false;
737         DecimalFormatSymbols other = (DecimalFormatSymbols) obj;
738         return (zeroDigit == other.zeroDigit &&
739             groupingSeparator == other.groupingSeparator &&
740             decimalSeparator == other.decimalSeparator &&
741             percent == other.percent &&
742             percentText.equals(other.percentText) &&
743             perMill == other.perMill &&
744             perMillText.equals(other.perMillText) &&
745             digit == other.digit &&
746             minusSign == other.minusSign &&
747             minusSignText.equals(other.minusSignText) &&
748             patternSeparator == other.patternSeparator &&
749             infinity.equals(other.infinity) &&
750             NaN.equals(other.NaN) &&
751             getCurrencySymbol().equals(other.getCurrencySymbol()) && // possible currency init occurs here
752             intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
753             currency == other.currency &&
754             monetarySeparator == other.monetarySeparator &&
755             monetaryGroupingSeparator == other.monetaryGroupingSeparator &&
756             exponentialSeparator.equals(other.exponentialSeparator) &&
757             locale.equals(other.locale));
758     }
759 
760     /**
761      * Override hashCode.
762      */
763     @Override
hashCode()764     public int hashCode() {
765         if (hashCode == 0) {
766             hashCode = Objects.hash(
767                 zeroDigit,
768                 groupingSeparator,
769                 decimalSeparator,
770                 percent,
771                 percentText,
772                 perMill,
773                 perMillText,
774                 digit,
775                 minusSign,
776                 minusSignText,
777                 patternSeparator,
778                 infinity,
779                 NaN,
780                 getCurrencySymbol(), // possible currency init occurs here
781                 intlCurrencySymbol,
782                 currency,
783                 monetarySeparator,
784                 monetaryGroupingSeparator,
785                 exponentialSeparator,
786                 locale);
787         }
788         return hashCode;
789     }
790 
791     /**
792      * Initializes the symbols from the FormatData resource bundle.
793      */
initialize( Locale locale )794     private void initialize( Locale locale ) {
795         this.locale = locale;
796 
797         // check for region override
798         Locale override = locale.getUnicodeLocaleType("nu") == null ?
799             CalendarDataUtility.findRegionOverride(locale) :
800             locale;
801 
802         // get resource bundle data
803         LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, override);
804         // Avoid potential recursions
805         if (!(adapter instanceof ResourceBundleBasedAdapter)) {
806             adapter = LocaleProviderAdapter.getResourceBundleBased();
807         }
808         Object[] data = adapter.getLocaleResources(override).getDecimalFormatSymbolsData();
809         String[] numberElements = (String[]) data[0];
810 
811         decimalSeparator = numberElements[0].charAt(0);
812         groupingSeparator = numberElements[1].charAt(0);
813         patternSeparator = numberElements[2].charAt(0);
814         percentText = numberElements[3];
815         percent = findNonFormatChar(percentText, '%');
816         zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc.
817         digit = numberElements[5].charAt(0);
818         minusSignText = numberElements[6];
819         minusSign = findNonFormatChar(minusSignText, '-');
820         exponential = numberElements[7].charAt(0);
821         exponentialSeparator = numberElements[7]; //string representation new since 1.6
822         perMillText = numberElements[8];
823         perMill = findNonFormatChar(perMillText, '\u2030');
824         infinity  = numberElements[9];
825         NaN = numberElements[10];
826 
827         // monetary decimal/grouping separators may be missing in resource bundles
828         monetarySeparator = numberElements.length < 12 || numberElements[11].isEmpty() ?
829             decimalSeparator : numberElements[11].charAt(0);
830         monetaryGroupingSeparator = numberElements.length < 13 || numberElements[12].isEmpty() ?
831             groupingSeparator : numberElements[12].charAt(0);
832 
833         // maybe filled with previously cached values, or null.
834         intlCurrencySymbol = (String) data[1];
835         currencySymbol = (String) data[2];
836     }
837 
838     /**
839      * Obtains non-format single character from String
840      */
841     private char findNonFormatChar(String src, char defChar) {
842         return (char)src.chars()
843             .filter(c -> Character.getType(c) != Character.FORMAT)
844             .findFirst()
845             .orElse(defChar);
846     }
847 
848     /**
849      * Lazy initialization for currency related fields
850      */
851     private void initializeCurrency(Locale locale) {
852         if (currencyInitialized) {
853             return;
854         }
855 
856         // Try to obtain the currency used in the locale's country.
857         // Check for empty country string separately because it's a valid
858         // country ID for Locale (and used for the C locale), but not a valid
859         // ISO 3166 country code, and exceptions are expensive.
860         if (!locale.getCountry().isEmpty()) {
861             try {
862                 currency = Currency.getInstance(locale);
863             } catch (IllegalArgumentException e) {
864                 // use default values below for compatibility
865             }
866         }
867 
868         if (currency != null) {
869             // get resource bundle data
870             LocaleProviderAdapter adapter =
871                 LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
872             // Avoid potential recursions
873             if (!(adapter instanceof ResourceBundleBasedAdapter)) {
874                 adapter = LocaleProviderAdapter.getResourceBundleBased();
875             }
876             Object[] data = adapter.getLocaleResources(locale).getDecimalFormatSymbolsData();
877             intlCurrencySymbol = currency.getCurrencyCode();
878             if (data[1] != null && data[1] == intlCurrencySymbol) {
879                 currencySymbol = (String) data[2];
880             } else {
881                 currencySymbol = currency.getSymbol(locale);
882                 data[1] = intlCurrencySymbol;
883                 data[2] = currencySymbol;
884             }
885         } else {
886             // default values
887             intlCurrencySymbol = "XXX";
888             try {
889                 currency = Currency.getInstance(intlCurrencySymbol);
890             } catch (IllegalArgumentException e) {
891             }
892             currencySymbol = "\u00A4";
893         }
894 
895         currencyInitialized = true;
896     }
897 
898     /**
899      * Reads the default serializable fields, provides default values for objects
900      * in older serial versions, and initializes non-serializable fields.
901      * If {@code serialVersionOnStream}
902      * is less than 1, initializes {@code monetarySeparator} to be
903      * the same as {@code decimalSeparator} and {@code exponential}
904      * to be 'E'.
905      * If {@code serialVersionOnStream} is less than 2,
906      * initializes {@code locale} to the root locale, and initializes
907      * If {@code serialVersionOnStream} is less than 3, it initializes
908      * {@code exponentialSeparator} using {@code exponential}.
909      * If {@code serialVersionOnStream} is less than 4, it initializes
910      * {@code perMillText}, {@code percentText}, and
911      * {@code minusSignText} using {@code perMill}, {@code percent}, and
912      * {@code minusSign} respectively.
913      * If {@code serialVersionOnStream} is less than 5, it initializes
914      * {@code monetaryGroupingSeparator} using {@code groupingSeparator}.
915      * Sets {@code serialVersionOnStream} back to the maximum allowed value so that
916      * default serialization will work properly if this object is streamed out again.
917      * Initializes the currency from the intlCurrencySymbol field.
918      *
919      * @throws InvalidObjectException if {@code char} and {@code String}
920      *      representations of either percent, per mille, and/or minus sign disagree.
921      * @since  1.1.6
922      */
923     @java.io.Serial
924     private void readObject(ObjectInputStream stream)
925             throws IOException, ClassNotFoundException {
926         stream.defaultReadObject();
927         if (serialVersionOnStream < 1) {
928             // Didn't have monetarySeparator or exponential field;
929             // use defaults.
930             monetarySeparator = decimalSeparator;
931             exponential       = 'E';
932         }
933         if (serialVersionOnStream < 2) {
934             // didn't have locale; use root locale
935             locale = Locale.ROOT;
936         }
937         if (serialVersionOnStream < 3) {
938             // didn't have exponentialSeparator. Create one using exponential
939             exponentialSeparator = Character.toString(exponential);
940         }
941         if (serialVersionOnStream < 4) {
942             // didn't have perMillText, percentText, and minusSignText.
943             // Create one using corresponding char variations.
944             perMillText = Character.toString(perMill);
945             percentText = Character.toString(percent);
946             minusSignText = Character.toString(minusSign);
947         } else {
948             // Check whether char and text fields agree
949             if (findNonFormatChar(perMillText, '\uFFFF') != perMill ||
950                 findNonFormatChar(percentText, '\uFFFF') != percent ||
951                 findNonFormatChar(minusSignText, '\uFFFF') != minusSign) {
952                 throw new InvalidObjectException(
953                     "'char' and 'String' representations of either percent, " +
954                     "per mille, and/or minus sign disagree.");
955             }
956         }
957         if (serialVersionOnStream < 5) {
958             // didn't have monetaryGroupingSeparator. Create one using groupingSeparator
959             monetaryGroupingSeparator = groupingSeparator;
960         }
961 
962         serialVersionOnStream = currentSerialVersion;
963 
964         if (intlCurrencySymbol != null) {
965             try {
966                  currency = Currency.getInstance(intlCurrencySymbol);
967             } catch (IllegalArgumentException e) {
968             }
969             currencyInitialized = true;
970         }
971     }
972 
973     /**
974      * Character used for zero.
975      *
976      * @serial
977      * @see #getZeroDigit
978      */
979     private  char    zeroDigit;
980 
981     /**
982      * Character used for grouping separator.
983      *
984      * @serial
985      * @see #getGroupingSeparator
986      */
987     private  char    groupingSeparator;
988 
989     /**
990      * Character used for decimal sign.
991      *
992      * @serial
993      * @see #getDecimalSeparator
994      */
995     private  char    decimalSeparator;
996 
997     /**
998      * Character used for per mille sign.
999      *
1000      * @serial
1001      * @see #getPerMill
1002      */
1003     private  char    perMill;
1004 
1005     /**
1006      * Character used for percent sign.
1007      * @serial
1008      * @see #getPercent
1009      */
1010     private  char    percent;
1011 
1012     /**
1013      * Character used for a digit in a pattern.
1014      *
1015      * @serial
1016      * @see #getDigit
1017      */
1018     private  char    digit;
1019 
1020     /**
1021      * Character used to separate positive and negative subpatterns
1022      * in a pattern.
1023      *
1024      * @serial
1025      * @see #getPatternSeparator
1026      */
1027     private  char    patternSeparator;
1028 
1029     /**
1030      * String used to represent infinity.
1031      * @serial
1032      * @see #getInfinity
1033      */
1034     private  String  infinity;
1035 
1036     /**
1037      * String used to represent "not a number".
1038      * @serial
1039      * @see #getNaN
1040      */
1041     private  String  NaN;
1042 
1043     /**
1044      * Character used to represent minus sign.
1045      * @serial
1046      * @see #getMinusSign
1047      */
1048     private  char    minusSign;
1049 
1050     /**
1051      * String denoting the local currency, e.g. "$".
1052      * @serial
1053      * @see #getCurrencySymbol
1054      */
1055     private  String  currencySymbol;
1056 
1057     /**
1058      * ISO 4217 currency code denoting the local currency, e.g. "USD".
1059      * @serial
1060      * @see #getInternationalCurrencySymbol
1061      */
1062     private  String  intlCurrencySymbol;
1063 
1064     /**
1065      * The decimal separator used when formatting currency values.
1066      * @serial
1067      * @since  1.1.6
1068      * @see #getMonetaryDecimalSeparator
1069      */
1070     private  char    monetarySeparator; // Field new in JDK 1.1.6
1071 
1072     /**
1073      * The character used to distinguish the exponent in a number formatted
1074      * in exponential notation, e.g. 'E' for a number such as "1.23E45".
1075      * <p>
1076      * Note that the public API provides no way to set this field,
1077      * even though it is supported by the implementation and the stream format.
1078      * The intent is that this will be added to the API in the future.
1079      *
1080      * @serial
1081      * @since  1.1.6
1082      */
1083     private  char    exponential;       // Field new in JDK 1.1.6
1084 
1085     /**
1086      * The string used to separate the mantissa from the exponent.
1087      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
1088      * <p>
1089      * If both {@code exponential} and {@code exponentialSeparator}
1090      * exist, this {@code exponentialSeparator} has the precedence.
1091      *
1092      * @serial
1093      * @since 1.6
1094      */
1095     private  String    exponentialSeparator;       // Field new in JDK 1.6
1096 
1097     /**
1098      * The locale of these currency format symbols.
1099      *
1100      * @serial
1101      * @since 1.4
1102      */
1103     private Locale locale;
1104 
1105     /**
1106      * String representation of per mille sign, which may include
1107      * formatting characters, such as BiDi control characters.
1108      * The first non-format character of this string is the same as
1109      * {@code perMill}.
1110      *
1111      * @serial
1112      * @since 13
1113      */
1114     private  String perMillText;
1115 
1116     /**
1117      * String representation of percent sign, which may include
1118      * formatting characters, such as BiDi control characters.
1119      * The first non-format character of this string is the same as
1120      * {@code percent}.
1121      *
1122      * @serial
1123      * @since 13
1124      */
1125     private  String percentText;
1126 
1127     /**
1128      * String representation of minus sign, which may include
1129      * formatting characters, such as BiDi control characters.
1130      * The first non-format character of this string is the same as
1131      * {@code minusSign}.
1132      *
1133      * @serial
1134      * @since 13
1135      */
1136     private  String minusSignText;
1137 
1138     /**
1139      * The grouping separator used when formatting currency values.
1140      *
1141      * @serial
1142      * @since 15
1143      */
1144     private  char    monetaryGroupingSeparator;
1145 
1146     // currency; only the ISO code is serialized.
1147     private transient Currency currency;
1148     private transient volatile boolean currencyInitialized;
1149 
1150     /**
1151      * Cached hash code.
1152      */
1153     private transient volatile int hashCode;
1154 
1155     // Proclaim JDK 1.1 FCS compatibility
1156     @java.io.Serial
1157     static final long serialVersionUID = 5772796243397350300L;
1158 
1159     // The internal serial version which says which version was written
1160     // - 0 (default) for version up to JDK 1.1.5
1161     // - 1 for version from JDK 1.1.6, which includes two new fields:
1162     //     monetarySeparator and exponential.
1163     // - 2 for version from J2SE 1.4, which includes locale field.
1164     // - 3 for version from J2SE 1.6, which includes exponentialSeparator field.
1165     // - 4 for version from Java SE 13, which includes perMillText, percentText,
1166     //      and minusSignText field.
1167     // - 5 for version from Java SE 15, which includes monetaryGroupingSeparator.
1168     private static final int currentSerialVersion = 5;
1169 
1170     /**
1171      * Describes the version of {@code DecimalFormatSymbols} present on the stream.
1172      * Possible values are:
1173      * <ul>
1174      * <li><b>0</b> (or uninitialized): versions prior to JDK 1.1.6.
1175      *
1176      * <li><b>1</b>: Versions written by JDK 1.1.6 or later, which include
1177      *      two new fields: {@code monetarySeparator} and {@code exponential}.
1178      * <li><b>2</b>: Versions written by J2SE 1.4 or later, which include a
1179      *      new {@code locale} field.
1180      * <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a
1181      *      new {@code exponentialSeparator} field.
1182      * <li><b>4</b>: Versions written by Java SE 13 or later, which include
1183      *      new {@code perMillText}, {@code percentText}, and
1184      *      {@code minusSignText} field.
1185      * <li><b>5</b>: Versions written by Java SE 15 or later, which include
1186      *      new {@code monetaryGroupingSeparator} field.
1187      * * </ul>
1188      * When streaming out a {@code DecimalFormatSymbols}, the most recent format
1189      * (corresponding to the highest allowable {@code serialVersionOnStream})
1190      * is always written.
1191      *
1192      * @serial
1193      * @since  1.1.6
1194      */
1195     private int serialVersionOnStream = currentSerialVersion;
1196 }
1197