diff options
10 files changed, 186 insertions, 84 deletions
diff --git a/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java b/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java index 18c22c47e..5971d831a 100644 --- a/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java +++ b/android_icu4j/src/main/java/android/icu/number/NumberFormatterImpl.java @@ -3,8 +3,6 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package android.icu.number; -import android.icu.impl.CurrencyData; -import android.icu.impl.CurrencyData.CurrencyFormatInfo; import android.icu.impl.FormattedStringBuilder; import android.icu.impl.StandardPlural; import android.icu.impl.number.CompactData.CompactType; @@ -215,21 +213,16 @@ class NumberFormatterImpl { micros.symbols = (DecimalFormatSymbols) macros.symbols; } else { micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns); + if (isCurrency) { + micros.symbols.setCurrency(currency); + } } // Load and parse the pattern string. It is used for grouping sizes and affixes only. // If we are formatting currency, check for a currency-specific pattern. String pattern = null; - if (isCurrency) { - CurrencyFormatInfo info = CurrencyData.provider.getInstance(macros.loc, true) - .getFormatInfo(currency.getCurrencyCode()); - if (info != null) { - pattern = info.currencyPattern; - // It's clunky to clone an object here, but this code is not frequently executed. - micros.symbols = (DecimalFormatSymbols) micros.symbols.clone(); - micros.symbols.setMonetaryDecimalSeparatorString(info.monetaryDecimalSeparator); - micros.symbols.setMonetaryGroupingSeparatorString(info.monetaryGroupingSeparator); - } + if (isCurrency && micros.symbols.getCurrencyPattern() != null) { + pattern = micros.symbols.getCurrencyPattern(); } if (pattern == null) { int patternStyle; diff --git a/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java b/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java index 5fe0c0d6e..d959b34d0 100644 --- a/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java +++ b/android_icu4j/src/main/java/android/icu/text/DecimalFormat.java @@ -760,7 +760,15 @@ public class DecimalFormat extends NumberFormat { */ @Override public StringBuffer format(CurrencyAmount currAmt, StringBuffer result, FieldPosition fieldPosition) { - FormattedNumber output = formatter.format(currAmt); + // We need to make localSymbols in order for monetary symbols to be initialized. + // Also, bypass the CurrencyAmount override of LocalizedNumberFormatter#format, + // because its caching mechanism will not provide any benefit here. + DecimalFormatSymbols localSymbols = (DecimalFormatSymbols) symbols.clone(); + localSymbols.setCurrency(currAmt.getCurrency()); + FormattedNumber output = formatter + .symbols(localSymbols) + .unit(currAmt.getCurrency()) + .format(currAmt.getNumber()); fieldPositionHelper(output, fieldPosition, result.length()); output.appendTo(result); return result; @@ -1890,11 +1898,8 @@ public class DecimalFormat extends NumberFormat { @Override public synchronized void setCurrency(Currency currency) { properties.setCurrency(currency); - // Backwards compatibility: also set the currency in the DecimalFormatSymbols if (currency != null) { symbols.setCurrency(currency); - String symbol = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null); - symbols.setCurrencySymbol(symbol); } refreshFormatter(); } diff --git a/android_icu4j/src/main/java/android/icu/text/DecimalFormatSymbols.java b/android_icu4j/src/main/java/android/icu/text/DecimalFormatSymbols.java index 6dd51fe21..3274a50ce 100644 --- a/android_icu4j/src/main/java/android/icu/text/DecimalFormatSymbols.java +++ b/android_icu4j/src/main/java/android/icu/text/DecimalFormatSymbols.java @@ -821,9 +821,32 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { if (currency == null) { throw new NullPointerException(); } + if (currency.equals(this.currency)) { + return; + } + CurrencyDisplayInfo displayInfo = CurrencyData.provider.getInstance(ulocale, true); + setCurrencyOrNull(currency, displayInfo); + } + + private void setCurrencyOrNull(Currency currency, CurrencyDisplayInfo displayInfo) { this.currency = currency; + + if (currency == null) { + intlCurrencySymbol = "XXX"; + currencySymbol = "\u00A4"; // 'OX' currency symbol + currencyPattern = null; + return; + } + intlCurrencySymbol = currency.getCurrencyCode(); - currencySymbol = currency.getSymbol(requestedLocale); + currencySymbol = currency.getSymbol(ulocale); + + CurrencyFormatInfo formatInfo = displayInfo.getFormatInfo(currency.getCurrencyCode()); + if (formatInfo != null) { + setMonetaryDecimalSeparatorString(formatInfo.monetaryDecimalSeparator); + setMonetaryGroupingSeparatorString(formatInfo.monetaryGroupingSeparator); + currencyPattern = formatInfo.currencyPattern; + } } /** @@ -927,11 +950,13 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { } /** - } * Internal API for NumberFormat * @return String currency pattern string + * @deprecated This API is for ICU internal use only + * @hide unsupported on Android */ - String getCurrencyPattern() { + @Deprecated + public String getCurrencyPattern() { return currencyPattern; } @@ -1303,30 +1328,10 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { padEscape = '*'; sigDigit = '@'; + CurrencyDisplayInfo displayInfo = CurrencyData.provider.getInstance(ulocale, true); + initSpacingInfo(displayInfo.getSpacingInfo()); - CurrencyDisplayInfo info = CurrencyData.provider.getInstance(locale, true); - - // Obtain currency data from the currency API. This is strictly - // for backward compatibility; we don't use DecimalFormatSymbols - // for currency data anymore. - currency = Currency.getInstance(locale); - if (currency != null) { - intlCurrencySymbol = currency.getCurrencyCode(); - currencySymbol = currency.getName(locale, Currency.SYMBOL_NAME, null); - CurrencyFormatInfo fmtInfo = info.getFormatInfo(intlCurrencySymbol); - if (fmtInfo != null) { - currencyPattern = fmtInfo.currencyPattern; - setMonetaryDecimalSeparatorString(fmtInfo.monetaryDecimalSeparator); - setMonetaryGroupingSeparatorString(fmtInfo.monetaryGroupingSeparator); - } - } else { - intlCurrencySymbol = "XXX"; - currencySymbol = "\u00A4"; // 'OX' currency symbol - } - - - // Get currency spacing data. - initSpacingInfo(info.getSpacingInfo()); + setCurrencyOrNull(Currency.getInstance(ulocale), displayInfo); } private static CacheData loadData(ULocale locale) { diff --git a/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java b/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java index 8f28d38e0..1b0f6bb2a 100644 --- a/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java +++ b/android_icu4j/src/main/tests/android/icu/dev/test/format/NumberFormatTest.java @@ -44,6 +44,7 @@ import org.junit.runners.JUnit4; import android.icu.dev.test.TestFmwk; import android.icu.dev.test.TestUtil; import android.icu.dev.test.format.IntlTestDecimalFormatAPIC.FieldContainer; +import android.icu.impl.DontCareFieldPosition; import android.icu.impl.ICUConfig; import android.icu.impl.LocaleUtility; import android.icu.impl.data.ResourceReader; @@ -6617,6 +6618,26 @@ public class NumberFormatTest extends TestFmwk { } @Test + public void test20956_MonetarySymbolGetters() { + Locale locale = new Locale.Builder().setLocale(Locale.forLanguageTag("et")).build(); + DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale); + Currency currency = Currency.getInstance("EEK"); + + decimalFormat.setCurrency(currency); + + DecimalFormatSymbols decimalFormatSymbols = decimalFormat.getDecimalFormatSymbols(); + assertEquals("MONETARY DECIMAL SEPARATOR", ".", decimalFormatSymbols.getMonetaryDecimalSeparatorString()); + assertEquals("DECIMAL SEPARATOR", ",", decimalFormatSymbols.getDecimalSeparatorString()); + assertEquals("MONETARY GROUPING SEPARATOR", " ", decimalFormatSymbols.getMonetaryGroupingSeparatorString()); + assertEquals("GROUPING SEPARATOR", " ", decimalFormatSymbols.getGroupingSeparatorString()); + assertEquals("CURRENCY SYMBOL", "kr", decimalFormatSymbols.getCurrencySymbol()); + + StringBuffer sb = new StringBuffer(); + decimalFormat.format(new BigDecimal(12345.12), sb, DontCareFieldPosition.INSTANCE); + assertEquals("OUTPUT", "12 345.12 kr", sb.toString()); + } + + @Test public void test20358_GroupingInPattern() { DecimalFormat fmt = (DecimalFormat) NumberFormat.getInstance(ULocale.ENGLISH); assertEquals("Initial pattern", diff --git a/android_icu4j/testing/src/com/android/libcore/java/text/NumberFormatTest.java b/android_icu4j/testing/src/com/android/libcore/java/text/NumberFormatTest.java new file mode 100644 index 000000000..fc2bfbfb1 --- /dev/null +++ b/android_icu4j/testing/src/com/android/libcore/java/text/NumberFormatTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.libcore.java.text; + +import static org.junit.Assert.assertEquals; + +import android.icu.testsharding.MainTestShard; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * Test for libcore APIs implemented by ICU. + * This test isn't put into CtsLibocreTestCases due to ART/libcore unbundling. http://b/164511892 + */ +@MainTestShard +@RunWith(JUnit4.class) +public class NumberFormatTest { + + // http://b/162744366 + @Test + public void testMonetarySeparator() { + Locale locale = Locale.forLanguageTag("en-BE"); + DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance(locale); + DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); + // Check the assumption on the en-BE locale data. If the data is changed, please fix the + // below assert statement. + assertEquals('.', dfs.getGroupingSeparator()); + assertEquals(',', dfs.getDecimalSeparator()); + assertEquals(',', dfs.getMonetaryDecimalSeparator()); + // Assert that the separators in DecimalFormatSymbols are used. + assertEquals("€9.876,66",df.format(9876.66)); + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java index 5b772bf8b..75ad34f14 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java @@ -2,8 +2,6 @@ // License & terms of use: http://www.unicode.org/copyright.html#License package com.ibm.icu.number; -import com.ibm.icu.impl.CurrencyData; -import com.ibm.icu.impl.CurrencyData.CurrencyFormatInfo; import com.ibm.icu.impl.FormattedStringBuilder; import com.ibm.icu.impl.StandardPlural; import com.ibm.icu.impl.number.CompactData.CompactType; @@ -214,21 +212,16 @@ class NumberFormatterImpl { micros.symbols = (DecimalFormatSymbols) macros.symbols; } else { micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns); + if (isCurrency) { + micros.symbols.setCurrency(currency); + } } // Load and parse the pattern string. It is used for grouping sizes and affixes only. // If we are formatting currency, check for a currency-specific pattern. String pattern = null; - if (isCurrency) { - CurrencyFormatInfo info = CurrencyData.provider.getInstance(macros.loc, true) - .getFormatInfo(currency.getCurrencyCode()); - if (info != null) { - pattern = info.currencyPattern; - // It's clunky to clone an object here, but this code is not frequently executed. - micros.symbols = (DecimalFormatSymbols) micros.symbols.clone(); - micros.symbols.setMonetaryDecimalSeparatorString(info.monetaryDecimalSeparator); - micros.symbols.setMonetaryGroupingSeparatorString(info.monetaryGroupingSeparator); - } + if (isCurrency && micros.symbols.getCurrencyPattern() != null) { + pattern = micros.symbols.getCurrencyPattern(); } if (pattern == null) { int patternStyle; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index 1bfeff4c8..a83bd1ba2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -787,7 +787,15 @@ public class DecimalFormat extends NumberFormat { */ @Override public StringBuffer format(CurrencyAmount currAmt, StringBuffer result, FieldPosition fieldPosition) { - FormattedNumber output = formatter.format(currAmt); + // We need to make localSymbols in order for monetary symbols to be initialized. + // Also, bypass the CurrencyAmount override of LocalizedNumberFormatter#format, + // because its caching mechanism will not provide any benefit here. + DecimalFormatSymbols localSymbols = (DecimalFormatSymbols) symbols.clone(); + localSymbols.setCurrency(currAmt.getCurrency()); + FormattedNumber output = formatter + .symbols(localSymbols) + .unit(currAmt.getCurrency()) + .format(currAmt.getNumber()); fieldPositionHelper(output, fieldPosition, result.length()); output.appendTo(result); return result; @@ -2043,11 +2051,8 @@ public class DecimalFormat extends NumberFormat { @Override public synchronized void setCurrency(Currency currency) { properties.setCurrency(currency); - // Backwards compatibility: also set the currency in the DecimalFormatSymbols if (currency != null) { symbols.setCurrency(currency); - String symbol = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null); - symbols.setCurrencySymbol(symbol); } refreshFormatter(); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java index 8ad220ffa..749971b93 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormatSymbols.java @@ -888,9 +888,32 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { if (currency == null) { throw new NullPointerException(); } + if (currency.equals(this.currency)) { + return; + } + CurrencyDisplayInfo displayInfo = CurrencyData.provider.getInstance(ulocale, true); + setCurrencyOrNull(currency, displayInfo); + } + + private void setCurrencyOrNull(Currency currency, CurrencyDisplayInfo displayInfo) { this.currency = currency; + + if (currency == null) { + intlCurrencySymbol = "XXX"; + currencySymbol = "\u00A4"; // 'OX' currency symbol + currencyPattern = null; + return; + } + intlCurrencySymbol = currency.getCurrencyCode(); - currencySymbol = currency.getSymbol(requestedLocale); + currencySymbol = currency.getSymbol(ulocale); + + CurrencyFormatInfo formatInfo = displayInfo.getFormatInfo(currency.getCurrencyCode()); + if (formatInfo != null) { + setMonetaryDecimalSeparatorString(formatInfo.monetaryDecimalSeparator); + setMonetaryGroupingSeparatorString(formatInfo.monetaryGroupingSeparator); + currencyPattern = formatInfo.currencyPattern; + } } /** @@ -1004,11 +1027,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { } /** - } * Internal API for NumberFormat * @return String currency pattern string + * @deprecated This API is for ICU internal use only */ - String getCurrencyPattern() { + @Deprecated + public String getCurrencyPattern() { return currencyPattern; } @@ -1396,30 +1420,10 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { padEscape = '*'; sigDigit = '@'; + CurrencyDisplayInfo displayInfo = CurrencyData.provider.getInstance(ulocale, true); + initSpacingInfo(displayInfo.getSpacingInfo()); - CurrencyDisplayInfo info = CurrencyData.provider.getInstance(locale, true); - - // Obtain currency data from the currency API. This is strictly - // for backward compatibility; we don't use DecimalFormatSymbols - // for currency data anymore. - currency = Currency.getInstance(locale); - if (currency != null) { - intlCurrencySymbol = currency.getCurrencyCode(); - currencySymbol = currency.getName(locale, Currency.SYMBOL_NAME, null); - CurrencyFormatInfo fmtInfo = info.getFormatInfo(intlCurrencySymbol); - if (fmtInfo != null) { - currencyPattern = fmtInfo.currencyPattern; - setMonetaryDecimalSeparatorString(fmtInfo.monetaryDecimalSeparator); - setMonetaryGroupingSeparatorString(fmtInfo.monetaryGroupingSeparator); - } - } else { - intlCurrencySymbol = "XXX"; - currencySymbol = "\u00A4"; // 'OX' currency symbol - } - - - // Get currency spacing data. - initSpacingInfo(info.getSpacingInfo()); + setCurrencyOrNull(Currency.getInstance(ulocale), displayInfo); } private static CacheData loadData(ULocale locale) { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 1729f2ada..b851f7650 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -43,6 +43,7 @@ import org.junit.runners.JUnit4; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.dev.test.TestUtil; import com.ibm.icu.dev.test.format.IntlTestDecimalFormatAPIC.FieldContainer; +import com.ibm.icu.impl.DontCareFieldPosition; import com.ibm.icu.impl.ICUConfig; import com.ibm.icu.impl.LocaleUtility; import com.ibm.icu.impl.data.ResourceReader; @@ -6614,6 +6615,26 @@ public class NumberFormatTest extends TestFmwk { } @Test + public void test20956_MonetarySymbolGetters() { + Locale locale = new Locale.Builder().setLocale(Locale.forLanguageTag("et")).build(); + DecimalFormat decimalFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale); + Currency currency = Currency.getInstance("EEK"); + + decimalFormat.setCurrency(currency); + + DecimalFormatSymbols decimalFormatSymbols = decimalFormat.getDecimalFormatSymbols(); + assertEquals("MONETARY DECIMAL SEPARATOR", ".", decimalFormatSymbols.getMonetaryDecimalSeparatorString()); + assertEquals("DECIMAL SEPARATOR", ",", decimalFormatSymbols.getDecimalSeparatorString()); + assertEquals("MONETARY GROUPING SEPARATOR", " ", decimalFormatSymbols.getMonetaryGroupingSeparatorString()); + assertEquals("GROUPING SEPARATOR", " ", decimalFormatSymbols.getGroupingSeparatorString()); + assertEquals("CURRENCY SYMBOL", "kr", decimalFormatSymbols.getCurrencySymbol()); + + StringBuffer sb = new StringBuffer(); + decimalFormat.format(new BigDecimal(12345.12), sb, DontCareFieldPosition.INSTANCE); + assertEquals("OUTPUT", "12 345.12 kr", sb.toString()); + } + + @Test public void test20358_GroupingInPattern() { DecimalFormat fmt = (DecimalFormat) NumberFormat.getInstance(ULocale.ENGLISH); assertEquals("Initial pattern", diff --git a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java index bc9c4e210..d43da0cd1 100644 --- a/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java +++ b/tools/srcgen/src/main/java/com/android/icu4j/srcgen/Icu4jTransform.java @@ -689,6 +689,7 @@ public class Icu4jTransform { "method:android.icu.text.Collator#registerInstance(Collator,ULocale)", "method:android.icu.text.Collator#unregister(Object)", "method:android.icu.text.DecimalFormat#toNumberFormatter()", + "method:android.icu.text.DecimalFormatSymbols#getCurrencyPattern()", "method:android.icu.text.NumberFormat#registerFactory(NumberFormatFactory)", "method:android.icu.text.NumberFormat#unregister(Object)", "method:android.icu.text.RuleBasedCollator#getRawCollationKey(String,RawCollationKey)", |