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