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