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