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.Arrays;
26import java.util.Currency;
27import java.util.Locale;
28
29import com.ibm.icu4jni.util.LocaleData;
30
31/**
32 * Encapsulates the set of symbols (such as the decimal separator, the grouping
33 * separator, and so on) needed by {@code DecimalFormat} to format numbers.
34 * {@code DecimalFormat} internally creates an instance of
35 * {@code DecimalFormatSymbols} from its locale data. If you need to change any
36 * of these symbols, you can get the {@code DecimalFormatSymbols} object from
37 * your {@code DecimalFormat} and modify it.
38 *
39 * @see java.util.Locale
40 * @see DecimalFormat
41 */
42public final class DecimalFormatSymbols implements Cloneable, Serializable {
43
44    private static final long serialVersionUID = 5772796243397350300L;
45
46    private final int ZeroDigit = 0, Digit = 1, DecimalSeparator = 2,
47            GroupingSeparator = 3, PatternSeparator = 4, Percent = 5,
48            PerMill = 6, Exponent = 7, MonetaryDecimalSeparator = 8,
49            MinusSign = 9;
50
51    private transient char[] patternChars;
52
53    private transient Currency currency;
54
55    private transient Locale locale;
56
57    private String infinity, NaN, currencySymbol, intlCurrencySymbol;
58
59    /**
60     * Constructs a new {@code DecimalFormatSymbols} containing the symbols for
61     * the default locale. Best practice is to create a {@code DecimalFormat}
62     * and then to get the {@code DecimalFormatSymbols} from that object by
63     * calling {@link DecimalFormat#getDecimalFormatSymbols()}.
64     */
65    public DecimalFormatSymbols() {
66        this(Locale.getDefault());
67    }
68
69    /**
70     * Constructs a new DecimalFormatSymbols containing the symbols for the
71     * specified Locale. Best practice is to create a {@code DecimalFormat}
72     * and then to get the {@code DecimalFormatSymbols} from that object by
73     * calling {@link DecimalFormat#getDecimalFormatSymbols()}.
74     *
75     * @param locale
76     *            the locale.
77     */
78    public DecimalFormatSymbols(Locale locale) {
79        // BEGIN android-changed
80        LocaleData localeData = com.ibm.icu4jni.util.Resources.getLocaleData(locale);
81        this.patternChars = localeData.decimalPatternChars.toCharArray();
82        this.infinity = localeData.infinity;
83        this.NaN = localeData.NaN;
84        this.locale = locale;
85        try {
86            currency = Currency.getInstance(locale);
87            currencySymbol = currency.getSymbol(locale);
88            intlCurrencySymbol = currency.getCurrencyCode();
89        } catch (IllegalArgumentException e) {
90            currency = Currency.getInstance("XXX"); //$NON-NLS-1$
91            currencySymbol = localeData.currencySymbol;
92            intlCurrencySymbol = localeData.internationalCurrencySymbol;
93        }
94        // END android-changed
95    }
96
97
98    /**
99     * Returns a new {@code DecimalFormatSymbols} with the same symbols as this
100     * {@code DecimalFormatSymbols}.
101     *
102     * @return a shallow copy of this {@code DecimalFormatSymbols}.
103     *
104     * @see java.lang.Cloneable
105     */
106    @Override
107    public Object clone() {
108        try {
109            DecimalFormatSymbols symbols = (DecimalFormatSymbols) super.clone();
110            symbols.patternChars = patternChars.clone();
111            return symbols;
112        } catch (CloneNotSupportedException e) {
113            throw new AssertionError(e); // android-changed
114        }
115    }
116
117    /**
118     * Compares the specified object to this {@code DecimalFormatSymbols} and
119     * indicates if they are equal. In order to be equal, {@code object} must be
120     * an instance of {@code DecimalFormatSymbols} and contain the same symbols.
121     *
122     * @param object
123     *            the object to compare with this object.
124     * @return {@code true} if the specified object is equal to this
125     *         {@code DecimalFormatSymbols}; {@code false} otherwise.
126     * @see #hashCode
127     */
128    @Override
129    public boolean equals(Object object) {
130        if (this == object) {
131            return true;
132        }
133        if (!(object instanceof DecimalFormatSymbols)) {
134            return false;
135        }
136        DecimalFormatSymbols obj = (DecimalFormatSymbols) object;
137        return Arrays.equals(patternChars, obj.patternChars)
138                && infinity.equals(obj.infinity) && NaN.equals(obj.NaN)
139                && currencySymbol.equals(obj.currencySymbol)
140                && intlCurrencySymbol.equals(obj.intlCurrencySymbol);
141    }
142
143    /**
144     * Returns the currency.
145     * <p>
146     * {@code null} is returned if {@code setInternationalCurrencySymbol()} has
147     * been previously called with a value that is not a valid ISO 4217 currency
148     * code.
149     * <p>
150     *
151     * @return the currency that was set in the constructor or by calling
152     *         {@code setCurrency()} or {@code setInternationalCurrencySymbol()},
153     *         or {@code null} if an invalid currency was set.
154     * @see #setCurrency(Currency)
155     * @see #setInternationalCurrencySymbol(String)
156     */
157    public Currency getCurrency() {
158        return currency;
159    }
160
161    /**
162     * Returns the international currency symbol.
163     *
164     * @return the international currency symbol as string.
165     */
166    public String getInternationalCurrencySymbol() {
167        return intlCurrencySymbol;
168    }
169
170    /**
171     * Returns the currency symbol.
172     *
173     * @return the currency symbol as string.
174     */
175    public String getCurrencySymbol() {
176        return currencySymbol;
177    }
178
179    /**
180     * Returns the character which represents the decimal point in a number.
181     *
182     * @return the decimal separator character.
183     */
184    public char getDecimalSeparator() {
185        return patternChars[DecimalSeparator];
186    }
187
188    /**
189     * Returns the character which represents a single digit in a format
190     * pattern.
191     *
192     * @return the digit pattern character.
193     */
194    public char getDigit() {
195        return patternChars[Digit];
196    }
197
198    /**
199     * Returns the character used as the thousands separator in a number.
200     *
201     * @return the thousands separator character.
202     */
203    public char getGroupingSeparator() {
204        return patternChars[GroupingSeparator];
205    }
206
207    /**
208     * Returns the string which represents infinity.
209     *
210     * @return the infinity symbol as a string.
211     */
212    public String getInfinity() {
213        return infinity;
214    }
215
216    /**
217     * Returns the minus sign character.
218     *
219     * @return the minus sign as a character.
220     */
221    public char getMinusSign() {
222        return patternChars[MinusSign];
223    }
224
225    /**
226     * Returns the character which represents the decimal point in a monetary
227     * value.
228     *
229     * @return the monetary decimal point as a character.
230     */
231    public char getMonetaryDecimalSeparator() {
232        return patternChars[MonetaryDecimalSeparator];
233    }
234
235    /**
236     * Returns the string which represents NaN.
237     *
238     * @return the symbol NaN as a string.
239     */
240    public String getNaN() {
241        return NaN;
242    }
243
244    /**
245     * Returns the character which separates the positive and negative patterns
246     * in a format pattern.
247     *
248     * @return the pattern separator character.
249     */
250    public char getPatternSeparator() {
251        return patternChars[PatternSeparator];
252    }
253
254    /**
255     * Returns the percent character.
256     *
257     * @return the percent character.
258     */
259    public char getPercent() {
260        return patternChars[Percent];
261    }
262
263    /**
264     * Returns the per mill sign character.
265     *
266     * @return the per mill sign character.
267     */
268    public char getPerMill() {
269        return patternChars[PerMill];
270    }
271
272    /**
273     * Returns the character which represents zero.
274     *
275     * @return the zero character.
276     */
277    public char getZeroDigit() {
278        return patternChars[ZeroDigit];
279    }
280
281    /*
282     * Returns the exponent as a character.
283     */
284    char getExponential() {
285        return patternChars[Exponent];
286    }
287
288    @Override
289    public int hashCode() {
290        return new String(patternChars).hashCode() + infinity.hashCode()
291                + NaN.hashCode() + currencySymbol.hashCode()
292                + intlCurrencySymbol.hashCode();
293    }
294
295    /**
296     * Sets the currency.
297     * <p>
298     * The international currency symbol and the currency symbol are updated,
299     * but the min and max number of fraction digits stays the same.
300     * <p>
301     *
302     * @param currency
303     *            the new currency.
304     * @throws NullPointerException
305     *             if {@code currency} is {@code null}.
306     */
307    public void setCurrency(Currency currency) {
308        if (currency == null) {
309            throw new NullPointerException();
310        }
311        if (currency == this.currency) {
312            return;
313        }
314        this.currency = currency;
315        intlCurrencySymbol = currency.getCurrencyCode();
316        currencySymbol = currency.getSymbol(locale);
317    }
318
319    /**
320     * Sets the international currency symbol.
321     * <p>
322     * The currency and currency symbol are also updated if {@code value} is a
323     * valid ISO4217 currency code.
324     * <p>
325     * The min and max number of fraction digits stay the same.
326     *
327     * @param value
328     *            the currency code.
329     */
330    public void setInternationalCurrencySymbol(String value) {
331        if (value == null) {
332            currency = null;
333            intlCurrencySymbol = null;
334            return;
335        }
336
337        if (value.equals(intlCurrencySymbol)) {
338            return;
339        }
340
341        try {
342            currency = Currency.getInstance(value);
343            currencySymbol = currency.getSymbol(locale);
344        } catch (IllegalArgumentException e) {
345            currency = null;
346        }
347        intlCurrencySymbol = value;
348    }
349
350    /**
351     * Sets the currency symbol.
352     *
353     * @param value
354     *            the currency symbol.
355     */
356    public void setCurrencySymbol(String value) {
357        currencySymbol = value;
358    }
359
360    /**
361     * Sets the character which represents the decimal point in a number.
362     *
363     * @param value
364     *            the decimal separator character.
365     */
366    public void setDecimalSeparator(char value) {
367        patternChars[DecimalSeparator] = value;
368    }
369
370    /**
371     * Sets the character which represents a single digit in a format pattern.
372     *
373     * @param value
374     *            the digit character.
375     */
376    public void setDigit(char value) {
377        patternChars[Digit] = value;
378    }
379
380    /**
381     * Sets the character used as the thousands separator in a number.
382     *
383     * @param value
384     *            the grouping separator character.
385     */
386    public void setGroupingSeparator(char value) {
387        patternChars[GroupingSeparator] = value;
388    }
389
390    /**
391     * Sets the string which represents infinity.
392     *
393     * @param value
394     *            the string representing infinity.
395     */
396    public void setInfinity(String value) {
397        infinity = value;
398    }
399
400    /**
401     * Sets the minus sign character.
402     *
403     * @param value
404     *            the minus sign character.
405     */
406    public void setMinusSign(char value) {
407        patternChars[MinusSign] = value;
408    }
409
410    /**
411     * Sets the character which represents the decimal point in a monetary
412     * value.
413     *
414     * @param value
415     *            the monetary decimal separator character.
416     */
417    public void setMonetaryDecimalSeparator(char value) {
418        patternChars[MonetaryDecimalSeparator] = value;
419    }
420
421    /**
422     * Sets the string which represents NaN.
423     *
424     * @param value
425     *            the string representing NaN.
426     */
427    public void setNaN(String value) {
428        NaN = value;
429    }
430
431    /**
432     * Sets the character which separates the positive and negative patterns in
433     * a format pattern.
434     *
435     * @param value
436     *            the pattern separator character.
437     */
438    public void setPatternSeparator(char value) {
439        patternChars[PatternSeparator] = value;
440    }
441
442    /**
443     * Sets the percent character.
444     *
445     * @param value
446     *            the percent character.
447     */
448    public void setPercent(char value) {
449        patternChars[Percent] = value;
450    }
451
452    /**
453     * Sets the per mill sign character.
454     *
455     * @param value
456     *            the per mill character.
457     */
458    public void setPerMill(char value) {
459        patternChars[PerMill] = value;
460    }
461
462    /**
463     * Sets the character which represents zero.
464     *
465     * @param value
466     *            the zero digit character.
467     */
468    public void setZeroDigit(char value) {
469        patternChars[ZeroDigit] = value;
470    }
471
472    /*
473     * Sets the exponent character.
474     */
475    void setExponential(char value) {
476        patternChars[Exponent] = value;
477    }
478
479    private static final ObjectStreamField[] serialPersistentFields = {
480            new ObjectStreamField("currencySymbol", String.class), //$NON-NLS-1$
481            new ObjectStreamField("decimalSeparator", Character.TYPE), //$NON-NLS-1$
482            new ObjectStreamField("digit", Character.TYPE), //$NON-NLS-1$
483            new ObjectStreamField("exponential", Character.TYPE), //$NON-NLS-1$
484            new ObjectStreamField("groupingSeparator", Character.TYPE), //$NON-NLS-1$
485            new ObjectStreamField("infinity", String.class), //$NON-NLS-1$
486            new ObjectStreamField("intlCurrencySymbol", String.class), //$NON-NLS-1$
487            new ObjectStreamField("minusSign", Character.TYPE), //$NON-NLS-1$
488            new ObjectStreamField("monetarySeparator", Character.TYPE), //$NON-NLS-1$
489            new ObjectStreamField("NaN", String.class), //$NON-NLS-1$
490            new ObjectStreamField("patternSeparator", Character.TYPE), //$NON-NLS-1$
491            new ObjectStreamField("percent", Character.TYPE), //$NON-NLS-1$
492            new ObjectStreamField("perMill", Character.TYPE), //$NON-NLS-1$
493            new ObjectStreamField("serialVersionOnStream", Integer.TYPE), //$NON-NLS-1$
494            new ObjectStreamField("zeroDigit", Character.TYPE), //$NON-NLS-1$
495            new ObjectStreamField("locale", Locale.class), }; //$NON-NLS-1$
496
497    private void writeObject(ObjectOutputStream stream) throws IOException {
498        ObjectOutputStream.PutField fields = stream.putFields();
499        fields.put("currencySymbol", currencySymbol); //$NON-NLS-1$
500        fields.put("decimalSeparator", getDecimalSeparator()); //$NON-NLS-1$
501        fields.put("digit", getDigit()); //$NON-NLS-1$
502        fields.put("exponential", getExponential()); //$NON-NLS-1$
503        fields.put("groupingSeparator", getGroupingSeparator()); //$NON-NLS-1$
504        fields.put("infinity", infinity); //$NON-NLS-1$
505        fields.put("intlCurrencySymbol", intlCurrencySymbol); //$NON-NLS-1$
506        fields.put("minusSign", getMinusSign()); //$NON-NLS-1$
507        fields.put("monetarySeparator", getMonetaryDecimalSeparator()); //$NON-NLS-1$
508        fields.put("NaN", NaN); //$NON-NLS-1$
509        fields.put("patternSeparator", getPatternSeparator()); //$NON-NLS-1$
510        fields.put("percent", getPercent()); //$NON-NLS-1$
511        fields.put("perMill", getPerMill()); //$NON-NLS-1$
512        fields.put("serialVersionOnStream", 1); //$NON-NLS-1$
513        fields.put("zeroDigit", getZeroDigit()); //$NON-NLS-1$
514        fields.put("locale", locale); //$NON-NLS-1$
515        stream.writeFields();
516    }
517
518    private void readObject(ObjectInputStream stream) throws IOException,
519            ClassNotFoundException {
520        ObjectInputStream.GetField fields = stream.readFields();
521        patternChars = new char[10];
522        currencySymbol = (String) fields.get("currencySymbol", ""); //$NON-NLS-1$ //$NON-NLS-2$
523        setDecimalSeparator(fields.get("decimalSeparator", '.')); //$NON-NLS-1$
524        setDigit(fields.get("digit", '#')); //$NON-NLS-1$
525        setGroupingSeparator(fields.get("groupingSeparator", ',')); //$NON-NLS-1$
526        infinity = (String) fields.get("infinity", ""); //$NON-NLS-1$ //$NON-NLS-2$
527        intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", ""); //$NON-NLS-1$ //$NON-NLS-2$
528        setMinusSign(fields.get("minusSign", '-')); //$NON-NLS-1$
529        NaN = (String) fields.get("NaN", ""); //$NON-NLS-1$ //$NON-NLS-2$
530        setPatternSeparator(fields.get("patternSeparator", ';')); //$NON-NLS-1$
531        setPercent(fields.get("percent", '%')); //$NON-NLS-1$
532        setPerMill(fields.get("perMill", '\u2030')); //$NON-NLS-1$
533        setZeroDigit(fields.get("zeroDigit", '0')); //$NON-NLS-1$
534        locale = (Locale) fields.get("locale", null); //$NON-NLS-1$
535        if (fields.get("serialVersionOnStream", 0) == 0) { //$NON-NLS-1$
536            setMonetaryDecimalSeparator(getDecimalSeparator());
537            setExponential('E');
538        } else {
539            setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.')); //$NON-NLS-1$
540            setExponential(fields.get("exponential", 'E')); //$NON-NLS-1$
541
542        }
543        try {
544            currency = Currency.getInstance(intlCurrencySymbol);
545        } catch (IllegalArgumentException e) {
546            currency = null;
547        }
548    }
549}
550