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