/* * 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.util.ArrayList; import java.util.Arrays; 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 * @summary test API changes to Locale */ public class LocaleEnhanceTest extends LocaleTestFmwk { public static void main(String[] args) throws Exception { List argList = new ArrayList(); 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'. *

* 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 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 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 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')); // 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 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); } } /// /// 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 exceptionClass; ExceptionTest(Class 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 + "'"; } } }