script-astra/Android/Sdk/sources/android-35/java/text/DecimalFormatSymbols.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

1515 lines
55 KiB
Java

/*
* Copyright (C) 2014 The Android Open Source Project
* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
*
* The original version of this source code and documentation is copyrighted
* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
* materials are provided under terms of a License Agreement between Taligent
* and Sun. This technology is protected by multiple US and International
* patents. This notice and attribution to Taligent may not be removed.
* Taligent is a registered trademark of Taligent, Inc.
*
*/
package java.text;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.util.Currency;
import java.util.Locale;
import java.util.Objects;
import libcore.icu.DecimalFormatData;
import libcore.icu.ICU;
import libcore.icu.LocaleData;
// Android-removed: Remove javadoc related to "rg" Locale extension.
// The "rg" extension isn't supported until https://unicode-org.atlassian.net/browse/ICU-21831
// is resolved, because java.text.* stack relies on ICU on resource resolution.
/**
* This class represents the set of symbols (such as the decimal separator,
* the grouping separator, and so on) needed by {@code DecimalFormat}
* to format numbers. {@code DecimalFormat} creates for itself an instance of
* {@code DecimalFormatSymbols} from its locale data. If you need to change any
* of these symbols, you can get the {@code DecimalFormatSymbols} object from
* your {@code DecimalFormat} and modify it.
*
* @see java.util.Locale
* @see DecimalFormat
* @author Mark Davis
* @author Alan Liu
* @since 1.1
*/
public class DecimalFormatSymbols implements Cloneable, Serializable {
// Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
/**
* Create a DecimalFormatSymbols object for the default
* {@link java.util.Locale.Category#FORMAT FORMAT} locale.
* It is recommended that the {@link #getInstance(Locale) getInstance} method is used
* instead.
* <p>This is equivalent to calling
* {@link #DecimalFormatSymbols(Locale)
* DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT))}.
* @see java.util.Locale#getDefault(java.util.Locale.Category)
* @see java.util.Locale.Category#FORMAT
*/
public DecimalFormatSymbols() {
initialize( Locale.getDefault(Locale.Category.FORMAT) );
}
// Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
/**
* Create a DecimalFormatSymbols object for the given locale.
* It is recommended that the {@link #getInstance(Locale) getInstance} method is used
* instead.
* If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
* for the numbering system, the instance is initialized with the specified numbering
* system if the JRE implementation supports it. For example,
* <pre>
* NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
* </pre>
* This may return a {@code NumberFormat} instance with the Thai numbering system,
* instead of the Latin numbering system.
*
* @param locale the desired locale
* @throws NullPointerException if {@code locale} is null
*/
public DecimalFormatSymbols( Locale locale ) {
initialize( locale );
}
// Android-changed: Removed reference to DecimalFormatSymbolsProvider.
/**
* Returns an array of all locales for which the
* {@code getInstance} methods of this class can return
* localized instances.
*
* It must contain at least a {@code Locale}
* instance equal to {@link java.util.Locale#US Locale.US}.
*
* @return an array of locales for which localized
* {@code DecimalFormatSymbols} instances are available.
* @since 1.6
*/
public static Locale[] getAvailableLocales() {
// Android-changed: Removed used of DecimalFormatSymbolsProvider. Switched to use ICU.
return ICU.getAvailableLocales();
}
// Android-changed: Removed reference to DecimalFormatSymbolsProvider.
/**
* Gets the {@code DecimalFormatSymbols} instance for the default
* locale.
* <p>This is equivalent to calling
* {@link #getInstance(Locale)
* getInstance(Locale.getDefault(Locale.Category.FORMAT))}.
* @see java.util.Locale#getDefault(java.util.Locale.Category)
* @see java.util.Locale.Category#FORMAT
* @return a {@code DecimalFormatSymbols} instance.
* @since 1.6
*/
public static final DecimalFormatSymbols getInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT));
}
// Android-changed: Removed reference to DecimalFormatSymbolsProvider.
/**
* Gets the {@code DecimalFormatSymbols} instance for the specified
* locale.
* If the specified locale contains the {@link java.util.Locale#UNICODE_LOCALE_EXTENSION}
* for the numbering system, the instance is initialized with the specified numbering
* system if the JRE implementation supports it. For example,
* <pre>
* NumberFormat.getNumberInstance(Locale.forLanguageTag("th-TH-u-nu-thai"))
* </pre>
* This may return a {@code NumberFormat} instance with the Thai numbering system,
* instead of the Latin numbering system.
*
* @param locale the desired locale.
* @return a {@code DecimalFormatSymbols} instance.
* @throws NullPointerException if {@code locale} is null
* @since 1.6
*/
public static final DecimalFormatSymbols getInstance(Locale locale) {
// Android-changed: Removed used of DecimalFormatSymbolsProvider.
return new DecimalFormatSymbols(locale);
}
/**
* Gets the character used for zero. Different for Arabic, etc.
*
* @return the character used for zero
*/
public char getZeroDigit() {
return zeroDigit;
}
/**
* Sets the character used for zero. Different for Arabic, etc.
*
* @param zeroDigit the character used for zero
*/
public void setZeroDigit(char zeroDigit) {
hashCode = 0;
this.zeroDigit = zeroDigit;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used for grouping separator. Different for French, etc.
*
* @return the grouping separator
*/
public char getGroupingSeparator() {
return groupingSeparator;
}
/**
* Sets the character used for grouping separator. Different for French, etc.
*
* @param groupingSeparator the grouping separator
*/
public void setGroupingSeparator(char groupingSeparator) {
hashCode = 0;
this.groupingSeparator = groupingSeparator;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used for decimal sign. Different for French, etc.
*
* @return the character used for decimal sign
*/
public char getDecimalSeparator() {
return decimalSeparator;
}
/**
* Sets the character used for decimal sign. Different for French, etc.
*
* @param decimalSeparator the character used for decimal sign
*/
public void setDecimalSeparator(char decimalSeparator) {
hashCode = 0;
this.decimalSeparator = decimalSeparator;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used for per mille sign. Different for Arabic, etc.
*
* @return the character used for per mille sign
*/
public char getPerMill() {
return perMill;
}
/**
* Sets the character used for per mille sign. Different for Arabic, etc.
*
* @param perMill the character used for per mille sign
*/
public void setPerMill(char perMill) {
hashCode = 0;
this.perMill = perMill;
this.perMillText = Character.toString(perMill);
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used for percent sign. Different for Arabic, etc.
*
* @return the character used for percent sign
*/
public char getPercent() {
return percent;
}
// Android-added: getPercentString() for @UnsupportedAppUsage. Use getPercentText() otherwise.
/**
* Gets the string used for percent sign. Different for Arabic, etc.
*
* @hide
*/
public String getPercentString() {
return getPercentText();
}
/**
* Sets the character used for percent sign. Different for Arabic, etc.
*
* @param percent the character used for percent sign
*/
public void setPercent(char percent) {
hashCode = 0;
this.percent = percent;
this.percentText = Character.toString(percent);
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used for a digit in a pattern.
*
* @return the character used for a digit in a pattern
*/
public char getDigit() {
return digit;
}
/**
* Sets the character used for a digit in a pattern.
*
* @param digit the character used for a digit in a pattern
*/
public void setDigit(char digit) {
hashCode = 0;
this.digit = digit;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used to separate positive and negative subpatterns
* in a pattern.
*
* @return the pattern separator
*/
public char getPatternSeparator() {
return patternSeparator;
}
/**
* Sets the character used to separate positive and negative subpatterns
* in a pattern.
*
* @param patternSeparator the pattern separator
*/
public void setPatternSeparator(char patternSeparator) {
hashCode = 0;
this.patternSeparator = patternSeparator;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the string used to represent infinity. Almost always left
* unchanged.
*
* @return the string representing infinity
*/
public String getInfinity() {
return infinity;
}
/**
* Sets the string used to represent infinity. Almost always left
* unchanged.
*
* @param infinity the string representing infinity
*/
public void setInfinity(String infinity) {
hashCode = 0;
this.infinity = infinity;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the string used to represent "not a number". Almost always left
* unchanged.
*
* @return the string representing "not a number"
*/
public String getNaN() {
return NaN;
}
/**
* Sets the string used to represent "not a number". Almost always left
* unchanged.
*
* @param NaN the string representing "not a number"
*/
public void setNaN(String NaN) {
hashCode = 0;
this.NaN = NaN;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used to represent minus sign. If no explicit
* negative format is specified, one is formed by prefixing
* minusSign to the positive format.
*
* @return the character representing minus sign
*/
public char getMinusSign() {
return minusSign;
}
/**
* Sets the character used to represent minus sign. If no explicit
* negative format is specified, one is formed by prefixing
* minusSign to the positive format.
*
* @param minusSign the character representing minus sign
*/
public void setMinusSign(char minusSign) {
hashCode = 0;
this.minusSign = minusSign;
this.minusSignText = Character.toString(minusSign);
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Returns the currency symbol for the currency of these
* DecimalFormatSymbols in their locale.
*
* @return the currency symbol
* @since 1.2
*/
public String getCurrencySymbol()
{
initializeCurrency(locale);
return currencySymbol;
}
/**
* Sets the currency symbol for the currency of these
* DecimalFormatSymbols in their locale.
*
* @param currency the currency symbol
* @since 1.2
*/
public void setCurrencySymbol(String currency)
{
initializeCurrency(locale);
hashCode = 0;
currencySymbol = currency;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Returns the ISO 4217 currency code of the currency of these
* DecimalFormatSymbols.
*
* @return the currency code
* @since 1.2
*/
public String getInternationalCurrencySymbol()
{
initializeCurrency(locale);
return intlCurrencySymbol;
}
/**
* Sets the ISO 4217 currency code of the currency of these
* DecimalFormatSymbols.
* If the currency code is valid (as defined by
* {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}),
* this also sets the currency attribute to the corresponding Currency
* instance and the currency symbol attribute to the currency's symbol
* in the DecimalFormatSymbols' locale. If the currency code is not valid,
* then the currency attribute is set to null and the currency symbol
* attribute is not modified.
*
* @param currencyCode the currency code
* @see #setCurrency
* @see #setCurrencySymbol
* @since 1.2
*/
public void setInternationalCurrencySymbol(String currencyCode)
{
initializeCurrency(locale);
hashCode = 0;
intlCurrencySymbol = currencyCode;
currency = null;
if (currencyCode != null) {
try {
currency = Currency.getInstance(currencyCode);
// Android-changed: get currencySymbol for locale.
currencySymbol = currency.getSymbol(locale);
} catch (IllegalArgumentException e) {
}
}
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the currency of these DecimalFormatSymbols. May be null if the
* currency symbol attribute was previously set to a value that's not
* a valid ISO 4217 currency code.
*
* @return the currency used, or null
* @since 1.4
*/
public Currency getCurrency() {
initializeCurrency(locale);
return currency;
}
/**
* Sets the currency of these DecimalFormatSymbols.
* This also sets the currency symbol attribute to the currency's symbol
* in the DecimalFormatSymbols' locale, and the international currency
* symbol attribute to the currency's ISO 4217 currency code.
*
* @param currency the new currency to be used
* @throws NullPointerException if {@code currency} is null
* @since 1.4
* @see #setCurrencySymbol
* @see #setInternationalCurrencySymbol
*/
public void setCurrency(Currency currency) {
if (currency == null) {
throw new NullPointerException();
}
initializeCurrency(locale);
hashCode = 0;
this.currency = currency;
intlCurrencySymbol = currency.getCurrencyCode();
currencySymbol = currency.getSymbol(locale);
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Returns the monetary decimal separator.
*
* @return the monetary decimal separator
* @since 1.2
*/
public char getMonetaryDecimalSeparator()
{
return monetarySeparator;
}
/**
* Sets the monetary decimal separator.
*
* @param sep the monetary decimal separator
* @since 1.2
*/
public void setMonetaryDecimalSeparator(char sep)
{
hashCode = 0;
monetarySeparator = sep;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Returns the string used to separate the mantissa from the exponent.
* Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
*
* @return the exponent separator string
* @see #setExponentSeparator(java.lang.String)
* @since 1.6
*/
public String getExponentSeparator()
{
return exponentialSeparator;
}
/**
* Sets the string used to separate the mantissa from the exponent.
* Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
*
* @param exp the exponent separator string
* @throws NullPointerException if {@code exp} is null
* @see #getExponentSeparator()
* @since 1.6
*/
public void setExponentSeparator(String exp)
{
if (exp == null) {
throw new NullPointerException();
}
hashCode = 0;
exponentialSeparator = exp;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the character used for grouping separator for currencies.
* May be different from {@code grouping separator} in some locales,
* e.g, German in Austria.
*
* @return the monetary grouping separator
* @since 15
*/
public char getMonetaryGroupingSeparator() {
return monetaryGroupingSeparator;
}
/**
* Sets the character used for grouping separator for currencies.
* Invocation of this method will not affect the normal
* {@code grouping separator}.
*
* @param monetaryGroupingSeparator the monetary grouping separator
* @see #setGroupingSeparator(char)
* @since 15
*/
public void setMonetaryGroupingSeparator(char monetaryGroupingSeparator)
{
hashCode = 0;
this.monetaryGroupingSeparator = monetaryGroupingSeparator;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
//------------------------------------------------------------
// BEGIN Package Private methods ... to be made public later
//------------------------------------------------------------
/**
* Returns the character used to separate the mantissa from the exponent.
*/
char getExponentialSymbol()
{
return exponential;
}
/**
* Sets the character used to separate the mantissa from the exponent.
*/
void setExponentialSymbol(char exp)
{
exponential = exp;
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the string used for per mille sign. Different for Arabic, etc.
*
* @return the string used for per mille sign
* @since 13
*/
String getPerMillText() {
return perMillText;
}
/**
* Sets the string used for per mille sign. Different for Arabic, etc.
*
* Setting the {@code perMillText} affects the return value of
* {@link #getPerMill()}, in which the first non-format character of
* {@code perMillText} is returned.
*
* @param perMillText the string used for per mille sign
* @throws NullPointerException if {@code perMillText} is null
* @throws IllegalArgumentException if {@code perMillText} is an empty string
* @see #getPerMill()
* @see #getPerMillText()
* @since 13
*/
void setPerMillText(String perMillText) {
Objects.requireNonNull(perMillText);
if (perMillText.isEmpty()) {
throw new IllegalArgumentException("Empty argument string");
}
hashCode = 0;
this.perMillText = perMillText;
this.perMill = findNonFormatChar(perMillText, '\u2030');
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the string used for percent sign. Different for Arabic, etc.
*
* @return the string used for percent sign
* @since 13
*/
String getPercentText() {
return percentText;
}
/**
* Sets the string used for percent sign. Different for Arabic, etc.
*
* Setting the {@code percentText} affects the return value of
* {@link #getPercent()}, in which the first non-format character of
* {@code percentText} is returned.
*
* @param percentText the string used for percent sign
* @throws NullPointerException if {@code percentText} is null
* @throws IllegalArgumentException if {@code percentText} is an empty string
* @see #getPercent()
* @see #getPercentText()
* @since 13
*/
void setPercentText(String percentText) {
Objects.requireNonNull(percentText);
if (percentText.isEmpty()) {
throw new IllegalArgumentException("Empty argument string");
}
hashCode = 0;
this.percentText = percentText;
this.percent = findNonFormatChar(percentText, '%');
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
/**
* Gets the string used to represent minus sign. If no explicit
* negative format is specified, one is formed by prefixing
* minusSignText to the positive format.
*
* @return the string representing minus sign
* @since 13
*/
String getMinusSignText() {
return minusSignText;
}
/**
* Sets the string used to represent minus sign. If no explicit
* negative format is specified, one is formed by prefixing
* minusSignText to the positive format.
*
* Setting the {@code minusSignText} affects the return value of
* {@link #getMinusSign()}, in which the first non-format character of
* {@code minusSignText} is returned.
*
* @param minusSignText the character representing minus sign
* @throws NullPointerException if {@code minusSignText} is null
* @throws IllegalArgumentException if {@code minusSignText} is an
* empty string
* @see #getMinusSign()
* @see #getMinusSignText()
* @since 13
*/
void setMinusSignText(String minusSignText) {
Objects.requireNonNull(minusSignText);
if (minusSignText.isEmpty()) {
throw new IllegalArgumentException("Empty argument string");
}
hashCode = 0;
this.minusSignText = minusSignText;
this.minusSign = findNonFormatChar(minusSignText, '-');
// Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
//------------------------------------------------------------
// END Package Private methods ... to be made public later
//------------------------------------------------------------
/**
* Standard override.
*/
@Override
public Object clone() {
try {
return (DecimalFormatSymbols)super.clone();
// other fields are bit-copied
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
/**
* Override equals.
*/
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (this == obj) return true;
if (getClass() != obj.getClass()) return false;
DecimalFormatSymbols other = (DecimalFormatSymbols) obj;
return (zeroDigit == other.zeroDigit &&
groupingSeparator == other.groupingSeparator &&
decimalSeparator == other.decimalSeparator &&
percent == other.percent &&
percentText.equals(other.percentText) &&
perMill == other.perMill &&
perMillText.equals(other.perMillText) &&
digit == other.digit &&
minusSign == other.minusSign &&
minusSignText.equals(other.minusSignText) &&
patternSeparator == other.patternSeparator &&
infinity.equals(other.infinity) &&
NaN.equals(other.NaN) &&
getCurrencySymbol().equals(other.getCurrencySymbol()) && // possible currency init occurs here
intlCurrencySymbol.equals(other.intlCurrencySymbol) &&
currency == other.currency &&
monetarySeparator == other.monetarySeparator &&
monetaryGroupingSeparator == other.monetaryGroupingSeparator &&
exponentialSeparator.equals(other.exponentialSeparator) &&
locale.equals(other.locale));
}
/**
* Override hashCode.
*/
@Override
public int hashCode() {
if (hashCode == 0) {
hashCode = Objects.hash(
zeroDigit,
groupingSeparator,
decimalSeparator,
percent,
percentText,
perMill,
perMillText,
digit,
minusSign,
minusSignText,
patternSeparator,
infinity,
NaN,
getCurrencySymbol(), // possible currency init occurs here
intlCurrencySymbol,
currency,
monetarySeparator,
monetaryGroupingSeparator,
exponentialSeparator,
locale);
}
return hashCode;
}
/**
* Initializes the symbols from the FormatData resource bundle.
*/
private void initialize( Locale locale ) {
this.locale = locale;
// BEGIN Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
/*
// check for region override
Locale override = locale.getUnicodeLocaleType("nu") == null ?
CalendarDataUtility.findRegionOverride(locale) :
locale;
// get resource bundle data
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, override);
// Avoid potential recursions
if (!(adapter instanceof ResourceBundleBasedAdapter)) {
adapter = LocaleProviderAdapter.getResourceBundleBased();
}
Object[] data = adapter.getLocaleResources(override).getDecimalFormatSymbolsData();
String[] numberElements = (String[]) data[0];
*/
if (locale == null) {
throw new NullPointerException("locale");
}
locale = LocaleData.mapInvalidAndNullLocales(locale);
DecimalFormatData decimalFormatData = DecimalFormatData.getInstance(locale);
String[] values = new String[13];
values[0] = String.valueOf(decimalFormatData.getDecimalSeparator());
values[1] = String.valueOf(decimalFormatData.getGroupingSeparator());
values[2] = String.valueOf(decimalFormatData.getPatternSeparator());
values[3] = decimalFormatData.getPercent();
values[4] = String.valueOf(decimalFormatData.getZeroDigit());
values[5] = "#";
values[6] = decimalFormatData.getMinusSign();
values[7] = decimalFormatData.getExponentSeparator();
values[8] = decimalFormatData.getPerMill();
values[9] = decimalFormatData.getInfinity();
values[10] = decimalFormatData.getNaN();
values[11] = decimalFormatData.getMonetaryDecimalSeparator();
values[12] = decimalFormatData.getMonetaryGroupSeparator();
String[] numberElements = values;
// END Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
decimalSeparator = numberElements[0].charAt(0);
groupingSeparator = numberElements[1].charAt(0);
patternSeparator = numberElements[2].charAt(0);
// Android-changed: For app compat, use single char for percent, per mill and minus sign.
// TODO: Support 2-char percent, per mill and minus sign.
// percentText = numberElements[3];
// percent = findNonFormatChar(percentText, '%');
percent = findNonFormatChar(numberElements[3], '%');
percentText = Character.toString(percent);
zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc.
digit = numberElements[5].charAt(0);
// minusSignText = numberElements[6];
// minusSign = findNonFormatChar(minusSignText, '-');
minusSign = findNonFormatChar(numberElements[6], '-');
minusSignText = Character.toString(minusSign);
exponential = numberElements[7].charAt(0);
exponentialSeparator = numberElements[7]; //string representation new since 1.6
// perMillText = numberElements[8];
// perMill = findNonFormatChar(perMillText, '\u2030');
perMill = findNonFormatChar(numberElements[8], '\u2030');
perMillText = Character.toString(perMill);
infinity = numberElements[9];
NaN = numberElements[10];
// monetary decimal/grouping separators may be missing in resource bundles
monetarySeparator = numberElements.length < 12 || numberElements[11].isEmpty() ?
decimalSeparator : numberElements[11].charAt(0);
monetaryGroupingSeparator = numberElements.length < 13 || numberElements[12].isEmpty() ?
groupingSeparator : numberElements[12].charAt(0);
// Android-removed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
// Upstream tries to re-use the strings from the cache, but Android doesn't have
// LocaleProviderAdapter to cache the strings.
// maybe filled with previously cached values, or null.
// intlCurrencySymbol = (String) data[1];
// currencySymbol = (String) data[2];
}
/**
* Obtains non-format single character from String
*/
private char findNonFormatChar(String src, char defChar) {
// Android-changed: Use maybeStripMarkers for backward compatibility.
// TODO: Consider using the OpenJDK implementation on Android U.
/*
return (char)src.chars()
.filter(c -> Character.getType(c) != Character.FORMAT)
.findFirst()
.orElse(defChar);
*/
return maybeStripMarkers(src, defChar);
}
/**
* Lazy initialization for currency related fields
*/
private void initializeCurrency(Locale locale) {
if (currencyInitialized) {
return;
}
// Try to obtain the currency used in the locale's country.
// Check for empty country string separately because it's a valid
// country ID for Locale (and used for the C locale), but not a valid
// ISO 3166 country code, and exceptions are expensive.
if (!locale.getCountry().isEmpty()) {
try {
currency = Currency.getInstance(locale);
} catch (IllegalArgumentException e) {
// use default values below for compatibility
}
}
if (currency != null) {
// BEGIN Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
// Android doesn't have DecimalFormatSymbolsProvider to cache the values.
// Thus, simplify the code not loading from the cache.
/*
// get resource bundle data
LocaleProviderAdapter adapter =
LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
// Avoid potential recursions
if (!(adapter instanceof ResourceBundleBasedAdapter)) {
adapter = LocaleProviderAdapter.getResourceBundleBased();
}
Object[] data = adapter.getLocaleResources(locale).getDecimalFormatSymbolsData();
intlCurrencySymbol = currency.getCurrencyCode();
if (data[1] != null && data[1] == intlCurrencySymbol) {
currencySymbol = (String) data[2];
} else {
currencySymbol = currency.getSymbol(locale);
data[1] = intlCurrencySymbol;
data[2] = currencySymbol;
}
*/
intlCurrencySymbol = currency.getCurrencyCode();
currencySymbol = currency.getSymbol(locale);
// END Android-changed: Removed use of DecimalFormatSymbolsProvider. Switched to ICU.
} else {
// default values
intlCurrencySymbol = "XXX";
try {
currency = Currency.getInstance(intlCurrencySymbol);
} catch (IllegalArgumentException e) {
}
currencySymbol = "\u00A4";
}
currencyInitialized = true;
}
// Android-changed: maybeStripMarkers added in b/26207216, fixed in b/32465689.
/**
* Attempts to strip RTL, LTR and Arabic letter markers from {@code symbol}.
* If the string contains a single non-marker character (and any number of marker characters),
* then that character is returned, otherwise {@code fallback} is returned.
*
* @hide
*/
// VisibleForTesting
public static char maybeStripMarkers(String symbol, char fallback) {
final int length = symbol.length();
if (length >= 1) {
boolean sawNonMarker = false;
char nonMarker = 0;
for (int i = 0; i < length; i++) {
final char c = symbol.charAt(i);
if (c == '\u200E' || c == '\u200F' || c == '\u061C') {
continue;
}
if (sawNonMarker) {
// More than one non-marker character.
return fallback;
}
sawNonMarker = true;
nonMarker = c;
}
if (sawNonMarker) {
return nonMarker;
}
}
return fallback;
}
// BEGIN Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
/**
* Convert an instance of this class to the ICU version so that it can be used with ICU4J.
* @hide
*/
protected android.icu.text.DecimalFormatSymbols getIcuDecimalFormatSymbols() {
if (cachedIcuDFS != null) {
return cachedIcuDFS;
}
initializeCurrency(this.locale);
cachedIcuDFS = new android.icu.text.DecimalFormatSymbols(this.locale);
// Do not localize plus sign. See "Special Pattern Characters" section in DecimalFormat.
// http://b/67034519
cachedIcuDFS.setPlusSign('+');
cachedIcuDFS.setZeroDigit(zeroDigit);
cachedIcuDFS.setDigit(digit);
cachedIcuDFS.setDecimalSeparator(decimalSeparator);
cachedIcuDFS.setGroupingSeparator(groupingSeparator);
cachedIcuDFS.setPatternSeparator(patternSeparator);
cachedIcuDFS.setPercentString(percentText);
cachedIcuDFS.setPerMillString(perMillText);
cachedIcuDFS.setMonetaryDecimalSeparator(monetarySeparator);
cachedIcuDFS.setMinusSignString(minusSignText);
cachedIcuDFS.setInfinity(infinity);
cachedIcuDFS.setNaN(NaN);
cachedIcuDFS.setExponentSeparator(exponentialSeparator);
cachedIcuDFS.setMonetaryGroupingSeparator(monetaryGroupingSeparator);
// j.t.DecimalFormatSymbols doesn't insert whitespace before/after currency by default.
// Override ICU default value to retain historic Android behavior.
// http://b/112127077
cachedIcuDFS.setPatternForCurrencySpacing(
android.icu.text.DecimalFormatSymbols.CURRENCY_SPC_INSERT,
false /* beforeCurrency */, "");
cachedIcuDFS.setPatternForCurrencySpacing(
android.icu.text.DecimalFormatSymbols.CURRENCY_SPC_INSERT,
true /* beforeCurrency */, "");
try {
cachedIcuDFS.setCurrency(
android.icu.util.Currency.getInstance(getCurrency().getCurrencyCode()));
} catch (NullPointerException e) {
currency = Currency.getInstance("XXX");
}
cachedIcuDFS.setCurrencySymbol(currencySymbol);
cachedIcuDFS.setInternationalCurrencySymbol(intlCurrencySymbol);
return cachedIcuDFS;
}
/**
* Create an instance of DecimalFormatSymbols using the ICU equivalent of this class.
* @hide
*/
protected static DecimalFormatSymbols fromIcuInstance(
android.icu.text.DecimalFormatSymbols dfs) {
DecimalFormatSymbols result = new DecimalFormatSymbols(dfs.getLocale());
result.setZeroDigit(dfs.getZeroDigit());
result.setDigit(dfs.getDigit());
result.setDecimalSeparator(dfs.getDecimalSeparator());
result.setGroupingSeparator(dfs.getGroupingSeparator());
result.setPatternSeparator(dfs.getPatternSeparator());
// TODO: Remove findNonFormatChar filter to support 2-char percent, per mill and minus sign.
result.setPercent(result.findNonFormatChar(dfs.getPercentString(), '%'));
result.setPerMill(result.findNonFormatChar(dfs.getPerMillString(), '\u2030'));
result.setMonetaryDecimalSeparator(dfs.getMonetaryDecimalSeparator());
result.setMinusSign(result.findNonFormatChar(dfs.getMinusSignString(), '-'));
result.setInfinity(dfs.getInfinity());
result.setNaN(dfs.getNaN());
result.setExponentSeparator(dfs.getExponentSeparator());
result.setMonetaryGroupingSeparator(dfs.getMonetaryGroupingSeparator());
try {
if (dfs.getCurrency() != null) {
result.setCurrency(Currency.getInstance(dfs.getCurrency().getCurrencyCode()));
} else {
result.setCurrency(Currency.getInstance("XXX"));
}
} catch (IllegalArgumentException e) {
result.setCurrency(Currency.getInstance("XXX"));
}
result.setInternationalCurrencySymbol(dfs.getInternationalCurrencySymbol());
result.setCurrencySymbol(dfs.getCurrencySymbol());
return result;
}
// END Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
// BEGIN Android-added: Android specific serialization code.
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("currencySymbol", String.class),
new ObjectStreamField("decimalSeparator", char.class),
new ObjectStreamField("digit", char.class),
new ObjectStreamField("exponential", char.class),
new ObjectStreamField("exponentialSeparator", String.class),
new ObjectStreamField("groupingSeparator", char.class),
new ObjectStreamField("infinity", String.class),
new ObjectStreamField("intlCurrencySymbol", String.class),
new ObjectStreamField("minusSign", char.class),
new ObjectStreamField("monetarySeparator", char.class),
new ObjectStreamField("NaN", String.class),
new ObjectStreamField("patternSeparator", char.class),
new ObjectStreamField("percent", char.class),
new ObjectStreamField("perMill", char.class),
new ObjectStreamField("serialVersionOnStream", int.class),
new ObjectStreamField("zeroDigit", char.class),
new ObjectStreamField("locale", Locale.class),
new ObjectStreamField("minusSignStr", String.class),
new ObjectStreamField("percentStr", String.class),
new ObjectStreamField("perMillText", String.class),
new ObjectStreamField("percentText", String.class),
new ObjectStreamField("minusSignText", String.class),
new ObjectStreamField("monetaryGroupingSeparator", char.class),
};
private void writeObject(ObjectOutputStream stream) throws IOException {
ObjectOutputStream.PutField fields = stream.putFields();
fields.put("currencySymbol", currencySymbol);
fields.put("decimalSeparator", getDecimalSeparator());
fields.put("digit", getDigit());
fields.put("exponential", exponentialSeparator.charAt(0));
fields.put("exponentialSeparator", exponentialSeparator);
fields.put("groupingSeparator", getGroupingSeparator());
fields.put("infinity", infinity);
fields.put("intlCurrencySymbol", intlCurrencySymbol);
fields.put("monetarySeparator", getMonetaryDecimalSeparator());
fields.put("NaN", NaN);
fields.put("patternSeparator", getPatternSeparator());
fields.put("perMill", getPerMill());
fields.put("serialVersionOnStream", serialVersionOnStream);
fields.put("zeroDigit", getZeroDigit());
fields.put("locale", locale);
// Hardcode values here for backwards compatibility. These values will only be used
// if we're de-serializing this object on an earlier version of android.
fields.put("minusSign", minusSign);
fields.put("percent", percent);
// minusSignStr is a single-char string.
fields.put("minusSignStr", String.valueOf(minusSign));
fields.put("percentStr", getPercentString());
// Fields added when serialVersionOnStream increased from 3 to 5 on ART U module.
fields.put("perMillText", getPerMillText());
fields.put("percentText", getPercentText());
fields.put("minusSignText", getMinusSignText());
fields.put("monetaryGroupingSeparator", getMonetaryGroupingSeparator());
stream.writeFields();
}
// END Android-added: Android specific serialization code.
/**
* Reads the default serializable fields, provides default values for objects
* in older serial versions, and initializes non-serializable fields.
* If {@code serialVersionOnStream}
* is less than 1, initializes {@code monetarySeparator} to be
* the same as {@code decimalSeparator} and {@code exponential}
* to be 'E'.
* If {@code serialVersionOnStream} is less than 2,
* initializes {@code locale} to the root locale, and initializes
* If {@code serialVersionOnStream} is less than 3, it initializes
* {@code exponentialSeparator} using {@code exponential}.
* If {@code serialVersionOnStream} is less than 4, it initializes
* {@code perMillText}, {@code percentText}, and
* {@code minusSignText} using {@code perMill}, {@code percent}, and
* {@code minusSign} respectively.
* If {@code serialVersionOnStream} is less than 5, it initializes
* {@code monetaryGroupingSeparator} using {@code groupingSeparator}.
* Sets {@code serialVersionOnStream} back to the maximum allowed value so that
* default serialization will work properly if this object is streamed out again.
* Initializes the currency from the intlCurrencySymbol field.
*
* @throws InvalidObjectException if {@code char} and {@code String}
* representations of either percent, per mille, and/or minus sign disagree.
* @since 1.1.6
*/
@java.io.Serial
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
// BEGIN Android-changed: Android specific serialization code.
ObjectInputStream.GetField fields = stream.readFields();
final int serialVersionOnStream = fields.get("serialVersionOnStream", 0);
currencySymbol = (String) fields.get("currencySymbol", "");
setDecimalSeparator(fields.get("decimalSeparator", '.'));
setDigit(fields.get("digit", '#'));
setGroupingSeparator(fields.get("groupingSeparator", ','));
infinity = (String) fields.get("infinity", "");
intlCurrencySymbol = (String) fields.get("intlCurrencySymbol", "");
NaN = (String) fields.get("NaN", "");
setPatternSeparator(fields.get("patternSeparator", ';'));
// Special handling for minusSign and percent. If we've serialized the string versions of
// these fields, use them. If not, fall back to the single character versions. This can
// only happen if we're de-serializing an object that was written by an older version of
// android (something that's strongly discouraged anyway).
final String minusSignStr = (String) fields.get("minusSignStr", null);
if (minusSignStr != null) {
minusSign = minusSignStr.charAt(0);
} else {
setMinusSign(fields.get("minusSign", '-'));
}
final String percentStr = (String) fields.get("percentStr", null);
if (percentStr != null) {
percent = percentStr.charAt(0);
} else {
setPercent(fields.get("percent", '%'));
}
setPerMill(fields.get("perMill", '\u2030'));
setZeroDigit(fields.get("zeroDigit", '0'));
locale = (Locale) fields.get("locale", null);
if (serialVersionOnStream == 0) {
setMonetaryDecimalSeparator(getDecimalSeparator());
} else {
setMonetaryDecimalSeparator(fields.get("monetarySeparator", '.'));
}
if (serialVersionOnStream == 0) {
// Prior to Java 1.1.6, the exponent separator wasn't configurable.
exponentialSeparator = "E";
} else if (serialVersionOnStream < 3) {
// In Javas 1.1.6 and 1.4, there was a character field "exponential".
setExponentSeparator(String.valueOf(fields.get("exponential", 'E')));
} else {
// In Java 6, there's a new "exponentialSeparator" field.
setExponentSeparator((String) fields.get("exponentialSeparator", "E"));
}
if (serialVersionOnStream < 4) {
// didn't have perMillText, percentText, and minusSignText.
// Create one using corresponding char variations.
perMillText = Character.toString(perMill);
percentText = Character.toString(percent);
minusSignText = Character.toString(minusSign);
} else {
// Android-changed: Read the fields manually.
perMillText = (String) fields.get("perMillText", Character.toString(perMill));
percentText = (String) fields.get("percentText", Character.toString(percent));
minusSignText = (String) fields.get("minusSignText", Character.toString(minusSign));
// Check whether char and text fields agree
if (findNonFormatChar(perMillText, '\uFFFF') != perMill ||
findNonFormatChar(percentText, '\uFFFF') != percent ||
findNonFormatChar(minusSignText, '\uFFFF') != minusSign) {
throw new InvalidObjectException(
"'char' and 'String' representations of either percent, " +
"per mille, and/or minus sign disagree.");
}
}
if (serialVersionOnStream < 5) {
// didn't have monetaryGroupingSeparator. Create one using groupingSeparator
monetaryGroupingSeparator = groupingSeparator;
}
// Android-changed: Read the monetaryGroupingSeparator field manually.
else {
monetaryGroupingSeparator = fields.get("monetaryGroupingSeparator", groupingSeparator);
}
// Android-changed: Add `this` to avoid conflict with the local variable.
// serialVersionOnStream = currentSerialVersion;
this.serialVersionOnStream = currentSerialVersion;
if (intlCurrencySymbol != null) {
try {
currency = Currency.getInstance(intlCurrencySymbol);
currencyInitialized = true;
} catch (IllegalArgumentException e) {
currency = null;
}
}
// END Android-changed: Android specific serialization code.
}
/**
* Character used for zero.
*
* @serial
* @see #getZeroDigit
*/
private char zeroDigit;
/**
* Character used for grouping separator.
*
* @serial
* @see #getGroupingSeparator
*/
private char groupingSeparator;
/**
* Character used for decimal sign.
*
* @serial
* @see #getDecimalSeparator
*/
private char decimalSeparator;
/**
* Character used for per mille sign.
*
* @serial
* @see #getPerMill
*/
private char perMill;
/**
* Character used for percent sign.
* @serial
* @see #getPercent
*/
private char percent;
/**
* Character used for a digit in a pattern.
*
* @serial
* @see #getDigit
*/
private char digit;
/**
* Character used to separate positive and negative subpatterns
* in a pattern.
*
* @serial
* @see #getPatternSeparator
*/
private char patternSeparator;
/**
* String used to represent infinity.
* @serial
* @see #getInfinity
*/
private String infinity;
/**
* String used to represent "not a number".
* @serial
* @see #getNaN
*/
private String NaN;
/**
* Character used to represent minus sign.
* @serial
* @see #getMinusSign
*/
private char minusSign;
/**
* String denoting the local currency, e.g. "$".
* @serial
* @see #getCurrencySymbol
*/
private String currencySymbol;
/**
* ISO 4217 currency code denoting the local currency, e.g. "USD".
* @serial
* @see #getInternationalCurrencySymbol
*/
private String intlCurrencySymbol;
/**
* The decimal separator used when formatting currency values.
* @serial
* @since 1.1.6
* @see #getMonetaryDecimalSeparator
*/
private char monetarySeparator; // Field new in JDK 1.1.6
/**
* The character used to distinguish the exponent in a number formatted
* in exponential notation, e.g. 'E' for a number such as "1.23E45".
* <p>
* Note that the public API provides no way to set this field,
* even though it is supported by the implementation and the stream format.
* The intent is that this will be added to the API in the future.
*
* @serial
* @since 1.1.6
*/
private char exponential; // Field new in JDK 1.1.6
/**
* The string used to separate the mantissa from the exponent.
* Examples: "x10^" for 1.23x10^4, "E" for 1.23E4.
* <p>
* If both {@code exponential} and {@code exponentialSeparator}
* exist, this {@code exponentialSeparator} has the precedence.
*
* @serial
* @since 1.6
*/
private String exponentialSeparator; // Field new in JDK 1.6
/**
* The locale of these currency format symbols.
*
* @serial
* @since 1.4
*/
private Locale locale;
/**
* String representation of per mille sign, which may include
* formatting characters, such as BiDi control characters.
* The first non-format character of this string is the same as
* {@code perMill}.
*
* @serial
* @since 13
*/
private String perMillText;
/**
* String representation of percent sign, which may include
* formatting characters, such as BiDi control characters.
* The first non-format character of this string is the same as
* {@code percent}.
*
* @serial
* @since 13
*/
private String percentText;
/**
* String representation of minus sign, which may include
* formatting characters, such as BiDi control characters.
* The first non-format character of this string is the same as
* {@code minusSign}.
*
* @serial
* @since 13
*/
private String minusSignText;
/**
* The grouping separator used when formatting currency values.
*
* @serial
* @since 15
*/
private char monetaryGroupingSeparator;
// currency; only the ISO code is serialized.
private transient Currency currency;
private transient volatile boolean currencyInitialized;
/**
* Cached hash code.
*/
private transient volatile int hashCode;
// Proclaim JDK 1.1 FCS compatibility
@java.io.Serial
static final long serialVersionUID = 5772796243397350300L;
// The internal serial version which says which version was written
// - 0 (default) for version up to JDK 1.1.5
// - 1 for version from JDK 1.1.6, which includes two new fields:
// monetarySeparator and exponential.
// - 2 for version from J2SE 1.4, which includes locale field.
// - 3 for version from J2SE 1.6, which includes exponentialSeparator field.
// - 4 for version from Java SE 13, which includes perMillText, percentText,
// and minusSignText field.
// - 5 for version from Java SE 15, which includes monetaryGroupingSeparator.
private static final int currentSerialVersion = 5;
/**
* Describes the version of {@code DecimalFormatSymbols} present on the stream.
* Possible values are:
* <ul>
* <li><b>0</b> (or uninitialized): versions prior to JDK 1.1.6.
*
* <li><b>1</b>: Versions written by JDK 1.1.6 or later, which include
* two new fields: {@code monetarySeparator} and {@code exponential}.
* <li><b>2</b>: Versions written by J2SE 1.4 or later, which include a
* new {@code locale} field.
* <li><b>3</b>: Versions written by J2SE 1.6 or later, which include a
* new {@code exponentialSeparator} field.
* <li><b>4</b>: Versions written by Java SE 13 or later, which include
* new {@code perMillText}, {@code percentText}, and
* {@code minusSignText} field.
* <li><b>5</b>: Versions written by Java SE 15 or later, which include
* new {@code monetaryGroupingSeparator} field.
* * </ul>
* When streaming out a {@code DecimalFormatSymbols}, the most recent format
* (corresponding to the highest allowable {@code serialVersionOnStream})
* is always written.
*
* @serial
* @since 1.1.6
*/
private int serialVersionOnStream = currentSerialVersion;
// BEGIN Android-added: cache for cachedIcuDFS.
/**
* Lazily created cached instance of an ICU DecimalFormatSymbols that's equivalent to this one.
* This field is reset to null whenever any of the relevant fields of this class are modified
* and will be re-created by {@link #getIcuDecimalFormatSymbols()} as necessary.
*/
private transient android.icu.text.DecimalFormatSymbols cachedIcuDFS = null;
// END Android-added: cache for cachedIcuDFS.
}