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