Fix a couple of Locale bugs discovered by ICU4J tests.
ULocaleTest revealed two issues :
- We weren't handling the legacy ja_JP_JP and th_TH_TH locales
correctly.
- We weren't lower-casing the extension key in forLanguageTag. This
shouldn't matter since language tags are case insensitive, by
definition but the convention is to keep them lowercase. This also
fixes a bug in the builder where extension keys were case sensitive
since we weren't normalizing them properly.
bug: 20252611
Change-Id: Id15823186c282aeac9adb937d200dc3409d580df
diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java
index 1885f83..7226e1c 100644
--- a/luni/src/main/java/java/util/Locale.java
+++ b/luni/src/main/java/java/util/Locale.java
@@ -744,12 +744,13 @@
final String normalizedValue = value.toLowerCase(Locale.ROOT).replace('_', '-');
final String[] subtags = normalizedValue.split("-");
+ final char normalizedKey = Character.toLowerCase(key);
// Lengths for subtags in the private use extension should be [1, 8] chars.
// For all other extensions, they should be [2, 8] chars.
//
// http://www.rfc-editor.org/rfc/bcp/bcp47.txt
- final int minimumLength = (key == PRIVATE_USE_EXTENSION) ? 1 : 2;
+ final int minimumLength = (normalizedKey == PRIVATE_USE_EXTENSION) ? 1 : 2;
for (String subtag : subtags) {
if (!isValidBcp47Alphanum(subtag, minimumLength, 8)) {
throw new IllformedLocaleException(
@@ -759,14 +760,14 @@
// We need to take special action in the case of unicode extensions,
// since we claim to understand their keywords and attributes.
- if (key == UNICODE_LOCALE_EXTENSION) {
+ if (normalizedKey == UNICODE_LOCALE_EXTENSION) {
// First clear existing attributes and keywords.
extensions.clear();
attributes.clear();
parseUnicodeExtension(subtags, keywords, attributes);
} else {
- extensions.put(key, normalizedValue);
+ extensions.put(normalizedKey, normalizedValue);
}
return this;
@@ -986,6 +987,27 @@
this.unicodeKeywords = Collections.unmodifiableMap(keywordsCopy);
this.extensions = Collections.unmodifiableMap(extensionsCopy);
} else {
+
+ // The locales ja_JP_JP and th_TH_TH are ill formed since their variant is too
+ // short, however they have been used to represent a locale with the japanese imperial
+ // calendar and thai numbering respectively. We add an extension in their constructor
+ // to modernize them.
+ if ("ja".equals(language) && "JP".equals(country) && "JP".equals(variant)) {
+ Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
+ keywordsCopy.put("ca", "japanese");
+ unicodeKeywords = keywordsCopy;
+ } else if ("th".equals(language) && "TH".equals(country) && "TH".equals(variant)) {
+ Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
+ keywordsCopy.put("nu", "thai");
+ unicodeKeywords = keywordsCopy;
+ }
+
+ if (!unicodeKeywords.isEmpty() || !unicodeAttributes.isEmpty()) {
+ Map<Character, String> extensionsCopy = new TreeMap<>(extensions);
+ addUnicodeExtensionToExtensionsMap(unicodeAttributes, unicodeKeywords, extensionsCopy);
+ extensions = extensionsCopy;
+ }
+
this.unicodeAttributes = unicodeAttributes;
this.unicodeKeywords = unicodeKeywords;
this.extensions = extensions;
@@ -2134,7 +2156,7 @@
return extensionKeyIndex;
}
- final String key = subtags[extensionKeyIndex];
+ final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
if (extensions.containsKey(key.charAt(0))) {
return extensionKeyIndex;
}
@@ -2146,7 +2168,7 @@
// Mark the start of the next extension. Also keep track of whether this
// is a private use extension, and throw an error if it doesn't come last.
extensionKeyIndex = i;
- if ("x".equals(subtag)) {
+ if ("x".equals(subtag.toLowerCase(Locale.ROOT))) {
privateUseExtensionIndex = i;
} else if (privateUseExtensionIndex != -1) {
// The private use extension must come last.
@@ -2169,7 +2191,7 @@
return extensionKeyIndex;
}
- final String key = subtags[extensionKeyIndex];
+ final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
if (extensions.containsKey(key.charAt(0))) {
return extensionKeyIndex;
}
diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java
index e1e84ab..9005f25 100644
--- a/luni/src/test/java/libcore/java/util/LocaleTest.java
+++ b/luni/src/test/java/libcore/java/util/LocaleTest.java
@@ -1203,4 +1203,38 @@
System.setUnchangeableSystemProperty("user.locale", userLocale);
}
}
+
+ // http://b/20252611
+ public void testLegacyLocalesWithExtensions() {
+ Locale ja_JP_JP = new Locale("ja", "JP", "JP");
+ assertEquals("ca-japanese", ja_JP_JP.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+ assertEquals("japanese", ja_JP_JP.getUnicodeLocaleType("ca"));
+
+ Locale th_TH_TH = new Locale("th", "TH", "TH");
+ assertEquals("nu-thai", th_TH_TH.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+ assertEquals("thai", th_TH_TH.getUnicodeLocaleType("nu"));
+ }
+
+ // http://b/20252611
+ public void testLowerCaseExtensionKeys() {
+ // We must lowercase extension keys in forLanguageTag..
+ Locale ar_EG = Locale.forLanguageTag("ar-EG-U-nu-arab");
+ assertEquals("nu-arab", ar_EG.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
+ assertEquals("ar-EG-u-nu-arab", ar_EG.toLanguageTag());
+
+ // ... and in builders.
+ Locale.Builder b = new Locale.Builder();
+ b.setLanguage("ar");
+ b.setRegion("EG");
+ b.setExtension('U', "nu-arab");
+ assertEquals("ar-EG-u-nu-arab", b.build().toLanguageTag());
+
+ // Corollary : extension keys are case insensitive.
+ b = new Locale.Builder();
+ b.setLanguage("ar");
+ b.setRegion("EG");
+ b.setExtension('U', "nu-arab");
+ b.setExtension('u', "nu-thai");
+ assertEquals("ar-EG-u-nu-thai", b.build().toLanguageTag());
+ }
}