DecimalFormatSymbols.java revision 28eb98ecd43c27702e85b0561e040e2da10320a6
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 = localeData.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", Character.TYPE),
569        new ObjectStreamField("digit", Character.TYPE),
570        new ObjectStreamField("exponential", Character.TYPE),
571        new ObjectStreamField("exponentialSeparator", String.class),
572        new ObjectStreamField("groupingSeparator", Character.TYPE),
573        new ObjectStreamField("infinity", String.class),
574        new ObjectStreamField("intlCurrencySymbol", String.class),
575        new ObjectStreamField("minusSign", Character.TYPE),
576        new ObjectStreamField("monetarySeparator", Character.TYPE),
577        new ObjectStreamField("NaN", String.class),
578        new ObjectStreamField("patternSeparator", Character.TYPE),
579        new ObjectStreamField("percent", Character.TYPE),
580        new ObjectStreamField("perMill", Character.TYPE),
581        new ObjectStreamField("serialVersionOnStream", Integer.TYPE),
582        new ObjectStreamField("zeroDigit", Character.TYPE),
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