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