Home | History | Annotate | Download | only in text
      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      */
     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         throw new UnsupportedOperationException(
    301                 "Minus sign spans multiple characters: " + minusSign);
    302     }
    303 
    304     /** @hide */
    305     public String getMinusSignString() {
    306         return minusSign;
    307     }
    308 
    309     /** @hide */
    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      */
    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      */
    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      */
    339     public char getPatternSeparator() {
    340         return patternSeparator;
    341     }
    342 
    343     /**
    344      * Returns the percent character.
    345      *
    346      * @return the percent character.
    347      */
    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      */
    360     public char getPerMill() {
    361         return perMill;
    362     }
    363 
    364     /**
    365      * Returns the character which represents zero.
    366      *
    367      * @return the zero character.
    368      */
    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      */
    377     public String getExponentSeparator() {
    378         return exponentSeparator;
    379     }
    380 
    381     @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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