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