--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Locale/LocaleEnhanceTest.java Fri Sep 10 15:29:40 2010 -0700
@@ -0,0 +1,1293 @@
+/*
+ * Copyright (c) 2010, 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
+ * @summary test API changes to Locale
+ */
+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",
+ "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());
+ }
+ }
+
+ 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", "und-x-abc" },
+ { "x-a-b-c", "und-x-a-b-c" },
+ { "x-a-12345678", "und-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", "und-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));
+ }
+
+ ///
+ /// 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);
+ }
+
+ // desrialize
+ try {
+ FileInputStream fis = new FileInputStream(testfile);
+ ObjectInputStream ois = new ObjectInputStream(fis);
+
+ Object o = ois.readObject();
+ assertEquals("Deserialize Java 6 Locale " + locale, o, locale);
+ ois.close();
+ } catch (Exception e) {
+ errln("Exception while reading " + testfile.getAbsolutePath() + " - " + e.getMessage());
+ }
+ }
+ }
+
+ ///
+ /// 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 + "'";
+ }
+ }
+}