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