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 char percent; 51 private char perMill; 52 private char monetarySeparator; 53 private char 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 LocaleData localeData = LocaleData.get(locale); 85 this.zeroDigit = localeData.zeroDigit; 86 this.digit = '#'; 87 this.decimalSeparator = localeData.decimalSeparator; 88 this.groupingSeparator = localeData.groupingSeparator; 89 this.patternSeparator = localeData.patternSeparator; 90 this.percent = localeData.percent; 91 this.perMill = localeData.perMill; 92 this.monetarySeparator = localeData.monetarySeparator; 93 this.minusSign = localeData.minusSign; 94 this.infinity = localeData.infinity; 95 this.NaN = localeData.NaN; 96 this.exponentSeparator = localeData.exponentSeparator; 97 this.locale = locale; 98 try { 99 currency = Currency.getInstance(locale); 100 currencySymbol = currency.getSymbol(locale); 101 intlCurrencySymbol = currency.getCurrencyCode(); 102 } catch (IllegalArgumentException e) { 103 currency = Currency.getInstance("XXX"); 104 currencySymbol = localeData.currencySymbol; 105 intlCurrencySymbol = localeData.internationalCurrencySymbol; 106 } 107 } 108 109 /** 110 * Returns a new {@code DecimalFormatSymbols} instance for the user's default locale. 111 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 112 * 113 * @return an instance of {@code DecimalFormatSymbols} 114 * @since 1.6 115 */ 116 public static DecimalFormatSymbols getInstance() { 117 return getInstance(Locale.getDefault()); 118 } 119 120 /** 121 * Returns a new {@code DecimalFormatSymbols} for the given locale. 122 * 123 * @param locale the locale 124 * @return an instance of {@code DecimalFormatSymbols} 125 * @throws NullPointerException if {@code locale == null} 126 * @since 1.6 127 */ 128 public static DecimalFormatSymbols getInstance(Locale locale) { 129 if (locale == null) { 130 throw new NullPointerException(); 131 } 132 return new DecimalFormatSymbols(locale); 133 } 134 135 /** 136 * Returns an array of locales for which custom {@code DecimalFormatSymbols} instances 137 * are available. 138 * <p>Note that Android does not support user-supplied locale service providers. 139 * @since 1.6 140 */ 141 public static Locale[] getAvailableLocales() { 142 return ICU.getAvailableDecimalFormatSymbolsLocales(); 143 } 144 145 @Override 146 public Object clone() { 147 try { 148 return super.clone(); 149 } catch (CloneNotSupportedException e) { 150 throw new AssertionError(e); 151 } 152 } 153 154 /** 155 * Compares the specified object to this {@code DecimalFormatSymbols} and 156 * indicates if they are equal. In order to be equal, {@code object} must be 157 * an instance of {@code DecimalFormatSymbols} and contain the same symbols. 158 * 159 * @param object 160 * the object to compare with this object. 161 * @return {@code true} if the specified object is equal to this 162 * {@code DecimalFormatSymbols}; {@code false} otherwise. 163 * @see #hashCode 164 */ 165 @Override 166 public boolean equals(Object object) { 167 if (this == object) { 168 return true; 169 } 170 if (!(object instanceof DecimalFormatSymbols)) { 171 return false; 172 } 173 DecimalFormatSymbols obj = (DecimalFormatSymbols) object; 174 return currency.equals(obj.currency) && 175 currencySymbol.equals(obj.currencySymbol) && 176 decimalSeparator == obj.decimalSeparator && 177 digit == obj.digit && 178 exponentSeparator.equals(obj.exponentSeparator) && 179 groupingSeparator == obj.groupingSeparator && 180 infinity.equals(obj.infinity) && 181 intlCurrencySymbol.equals(obj.intlCurrencySymbol) && 182 minusSign == obj.minusSign && 183 monetarySeparator == obj.monetarySeparator && 184 NaN.equals(obj.NaN) && 185 patternSeparator == obj.patternSeparator && 186 perMill == obj.perMill && 187 percent == obj.percent && 188 zeroDigit == obj.zeroDigit; 189 } 190 191 @Override 192 public String toString() { 193 return getClass().getName() + 194 "[currency=" + currency + 195 ",currencySymbol=" + currencySymbol + 196 ",decimalSeparator=" + decimalSeparator + 197 ",digit=" + digit + 198 ",exponentSeparator=" + exponentSeparator + 199 ",groupingSeparator=" + groupingSeparator + 200 ",infinity=" + infinity + 201 ",intlCurrencySymbol=" + intlCurrencySymbol + 202 ",minusSign=" + minusSign + 203 ",monetarySeparator=" + monetarySeparator + 204 ",NaN=" + NaN + 205 ",patternSeparator=" + patternSeparator + 206 ",perMill=" + perMill + 207 ",percent=" + percent + 208 ",zeroDigit=" + zeroDigit + 209 "]"; 210 } 211 212 /** 213 * Returns the currency. 214 * <p> 215 * {@code null} is returned if {@code setInternationalCurrencySymbol()} has 216 * been previously called with a value that is not a valid ISO 4217 currency 217 * code. 218 * <p> 219 * 220 * @return the currency that was set in the constructor or by calling 221 * {@code setCurrency()} or {@code setInternationalCurrencySymbol()}, 222 * or {@code null} if an invalid currency was set. 223 * @see #setCurrency(Currency) 224 * @see #setInternationalCurrencySymbol(String) 225 */ 226 public Currency getCurrency() { 227 return currency; 228 } 229 230 /** 231 * Returns the international currency symbol. 232 * 233 * @return the international currency symbol as string. 234 */ 235 public String getInternationalCurrencySymbol() { 236 return intlCurrencySymbol; 237 } 238 239 /** 240 * Returns the currency symbol. 241 * 242 * @return the currency symbol as string. 243 */ 244 public String getCurrencySymbol() { 245 return currencySymbol; 246 } 247 248 /** 249 * Returns the character which represents the decimal point in a number. 250 * 251 * @return the decimal separator character. 252 */ 253 public char getDecimalSeparator() { 254 return decimalSeparator; 255 } 256 257 /** 258 * Returns the character which represents a single digit in a format 259 * pattern. 260 * 261 * @return the digit pattern character. 262 */ 263 public char getDigit() { 264 return digit; 265 } 266 267 /** 268 * Returns the character used as the thousands separator in a number. 269 * 270 * @return the thousands separator character. 271 */ 272 public char getGroupingSeparator() { 273 return groupingSeparator; 274 } 275 276 /** 277 * Returns the string which represents infinity. 278 * 279 * @return the infinity symbol as a string. 280 */ 281 public String getInfinity() { 282 return infinity; 283 } 284 285 /** 286 * Returns the minus sign character. 287 * 288 * @return the minus sign as a character. 289 */ 290 public char getMinusSign() { 291 return minusSign; 292 } 293 294 /** 295 * Returns the character which represents the decimal point in a monetary 296 * value. 297 * 298 * @return the monetary decimal point as a character. 299 */ 300 public char getMonetaryDecimalSeparator() { 301 return monetarySeparator; 302 } 303 304 /** 305 * Returns the string which represents NaN. 306 * 307 * @return the symbol NaN as a string. 308 */ 309 public String getNaN() { 310 return NaN; 311 } 312 313 /** 314 * Returns the character which separates the positive and negative patterns 315 * in a format pattern. 316 * 317 * @return the pattern separator character. 318 */ 319 public char getPatternSeparator() { 320 return patternSeparator; 321 } 322 323 /** 324 * Returns the percent character. 325 * 326 * @return the percent character. 327 */ 328 public char getPercent() { 329 return percent; 330 } 331 332 /** 333 * Returns the per mill sign character. 334 * 335 * @return the per mill sign character. 336 */ 337 public char getPerMill() { 338 return perMill; 339 } 340 341 /** 342 * Returns the character which represents zero. 343 * 344 * @return the zero character. 345 */ 346 public char getZeroDigit() { 347 return zeroDigit; 348 } 349 350 /* 351 * Returns the string used to separate mantissa and exponent. Typically "E", as in "1.2E3". 352 * @since 1.6 353 */ 354 public String getExponentSeparator() { 355 return exponentSeparator; 356 } 357 358 @Override 359 public int hashCode() { 360 int result = 17; 361 result = 31*result + zeroDigit; 362 result = 31*result + digit; 363 result = 31*result + decimalSeparator; 364 result = 31*result + groupingSeparator; 365 result = 31*result + patternSeparator; 366 result = 31*result + percent; 367 result = 31*result + perMill; 368 result = 31*result + monetarySeparator; 369 result = 31*result + minusSign; 370 result = 31*result + exponentSeparator.hashCode(); 371 result = 31*result + infinity.hashCode(); 372 result = 31*result + NaN.hashCode(); 373 result = 31*result + currencySymbol.hashCode(); 374 result = 31*result + intlCurrencySymbol.hashCode(); 375 return result; 376 } 377 378 /** 379 * Sets the currency. 380 * <p> 381 * The international currency symbol and the currency symbol are updated, 382 * but the min and max number of fraction digits stays the same. 383 * <p> 384 * 385 * @param currency 386 * the new currency. 387 * @throws NullPointerException 388 * if {@code currency} is {@code null}. 389 */ 390 public void setCurrency(Currency currency) { 391 if (currency == null) { 392 throw new NullPointerException(); 393 } 394 if (currency == this.currency) { 395 return; 396 } 397 this.currency = currency; 398 intlCurrencySymbol = currency.getCurrencyCode(); 399 currencySymbol = currency.getSymbol(locale); 400 } 401 402 /** 403 * Sets the international currency symbol. 404 * <p> 405 * The currency and currency symbol are also updated if {@code value} is a 406 * valid ISO4217 currency code. 407 * <p> 408 * The min and max number of fraction digits stay the same. 409 * 410 * @param value 411 * the currency code. 412 */ 413 public void setInternationalCurrencySymbol(String value) { 414 if (value == null) { 415 currency = null; 416 intlCurrencySymbol = null; 417 return; 418 } 419 420 if (value.equals(intlCurrencySymbol)) { 421 return; 422 } 423 424 try { 425 currency = Currency.getInstance(value); 426 currencySymbol = currency.getSymbol(locale); 427 } catch (IllegalArgumentException e) { 428 currency = null; 429 } 430 intlCurrencySymbol = value; 431 } 432 433 /** 434 * Sets the currency symbol. 435 * 436 * @param value 437 * the currency symbol. 438 */ 439 public void setCurrencySymbol(String value) { 440 this.currencySymbol = value; 441 } 442 443 /** 444 * Sets the character which represents the decimal point in a number. 445 * 446 * @param value 447 * the decimal separator character. 448 */ 449 public void setDecimalSeparator(char value) { 450 this.decimalSeparator = value; 451 } 452 453 /** 454 * Sets the character which represents a single digit in a format pattern. 455 * 456 * @param value 457 * the digit character. 458 */ 459 public void setDigit(char value) { 460 this.digit = value; 461 } 462 463 /** 464 * Sets the character used as the thousands separator in a number. 465 * 466 * @param value 467 * the grouping separator character. 468 */ 469 public void setGroupingSeparator(char value) { 470 this.groupingSeparator = value; 471 } 472 473 /** 474 * Sets the string which represents infinity. 475 * 476 * @param value 477 * the string representing infinity. 478 */ 479 public void setInfinity(String value) { 480 this.infinity = value; 481 } 482 483 /** 484 * Sets the minus sign character. 485 * 486 * @param value 487 * the minus sign character. 488 */ 489 public void setMinusSign(char value) { 490 this.minusSign = value; 491 } 492 493 /** 494 * Sets the character which represents the decimal point in a monetary 495 * value. 496 * 497 * @param value 498 * the monetary decimal separator character. 499 */ 500 public void setMonetaryDecimalSeparator(char value) { 501 this.monetarySeparator = value; 502 } 503 504 /** 505 * Sets the string which represents NaN. 506 * 507 * @param value 508 * the string representing NaN. 509 */ 510 public void setNaN(String value) { 511 this.NaN = value; 512 } 513 514 /** 515 * Sets the character which separates the positive and negative patterns in 516 * a format pattern. 517 * 518 * @param value 519 * the pattern separator character. 520 */ 521 public void setPatternSeparator(char value) { 522 this.patternSeparator = value; 523 } 524 525 /** 526 * Sets the percent character. 527 * 528 * @param value 529 * the percent character. 530 */ 531 public void setPercent(char value) { 532 this.percent = value; 533 } 534 535 /** 536 * Sets the per mill sign character. 537 * 538 * @param value 539 * the per mill character. 540 */ 541 public void setPerMill(char value) { 542 this.perMill = value; 543 } 544 545 /** 546 * Sets the character which represents zero. 547 * 548 * @param value 549 * the zero digit character. 550 */ 551 public void setZeroDigit(char value) { 552 this.zeroDigit = value; 553 } 554 555 /** 556 * Sets the string used to separate mantissa and exponent. Typically "E", as in "1.2E3". 557 * @since 1.6 558 */ 559 public void setExponentSeparator(String value) { 560 if (value == null) { 561 throw new NullPointerException(); 562 } 563 this.exponentSeparator = value; 564 } 565 566 private static final ObjectStreamField[] serialPersistentFields = { 567 new ObjectStreamField("currencySymbol", String.class), 568 new ObjectStreamField("decimalSeparator", char.class), 569 new ObjectStreamField("digit", char.class), 570 new ObjectStreamField("exponential", char.class), 571 new ObjectStreamField("exponentialSeparator", String.class), 572 new ObjectStreamField("groupingSeparator", char.class), 573 new ObjectStreamField("infinity", String.class), 574 new ObjectStreamField("intlCurrencySymbol", String.class), 575 new ObjectStreamField("minusSign", char.class), 576 new ObjectStreamField("monetarySeparator", char.class), 577 new ObjectStreamField("NaN", String.class), 578 new ObjectStreamField("patternSeparator", char.class), 579 new ObjectStreamField("percent", char.class), 580 new ObjectStreamField("perMill", char.class), 581 new ObjectStreamField("serialVersionOnStream", int.class), 582 new ObjectStreamField("zeroDigit", char.class), 583 new ObjectStreamField("locale", Locale.class), 584 }; 585 586 private void writeObject(ObjectOutputStream stream) throws IOException { 587 ObjectOutputStream.PutField fields = stream.putFields(); 588 fields.put("currencySymbol", currencySymbol); 589 fields.put("decimalSeparator", getDecimalSeparator()); 590 fields.put("digit", getDigit()); 591 fields.put("exponential", exponentSeparator.charAt(0)); 592 fields.put("exponentialSeparator", exponentSeparator); 593 fields.put("groupingSeparator", getGroupingSeparator()); 594 fields.put("infinity", infinity); 595 fields.put("intlCurrencySymbol", intlCurrencySymbol); 596 fields.put("minusSign", getMinusSign()); 597 fields.put("monetarySeparator", getMonetaryDecimalSeparator()); 598 fields.put("NaN", NaN); 599 fields.put("patternSeparator", getPatternSeparator()); 600 fields.put("percent", getPercent()); 601 fields.put("perMill", getPerMill()); 602 fields.put("serialVersionOnStream", 3); 603 fields.put("zeroDigit", getZeroDigit()); 604 fields.put("locale", locale); 605 stream.writeFields(); 606 } 607 608 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 609 ObjectInputStream.GetField fields = stream.readFields(); 610 final int serialVersionOnStream = fields.get("serialVersionOnStream", 0); 611 currencySymbol = (String) fields.get("currencySymbol", ""); 612 setDecimalSeparator(fields.get("decimalSeparator", '.')); 613 setDigit(fields.get("digit", '#')); 614 setGroupingSeparator(fields.get("groupingSeparator", ',')); 615 infinity = (String) fields.get("infinity", ""); 616 intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", ""); 617 setMinusSign(fields.get("minusSign", '-')); 618 NaN = (String) fields.get("NaN", ""); 619 setPatternSeparator(fields.get("patternSeparator", ';')); 620 setPercent(fields.get("percent", '%')); 621 setPerMill(fields.get("perMill", '\u2030')); 622 setZeroDigit(fields.get("zeroDigit", '0')); 623 locale = (Locale) fields.get("locale", null); 624 if (serialVersionOnStream == 0) { 625 setMonetaryDecimalSeparator(getDecimalSeparator()); 626 } else { 627 setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.')); 628 } 629 630 if (serialVersionOnStream == 0) { 631 // Prior to Java 1.1.6, the exponent separator wasn't configurable. 632 exponentSeparator = "E"; 633 } else if (serialVersionOnStream < 3) { 634 // In Javas 1.1.6 and 1.4, there was a character field "exponential". 635 setExponentSeparator(String.valueOf(fields.get("exponential", 'E'))); 636 } else { 637 // In Java 6, there's a new "exponentialSeparator" field. 638 setExponentSeparator((String) fields.get("exponentialSeparator", "E")); 639 } 640 641 try { 642 currency = Currency.getInstance(intlCurrencySymbol); 643 } catch (IllegalArgumentException e) { 644 currency = null; 645 } 646 } 647} 648