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