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