1 /*
   2  * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.BufferedReader;
  25 import java.io.ByteArrayInputStream;
  26 import java.io.ByteArrayOutputStream;
  27 import java.io.File;
  28 import java.io.FileInputStream;
  29 import java.io.InputStreamReader;
  30 import java.io.ObjectInputStream;
  31 import java.io.ObjectOutputStream;
  32 import java.net.URISyntaxException;
  33 import java.net.URL;
  34 import java.text.DecimalFormatSymbols;
  35 import java.util.ArrayList;
  36 import java.util.Arrays;
  37 import java.util.Calendar;
  38 import java.util.IllformedLocaleException;
  39 import java.util.List;
  40 import java.util.Locale;
  41 import java.util.Locale.Builder;
  42 import java.util.Set;
  43 
  44 /**
  45  * @test
  46  * @bug 6875847 6992272 7002320 7015500 7023613 7032820 7033504 7004603
  47  *    7044019 8008577 8176853 8255086 8263202 8287868
  48  * @summary test API changes to Locale
  49  * @library /java/text/testlib
  50  * @modules jdk.localedata
  51  * @compile LocaleEnhanceTest.java
  52  * @run main/othervm -Djava.locale.providers=JRE,SPI -esa LocaleEnhanceTest
  53  */
  54 public class LocaleEnhanceTest extends IntlTest {
  55 
  56     public static void main(String[] args) throws Exception {
  57         List<String> argList = new ArrayList<String>();
  58         argList.addAll(Arrays.asList(args));
  59         argList.add("-nothrow");
  60         new LocaleEnhanceTest().run(argList.toArray(new String[argList.size()]));
  61     }
  62 
  63     public LocaleEnhanceTest() {
  64     }
  65 
  66     ///
  67     /// Generic sanity tests
  68     ///
  69 
  70     /** A canonical language code. */
  71     private static final String l = "en";
  72 
  73     /** A canonical script code.. */
  74     private static final String s = "Latn";
  75 
  76     /** A canonical region code. */
  77     private static final String c = "US";
  78 
  79     /** A canonical variant code. */
  80     private static final String v = "NewYork";
  81 
  82     /**
  83      * Ensure that Builder builds locales that have the expected
  84      * tag and java6 ID.  Note the odd cases for the ID.
  85      */
  86     public void testCreateLocaleCanonicalValid() {
  87         String[] valids = {
  88             "en-Latn-US-NewYork", "en_US_NewYork_#Latn",
  89             "en-Latn-US", "en_US_#Latn",
  90             "en-Latn-NewYork", "en__NewYork_#Latn", // double underscore
  91             "en-Latn", "en__#Latn", // double underscore
  92             "en-US-NewYork", "en_US_NewYork",
  93             "en-US", "en_US",
  94             "en-NewYork", "en__NewYork", // double underscore
  95             "en", "en",
  96             "und-Latn-US-NewYork", "_US_NewYork_#Latn",
  97             "und-Latn-US", "_US_#Latn",
  98             "und-Latn-NewYork", "", // variant only not supported
  99             "und-Latn", "",
 100             "und-US-NewYork", "_US_NewYork",
 101             "und-US", "_US",
 102             "und-NewYork", "", // variant only not supported
 103             "und", ""
 104         };
 105 
 106         Builder builder = new Builder();
 107 
 108         for (int i = 0; i < valids.length; i += 2) {
 109             String tag = valids[i];
 110             String id = valids[i+1];
 111 
 112             String idl = (i & 16) == 0 ? l : "";
 113             String ids = (i & 8) == 0 ? s : "";
 114             String idc = (i & 4) == 0 ? c : "";
 115             String idv = (i & 2) == 0 ? v : "";
 116 
 117             String msg = String.valueOf(i/2) + ": '" + tag + "' ";
 118 
 119             try {
 120                 Locale l = builder
 121                     .setLanguage(idl)
 122                     .setScript(ids)
 123                     .setRegion(idc)
 124                     .setVariant(idv)
 125                     .build();
 126                 assertEquals(msg + "language", idl, l.getLanguage());
 127                 assertEquals(msg + "script", ids, l.getScript());
 128                 assertEquals(msg + "country", idc, l.getCountry());
 129                 assertEquals(msg + "variant", idv, l.getVariant());
 130                 assertEquals(msg + "tag", tag, l.toLanguageTag());
 131                 assertEquals(msg + "id", id, l.toString());
 132             }
 133             catch (IllegalArgumentException e) {
 134                 errln(msg + e.getMessage());
 135             }
 136         }
 137     }
 138 
 139     /**
 140      * Test that locale construction works with 'multiple variants'.
 141      * <p>
 142      * The string "Newer__Yorker" is treated as three subtags,
 143      * "Newer", "", and "Yorker", and concatenated into one
 144      * subtag by omitting empty subtags and joining the remainer
 145      * with underscores.  So the resulting variant tag is "Newer_Yorker".
 146      * Note that 'New' and 'York' are invalid BCP47 variant subtags
 147      * because they are too short.
 148      */
 149     public void testCreateLocaleMultipleVariants() {
 150 
 151         String[] valids = {
 152             "en-Latn-US-Newer-Yorker",  "en_US_Newer_Yorker_#Latn",
 153             "en-Latn-Newer-Yorker",     "en__Newer_Yorker_#Latn",
 154             "en-US-Newer-Yorker",       "en_US_Newer_Yorker",
 155             "en-Newer-Yorker",          "en__Newer_Yorker",
 156             "und-Latn-US-Newer-Yorker", "_US_Newer_Yorker_#Latn",
 157             "und-Latn-Newer-Yorker",    "",
 158             "und-US-Newer-Yorker",      "_US_Newer_Yorker",
 159             "und-Newer-Yorker",         "",
 160         };
 161 
 162         Builder builder = new Builder(); // lenient variant
 163 
 164         final String idv = "Newer_Yorker";
 165         for (int i = 0; i < valids.length; i += 2) {
 166             String tag = valids[i];
 167             String id = valids[i+1];
 168 
 169             String idl = (i & 8) == 0 ? l : "";
 170             String ids = (i & 4) == 0 ? s : "";
 171             String idc = (i & 2) == 0 ? c : "";
 172 
 173             String msg = String.valueOf(i/2) + ": " + tag + " ";
 174             try {
 175                 Locale l = builder
 176                     .setLanguage(idl)
 177                     .setScript(ids)
 178                     .setRegion(idc)
 179                     .setVariant(idv)
 180                     .build();
 181 
 182                 assertEquals(msg + " language", idl, l.getLanguage());
 183                 assertEquals(msg + " script", ids, l.getScript());
 184                 assertEquals(msg + " country", idc, l.getCountry());
 185                 assertEquals(msg + " variant", idv, l.getVariant());
 186 
 187                 assertEquals(msg + "tag", tag, l.toLanguageTag());
 188                 assertEquals(msg + "id", id, l.toString());
 189             }
 190             catch (IllegalArgumentException e) {
 191                 errln(msg + e.getMessage());
 192             }
 193         }
 194     }
 195 
 196     /**
 197      * Ensure that all these invalid formats are not recognized by
 198      * forLanguageTag.
 199      */
 200     public void testCreateLocaleCanonicalInvalidSeparator() {
 201         String[] invalids = {
 202             // trailing separator
 203             "en_Latn_US_NewYork_",
 204             "en_Latn_US_",
 205             "en_Latn_",
 206             "en_",
 207             "_",
 208 
 209             // double separator
 210             "en_Latn_US__NewYork",
 211             "_Latn_US__NewYork",
 212             "en_US__NewYork",
 213             "_US__NewYork",
 214 
 215             // are these OK?
 216             // "en_Latn__US_NewYork", // variant is 'US_NewYork'
 217             // "_Latn__US_NewYork", // variant is 'US_NewYork'
 218             // "en__Latn_US_NewYork", // variant is 'Latn_US_NewYork'
 219             // "en__US_NewYork", // variant is 'US_NewYork'
 220 
 221             // double separator without language or script
 222             "__US",
 223             "__NewYork",
 224 
 225             // triple separator anywhere except within variant
 226             "en___NewYork",
 227             "en_Latn___NewYork",
 228             "_Latn___NewYork",
 229             "___NewYork",
 230         };
 231 
 232         for (int i = 0; i < invalids.length; ++i) {
 233             String id = invalids[i];
 234             Locale l = Locale.forLanguageTag(id);
 235             assertEquals(id, "und", l.toLanguageTag());
 236         }
 237     }
 238 
 239     /**
 240      * Ensure that all current locale ids parse.  Use DateFormat as a proxy
 241      * for all current locale ids.
 242      */
 243     public void testCurrentLocales() {
 244         Locale[] locales = java.text.DateFormat.getAvailableLocales();
 245         Builder builder = new Builder();
 246 
 247         for (Locale target : locales) {
 248             String tag = target.toLanguageTag();
 249 
 250             // the tag recreates the original locale,
 251             // except no_NO_NY
 252             Locale tagResult = Locale.forLanguageTag(tag);
 253             if (!target.getVariant().equals("NY")) {
 254                 assertEquals("tagResult", target, tagResult);
 255             }
 256 
 257             // the builder also recreates the original locale,
 258             // except ja_JP_JP, th_TH_TH and no_NO_NY
 259             Locale builderResult = builder.setLocale(target).build();
 260             if (target.getVariant().length() != 2) {
 261                 assertEquals("builderResult", target, builderResult);
 262             }
 263         }
 264     }
 265 
 266     /**
 267      * Ensure that all icu locale ids parse.
 268      */
 269     public void testIcuLocales() throws Exception {
 270         BufferedReader br = new BufferedReader(
 271             new InputStreamReader(
 272                 LocaleEnhanceTest.class.getResourceAsStream("icuLocales.txt"),
 273                 "UTF-8"));
 274         String id = null;
 275         while (null != (id = br.readLine())) {
 276             Locale result = Locale.forLanguageTag(id);
 277             assertEquals("ulocale", id, result.toLanguageTag());
 278         }
 279     }
 280 
 281     ///
 282     /// Compatibility tests
 283     ///
 284 
 285     public void testConstructor() {
 286         // all the old weirdness still holds, no new weirdness
 287         String[][] tests = {
 288             // language to lower case, region to upper, variant unchanged
 289             // short
 290             { "X", "y", "z", "x", "Y" },
 291             // long
 292             { "xXxXxXxXxXxX", "yYyYyYyYyYyYyYyY", "zZzZzZzZzZzZzZzZ",
 293               "xxxxxxxxxxxx", "YYYYYYYYYYYYYYYY" },
 294             // mapped language ids
 295             { "he", "IL", "", "he" },
 296             { "iw", "IL", "", "he" },
 297             { "yi", "DE", "", "yi" },
 298             { "ji", "DE", "", "yi" },
 299             { "id", "ID", "", "id" },
 300             { "in", "ID", "", "id" },
 301             // special variants
 302             { "ja", "JP", "JP" },
 303             { "th", "TH", "TH" },
 304             { "no", "NO", "NY" },
 305             { "no", "NO", "NY" },
 306             // no canonicalization of 3-letter language codes
 307             { "eng", "US", "" }
 308         };
 309         for (int i = 0; i < tests.length; ++ i) {
 310             String[] test = tests[i];
 311             String id = String.valueOf(i);
 312             Locale locale = Locale.of(test[0], test[1], test[2]);
 313             assertEquals(id + " lang", test.length > 3 ? test[3] : test[0], locale.getLanguage());
 314             assertEquals(id + " region", test.length > 4 ? test[4] : test[1], locale.getCountry());
 315             assertEquals(id + " variant", test.length > 5 ? test[5] : test[2], locale.getVariant());
 316         }
 317     }
 318 
 319     ///
 320     /// Locale API tests.
 321     ///
 322 
 323     public void testGetScript() {
 324         // forLanguageTag normalizes case
 325         Locale locale = Locale.forLanguageTag("und-latn");
 326         assertEquals("forLanguageTag", "Latn", locale.getScript());
 327 
 328         // Builder normalizes case
 329         locale = new Builder().setScript("LATN").build();
 330         assertEquals("builder", "Latn", locale.getScript());
 331 
 332         // empty string is returned, not null, if there is no script
 333         locale = Locale.forLanguageTag("und");
 334         assertEquals("script is empty string", "", locale.getScript());
 335     }
 336 
 337     public void testGetExtension() {
 338         // forLanguageTag does NOT normalize to hyphen
 339         Locale locale = Locale.forLanguageTag("und-a-some_ex-tension");
 340         assertEquals("some_ex-tension", null, locale.getExtension('a'));
 341 
 342         // regular extension
 343         locale = new Builder().setExtension('a', "some-ex-tension").build();
 344         assertEquals("builder", "some-ex-tension", locale.getExtension('a'));
 345 
 346         // returns null if extension is not present
 347         assertEquals("empty b", null, locale.getExtension('b'));
 348 
 349         // throws exception if extension tag is illegal
 350         new ExpectIAE() { public void call() { Locale.forLanguageTag("").getExtension('\uD800'); }};
 351 
 352         // 'x' is not an extension, it's a private use tag, but it's accessed through this API
 353         locale = Locale.forLanguageTag("x-y-z-blork");
 354         assertEquals("x", "y-z-blork", locale.getExtension('x'));
 355     }
 356 
 357     public void testGetExtensionKeys() {
 358         Locale locale = Locale.forLanguageTag("und-a-xx-yy-b-zz-ww");
 359         Set<Character> result = locale.getExtensionKeys();
 360         assertEquals("result size", 2, result.size());
 361         assertTrue("'a','b'", result.contains('a') && result.contains('b'));
 362 
 363         // result is not mutable
 364         try {
 365             result.add('x');
 366             errln("expected exception on add to extension key set");
 367         }
 368         catch (UnsupportedOperationException e) {
 369             // ok
 370         }
 371 
 372         // returns empty set if no extensions
 373         locale = Locale.forLanguageTag("und");
 374         assertTrue("empty result", locale.getExtensionKeys().isEmpty());
 375     }
 376 
 377     public void testGetUnicodeLocaleAttributes() {
 378         Locale locale = Locale.forLanguageTag("en-US-u-abc-def");
 379         Set<String> attributes = locale.getUnicodeLocaleAttributes();
 380         assertEquals("number of attributes", 2, attributes.size());
 381         assertTrue("attribute abc", attributes.contains("abc"));
 382         assertTrue("attribute def", attributes.contains("def"));
 383 
 384         locale = Locale.forLanguageTag("en-US-u-ca-gregory");
 385         attributes = locale.getUnicodeLocaleAttributes();
 386         assertTrue("empty attributes", attributes.isEmpty());
 387     }
 388 
 389     public void testGetUnicodeLocaleType() {
 390         Locale locale = Locale.forLanguageTag("und-u-co-japanese-nu-thai");
 391         assertEquals("collation", "japanese", locale.getUnicodeLocaleType("co"));
 392         assertEquals("numbers", "thai", locale.getUnicodeLocaleType("nu"));
 393 
 394         // Unicode locale extension key is case insensitive
 395         assertEquals("key case", "japanese", locale.getUnicodeLocaleType("Co"));
 396 
 397         // if keyword is not present, returns null
 398         assertEquals("locale keyword not present", null, locale.getUnicodeLocaleType("xx"));
 399 
 400         // if no locale extension is set, returns null
 401         locale = Locale.forLanguageTag("und");
 402         assertEquals("locale extension not present", null, locale.getUnicodeLocaleType("co"));
 403 
 404         // typeless keyword
 405         locale = Locale.forLanguageTag("und-u-kn");
 406         assertEquals("typeless keyword", "", locale.getUnicodeLocaleType("kn"));
 407 
 408         // invalid keys throw exception
 409         new ExpectIAE() { public void call() { Locale.forLanguageTag("").getUnicodeLocaleType("q"); }};
 410         new ExpectIAE() { public void call() { Locale.forLanguageTag("").getUnicodeLocaleType("abcdefghi"); }};
 411 
 412         // null argument throws exception
 413         new ExpectNPE() { public void call() { Locale.forLanguageTag("").getUnicodeLocaleType(null); }};
 414     }
 415 
 416     public void testGetUnicodeLocaleKeys() {
 417         Locale locale = Locale.forLanguageTag("und-u-co-japanese-nu-thai");
 418         Set<String> result = locale.getUnicodeLocaleKeys();
 419         assertEquals("two keys", 2, result.size());
 420         assertTrue("co and nu", result.contains("co") && result.contains("nu"));
 421 
 422         // result is not modifiable
 423         try {
 424             result.add("frobozz");
 425             errln("expected exception when add to locale key set");
 426         }
 427         catch (UnsupportedOperationException e) {
 428             // ok
 429         }
 430     }
 431 
 432     public void testPrivateUseExtension() {
 433         Locale locale = Locale.forLanguageTag("x-y-x-blork-");
 434         assertEquals("blork", "y-x-blork", locale.getExtension(Locale.PRIVATE_USE_EXTENSION));
 435 
 436         locale = Locale.forLanguageTag("und");
 437         assertEquals("no privateuse", null, locale.getExtension(Locale.PRIVATE_USE_EXTENSION));
 438     }
 439 
 440     public void testToLanguageTag() {
 441         // lots of normalization to test here
 442         // test locales created using the constructor
 443         String[][] tests = {
 444             // empty locale canonicalizes to 'und'
 445             { "", "", "", "und" },
 446             // variant alone is not a valid Locale, but has a valid language tag
 447             { "", "", "NewYork", "und-NewYork" },
 448             // standard valid locales
 449             { "", "Us", "", "und-US" },
 450             { "", "US", "NewYork", "und-US-NewYork" },
 451             { "EN", "", "", "en" },
 452             { "EN", "", "NewYork", "en-NewYork" },
 453             { "EN", "US", "", "en-US" },
 454             { "EN", "US", "NewYork", "en-US-NewYork" },
 455             // underscore in variant will be emitted as multiple variant subtags
 456             { "en", "US", "Newer_Yorker", "en-US-Newer-Yorker" },
 457             // invalid variant subtags are appended as private use
 458             { "en", "US", "new_yorker", "en-US-x-lvariant-new-yorker" },
 459             // the first invalid variant subtags and following variant subtags are appended as private use
 460             { "en", "US", "Windows_XP_Home", "en-US-Windows-x-lvariant-XP-Home" },
 461             // too long variant and following variant subtags disappear
 462             { "en", "US", "WindowsVista_SP2", "en-US" },
 463             // invalid region subtag disappears
 464             { "en", "USA", "", "en" },
 465             // invalid language tag disappears
 466             { "e", "US", "", "und-US" },
 467             // three-letter language tags are not canonicalized
 468             { "Eng", "", "", "eng" },
 469             // legacy languages canonicalize to modern equivalents
 470             { "he", "IL", "", "he-IL" },
 471             { "iw", "IL", "", "he-IL" },
 472             { "yi", "DE", "", "yi-DE" },
 473             { "ji", "DE", "", "yi-DE" },
 474             { "id", "ID", "", "id-ID" },
 475             { "in", "ID", "", "id-ID" },
 476             // special values are converted on output
 477             { "ja", "JP", "JP", "ja-JP-u-ca-japanese-x-lvariant-JP" },
 478             { "th", "TH", "TH", "th-TH-u-nu-thai-x-lvariant-TH" },
 479             { "no", "NO", "NY", "nn-NO" }
 480         };
 481         for (int i = 0; i < tests.length; ++i) {
 482             String[] test = tests[i];
 483             Locale locale = Locale.of(test[0], test[1], test[2]);
 484             assertEquals("case " + i, test[3], locale.toLanguageTag());
 485         }
 486 
 487         // test locales created from forLanguageTag
 488         String[][] tests1 = {
 489             // case is normalized during the round trip
 490             { "EN-us", "en-US" },
 491             { "en-Latn-US", "en-Latn-US" },
 492             // reordering Unicode locale extensions
 493             { "de-u-co-phonebk-ca-gregory", "de-u-ca-gregory-co-phonebk" },
 494             // private use only language tag is preserved (no extra "und")
 495             { "x-elmer", "x-elmer" },
 496             { "x-lvariant-JP", "x-lvariant-JP" },
 497         };
 498         for (String[] test : tests1) {
 499             Locale locale = Locale.forLanguageTag(test[0]);
 500             assertEquals("case " + test[0], test[1], locale.toLanguageTag());
 501         }
 502 
 503     }
 504 
 505     public void testForLanguageTag() {
 506         // forLanguageTag implements the 'Language-Tag' production of
 507         // BCP47, so it handles private use and legacy language tags,
 508         // unlike locale builder.  Tags listed below (except for the
 509         // sample private use tags) come from 4646bis Feb 29, 2009.
 510 
 511         String[][] tests = {
 512             // private use tags only
 513             { "x-abc", "x-abc" },
 514             { "x-a-b-c", "x-a-b-c" },
 515             { "x-a-12345678", "x-a-12345678" },
 516 
 517             // legacy language tags with preferred mappings
 518             { "i-ami", "ami" },
 519             { "i-bnn", "bnn" },
 520             { "i-hak", "hak" },
 521             { "i-klingon", "tlh" },
 522             { "i-lux", "lb" }, // two-letter tag
 523             { "i-navajo", "nv" }, // two-letter tag
 524             { "i-pwn", "pwn" },
 525             { "i-tao", "tao" },
 526             { "i-tay", "tay" },
 527             { "i-tsu", "tsu" },
 528             { "art-lojban", "jbo" },
 529             { "no-bok", "nb" },
 530             { "no-nyn", "nn" },
 531             { "sgn-BE-FR", "sfb" },
 532             { "sgn-BE-NL", "vgt" },
 533             { "sgn-CH-DE", "sgg" },
 534             { "zh-guoyu", "cmn" },
 535             { "zh-hakka", "hak" },
 536             { "zh-min-nan", "nan" },
 537             { "zh-xiang", "hsn" },
 538 
 539             // irregular legacy language tags, no preferred mappings, drop illegal fields
 540             // from end.  If no subtag is mappable, fallback to 'und'
 541             { "i-default", "en-x-i-default" },
 542             { "i-enochian", "x-i-enochian" },
 543             { "i-mingo", "see-x-i-mingo" },
 544             { "en-GB-oed", "en-GB-x-oed" },
 545             { "zh-min", "nan-x-zh-min" },
 546             { "cel-gaulish", "xtg-x-cel-gaulish" },
 547         };
 548         for (int i = 0; i < tests.length; ++i) {
 549             String[] test = tests[i];
 550             Locale locale = Locale.forLanguageTag(test[0]);
 551             assertEquals("legacy language tag case " + i, test[1], locale.toLanguageTag());
 552         }
 553 
 554         // forLanguageTag ignores everything past the first place it encounters
 555         // a syntax error
 556         tests = new String[][] {
 557             { "valid",
 558               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y-12345678-z",
 559               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y-12345678-z" },
 560             { "segment of private use tag too long",
 561               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y-123456789-z",
 562               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y" },
 563             { "segment of private use tag is empty",
 564               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y--12345678-z",
 565               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x-y" },
 566             { "first segment of private use tag is empty",
 567               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-x--y-12345678-z",
 568               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def" },
 569             { "illegal extension tag",
 570               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def-\uD800-y-12345678-z",
 571               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-def" },
 572             { "locale subtag with no value",
 573               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-x-y-12345678-z",
 574               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-bb-x-y-12345678-z" },
 575             { "locale key subtag invalid",
 576               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc-123456789-def-x-y-12345678-z",
 577               "en-US-Newer-Yorker-a-bb-cc-dd-u-aa-abc" },
 578             // locale key subtag invalid in earlier position, all following subtags
 579             // dropped (and so the locale extension dropped as well)
 580             { "locale key subtag invalid in earlier position",
 581               "en-US-Newer-Yorker-a-bb-cc-dd-u-123456789-abc-bb-def-x-y-12345678-z",
 582               "en-US-Newer-Yorker-a-bb-cc-dd" },
 583         };
 584         for (int i = 0; i < tests.length; ++i) {
 585             String[] test = tests[i];
 586             String msg = "syntax error case " + i + " " + test[0];
 587             try {
 588                 Locale locale = Locale.forLanguageTag(test[1]);
 589                 assertEquals(msg, test[2], locale.toLanguageTag());
 590             }
 591             catch (IllegalArgumentException e) {
 592                 errln(msg + " caught exception: " + e);
 593             }
 594         }
 595 
 596         // duplicated extension are just ignored
 597         Locale locale = Locale.forLanguageTag("und-d-aa-00-bb-01-D-AA-10-cc-11-c-1234");
 598         assertEquals("extension", "aa-00-bb-01", locale.getExtension('d'));
 599         assertEquals("extension c", "1234", locale.getExtension('c'));
 600 
 601         locale = Locale.forLanguageTag("und-U-ca-gregory-u-ca-japanese");
 602         assertEquals("Unicode extension", "ca-gregory", locale.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
 603 
 604         // redundant Unicode locale keys in an extension are ignored
 605         locale = Locale.forLanguageTag("und-u-aa-000-bb-001-bB-002-cc-003-c-1234");
 606         assertEquals("Unicode keywords", "aa-000-bb-001-cc-003", locale.getExtension(Locale.UNICODE_LOCALE_EXTENSION));
 607         assertEquals("Duplicated Unicode locake key followed by an extension", "1234", locale.getExtension('c'));
 608     }
 609 
 610     public void testGetDisplayScript() {
 611         Locale latnLocale = Locale.forLanguageTag("und-latn");
 612         Locale hansLocale = Locale.forLanguageTag("und-hans");
 613 
 614         Locale oldLocale = Locale.getDefault();
 615 
 616         Locale.setDefault(Locale.US);
 617         assertEquals("latn US", "Latin", latnLocale.getDisplayScript());
 618         assertEquals("hans US", "Simplified", hansLocale.getDisplayScript());
 619 
 620         Locale.setDefault(Locale.GERMANY);
 621         assertEquals("latn DE", "Lateinisch", latnLocale.getDisplayScript());
 622         assertEquals("hans DE", "Vereinfacht", hansLocale.getDisplayScript());
 623 
 624         Locale.setDefault(oldLocale);
 625     }
 626 
 627     public void testGetDisplayScriptWithLocale() {
 628         Locale latnLocale = Locale.forLanguageTag("und-latn");
 629         Locale hansLocale = Locale.forLanguageTag("und-hans");
 630 
 631         assertEquals("latn US", "Latin", latnLocale.getDisplayScript(Locale.US));
 632         assertEquals("hans US", "Simplified", hansLocale.getDisplayScript(Locale.US));
 633 
 634         assertEquals("latn DE", "Lateinisch", latnLocale.getDisplayScript(Locale.GERMANY));
 635         assertEquals("hans DE", "Vereinfacht", hansLocale.getDisplayScript(Locale.GERMANY));
 636     }
 637 
 638     public void testGetDisplayName() {
 639         final Locale[] testLocales = {
 640                 Locale.ROOT,
 641                 Locale.ENGLISH,
 642                 Locale.US,
 643                 Locale.of("", "US"),
 644                 Locale.of("no", "NO", "NY"),
 645                 Locale.of("", "", "NY"),
 646                 Locale.forLanguageTag("zh-Hans"),
 647                 Locale.forLanguageTag("zh-Hant"),
 648                 Locale.forLanguageTag("zh-Hans-CN"),
 649                 Locale.forLanguageTag("und-Hans"),
 650         };
 651 
 652         final String[] displayNameEnglish = {
 653                 "",
 654                 "English",
 655                 "English (United States)",
 656                 "United States",
 657                 "Norwegian (Norway,Nynorsk)",
 658                 "Nynorsk",
 659                 "Chinese (Simplified)",
 660                 "Chinese (Traditional)",
 661                 "Chinese (Simplified,China)",
 662                 "Simplified",
 663         };
 664 
 665         final String[] displayNameSimplifiedChinese = {
 666                 "",
 667                 "\u82f1\u8bed",
 668                 "\u82f1\u8bed (\u7f8e\u56fd)",
 669                 "\u7f8e\u56fd",
 670                 "\u632a\u5a01\u8bed (\u632a\u5a01,Nynorsk)",
 671                 "Nynorsk",
 672                 "\u4e2d\u6587 (\u7b80\u4f53)",
 673                 "\u4e2d\u6587 (\u7e41\u4f53)",
 674                 "\u4e2d\u6587 (\u7b80\u4f53,\u4e2d\u56fd)",
 675                 "\u7b80\u4f53",
 676         };
 677 
 678         for (int i = 0; i < testLocales.length; i++) {
 679             Locale loc = testLocales[i];
 680             assertEquals("English display name for " + loc.toLanguageTag(),
 681                     displayNameEnglish[i], loc.getDisplayName(Locale.ENGLISH));
 682             assertEquals("Simplified Chinese display name for " + loc.toLanguageTag(),
 683                     displayNameSimplifiedChinese[i], loc.getDisplayName(Locale.CHINA));
 684         }
 685     }
 686 
 687     ///
 688     /// Builder tests
 689     ///
 690 
 691     public void testBuilderSetLocale() {
 692         Builder builder = new Builder();
 693         Builder lenientBuilder = new Builder();
 694 
 695         String languageTag = "en-Latn-US-NewYork-a-bb-ccc-u-co-japanese-x-y-z";
 696         String target = "en-Latn-US-NewYork-a-bb-ccc-u-co-japanese-x-y-z";
 697 
 698         Locale locale = Locale.forLanguageTag(languageTag);
 699         Locale result = lenientBuilder
 700             .setLocale(locale)
 701             .build();
 702         assertEquals("long tag", target, result.toLanguageTag());
 703         assertEquals("long tag", locale, result);
 704 
 705         // null is illegal
 706         new BuilderNPE("locale") {
 707             public void call() { b.setLocale(null); }
 708         };
 709 
 710         // builder canonicalizes the three legacy locales:
 711         // ja_JP_JP, th_TH_TH, no_NY_NO.
 712         locale = builder.setLocale(Locale.of("ja", "JP", "JP")).build();
 713         assertEquals("ja_JP_JP languagetag", "ja-JP-u-ca-japanese", locale.toLanguageTag());
 714         assertEquals("ja_JP_JP variant", "", locale.getVariant());
 715 
 716         locale = builder.setLocale(Locale.of("th", "TH", "TH")).build();
 717         assertEquals("th_TH_TH languagetag", "th-TH-u-nu-thai", locale.toLanguageTag());
 718         assertEquals("th_TH_TH variant", "", locale.getVariant());
 719 
 720         locale = builder.setLocale(Locale.of("no", "NO", "NY")).build();
 721         assertEquals("no_NO_NY languagetag", "nn-NO", locale.toLanguageTag());
 722         assertEquals("no_NO_NY language", "nn", locale.getLanguage());
 723         assertEquals("no_NO_NY variant", "", locale.getVariant());
 724 
 725         // non-canonical, non-legacy locales are invalid
 726         new BuilderILE("123_4567_89") {
 727             public void call() {
 728                 b.setLocale(Locale.of("123", "4567", "89"));
 729             }
 730         };
 731     }
 732 
 733     public void testBuilderSetLanguageTag() {
 734         String source = "eN-LaTn-Us-NewYork-A-Xx-B-Yy-X-1-2-3";
 735         String target = "en-Latn-US-NewYork-a-xx-b-yy-x-1-2-3";
 736         Builder builder = new Builder();
 737         String result = builder
 738             .setLanguageTag(source)
 739             .build()
 740             .toLanguageTag();
 741         assertEquals("language", target, result);
 742 
 743         // redundant extensions cause a failure
 744         new BuilderILE() { public void call() { b.setLanguageTag("und-a-xx-yy-b-ww-A-00-11-c-vv"); }};
 745 
 746         // redundant Unicode locale extension keys within an Unicode locale extension cause a failure
 747         new BuilderILE() { public void call() { b.setLanguageTag("und-u-nu-thai-NU-chinese-xx-1234"); }};
 748     }
 749 
 750     public void testBuilderSetLanguage() {
 751         // language is normalized to lower case
 752         String source = "eN";
 753         String target = "en";
 754         String defaulted = "";
 755         Builder builder = new Builder();
 756         String result = builder
 757             .setLanguage(source)
 758             .build()
 759             .getLanguage();
 760         assertEquals("en", target, result);
 761 
 762         // setting with empty resets
 763         result = builder
 764             .setLanguage(target)
 765             .setLanguage("")
 766             .build()
 767             .getLanguage();
 768         assertEquals("empty", defaulted, result);
 769 
 770         // setting with null resets too
 771         result = builder
 772                 .setLanguage(target)
 773                 .setLanguage(null)
 774                 .build()
 775                 .getLanguage();
 776         assertEquals("null", defaulted, result);
 777 
 778         // language codes must be 2-8 alpha
 779         // for forwards compatibility, 4-alpha and 5-8 alpha (registered)
 780         // languages are accepted syntax
 781         new BuilderILE("q", "abcdefghi", "13") { public void call() { b.setLanguage(arg); }};
 782 
 783         // language code validation is NOT performed, any 2-8-alpha passes
 784         assertNotNull("2alpha", builder.setLanguage("zz").build());
 785         assertNotNull("8alpha", builder.setLanguage("abcdefgh").build());
 786 
 787         // three-letter language codes are NOT canonicalized to two-letter
 788         result = builder
 789             .setLanguage("eng")
 790             .build()
 791             .getLanguage();
 792         assertEquals("eng", "eng", result);
 793     }
 794 
 795     public void testBuilderSetScript() {
 796         // script is normalized to title case
 797         String source = "lAtN";
 798         String target = "Latn";
 799         String defaulted = "";
 800         Builder builder = new Builder();
 801         String result = builder
 802             .setScript(source)
 803             .build()
 804             .getScript();
 805         assertEquals("script", target, result);
 806 
 807         // setting with empty resets
 808         result = builder
 809             .setScript(target)
 810             .setScript("")
 811             .build()
 812             .getScript();
 813         assertEquals("empty", defaulted, result);
 814 
 815         // settting with null also resets
 816         result = builder
 817                 .setScript(target)
 818                 .setScript(null)
 819                 .build()
 820                 .getScript();
 821         assertEquals("null", defaulted, result);
 822 
 823         // ill-formed script codes throw IAE
 824         // must be 4alpha
 825         new BuilderILE("abc", "abcde", "l3tn") { public void call() { b.setScript(arg); }};
 826 
 827         // script code validation is NOT performed, any 4-alpha passes
 828         assertEquals("4alpha", "Wxyz", builder.setScript("wxyz").build().getScript());
 829     }
 830 
 831     public void testBuilderSetRegion() {
 832         // region is normalized to upper case
 833         String source = "uS";
 834         String target = "US";
 835         String defaulted = "";
 836         Builder builder = new Builder();
 837         String result = builder
 838             .setRegion(source)
 839             .build()
 840             .getCountry();
 841         assertEquals("us", target, result);
 842 
 843         // setting with empty resets
 844         result = builder
 845             .setRegion(target)
 846             .setRegion("")
 847             .build()
 848             .getCountry();
 849         assertEquals("empty", defaulted, result);
 850 
 851         // setting with null also resets
 852         result = builder
 853                 .setRegion(target)
 854                 .setRegion(null)
 855                 .build()
 856                 .getCountry();
 857         assertEquals("null", defaulted, result);
 858 
 859         // ill-formed region codes throw IAE
 860         // 2 alpha or 3 numeric
 861         new BuilderILE("q", "abc", "12", "1234", "a3", "12a") { public void call() { b.setRegion(arg); }};
 862 
 863         // region code validation is NOT performed, any 2-alpha or 3-digit passes
 864         assertEquals("2alpha", "ZZ", builder.setRegion("ZZ").build().getCountry());
 865         assertEquals("3digit", "000", builder.setRegion("000").build().getCountry());
 866     }
 867 
 868     public void testBuilderSetVariant() {
 869         // Variant case is not normalized in lenient variant mode
 870         String source = "NewYork";
 871         String target = source;
 872         String defaulted = "";
 873         Builder builder = new Builder();
 874         String result = builder
 875             .setVariant(source)
 876             .build()
 877             .getVariant();
 878         assertEquals("NewYork", target, result);
 879 
 880         result = builder
 881             .setVariant("NeWeR_YoRkEr")
 882             .build()
 883             .toLanguageTag();
 884         assertEquals("newer yorker", "und-NeWeR-YoRkEr", result);
 885 
 886         // subtags of variant are NOT reordered
 887         result = builder
 888             .setVariant("zzzzz_yyyyy_xxxxx")
 889             .build()
 890             .getVariant();
 891         assertEquals("zyx", "zzzzz_yyyyy_xxxxx", result);
 892 
 893         // setting to empty resets
 894         result = builder
 895             .setVariant(target)
 896             .setVariant("")
 897             .build()
 898             .getVariant();
 899         assertEquals("empty", defaulted, result);
 900 
 901         // setting to null also resets
 902         result = builder
 903                 .setVariant(target)
 904                 .setVariant(null)
 905                 .build()
 906                 .getVariant();
 907         assertEquals("null", defaulted, result);
 908 
 909         // ill-formed variants throw IAE
 910         // digit followed by 3-7 characters, or alpha followed by 4-8 characters.
 911         new BuilderILE("abcd", "abcdefghi", "1ab", "1abcdefgh") { public void call() { b.setVariant(arg); }};
 912 
 913         // 4 characters is ok as long as the first is a digit
 914         assertEquals("digit+3alpha", "1abc", builder.setVariant("1abc").build().getVariant());
 915 
 916         // all subfields must conform
 917         new BuilderILE("abcde-fg") { public void call() { b.setVariant(arg); }};
 918     }
 919 
 920     public void testBuilderSetExtension() {
 921         // upper case characters are normalized to lower case
 922         final char sourceKey = 'a';
 923         final String sourceValue = "aB-aBcdefgh-12-12345678";
 924         String target = "ab-abcdefgh-12-12345678";
 925         Builder builder = new Builder();
 926         String result = builder
 927             .setExtension(sourceKey, sourceValue)
 928             .build()
 929             .getExtension(sourceKey);
 930         assertEquals("extension", target, result);
 931 
 932         // setting with empty resets
 933         result = builder
 934             .setExtension(sourceKey, sourceValue)
 935             .setExtension(sourceKey, "")
 936             .build()
 937             .getExtension(sourceKey);
 938         assertEquals("empty", null, result);
 939 
 940         // setting with null also resets
 941         result = builder
 942                 .setExtension(sourceKey, sourceValue)
 943                 .setExtension(sourceKey, null)
 944                 .build()
 945                 .getExtension(sourceKey);
 946         assertEquals("null", null, result);
 947 
 948         // ill-formed extension keys throw IAE
 949         // must be in [0-9a-ZA-Z]
 950         new BuilderILE("$") { public void call() { b.setExtension('$', sourceValue); }};
 951 
 952         // each segment of value must be 2-8 alphanum
 953         new BuilderILE("ab-cd-123456789") { public void call() { b.setExtension(sourceKey, arg); }};
 954 
 955         // no multiple hyphens.
 956         new BuilderILE("ab--cd") { public void call() { b.setExtension(sourceKey, arg); }};
 957 
 958         // locale extension key has special handling
 959         Locale locale = builder
 960             .setExtension('u', "co-japanese")
 961             .build();
 962         assertEquals("locale extension", "japanese", locale.getUnicodeLocaleType("co"));
 963 
 964         // locale extension has same behavior with set locale keyword
 965         Locale locale2 = builder
 966             .setUnicodeLocaleKeyword("co", "japanese")
 967             .build();
 968         assertEquals("locales with extension", locale, locale2);
 969 
 970         // setting locale extension overrides all previous calls to setLocaleKeyword
 971         Locale locale3 = builder
 972             .setExtension('u', "xxx-nu-thai")
 973             .build();
 974         assertEquals("remove co", null, locale3.getUnicodeLocaleType("co"));
 975         assertEquals("override thai", "thai", locale3.getUnicodeLocaleType("nu"));
 976         assertEquals("override attribute", 1, locale3.getUnicodeLocaleAttributes().size());
 977 
 978         // setting locale keyword extends values already set by the locale extension
 979         Locale locale4 = builder
 980             .setUnicodeLocaleKeyword("co", "japanese")
 981             .build();
 982         assertEquals("extend", "japanese", locale4.getUnicodeLocaleType("co"));
 983         assertEquals("extend", "thai", locale4.getUnicodeLocaleType("nu"));
 984 
 985         // locale extension subtags are reordered
 986         result = builder
 987             .clear()
 988             .setExtension('u', "456-123-zz-123-yy-456-xx-789")
 989             .build()
 990             .toLanguageTag();
 991         assertEquals("reorder", "und-u-123-456-xx-789-yy-456-zz-123", result);
 992 
 993         // multiple keyword types
 994         result = builder
 995             .clear()
 996             .setExtension('u', "nu-thai-foobar")
 997             .build()
 998             .getUnicodeLocaleType("nu");
 999         assertEquals("multiple types", "thai-foobar", result);
1000 
1001         // redundant locale extensions are ignored
1002         result = builder
1003             .clear()
1004             .setExtension('u', "nu-thai-NU-chinese-xx-1234")
1005             .build()
1006             .toLanguageTag();
1007         assertEquals("duplicate keys", "und-u-nu-thai-xx-1234", result);
1008     }
1009 
1010     public void testBuilderAddUnicodeLocaleAttribute() {
1011         Builder builder = new Builder();
1012         Locale locale = builder
1013             .addUnicodeLocaleAttribute("def")
1014             .addUnicodeLocaleAttribute("abc")
1015             .build();
1016 
1017         Set<String> uattrs = locale.getUnicodeLocaleAttributes();
1018         assertEquals("number of attributes", 2, uattrs.size());
1019         assertTrue("attribute abc", uattrs.contains("abc"));
1020         assertTrue("attribute def", uattrs.contains("def"));
1021 
1022         // remove attribute
1023         locale = builder.removeUnicodeLocaleAttribute("xxx")
1024             .build();
1025 
1026         assertEquals("remove bogus", 2, uattrs.size());
1027 
1028         // add duplicate
1029         locale = builder.addUnicodeLocaleAttribute("abc")
1030             .build();
1031         assertEquals("add duplicate", 2, uattrs.size());
1032 
1033         // null attribute throws NPE
1034         new BuilderNPE("null attribute") { public void call() { b.addUnicodeLocaleAttribute(null); }};
1035         new BuilderNPE("null attribute removal") { public void call() { b.removeUnicodeLocaleAttribute(null); }};
1036 
1037         // illformed attribute throws IllformedLocaleException
1038         new BuilderILE("invalid attribute") { public void call() { b.addUnicodeLocaleAttribute("ca"); }};
1039     }
1040 
1041     public void testBuildersetUnicodeLocaleKeyword() {
1042         // Note: most behavior is tested in testBuilderSetExtension
1043         Builder builder = new Builder();
1044         Locale locale = builder
1045             .setUnicodeLocaleKeyword("co", "japanese")
1046             .setUnicodeLocaleKeyword("nu", "thai")
1047             .build();
1048         assertEquals("co", "japanese", locale.getUnicodeLocaleType("co"));
1049         assertEquals("nu", "thai", locale.getUnicodeLocaleType("nu"));
1050         assertEquals("keys", 2, locale.getUnicodeLocaleKeys().size());
1051 
1052         // can clear a keyword by setting to null, others remain
1053         String result = builder
1054             .setUnicodeLocaleKeyword("co", null)
1055             .build()
1056             .toLanguageTag();
1057         assertEquals("empty co", "und-u-nu-thai", result);
1058 
1059         // locale keyword extension goes when all keywords are gone
1060         result = builder
1061             .setUnicodeLocaleKeyword("nu", null)
1062             .build()
1063             .toLanguageTag();
1064         assertEquals("empty nu", "und", result);
1065 
1066         // locale keywords are ordered independent of order of addition
1067         result = builder
1068             .setUnicodeLocaleKeyword("zz", "012")
1069             .setUnicodeLocaleKeyword("aa", "345")
1070             .build()
1071             .toLanguageTag();
1072         assertEquals("reordered", "und-u-aa-345-zz-012", result);
1073 
1074         // null keyword throws NPE
1075         new BuilderNPE("keyword") { public void call() { b.setUnicodeLocaleKeyword(null, "thai"); }};
1076 
1077         // well-formed keywords are two alphanum
1078         new BuilderILE("a", "abc") { public void call() { b.setUnicodeLocaleKeyword(arg, "value"); }};
1079 
1080         // well-formed values are 3-8 alphanum
1081         new BuilderILE("ab", "abcdefghi") { public void call() { b.setUnicodeLocaleKeyword("ab", arg); }};
1082     }
1083 
1084     public void testBuilderPrivateUseExtension() {
1085         // normalizes hyphens to underscore, case to lower
1086         String source = "c-B-a";
1087         String target = "c-b-a";
1088         Builder builder = new Builder();
1089         String result = builder
1090             .setExtension(Locale.PRIVATE_USE_EXTENSION, source)
1091             .build()
1092             .getExtension(Locale.PRIVATE_USE_EXTENSION);
1093         assertEquals("abc", target, result);
1094 
1095         // multiple hyphens are ill-formed
1096         new BuilderILE("a--b") { public void call() { b.setExtension(Locale.PRIVATE_USE_EXTENSION, arg); }};
1097     }
1098 
1099     public void testBuilderClear() {
1100         String monster = "en-latn-US-NewYork-a-bb-cc-u-co-japanese-x-z-y-x-x";
1101         Builder builder = new Builder();
1102         Locale locale = Locale.forLanguageTag(monster);
1103         String result = builder
1104             .setLocale(locale)
1105             .clear()
1106             .build()
1107             .toLanguageTag();
1108         assertEquals("clear", "und", result);
1109     }
1110 
1111     public void testBuilderRemoveUnicodeAttribute() {
1112         // tested in testBuilderAddUnicodeAttribute
1113     }
1114 
1115     public void testBuilderBuild() {
1116         // tested in other test methods
1117     }
1118 
1119     public void testSerialize() {
1120         final Locale[] testLocales = {
1121             Locale.ROOT,
1122             Locale.ENGLISH,
1123             Locale.US,
1124             Locale.of("en", "US", "Win"),
1125             Locale.of("en", "US", "Win_XP"),
1126             Locale.JAPAN,
1127             Locale.of("ja", "JP", "JP"),
1128             Locale.of("th", "TH"),
1129             Locale.of("th", "TH", "TH"),
1130             Locale.of("no", "NO"),
1131             Locale.of("nb", "NO"),
1132             Locale.of("nn", "NO"),
1133             Locale.of("no", "NO", "NY"),
1134             Locale.of("nn", "NO", "NY"),
1135             Locale.of("he", "IL"),
1136             Locale.of("he", "IL", "var"),
1137             Locale.of("Language", "Country", "Variant"),
1138             Locale.of("", "US"),
1139             Locale.of("", "", "Java"),
1140             Locale.forLanguageTag("en-Latn-US"),
1141             Locale.forLanguageTag("zh-Hans"),
1142             Locale.forLanguageTag("zh-Hant-TW"),
1143             Locale.forLanguageTag("ja-JP-u-ca-japanese"),
1144             Locale.forLanguageTag("und-Hant"),
1145             Locale.forLanguageTag("und-a-123-456"),
1146             Locale.forLanguageTag("en-x-java"),
1147             Locale.forLanguageTag("th-TH-u-ca-buddist-nu-thai-x-lvariant-TH"),
1148         };
1149 
1150         for (Locale locale : testLocales) {
1151             try {
1152                 // write
1153                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
1154                 ObjectOutputStream oos = new ObjectOutputStream(bos);
1155                 oos.writeObject(locale);
1156 
1157                 // read
1158                 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
1159                 ObjectInputStream ois = new ObjectInputStream(bis);
1160                 Object o = ois.readObject();
1161 
1162                 assertEquals("roundtrip " + locale, locale, o);
1163             } catch (Exception e) {
1164                 errln(locale + " encountered exception:" + e.getLocalizedMessage());
1165             }
1166         }
1167     }
1168 
1169     public void testDeserialize6() {
1170         final String TESTFILEPREFIX = "java6locale_";
1171 
1172         File dataDir = null;
1173         String dataDirName = System.getProperty("serialized.data.dir");
1174         if (dataDirName == null) {
1175             URL resdirUrl = getClass().getClassLoader().getResource("serialized");
1176             if (resdirUrl != null) {
1177                 try {
1178                     dataDir = new File(resdirUrl.toURI());
1179                 } catch (URISyntaxException urie) {
1180                 }
1181             }
1182         } else {
1183             dataDir = new File(dataDirName);
1184         }
1185 
1186         if (dataDir == null) {
1187             errln("'dataDir' is null. serialized.data.dir Property value is "+dataDirName);
1188             return;
1189         } else if (!dataDir.isDirectory()) {
1190             errln("'dataDir' is not a directory. dataDir: "+dataDir.toString());
1191             return;
1192         }
1193 
1194         File[] files = dataDir.listFiles();
1195         for (File testfile : files) {
1196             if (testfile.isDirectory()) {
1197                 continue;
1198             }
1199             String name = testfile.getName();
1200             if (!name.startsWith(TESTFILEPREFIX)) {
1201                 continue;
1202             }
1203             Locale locale;
1204             String locStr = name.substring(TESTFILEPREFIX.length());
1205             if (locStr.equals("ROOT")) {
1206                 locale = Locale.ROOT;
1207             } else {
1208                 String[] fields = locStr.split("_", 3);
1209                 String lang = fields[0];
1210                 String country = (fields.length >= 2) ? fields[1] : "";
1211                 String variant = (fields.length == 3) ? fields[2] : "";
1212                 locale = Locale.of(lang, country, variant);
1213             }
1214 
1215             // deserialize
1216             try (FileInputStream fis = new FileInputStream(testfile);
1217                  ObjectInputStream ois = new ObjectInputStream(fis))
1218             {
1219                 Object o = ois.readObject();
1220                 assertEquals("Deserialize Java 6 Locale " + locale, o, locale);
1221             } catch (Exception e) {
1222                 errln("Exception while reading " + testfile.getAbsolutePath() + " - " + e.getMessage());
1223             }
1224         }
1225     }
1226 
1227     public void testBug7002320() {
1228         // forLanguageTag() and Builder.setLanguageTag(String)
1229         // should add a location extension for following two cases.
1230         //
1231         // 1. language/country are "ja"/"JP" and the resolved variant (x-lvariant-*)
1232         //    is exactly "JP" and no BCP 47 extensions are available, then add
1233         //    a Unicode locale extension "ca-japanese".
1234         // 2. language/country are "th"/"TH" and the resolved variant is exactly
1235         //    "TH" and no BCP 47 extensions are available, then add a Unicode locale
1236         //    extension "nu-thai".
1237         //
1238         String[][] testdata = {
1239             {"ja-JP-x-lvariant-JP", "ja-JP-u-ca-japanese-x-lvariant-JP"},   // special case 1
1240             {"ja-JP-x-lvariant-JP-XXX"},
1241             {"ja-JP-u-ca-japanese-x-lvariant-JP"},
1242             {"ja-JP-u-ca-gregory-x-lvariant-JP"},
1243             {"ja-JP-u-cu-jpy-x-lvariant-JP"},
1244             {"ja-x-lvariant-JP"},
1245             {"th-TH-x-lvariant-TH", "th-TH-u-nu-thai-x-lvariant-TH"},   // special case 2
1246             {"th-TH-u-nu-thai-x-lvariant-TH"},
1247             {"en-US-x-lvariant-JP"},
1248         };
1249 
1250         Builder bldr = new Builder();
1251 
1252         for (String[] data : testdata) {
1253             String in = data[0];
1254             String expected = (data.length == 1) ? data[0] : data[1];
1255 
1256             // forLanguageTag
1257             Locale loc = Locale.forLanguageTag(in);
1258             String out = loc.toLanguageTag();
1259             assertEquals("Language tag roundtrip by forLanguageTag with input: " + in, expected, out);
1260 
1261             // setLanguageTag
1262             bldr.clear();
1263             bldr.setLanguageTag(in);
1264             loc = bldr.build();
1265             out = loc.toLanguageTag();
1266             assertEquals("Language tag roundtrip by Builder.setLanguageTag with input: " + in, expected, out);
1267         }
1268     }
1269 
1270     public void testBug7023613() {
1271         String[][] testdata = {
1272             {"en-Latn", "en__#Latn"},
1273             {"en-u-ca-japanese", "en__#u-ca-japanese"},
1274         };
1275 
1276         for (String[] data : testdata) {
1277             String in = data[0];
1278             String expected = (data.length == 1) ? data[0] : data[1];
1279 
1280             Locale loc = Locale.forLanguageTag(in);
1281             String out = loc.toString();
1282             assertEquals("Empty country field with non-empty script/extension with input: " + in, expected, out);
1283         }
1284     }
1285 
1286     /*
1287      * 7033504: (lc) incompatible behavior change for ja_JP_JP and th_TH_TH locales
1288      */
1289     public void testBug7033504() {
1290         checkCalendar(Locale.of("ja", "JP", "jp"), "java.util.GregorianCalendar");
1291         checkCalendar(Locale.of("ja", "jp", "jp"), "java.util.GregorianCalendar");
1292         checkCalendar(Locale.of("ja", "JP", "JP"), "java.util.JapaneseImperialCalendar");
1293         checkCalendar(Locale.of("ja", "jp", "JP"), "java.util.JapaneseImperialCalendar");
1294         checkCalendar(Locale.forLanguageTag("en-u-ca-japanese"),
1295                       "java.util.JapaneseImperialCalendar");
1296 
1297         checkDigit(Locale.of("th", "TH", "th"), '0');
1298         checkDigit(Locale.of("th", "th", "th"), '0');
1299         checkDigit(Locale.of("th", "TH", "TH"), '\u0e50');
1300         checkDigit(Locale.of("th", "TH", "TH"), '\u0e50');
1301         checkDigit(Locale.forLanguageTag("en-u-nu-thai"), '\u0e50');
1302     }
1303 
1304     private void checkCalendar(Locale loc, String expected) {
1305         Calendar cal = Calendar.getInstance(loc);
1306         assertEquals("Wrong calendar", expected, cal.getClass().getName());
1307     }
1308 
1309     private void checkDigit(Locale loc, Character expected) {
1310         DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(loc);
1311         Character zero = dfs.getZeroDigit();
1312         assertEquals("Wrong digit zero char", expected, zero);
1313     }
1314 
1315     ///
1316     /// utility asserts
1317     ///
1318 
1319     private void assertTrue(String msg, boolean v) {
1320         if (!v) {
1321             errln(msg + ": expected true");
1322         }
1323     }
1324 
1325     private void assertFalse(String msg, boolean v) {
1326         if (v) {
1327             errln(msg + ": expected false");
1328         }
1329     }
1330 
1331     private void assertEquals(String msg, Object e, Object v) {
1332         if (e == null ? v != null : !e.equals(v)) {
1333             if (e != null) {
1334                 e = "'" + e + "'";
1335             }
1336             if (v != null) {
1337                 v = "'" + v + "'";
1338             }
1339             errln(msg + ": expected " + e + " but got " + v);
1340         }
1341     }
1342 
1343     private void assertNotEquals(String msg, Object e, Object v) {
1344         if (e == null ? v == null : e.equals(v)) {
1345             if (e != null) {
1346                 e = "'" + e + "'";
1347             }
1348             errln(msg + ": expected not equal " + e);
1349         }
1350     }
1351 
1352     private void assertNull(String msg, Object o) {
1353         if (o != null) {
1354             errln(msg + ": expected null but got '" + o + "'");
1355         }
1356     }
1357 
1358     private void assertNotNull(String msg, Object o) {
1359         if (o == null) {
1360             errln(msg + ": expected non null");
1361         }
1362     }
1363 
1364     // not currently used, might get rid of exceptions from the API
1365     private abstract class ExceptionTest {
1366         private final Class<? extends Exception> exceptionClass;
1367 
1368         ExceptionTest(Class<? extends Exception> exceptionClass) {
1369             this.exceptionClass = exceptionClass;
1370         }
1371 
1372         public void run() {
1373             String failMsg = null;
1374             try {
1375                 call();
1376                 failMsg = "expected " + exceptionClass.getName() + "  but no exception thrown.";
1377             }
1378             catch (Exception e) {
1379                 if (!exceptionClass.isAssignableFrom(e.getClass())) {
1380                     failMsg = "expected " + exceptionClass.getName() + " but caught " + e;
1381                 }
1382             }
1383             if (failMsg != null) {
1384                 String msg = message();
1385                 msg = msg == null ? "" : msg + " ";
1386                 errln(msg + failMsg);
1387             }
1388         }
1389 
1390         public String message() {
1391             return null;
1392         }
1393 
1394         public abstract void call();
1395     }
1396 
1397     private abstract class ExpectNPE extends ExceptionTest {
1398         ExpectNPE() {
1399             super(NullPointerException.class);
1400             run();
1401         }
1402     }
1403 
1404     private abstract class BuilderNPE extends ExceptionTest {
1405         protected final String msg;
1406         protected final Builder b = new Builder();
1407 
1408         BuilderNPE(String msg) {
1409             super(NullPointerException.class);
1410 
1411             this.msg = msg;
1412 
1413             run();
1414         }
1415 
1416         public String message() {
1417             return msg;
1418         }
1419     }
1420 
1421     private abstract class ExpectIAE extends ExceptionTest {
1422         ExpectIAE() {
1423             super(IllegalArgumentException.class);
1424             run();
1425         }
1426     }
1427 
1428     private abstract class BuilderILE extends ExceptionTest {
1429         protected final String[] args;
1430         protected final Builder b = new Builder();
1431 
1432         protected String arg; // mutates during call
1433 
1434         BuilderILE(String... args) {
1435             super(IllformedLocaleException.class);
1436 
1437             this.args = args;
1438 
1439             run();
1440         }
1441 
1442         public void run() {
1443             for (String arg : args) {
1444                 this.arg = arg;
1445                 super.run();
1446             }
1447         }
1448 
1449         public String message() {
1450             return "arg: '" + arg + "'";
1451         }
1452     }
1453 }