7028818: (lc) Lazily initialize locale extension
7029740: (lc) New Locale class implementation doesn't follow the Java coding conventions
7032820: (lc) sun.util.locale.InternalLocaleBuilder.CaseInsensitiveChar.equals problems
7033503: (lc) Restore optimization code for Locale class initialization
7033504: (lc) incompatible behavior change for ja_JP_JP and th_TH_TH locales
Reviewed-by: naoto
/*
* Copyright (c) 2010, 2011, 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.
*/
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.IllformedLocaleException;
import java.util.List;
import java.util.Locale;
import java.util.Locale.Builder;
import java.util.Set;
/**
* @test
* @bug 6875847 6992272 7002320 7015500 7023613 7032820 7033504
* @summary test API changes to Locale
* @compile LocaleEnhanceTest.java
* @run main/othervm -esa LocaleEnhanceTest
*/
public class LocaleEnhanceTest extends LocaleTestFmwk {
public static void main(String[] args) throws Exception {
List<String> argList = new ArrayList<String>();
argList.addAll(Arrays.asList(args));
argList.add("-nothrow");
new LocaleEnhanceTest().run(argList.toArray(new String[argList.size()]));
}
public LocaleEnhanceTest() {
}
///
/// Generic sanity tests
///
/** A canonical language code. */
private static final String l = "en";
/** A canonical script code.. */
private static final String s = "Latn";
/** A canonical region code. */
private static final String c = "US";
/** A canonical variant code. */
private static final String v = "NewYork";
/**
* Ensure that Builder builds locales that have the expected
* tag and java6 ID. Note the odd cases for the ID.
*/
public void testCreateLocaleCanonicalValid() {
String[] valids = {
"en-Latn-US-NewYork", "en_US_NewYork_#Latn",
"en-Latn-US", "en_US_#Latn",
"en-Latn-NewYork", "en__NewYork_#Latn", // double underscore
"en-Latn", "en__#Latn", // double underscore
"en-US-NewYork", "en_US_NewYork",
"en-US", "en_US",
"en-NewYork", "en__NewYork", // double underscore
"en", "en",
"und-Latn-US-NewYork", "_US_NewYork_#Latn",
"und-Latn-US", "_US_#Latn",
"und-Latn-NewYork", "", // variant only not supported
"und-Latn", "",
"und-US-NewYork", "_US_NewYork",
"und-US", "_US",
"und-NewYork", "", // variant only not supported
"und", ""
};
Builder builder = new Builder();
for (int i = 0; i < valids.length; i += 2) {
String tag = valids[i];
String id = valids[i+1];
String idl = (i & 16) == 0 ? l : "";
String ids = (i & 8) == 0 ? s : "";
String idc = (i & 4) == 0 ? c : "";
String idv = (i & 2) == 0 ? v : "";
String msg = String.valueOf(i/2) + ": '" + tag + "' ";
try {
Locale l = builder
.setLanguage(idl)
.setScript(ids)
.setRegion(idc)
.setVariant(idv)
.build();
assertEquals(msg + "language", idl, l.getLanguage());
assertEquals(msg + "script", ids, l.getScript());
assertEquals(msg + "country", idc, l.getCountry());
assertEquals(msg + "variant", idv, l.getVariant());
assertEquals(msg + "tag", tag, l.toLanguageTag());
assertEquals(msg + "id", id, l.toString());
}
catch (IllegalArgumentException e) {
errln(msg + e.getMessage());
}
}
}
/**
* Test that locale construction works with 'multiple variants'.
* <p>
* The string "Newer__Yorker" is treated as three subtags,
* "Newer", "", and "Yorker", and concatenated into one
* subtag by omitting empty subtags and joining the remainer
* with underscores. So the resulting variant tag is "Newer_Yorker".
* Note that 'New' and 'York' are invalid BCP47 variant subtags
* because they are too short.
*/
public void testCreateLocaleMultipleVariants() {
String[] valids = {
"en-Latn-US-Newer-Yorker", "en_US_Newer_Yorker_#Latn",
"en-Latn-Newer-Yorker", "en__Newer_Yorker_#Latn",
"en-US-Newer-Yorker", "en_US_Newer_Yorker",
"en-Newer-Yorker", "en__Newer_Yorker",
"und-Latn-US-Newer-Yorker", "_US_Newer_Yorker_#Latn",
"und-Latn-Newer-Yorker", "",
"und-US-Newer-Yorker", "_US_Newer_Yorker",
"und-Newer-Yorker", "",
};
Builder builder = new Builder(); // lenient variant
final String idv = "Newer_Yorker";
for (int i = 0; i < valids.length; i += 2) {
String tag = valids[i];
String id = valids[i+1];
String idl = (i & 8) == 0 ? l : "";
String ids = (i & 4) == 0 ? s : "";
String idc = (i & 2) == 0 ? c : "";
String msg = String.valueOf(i/2) + ": " + tag + " ";
try {
Locale l = builder
.setLanguage(idl)
.setScript(ids)
.setRegion(idc)
.setVariant(idv)
.build();
assertEquals(msg + " language", idl, l.getLanguage());
assertEquals(msg + " script", ids, l.getScript());
assertEquals(msg + " country", idc, l.getCountry());
assertEquals(msg + " variant", idv, l.getVariant());
assertEquals(msg + "tag", tag, l.toLanguageTag());
assertEquals(msg + "id", id, l.toString());
}
catch (IllegalArgumentException e) {
errln(msg + e.getMessage());
}
}
}
/**
* Ensure that all these invalid formats are not recognized by
* forLanguageTag.
*/
public void testCreateLocaleCanonicalInvalidSeparator() {
String[] invalids = {
// trailing separator
"en_Latn_US_NewYork_",
"en_Latn_US_",
"en_Latn_",
"en_",
"_",
// double separator
"en_Latn_US__NewYork",
"_Latn_US__NewYork",
"en_US__NewYork",
"_US__NewYork",
// are these OK?
// "en_Latn__US_NewYork", // variant is 'US_NewYork'
// "_Latn__US_NewYork", // variant is 'US_NewYork'
// "en__Latn_US_NewYork", // variant is 'Latn_US_NewYork'
// "en__US_NewYork", // variant is 'US_NewYork'
// double separator without language or script
"__US",
"__NewYork",
// triple separator anywhere except within variant
"en___NewYork",
"en_Latn___NewYork",
"_Latn___NewYork",
"___NewYork",
};
for (int i = 0; i < invalids.length; ++i) {
String id = invalids[i];
Locale l = Locale.forLanguageTag(id);
assertEquals(id, "und", l.toLanguageTag());
}
}
/**
* Ensure that all current locale ids parse. Use DateFormat as a proxy
* for all current locale ids.
*/
public void testCurrentLocales() {
Locale[] locales = java.text.DateFormat.getAvailableLocales();
Builder builder = new Builder();
for (Locale target : locales) {
String tag = target.toLanguageTag();
// the tag recreates the original locale,
// except no_NO_NY
Locale tagResult = Locale.forLanguageTag(tag);
if (!target.getVariant().equals("NY")) {
assertEquals("tagResult", target, tagResult);
}
// the builder also recreates the original locale,
// except ja_JP_JP, th_TH_TH and no_NO_NY
Locale builderResult = builder.setLocale(target).build();
if (target.getVariant().length() != 2) {
assertEquals("builderResult", target, builderResult);
}
}
}
/**
* Ensure that all icu locale ids parse.
*/
public void testIcuLocales() throws Exception {
BufferedReader br = new BufferedReader(
new InputStreamReader(
LocaleEnhanceTest.class.getResourceAsStream("icuLocales.txt"),
"UTF-8"));
String id = null;
while (null != (id = br.readLine())) {
Locale result = Locale.forLanguageTag(id);
assertEquals("ulocale", id, result.toLanguageTag());
}
}
///
/// Compatibility tests
///
public void testConstructor() {
// all the old weirdness still holds, no new weirdness
String[][] tests = {
// language to lower case, region to upper, variant unchanged
// short
{ "X", "y", "z", "x", "Y" },
// long
{ "xXxXxXxXxXxX", "yYyYyYyYyYyYyYyY", "zZzZzZzZzZzZzZzZ",
"xxxxxxxxxxxx", "YYYYYYYYYYYYYYYY" },
// mapped language ids
{ "he", "IW", "", "iw" },
{ "iw", "IW", "", "iw" },
{ "yi", "DE", "", "ji" },
{ "ji", "DE", "", "ji" },
{ "id", "ID", "", "in" },
{ "in", "ID", "", "in" },
// special variants
{ "ja", "JP", "JP" },
{ "th", "TH", "TH" },
{ "no", "NO", "NY" },
{ "no", "NO", "NY" },
// no canonicalization of 3-letter language codes
{ "eng", "US", "" }
};
for (int i = 0; i < tests.length; ++ i) {
String[] test = tests[i];
String id = String.valueOf(i);
Locale locale = new Locale(test[0], test[1], test[2]);
assertEquals(id + " lang", test.length > 3 ? test[3] : test[0], locale.getLanguage());
assertEquals(id + " region", test.length > 4 ? test[4] : test[1], locale.getCountry());
assertEquals(id + " variant", test.length > 5 ? test[5] : test[2], locale.getVariant());
}
}
///
/// Locale API tests.
///
public void testGetScript() {
// forLanguageTag normalizes case
Locale locale = Locale.forLanguageTag("und-latn");
assertEquals("forLanguageTag", "Latn", locale.getScript());
// Builder normalizes case
locale = new Builder().setScript("LATN").build();
assertEquals("builder", "Latn", locale.getScript());
// empty string is returned, not null, if there is no script
locale = Locale.forLanguageTag("und");
assertEquals("script is empty string", "", locale.getScript());
}
public void testGetExtension() {
// forLanguageTag does NOT normalize to hyphen
Locale locale = Locale.forLanguageTag("und-a-some_ex-tension");
assertEquals("some_ex-tension", null, locale.getExtension('a'));
// regular extension
locale = new Builder().setExtension('a', "some-ex-tension").build();
assertEquals("builder", "some-ex-tension", locale.getExtension('a'));
// returns null if extension is not present
assertEquals("empty b", null, locale.getExtension('b'));
// throws exception if extension tag is illegal
new ExpectIAE() { public void call() { Locale.forLanguageTag("").getExtension('\uD800'); }};
// 'x' is not an extension, it's a private use tag, but it's accessed through this API
locale = Locale.forLanguageTag("x-y-z-blork");
assertEquals("x", "y-z-blork", locale.getExtension('x'));
}
public void testGetExtensionKeys() {
Locale locale = Locale.forLanguageTag("und-a-xx-yy-b-zz-ww");
Set<Character> result = locale.getExtensionKeys();
assertEquals("result size", 2, result.size());
assertTrue("'a','b'", result.contains('a') && result.contains('b'));
// result is not mutable
try {
result.add('x');
errln("expected exception on add to extension key set");
}
catch (UnsupportedOperationException e) {
// ok
}
// returns empty set if no extensions
locale = Locale.forLanguageTag("und");
assertTrue("empty result", locale.getExtensionKeys().isEmpty());
}
public void testGetUnicodeLocaleAttributes() {
Locale locale = Locale.forLanguageTag("en-US-u-abc-def");
Set<String> attributes = locale.getUnicodeLocaleAttributes();
assertEquals("number of attributes", 2, attributes.size());
assertTrue("attribute abc", attributes.contains("abc"));
assertTrue("attribute def", attributes.contains("def"));
locale = Locale.forLanguageTag("en-US-u-ca-gregory");
attributes = locale.getUnicodeLocaleAttributes();
assertTrue("empty attributes", attributes.isEmpty());
}
public void testGetUnicodeLocaleType() {
Locale locale = Locale.forLanguageTag("und-u-co-japanese-nu-thai");
assertEquals("collation", "japanese", locale.getUnicodeLocaleType("co"));
assertEquals("numbers", "thai", locale.getUnicodeLocaleType("nu"));
// Unicode locale extension key is case insensitive
assertEquals("key case", "japanese", locale.getUnicodeLocaleType("Co"));
// if keyword is not present, returns null
assertEquals("locale keyword not present", null, locale.getUnicodeLocaleType("xx"));
// if no locale extension is set, returns null
locale = Locale.forLanguageTag("und");
assertEquals("locale extension not present", null, locale.getUnicodeLocaleType("co"));
// typeless keyword
locale = Locale.forLanguageTag("und-u-kn");
assertEquals("typeless keyword", "", locale.getUnicodeLocaleType("kn"));
// invalid keys throw exception
new ExpectIAE() { public void call() { Locale.forLanguageTag("").getUnicodeLocaleType("q"); }};
new ExpectIAE() { public void call() { Locale.forLanguageTag("").getUnicodeLocaleType("abcdefghi"); }};
// null argument throws exception
new ExpectNPE() { public void call() { Locale.forLanguageTag("").getUnicodeLocaleType(null); }};
}
public void testGetUnicodeLocaleKeys() {
Locale locale = Locale.forLanguageTag("und-u-co-japanese-nu-thai");
Set<String> result = locale.getUnicodeLocaleKeys();
assertEquals("two keys", 2, result.size());
assertTrue("co and nu", result.contains("co") && result.contains("nu"));
// result is not modifiable
try {
result.add("frobozz");
errln("expected exception when add to locale key set");
}
catch (UnsupportedOperationException e) {
// ok
}
}
public void testPrivateUseExtension() {
Locale locale = Locale.forLanguageTag("x-y-x-blork-");
assertEquals("blork", "y-x-blork", locale.getExtension(Locale.PRIVATE_USE_EXTENSION));
locale = Locale.forLanguageTag("und");
assertEquals("no privateuse", null, locale.getExtension(Locale.PRIVATE_USE_EXTENSION));
}
public void testToLanguageTag() {
// lots of normalization to test here
// test locales created using the constructor
String[][] tests = {
// empty locale canonicalizes to 'und'
{ "", "", "", "und" },
// variant alone is not a valid Locale, but has a valid language tag
{ "", "", "NewYork", "und-NewYork" },
// standard valid locales
{ "", "Us", "", "und-US" },
{ "", "US", "NewYork", "und-US-NewYork" },
{ "EN", "", "", "en" },
{ "EN", "", "NewYork", "en-NewYork" },
{ "EN", "US", "", "en-US" },
{ "EN", "US", "NewYork", "en-US-NewYork" },
// underscore in variant will be emitted as multiple variant subtags
{ "en", "US", "Newer_Yorker", "en-US-Newer-Yorker" },
// invalid variant subtags are appended as private use
{ "en", "US", "new_yorker", "en-US-x-lvariant-new-yorker" },
// the first invalid variant subtags and following variant subtags are appended as private use
{ "en", "US", "Windows_XP_Home", "en-US-Windows-x-lvariant-XP-Home" },
// too long variant and following variant subtags disappear
{ "en", "US", "WindowsVista_SP2", "en-US" },
// invalid region subtag disappears
{ "en", "USA", "", "en" },
// invalid language tag disappears
{ "e", "US", "", "und-US" },
// three-letter language tags are not canonicalized
{ "Eng", "", "", "eng" },
// legacy languages canonicalize to modern equivalents
{ "he", "IW", "", "he-IW" },
{ "iw", "IW", "", "he-IW" },
{ "yi", "DE", "", "yi-DE" },
{ "ji", "DE", "", "yi-DE" },
{ "id", "ID", "", "id-ID" },
{ "in", "ID", "", "id-ID" },
// special values are converted on output
{ "ja", "JP", "JP", "ja-JP-u-ca-japanese-x-lvariant-JP" },
{ "th", "TH", "TH", "th-TH-u-nu-thai-x-lvariant-TH" },
{ "no", "NO", "NY", "nn-NO" }
};
for (int i = 0; i < tests.length; ++i) {
String[] test = tests[i];
Locale locale = new Locale(test[0], test[1], test[2]);
assertEquals("case " + i, test[3], locale.toLanguageTag());
}
// test locales created from forLanguageTag
String[][] tests1 = {
// case is normalized during the round trip
{ "EN-us", "en-US" },
{ "en-Latn-US", "en-Latn-US" },
// reordering Unicode locale extensions
{ "de-u-co-phonebk-ca-gregory", "de-u-ca-gregory-co-phonebk" },
// private use only language tag is preserved (no extra "und")
{ "x-elmer", "x-elmer" },
{ "x-lvariant-JP", "x-lvariant-JP" },
};
for (String[] test : tests1) {
Locale locale = Locale.forLanguageTag(test[0]);
assertEquals("case " + test[0], test[1], locale.toLanguageTag());
}
}
public void testForLanguageTag() {
// forLanguageTag implements the 'Language-Tag' production of
// BCP47, so it handles private use and grandfathered tags,
// unlike locale builder. Tags listed below (except for the
// sample private use tags) come from 4646bis Feb 29, 2009.
String[][] tests = {
// private use tags only
{ "x-abc", "x-abc" },
{ "x-a-b-c", "x-a-b-c" },
{ "x-a-12345678", "x-a-12345678" },
// grandfathered tags with preferred mappings
{ "i-ami", "ami" },
{ "i-bnn", "bnn" },
{ "i-hak", "hak" },
{ "i-klingon", "tlh" },
{ "i-lux", "lb" }, // two-letter tag
{ "i-navajo", "nv" }, // two-letter tag
{ "i-pwn", "pwn" },
{ "i-tao", "tao" },
{ "i-tay", "tay" },
{ "i-tsu", "tsu" },
{ "art-lojban", "jbo" },
{ "no-bok", "nb" },
{ "no-nyn", "nn" },
{ "sgn-BE-FR", "sfb" },
{ "sgn-BE-NL", "vgt" },
{ "sgn-CH-DE", "sgg" },
{ "zh-guoyu", "cmn" },
{ "zh-hakka", "hak" },
{ "zh-min-nan", "nan" },
{ "zh-xiang", "hsn" },
// grandfathered irregular tags, no preferred mappings, drop illegal fields
// from end. If no subtag is mappable, fallback to 'und'
{ "i-default", "en-x-i-default" },
{ "i-enochian", "x-i-enochian" },
{ "i-mingo", "see-x-i-mingo" },
{ "en-GB-oed", "en-GB-x-oed" },
{ "zh-min", "nan-x-zh-min" },
{ "cel-gaulish", "xtg-x-cel-gaulish" },
};
for (int i = 0; i < tests.length; ++i) {
String[] test = tests[i];
Locale locale = Locale.forLanguageTag(test[0]);
assertEquals("grandfathered case " + i, test[1], locale.toLanguageTag());
}
// forLanguageTag ignores everything past the first place it encounters
// a syntax error
tests = new String[][] {
{ "valid",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y-12345678-z",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y-12345678-z" },
{ "segment of private use tag too long",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y-123456789-z",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y" },
{ "segment of private use tag is empty",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y--12345678-z",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y" },
{ "first segment of private use tag is empty",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x--y-12345678-z",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def" },
{ "illegal extension tag",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-\uD800-y-12345678-z",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def" },
{ "locale subtag with no value",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-x-y-12345678-z",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-x-y-12345678-z" },
{ "locale key subtag invalid",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-123456789-def-x-y-12345678-z",
"en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc" },
// locale key subtag invalid in earlier position, all following subtags
// dropped (and so the locale extension dropped as well)
{ "locale key subtag invalid in earlier position",
"en-US-Newer-Yorker-a-bb-cc-dd-u-123456789-abc-bb-def-x-y-12345678-z",
"en-US-Newer-Yorker-a-bb-cc-dd" },
};
for (int i = 0; i < tests.length; ++i) {
String[] test = tests[i];
String msg = "syntax error case " + i + " " + test[0];
try {
Locale locale = Locale.forLanguageTag(test[1]);
assertEquals(msg, test[2], locale.toLanguageTag());
}
catch (IllegalArgumentException e) {
errln(msg + " caught exception: " + e);
}
}
// duplicated extension are just ignored
Locale locale = Locale.forLanguageTag("und-d-aa-00-bb-01-D-AA-10-cc-11-c-1234");
assertEquals("extension", "aa-00-bb-01", locale.getExtension('d'));
assertEquals("extension c", "1234", locale.getExtension('c'));
locale = Locale.forLanguageTag("und-U-ca-gregory-u-ca-japanese");
assertEquals("Unicode extension", "ca-gregory", locale.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
// redundant Unicode locale keys in an extension are ignored
locale = Locale.forLanguageTag("und-u-aa-000-bb-001-bB-002-cc-003-c-1234");
assertEquals("Unicode keywords", "aa-000-bb-001-cc-003", locale.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
assertEquals("Duplicated Unicode locake key followed by an extension", "1234", locale.getExtension('c'));
}
public void testGetDisplayScript() {
Locale latnLocale = Locale.forLanguageTag("und-latn");
Locale hansLocale = Locale.forLanguageTag("und-hans");
Locale oldLocale = Locale.getDefault();
Locale.setDefault(Locale.US);
assertEquals("latn US", "Latin", latnLocale.getDisplayScript());
assertEquals("hans US", "Simplified Han", hansLocale.getDisplayScript());
// note, no localization data yet other than US
// this should break when we have localization data for DE
Locale.setDefault(Locale.GERMANY);
assertEquals("latn DE", "Latin", latnLocale.getDisplayScript());
assertEquals("hans DE", "Simplified Han", hansLocale.getDisplayScript());
Locale.setDefault(oldLocale);
}
public void testGetDisplayScriptWithLocale() {
Locale latnLocale = Locale.forLanguageTag("und-latn");
Locale hansLocale = Locale.forLanguageTag("und-hans");
assertEquals("latn US", "Latin", latnLocale.getDisplayScript(Locale.US));
assertEquals("hans US", "Simplified Han", hansLocale.getDisplayScript(Locale.US));
// note, no localization data yet other than US
// this should break when we have localization data for DE
assertEquals("latn DE", "Latin", latnLocale.getDisplayScript(Locale.GERMANY));
assertEquals("hans DE", "Simplified Han", hansLocale.getDisplayScript(Locale.GERMANY));
}
public void testGetDisplayName() {
final Locale[] testLocales = {
Locale.ROOT,
new Locale("en"),
new Locale("en", "US"),
new Locale("", "US"),
new Locale("no", "NO", "NY"),
new Locale("", "", "NY"),
Locale.forLanguageTag("zh-Hans"),
Locale.forLanguageTag("zh-Hant"),
Locale.forLanguageTag("zh-Hans-CN"),
Locale.forLanguageTag("und-Hans"),
};
final String[] displayNameEnglish = {
"",
"English",
"English (United States)",
"United States",
"Norwegian (Norway,Nynorsk)",
"Nynorsk",
"Chinese (Simplified Han)",
"Chinese (Traditional Han)",
"Chinese (Simplified Han,China)",
"Simplified Han",
};
final String[] displayNameSimplifiedChinese = {
"",
"\u82f1\u6587",
"\u82f1\u6587 (\u7f8e\u56fd)",
"\u7f8e\u56fd",
"\u632a\u5a01\u6587 (\u632a\u5a01,Nynorsk)",
"Nynorsk",
"\u4e2d\u6587 (\u7b80\u4f53\u4e2d\u6587)",
"\u4e2d\u6587 (\u7e41\u4f53\u4e2d\u6587)",
"\u4e2d\u6587 (\u7b80\u4f53\u4e2d\u6587,\u4e2d\u56fd)",
"\u7b80\u4f53\u4e2d\u6587",
};
for (int i = 0; i < testLocales.length; i++) {
Locale loc = testLocales[i];
assertEquals("English display name for " + loc.toLanguageTag(),
displayNameEnglish[i], loc.getDisplayName(Locale.ENGLISH));
assertEquals("Simplified Chinese display name for " + loc.toLanguageTag(),
displayNameSimplifiedChinese[i], loc.getDisplayName(Locale.CHINA));
}
}
///
/// Builder tests
///
public void testBuilderSetLocale() {
Builder builder = new Builder();
Builder lenientBuilder = new Builder();
String languageTag = "en-Latn-US-NewYork-a-bb-ccc-u-co-japanese-x-y-z";
String target = "en-Latn-US-NewYork-a-bb-ccc-u-co-japanese-x-y-z";
Locale locale = Locale.forLanguageTag(languageTag);
Locale result = lenientBuilder
.setLocale(locale)
.build();
assertEquals("long tag", target, result.toLanguageTag());
assertEquals("long tag", locale, result);
// null is illegal
new BuilderNPE("locale") {
public void call() { b.setLocale(null); }
};
// builder canonicalizes the three legacy locales:
// ja_JP_JP, th_TH_TH, no_NY_NO.
locale = builder.setLocale(new Locale("ja", "JP", "JP")).build();
assertEquals("ja_JP_JP languagetag", "ja-JP-u-ca-japanese", locale.toLanguageTag());
assertEquals("ja_JP_JP variant", "", locale.getVariant());
locale = builder.setLocale(new Locale("th", "TH", "TH")).build();
assertEquals("th_TH_TH languagetag", "th-TH-u-nu-thai", locale.toLanguageTag());
assertEquals("th_TH_TH variant", "", locale.getVariant());
locale = builder.setLocale(new Locale("no", "NO", "NY")).build();
assertEquals("no_NO_NY languagetag", "nn-NO", locale.toLanguageTag());
assertEquals("no_NO_NY language", "nn", locale.getLanguage());
assertEquals("no_NO_NY variant", "", locale.getVariant());
// non-canonical, non-legacy locales are invalid
new BuilderILE("123_4567_89") {
public void call() {
b.setLocale(new Locale("123", "4567", "89"));
}
};
}
public void testBuilderSetLanguageTag() {
String source = "eN-LaTn-Us-NewYork-A-Xx-B-Yy-X-1-2-3";
String target = "en-Latn-US-NewYork-a-xx-b-yy-x-1-2-3";
Builder builder = new Builder();
String result = builder
.setLanguageTag(source)
.build()
.toLanguageTag();
assertEquals("language", target, result);
// redundant extensions cause a failure
new BuilderILE() { public void call() { b.setLanguageTag("und-a-xx-yy-b-ww-A-00-11-c-vv"); }};
// redundant Unicode locale extension keys within an Unicode locale extension cause a failure
new BuilderILE() { public void call() { b.setLanguageTag("und-u-nu-thai-NU-chinese-xx-1234"); }};
}
public void testBuilderSetLanguage() {
// language is normalized to lower case
String source = "eN";
String target = "en";
String defaulted = "";
Builder builder = new Builder();
String result = builder
.setLanguage(source)
.build()
.getLanguage();
assertEquals("en", target, result);
// setting with empty resets
result = builder
.setLanguage(target)
.setLanguage("")
.build()
.getLanguage();
assertEquals("empty", defaulted, result);
// setting with null resets too
result = builder
.setLanguage(target)
.setLanguage(null)
.build()
.getLanguage();
assertEquals("null", defaulted, result);
// language codes must be 2-8 alpha
// for forwards compatibility, 4-alpha and 5-8 alpha (registered)
// languages are accepted syntax
new BuilderILE("q", "abcdefghi", "13") { public void call() { b.setLanguage(arg); }};
// language code validation is NOT performed, any 2-8-alpha passes
assertNotNull("2alpha", builder.setLanguage("zz").build());
assertNotNull("8alpha", builder.setLanguage("abcdefgh").build());
// three-letter language codes are NOT canonicalized to two-letter
result = builder
.setLanguage("eng")
.build()
.getLanguage();
assertEquals("eng", "eng", result);
}
public void testBuilderSetScript() {
// script is normalized to title case
String source = "lAtN";
String target = "Latn";
String defaulted = "";
Builder builder = new Builder();
String result = builder
.setScript(source)
.build()
.getScript();
assertEquals("script", target, result);
// setting with empty resets
result = builder
.setScript(target)
.setScript("")
.build()
.getScript();
assertEquals("empty", defaulted, result);
// settting with null also resets
result = builder
.setScript(target)
.setScript(null)
.build()
.getScript();
assertEquals("null", defaulted, result);
// ill-formed script codes throw IAE
// must be 4alpha
new BuilderILE("abc", "abcde", "l3tn") { public void call() { b.setScript(arg); }};
// script code validation is NOT performed, any 4-alpha passes
assertEquals("4alpha", "Wxyz", builder.setScript("wxyz").build().getScript());
}
public void testBuilderSetRegion() {
// region is normalized to upper case
String source = "uS";
String target = "US";
String defaulted = "";
Builder builder = new Builder();
String result = builder
.setRegion(source)
.build()
.getCountry();
assertEquals("us", target, result);
// setting with empty resets
result = builder
.setRegion(target)
.setRegion("")
.build()
.getCountry();
assertEquals("empty", defaulted, result);
// setting with null also resets
result = builder
.setRegion(target)
.setRegion(null)
.build()
.getCountry();
assertEquals("null", defaulted, result);
// ill-formed region codes throw IAE
// 2 alpha or 3 numeric
new BuilderILE("q", "abc", "12", "1234", "a3", "12a") { public void call() { b.setRegion(arg); }};
// region code validation is NOT performed, any 2-alpha or 3-digit passes
assertEquals("2alpha", "ZZ", builder.setRegion("ZZ").build().getCountry());
assertEquals("3digit", "000", builder.setRegion("000").build().getCountry());
}
public void testBuilderSetVariant() {
// Variant case is not normalized in lenient variant mode
String source = "NewYork";
String target = source;
String defaulted = "";
Builder builder = new Builder();
String result = builder
.setVariant(source)
.build()
.getVariant();
assertEquals("NewYork", target, result);
result = builder
.setVariant("NeWeR_YoRkEr")
.build()
.toLanguageTag();
assertEquals("newer yorker", "und-NeWeR-YoRkEr", result);
// subtags of variant are NOT reordered
result = builder
.setVariant("zzzzz_yyyyy_xxxxx")
.build()
.getVariant();
assertEquals("zyx", "zzzzz_yyyyy_xxxxx", result);
// setting to empty resets
result = builder
.setVariant(target)
.setVariant("")
.build()
.getVariant();
assertEquals("empty", defaulted, result);
// setting to null also resets
result = builder
.setVariant(target)
.setVariant(null)
.build()
.getVariant();
assertEquals("null", defaulted, result);
// ill-formed variants throw IAE
// digit followed by 3-7 characters, or alpha followed by 4-8 characters.
new BuilderILE("abcd", "abcdefghi", "1ab", "1abcdefgh") { public void call() { b.setVariant(arg); }};
// 4 characters is ok as long as the first is a digit
assertEquals("digit+3alpha", "1abc", builder.setVariant("1abc").build().getVariant());
// all subfields must conform
new BuilderILE("abcde-fg") { public void call() { b.setVariant(arg); }};
}
public void testBuilderSetExtension() {
// upper case characters are normalized to lower case
final char sourceKey = 'a';
final String sourceValue = "aB-aBcdefgh-12-12345678";
String target = "ab-abcdefgh-12-12345678";
Builder builder = new Builder();
String result = builder
.setExtension(sourceKey, sourceValue)
.build()
.getExtension(sourceKey);
assertEquals("extension", target, result);
// setting with empty resets
result = builder
.setExtension(sourceKey, sourceValue)
.setExtension(sourceKey, "")
.build()
.getExtension(sourceKey);
assertEquals("empty", null, result);
// setting with null also resets
result = builder
.setExtension(sourceKey, sourceValue)
.setExtension(sourceKey, null)
.build()
.getExtension(sourceKey);
assertEquals("null", null, result);
// ill-formed extension keys throw IAE
// must be in [0-9a-ZA-Z]
new BuilderILE("$") { public void call() { b.setExtension('$', sourceValue); }};
// each segment of value must be 2-8 alphanum
new BuilderILE("ab-cd-123456789") { public void call() { b.setExtension(sourceKey, arg); }};
// no multiple hyphens.
new BuilderILE("ab--cd") { public void call() { b.setExtension(sourceKey, arg); }};
// locale extension key has special handling
Locale locale = builder
.setExtension('u', "co-japanese")
.build();
assertEquals("locale extension", "japanese", locale.getUnicodeLocaleType("co"));
// locale extension has same behavior with set locale keyword
Locale locale2 = builder
.setUnicodeLocaleKeyword("co", "japanese")
.build();
assertEquals("locales with extension", locale, locale2);
// setting locale extension overrides all previous calls to setLocaleKeyword
Locale locale3 = builder
.setExtension('u', "xxx-nu-thai")
.build();
assertEquals("remove co", null, locale3.getUnicodeLocaleType("co"));
assertEquals("override thai", "thai", locale3.getUnicodeLocaleType("nu"));
assertEquals("override attribute", 1, locale3.getUnicodeLocaleAttributes().size());
// setting locale keyword extends values already set by the locale extension
Locale locale4 = builder
.setUnicodeLocaleKeyword("co", "japanese")
.build();
assertEquals("extend", "japanese", locale4.getUnicodeLocaleType("co"));
assertEquals("extend", "thai", locale4.getUnicodeLocaleType("nu"));
// locale extension subtags are reordered
result = builder
.clear()
.setExtension('u', "456-123-zz-123-yy-456-xx-789")
.build()
.toLanguageTag();
assertEquals("reorder", "und-u-123-456-xx-789-yy-456-zz-123", result);
// multiple keyword types
result = builder
.clear()
.setExtension('u', "nu-thai-foobar")
.build()
.getUnicodeLocaleType("nu");
assertEquals("multiple types", "thai-foobar", result);
// redundant locale extensions are ignored
result = builder
.clear()
.setExtension('u', "nu-thai-NU-chinese-xx-1234")
.build()
.toLanguageTag();
assertEquals("duplicate keys", "und-u-nu-thai-xx-1234", result);
}
public void testBuilderAddUnicodeLocaleAttribute() {
Builder builder = new Builder();
Locale locale = builder
.addUnicodeLocaleAttribute("def")
.addUnicodeLocaleAttribute("abc")
.build();
Set<String> uattrs = locale.getUnicodeLocaleAttributes();
assertEquals("number of attributes", 2, uattrs.size());
assertTrue("attribute abc", uattrs.contains("abc"));
assertTrue("attribute def", uattrs.contains("def"));
// remove attribute
locale = builder.removeUnicodeLocaleAttribute("xxx")
.build();
assertEquals("remove bogus", 2, uattrs.size());
// add duplicate
locale = builder.addUnicodeLocaleAttribute("abc")
.build();
assertEquals("add duplicate", 2, uattrs.size());
// null attribute throws NPE
new BuilderNPE("null attribute") { public void call() { b.addUnicodeLocaleAttribute(null); }};
// illformed attribute throws IllformedLocaleException
new BuilderILE("invalid attribute") { public void call() { b.addUnicodeLocaleAttribute("ca"); }};
}
public void testBuildersetUnicodeLocaleKeyword() {
// Note: most behavior is tested in testBuilderSetExtension
Builder builder = new Builder();
Locale locale = builder
.setUnicodeLocaleKeyword("co", "japanese")
.setUnicodeLocaleKeyword("nu", "thai")
.build();
assertEquals("co", "japanese", locale.getUnicodeLocaleType("co"));
assertEquals("nu", "thai", locale.getUnicodeLocaleType("nu"));
assertEquals("keys", 2, locale.getUnicodeLocaleKeys().size());
// can clear a keyword by setting to null, others remain
String result = builder
.setUnicodeLocaleKeyword("co", null)
.build()
.toLanguageTag();
assertEquals("empty co", "und-u-nu-thai", result);
// locale keyword extension goes when all keywords are gone
result = builder
.setUnicodeLocaleKeyword("nu", null)
.build()
.toLanguageTag();
assertEquals("empty nu", "und", result);
// locale keywords are ordered independent of order of addition
result = builder
.setUnicodeLocaleKeyword("zz", "012")
.setUnicodeLocaleKeyword("aa", "345")
.build()
.toLanguageTag();
assertEquals("reordered", "und-u-aa-345-zz-012", result);
// null keyword throws NPE
new BuilderNPE("keyword") { public void call() { b.setUnicodeLocaleKeyword(null, "thai"); }};
// well-formed keywords are two alphanum
new BuilderILE("a", "abc") { public void call() { b.setUnicodeLocaleKeyword(arg, "value"); }};
// well-formed values are 3-8 alphanum
new BuilderILE("ab", "abcdefghi") { public void call() { b.setUnicodeLocaleKeyword("ab", arg); }};
}
public void testBuilderPrivateUseExtension() {
// normalizes hyphens to underscore, case to lower
String source = "c-B-a";
String target = "c-b-a";
Builder builder = new Builder();
String result = builder
.setExtension(Locale.PRIVATE_USE_EXTENSION, source)
.build()
.getExtension(Locale.PRIVATE_USE_EXTENSION);
assertEquals("abc", target, result);
// multiple hyphens are ill-formed
new BuilderILE("a--b") { public void call() { b.setExtension(Locale.PRIVATE_USE_EXTENSION, arg); }};
}
public void testBuilderClear() {
String monster = "en-latn-US-NewYork-a-bb-cc-u-co-japanese-x-z-y-x-x";
Builder builder = new Builder();
Locale locale = Locale.forLanguageTag(monster);
String result = builder
.setLocale(locale)
.clear()
.build()
.toLanguageTag();
assertEquals("clear", "und", result);
}
public void testBuilderRemoveUnicodeAttribute() {
// tested in testBuilderAddUnicodeAttribute
}
public void testBuilderBuild() {
// tested in other test methods
}
public void testSerialize() {
final Locale[] testLocales = {
Locale.ROOT,
new Locale("en"),
new Locale("en", "US"),
new Locale("en", "US", "Win"),
new Locale("en", "US", "Win_XP"),
new Locale("ja", "JP"),
new Locale("ja", "JP", "JP"),
new Locale("th", "TH"),
new Locale("th", "TH", "TH"),
new Locale("no", "NO"),
new Locale("nb", "NO"),
new Locale("nn", "NO"),
new Locale("no", "NO", "NY"),
new Locale("nn", "NO", "NY"),
new Locale("he", "IL"),
new Locale("he", "IL", "var"),
new Locale("Language", "Country", "Variant"),
new Locale("", "US"),
new Locale("", "", "Java"),
Locale.forLanguageTag("en-Latn-US"),
Locale.forLanguageTag("zh-Hans"),
Locale.forLanguageTag("zh-Hant-TW"),
Locale.forLanguageTag("ja-JP-u-ca-japanese"),
Locale.forLanguageTag("und-Hant"),
Locale.forLanguageTag("und-a-123-456"),
Locale.forLanguageTag("en-x-java"),
Locale.forLanguageTag("th-TH-u-ca-buddist-nu-thai-x-lvariant-TH"),
};
for (Locale locale : testLocales) {
try {
// write
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(locale);
// read
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Object o = ois.readObject();
assertEquals("roundtrip " + locale, locale, o);
} catch (Exception e) {
errln(locale + " encountered exception:" + e.getLocalizedMessage());
}
}
}
public void testDeserialize6() {
final String TESTFILEPREFIX = "java6locale_";
File dataDir = null;
String dataDirName = System.getProperty("serialized.data.dir");
if (dataDirName == null) {
URL resdirUrl = getClass().getClassLoader().getResource("serialized");
if (resdirUrl != null) {
try {
dataDir = new File(resdirUrl.toURI());
} catch (URISyntaxException urie) {
}
}
} else {
dataDir = new File(dataDirName);
}
if (dataDir == null || !dataDir.isDirectory()) {
errln("Could not locate the serialized test case data location");
return;
}
File[] files = dataDir.listFiles();
for (File testfile : files) {
if (testfile.isDirectory()) {
continue;
}
String name = testfile.getName();
if (!name.startsWith(TESTFILEPREFIX)) {
continue;
}
Locale locale;
String locStr = name.substring(TESTFILEPREFIX.length());
if (locStr.equals("ROOT")) {
locale = Locale.ROOT;
} else {
String[] fields = locStr.split("_", 3);
String lang = fields[0];
String country = (fields.length >= 2) ? fields[1] : "";
String variant = (fields.length == 3) ? fields[2] : "";
locale = new Locale(lang, country, variant);
}
// deserialize
try (FileInputStream fis = new FileInputStream(testfile);
ObjectInputStream ois = new ObjectInputStream(fis))
{
Object o = ois.readObject();
assertEquals("Deserialize Java 6 Locale " + locale, o, locale);
} catch (Exception e) {
errln("Exception while reading " + testfile.getAbsolutePath() + " - " + e.getMessage());
}
}
}
public void testBug7002320() {
// forLanguageTag() and Builder.setLanguageTag(String)
// should add a location extension for following two cases.
//
// 1. language/country are "ja"/"JP" and the resolved variant (x-lvariant-*)
// is exactly "JP" and no BCP 47 extensions are available, then add
// a Unicode locale extension "ca-japanese".
// 2. language/country are "th"/"TH" and the resolved variant is exactly
// "TH" and no BCP 47 extensions are available, then add a Unicode locale
// extension "nu-thai".
//
String[][] testdata = {
{"ja-JP-x-lvariant-JP", "ja-JP-u-ca-japanese-x-lvariant-JP"}, // special case 1
{"ja-JP-x-lvariant-JP-XXX"},
{"ja-JP-u-ca-japanese-x-lvariant-JP"},
{"ja-JP-u-ca-gregory-x-lvariant-JP"},
{"ja-JP-u-cu-jpy-x-lvariant-JP"},
{"ja-x-lvariant-JP"},
{"th-TH-x-lvariant-TH", "th-TH-u-nu-thai-x-lvariant-TH"}, // special case 2
{"th-TH-u-nu-thai-x-lvariant-TH"},
{"en-US-x-lvariant-JP"},
};
Builder bldr = new Builder();
for (String[] data : testdata) {
String in = data[0];
String expected = (data.length == 1) ? data[0] : data[1];
// forLanguageTag
Locale loc = Locale.forLanguageTag(in);
String out = loc.toLanguageTag();
assertEquals("Language tag roundtrip by forLanguageTag with input: " + in, expected, out);
// setLanguageTag
bldr.clear();
bldr.setLanguageTag(in);
loc = bldr.build();
out = loc.toLanguageTag();
assertEquals("Language tag roundtrip by Builder.setLanguageTag with input: " + in, expected, out);
}
}
public void testBug7023613() {
String[][] testdata = {
{"en-Latn", "en__#Latn"},
{"en-u-ca-japanese", "en__#u-ca-japanese"},
};
for (String[] data : testdata) {
String in = data[0];
String expected = (data.length == 1) ? data[0] : data[1];
Locale loc = Locale.forLanguageTag(in);
String out = loc.toString();
assertEquals("Empty country field with non-empty script/extension with input: " + in, expected, out);
}
}
/*
* 7033504: (lc) incompatible behavior change for ja_JP_JP and th_TH_TH locales
*/
public void testBug7033504() {
checkCalendar(new Locale("ja", "JP", "jp"), "java.util.GregorianCalendar");
checkCalendar(new Locale("ja", "jp", "jp"), "java.util.GregorianCalendar");
checkCalendar(new Locale("ja", "JP", "JP"), "java.util.JapaneseImperialCalendar");
checkCalendar(new Locale("ja", "jp", "JP"), "java.util.JapaneseImperialCalendar");
checkCalendar(Locale.forLanguageTag("en-u-ca-japanese"),
"java.util.JapaneseImperialCalendar");
checkDigit(new Locale("th", "TH", "th"), '0');
checkDigit(new Locale("th", "th", "th"), '0');
checkDigit(new Locale("th", "TH", "TH"), '\u0e50');
checkDigit(new Locale("th", "TH", "TH"), '\u0e50');
checkDigit(Locale.forLanguageTag("en-u-nu-thai"), '\u0e50');
}
private void checkCalendar(Locale loc, String expected) {
Calendar cal = Calendar.getInstance(loc);
assertEquals("Wrong calendar", expected, cal.getClass().getName());
}
private void checkDigit(Locale loc, Character expected) {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(loc);
Character zero = dfs.getZeroDigit();
assertEquals("Wrong digit zero char", expected, zero);
}
///
/// utility asserts
///
private void assertTrue(String msg, boolean v) {
if (!v) {
errln(msg + ": expected true");
}
}
private void assertFalse(String msg, boolean v) {
if (v) {
errln(msg + ": expected false");
}
}
private void assertEquals(String msg, Object e, Object v) {
if (e == null ? v != null : !e.equals(v)) {
if (e != null) {
e = "'" + e + "'";
}
if (v != null) {
v = "'" + v + "'";
}
errln(msg + ": expected " + e + " but got " + v);
}
}
private void assertNotEquals(String msg, Object e, Object v) {
if (e == null ? v == null : e.equals(v)) {
if (e != null) {
e = "'" + e + "'";
}
errln(msg + ": expected not equal " + e);
}
}
private void assertNull(String msg, Object o) {
if (o != null) {
errln(msg + ": expected null but got '" + o + "'");
}
}
private void assertNotNull(String msg, Object o) {
if (o == null) {
errln(msg + ": expected non null");
}
}
// not currently used, might get rid of exceptions from the API
private abstract class ExceptionTest {
private final Class<? extends Exception> exceptionClass;
ExceptionTest(Class<? extends Exception> exceptionClass) {
this.exceptionClass = exceptionClass;
}
public void run() {
String failMsg = null;
try {
call();
failMsg = "expected " + exceptionClass.getName() + " but no exception thrown.";
}
catch (Exception e) {
if (!exceptionClass.isAssignableFrom(e.getClass())) {
failMsg = "expected " + exceptionClass.getName() + " but caught " + e;
}
}
if (failMsg != null) {
String msg = message();
msg = msg == null ? "" : msg + " ";
errln(msg + failMsg);
}
}
public String message() {
return null;
}
public abstract void call();
}
private abstract class ExpectNPE extends ExceptionTest {
ExpectNPE() {
super(NullPointerException.class);
run();
}
}
private abstract class BuilderNPE extends ExceptionTest {
protected final String msg;
protected final Builder b = new Builder();
BuilderNPE(String msg) {
super(NullPointerException.class);
this.msg = msg;
run();
}
public String message() {
return msg;
}
}
private abstract class ExpectIAE extends ExceptionTest {
ExpectIAE() {
super(IllegalArgumentException.class);
run();
}
}
private abstract class BuilderILE extends ExceptionTest {
protected final String[] args;
protected final Builder b = new Builder();
protected String arg; // mutates during call
BuilderILE(String... args) {
super(IllformedLocaleException.class);
this.args = args;
run();
}
public void run() {
for (String arg : args) {
this.arg = arg;
super.run();
}
}
public String message() {
return "arg: '" + arg + "'";
}
}
}