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