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