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