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