1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.text; 19 20import java.io.IOException; 21import java.io.ObjectInputStream; 22import java.io.ObjectOutputStream; 23import java.io.ObjectStreamField; 24import java.io.Serializable; 25import java.util.Currency; 26import java.util.Locale; 27import libcore.icu.ICU; 28import libcore.icu.LocaleData; 29 30/** 31 * Encapsulates the set of symbols (such as the decimal separator, the grouping 32 * separator, and so on) needed by {@code DecimalFormat} to format numbers. 33 * {@code DecimalFormat} internally creates an instance of 34 * {@code DecimalFormatSymbols} from its locale data. If you need to change any 35 * of these symbols, you can get the {@code DecimalFormatSymbols} object from 36 * your {@code DecimalFormat} and modify it. 37 * 38 * @see java.util.Locale 39 * @see DecimalFormat 40 */ 41public class DecimalFormatSymbols implements Cloneable, Serializable { 42 43 private static final long serialVersionUID = 5772796243397350300L; 44 45 private char zeroDigit; 46 private char digit; 47 private char decimalSeparator; 48 private char groupingSeparator; 49 private char patternSeparator; 50 private String percent; 51 private char perMill; 52 private char monetarySeparator; 53 private String minusSign; 54 private String infinity, NaN, currencySymbol, intlCurrencySymbol; 55 56 private transient Currency currency; 57 private transient Locale locale; 58 private transient String exponentSeparator; 59 60 /** 61 * Constructs a new {@code DecimalFormatSymbols} containing the symbols for 62 * the user's default locale. 63 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 64 * Best practice is to create a {@code DecimalFormat} 65 * and then to get the {@code DecimalFormatSymbols} from that object by 66 * calling {@link DecimalFormat#getDecimalFormatSymbols()}. 67 */ 68 public DecimalFormatSymbols() { 69 this(Locale.getDefault()); 70 } 71 72 /** 73 * Constructs a new DecimalFormatSymbols containing the symbols for the 74 * specified Locale. 75 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 76 * Best practice is to create a {@code DecimalFormat} 77 * and then to get the {@code DecimalFormatSymbols} from that object by 78 * calling {@link DecimalFormat#getDecimalFormatSymbols()}. 79 * 80 * @param locale 81 * the locale. 82 */ 83 public DecimalFormatSymbols(Locale locale) { 84 if (locale == null) { 85 throw new NullPointerException("locale == null"); 86 } 87 88 locale = LocaleData.mapInvalidAndNullLocales(locale); 89 LocaleData localeData = LocaleData.get(locale); 90 this.zeroDigit = localeData.zeroDigit; 91 this.digit = '#'; 92 this.decimalSeparator = localeData.decimalSeparator; 93 this.groupingSeparator = localeData.groupingSeparator; 94 this.patternSeparator = localeData.patternSeparator; 95 this.percent = localeData.percent; 96 this.perMill = localeData.perMill; 97 this.monetarySeparator = localeData.monetarySeparator; 98 this.minusSign = localeData.minusSign; 99 this.infinity = localeData.infinity; 100 this.NaN = localeData.NaN; 101 this.exponentSeparator = localeData.exponentSeparator; 102 this.locale = locale; 103 try { 104 currency = Currency.getInstance(locale); 105 currencySymbol = currency.getSymbol(locale); 106 intlCurrencySymbol = currency.getCurrencyCode(); 107 } catch (IllegalArgumentException e) { 108 currency = Currency.getInstance("XXX"); 109 currencySymbol = localeData.currencySymbol; 110 intlCurrencySymbol = localeData.internationalCurrencySymbol; 111 } 112 } 113 114 /** 115 * Returns a new {@code DecimalFormatSymbols} instance for the user's default locale. 116 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 117 * 118 * @return an instance of {@code DecimalFormatSymbols} 119 * @since 1.6 120 */ 121 public static DecimalFormatSymbols getInstance() { 122 return getInstance(Locale.getDefault()); 123 } 124 125 /** 126 * Returns a new {@code DecimalFormatSymbols} for the given locale. 127 * 128 * @param locale the locale 129 * @return an instance of {@code DecimalFormatSymbols} 130 * @throws NullPointerException if {@code locale == null} 131 * @since 1.6 132 */ 133 public static DecimalFormatSymbols getInstance(Locale locale) { 134 if (locale == null) { 135 throw new NullPointerException("locale == null"); 136 } 137 return new DecimalFormatSymbols(locale); 138 } 139 140 /** 141 * Returns an array of locales for which custom {@code DecimalFormatSymbols} instances 142 * are available. 143 * <p>Note that Android does not support user-supplied locale service providers. 144 * @since 1.6 145 */ 146 public static Locale[] getAvailableLocales() { 147 return ICU.getAvailableDecimalFormatSymbolsLocales(); 148 } 149 150 @Override 151 public Object clone() { 152 try { 153 return super.clone(); 154 } catch (CloneNotSupportedException e) { 155 throw new AssertionError(e); 156 } 157 } 158 159 /** 160 * Compares the specified object to this {@code DecimalFormatSymbols} and 161 * indicates if they are equal. In order to be equal, {@code object} must be 162 * an instance of {@code DecimalFormatSymbols} and contain the same symbols. 163 * 164 * @param object 165 * the object to compare with this object. 166 * @return {@code true} if the specified object is equal to this 167 * {@code DecimalFormatSymbols}; {@code false} otherwise. 168 * @see #hashCode 169 */ 170 @Override 171 public boolean equals(Object object) { 172 if (this == object) { 173 return true; 174 } 175 if (!(object instanceof DecimalFormatSymbols)) { 176 return false; 177 } 178 DecimalFormatSymbols obj = (DecimalFormatSymbols) object; 179 return currency.equals(obj.currency) && 180 currencySymbol.equals(obj.currencySymbol) && 181 decimalSeparator == obj.decimalSeparator && 182 digit == obj.digit && 183 exponentSeparator.equals(obj.exponentSeparator) && 184 groupingSeparator == obj.groupingSeparator && 185 infinity.equals(obj.infinity) && 186 intlCurrencySymbol.equals(obj.intlCurrencySymbol) && 187 minusSign.equals(obj.minusSign) && 188 monetarySeparator == obj.monetarySeparator && 189 NaN.equals(obj.NaN) && 190 patternSeparator == obj.patternSeparator && 191 perMill == obj.perMill && 192 percent.equals(obj.percent) && 193 zeroDigit == obj.zeroDigit; 194 } 195 196 @Override 197 public String toString() { 198 return getClass().getName() + 199 "[currency=" + currency + 200 ",currencySymbol=" + currencySymbol + 201 ",decimalSeparator=" + decimalSeparator + 202 ",digit=" + digit + 203 ",exponentSeparator=" + exponentSeparator + 204 ",groupingSeparator=" + groupingSeparator + 205 ",infinity=" + infinity + 206 ",intlCurrencySymbol=" + intlCurrencySymbol + 207 ",minusSign=" + minusSign + 208 ",monetarySeparator=" + monetarySeparator + 209 ",NaN=" + NaN + 210 ",patternSeparator=" + patternSeparator + 211 ",perMill=" + perMill + 212 ",percent=" + percent + 213 ",zeroDigit=" + zeroDigit + 214 "]"; 215 } 216 217 /** 218 * Returns the currency. 219 * <p> 220 * {@code null} is returned if {@code setInternationalCurrencySymbol()} has 221 * been previously called with a value that is not a valid ISO 4217 currency 222 * code. 223 * <p> 224 * 225 * @return the currency that was set in the constructor or by calling 226 * {@code setCurrency()} or {@code setInternationalCurrencySymbol()}, 227 * or {@code null} if an invalid currency was set. 228 * @see #setCurrency(Currency) 229 * @see #setInternationalCurrencySymbol(String) 230 */ 231 public Currency getCurrency() { 232 return currency; 233 } 234 235 /** 236 * Returns the international currency symbol. 237 * 238 * @return the international currency symbol as string. 239 */ 240 public String getInternationalCurrencySymbol() { 241 return intlCurrencySymbol; 242 } 243 244 /** 245 * Returns the currency symbol. 246 * 247 * @return the currency symbol as string. 248 */ 249 public String getCurrencySymbol() { 250 return currencySymbol; 251 } 252 253 /** 254 * Returns the character which represents the decimal point in a number. 255 * 256 * @return the decimal separator character. 257 */ 258 public char getDecimalSeparator() { 259 return decimalSeparator; 260 } 261 262 /** 263 * Returns the character which represents a single digit in a format 264 * pattern. 265 * 266 * @return the digit pattern character. 267 */ 268 public char getDigit() { 269 return digit; 270 } 271 272 /** 273 * Returns the character used as the thousands separator in a number. 274 * 275 * @return the thousands separator character. 276 */ 277 public char getGroupingSeparator() { 278 return groupingSeparator; 279 } 280 281 /** 282 * Returns the string which represents infinity. 283 * 284 * @return the infinity symbol as a string. 285 */ 286 public String getInfinity() { 287 return infinity; 288 } 289 290 /** 291 * Returns the minus sign character. 292 * 293 * @return the minus sign as a character. 294 */ 295 public char getMinusSign() { 296 if (minusSign.length() == 1) { 297 return minusSign.charAt(0); 298 } 299 300 // Return the minus sign from Locale.ROOT instead of crashing. None of libcore the parsers 301 // or formatters actually call this function, they use {@code getMinusSignString()} instead 302 // and that function always returns the correct (possibly multi-char) symbol. 303 // 304 // Callers of this method that format strings and expect them to be parseable by 305 // the "standard" parsers (or vice-versa) are hosed, but there's not much we can do to 306 // save them. 307 return '-'; 308 } 309 310 /** @hide */ 311 public String getMinusSignString() { 312 return minusSign; 313 } 314 315 /** @hide */ 316 public String getPercentString() { 317 return percent; 318 } 319 320 /** 321 * Returns the character which represents the decimal point in a monetary 322 * value. 323 * 324 * @return the monetary decimal point as a character. 325 */ 326 public char getMonetaryDecimalSeparator() { 327 return monetarySeparator; 328 } 329 330 /** 331 * Returns the string which represents NaN. 332 * 333 * @return the symbol NaN as a string. 334 */ 335 public String getNaN() { 336 return NaN; 337 } 338 339 /** 340 * Returns the character which separates the positive and negative patterns 341 * in a format pattern. 342 * 343 * @return the pattern separator character. 344 */ 345 public char getPatternSeparator() { 346 return patternSeparator; 347 } 348 349 /** 350 * Returns the percent character. 351 * 352 * @return the percent character. 353 */ 354 public char getPercent() { 355 if (percent.length() == 1) { 356 return percent.charAt(0); 357 } 358 359 // Return the percent sign from Locale.ROOT instead of crashing. None of the libcore parsers 360 // or formatters actually call this function, they use {@code getPercentString()} instead 361 // and that function always returns the correct (possibly multi-char) symbol. 362 // 363 // Callers of this method that format strings and expect them to be parseable by 364 // the "standard" parsers (or vice-versa) are hosed, but there's not much we can do to 365 // save them. 366 return '%'; 367 } 368 369 /** 370 * Returns the per mill sign character. 371 * 372 * @return the per mill sign character. 373 */ 374 public char getPerMill() { 375 return perMill; 376 } 377 378 /** 379 * Returns the character which represents zero. 380 * 381 * @return the zero character. 382 */ 383 public char getZeroDigit() { 384 return zeroDigit; 385 } 386 387 /* 388 * Returns the string used to separate mantissa and exponent. Typically "E", as in "1.2E3". 389 * @since 1.6 390 */ 391 public String getExponentSeparator() { 392 return exponentSeparator; 393 } 394 395 @Override 396 public int hashCode() { 397 int result = 17; 398 result = 31*result + zeroDigit; 399 result = 31*result + digit; 400 result = 31*result + decimalSeparator; 401 result = 31*result + groupingSeparator; 402 result = 31*result + patternSeparator; 403 result = 31*result + percent.hashCode(); 404 result = 31*result + perMill; 405 result = 31*result + monetarySeparator; 406 result = 31*result + minusSign.hashCode(); 407 result = 31*result + exponentSeparator.hashCode(); 408 result = 31*result + infinity.hashCode(); 409 result = 31*result + NaN.hashCode(); 410 result = 31*result + currencySymbol.hashCode(); 411 result = 31*result + intlCurrencySymbol.hashCode(); 412 return result; 413 } 414 415 /** 416 * Sets the currency. 417 * <p> 418 * The international currency symbol and the currency symbol are updated, 419 * but the min and max number of fraction digits stays the same. 420 * <p> 421 * 422 * @param currency 423 * the new currency. 424 * @throws NullPointerException 425 * if {@code currency} is {@code null}. 426 */ 427 public void setCurrency(Currency currency) { 428 if (currency == null) { 429 throw new NullPointerException("currency == null"); 430 } 431 this.currency = currency; 432 intlCurrencySymbol = currency.getCurrencyCode(); 433 currencySymbol = currency.getSymbol(locale); 434 } 435 436 /** 437 * Sets the international currency symbol. 438 * <p> 439 * The currency and currency symbol are also updated if {@code value} is a 440 * valid ISO4217 currency code. 441 * <p> 442 * The min and max number of fraction digits stay the same. 443 * 444 * @param value 445 * the currency code. 446 */ 447 public void setInternationalCurrencySymbol(String value) { 448 if (value == null) { 449 currency = null; 450 intlCurrencySymbol = null; 451 return; 452 } 453 454 if (value.equals(intlCurrencySymbol)) { 455 return; 456 } 457 458 try { 459 currency = Currency.getInstance(value); 460 currencySymbol = currency.getSymbol(locale); 461 } catch (IllegalArgumentException e) { 462 currency = null; 463 } 464 intlCurrencySymbol = value; 465 } 466 467 /** 468 * Sets the currency symbol. 469 * 470 * @param value 471 * the currency symbol. 472 */ 473 public void setCurrencySymbol(String value) { 474 this.currencySymbol = value; 475 } 476 477 /** 478 * Sets the character which represents the decimal point in a number. 479 * 480 * @param value 481 * the decimal separator character. 482 */ 483 public void setDecimalSeparator(char value) { 484 this.decimalSeparator = value; 485 } 486 487 /** 488 * Sets the character which represents a single digit in a format pattern. 489 * 490 * @param value 491 * the digit character. 492 */ 493 public void setDigit(char value) { 494 this.digit = value; 495 } 496 497 /** 498 * Sets the character used as the thousands separator in a number. 499 * 500 * @param value 501 * the grouping separator character. 502 */ 503 public void setGroupingSeparator(char value) { 504 this.groupingSeparator = value; 505 } 506 507 /** 508 * Sets the string which represents infinity. 509 * 510 * @param value 511 * the string representing infinity. 512 */ 513 public void setInfinity(String value) { 514 this.infinity = value; 515 } 516 517 /** 518 * Sets the minus sign character. 519 * 520 * @param value 521 * the minus sign character. 522 */ 523 public void setMinusSign(char value) { 524 this.minusSign = String.valueOf(value); 525 } 526 527 /** 528 * Sets the character which represents the decimal point in a monetary 529 * value. 530 * 531 * @param value 532 * the monetary decimal separator character. 533 */ 534 public void setMonetaryDecimalSeparator(char value) { 535 this.monetarySeparator = value; 536 } 537 538 /** 539 * Sets the string which represents NaN. 540 * 541 * @param value 542 * the string representing NaN. 543 */ 544 public void setNaN(String value) { 545 this.NaN = value; 546 } 547 548 /** 549 * Sets the character which separates the positive and negative patterns in 550 * a format pattern. 551 * 552 * @param value 553 * the pattern separator character. 554 */ 555 public void setPatternSeparator(char value) { 556 this.patternSeparator = value; 557 } 558 559 /** 560 * Sets the percent character. 561 * 562 * @param value 563 * the percent character. 564 */ 565 public void setPercent(char value) { 566 this.percent = String.valueOf(value); 567 } 568 569 /** 570 * Sets the per mill sign character. 571 * 572 * @param value 573 * the per mill character. 574 */ 575 public void setPerMill(char value) { 576 this.perMill = value; 577 } 578 579 /** 580 * Sets the character which represents zero. 581 * 582 * @param value 583 * the zero digit character. 584 */ 585 public void setZeroDigit(char value) { 586 this.zeroDigit = value; 587 } 588 589 /** 590 * Sets the string used to separate mantissa and exponent. Typically "E", as in "1.2E3". 591 * @since 1.6 592 */ 593 public void setExponentSeparator(String value) { 594 if (value == null) { 595 throw new NullPointerException("value == null"); 596 } 597 this.exponentSeparator = value; 598 } 599 600 private static final ObjectStreamField[] serialPersistentFields = { 601 new ObjectStreamField("currencySymbol", String.class), 602 new ObjectStreamField("decimalSeparator", char.class), 603 new ObjectStreamField("digit", char.class), 604 new ObjectStreamField("exponential", char.class), 605 new ObjectStreamField("exponentialSeparator", String.class), 606 new ObjectStreamField("groupingSeparator", char.class), 607 new ObjectStreamField("infinity", String.class), 608 new ObjectStreamField("intlCurrencySymbol", String.class), 609 new ObjectStreamField("minusSign", char.class), 610 new ObjectStreamField("monetarySeparator", char.class), 611 new ObjectStreamField("NaN", String.class), 612 new ObjectStreamField("patternSeparator", char.class), 613 new ObjectStreamField("percent", char.class), 614 new ObjectStreamField("perMill", char.class), 615 new ObjectStreamField("serialVersionOnStream", int.class), 616 new ObjectStreamField("zeroDigit", char.class), 617 new ObjectStreamField("locale", Locale.class), 618 new ObjectStreamField("minusSignStr", String.class), 619 new ObjectStreamField("percentStr", String.class), 620 }; 621 622 private void writeObject(ObjectOutputStream stream) throws IOException { 623 ObjectOutputStream.PutField fields = stream.putFields(); 624 fields.put("currencySymbol", currencySymbol); 625 fields.put("decimalSeparator", getDecimalSeparator()); 626 fields.put("digit", getDigit()); 627 fields.put("exponential", exponentSeparator.charAt(0)); 628 fields.put("exponentialSeparator", exponentSeparator); 629 fields.put("groupingSeparator", getGroupingSeparator()); 630 fields.put("infinity", infinity); 631 fields.put("intlCurrencySymbol", intlCurrencySymbol); 632 fields.put("monetarySeparator", getMonetaryDecimalSeparator()); 633 fields.put("NaN", NaN); 634 fields.put("patternSeparator", getPatternSeparator()); 635 fields.put("perMill", getPerMill()); 636 fields.put("serialVersionOnStream", 3); 637 fields.put("zeroDigit", getZeroDigit()); 638 fields.put("locale", locale); 639 640 // Hardcode values here for backwards compatibility. These values will only be used 641 // if we're de-serializing this object on an earlier version of android. 642 fields.put("minusSign", minusSign.length() == 1 ? minusSign.charAt(0) : '-'); 643 fields.put("percent", percent.length() == 1 ? percent.charAt(0) : '%'); 644 645 fields.put("minusSignStr", getMinusSignString()); 646 fields.put("percentStr", getPercentString()); 647 stream.writeFields(); 648 } 649 650 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 651 ObjectInputStream.GetField fields = stream.readFields(); 652 final int serialVersionOnStream = fields.get("serialVersionOnStream", 0); 653 currencySymbol = (String) fields.get("currencySymbol", ""); 654 setDecimalSeparator(fields.get("decimalSeparator", '.')); 655 setDigit(fields.get("digit", '#')); 656 setGroupingSeparator(fields.get("groupingSeparator", ',')); 657 infinity = (String) fields.get("infinity", ""); 658 intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", ""); 659 NaN = (String) fields.get("NaN", ""); 660 setPatternSeparator(fields.get("patternSeparator", ';')); 661 662 // Special handling for minusSign and percent. If we've serialized the string versions of 663 // these fields, use them. If not, fall back to the single character versions. This can 664 // only happen if we're de-serializing an object that was written by an older version of 665 // android (something that's strongly discouraged anyway). 666 final String minusSignStr = (String) fields.get("minusSignStr", null); 667 if (minusSignStr != null) { 668 minusSign = minusSignStr; 669 } else { 670 setMinusSign(fields.get("minusSign", '-')); 671 } 672 final String percentStr = (String) fields.get("percentStr", null); 673 if (percentStr != null) { 674 percent = percentStr; 675 } else { 676 setPercent(fields.get("percent", '%')); 677 } 678 679 setPerMill(fields.get("perMill", '\u2030')); 680 setZeroDigit(fields.get("zeroDigit", '0')); 681 locale = (Locale) fields.get("locale", null); 682 if (serialVersionOnStream == 0) { 683 setMonetaryDecimalSeparator(getDecimalSeparator()); 684 } else { 685 setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.')); 686 } 687 688 if (serialVersionOnStream == 0) { 689 // Prior to Java 1.1.6, the exponent separator wasn't configurable. 690 exponentSeparator = "E"; 691 } else if (serialVersionOnStream < 3) { 692 // In Javas 1.1.6 and 1.4, there was a character field "exponential". 693 setExponentSeparator(String.valueOf(fields.get("exponential", 'E'))); 694 } else { 695 // In Java 6, there's a new "exponentialSeparator" field. 696 setExponentSeparator((String) fields.get("exponentialSeparator", "E")); 697 } 698 699 try { 700 currency = Currency.getInstance(intlCurrencySymbol); 701 } catch (IllegalArgumentException e) { 702 currency = null; 703 } 704 } 705} 706