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