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 18 package java.text; 19 20 import java.io.IOException; 21 import java.io.ObjectInputStream; 22 import java.io.ObjectOutputStream; 23 import java.io.ObjectStreamField; 24 import java.io.Serializable; 25 import java.util.Currency; 26 import java.util.Locale; 27 import libcore.icu.ICU; 28 import 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 */ 41 public 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 */ DecimalFormatSymbols()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 */ DecimalFormatSymbols(Locale locale)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 */ getInstance()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 */ getInstance(Locale locale)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 */ getAvailableLocales()146 public static Locale[] getAvailableLocales() { 147 return ICU.getAvailableDecimalFormatSymbolsLocales(); 148 } 149 150 @Override clone()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 equals(Object object)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 toString()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 */ getCurrency()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 */ getInternationalCurrencySymbol()240 public String getInternationalCurrencySymbol() { 241 return intlCurrencySymbol; 242 } 243 244 /** 245 * Returns the currency symbol. 246 * 247 * @return the currency symbol as string. 248 */ getCurrencySymbol()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 */ getDecimalSeparator()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 */ getDigit()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 */ getGroupingSeparator()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 */ getInfinity()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 */ getMinusSign()295 public char getMinusSign() { 296 if (minusSign.length() == 1) { 297 return minusSign.charAt(0); 298 } 299 300 throw new UnsupportedOperationException( 301 "Minus sign spans multiple characters: " + minusSign); 302 } 303 304 /** @hide */ getMinusSignString()305 public String getMinusSignString() { 306 return minusSign; 307 } 308 309 /** @hide */ getPercentString()310 public String getPercentString() { 311 return percent; 312 } 313 314 /** 315 * Returns the character which represents the decimal point in a monetary 316 * value. 317 * 318 * @return the monetary decimal point as a character. 319 */ getMonetaryDecimalSeparator()320 public char getMonetaryDecimalSeparator() { 321 return monetarySeparator; 322 } 323 324 /** 325 * Returns the string which represents NaN. 326 * 327 * @return the symbol NaN as a string. 328 */ getNaN()329 public String getNaN() { 330 return NaN; 331 } 332 333 /** 334 * Returns the character which separates the positive and negative patterns 335 * in a format pattern. 336 * 337 * @return the pattern separator character. 338 */ getPatternSeparator()339 public char getPatternSeparator() { 340 return patternSeparator; 341 } 342 343 /** 344 * Returns the percent character. 345 * 346 * @return the percent character. 347 */ getPercent()348 public char getPercent() { 349 if (percent.length() == 1) { 350 return percent.charAt(0); 351 } 352 throw new UnsupportedOperationException("Percent spans multiple characters: " + percent); 353 } 354 355 /** 356 * Returns the per mill sign character. 357 * 358 * @return the per mill sign character. 359 */ getPerMill()360 public char getPerMill() { 361 return perMill; 362 } 363 364 /** 365 * Returns the character which represents zero. 366 * 367 * @return the zero character. 368 */ getZeroDigit()369 public char getZeroDigit() { 370 return zeroDigit; 371 } 372 373 /* 374 * Returns the string used to separate mantissa and exponent. Typically "E", as in "1.2E3". 375 * @since 1.6 376 */ getExponentSeparator()377 public String getExponentSeparator() { 378 return exponentSeparator; 379 } 380 381 @Override hashCode()382 public int hashCode() { 383 int result = 17; 384 result = 31*result + zeroDigit; 385 result = 31*result + digit; 386 result = 31*result + decimalSeparator; 387 result = 31*result + groupingSeparator; 388 result = 31*result + patternSeparator; 389 result = 31*result + percent.hashCode(); 390 result = 31*result + perMill; 391 result = 31*result + monetarySeparator; 392 result = 31*result + minusSign.hashCode(); 393 result = 31*result + exponentSeparator.hashCode(); 394 result = 31*result + infinity.hashCode(); 395 result = 31*result + NaN.hashCode(); 396 result = 31*result + currencySymbol.hashCode(); 397 result = 31*result + intlCurrencySymbol.hashCode(); 398 return result; 399 } 400 401 /** 402 * Sets the currency. 403 * <p> 404 * The international currency symbol and the currency symbol are updated, 405 * but the min and max number of fraction digits stays the same. 406 * <p> 407 * 408 * @param currency 409 * the new currency. 410 * @throws NullPointerException 411 * if {@code currency} is {@code null}. 412 */ setCurrency(Currency currency)413 public void setCurrency(Currency currency) { 414 if (currency == null) { 415 throw new NullPointerException("currency == null"); 416 } 417 this.currency = currency; 418 intlCurrencySymbol = currency.getCurrencyCode(); 419 currencySymbol = currency.getSymbol(locale); 420 } 421 422 /** 423 * Sets the international currency symbol. 424 * <p> 425 * The currency and currency symbol are also updated if {@code value} is a 426 * valid ISO4217 currency code. 427 * <p> 428 * The min and max number of fraction digits stay the same. 429 * 430 * @param value 431 * the currency code. 432 */ setInternationalCurrencySymbol(String value)433 public void setInternationalCurrencySymbol(String value) { 434 if (value == null) { 435 currency = null; 436 intlCurrencySymbol = null; 437 return; 438 } 439 440 if (value.equals(intlCurrencySymbol)) { 441 return; 442 } 443 444 try { 445 currency = Currency.getInstance(value); 446 currencySymbol = currency.getSymbol(locale); 447 } catch (IllegalArgumentException e) { 448 currency = null; 449 } 450 intlCurrencySymbol = value; 451 } 452 453 /** 454 * Sets the currency symbol. 455 * 456 * @param value 457 * the currency symbol. 458 */ setCurrencySymbol(String value)459 public void setCurrencySymbol(String value) { 460 this.currencySymbol = value; 461 } 462 463 /** 464 * Sets the character which represents the decimal point in a number. 465 * 466 * @param value 467 * the decimal separator character. 468 */ setDecimalSeparator(char value)469 public void setDecimalSeparator(char value) { 470 this.decimalSeparator = value; 471 } 472 473 /** 474 * Sets the character which represents a single digit in a format pattern. 475 * 476 * @param value 477 * the digit character. 478 */ setDigit(char value)479 public void setDigit(char value) { 480 this.digit = value; 481 } 482 483 /** 484 * Sets the character used as the thousands separator in a number. 485 * 486 * @param value 487 * the grouping separator character. 488 */ setGroupingSeparator(char value)489 public void setGroupingSeparator(char value) { 490 this.groupingSeparator = value; 491 } 492 493 /** 494 * Sets the string which represents infinity. 495 * 496 * @param value 497 * the string representing infinity. 498 */ setInfinity(String value)499 public void setInfinity(String value) { 500 this.infinity = value; 501 } 502 503 /** 504 * Sets the minus sign character. 505 * 506 * @param value 507 * the minus sign character. 508 */ setMinusSign(char value)509 public void setMinusSign(char value) { 510 this.minusSign = String.valueOf(value); 511 } 512 513 /** 514 * Sets the character which represents the decimal point in a monetary 515 * value. 516 * 517 * @param value 518 * the monetary decimal separator character. 519 */ setMonetaryDecimalSeparator(char value)520 public void setMonetaryDecimalSeparator(char value) { 521 this.monetarySeparator = value; 522 } 523 524 /** 525 * Sets the string which represents NaN. 526 * 527 * @param value 528 * the string representing NaN. 529 */ setNaN(String value)530 public void setNaN(String value) { 531 this.NaN = value; 532 } 533 534 /** 535 * Sets the character which separates the positive and negative patterns in 536 * a format pattern. 537 * 538 * @param value 539 * the pattern separator character. 540 */ setPatternSeparator(char value)541 public void setPatternSeparator(char value) { 542 this.patternSeparator = value; 543 } 544 545 /** 546 * Sets the percent character. 547 * 548 * @param value 549 * the percent character. 550 */ setPercent(char value)551 public void setPercent(char value) { 552 this.percent = String.valueOf(value); 553 } 554 555 /** 556 * Sets the per mill sign character. 557 * 558 * @param value 559 * the per mill character. 560 */ setPerMill(char value)561 public void setPerMill(char value) { 562 this.perMill = value; 563 } 564 565 /** 566 * Sets the character which represents zero. 567 * 568 * @param value 569 * the zero digit character. 570 */ setZeroDigit(char value)571 public void setZeroDigit(char value) { 572 this.zeroDigit = value; 573 } 574 575 /** 576 * Sets the string used to separate mantissa and exponent. Typically "E", as in "1.2E3". 577 * @since 1.6 578 */ setExponentSeparator(String value)579 public void setExponentSeparator(String value) { 580 if (value == null) { 581 throw new NullPointerException("value == null"); 582 } 583 this.exponentSeparator = value; 584 } 585 586 private static final ObjectStreamField[] serialPersistentFields = { 587 new ObjectStreamField("currencySymbol", String.class), 588 new ObjectStreamField("decimalSeparator", char.class), 589 new ObjectStreamField("digit", char.class), 590 new ObjectStreamField("exponential", char.class), 591 new ObjectStreamField("exponentialSeparator", String.class), 592 new ObjectStreamField("groupingSeparator", char.class), 593 new ObjectStreamField("infinity", String.class), 594 new ObjectStreamField("intlCurrencySymbol", String.class), 595 new ObjectStreamField("minusSign", char.class), 596 new ObjectStreamField("monetarySeparator", char.class), 597 new ObjectStreamField("NaN", String.class), 598 new ObjectStreamField("patternSeparator", char.class), 599 new ObjectStreamField("percent", char.class), 600 new ObjectStreamField("perMill", char.class), 601 new ObjectStreamField("serialVersionOnStream", int.class), 602 new ObjectStreamField("zeroDigit", char.class), 603 new ObjectStreamField("locale", Locale.class), 604 }; 605 writeObject(ObjectOutputStream stream)606 private void writeObject(ObjectOutputStream stream) throws IOException { 607 ObjectOutputStream.PutField fields = stream.putFields(); 608 fields.put("currencySymbol", currencySymbol); 609 fields.put("decimalSeparator", getDecimalSeparator()); 610 fields.put("digit", getDigit()); 611 fields.put("exponential", exponentSeparator.charAt(0)); 612 fields.put("exponentialSeparator", exponentSeparator); 613 fields.put("groupingSeparator", getGroupingSeparator()); 614 fields.put("infinity", infinity); 615 fields.put("intlCurrencySymbol", intlCurrencySymbol); 616 fields.put("minusSign", getMinusSign()); 617 fields.put("monetarySeparator", getMonetaryDecimalSeparator()); 618 fields.put("NaN", NaN); 619 fields.put("patternSeparator", getPatternSeparator()); 620 fields.put("percent", getPercent()); 621 fields.put("perMill", getPerMill()); 622 fields.put("serialVersionOnStream", 3); 623 fields.put("zeroDigit", getZeroDigit()); 624 fields.put("locale", locale); 625 stream.writeFields(); 626 } 627 readObject(ObjectInputStream stream)628 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 629 ObjectInputStream.GetField fields = stream.readFields(); 630 final int serialVersionOnStream = fields.get("serialVersionOnStream", 0); 631 currencySymbol = (String) fields.get("currencySymbol", ""); 632 setDecimalSeparator(fields.get("decimalSeparator", '.')); 633 setDigit(fields.get("digit", '#')); 634 setGroupingSeparator(fields.get("groupingSeparator", ',')); 635 infinity = (String) fields.get("infinity", ""); 636 intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", ""); 637 setMinusSign(fields.get("minusSign", '-')); 638 NaN = (String) fields.get("NaN", ""); 639 setPatternSeparator(fields.get("patternSeparator", ';')); 640 setPercent(fields.get("percent", '%')); 641 setPerMill(fields.get("perMill", '\u2030')); 642 setZeroDigit(fields.get("zeroDigit", '0')); 643 locale = (Locale) fields.get("locale", null); 644 if (serialVersionOnStream == 0) { 645 setMonetaryDecimalSeparator(getDecimalSeparator()); 646 } else { 647 setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.')); 648 } 649 650 if (serialVersionOnStream == 0) { 651 // Prior to Java 1.1.6, the exponent separator wasn't configurable. 652 exponentSeparator = "E"; 653 } else if (serialVersionOnStream < 3) { 654 // In Javas 1.1.6 and 1.4, there was a character field "exponential". 655 setExponentSeparator(String.valueOf(fields.get("exponential", 'E'))); 656 } else { 657 // In Java 6, there's a new "exponentialSeparator" field. 658 setExponentSeparator((String) fields.get("exponentialSeparator", "E")); 659 } 660 661 try { 662 currency = Currency.getInstance(intlCurrencySymbol); 663 } catch (IllegalArgumentException e) { 664 currency = null; 665 } 666 } 667 } 668