1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package java.text;
19 
20 import java.io.IOException;
21 import java.io.ObjectInputStream;
22 import java.io.ObjectOutputStream;
23 import java.io.ObjectStreamField;
24 import java.io.Serializable;
25 import java.util.Currency;
26 import java.util.Locale;
27 import libcore.icu.ICU;
28 import libcore.icu.LocaleData;
29 
30 /**
31  * Encapsulates the set of symbols (such as the decimal separator, the grouping
32  * separator, and so on) needed by {@code DecimalFormat} to format numbers.
33  * {@code DecimalFormat} internally creates an instance of
34  * {@code DecimalFormatSymbols} from its locale data. If you need to change any
35  * of these symbols, you can get the {@code DecimalFormatSymbols} object from
36  * your {@code DecimalFormat} and modify it.
37  *
38  * @see java.util.Locale
39  * @see DecimalFormat
40  */
41 public class DecimalFormatSymbols implements Cloneable, Serializable {
42 
43     private static final long serialVersionUID = 5772796243397350300L;
44 
45     private char zeroDigit;
46     private char digit;
47     private char decimalSeparator;
48     private char groupingSeparator;
49     private char patternSeparator;
50     private String percent;
51     private char perMill;
52     private char monetarySeparator;
53     private String minusSign;
54     private String infinity, NaN, currencySymbol, intlCurrencySymbol;
55 
56     private transient Currency currency;
57     private transient Locale locale;
58     private transient String exponentSeparator;
59 
60     /**
61      * Constructs a new {@code DecimalFormatSymbols} containing the symbols for
62      * the user's default locale.
63      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
64      * Best practice is to create a {@code DecimalFormat}
65      * and then to get the {@code DecimalFormatSymbols} from that object by
66      * calling {@link DecimalFormat#getDecimalFormatSymbols()}.
67      */
DecimalFormatSymbols()68     public DecimalFormatSymbols() {
69         this(Locale.getDefault());
70     }
71 
72     /**
73      * Constructs a new DecimalFormatSymbols containing the symbols for the
74      * specified Locale.
75      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
76      * Best practice is to create a {@code DecimalFormat}
77      * and then to get the {@code DecimalFormatSymbols} from that object by
78      * calling {@link DecimalFormat#getDecimalFormatSymbols()}.
79      *
80      * @param locale
81      *            the locale.
82      */
DecimalFormatSymbols(Locale locale)83     public DecimalFormatSymbols(Locale locale) {
84         if (locale == null) {
85             throw new NullPointerException("locale == null");
86         }
87 
88         locale = LocaleData.mapInvalidAndNullLocales(locale);
89         LocaleData localeData = LocaleData.get(locale);
90         this.zeroDigit = localeData.zeroDigit;
91         this.digit = '#';
92         this.decimalSeparator = localeData.decimalSeparator;
93         this.groupingSeparator = localeData.groupingSeparator;
94         this.patternSeparator = localeData.patternSeparator;
95         this.percent = localeData.percent;
96         this.perMill = localeData.perMill;
97         this.monetarySeparator = localeData.monetarySeparator;
98         this.minusSign = localeData.minusSign;
99         this.infinity = localeData.infinity;
100         this.NaN = localeData.NaN;
101         this.exponentSeparator = localeData.exponentSeparator;
102         this.locale = locale;
103         try {
104             currency = Currency.getInstance(locale);
105             currencySymbol = currency.getSymbol(locale);
106             intlCurrencySymbol = currency.getCurrencyCode();
107         } catch (IllegalArgumentException e) {
108             currency = Currency.getInstance("XXX");
109             currencySymbol = localeData.currencySymbol;
110             intlCurrencySymbol = localeData.internationalCurrencySymbol;
111         }
112     }
113 
114     /**
115      * Returns a new {@code DecimalFormatSymbols} instance for the user's default locale.
116      * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
117      *
118      * @return an instance of {@code DecimalFormatSymbols}
119      * @since 1.6
120      */
getInstance()121     public static DecimalFormatSymbols getInstance() {
122         return getInstance(Locale.getDefault());
123     }
124 
125     /**
126      * Returns a new {@code DecimalFormatSymbols} for the given locale.
127      *
128      * @param locale the locale
129      * @return an instance of {@code DecimalFormatSymbols}
130      * @throws NullPointerException if {@code locale == null}
131      * @since 1.6
132      */
getInstance(Locale locale)133     public static DecimalFormatSymbols getInstance(Locale locale) {
134         if (locale == null) {
135             throw new NullPointerException("locale == null");
136         }
137         return new DecimalFormatSymbols(locale);
138     }
139 
140     /**
141      * Returns an array of locales for which custom {@code DecimalFormatSymbols} instances
142      * are available.
143      * <p>Note that Android does not support user-supplied locale service providers.
144      * @since 1.6
145      */
getAvailableLocales()146     public static Locale[] getAvailableLocales() {
147         return ICU.getAvailableDecimalFormatSymbolsLocales();
148     }
149 
150     @Override
clone()151     public Object clone() {
152         try {
153             return super.clone();
154         } catch (CloneNotSupportedException e) {
155             throw new AssertionError(e);
156         }
157     }
158 
159     /**
160      * Compares the specified object to this {@code DecimalFormatSymbols} and
161      * indicates if they are equal. In order to be equal, {@code object} must be
162      * an instance of {@code DecimalFormatSymbols} and contain the same symbols.
163      *
164      * @param object
165      *            the object to compare with this object.
166      * @return {@code true} if the specified object is equal to this
167      *         {@code DecimalFormatSymbols}; {@code false} otherwise.
168      * @see #hashCode
169      */
170     @Override
equals(Object object)171     public boolean equals(Object object) {
172         if (this == object) {
173             return true;
174         }
175         if (!(object instanceof DecimalFormatSymbols)) {
176             return false;
177         }
178         DecimalFormatSymbols obj = (DecimalFormatSymbols) object;
179         return currency.equals(obj.currency) &&
180                 currencySymbol.equals(obj.currencySymbol) &&
181                 decimalSeparator == obj.decimalSeparator &&
182                 digit == obj.digit &&
183                 exponentSeparator.equals(obj.exponentSeparator) &&
184                 groupingSeparator == obj.groupingSeparator &&
185                 infinity.equals(obj.infinity) &&
186                 intlCurrencySymbol.equals(obj.intlCurrencySymbol) &&
187                 minusSign.equals(obj.minusSign) &&
188                 monetarySeparator == obj.monetarySeparator &&
189                 NaN.equals(obj.NaN) &&
190                 patternSeparator == obj.patternSeparator &&
191                 perMill == obj.perMill &&
192                 percent.equals(obj.percent) &&
193                 zeroDigit == obj.zeroDigit;
194     }
195 
196     @Override
toString()197     public String toString() {
198         return getClass().getName() +
199                 "[currency=" + currency +
200                 ",currencySymbol=" + currencySymbol +
201                 ",decimalSeparator=" + decimalSeparator +
202                 ",digit=" + digit +
203                 ",exponentSeparator=" + exponentSeparator +
204                 ",groupingSeparator=" + groupingSeparator +
205                 ",infinity=" + infinity +
206                 ",intlCurrencySymbol=" + intlCurrencySymbol +
207                 ",minusSign=" + minusSign +
208                 ",monetarySeparator=" + monetarySeparator +
209                 ",NaN=" + NaN +
210                 ",patternSeparator=" + patternSeparator +
211                 ",perMill=" + perMill +
212                 ",percent=" + percent +
213                 ",zeroDigit=" + zeroDigit +
214                 "]";
215     }
216 
217     /**
218      * Returns the currency.
219      * <p>
220      * {@code null} is returned if {@code setInternationalCurrencySymbol()} has
221      * been previously called with a value that is not a valid ISO 4217 currency
222      * code.
223      * <p>
224      *
225      * @return the currency that was set in the constructor or by calling
226      *         {@code setCurrency()} or {@code setInternationalCurrencySymbol()},
227      *         or {@code null} if an invalid currency was set.
228      * @see #setCurrency(Currency)
229      * @see #setInternationalCurrencySymbol(String)
230      */
getCurrency()231     public Currency getCurrency() {
232         return currency;
233     }
234 
235     /**
236      * Returns the international currency symbol.
237      *
238      * @return the international currency symbol as string.
239      */
getInternationalCurrencySymbol()240     public String getInternationalCurrencySymbol() {
241         return intlCurrencySymbol;
242     }
243 
244     /**
245      * Returns the currency symbol.
246      *
247      * @return the currency symbol as string.
248      */
getCurrencySymbol()249     public String getCurrencySymbol() {
250         return currencySymbol;
251     }
252 
253     /**
254      * Returns the character which represents the decimal point in a number.
255      *
256      * @return the decimal separator character.
257      */
getDecimalSeparator()258     public char getDecimalSeparator() {
259         return decimalSeparator;
260     }
261 
262     /**
263      * Returns the character which represents a single digit in a format
264      * pattern.
265      *
266      * @return the digit pattern character.
267      */
getDigit()268     public char getDigit() {
269         return digit;
270     }
271 
272     /**
273      * Returns the character used as the thousands separator in a number.
274      *
275      * @return the thousands separator character.
276      */
getGroupingSeparator()277     public char getGroupingSeparator() {
278         return groupingSeparator;
279     }
280 
281     /**
282      * Returns the string which represents infinity.
283      *
284      * @return the infinity symbol as a string.
285      */
getInfinity()286     public String getInfinity() {
287         return infinity;
288     }
289 
290     /**
291      * Returns the minus sign character.
292      *
293      * @return the minus sign as a character.
294      */
getMinusSign()295     public char getMinusSign() {
296         if (minusSign.length() == 1) {
297             return minusSign.charAt(0);
298         }
299 
300         throw new UnsupportedOperationException(
301                 "Minus sign spans multiple characters: " + minusSign);
302     }
303 
304     /** @hide */
getMinusSignString()305     public String getMinusSignString() {
306         return minusSign;
307     }
308 
309     /** @hide */
getPercentString()310     public String getPercentString() {
311         return percent;
312     }
313 
314     /**
315      * Returns the character which represents the decimal point in a monetary
316      * value.
317      *
318      * @return the monetary decimal point as a character.
319      */
getMonetaryDecimalSeparator()320     public char getMonetaryDecimalSeparator() {
321         return monetarySeparator;
322     }
323 
324     /**
325      * Returns the string which represents NaN.
326      *
327      * @return the symbol NaN as a string.
328      */
getNaN()329     public String getNaN() {
330         return NaN;
331     }
332 
333     /**
334      * Returns the character which separates the positive and negative patterns
335      * in a format pattern.
336      *
337      * @return the pattern separator character.
338      */
getPatternSeparator()339     public char getPatternSeparator() {
340         return patternSeparator;
341     }
342 
343     /**
344      * Returns the percent character.
345      *
346      * @return the percent character.
347      */
getPercent()348     public char getPercent() {
349         if (percent.length() == 1) {
350             return percent.charAt(0);
351         }
352         throw new UnsupportedOperationException("Percent spans multiple characters: " + percent);
353     }
354 
355     /**
356      * Returns the per mill sign character.
357      *
358      * @return the per mill sign character.
359      */
getPerMill()360     public char getPerMill() {
361         return perMill;
362     }
363 
364     /**
365      * Returns the character which represents zero.
366      *
367      * @return the zero character.
368      */
getZeroDigit()369     public char getZeroDigit() {
370         return zeroDigit;
371     }
372 
373     /*
374      * Returns the string used to separate mantissa and exponent. Typically "E", as in "1.2E3".
375      * @since 1.6
376      */
getExponentSeparator()377     public String getExponentSeparator() {
378         return exponentSeparator;
379     }
380 
381     @Override
hashCode()382     public int hashCode() {
383         int result = 17;
384         result = 31*result + zeroDigit;
385         result = 31*result + digit;
386         result = 31*result + decimalSeparator;
387         result = 31*result + groupingSeparator;
388         result = 31*result + patternSeparator;
389         result = 31*result + percent.hashCode();
390         result = 31*result + perMill;
391         result = 31*result + monetarySeparator;
392         result = 31*result + minusSign.hashCode();
393         result = 31*result + exponentSeparator.hashCode();
394         result = 31*result + infinity.hashCode();
395         result = 31*result + NaN.hashCode();
396         result = 31*result + currencySymbol.hashCode();
397         result = 31*result + intlCurrencySymbol.hashCode();
398         return result;
399     }
400 
401     /**
402      * Sets the currency.
403      * <p>
404      * The international currency symbol and the currency symbol are updated,
405      * but the min and max number of fraction digits stays the same.
406      * <p>
407      *
408      * @param currency
409      *            the new currency.
410      * @throws NullPointerException
411      *             if {@code currency} is {@code null}.
412      */
setCurrency(Currency currency)413     public void setCurrency(Currency currency) {
414         if (currency == null) {
415             throw new NullPointerException("currency == null");
416         }
417         this.currency = currency;
418         intlCurrencySymbol = currency.getCurrencyCode();
419         currencySymbol = currency.getSymbol(locale);
420     }
421 
422     /**
423      * Sets the international currency symbol.
424      * <p>
425      * The currency and currency symbol are also updated if {@code value} is a
426      * valid ISO4217 currency code.
427      * <p>
428      * The min and max number of fraction digits stay the same.
429      *
430      * @param value
431      *            the currency code.
432      */
setInternationalCurrencySymbol(String value)433     public void setInternationalCurrencySymbol(String value) {
434         if (value == null) {
435             currency = null;
436             intlCurrencySymbol = null;
437             return;
438         }
439 
440         if (value.equals(intlCurrencySymbol)) {
441             return;
442         }
443 
444         try {
445             currency = Currency.getInstance(value);
446             currencySymbol = currency.getSymbol(locale);
447         } catch (IllegalArgumentException e) {
448             currency = null;
449         }
450         intlCurrencySymbol = value;
451     }
452 
453     /**
454      * Sets the currency symbol.
455      *
456      * @param value
457      *            the currency symbol.
458      */
setCurrencySymbol(String value)459     public void setCurrencySymbol(String value) {
460         this.currencySymbol = value;
461     }
462 
463     /**
464      * Sets the character which represents the decimal point in a number.
465      *
466      * @param value
467      *            the decimal separator character.
468      */
setDecimalSeparator(char value)469     public void setDecimalSeparator(char value) {
470         this.decimalSeparator = value;
471     }
472 
473     /**
474      * Sets the character which represents a single digit in a format pattern.
475      *
476      * @param value
477      *            the digit character.
478      */
setDigit(char value)479     public void setDigit(char value) {
480         this.digit = value;
481     }
482 
483     /**
484      * Sets the character used as the thousands separator in a number.
485      *
486      * @param value
487      *            the grouping separator character.
488      */
setGroupingSeparator(char value)489     public void setGroupingSeparator(char value) {
490         this.groupingSeparator = value;
491     }
492 
493     /**
494      * Sets the string which represents infinity.
495      *
496      * @param value
497      *            the string representing infinity.
498      */
setInfinity(String value)499     public void setInfinity(String value) {
500         this.infinity = value;
501     }
502 
503     /**
504      * Sets the minus sign character.
505      *
506      * @param value
507      *            the minus sign character.
508      */
setMinusSign(char value)509     public void setMinusSign(char value) {
510         this.minusSign = String.valueOf(value);
511     }
512 
513     /**
514      * Sets the character which represents the decimal point in a monetary
515      * value.
516      *
517      * @param value
518      *            the monetary decimal separator character.
519      */
setMonetaryDecimalSeparator(char value)520     public void setMonetaryDecimalSeparator(char value) {
521         this.monetarySeparator = value;
522     }
523 
524     /**
525      * Sets the string which represents NaN.
526      *
527      * @param value
528      *            the string representing NaN.
529      */
setNaN(String value)530     public void setNaN(String value) {
531         this.NaN = value;
532     }
533 
534     /**
535      * Sets the character which separates the positive and negative patterns in
536      * a format pattern.
537      *
538      * @param value
539      *            the pattern separator character.
540      */
setPatternSeparator(char value)541     public void setPatternSeparator(char value) {
542         this.patternSeparator = value;
543     }
544 
545     /**
546      * Sets the percent character.
547      *
548      * @param value
549      *            the percent character.
550      */
setPercent(char value)551     public void setPercent(char value) {
552         this.percent = String.valueOf(value);
553     }
554 
555     /**
556      * Sets the per mill sign character.
557      *
558      * @param value
559      *            the per mill character.
560      */
setPerMill(char value)561     public void setPerMill(char value) {
562         this.perMill = value;
563     }
564 
565     /**
566      * Sets the character which represents zero.
567      *
568      * @param value
569      *            the zero digit character.
570      */
setZeroDigit(char value)571     public void setZeroDigit(char value) {
572         this.zeroDigit = value;
573     }
574 
575     /**
576      * Sets the string used to separate mantissa and exponent. Typically "E", as in "1.2E3".
577      * @since 1.6
578      */
setExponentSeparator(String value)579     public void setExponentSeparator(String value) {
580         if (value == null) {
581             throw new NullPointerException("value == null");
582         }
583         this.exponentSeparator = value;
584     }
585 
586     private static final ObjectStreamField[] serialPersistentFields = {
587         new ObjectStreamField("currencySymbol", String.class),
588         new ObjectStreamField("decimalSeparator", char.class),
589         new ObjectStreamField("digit", char.class),
590         new ObjectStreamField("exponential", char.class),
591         new ObjectStreamField("exponentialSeparator", String.class),
592         new ObjectStreamField("groupingSeparator", char.class),
593         new ObjectStreamField("infinity", String.class),
594         new ObjectStreamField("intlCurrencySymbol", String.class),
595         new ObjectStreamField("minusSign", char.class),
596         new ObjectStreamField("monetarySeparator", char.class),
597         new ObjectStreamField("NaN", String.class),
598         new ObjectStreamField("patternSeparator", char.class),
599         new ObjectStreamField("percent", char.class),
600         new ObjectStreamField("perMill", char.class),
601         new ObjectStreamField("serialVersionOnStream", int.class),
602         new ObjectStreamField("zeroDigit", char.class),
603         new ObjectStreamField("locale", Locale.class),
604     };
605 
writeObject(ObjectOutputStream stream)606     private void writeObject(ObjectOutputStream stream) throws IOException {
607         ObjectOutputStream.PutField fields = stream.putFields();
608         fields.put("currencySymbol", currencySymbol);
609         fields.put("decimalSeparator", getDecimalSeparator());
610         fields.put("digit", getDigit());
611         fields.put("exponential", exponentSeparator.charAt(0));
612         fields.put("exponentialSeparator", exponentSeparator);
613         fields.put("groupingSeparator", getGroupingSeparator());
614         fields.put("infinity", infinity);
615         fields.put("intlCurrencySymbol", intlCurrencySymbol);
616         fields.put("minusSign", getMinusSign());
617         fields.put("monetarySeparator", getMonetaryDecimalSeparator());
618         fields.put("NaN", NaN);
619         fields.put("patternSeparator", getPatternSeparator());
620         fields.put("percent", getPercent());
621         fields.put("perMill", getPerMill());
622         fields.put("serialVersionOnStream", 3);
623         fields.put("zeroDigit", getZeroDigit());
624         fields.put("locale", locale);
625         stream.writeFields();
626     }
627 
readObject(ObjectInputStream stream)628     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
629         ObjectInputStream.GetField fields = stream.readFields();
630         final int serialVersionOnStream = fields.get("serialVersionOnStream", 0);
631         currencySymbol = (String) fields.get("currencySymbol", "");
632         setDecimalSeparator(fields.get("decimalSeparator", '.'));
633         setDigit(fields.get("digit", '#'));
634         setGroupingSeparator(fields.get("groupingSeparator", ','));
635         infinity = (String) fields.get("infinity", "");
636         intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", "");
637         setMinusSign(fields.get("minusSign", '-'));
638         NaN = (String) fields.get("NaN", "");
639         setPatternSeparator(fields.get("patternSeparator", ';'));
640         setPercent(fields.get("percent", '%'));
641         setPerMill(fields.get("perMill", '\u2030'));
642         setZeroDigit(fields.get("zeroDigit", '0'));
643         locale = (Locale) fields.get("locale", null);
644         if (serialVersionOnStream == 0) {
645             setMonetaryDecimalSeparator(getDecimalSeparator());
646         } else {
647             setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.'));
648         }
649 
650         if (serialVersionOnStream == 0) {
651             // Prior to Java 1.1.6, the exponent separator wasn't configurable.
652             exponentSeparator = "E";
653         } else if (serialVersionOnStream < 3) {
654             // In Javas 1.1.6 and 1.4, there was a character field "exponential".
655             setExponentSeparator(String.valueOf(fields.get("exponential", 'E')));
656         } else {
657             // In Java 6, there's a new "exponentialSeparator" field.
658             setExponentSeparator((String) fields.get("exponentialSeparator", "E"));
659         }
660 
661         try {
662             currency = Currency.getInstance(intlCurrencySymbol);
663         } catch (IllegalArgumentException e) {
664             currency = null;
665         }
666     }
667 }
668