• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 /*
28  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
29  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
30  *
31  *   The original version of this source code and documentation is copyrighted
32  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
33  * materials are provided under terms of a License Agreement between Taligent
34  * and Sun. This technology is protected by multiple US and International
35  * patents. This notice and attribution to Taligent may not be removed.
36  *   Taligent is a registered trademark of Taligent, Inc.
37  *
38  */
39 
40 package java.text;
41 
42 import java.io.IOException;
43 import java.io.ObjectInputStream;
44 import java.io.ObjectOutputStream;
45 import java.io.ObjectStreamField;
46 import java.io.Serializable;
47 import java.util.Currency;
48 import java.util.Locale;
49 import libcore.icu.ICU;
50 import libcore.icu.LocaleData;
51 
52 /**
53  * This class represents the set of symbols (such as the decimal separator,
54  * the grouping separator, and so on) needed by <code>DecimalFormat</code>
55  * to format numbers. <code>DecimalFormat</code> creates for itself an instance of
56  * <code>DecimalFormatSymbols</code> from its locale data.  If you need to change any
57  * of these symbols, you can get the <code>DecimalFormatSymbols</code> object from
58  * your <code>DecimalFormat</code> and modify it.
59  *
60  * @see          java.util.Locale
61  * @see          DecimalFormat
62  * @author       Mark Davis
63  * @author       Alan Liu
64  */
65 
66 public class DecimalFormatSymbols implements Cloneable, Serializable {
67 
68     // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
69     /**
70      * Create a DecimalFormatSymbols object for the default
71      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
72      * It is recommended that the {@link #getInstance(Locale) getInstance} method is used
73      * instead.
74      * <p>This is equivalent to calling
75      * {@link #DecimalFormatSymbols(Locale)
76      *     DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT))}.
77      * @see java.util.Locale#getDefault(java.util.Locale.Category)
78      * @see java.util.Locale.Category#FORMAT
79      */
DecimalFormatSymbols()80     public DecimalFormatSymbols() {
81         initialize( Locale.getDefault(Locale.Category.FORMAT) );
82     }
83 
84     // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
85     /**
86      * Create a DecimalFormatSymbols object for the given locale.
87      * It is recommended that the {@link #getInstance(Locale) getInstance} method is used
88      * instead.
89      * If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
90      * for the numbering system, the instance is initialized with the specified numbering
91      * system if the JRE implementation supports it. For example,
92      * <pre>
93      * NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
94      * </pre>
95      * This may return a {@code NumberFormat} instance with the Thai numbering system,
96      * instead of the Latin numbering system.
97      *
98      * @param locale the desired locale
99      * @exception NullPointerException if <code>locale</code> is null
100      */
DecimalFormatSymbols( Locale locale )101     public DecimalFormatSymbols( Locale locale ) {
102         initialize( locale );
103     }
104 
105     // Android-changed: Removed reference to DecimalFormatSymbolsProvider.
106     /**
107      * Returns an array of all locales for which the
108      * <code>getInstance</code> methods of this class can return
109      * localized instances.
110      *
111      * @return an array of locales for which localized
112      *         <code>DecimalFormatSymbols</code> instances are available.
113      * @since 1.6
114      */
getAvailableLocales()115     public static Locale[] getAvailableLocales() {
116         // Android-changed: Removed used of DecimalFormatSymbolsProvider. Switched to use ICU.
117         return ICU.getAvailableLocales();
118     }
119 
120     // Android-changed: Removed reference to DecimalFormatSymbolsProvider.
121     /**
122      * Gets the <code>DecimalFormatSymbols</code> instance for the default
123      * locale.
124      * <p>This is equivalent to calling
125      * {@link #getInstance(Locale)
126      *     getInstance(Locale.getDefault(Locale.Category.FORMAT))}.
127      * @see java.util.Locale#getDefault(java.util.Locale.Category)
128      * @see java.util.Locale.Category#FORMAT
129      * @return a <code>DecimalFormatSymbols</code> instance.
130      * @since 1.6
131      */
getInstance()132     public static final DecimalFormatSymbols getInstance() {
133         return getInstance(Locale.getDefault(Locale.Category.FORMAT));
134     }
135 
136     // Android-changed: Removed reference to DecimalFormatSymbolsProvider.
137     /**
138      * Gets the <code>DecimalFormatSymbols</code> instance for the specified
139      * locale.
140      * If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
141      * for the numbering system, the instance is initialized with the specified numbering
142      * system if the JRE implementation supports it. For example,
143      * <pre>
144      * NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
145      * </pre>
146      * This may return a {@code NumberFormat} instance with the Thai numbering system,
147      * instead of the Latin numbering system.
148      *
149      * @param locale the desired locale.
150      * @return a <code>DecimalFormatSymbols</code> instance.
151      * @exception NullPointerException if <code>locale</code> is null
152      * @since 1.6
153      */
getInstance(Locale locale)154     public static final DecimalFormatSymbols getInstance(Locale locale) {
155         // Android-changed: Removed used of DecimalFormatSymbolsProvider.
156         return new DecimalFormatSymbols(locale);
157     }
158 
159     /**
160      * Gets the character used for zero. Different for Arabic, etc.
161      *
162      * @return the character used for zero
163      */
getZeroDigit()164     public char getZeroDigit() {
165         return zeroDigit;
166     }
167 
168     /**
169      * Sets the character used for zero. Different for Arabic, etc.
170      *
171      * @param zeroDigit the character used for zero
172      */
setZeroDigit(char zeroDigit)173     public void setZeroDigit(char zeroDigit) {
174         this.zeroDigit = zeroDigit;
175         // Android-added: reset cachedIcuDFS.
176         cachedIcuDFS = null;
177     }
178 
179     /**
180      * Gets the character used for thousands separator. Different for French, etc.
181      *
182      * @return the grouping separator
183      */
getGroupingSeparator()184     public char getGroupingSeparator() {
185         return groupingSeparator;
186     }
187 
188     /**
189      * Sets the character used for thousands separator. Different for French, etc.
190      *
191      * @param groupingSeparator the grouping separator
192      */
setGroupingSeparator(char groupingSeparator)193     public void setGroupingSeparator(char groupingSeparator) {
194         this.groupingSeparator = groupingSeparator;
195         // Android-added: reset cachedIcuDFS.
196         cachedIcuDFS = null;
197     }
198 
199     /**
200      * Gets the character used for decimal sign. Different for French, etc.
201      *
202      * @return the character used for decimal sign
203      */
getDecimalSeparator()204     public char getDecimalSeparator() {
205         return decimalSeparator;
206     }
207 
208     /**
209      * Sets the character used for decimal sign. Different for French, etc.
210      *
211      * @param decimalSeparator the character used for decimal sign
212      */
setDecimalSeparator(char decimalSeparator)213     public void setDecimalSeparator(char decimalSeparator) {
214         this.decimalSeparator = decimalSeparator;
215         // Android-added: reset cachedIcuDFS.
216         cachedIcuDFS = null;
217     }
218 
219     /**
220      * Gets the character used for per mille sign. Different for Arabic, etc.
221      *
222      * @return the character used for per mille sign
223      */
getPerMill()224     public char getPerMill() {
225         return perMill;
226     }
227 
228     /**
229      * Sets the character used for per mille sign. Different for Arabic, etc.
230      *
231      * @param perMill the character used for per mille sign
232      */
setPerMill(char perMill)233     public void setPerMill(char perMill) {
234         this.perMill = perMill;
235         // Android-added: reset cachedIcuDFS.
236         cachedIcuDFS = null;
237     }
238 
239     /**
240      * Gets the character used for percent sign. Different for Arabic, etc.
241      *
242      * @return the character used for percent sign
243      */
getPercent()244     public char getPercent() {
245         return percent;
246     }
247 
248     // Android-added: getPercentString() for percent signs longer than one char.
249     /**
250      * Gets the string used for percent sign. Different for Arabic, etc.
251      *
252      * @hide
253      */
getPercentString()254     public String getPercentString() {
255         return String.valueOf(percent);
256     }
257 
258     /**
259      * Sets the character used for percent sign. Different for Arabic, etc.
260      *
261      * @param percent the character used for percent sign
262      */
setPercent(char percent)263     public void setPercent(char percent) {
264         this.percent = percent;
265         // Android-added: reset cachedIcuDFS.
266         cachedIcuDFS = null;
267     }
268 
269     /**
270      * Gets the character used for a digit in a pattern.
271      *
272      * @return the character used for a digit in a pattern
273      */
getDigit()274     public char getDigit() {
275         return digit;
276     }
277 
278     /**
279      * Sets the character used for a digit in a pattern.
280      *
281      * @param digit the character used for a digit in a pattern
282      */
setDigit(char digit)283     public void setDigit(char digit) {
284         this.digit = digit;
285         // Android-added: reset cachedIcuDFS.
286         cachedIcuDFS = null;
287     }
288 
289     /**
290      * Gets the character used to separate positive and negative subpatterns
291      * in a pattern.
292      *
293      * @return the pattern separator
294      */
getPatternSeparator()295     public char getPatternSeparator() {
296         return patternSeparator;
297     }
298 
299     /**
300      * Sets the character used to separate positive and negative subpatterns
301      * in a pattern.
302      *
303      * @param patternSeparator the pattern separator
304      */
setPatternSeparator(char patternSeparator)305     public void setPatternSeparator(char patternSeparator) {
306         this.patternSeparator = patternSeparator;
307         // Android-added: reset cachedIcuDFS.
308         cachedIcuDFS = null;
309     }
310 
311     /**
312      * Gets the string used to represent infinity. Almost always left
313      * unchanged.
314      *
315      * @return the string representing infinity
316      */
getInfinity()317     public String getInfinity() {
318         return infinity;
319     }
320 
321     /**
322      * Sets the string used to represent infinity. Almost always left
323      * unchanged.
324      *
325      * @param infinity the string representing infinity
326      */
setInfinity(String infinity)327     public void setInfinity(String infinity) {
328         this.infinity = infinity;
329         // Android-added: reset cachedIcuDFS.
330         cachedIcuDFS = null;
331     }
332 
333     /**
334      * Gets the string used to represent "not a number". Almost always left
335      * unchanged.
336      *
337      * @return the string representing "not a number"
338      */
getNaN()339     public String getNaN() {
340         return NaN;
341     }
342 
343     /**
344      * Sets the string used to represent "not a number". Almost always left
345      * unchanged.
346      *
347      * @param NaN the string representing "not a number"
348      */
setNaN(String NaN)349     public void setNaN(String NaN) {
350         this.NaN = NaN;
351         // Android-added: reset cachedIcuDFS.
352         cachedIcuDFS = null;
353     }
354 
355     /**
356      * Gets the character used to represent minus sign. If no explicit
357      * negative format is specified, one is formed by prefixing
358      * minusSign to the positive format.
359      *
360      * @return the character representing minus sign
361      */
getMinusSign()362     public char getMinusSign() {
363         return minusSign;
364     }
365 
366 
367     // Android-added: getPercentString() for percent signs longer than one char.
368     /**
369      * Gets the string used to represent minus sign. If no explicit
370      * negative format is specified, one is formed by prefixing
371      * minusSign to the positive format.
372      *
373      * @hide
374      */
getMinusSignString()375     public String getMinusSignString() {
376         return String.valueOf(minusSign);
377     }
378 
379     /**
380      * Sets the character used to represent minus sign. If no explicit
381      * negative format is specified, one is formed by prefixing
382      * minusSign to the positive format.
383      *
384      * @param minusSign the character representing minus sign
385      */
setMinusSign(char minusSign)386     public void setMinusSign(char minusSign) {
387         this.minusSign = minusSign;
388         // Android-added: reset cachedIcuDFS.
389         cachedIcuDFS = null;
390     }
391 
392     /**
393      * Returns the currency symbol for the currency of these
394      * DecimalFormatSymbols in their locale.
395      *
396      * @return the currency symbol
397      * @since 1.2
398      */
getCurrencySymbol()399     public String getCurrencySymbol()
400     {
401         return currencySymbol;
402     }
403 
404     /**
405      * Sets the currency symbol for the currency of these
406      * DecimalFormatSymbols in their locale.
407      *
408      * @param currency the currency symbol
409      * @since 1.2
410      */
setCurrencySymbol(String currency)411     public void setCurrencySymbol(String currency)
412     {
413         currencySymbol = currency;
414         // Android-added: reset cachedIcuDFS.
415         cachedIcuDFS = null;
416     }
417 
418     /**
419      * Returns the ISO 4217 currency code of the currency of these
420      * DecimalFormatSymbols.
421      *
422      * @return the currency code
423      * @since 1.2
424      */
getInternationalCurrencySymbol()425     public String getInternationalCurrencySymbol()
426     {
427         return intlCurrencySymbol;
428     }
429 
430     /**
431      * Sets the ISO 4217 currency code of the currency of these
432      * DecimalFormatSymbols.
433      * If the currency code is valid (as defined by
434      * {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}),
435      * this also sets the currency attribute to the corresponding Currency
436      * instance and the currency symbol attribute to the currency's symbol
437      * in the DecimalFormatSymbols' locale. If the currency code is not valid,
438      * then the currency attribute is set to null and the currency symbol
439      * attribute is not modified.
440      *
441      * @param currencyCode the currency code
442      * @see #setCurrency
443      * @see #setCurrencySymbol
444      * @since 1.2
445      */
setInternationalCurrencySymbol(String currencyCode)446     public void setInternationalCurrencySymbol(String currencyCode)
447     {
448         intlCurrencySymbol = currencyCode;
449         currency = null;
450         if (currencyCode != null) {
451             try {
452                 currency = Currency.getInstance(currencyCode);
453                 // Android-changed: get currencySymbol for locale.
454                 currencySymbol = currency.getSymbol(locale);
455             } catch (IllegalArgumentException e) {
456             }
457         }
458         // Android-added: reset cachedIcuDFS.
459         cachedIcuDFS = null;
460     }
461 
462     /**
463      * Gets the currency of these DecimalFormatSymbols. May be null if the
464      * currency symbol attribute was previously set to a value that's not
465      * a valid ISO 4217 currency code.
466      *
467      * @return the currency used, or null
468      * @since 1.4
469      */
getCurrency()470     public Currency getCurrency() {
471         return currency;
472     }
473 
474     /**
475      * Sets the currency of these DecimalFormatSymbols.
476      * This also sets the currency symbol attribute to the currency's symbol
477      * in the DecimalFormatSymbols' locale, and the international currency
478      * symbol attribute to the currency's ISO 4217 currency code.
479      *
480      * @param currency the new currency to be used
481      * @exception NullPointerException if <code>currency</code> is null
482      * @since 1.4
483      * @see #setCurrencySymbol
484      * @see #setInternationalCurrencySymbol
485      */
setCurrency(Currency currency)486     public void setCurrency(Currency currency) {
487         if (currency == null) {
488             throw new NullPointerException();
489         }
490         this.currency = currency;
491         intlCurrencySymbol = currency.getCurrencyCode();
492         currencySymbol = currency.getSymbol(locale);
493         // Android-added: reset cachedIcuDFS.
494         cachedIcuDFS = null;
495     }
496 
497 
498     /**
499      * Returns the monetary decimal separator.
500      *
501      * @return the monetary decimal separator
502      * @since 1.2
503      */
getMonetaryDecimalSeparator()504     public char getMonetaryDecimalSeparator()
505     {
506         return monetarySeparator;
507     }
508 
509     /**
510      * Sets the monetary decimal separator.
511      *
512      * @param sep the monetary decimal separator
513      * @since 1.2
514      */
setMonetaryDecimalSeparator(char sep)515     public void setMonetaryDecimalSeparator(char sep)
516     {
517         monetarySeparator = sep;
518         // Android-added: reset cachedIcuDFS.
519         cachedIcuDFS = null;
520     }
521 
522     //------------------------------------------------------------
523     // BEGIN   Package Private methods ... to be made public later
524     //------------------------------------------------------------
525 
526     /**
527      * Returns the character used to separate the mantissa from the exponent.
528      */
getExponentialSymbol()529     char getExponentialSymbol()
530     {
531         return exponential;
532     }
533   /**
534    * Returns the string used to separate the mantissa from the exponent.
535    * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
536    *
537    * @return the exponent separator string
538    * @see #setExponentSeparator(java.lang.String)
539    * @since 1.6
540    */
getExponentSeparator()541     public String getExponentSeparator()
542     {
543         return exponentialSeparator;
544     }
545 
546     /**
547      * Sets the character used to separate the mantissa from the exponent.
548      */
setExponentialSymbol(char exp)549     void setExponentialSymbol(char exp)
550     {
551         exponential = exp;
552         // Android-added: reset cachedIcuDFS.
553         cachedIcuDFS = null;
554     }
555 
556   /**
557    * Sets the string used to separate the mantissa from the exponent.
558    * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
559    *
560    * @param exp the exponent separator string
561    * @exception NullPointerException if <code>exp</code> is null
562    * @see #getExponentSeparator()
563    * @since 1.6
564    */
setExponentSeparator(String exp)565     public void setExponentSeparator(String exp)
566     {
567         if (exp == null) {
568             throw new NullPointerException();
569         }
570         exponentialSeparator = exp;
571      }
572 
573 
574     //------------------------------------------------------------
575     // END     Package Private methods ... to be made public later
576     //------------------------------------------------------------
577 
578     /**
579      * Standard override.
580      */
581     @Override
clone()582     public Object clone() {
583         try {
584             return (DecimalFormatSymbols)super.clone();
585             // other fields are bit-copied
586         } catch (CloneNotSupportedException e) {
587             throw new InternalError(e);
588         }
589     }
590 
591     /**
592      * Override equals.
593      */
594     @Override
equals(Object obj)595     public boolean equals(Object obj) {
596         if (obj == null) return false;
597         if (this == obj) return true;
598         if (getClass() != obj.getClass()) return false;
599         DecimalFormatSymbols other = (DecimalFormatSymbols) obj;
600         return (zeroDigit == other.zeroDigit &&
601         groupingSeparator == other.groupingSeparator &&
602         decimalSeparator == other.decimalSeparator &&
603         percent == other.percent &&
604         perMill == other.perMill &&
605         digit == other.digit &&
606         minusSign == other.minusSign &&
607         patternSeparator == other.patternSeparator &&
608         infinity.equals(other.infinity) &&
609         NaN.equals(other.NaN) &&
610         currencySymbol.equals(other.currencySymbol) &&
611         intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
612         currency == other.currency &&
613         monetarySeparator == other.monetarySeparator &&
614         exponentialSeparator.equals(other.exponentialSeparator) &&
615         locale.equals(other.locale));
616     }
617 
618     /**
619      * Override hashCode.
620      */
621     @Override
hashCode()622     public int hashCode() {
623             int result = zeroDigit;
624             result = result * 37 + groupingSeparator;
625             result = result * 37 + decimalSeparator;
626             // BEGIN Android-added: more fields in hashcode calculation.
627             result = result * 37 + percent;
628             result = result * 37 + perMill;
629             result = result * 37 + digit;
630             result = result * 37 + minusSign;
631             result = result * 37 + patternSeparator;
632             result = result * 37 + infinity.hashCode();
633             result = result * 37 + NaN.hashCode();
634             result = result * 37 + currencySymbol.hashCode();
635             result = result * 37 + intlCurrencySymbol.hashCode();
636             result = result * 37 + currency.hashCode();
637             result = result * 37 + monetarySeparator;
638             result = result * 37 + exponentialSeparator.hashCode();
639             result = result * 37 + locale.hashCode();
640            // END Android-added: more fields in hashcode calculation.
641             return result;
642     }
643 
644     /**
645      * Initializes the symbols from the FormatData resource bundle.
646      */
initialize( Locale locale )647     private void initialize( Locale locale ) {
648         this.locale = locale;
649 
650         // BEGIN Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
651         /*
652         // get resource bundle data
653         LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
654         // Avoid potential recursions
655         if (!(adapter instanceof ResourceBundleBasedAdapter)) {
656             adapter = LocaleProviderAdapter.getResourceBundleBased();
657         }
658         Object[] data = adapter.getLocaleResources(locale).getDecimalFormatSymbolsData();
659         */
660         if (locale == null) {
661             throw new NullPointerException("locale");
662         }
663         locale = LocaleData.mapInvalidAndNullLocales(locale);
664         LocaleData localeData = LocaleData.get(locale);
665         Object[] data = new Object[3];
666         String[] values = new String[11];
667         values[0] = String.valueOf(localeData.decimalSeparator);
668         values[1] = String.valueOf(localeData.groupingSeparator);
669         values[2] = String.valueOf(localeData.patternSeparator);
670         values[3] = localeData.percent;
671         values[4] = String.valueOf(localeData.zeroDigit);
672         values[5] = "#";
673         values[6] = localeData.minusSign;
674         values[7] = localeData.exponentSeparator;
675         values[8] = localeData.perMill;
676         values[9] = localeData.infinity;
677         values[10] = localeData.NaN;
678         data[0] = values;
679         // END Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
680 
681         String[] numberElements = (String[]) data[0];
682 
683         // Android-changed: Added maybeStripMarkers
684         decimalSeparator = numberElements[0].charAt(0);
685         groupingSeparator = numberElements[1].charAt(0);
686         patternSeparator = numberElements[2].charAt(0);
687         percent = maybeStripMarkers(numberElements[3], '%');
688         zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc.
689         digit = numberElements[5].charAt(0);
690         minusSign = maybeStripMarkers(numberElements[6], '-');
691         exponential = numberElements[7].charAt(0);
692         exponentialSeparator = numberElements[7]; //string representation new since 1.6
693         perMill = maybeStripMarkers(numberElements[8], '\u2030');
694         infinity  = numberElements[9];
695         NaN = numberElements[10];
696 
697         // Try to obtain the currency used in the locale's country.
698         // Check for empty country string separately because it's a valid
699         // country ID for Locale (and used for the C locale), but not a valid
700         // ISO 3166 country code, and exceptions are expensive.
701         if (locale.getCountry().length() > 0) {
702             try {
703                 currency = Currency.getInstance(locale);
704             } catch (IllegalArgumentException e) {
705                 // use default values below for compatibility
706             }
707         }
708         if (currency != null) {
709             intlCurrencySymbol = currency.getCurrencyCode();
710             if (data[1] != null && data[1] == intlCurrencySymbol) {
711                 currencySymbol = (String) data[2];
712             } else {
713                 currencySymbol = currency.getSymbol(locale);
714                 data[1] = intlCurrencySymbol;
715                 data[2] = currencySymbol;
716             }
717         } else {
718             // default values
719             intlCurrencySymbol = "XXX";
720             try {
721                 currency = Currency.getInstance(intlCurrencySymbol);
722             } catch (IllegalArgumentException e) {
723             }
724             currencySymbol = "\u00A4";
725         }
726         // Currently the monetary decimal separator is the same as the
727         // standard decimal separator for all locales that we support.
728         // If that changes, add a new entry to NumberElements.
729         monetarySeparator = decimalSeparator;
730     }
731 
732     // Android-changed: maybeStripMarkers added in b/26207216, fixed in b/32465689.
733     /**
734      * Attempts to strip RTL, LTR and Arabic letter markers from {@code symbol}.
735      * If the string contains a single non-marker character (and any number of marker characters),
736      * then that character is returned, otherwise {@code fallback} is returned.
737      *
738      * @hide
739      */
740     // VisibleForTesting
maybeStripMarkers(String symbol, char fallback)741     public static char maybeStripMarkers(String symbol, char fallback) {
742         final int length = symbol.length();
743         if (length >= 1) {
744             boolean sawNonMarker = false;
745             char nonMarker = 0;
746             for (int i = 0; i < length; i++) {
747                 final char c = symbol.charAt(i);
748                 if (c == '\u200E' || c == '\u200F' || c == '\u061C') {
749                     continue;
750                 }
751                 if (sawNonMarker) {
752                     // More than one non-marker character.
753                     return fallback;
754                 }
755                 sawNonMarker = true;
756                 nonMarker = c;
757             }
758             if (sawNonMarker) {
759                 return nonMarker;
760             }
761         }
762         return fallback;
763     }
764 
765     // BEGIN Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
766     /**
767      * Convert an instance of this class to the ICU version so that it can be used with ICU4J.
768      * @hide
769      */
getIcuDecimalFormatSymbols()770     protected android.icu.text.DecimalFormatSymbols getIcuDecimalFormatSymbols() {
771         if (cachedIcuDFS != null) {
772             return cachedIcuDFS;
773         }
774 
775         cachedIcuDFS = new android.icu.text.DecimalFormatSymbols(this.locale);
776         // Do not localize plus sign. See "Special Pattern Characters" section in DecimalFormat.
777         // http://b/67034519
778         cachedIcuDFS.setPlusSign('+');
779         cachedIcuDFS.setZeroDigit(zeroDigit);
780         cachedIcuDFS.setDigit(digit);
781         cachedIcuDFS.setDecimalSeparator(decimalSeparator);
782         cachedIcuDFS.setGroupingSeparator(groupingSeparator);
783         // {@link #setGroupingSeparator(char)} should set grouping separator for currency, but
784         // ICU has a separate API setMonetaryGroupingSeparator. Need to call it explicitly here.
785         // http://b/38021063
786         cachedIcuDFS.setMonetaryGroupingSeparator(groupingSeparator);
787         cachedIcuDFS.setPatternSeparator(patternSeparator);
788         cachedIcuDFS.setPercent(percent);
789         cachedIcuDFS.setPerMill(perMill);
790         cachedIcuDFS.setMonetaryDecimalSeparator(monetarySeparator);
791         cachedIcuDFS.setMinusSign(minusSign);
792         cachedIcuDFS.setInfinity(infinity);
793         cachedIcuDFS.setNaN(NaN);
794         cachedIcuDFS.setExponentSeparator(exponentialSeparator);
795 
796         try {
797             cachedIcuDFS.setCurrency(
798                     android.icu.util.Currency.getInstance(currency.getCurrencyCode()));
799         } catch (NullPointerException e) {
800             currency = Currency.getInstance("XXX");
801         }
802 
803         cachedIcuDFS.setCurrencySymbol(currencySymbol);
804         cachedIcuDFS.setInternationalCurrencySymbol(intlCurrencySymbol);
805 
806         return cachedIcuDFS;
807     }
808 
809     /**
810      * Create an instance of DecimalFormatSymbols using the ICU equivalent of this class.
811      * @hide
812      */
fromIcuInstance( android.icu.text.DecimalFormatSymbols dfs)813     protected static DecimalFormatSymbols fromIcuInstance(
814             android.icu.text.DecimalFormatSymbols dfs) {
815         DecimalFormatSymbols result = new DecimalFormatSymbols(dfs.getLocale());
816         result.setZeroDigit(dfs.getZeroDigit());
817         result.setDigit(dfs.getDigit());
818         result.setDecimalSeparator(dfs.getDecimalSeparator());
819         result.setGroupingSeparator(dfs.getGroupingSeparator());
820         result.setPatternSeparator(dfs.getPatternSeparator());
821         result.setPercent(dfs.getPercent());
822         result.setPerMill(dfs.getPerMill());
823         result.setMonetaryDecimalSeparator(dfs.getMonetaryDecimalSeparator());
824         result.setMinusSign(dfs.getMinusSign());
825         result.setInfinity(dfs.getInfinity());
826         result.setNaN(dfs.getNaN());
827         result.setExponentSeparator(dfs.getExponentSeparator());
828 
829         try {
830             if (dfs.getCurrency() != null) {
831                 result.setCurrency(Currency.getInstance(dfs.getCurrency().getCurrencyCode()));
832             } else {
833                 result.setCurrency(Currency.getInstance("XXX"));
834             }
835         } catch (IllegalArgumentException e) {
836             result.setCurrency(Currency.getInstance("XXX"));
837         }
838 
839         result.setInternationalCurrencySymbol(dfs.getInternationalCurrencySymbol());
840         result.setCurrencySymbol(dfs.getCurrencySymbol());
841         return result;
842     }
843     // END Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
844 
845     // BEGIN Android-added: Android specific serialization code.
846     private static final ObjectStreamField[] serialPersistentFields = {
847             new ObjectStreamField("currencySymbol", String.class),
848             new ObjectStreamField("decimalSeparator", char.class),
849             new ObjectStreamField("digit", char.class),
850             new ObjectStreamField("exponential", char.class),
851             new ObjectStreamField("exponentialSeparator", String.class),
852             new ObjectStreamField("groupingSeparator", char.class),
853             new ObjectStreamField("infinity", String.class),
854             new ObjectStreamField("intlCurrencySymbol", String.class),
855             new ObjectStreamField("minusSign", char.class),
856             new ObjectStreamField("monetarySeparator", char.class),
857             new ObjectStreamField("NaN", String.class),
858             new ObjectStreamField("patternSeparator", char.class),
859             new ObjectStreamField("percent", char.class),
860             new ObjectStreamField("perMill", char.class),
861             new ObjectStreamField("serialVersionOnStream", int.class),
862             new ObjectStreamField("zeroDigit", char.class),
863             new ObjectStreamField("locale", Locale.class),
864             new ObjectStreamField("minusSignStr", String.class),
865             new ObjectStreamField("percentStr", String.class),
866     };
867 
writeObject(ObjectOutputStream stream)868     private void writeObject(ObjectOutputStream stream) throws IOException {
869         ObjectOutputStream.PutField fields = stream.putFields();
870         fields.put("currencySymbol", currencySymbol);
871         fields.put("decimalSeparator", getDecimalSeparator());
872         fields.put("digit", getDigit());
873         fields.put("exponential", exponentialSeparator.charAt(0));
874         fields.put("exponentialSeparator", exponentialSeparator);
875         fields.put("groupingSeparator", getGroupingSeparator());
876         fields.put("infinity", infinity);
877         fields.put("intlCurrencySymbol", intlCurrencySymbol);
878         fields.put("monetarySeparator", getMonetaryDecimalSeparator());
879         fields.put("NaN", NaN);
880         fields.put("patternSeparator", getPatternSeparator());
881         fields.put("perMill", getPerMill());
882         fields.put("serialVersionOnStream", 3);
883         fields.put("zeroDigit", getZeroDigit());
884         fields.put("locale", locale);
885 
886         // Hardcode values here for backwards compatibility. These values will only be used
887         // if we're de-serializing this object on an earlier version of android.
888         fields.put("minusSign", minusSign);
889         fields.put("percent", percent);
890 
891         fields.put("minusSignStr", getMinusSignString());
892         fields.put("percentStr", getPercentString());
893         stream.writeFields();
894     }
895     // END Android-added: Android specific serialization code.
896 
897     /**
898      * Reads the default serializable fields, provides default values for objects
899      * in older serial versions, and initializes non-serializable fields.
900      * If <code>serialVersionOnStream</code>
901      * is less than 1, initializes <code>monetarySeparator</code> to be
902      * the same as <code>decimalSeparator</code> and <code>exponential</code>
903      * to be 'E'.
904      * If <code>serialVersionOnStream</code> is less than 2,
905      * initializes <code>locale</code>to the root locale, and initializes
906      * If <code>serialVersionOnStream</code> is less than 3, it initializes
907      * <code>exponentialSeparator</code> using <code>exponential</code>.
908      * Sets <code>serialVersionOnStream</code> back to the maximum allowed value so that
909      * default serialization will work properly if this object is streamed out again.
910      * Initializes the currency from the intlCurrencySymbol field.
911      *
912      * @since JDK 1.1.6
913      */
readObject(ObjectInputStream stream)914     private void readObject(ObjectInputStream stream)
915             throws IOException, ClassNotFoundException {
916         // BEGIN Android-changed: Android specific serialization code.
917         ObjectInputStream.GetField fields = stream.readFields();
918         final int serialVersionOnStream = fields.get("serialVersionOnStream", 0);
919         currencySymbol = (String) fields.get("currencySymbol", "");
920         setDecimalSeparator(fields.get("decimalSeparator", '.'));
921         setDigit(fields.get("digit", '#'));
922         setGroupingSeparator(fields.get("groupingSeparator", ','));
923         infinity = (String) fields.get("infinity", "");
924         intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", "");
925         NaN = (String) fields.get("NaN", "");
926         setPatternSeparator(fields.get("patternSeparator", ';'));
927 
928         // Special handling for minusSign and percent. If we've serialized the string versions of
929         // these fields, use them. If not, fall back to the single character versions. This can
930         // only happen if we're de-serializing an object that was written by an older version of
931         // android (something that's strongly discouraged anyway).
932         final String minusSignStr = (String) fields.get("minusSignStr", null);
933         if (minusSignStr != null) {
934             minusSign = minusSignStr.charAt(0);
935         } else {
936             setMinusSign(fields.get("minusSign", '-'));
937         }
938         final String percentStr = (String) fields.get("percentStr", null);
939         if (percentStr != null) {
940             percent = percentStr.charAt(0);
941         } else {
942             setPercent(fields.get("percent", '%'));
943         }
944 
945         setPerMill(fields.get("perMill", '\u2030'));
946         setZeroDigit(fields.get("zeroDigit", '0'));
947         locale = (Locale) fields.get("locale", null);
948         if (serialVersionOnStream == 0) {
949             setMonetaryDecimalSeparator(getDecimalSeparator());
950         } else {
951             setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.'));
952         }
953 
954         if (serialVersionOnStream == 0) {
955             // Prior to Java 1.1.6, the exponent separator wasn't configurable.
956             exponentialSeparator = "E";
957         } else if (serialVersionOnStream < 3) {
958             // In Javas 1.1.6 and 1.4, there was a character field "exponential".
959             setExponentSeparator(String.valueOf(fields.get("exponential", 'E')));
960         } else {
961             // In Java 6, there's a new "exponentialSeparator" field.
962             setExponentSeparator((String) fields.get("exponentialSeparator", "E"));
963         }
964 
965         try {
966             currency = Currency.getInstance(intlCurrencySymbol);
967         } catch (IllegalArgumentException e) {
968             currency = null;
969         }
970         // END Android-changed: Android specific serialization code.
971     }
972 
973     /**
974      * Character used for zero.
975      *
976      * @serial
977      * @see #getZeroDigit
978      */
979     private  char    zeroDigit;
980 
981     /**
982      * Character used for thousands separator.
983      *
984      * @serial
985      * @see #getGroupingSeparator
986      */
987     private  char    groupingSeparator;
988 
989     /**
990      * Character used for decimal sign.
991      *
992      * @serial
993      * @see #getDecimalSeparator
994      */
995     private  char    decimalSeparator;
996 
997     /**
998      * Character used for per mille sign.
999      *
1000      * @serial
1001      * @see #getPerMill
1002      */
1003     private  char    perMill;
1004 
1005     /**
1006      * Character used for percent sign.
1007      * @serial
1008      * @see #getPercent
1009      */
1010     private  char    percent;
1011 
1012     /**
1013      * Character used for a digit in a pattern.
1014      *
1015      * @serial
1016      * @see #getDigit
1017      */
1018     private  char    digit;
1019 
1020     /**
1021      * Character used to separate positive and negative subpatterns
1022      * in a pattern.
1023      *
1024      * @serial
1025      * @see #getPatternSeparator
1026      */
1027     private  char    patternSeparator;
1028 
1029     /**
1030      * String used to represent infinity.
1031      * @serial
1032      * @see #getInfinity
1033      */
1034     private  String  infinity;
1035 
1036     /**
1037      * String used to represent "not a number".
1038      * @serial
1039      * @see #getNaN
1040      */
1041     private  String  NaN;
1042 
1043     /**
1044      * Character used to represent minus sign.
1045      * @serial
1046      * @see #getMinusSign
1047      */
1048     private  char    minusSign;
1049 
1050     /**
1051      * String denoting the local currency, e.g. "$".
1052      * @serial
1053      * @see #getCurrencySymbol
1054      */
1055     private  String  currencySymbol;
1056 
1057     /**
1058      * ISO 4217 currency code denoting the local currency, e.g. "USD".
1059      * @serial
1060      * @see #getInternationalCurrencySymbol
1061      */
1062     private  String  intlCurrencySymbol;
1063 
1064     /**
1065      * The decimal separator used when formatting currency values.
1066      * @serial
1067      * @since JDK 1.1.6
1068      * @see #getMonetaryDecimalSeparator
1069      */
1070     private  char    monetarySeparator; // Field new in JDK 1.1.6
1071 
1072     /**
1073      * The character used to distinguish the exponent in a number formatted
1074      * in exponential notation, e.g. 'E' for a number such as "1.23E45".
1075      * <p>
1076      * Note that the public API provides no way to set this field,
1077      * even though it is supported by the implementation and the stream format.
1078      * The intent is that this will be added to the API in the future.
1079      *
1080      * @serial
1081      * @since JDK 1.1.6
1082      */
1083     private  char    exponential;       // Field new in JDK 1.1.6
1084 
1085   /**
1086    * The string used to separate the mantissa from the exponent.
1087    * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
1088    * <p>
1089    * If both <code>exponential</code> and <code>exponentialSeparator</code>
1090    * exist, this <code>exponentialSeparator</code> has the precedence.
1091    *
1092    * @serial
1093    * @since 1.6
1094    */
1095     private  String    exponentialSeparator;       // Field new in JDK 1.6
1096 
1097     /**
1098      * The locale of these currency format symbols.
1099      *
1100      * @serial
1101      * @since 1.4
1102      */
1103     private Locale locale;
1104 
1105     // currency; only the ISO code is serialized.
1106     private transient Currency currency;
1107 
1108     // Proclaim JDK 1.1 FCS compatibility
1109     static final long serialVersionUID = 5772796243397350300L;
1110 
1111     // The internal serial version which says which version was written
1112     // - 0 (default) for version up to JDK 1.1.5
1113     // - 1 for version from JDK 1.1.6, which includes two new fields:
1114     //     monetarySeparator and exponential.
1115     // - 2 for version from J2SE 1.4, which includes locale field.
1116     // - 3 for version from J2SE 1.6, which includes exponentialSeparator field.
1117     private static final int currentSerialVersion = 3;
1118 
1119     /**
1120      * Describes the version of <code>DecimalFormatSymbols</code> present on the stream.
1121      * Possible values are:
1122      * <ul>
1123      * <li><b>0</b> (or uninitialized): versions prior to JDK 1.1.6.
1124      *
1125      * <li><b>1</b>: Versions written by JDK 1.1.6 or later, which include
1126      *      two new fields: <code>monetarySeparator</code> and <code>exponential</code>.
1127      * <li><b>2</b>: Versions written by J2SE 1.4 or later, which include a
1128      *      new <code>locale</code> field.
1129      * <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a
1130      *      new <code>exponentialSeparator</code> field.
1131      * </ul>
1132      * When streaming out a <code>DecimalFormatSymbols</code>, the most recent format
1133      * (corresponding to the highest allowable <code>serialVersionOnStream</code>)
1134      * is always written.
1135      *
1136      * @serial
1137      * @since JDK 1.1.6
1138      */
1139     private int serialVersionOnStream = currentSerialVersion;
1140 
1141     // BEGIN Android-added: cache for cachedIcuDFS.
1142     /**
1143      * Lazily created cached instance of an ICU DecimalFormatSymbols that's equivalent to this one.
1144      * This field is reset to null whenever any of the relevant fields of this class are modified
1145      * and will be re-created by {@link #getIcuDecimalFormatSymbols()} as necessary.
1146      */
1147     private transient android.icu.text.DecimalFormatSymbols cachedIcuDFS = null;
1148     // END Android-added: cache for cachedIcuDFS.
1149 }
1150