1 /*
   2  * Copyright (c) 2010, 2016, 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
  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", "IW", "", "iw" },
 296             { "iw", "IW", "", "iw" },
 297             { "yi", "DE", "", "ji" },
 298             { "ji", "DE", "", "ji" },
 299             { "id", "ID", "", "in" },
 300             { "in", "ID", "", "in" },
 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 = new Locale(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", "IW", "", "he-IW" },
 471             { "iw", "IW", "", "he-IW" },
 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 = new Locale(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 grandfathered 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             // grandfathered 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             // grandfathered irregular 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("grandfathered 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 Han", hansLocale.getDisplayScript());
 619 
 620         Locale.setDefault(Locale.GERMANY);
 621         assertEquals("latn DE", "Lateinisch", latnLocale.getDisplayScript());
 622         assertEquals("hans DE", "Vereinfachte Chinesische Schrift", 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 Han", hansLocale.getDisplayScript(Locale.US));
 633 
 634         assertEquals("latn DE", "Lateinisch", latnLocale.getDisplayScript(Locale.GERMANY));
 635         assertEquals("hans DE", "Vereinfachte Chinesische Schrift", hansLocale.getDisplayScript(Locale.GERMANY));
 636     }
 637 
 638     public void testGetDisplayName() {
 639         final Locale[] testLocales = {
 640                 Locale.ROOT,
 641                 new Locale("en"),
 642                 new Locale("en", "US"),
 643                 new Locale("", "US"),
 644                 new Locale("no", "NO", "NY"),
 645                 new Locale("", "", "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 Han)",
 660                 "Chinese (Traditional Han)",
 661                 "Chinese (Simplified Han,China)",
 662                 "Simplified Han",
 663         };
 664 
 665         final String[] displayNameSimplifiedChinese = {
 666                 "",
 667                 "\u82f1\u6587",
 668                 "\u82f1\u6587 (\u7f8e\u56fd)",
 669                 "\u7f8e\u56fd",
 670                 "\u632a\u5a01\u6587 (\u632a\u5a01,Nynorsk)",
 671                 "Nynorsk",
 672                 "\u4e2d\u6587 (\u7b80\u4f53\u4e2d\u6587)",
 673                 "\u4e2d\u6587 (\u7e41\u4f53\u4e2d\u6587)",
 674                 "\u4e2d\u6587 (\u7b80\u4f53\u4e2d\u6587,\u4e2d\u56fd)",
 675                 "\u7b80\u4f53\u4e2d\u6587",
 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(new Locale("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(new Locale("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(new Locale("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(new Locale("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 
1036         // illformed attribute throws IllformedLocaleException
1037         new BuilderILE("invalid attribute") { public void call() { b.addUnicodeLocaleAttribute("ca"); }};
1038     }
1039 
1040     public void testBuildersetUnicodeLocaleKeyword() {
1041         // Note: most behavior is tested in testBuilderSetExtension
1042         Builder builder = new Builder();
1043         Locale locale = builder
1044             .setUnicodeLocaleKeyword("co", "japanese")
1045             .setUnicodeLocaleKeyword("nu", "thai")
1046             .build();
1047         assertEquals("co", "japanese", locale.getUnicodeLocaleType("co"));
1048         assertEquals("nu", "thai", locale.getUnicodeLocaleType("nu"));
1049         assertEquals("keys", 2, locale.getUnicodeLocaleKeys().size());
1050 
1051         // can clear a keyword by setting to null, others remain
1052         String result = builder
1053             .setUnicodeLocaleKeyword("co", null)
1054             .build()
1055             .toLanguageTag();
1056         assertEquals("empty co", "und-u-nu-thai", result);
1057 
1058         // locale keyword extension goes when all keywords are gone
1059         result = builder
1060             .setUnicodeLocaleKeyword("nu", null)
1061             .build()
1062             .toLanguageTag();
1063         assertEquals("empty nu", "und", result);
1064 
1065         // locale keywords are ordered independent of order of addition
1066         result = builder
1067             .setUnicodeLocaleKeyword("zz", "012")
1068             .setUnicodeLocaleKeyword("aa", "345")
1069             .build()
1070             .toLanguageTag();
1071         assertEquals("reordered", "und-u-aa-345-zz-012", result);
1072 
1073         // null keyword throws NPE
1074         new BuilderNPE("keyword") { public void call() { b.setUnicodeLocaleKeyword(null, "thai"); }};
1075 
1076         // well-formed keywords are two alphanum
1077         new BuilderILE("a", "abc") { public void call() { b.setUnicodeLocaleKeyword(arg, "value"); }};
1078 
1079         // well-formed values are 3-8 alphanum
1080         new BuilderILE("ab", "abcdefghi") { public void call() { b.setUnicodeLocaleKeyword("ab", arg); }};
1081     }
1082 
1083     public void testBuilderPrivateUseExtension() {
1084         // normalizes hyphens to underscore, case to lower
1085         String source = "c-B-a";
1086         String target = "c-b-a";
1087         Builder builder = new Builder();
1088         String result = builder
1089             .setExtension(Locale.PRIVATE_USE_EXTENSION, source)
1090             .build()
1091             .getExtension(Locale.PRIVATE_USE_EXTENSION);
1092         assertEquals("abc", target, result);
1093 
1094         // multiple hyphens are ill-formed
1095         new BuilderILE("a--b") { public void call() { b.setExtension(Locale.PRIVATE_USE_EXTENSION, arg); }};
1096     }
1097 
1098     public void testBuilderClear() {
1099         String monster = "en-latn-US-NewYork-a-bb-cc-u-co-japanese-x-z-y-x-x";
1100         Builder builder = new Builder();
1101         Locale locale = Locale.forLanguageTag(monster);
1102         String result = builder
1103             .setLocale(locale)
1104             .clear()
1105             .build()
1106             .toLanguageTag();
1107         assertEquals("clear", "und", result);
1108     }
1109 
1110     public void testBuilderRemoveUnicodeAttribute() {
1111         // tested in testBuilderAddUnicodeAttribute
1112     }
1113 
1114     public void testBuilderBuild() {
1115         // tested in other test methods
1116     }
1117 
1118     public void testSerialize() {
1119         final Locale[] testLocales = {
1120             Locale.ROOT,
1121             new Locale("en"),
1122             new Locale("en", "US"),
1123             new Locale("en", "US", "Win"),
1124             new Locale("en", "US", "Win_XP"),
1125             new Locale("ja", "JP"),
1126             new Locale("ja", "JP", "JP"),
1127             new Locale("th", "TH"),
1128             new Locale("th", "TH", "TH"),
1129             new Locale("no", "NO"),
1130             new Locale("nb", "NO"),
1131             new Locale("nn", "NO"),
1132             new Locale("no", "NO", "NY"),
1133             new Locale("nn", "NO", "NY"),
1134             new Locale("he", "IL"),
1135             new Locale("he", "IL", "var"),
1136             new Locale("Language", "Country", "Variant"),
1137             new Locale("", "US"),
1138             new Locale("", "", "Java"),
1139             Locale.forLanguageTag("en-Latn-US"),
1140             Locale.forLanguageTag("zh-Hans"),
1141             Locale.forLanguageTag("zh-Hant-TW"),
1142             Locale.forLanguageTag("ja-JP-u-ca-japanese"),
1143             Locale.forLanguageTag("und-Hant"),
1144             Locale.forLanguageTag("und-a-123-456"),
1145             Locale.forLanguageTag("en-x-java"),
1146             Locale.forLanguageTag("th-TH-u-ca-buddist-nu-thai-x-lvariant-TH"),
1147         };
1148 
1149         for (Locale locale : testLocales) {
1150             try {
1151                 // write
1152                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
1153                 ObjectOutputStream oos = new ObjectOutputStream(bos);
1154                 oos.writeObject(locale);
1155 
1156                 // read
1157                 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
1158                 ObjectInputStream ois = new ObjectInputStream(bis);
1159                 Object o = ois.readObject();
1160 
1161                 assertEquals("roundtrip " + locale, locale, o);
1162             } catch (Exception e) {
1163                 errln(locale + " encountered exception:" + e.getLocalizedMessage());
1164             }
1165         }
1166     }
1167 
1168     public void testDeserialize6() {
1169         final String TESTFILEPREFIX = "java6locale_";
1170 
1171         File dataDir = null;
1172         String dataDirName = System.getProperty("serialized.data.dir");
1173         if (dataDirName == null) {
1174             URL resdirUrl = getClass().getClassLoader().getResource("serialized");
1175             if (resdirUrl != null) {
1176                 try {
1177                     dataDir = new File(resdirUrl.toURI());
1178                 } catch (URISyntaxException urie) {
1179                 }
1180             }
1181         } else {
1182             dataDir = new File(dataDirName);
1183         }
1184 
1185         if (dataDir == null) {
1186             errln("'dataDir' is null. serialized.data.dir Property value is "+dataDirName);
1187             return;
1188         } else if (!dataDir.isDirectory()) {
1189             errln("'dataDir' is not a directory. dataDir: "+dataDir.toString());
1190             return;
1191         }
1192 
1193         File[] files = dataDir.listFiles();
1194         for (File testfile : files) {
1195             if (testfile.isDirectory()) {
1196                 continue;
1197             }
1198             String name = testfile.getName();
1199             if (!name.startsWith(TESTFILEPREFIX)) {
1200                 continue;
1201             }
1202             Locale locale;
1203             String locStr = name.substring(TESTFILEPREFIX.length());
1204             if (locStr.equals("ROOT")) {
1205                 locale = Locale.ROOT;
1206             } else {
1207                 String[] fields = locStr.split("_", 3);
1208                 String lang = fields[0];
1209                 String country = (fields.length >= 2) ? fields[1] : "";
1210                 String variant = (fields.length == 3) ? fields[2] : "";
1211                 locale = new Locale(lang, country, variant);
1212             }
1213 
1214             // deserialize
1215             try (FileInputStream fis = new FileInputStream(testfile);
1216                  ObjectInputStream ois = new ObjectInputStream(fis))
1217             {
1218                 Object o = ois.readObject();
1219                 assertEquals("Deserialize Java 6 Locale " + locale, o, locale);
1220             } catch (Exception e) {
1221                 errln("Exception while reading " + testfile.getAbsolutePath() + " - " + e.getMessage());
1222             }
1223         }
1224     }
1225 
1226     public void testBug7002320() {
1227         // forLanguageTag() and Builder.setLanguageTag(String)
1228         // should add a location extension for following two cases.
1229         //
1230         // 1. language/country are "ja"/"JP" and the resolved variant (x-lvariant-*)
1231         //    is exactly "JP" and no BCP 47 extensions are available, then add
1232         //    a Unicode locale extension "ca-japanese".
1233         // 2. language/country are "th"/"TH" and the resolved variant is exactly
1234         //    "TH" and no BCP 47 extensions are available, then add a Unicode locale
1235         //    extension "nu-thai".
1236         //
1237         String[][] testdata = {
1238             {"ja-JP-x-lvariant-JP", "ja-JP-u-ca-japanese-x-lvariant-JP"},   // special case 1
1239             {"ja-JP-x-lvariant-JP-XXX"},
1240             {"ja-JP-u-ca-japanese-x-lvariant-JP"},
1241             {"ja-JP-u-ca-gregory-x-lvariant-JP"},
1242             {"ja-JP-u-cu-jpy-x-lvariant-JP"},
1243             {"ja-x-lvariant-JP"},
1244             {"th-TH-x-lvariant-TH", "th-TH-u-nu-thai-x-lvariant-TH"},   // special case 2
1245             {"th-TH-u-nu-thai-x-lvariant-TH"},
1246             {"en-US-x-lvariant-JP"},
1247         };
1248 
1249         Builder bldr = new Builder();
1250 
1251         for (String[] data : testdata) {
1252             String in = data[0];
1253             String expected = (data.length == 1) ? data[0] : data[1];
1254 
1255             // forLanguageTag
1256             Locale loc = Locale.forLanguageTag(in);
1257             String out = loc.toLanguageTag();
1258             assertEquals("Language tag roundtrip by forLanguageTag with input: " + in, expected, out);
1259 
1260             // setLanguageTag
1261             bldr.clear();
1262             bldr.setLanguageTag(in);
1263             loc = bldr.build();
1264             out = loc.toLanguageTag();
1265             assertEquals("Language tag roundtrip by Builder.setLanguageTag with input: " + in, expected, out);
1266         }
1267     }
1268 
1269     public void testBug7023613() {
1270         String[][] testdata = {
1271             {"en-Latn", "en__#Latn"},
1272             {"en-u-ca-japanese", "en__#u-ca-japanese"},
1273         };
1274 
1275         for (String[] data : testdata) {
1276             String in = data[0];
1277             String expected = (data.length == 1) ? data[0] : data[1];
1278 
1279             Locale loc = Locale.forLanguageTag(in);
1280             String out = loc.toString();
1281             assertEquals("Empty country field with non-empty script/extension with input: " + in, expected, out);
1282         }
1283     }
1284 
1285     /*
1286      * 7033504: (lc) incompatible behavior change for ja_JP_JP and th_TH_TH locales
1287      */
1288     public void testBug7033504() {
1289         checkCalendar(new Locale("ja", "JP", "jp"), "java.util.GregorianCalendar");
1290         checkCalendar(new Locale("ja", "jp", "jp"), "java.util.GregorianCalendar");
1291         checkCalendar(new Locale("ja", "JP", "JP"), "java.util.JapaneseImperialCalendar");
1292         checkCalendar(new Locale("ja", "jp", "JP"), "java.util.JapaneseImperialCalendar");
1293         checkCalendar(Locale.forLanguageTag("en-u-ca-japanese"),
1294                       "java.util.JapaneseImperialCalendar");
1295 
1296         checkDigit(new Locale("th", "TH", "th"), '0');
1297         checkDigit(new Locale("th", "th", "th"), '0');
1298         checkDigit(new Locale("th", "TH", "TH"), '\u0e50');
1299         checkDigit(new Locale("th", "TH", "TH"), '\u0e50');
1300         checkDigit(Locale.forLanguageTag("en-u-nu-thai"), '\u0e50');
1301     }
1302 
1303     private void checkCalendar(Locale loc, String expected) {
1304         Calendar cal = Calendar.getInstance(loc);
1305         assertEquals("Wrong calendar", expected, cal.getClass().getName());
1306     }
1307 
1308     private void checkDigit(Locale loc, Character expected) {
1309         DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(loc);
1310         Character zero = dfs.getZeroDigit();
1311         assertEquals("Wrong digit zero char", expected, zero);
1312     }
1313 
1314     ///
1315     /// utility asserts
1316     ///
1317 
1318     private void assertTrue(String msg, boolean v) {
1319         if (!v) {
1320             errln(msg + ": expected true");
1321         }
1322     }
1323 
1324     private void assertFalse(String msg, boolean v) {
1325         if (v) {
1326             errln(msg + ": expected false");
1327         }
1328     }
1329 
1330     private void assertEquals(String msg, Object e, Object v) {
1331         if (e == null ? v != null : !e.equals(v)) {
1332             if (e != null) {
1333                 e = "'" + e + "'";
1334             }
1335             if (v != null) {
1336                 v = "'" + v + "'";
1337             }
1338             errln(msg + ": expected " + e + " but got " + v);
1339         }
1340     }
1341 
1342     private void assertNotEquals(String msg, Object e, Object v) {
1343         if (e == null ? v == null : e.equals(v)) {
1344             if (e != null) {
1345                 e = "'" + e + "'";
1346             }
1347             errln(msg + ": expected not equal " + e);
1348         }
1349     }
1350 
1351     private void assertNull(String msg, Object o) {
1352         if (o != null) {
1353             errln(msg + ": expected null but got '" + o + "'");
1354         }
1355     }
1356 
1357     private void assertNotNull(String msg, Object o) {
1358         if (o == null) {
1359             errln(msg + ": expected non null");
1360         }
1361     }
1362 
1363     // not currently used, might get rid of exceptions from the API
1364     private abstract class ExceptionTest {
1365         private final Class<? extends Exception> exceptionClass;
1366 
1367         ExceptionTest(Class<? extends Exception> exceptionClass) {
1368             this.exceptionClass = exceptionClass;
1369         }
1370 
1371         public void run() {
1372             String failMsg = null;
1373             try {
1374                 call();
1375                 failMsg = "expected " + exceptionClass.getName() + "  but no exception thrown.";
1376             }
1377             catch (Exception e) {
1378                 if (!exceptionClass.isAssignableFrom(e.getClass())) {
1379                     failMsg = "expected " + exceptionClass.getName() + " but caught " + e;
1380                 }
1381             }
1382             if (failMsg != null) {
1383                 String msg = message();
1384                 msg = msg == null ? "" : msg + " ";
1385                 errln(msg + failMsg);
1386             }
1387         }
1388 
1389         public String message() {
1390             return null;
1391         }
1392 
1393         public abstract void call();
1394     }
1395 
1396     private abstract class ExpectNPE extends ExceptionTest {
1397         ExpectNPE() {
1398             super(NullPointerException.class);
1399             run();
1400         }
1401     }
1402 
1403     private abstract class BuilderNPE extends ExceptionTest {
1404         protected final String msg;
1405         protected final Builder b = new Builder();
1406 
1407         BuilderNPE(String msg) {
1408             super(NullPointerException.class);
1409 
1410             this.msg = msg;
1411 
1412             run();
1413         }
1414 
1415         public String message() {
1416             return msg;
1417         }
1418     }
1419 
1420     private abstract class ExpectIAE extends ExceptionTest {
1421         ExpectIAE() {
1422             super(IllegalArgumentException.class);
1423             run();
1424         }
1425     }
1426 
1427     private abstract class BuilderILE extends ExceptionTest {
1428         protected final String[] args;
1429         protected final Builder b = new Builder();
1430 
1431         protected String arg; // mutates during call
1432 
1433         BuilderILE(String... args) {
1434             super(IllformedLocaleException.class);
1435 
1436             this.args = args;
1437 
1438             run();
1439         }
1440 
1441         public void run() {
1442             for (String arg : args) {
1443                 this.arg = arg;
1444                 super.run();
1445             }
1446         }
1447 
1448         public String message() {
1449             return "arg: '" + arg + "'";
1450         }
1451     }
1452 }