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