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      */
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 589     char getExponentialSymbol()
 590     {
 591         return exponential;
 592     }
 593 
 594     /**
 595      * Sets the character used to separate the mantissa from the exponent.
 596      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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
 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
 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     private volatile int hashCode;
 764     @Override
 765     public int hashCode() {
 766         if (hashCode == 0) {
 767             hashCode = Objects.hash(
 768                 zeroDigit,
 769                 groupingSeparator,
 770                 decimalSeparator,
 771                 percent,
 772                 percentText,
 773                 perMill,
 774                 perMillText,
 775                 digit,
 776                 minusSign,
 777                 minusSignText,
 778                 patternSeparator,
 779                 infinity,
 780                 NaN,
 781                 getCurrencySymbol(), // possible currency init occurs here
 782                 intlCurrencySymbol,
 783                 currency,
 784                 monetarySeparator,
 785                 monetaryGroupingSeparator,
 786                 exponentialSeparator,
 787                 locale);
 788         }
 789         return hashCode;
 790     }
 791 
 792     /**
 793      * Initializes the symbols from the FormatData resource bundle.
 794      */
 795     private void initialize( Locale locale ) {
 796         this.locale = locale;
 797 
 798         // check for region override
 799         Locale override = locale.getUnicodeLocaleType("nu") == null ?
 800             CalendarDataUtility.findRegionOverride(locale) :
 801             locale;
 802 
 803         // get resource bundle data
 804         LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, override);
 805         // Avoid potential recursions
 806         if (!(adapter instanceof ResourceBundleBasedAdapter)) {
 807             adapter = LocaleProviderAdapter.getResourceBundleBased();
 808         }
 809         Object[] data = adapter.getLocaleResources(override).getDecimalFormatSymbolsData();
 810         String[] numberElements = (String[]) data[0];
 811 
 812         decimalSeparator = numberElements[0].charAt(0);
 813         groupingSeparator = numberElements[1].charAt(0);
 814         patternSeparator = numberElements[2].charAt(0);
 815         percentText = numberElements[3];
 816         percent = findNonFormatChar(percentText, '%');
 817         zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc.
 818         digit = numberElements[5].charAt(0);
 819         minusSignText = numberElements[6];
 820         minusSign = findNonFormatChar(minusSignText, '-');
 821         exponential = numberElements[7].charAt(0);
 822         exponentialSeparator = numberElements[7]; //string representation new since 1.6
 823         perMillText = numberElements[8];
 824         perMill = findNonFormatChar(perMillText, '\u2030');
 825         infinity  = numberElements[9];
 826         NaN = numberElements[10];
 827 
 828         // monetary decimal/grouping separators may be missing in resource bundles
 829         monetarySeparator = numberElements.length < 12 || numberElements[11].isEmpty() ?
 830             decimalSeparator : numberElements[11].charAt(0);
 831         monetaryGroupingSeparator = numberElements.length < 13 || numberElements[12].isEmpty() ?
 832             groupingSeparator : numberElements[12].charAt(0);
 833 
 834         // maybe filled with previously cached values, or null.
 835         intlCurrencySymbol = (String) data[1];
 836         currencySymbol = (String) data[2];
 837     }
 838 
 839     /**
 840      * Obtains non-format single character from String
 841      */
 842     private char findNonFormatChar(String src, char defChar) {
 843         return (char)src.chars()
 844             .filter(c -> Character.getType(c) != Character.FORMAT)
 845             .findFirst()
 846             .orElse(defChar);
 847     }
 848 
 849     /**
 850      * Lazy initialization for currency related fields
 851      */
 852     private void initializeCurrency(Locale locale) {
 853         if (currencyInitialized) {
 854             return;
 855         }
 856 
 857         // Try to obtain the currency used in the locale's country.
 858         // Check for empty country string separately because it's a valid
 859         // country ID for Locale (and used for the C locale), but not a valid
 860         // ISO 3166 country code, and exceptions are expensive.
 861         if (!locale.getCountry().isEmpty()) {
 862             try {
 863                 currency = Currency.getInstance(locale);
 864             } catch (IllegalArgumentException e) {
 865                 // use default values below for compatibility
 866             }
 867         }
 868 
 869         if (currency != null) {
 870             // get resource bundle data
 871             LocaleProviderAdapter adapter =
 872                 LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
 873             // Avoid potential recursions
 874             if (!(adapter instanceof ResourceBundleBasedAdapter)) {
 875                 adapter = LocaleProviderAdapter.getResourceBundleBased();
 876             }
 877             Object[] data = adapter.getLocaleResources(locale).getDecimalFormatSymbolsData();
 878             intlCurrencySymbol = currency.getCurrencyCode();
 879             if (data[1] != null && data[1] == intlCurrencySymbol) {
 880                 currencySymbol = (String) data[2];
 881             } else {
 882                 currencySymbol = currency.getSymbol(locale);
 883                 data[1] = intlCurrencySymbol;
 884                 data[2] = currencySymbol;
 885             }
 886         } else {
 887             // default values
 888             intlCurrencySymbol = "XXX";
 889             try {
 890                 currency = Currency.getInstance(intlCurrencySymbol);
 891             } catch (IllegalArgumentException e) {
 892             }
 893             currencySymbol = "\u00A4";
 894         }
 895 
 896         currencyInitialized = true;
 897     }
 898 
 899     /**
 900      * Reads the default serializable fields, provides default values for objects
 901      * in older serial versions, and initializes non-serializable fields.
 902      * If {@code serialVersionOnStream}
 903      * is less than 1, initializes {@code monetarySeparator} to be
 904      * the same as {@code decimalSeparator} and {@code exponential}
 905      * to be 'E'.
 906      * If {@code serialVersionOnStream} is less than 2,
 907      * initializes {@code locale}to the root locale, and initializes
 908      * If {@code serialVersionOnStream} is less than 3, it initializes
 909      * {@code exponentialSeparator} using {@code exponential}.
 910      * If {@code serialVersionOnStream} is less than 4, it initializes
 911      * {@code perMillText}, {@code percentText}, and
 912      * {@code minusSignText} using {@code perMill}, {@code percent}, and
 913      * {@code minusSign} respectively.
 914      * If {@code serialVersionOnStream} is less than 5, it initializes
 915      * {@code monetaryGroupingSeparator} using {@code groupingSeparator}.
 916      * Sets {@code serialVersionOnStream} back to the maximum allowed value so that
 917      * default serialization will work properly if this object is streamed out again.
 918      * Initializes the currency from the intlCurrencySymbol field.
 919      *
 920      * @throws InvalidObjectException if {@code char} and {@code String}
 921      *      representations of either percent, per mille, and/or minus sign disagree.
 922      * @since  1.1.6
 923      */
 924     @java.io.Serial
 925     private void readObject(ObjectInputStream stream)
 926             throws IOException, ClassNotFoundException {
 927         stream.defaultReadObject();
 928         if (serialVersionOnStream < 1) {
 929             // Didn't have monetarySeparator or exponential field;
 930             // use defaults.
 931             monetarySeparator = decimalSeparator;
 932             exponential       = 'E';
 933         }
 934         if (serialVersionOnStream < 2) {
 935             // didn't have locale; use root locale
 936             locale = Locale.ROOT;
 937         }
 938         if (serialVersionOnStream < 3) {
 939             // didn't have exponentialSeparator. Create one using exponential
 940             exponentialSeparator = Character.toString(exponential);
 941         }
 942         if (serialVersionOnStream < 4) {
 943             // didn't have perMillText, percentText, and minusSignText.
 944             // Create one using corresponding char variations.
 945             perMillText = Character.toString(perMill);
 946             percentText = Character.toString(percent);
 947             minusSignText = Character.toString(minusSign);
 948         } else {
 949             // Check whether char and text fields agree
 950             if (findNonFormatChar(perMillText, '\uFFFF') != perMill ||
 951                 findNonFormatChar(percentText, '\uFFFF') != percent ||
 952                 findNonFormatChar(minusSignText, '\uFFFF') != minusSign) {
 953                 throw new InvalidObjectException(
 954                     "'char' and 'String' representations of either percent, " +
 955                     "per mille, and/or minus sign disagree.");
 956             }
 957         }
 958         if (serialVersionOnStream < 5) {
 959             // didn't have monetaryGroupingSeparator. Create one using groupingSeparator
 960             monetaryGroupingSeparator = groupingSeparator;
 961         }
 962 
 963         serialVersionOnStream = currentSerialVersion;
 964 
 965         if (intlCurrencySymbol != null) {
 966             try {
 967                  currency = Currency.getInstance(intlCurrencySymbol);
 968             } catch (IllegalArgumentException e) {
 969             }
 970             currencyInitialized = true;
 971         }
 972     }
 973 
 974     /**
 975      * Character used for zero.
 976      *
 977      * @serial
 978      * @see #getZeroDigit
 979      */
 980     private  char    zeroDigit;
 981 
 982     /**
 983      * Character used for grouping separator.
 984      *
 985      * @serial
 986      * @see #getGroupingSeparator
 987      */
 988     private  char    groupingSeparator;
 989 
 990     /**
 991      * Character used for decimal sign.
 992      *
 993      * @serial
 994      * @see #getDecimalSeparator
 995      */
 996     private  char    decimalSeparator;
 997 
 998     /**
 999      * Character used for per mille sign.
1000      *
1001      * @serial
1002      * @see #getPerMill
1003      */
1004     private  char    perMill;
1005 
1006     /**
1007      * Character used for percent sign.
1008      * @serial
1009      * @see #getPercent
1010      */
1011     private  char    percent;
1012 
1013     /**
1014      * Character used for a digit in a pattern.
1015      *
1016      * @serial
1017      * @see #getDigit
1018      */
1019     private  char    digit;
1020 
1021     /**
1022      * Character used to separate positive and negative subpatterns
1023      * in a pattern.
1024      *
1025      * @serial
1026      * @see #getPatternSeparator
1027      */
1028     private  char    patternSeparator;
1029 
1030     /**
1031      * String used to represent infinity.
1032      * @serial
1033      * @see #getInfinity
1034      */
1035     private  String  infinity;
1036 
1037     /**
1038      * String used to represent "not a number".
1039      * @serial
1040      * @see #getNaN
1041      */
1042     private  String  NaN;
1043 
1044     /**
1045      * Character used to represent minus sign.
1046      * @serial
1047      * @see #getMinusSign
1048      */
1049     private  char    minusSign;
1050 
1051     /**
1052      * String denoting the local currency, e.g. "$".
1053      * @serial
1054      * @see #getCurrencySymbol
1055      */
1056     private  String  currencySymbol;
1057 
1058     /**
1059      * ISO 4217 currency code denoting the local currency, e.g. "USD".
1060      * @serial
1061      * @see #getInternationalCurrencySymbol
1062      */
1063     private  String  intlCurrencySymbol;
1064 
1065     /**
1066      * The decimal separator used when formatting currency values.
1067      * @serial
1068      * @since  1.1.6
1069      * @see #getMonetaryDecimalSeparator
1070      */
1071     private  char    monetarySeparator; // Field new in JDK 1.1.6
1072 
1073     /**
1074      * The character used to distinguish the exponent in a number formatted
1075      * in exponential notation, e.g. 'E' for a number such as "1.23E45".
1076      * <p>
1077      * Note that the public API provides no way to set this field,
1078      * even though it is supported by the implementation and the stream format.
1079      * The intent is that this will be added to the API in the future.
1080      *
1081      * @serial
1082      * @since  1.1.6
1083      */
1084     private  char    exponential;       // Field new in JDK 1.1.6
1085 
1086     /**
1087      * The string used to separate the mantissa from the exponent.
1088      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
1089      * <p>
1090      * If both {@code exponential} and {@code exponentialSeparator}
1091      * exist, this {@code exponentialSeparator} has the precedence.
1092      *
1093      * @serial
1094      * @since 1.6
1095      */
1096     private  String    exponentialSeparator;       // Field new in JDK 1.6
1097 
1098     /**
1099      * The locale of these currency format symbols.
1100      *
1101      * @serial
1102      * @since 1.4
1103      */
1104     private Locale locale;
1105 
1106     /**
1107      * String representation of per mille sign, which may include
1108      * formatting characters, such as BiDi control characters.
1109      * The first non-format character of this string is the same as
1110      * {@code perMill}.
1111      *
1112      * @serial
1113      * @since 13
1114      */
1115     private  String perMillText;
1116 
1117     /**
1118      * String representation of percent sign, which may include
1119      * formatting characters, such as BiDi control characters.
1120      * The first non-format character of this string is the same as
1121      * {@code percent}.
1122      *
1123      * @serial
1124      * @since 13
1125      */
1126     private  String percentText;
1127 
1128     /**
1129      * String representation of minus sign, which may include
1130      * formatting characters, such as BiDi control characters.
1131      * The first non-format character of this string is the same as
1132      * {@code minusSign}.
1133      *
1134      * @serial
1135      * @since 13
1136      */
1137     private  String minusSignText;
1138 
1139     /**
1140      * The grouping separator used when formatting currency values.
1141      *
1142      * @serial
1143      * @since 15
1144      */
1145     private  char    monetaryGroupingSeparator;
1146 
1147     // currency; only the ISO code is serialized.
1148     private transient Currency currency;
1149     private transient volatile boolean currencyInitialized;
1150 
1151     // Proclaim JDK 1.1 FCS compatibility
1152     @java.io.Serial
1153     static final long serialVersionUID = 5772796243397350300L;
1154 
1155     // The internal serial version which says which version was written
1156     // - 0 (default) for version up to JDK 1.1.5
1157     // - 1 for version from JDK 1.1.6, which includes two new fields:
1158     //     monetarySeparator and exponential.
1159     // - 2 for version from J2SE 1.4, which includes locale field.
1160     // - 3 for version from J2SE 1.6, which includes exponentialSeparator field.
1161     // - 4 for version from Java SE 13, which includes perMillText, percentText,
1162     //      and minusSignText field.
1163     // - 5 for version from Java SE 15, which includes monetaryGroupingSeparator.
1164     private static final int currentSerialVersion = 5;
1165 
1166     /**
1167      * Describes the version of {@code DecimalFormatSymbols} present on the stream.
1168      * Possible values are:
1169      * <ul>
1170      * <li><b>0</b> (or uninitialized): versions prior to JDK 1.1.6.
1171      *
1172      * <li><b>1</b>: Versions written by JDK 1.1.6 or later, which include
1173      *      two new fields: {@code monetarySeparator} and {@code exponential}.
1174      * <li><b>2</b>: Versions written by J2SE 1.4 or later, which include a
1175      *      new {@code locale} field.
1176      * <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a
1177      *      new {@code exponentialSeparator} field.
1178      * <li><b>4</b>: Versions written by Java SE 13 or later, which include
1179      *      new {@code perMillText}, {@code percentText}, and
1180      *      {@code minusSignText} field.
1181      * <li><b>5</b>: Versions written by Java SE 15 or later, which include
1182      *      new {@code monetaryGroupingSeparator} field.
1183      * * </ul>
1184      * When streaming out a {@code DecimalFormatSymbols}, the most recent format
1185      * (corresponding to the highest allowable {@code serialVersionOnStream})
1186      * is always written.
1187      *
1188      * @serial
1189      * @since  1.1.6
1190      */
1191     private int serialVersionOnStream = currentSerialVersion;
1192 }