1 /*
   2  * Copyright (c) 1996, 2019, 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      * @exception 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      * @exception 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         this.zeroDigit = zeroDigit;
 206     }
 207 
 208     /**
 209      * Gets the character used for thousands separator. Different for French, etc.
 210      *
 211      * @return the grouping separator
 212      */
 213     public char getGroupingSeparator() {
 214         return groupingSeparator;
 215     }
 216 
 217     /**
 218      * Sets the character used for thousands separator. Different for French, etc.
 219      *
 220      * @param groupingSeparator the grouping separator
 221      */
 222     public void setGroupingSeparator(char groupingSeparator) {
 223         this.groupingSeparator = groupingSeparator;
 224     }
 225 
 226     /**
 227      * Gets the character used for decimal sign. Different for French, etc.
 228      *
 229      * @return the character used for decimal sign
 230      */
 231     public char getDecimalSeparator() {
 232         return decimalSeparator;
 233     }
 234 
 235     /**
 236      * Sets the character used for decimal sign. Different for French, etc.
 237      *
 238      * @param decimalSeparator the character used for decimal sign
 239      */
 240     public void setDecimalSeparator(char decimalSeparator) {
 241         this.decimalSeparator = decimalSeparator;
 242     }
 243 
 244     /**
 245      * Gets the character used for per mille sign. Different for Arabic, etc.
 246      *
 247      * @return the character used for per mille sign
 248      */
 249     public char getPerMill() {
 250         return perMill;
 251     }
 252 
 253     /**
 254      * Sets the character used for per mille sign. Different for Arabic, etc.
 255      *
 256      * @param perMill the character used for per mille sign
 257      */
 258     public void setPerMill(char perMill) {
 259         this.perMill = perMill;
 260         this.perMillText = Character.toString(perMill);
 261     }
 262 
 263     /**
 264      * Gets the string used for per mille sign. Different for Arabic, etc.
 265      *
 266      * @return the string used for per mille sign
 267      * @since 13
 268      */
 269     String getPerMillText() {
 270         return perMillText;
 271     }
 272 
 273     /**
 274      * Sets the string used for per mille sign. Different for Arabic, etc.
 275      *
 276      * Setting the {@code perMillText} affects the return value of
 277      * {@link #getPerMill()}, in which the first non-format character of
 278      * {@code perMillText} is returned.
 279      *
 280      * @param perMillText the string used for per mille sign
 281      * @throws NullPointerException if {@code perMillText} is null
 282      * @throws IllegalArgumentException if {@code perMillText} is an empty string
 283      * @see #getPerMill()
 284      * @see #getPerMillText()
 285      * @since 13
 286      */
 287     void setPerMillText(String perMillText) {
 288         Objects.requireNonNull(perMillText);
 289         if (perMillText.isEmpty()) {
 290             throw new IllegalArgumentException("Empty argument string");
 291         }
 292 
 293         this.perMillText = perMillText;
 294         this.perMill = findNonFormatChar(perMillText, '\u2030');
 295     }
 296 
 297     /**
 298      * Gets the character used for percent sign. Different for Arabic, etc.
 299      *
 300      * @return the character used for percent sign
 301      */
 302     public char getPercent() {
 303         return percent;
 304     }
 305 
 306     /**
 307      * Sets the character used for percent sign. Different for Arabic, etc.
 308      *
 309      * @param percent the character used for percent sign
 310      */
 311     public void setPercent(char percent) {
 312         this.percent = percent;
 313         this.percentText = Character.toString(percent);
 314     }
 315 
 316     /**
 317      * Gets the string used for percent sign. Different for Arabic, etc.
 318      *
 319      * @return the string used for percent sign
 320      * @since 13
 321      */
 322     String getPercentText() {
 323         return percentText;
 324     }
 325 
 326     /**
 327      * Sets the string used for percent sign. Different for Arabic, etc.
 328      *
 329      * Setting the {@code percentText} affects the return value of
 330      * {@link #getPercent()}, in which the first non-format character of
 331      * {@code percentText} is returned.
 332      *
 333      * @param percentText the string used for percent sign
 334      * @throws NullPointerException if {@code percentText} is null
 335      * @throws IllegalArgumentException if {@code percentText} is an empty string
 336      * @see #getPercent()
 337      * @see #getPercentText()
 338      * @since 13
 339      */
 340     void setPercentText(String percentText) {
 341         Objects.requireNonNull(percentText);
 342         if (percentText.isEmpty()) {
 343             throw new IllegalArgumentException("Empty argument string");
 344         }
 345 
 346         this.percentText = percentText;
 347         this.percent = findNonFormatChar(percentText, '%');
 348     }
 349 
 350     /**
 351      * Gets the character used for a digit in a pattern.
 352      *
 353      * @return the character used for a digit in a pattern
 354      */
 355     public char getDigit() {
 356         return digit;
 357     }
 358 
 359     /**
 360      * Sets the character used for a digit in a pattern.
 361      *
 362      * @param digit the character used for a digit in a pattern
 363      */
 364     public void setDigit(char digit) {
 365         this.digit = digit;
 366     }
 367 
 368     /**
 369      * Gets the character used to separate positive and negative subpatterns
 370      * in a pattern.
 371      *
 372      * @return the pattern separator
 373      */
 374     public char getPatternSeparator() {
 375         return patternSeparator;
 376     }
 377 
 378     /**
 379      * Sets the character used to separate positive and negative subpatterns
 380      * in a pattern.
 381      *
 382      * @param patternSeparator the pattern separator
 383      */
 384     public void setPatternSeparator(char patternSeparator) {
 385         this.patternSeparator = patternSeparator;
 386     }
 387 
 388     /**
 389      * Gets the string used to represent infinity. Almost always left
 390      * unchanged.
 391      *
 392      * @return the string representing infinity
 393      */
 394     public String getInfinity() {
 395         return infinity;
 396     }
 397 
 398     /**
 399      * Sets the string used to represent infinity. Almost always left
 400      * unchanged.
 401      *
 402      * @param infinity the string representing infinity
 403      */
 404     public void setInfinity(String infinity) {
 405         this.infinity = infinity;
 406     }
 407 
 408     /**
 409      * Gets the string used to represent "not a number". Almost always left
 410      * unchanged.
 411      *
 412      * @return the string representing "not a number"
 413      */
 414     public String getNaN() {
 415         return NaN;
 416     }
 417 
 418     /**
 419      * Sets the string used to represent "not a number". Almost always left
 420      * unchanged.
 421      *
 422      * @param NaN the string representing "not a number"
 423      */
 424     public void setNaN(String NaN) {
 425         this.NaN = NaN;
 426     }
 427 
 428     /**
 429      * Gets the character used to represent minus sign. If no explicit
 430      * negative format is specified, one is formed by prefixing
 431      * minusSign to the positive format.
 432      *
 433      * @return the character representing minus sign
 434      */
 435     public char getMinusSign() {
 436         return minusSign;
 437     }
 438 
 439     /**
 440      * Sets the character used to represent minus sign. If no explicit
 441      * negative format is specified, one is formed by prefixing
 442      * minusSign to the positive format.
 443      *
 444      * @param minusSign the character representing minus sign
 445      */
 446     public void setMinusSign(char minusSign) {
 447         this.minusSign = minusSign;
 448         this.minusSignText = Character.toString(minusSign);
 449     }
 450 
 451     /**
 452      * Gets the string used to represent minus sign. If no explicit
 453      * negative format is specified, one is formed by prefixing
 454      * minusSignText to the positive format.
 455      *
 456      * @return the string representing minus sign
 457      * @since 13
 458      */
 459     String getMinusSignText() {
 460         return minusSignText;
 461     }
 462 
 463     /**
 464      * Sets the string used to represent minus sign. If no explicit
 465      * negative format is specified, one is formed by prefixing
 466      * minusSignText to the positive format.
 467      *
 468      * Setting the {@code minusSignText} affects the return value of
 469      * {@link #getMinusSign()}, in which the first non-format character of
 470      * {@code minusSignText} is returned.
 471      *
 472      * @param minusSignText the character representing minus sign
 473      * @throws NullPointerException if {@code minusSignText} is null
 474      * @throws IllegalArgumentException if {@code minusSignText} is an
 475      *  empty string
 476      * @see #getMinusSign()
 477      * @see #getMinusSignText()
 478      * @since 13
 479      */
 480     void setMinusSignText(String minusSignText) {
 481         Objects.requireNonNull(minusSignText);
 482         if (minusSignText.isEmpty()) {
 483             throw new IllegalArgumentException("Empty argument string");
 484         }
 485 
 486         this.minusSignText = minusSignText;
 487         this.minusSign = findNonFormatChar(minusSignText, '-');
 488     }
 489 
 490     /**
 491      * Returns the currency symbol for the currency of these
 492      * DecimalFormatSymbols in their locale.
 493      *
 494      * @return the currency symbol
 495      * @since 1.2
 496      */
 497     public String getCurrencySymbol()
 498     {
 499         initializeCurrency(locale);
 500         return currencySymbol;
 501     }
 502 
 503     /**
 504      * Sets the currency symbol for the currency of these
 505      * DecimalFormatSymbols in their locale.
 506      *
 507      * @param currency the currency symbol
 508      * @since 1.2
 509      */
 510     public void setCurrencySymbol(String currency)
 511     {
 512         initializeCurrency(locale);
 513         currencySymbol = currency;
 514     }
 515 
 516     /**
 517      * Returns the ISO 4217 currency code of the currency of these
 518      * DecimalFormatSymbols.
 519      *
 520      * @return the currency code
 521      * @since 1.2
 522      */
 523     public String getInternationalCurrencySymbol()
 524     {
 525         initializeCurrency(locale);
 526         return intlCurrencySymbol;
 527     }
 528 
 529     /**
 530      * Sets the ISO 4217 currency code of the currency of these
 531      * DecimalFormatSymbols.
 532      * If the currency code is valid (as defined by
 533      * {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}),
 534      * this also sets the currency attribute to the corresponding Currency
 535      * instance and the currency symbol attribute to the currency's symbol
 536      * in the DecimalFormatSymbols' locale. If the currency code is not valid,
 537      * then the currency attribute is set to null and the currency symbol
 538      * attribute is not modified.
 539      *
 540      * @param currencyCode the currency code
 541      * @see #setCurrency
 542      * @see #setCurrencySymbol
 543      * @since 1.2
 544      */
 545     public void setInternationalCurrencySymbol(String currencyCode)
 546     {
 547         initializeCurrency(locale);
 548         intlCurrencySymbol = currencyCode;
 549         currency = null;
 550         if (currencyCode != null) {
 551             try {
 552                 currency = Currency.getInstance(currencyCode);
 553                 currencySymbol = currency.getSymbol();
 554             } catch (IllegalArgumentException e) {
 555             }
 556         }
 557     }
 558 
 559     /**
 560      * Gets the currency of these DecimalFormatSymbols. May be null if the
 561      * currency symbol attribute was previously set to a value that's not
 562      * a valid ISO 4217 currency code.
 563      *
 564      * @return the currency used, or null
 565      * @since 1.4
 566      */
 567     public Currency getCurrency() {
 568         initializeCurrency(locale);
 569         return currency;
 570     }
 571 
 572     /**
 573      * Sets the currency of these DecimalFormatSymbols.
 574      * This also sets the currency symbol attribute to the currency's symbol
 575      * in the DecimalFormatSymbols' locale, and the international currency
 576      * symbol attribute to the currency's ISO 4217 currency code.
 577      *
 578      * @param currency the new currency to be used
 579      * @exception NullPointerException if {@code currency} is null
 580      * @since 1.4
 581      * @see #setCurrencySymbol
 582      * @see #setInternationalCurrencySymbol
 583      */
 584     public void setCurrency(Currency currency) {
 585         if (currency == null) {
 586             throw new NullPointerException();
 587         }
 588         initializeCurrency(locale);
 589         this.currency = currency;
 590         intlCurrencySymbol = currency.getCurrencyCode();
 591         currencySymbol = currency.getSymbol(locale);
 592     }
 593 
 594 
 595     /**
 596      * Returns the monetary decimal separator.
 597      *
 598      * @return the monetary decimal separator
 599      * @since 1.2
 600      */
 601     public char getMonetaryDecimalSeparator()
 602     {
 603         return monetarySeparator;
 604     }
 605 
 606     /**
 607      * Sets the monetary decimal separator.
 608      *
 609      * @param sep the monetary decimal separator
 610      * @since 1.2
 611      */
 612     public void setMonetaryDecimalSeparator(char sep)
 613     {
 614         monetarySeparator = sep;
 615     }
 616 
 617     //------------------------------------------------------------
 618     // BEGIN   Package Private methods ... to be made public later
 619     //------------------------------------------------------------
 620 
 621     /**
 622      * Returns the character used to separate the mantissa from the exponent.
 623      */
 624     char getExponentialSymbol()
 625     {
 626         return exponential;
 627     }
 628 
 629     /**
 630      * Returns the string used to separate the mantissa from the exponent.
 631      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
 632      *
 633      * @return the exponent separator string
 634      * @see #setExponentSeparator(java.lang.String)
 635      * @since 1.6
 636      */
 637     public String getExponentSeparator()
 638     {
 639         return exponentialSeparator;
 640     }
 641 
 642     /**
 643      * Sets the character used to separate the mantissa from the exponent.
 644      */
 645     void setExponentialSymbol(char exp)
 646     {
 647         exponential = exp;
 648     }
 649 
 650     /**
 651      * Sets the string used to separate the mantissa from the exponent.
 652      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
 653      *
 654      * @param exp the exponent separator string
 655      * @exception NullPointerException if {@code exp} is null
 656      * @see #getExponentSeparator()
 657      * @since 1.6
 658      */
 659     public void setExponentSeparator(String exp)
 660     {
 661         if (exp == null) {
 662             throw new NullPointerException();
 663         }
 664         exponentialSeparator = exp;
 665     }
 666 
 667 
 668     //------------------------------------------------------------
 669     // END     Package Private methods ... to be made public later
 670     //------------------------------------------------------------
 671 
 672     /**
 673      * Standard override.
 674      */
 675     @Override
 676     public Object clone() {
 677         try {
 678             return (DecimalFormatSymbols)super.clone();
 679             // other fields are bit-copied
 680         } catch (CloneNotSupportedException e) {
 681             throw new InternalError(e);
 682         }
 683     }
 684 
 685     /**
 686      * Override equals.
 687      */
 688     @Override
 689     public boolean equals(Object obj) {
 690         if (obj == null) return false;
 691         if (this == obj) return true;
 692         if (getClass() != obj.getClass()) return false;
 693         DecimalFormatSymbols other = (DecimalFormatSymbols) obj;
 694         return (zeroDigit == other.zeroDigit &&
 695         groupingSeparator == other.groupingSeparator &&
 696         decimalSeparator == other.decimalSeparator &&
 697         percent == other.percent &&
 698         percentText.equals(other.percentText) &&
 699         perMill == other.perMill &&
 700         perMillText.equals(other.perMillText) &&
 701         digit == other.digit &&
 702         minusSign == other.minusSign &&
 703         minusSignText.equals(other.minusSignText) &&
 704         patternSeparator == other.patternSeparator &&
 705         infinity.equals(other.infinity) &&
 706         NaN.equals(other.NaN) &&
 707         getCurrencySymbol().equals(other.getCurrencySymbol()) && // possible currency init occurs here
 708         intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
 709         currency == other.currency &&
 710         monetarySeparator == other.monetarySeparator &&
 711         exponentialSeparator.equals(other.exponentialSeparator) &&
 712         locale.equals(other.locale));
 713     }
 714 
 715     /**
 716      * Override hashCode.
 717      */
 718     @Override
 719     public int hashCode() {
 720             int result = zeroDigit;
 721             result = result * 37 + groupingSeparator;
 722             result = result * 37 + decimalSeparator;
 723             return result;
 724     }
 725 
 726     /**
 727      * Initializes the symbols from the FormatData resource bundle.
 728      */
 729     private void initialize( Locale locale ) {
 730         this.locale = locale;
 731 
 732         // check for region override
 733         Locale override = locale.getUnicodeLocaleType("nu") == null ?
 734             CalendarDataUtility.findRegionOverride(locale) :
 735             locale;
 736 
 737         // get resource bundle data
 738         LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, override);
 739         // Avoid potential recursions
 740         if (!(adapter instanceof ResourceBundleBasedAdapter)) {
 741             adapter = LocaleProviderAdapter.getResourceBundleBased();
 742         }
 743         Object[] data = adapter.getLocaleResources(override).getDecimalFormatSymbolsData();
 744         String[] numberElements = (String[]) data[0];
 745 
 746         decimalSeparator = numberElements[0].charAt(0);
 747         groupingSeparator = numberElements[1].charAt(0);
 748         patternSeparator = numberElements[2].charAt(0);
 749         percentText = numberElements[3];
 750         percent = findNonFormatChar(percentText, '%');
 751         zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc.
 752         digit = numberElements[5].charAt(0);
 753         minusSignText = numberElements[6];
 754         minusSign = findNonFormatChar(minusSignText, '-');
 755         exponential = numberElements[7].charAt(0);
 756         exponentialSeparator = numberElements[7]; //string representation new since 1.6
 757         perMillText = numberElements[8];
 758         perMill = findNonFormatChar(perMillText, '\u2030');
 759         infinity  = numberElements[9];
 760         NaN = numberElements[10];
 761 
 762         // maybe filled with previously cached values, or null.
 763         intlCurrencySymbol = (String) data[1];
 764         currencySymbol = (String) data[2];
 765 
 766         // Currently the monetary decimal separator is the same as the
 767         // standard decimal separator for all locales that we support.
 768         // If that changes, add a new entry to NumberElements.
 769         monetarySeparator = decimalSeparator;
 770     }
 771 
 772     /**
 773      * Obtains non-format single character from String
 774      */
 775     private char findNonFormatChar(String src, char defChar) {
 776         return (char)src.chars()
 777             .filter(c -> Character.getType(c) != Character.FORMAT)
 778             .findFirst()
 779             .orElse(defChar);
 780     }
 781 
 782     /**
 783      * Lazy initialization for currency related fields
 784      */
 785     private void initializeCurrency(Locale locale) {
 786         if (currencyInitialized) {
 787             return;
 788         }
 789 
 790         // Try to obtain the currency used in the locale's country.
 791         // Check for empty country string separately because it's a valid
 792         // country ID for Locale (and used for the C locale), but not a valid
 793         // ISO 3166 country code, and exceptions are expensive.
 794         if (!locale.getCountry().isEmpty()) {
 795             try {
 796                 currency = Currency.getInstance(locale);
 797             } catch (IllegalArgumentException e) {
 798                 // use default values below for compatibility
 799             }
 800         }
 801 
 802         if (currency != null) {
 803             // get resource bundle data
 804             LocaleProviderAdapter adapter =
 805                 LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
 806             // Avoid potential recursions
 807             if (!(adapter instanceof ResourceBundleBasedAdapter)) {
 808                 adapter = LocaleProviderAdapter.getResourceBundleBased();
 809             }
 810             Object[] data = adapter.getLocaleResources(locale).getDecimalFormatSymbolsData();
 811             intlCurrencySymbol = currency.getCurrencyCode();
 812             if (data[1] != null && data[1] == intlCurrencySymbol) {
 813                 currencySymbol = (String) data[2];
 814             } else {
 815                 currencySymbol = currency.getSymbol(locale);
 816                 data[1] = intlCurrencySymbol;
 817                 data[2] = currencySymbol;
 818             }
 819         } else {
 820             // default values
 821             intlCurrencySymbol = "XXX";
 822             try {
 823                 currency = Currency.getInstance(intlCurrencySymbol);
 824             } catch (IllegalArgumentException e) {
 825             }
 826             currencySymbol = "\u00A4";
 827         }
 828 
 829         currencyInitialized = true;
 830     }
 831 
 832     /**
 833      * Reads the default serializable fields, provides default values for objects
 834      * in older serial versions, and initializes non-serializable fields.
 835      * If {@code serialVersionOnStream}
 836      * is less than 1, initializes {@code monetarySeparator} to be
 837      * the same as {@code decimalSeparator} and {@code exponential}
 838      * to be 'E'.
 839      * If {@code serialVersionOnStream} is less than 2,
 840      * initializes {@code locale}to the root locale, and initializes
 841      * If {@code serialVersionOnStream} is less than 3, it initializes
 842      * {@code exponentialSeparator} using {@code exponential}.
 843      * If {@code serialVersionOnStream} is less than 4, it initializes
 844      * {@code perMillText}, {@code percentText}, and
 845      * {@code minusSignText} using {@code perMill}, {@code percent}, and
 846      * {@code minusSign} respectively.
 847      * Sets {@code serialVersionOnStream} back to the maximum allowed value so that
 848      * default serialization will work properly if this object is streamed out again.
 849      * Initializes the currency from the intlCurrencySymbol field.
 850      *
 851      * @throws InvalidObjectException if {@code char} and {@code String}
 852      *      representations of either percent, per mille, and/or minus sign disagree.
 853      * @since  1.1.6
 854      */
 855     @java.io.Serial
 856     private void readObject(ObjectInputStream stream)
 857             throws IOException, ClassNotFoundException {
 858         stream.defaultReadObject();
 859         if (serialVersionOnStream < 1) {
 860             // Didn't have monetarySeparator or exponential field;
 861             // use defaults.
 862             monetarySeparator = decimalSeparator;
 863             exponential       = 'E';
 864         }
 865         if (serialVersionOnStream < 2) {
 866             // didn't have locale; use root locale
 867             locale = Locale.ROOT;
 868         }
 869         if (serialVersionOnStream < 3) {
 870             // didn't have exponentialSeparator. Create one using exponential
 871             exponentialSeparator = Character.toString(exponential);
 872         }
 873         if (serialVersionOnStream < 4) {
 874             // didn't have perMillText, percentText, and minusSignText.
 875             // Create one using corresponding char variations.
 876             perMillText = Character.toString(perMill);
 877             percentText = Character.toString(percent);
 878             minusSignText = Character.toString(minusSign);
 879         } else {
 880             // Check whether char and text fields agree
 881             if (findNonFormatChar(perMillText, '\uFFFF') != perMill ||
 882                 findNonFormatChar(percentText, '\uFFFF') != percent ||
 883                 findNonFormatChar(minusSignText, '\uFFFF') != minusSign) {
 884                 throw new InvalidObjectException(
 885                     "'char' and 'String' representations of either percent, " +
 886                     "per mille, and/or minus sign disagree.");
 887             }
 888         }
 889 
 890         serialVersionOnStream = currentSerialVersion;
 891 
 892         if (intlCurrencySymbol != null) {
 893             try {
 894                  currency = Currency.getInstance(intlCurrencySymbol);
 895             } catch (IllegalArgumentException e) {
 896             }
 897             currencyInitialized = true;
 898         }
 899     }
 900 
 901     /**
 902      * Character used for zero.
 903      *
 904      * @serial
 905      * @see #getZeroDigit
 906      */
 907     private  char    zeroDigit;
 908 
 909     /**
 910      * Character used for thousands separator.
 911      *
 912      * @serial
 913      * @see #getGroupingSeparator
 914      */
 915     private  char    groupingSeparator;
 916 
 917     /**
 918      * Character used for decimal sign.
 919      *
 920      * @serial
 921      * @see #getDecimalSeparator
 922      */
 923     private  char    decimalSeparator;
 924 
 925     /**
 926      * Character used for per mille sign.
 927      *
 928      * @serial
 929      * @see #getPerMill
 930      */
 931     private  char    perMill;
 932 
 933     /**
 934      * Character used for percent sign.
 935      * @serial
 936      * @see #getPercent
 937      */
 938     private  char    percent;
 939 
 940     /**
 941      * Character used for a digit in a pattern.
 942      *
 943      * @serial
 944      * @see #getDigit
 945      */
 946     private  char    digit;
 947 
 948     /**
 949      * Character used to separate positive and negative subpatterns
 950      * in a pattern.
 951      *
 952      * @serial
 953      * @see #getPatternSeparator
 954      */
 955     private  char    patternSeparator;
 956 
 957     /**
 958      * String used to represent infinity.
 959      * @serial
 960      * @see #getInfinity
 961      */
 962     private  String  infinity;
 963 
 964     /**
 965      * String used to represent "not a number".
 966      * @serial
 967      * @see #getNaN
 968      */
 969     private  String  NaN;
 970 
 971     /**
 972      * Character used to represent minus sign.
 973      * @serial
 974      * @see #getMinusSign
 975      */
 976     private  char    minusSign;
 977 
 978     /**
 979      * String denoting the local currency, e.g. "$".
 980      * @serial
 981      * @see #getCurrencySymbol
 982      */
 983     private  String  currencySymbol;
 984 
 985     /**
 986      * ISO 4217 currency code denoting the local currency, e.g. "USD".
 987      * @serial
 988      * @see #getInternationalCurrencySymbol
 989      */
 990     private  String  intlCurrencySymbol;
 991 
 992     /**
 993      * The decimal separator used when formatting currency values.
 994      * @serial
 995      * @since  1.1.6
 996      * @see #getMonetaryDecimalSeparator
 997      */
 998     private  char    monetarySeparator; // Field new in JDK 1.1.6
 999 
1000     /**
1001      * The character used to distinguish the exponent in a number formatted
1002      * in exponential notation, e.g. 'E' for a number such as "1.23E45".
1003      * <p>
1004      * Note that the public API provides no way to set this field,
1005      * even though it is supported by the implementation and the stream format.
1006      * The intent is that this will be added to the API in the future.
1007      *
1008      * @serial
1009      * @since  1.1.6
1010      */
1011     private  char    exponential;       // Field new in JDK 1.1.6
1012 
1013     /**
1014      * The string used to separate the mantissa from the exponent.
1015      * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
1016      * <p>
1017      * If both {@code exponential} and {@code exponentialSeparator}
1018      * exist, this {@code exponentialSeparator} has the precedence.
1019      *
1020      * @serial
1021      * @since 1.6
1022      */
1023     private  String    exponentialSeparator;       // Field new in JDK 1.6
1024 
1025     /**
1026      * The locale of these currency format symbols.
1027      *
1028      * @serial
1029      * @since 1.4
1030      */
1031     private Locale locale;
1032 
1033     /**
1034      * String representation of per mille sign, which may include
1035      * formatting characters, such as BiDi control characters.
1036      * The first non-format character of this string is the same as
1037      * {@code perMill}.
1038      *
1039      * @serial
1040      * @since 13
1041      */
1042     private  String perMillText;
1043 
1044     /**
1045      * String representation of percent sign, which may include
1046      * formatting characters, such as BiDi control characters.
1047      * The first non-format character of this string is the same as
1048      * {@code percent}.
1049      *
1050      * @serial
1051      * @since 13
1052      */
1053     private  String percentText;
1054 
1055     /**
1056      * String representation of minus sign, which may include
1057      * formatting characters, such as BiDi control characters.
1058      * The first non-format character of this string is the same as
1059      * {@code minusSign}.
1060      *
1061      * @serial
1062      * @since 13
1063      */
1064     private  String minusSignText;
1065 
1066     // currency; only the ISO code is serialized.
1067     private transient Currency currency;
1068     private transient volatile boolean currencyInitialized;
1069 
1070     // Proclaim JDK 1.1 FCS compatibility
1071     @java.io.Serial
1072     static final long serialVersionUID = 5772796243397350300L;
1073 
1074     // The internal serial version which says which version was written
1075     // - 0 (default) for version up to JDK 1.1.5
1076     // - 1 for version from JDK 1.1.6, which includes two new fields:
1077     //     monetarySeparator and exponential.
1078     // - 2 for version from J2SE 1.4, which includes locale field.
1079     // - 3 for version from J2SE 1.6, which includes exponentialSeparator field.
1080     // - 4 for version from Java SE 13, which includes perMillText, percentText,
1081     //      and minusSignText field.
1082     private static final int currentSerialVersion = 4;
1083 
1084     /**
1085      * Describes the version of {@code DecimalFormatSymbols} present on the stream.
1086      * Possible values are:
1087      * <ul>
1088      * <li><b>0</b> (or uninitialized): versions prior to JDK 1.1.6.
1089      *
1090      * <li><b>1</b>: Versions written by JDK 1.1.6 or later, which include
1091      *      two new fields: {@code monetarySeparator} and {@code exponential}.
1092      * <li><b>2</b>: Versions written by J2SE 1.4 or later, which include a
1093      *      new {@code locale} field.
1094      * <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a
1095      *      new {@code exponentialSeparator} field.
1096      * <li><b>4</b>: Versions written by Java SE 13 or later, which include
1097      *      new {@code perMillText}, {@code percentText}, and
1098      *      {@code minusSignText} field.
1099      * </ul>
1100      * When streaming out a {@code DecimalFormatSymbols}, the most recent format
1101      * (corresponding to the highest allowable {@code serialVersionOnStream})
1102      * is always written.
1103      *
1104      * @serial
1105      * @since  1.1.6
1106      */
1107     private int serialVersionOnStream = currentSerialVersion;
1108 }