8230284: Accounting currency format support does not cope with explicit number system

Reviewed-by: rriggs
This commit is contained in:
Naoto Sato 2019-09-09 12:42:01 -07:00
parent 6794a68681
commit ea0fbbca51
8 changed files with 677 additions and 608 deletions

View File

@ -33,6 +33,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
class Bundle {
static enum Type {
@ -213,27 +214,16 @@ class Bundle {
// merge individual strings into arrays
// if myMap has any of the NumberPatterns members
for (String k : NUMBER_PATTERN_KEYS) {
if (myMap.containsKey(k)) {
String[] numberPatterns = new String[NUMBER_PATTERN_KEYS.length];
for (int i = 0; i < NUMBER_PATTERN_KEYS.length; i++) {
String key = NUMBER_PATTERN_KEYS[i];
String value = (String) myMap.remove(key);
if (value == null) {
value = (String) parentsMap.remove(key);
}
if (value == null || value.isEmpty()) {
if (!key.endsWith("accounting")) {
// print warning unless it is for "accounting",
// which may be missing.
CLDRConverter.warning("empty pattern for " + key);
}
}
numberPatterns[i] = value;
}
myMap.put("NumberPatterns", numberPatterns);
break;
// if myMap has any of the NumberPatterns/NumberElements members, create a
// complete array of patterns/elements.
@SuppressWarnings("unchecked")
List<String> scripts = (List<String>) myMap.get("numberingScripts");
if (scripts != null) {
for (String script : scripts) {
myMap.put(script + ".NumberPatterns",
createNumberArray(myMap, parentsMap, NUMBER_PATTERN_KEYS, script));
myMap.put(script + ".NumberElements",
createNumberArray(myMap, parentsMap, NUMBER_ELEMENT_KEYS, script));
}
}
@ -247,40 +237,6 @@ class Bundle {
}
}
// if myMap has any of NUMBER_ELEMENT_KEYS, create a complete NumberElements.
String defaultScript = (String) myMap.get("DefaultNumberingSystem");
@SuppressWarnings("unchecked")
List<String> scripts = (List<String>) myMap.get("numberingScripts");
if (scripts != null) {
for (String script : scripts) {
for (String k : NUMBER_ELEMENT_KEYS) {
String[] numberElements = new String[NUMBER_ELEMENT_KEYS.length];
for (int i = 0; i < NUMBER_ELEMENT_KEYS.length; i++) {
String key = script + "." + NUMBER_ELEMENT_KEYS[i];
String value = (String) myMap.remove(key);
if (value == null) {
if (key.endsWith("/pattern")) {
value = "#";
} else {
value = (String) parentsMap.get(key);
if (value == null) {
// the last resort is "latn"
key = "latn." + NUMBER_ELEMENT_KEYS[i];
value = (String) parentsMap.get(key);
if (value == null) {
throw new InternalError("NumberElements: null for " + key);
}
}
}
}
numberElements[i] = value;
}
myMap.put(script + "." + "NumberElements", numberElements);
break;
}
}
}
// another hack: parentsMap is not used for date-time resources.
if ("root".equals(id)) {
parentsMap = null;
@ -798,4 +754,45 @@ class Bundle {
private interface ConvertDateTimeLetters {
void convert(CalendarType calendarType, char cldrLetter, int count, StringBuilder sb);
}
/**
* Returns a complete string array for NumberElements or NumberPatterns. If any
* array element is missing, it will fall back to parents map, as well as
* numbering script fallback.
*/
private String[] createNumberArray(Map<String, Object> myMap, Map<String, Object>parentsMap,
String[] keys, String script) {
String[] numArray = new String[keys.length];
for (int i = 0; i < keys.length; i++) {
String key = script + "." + keys[i];
final int idx = i;
Optional.ofNullable(
myMap.getOrDefault(key,
// if value not found in myMap, search for parentsMap
parentsMap.getOrDefault(key,
parentsMap.getOrDefault(keys[i],
// the last resort is "latn"
parentsMap.get("latn." + keys[i])))))
.ifPresentOrElse(v -> numArray[idx] = (String)v, () -> {
if (keys == NUMBER_PATTERN_KEYS) {
// NumberPatterns
if (!key.endsWith("accounting")) {
// throw error unless it is for "accounting",
// which may be missing.
throw new InternalError("NumberPatterns: null for " +
key + ", id: " + id);
}
} else {
// NumberElements
assert keys == NUMBER_ELEMENT_KEYS;
if (key.endsWith("/pattern")) {
numArray[idx] = "#";
} else {
throw new InternalError("NumberElements: null for " +
key + ", id: " + id);
}
}});
}
return numArray;
}
}

View File

@ -864,7 +864,7 @@ public class CLDRConverter {
}
for (String key : map.keySet()) {
// Copy available calendar names
// Copy available calendar names
if (key.startsWith(CLDRConverter.LOCALE_TYPE_PREFIX_CA)) {
String type = key.substring(CLDRConverter.LOCALE_TYPE_PREFIX_CA.length());
for (CalendarType calendarType : CalendarType.values()) {
@ -891,12 +891,13 @@ public class CLDRConverter {
List<String> numberingScripts = (List<String>) map.remove("numberingScripts");
if (numberingScripts != null) {
for (String script : numberingScripts) {
copyIfPresent(map, script + "." + "NumberElements", formatData);
copyIfPresent(map, script + ".NumberElements", formatData);
copyIfPresent(map, script + ".NumberPatterns", formatData);
}
} else {
copyIfPresent(map, "NumberElements", formatData);
copyIfPresent(map, "NumberPatterns", formatData);
}
copyIfPresent(map, "NumberPatterns", formatData);
copyIfPresent(map, "short.CompactNumberPatterns", formatData);
copyIfPresent(map, "long.CompactNumberPatterns", formatData);
@ -1159,4 +1160,22 @@ public class CLDRConverter {
.collect(Collectors.toList()),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
// for debug
private static void dumpMap(Map<String, Object> map) {
map.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> {
Object val = e.getValue();
String valStr = null;
if (val instanceof String[]) {
valStr = Arrays.asList((String[])val).toString();
} else if (val != null) {
valStr = val.toString();
}
return e.getKey() + " = " + valStr;
})
.forEach(System.out::println);
}
}

View File

@ -508,7 +508,8 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
String type = attributes.getValue("type");
if (null == type) {
// format data for decimal number format
pushStringEntry(qName, attributes, "NumberPatterns/decimal");
pushStringEntry(qName, attributes,
currentNumberingSystem + "NumberPatterns/decimal");
currentStyle = type;
} else {
switch (type) {
@ -586,6 +587,18 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
pushContainer(qName, attributes);
}
break;
case "currencyFormats":
case "decimalFormats":
case "percentFormats":
{
String script = attributes.getValue("numberSystem");
if (script != null) {
addNumberingScript(script);
currentNumberingSystem = script + ".";
}
pushContainer(qName, attributes);
}
break;
case "currencyFormatLength":
if (attributes.getValue("type") == null) {
// skipping type="short" data
@ -601,9 +614,11 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
// copy string for later assembly into NumberPatterns
String cfStyle = attributes.getValue("type");
if (cfStyle.equals("standard")) {
pushStringEntry(qName, attributes, "NumberPatterns/currency");
pushStringEntry(qName, attributes,
currentNumberingSystem + "NumberPatterns/currency");
} else if (cfStyle.equals("accounting")) {
pushStringEntry(qName, attributes, "NumberPatterns/accounting");
pushStringEntry(qName, attributes,
currentNumberingSystem + "NumberPatterns/accounting");
} else {
pushIgnoredContainer(qName);
}
@ -613,7 +628,8 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
// for FormatData
// copy string for later assembly into NumberPatterns
if (attributes.getValue("type").equals("standard")) {
pushStringEntry(qName, attributes, "NumberPatterns/percent");
pushStringEntry(qName, attributes,
currentNumberingSystem + "NumberPatterns/percent");
} else {
pushIgnoredContainer(qName);
}
@ -641,13 +657,7 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
break;
}
@SuppressWarnings("unchecked")
List<String> numberingScripts = (List<String>) get("numberingScripts");
if (numberingScripts == null) {
numberingScripts = new ArrayList<>();
put("numberingScripts", numberingScripts);
}
numberingScripts.add(script);
addNumberingScript(script);
put(currentNumberingSystem + "NumberElements/zero", digits.substring(0, 1));
pushContainer(qName, attributes);
}
@ -1020,6 +1030,13 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
compactCount = "";
putIfEntry();
break;
case "currencyFormats":
case "decimalFormats":
case "percentFormats":
case "symbols":
currentNumberingSystem = "";
putIfEntry();
break;
default:
putIfEntry();
}
@ -1086,4 +1103,16 @@ class LDMLParseHandler extends AbstractLDMLHandler<Object> {
return key;
}
}
private void addNumberingScript(String script) {
@SuppressWarnings("unchecked")
List<String> numberingScripts = (List<String>) get("numberingScripts");
if (numberingScripts == null) {
numberingScripts = new ArrayList<>();
put("numberingScripts", numberingScripts);
}
if (!numberingScripts.contains(script)) {
numberingScripts.add(script);
}
}
}

View File

@ -178,30 +178,7 @@ public class LocaleResources {
// elements are provided by the caller, yet they are cached here.
ResourceBundle rb = localeData.getNumberFormatData(locale);
dfsdata = new Object[3];
// NumberElements look up. First, try the Unicode extension
String numElemKey;
String numberType = locale.getUnicodeLocaleType("nu");
if (numberType != null) {
numElemKey = numberType + ".NumberElements";
if (rb.containsKey(numElemKey)) {
dfsdata[0] = rb.getStringArray(numElemKey);
}
}
// Next, try DefaultNumberingSystem value
if (dfsdata[0] == null && rb.containsKey("DefaultNumberingSystem")) {
numElemKey = rb.getString("DefaultNumberingSystem") + ".NumberElements";
if (rb.containsKey(numElemKey)) {
dfsdata[0] = rb.getStringArray(numElemKey);
}
}
// Last resort. No need to check the availability.
// Just let it throw MissingResourceException when needed.
if (dfsdata[0] == null) {
dfsdata[0] = rb.getStringArray("NumberElements");
}
dfsdata[0] = getNumberStrings(rb, "NumberElements");
cache.put(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY,
new ResourceReference(DECIMAL_FORMAT_SYMBOLS_DATA_CACHEKEY, (Object) dfsdata, referenceQueue));
@ -210,6 +187,37 @@ public class LocaleResources {
return dfsdata;
}
private String[] getNumberStrings(ResourceBundle rb, String type) {
String[] ret = null;
String key;
String numSys;
// Number strings look up. First, try the Unicode extension
numSys = locale.getUnicodeLocaleType("nu");
if (numSys != null) {
key = numSys + "." + type;
if (rb.containsKey(key)) {
ret = rb.getStringArray(key);
}
}
// Next, try DefaultNumberingSystem value
if (ret == null && rb.containsKey("DefaultNumberingSystem")) {
key = rb.getString("DefaultNumberingSystem") + "." + type;
if (rb.containsKey(key)) {
ret = rb.getStringArray(key);
}
}
// Last resort. No need to check the availability.
// Just let it throw MissingResourceException when needed.
if (ret == null) {
ret = rb.getStringArray(type);
}
return ret;
}
public String getCurrencyName(String key) {
Object currencyName = null;
String cacheKey = CURRENCY_NAMES + key;
@ -485,7 +493,7 @@ public class LocaleResources {
if (data == null || ((numberPatterns = (String[]) data.get()) == null)) {
ResourceBundle resource = localeData.getNumberFormatData(locale);
numberPatterns = resource.getStringArray("NumberPatterns");
numberPatterns = getNumberStrings(resource, "NumberPatterns");
cache.put(NUMBER_PATTERNS_CACHEKEY,
new ResourceReference(NUMBER_PATTERNS_CACHEKEY, (Object) numberPatterns, referenceQueue));
}

View File

@ -23,7 +23,7 @@
/**
* @test
* @bug 8220309
* @bug 8220309 8230284
* @library /java/text/testlib
* @summary Test String representation of MinusSign/Percent/PerMill symbols.
* This test assumes CLDR has numbering systems for "arab" and
@ -55,14 +55,14 @@ public class DFSMinusPerCentMill {
// Locale, FormatStyle, expected format, expected single char symbol
{US_ARAB, Type.NUMBER, "\u061c-\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666"},
{US_ARAB, Type.PERCENT, "\u061c-\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066a\u061c"},
{US_ARAB, Type.CURRENCY, "\u061c-$\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666"},
{US_ARAB, Type.CURRENCY, "\u061c-\u0661\u066c\u0662\u0663\u0664\u066b\u0665\u0666\u00a0$"},
{US_ARAB, Type.INTEGER, "\u061c-\u0661\u066c\u0662\u0663\u0665"},
{US_ARAB, Type.COMPACT, "\u061c-\u0661K"},
{US_ARAB, Type.PERMILL, "\u061c-\u0661\u0662\u0663\u0664\u0665\u0666\u0660\u0609"},
{US_ARABEXT, Type.NUMBER, "\u200e-\u200e\u06f1\u066c\u06f2\u06f3\u06f4\u066b\u06f5\u06f6"},
{US_ARABEXT, Type.PERCENT, "\u200e-\u200e\u06f1\u06f2\u06f3\u066c\u06f4\u06f5\u06f6\u066a"},
{US_ARABEXT, Type.CURRENCY, "\u200e-\u200e$\u06f1\u066c\u06f2\u06f3\u06f4\u066b\u06f5\u06f6"},
{US_ARABEXT, Type.CURRENCY, "\u200e-\u200e$\u00a0\u06f1\u066c\u06f2\u06f3\u06f4\u066b\u06f5\u06f6"},
{US_ARABEXT, Type.INTEGER, "\u200e-\u200e\u06f1\u066c\u06f2\u06f3\u06f5"},
{US_ARABEXT, Type.COMPACT, "\u200e-\u200e\u06f1K"},
{US_ARABEXT, Type.PERMILL, "\u200e-\u200e\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f0\u0609"},

View File

@ -24,10 +24,10 @@
/*
*
* @test
* @bug 8215181
* @bug 8215181 8230284
* @summary Tests the "u-cf" extension
* @modules jdk.localedata
* @run testng/othervm CurrencyFormatTests
* @run testng/othervm -Djava.locale.providers=CLDR CurrencyFormatTests
*/
import static org.testng.Assert.assertEquals;
@ -78,6 +78,22 @@ public class CurrencyFormatTests {
{Locale.forLanguageTag("en-US-u-rg-CHZZZZ-cf-standard"), -100, "CHF-100.00"},
{Locale.forLanguageTag("en-US-u-rg-CHZZZZ-cf-account"), -100, "CHF-100.00"},
{Locale.forLanguageTag("en-US-u-rg-CHZZZZ-cf-bogus"), -100, "CHF-100.00"},
// Numbering systems
// explicit
{Locale.forLanguageTag("zh-CN-u-nu-arab"), -100, "\u061c-\uffe5\u0661\u0660\u0660\u066b\u0660\u0660"},
{Locale.forLanguageTag("zh-CN-u-nu-arab-cf-standard"), -100, "\u061c-\uffe5\u0661\u0660\u0660\u066b\u0660\u0660"},
{Locale.forLanguageTag("zh-CN-u-nu-arab-cf-account"), -100, "\u061c-\uffe5\u0661\u0660\u0660\u066b\u0660\u0660"},
{Locale.forLanguageTag("zh-CN-u-nu-arab-cf-bogus"), -100, "\u061c-\uffe5\u0661\u0660\u0660\u066b\u0660\u0660"},
// implicit
{Locale.forLanguageTag("zh-CN"), -100, "-\uffe5100.00"},
{Locale.forLanguageTag("zh-CN-u-cf-standard"), -100, "-\uffe5100.00"},
{Locale.forLanguageTag("zh-CN-u-cf-account"), -100, "(\uffe5100.00)"},
{Locale.forLanguageTag("zh-CN-u-cf-bogus"), -100, "-\uffe5100.00"},
{Locale.forLanguageTag("ar-SA"), -100, "\u061c-\u0661\u0660\u0660\u066b\u0660\u0660\u00a0\u0631.\u0633.\u200f"},
{Locale.forLanguageTag("ar-SA-u-cf-standard"), -100, "\u061c-\u0661\u0660\u0660\u066b\u0660\u0660\u00a0\u0631.\u0633.\u200f"},
{Locale.forLanguageTag("ar-SA-u-cf-account"), -100, "\u061c-\u0661\u0660\u0660\u066b\u0660\u0660\u00a0\u0631.\u0633.\u200f"},
{Locale.forLanguageTag("ar-SA-u-cf-bogus"), -100, "\u061c-\u0661\u0660\u0660\u066b\u0660\u0660\u00a0\u0631.\u0633.\u200f"},
};
}

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,7 @@
* 8017142 8037343 8055222 8042126 8074791 8075173 8080774 8129361 8134916
* 8145136 8145952 8164784 8037111 8081643 7037368 8178872 8185841 8190918
* 8187946 8195478 8181157 8179071 8193552 8202026 8204269 8202537 8208746
* 8209775 8221432 8227127
* 8209775 8221432 8227127 8230284
* @summary Verify locale data
* @modules java.base/sun.util.resources
* @modules jdk.localedata