Home | History | Annotate | Download | only in text
      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, 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