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