1adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/*
2adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  Licensed to the Apache Software Foundation (ASF) under one or more
3adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  contributor license agreements.  See the NOTICE file distributed with
4adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  this work for additional information regarding copyright ownership.
5adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  The ASF licenses this file to You under the Apache License, Version 2.0
6adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  (the "License"); you may not use this file except in compliance with
7adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  the License.  You may obtain a copy of the License at
8adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
9adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *     http://www.apache.org/licenses/LICENSE-2.0
10adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *
11adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  Unless required by applicable law or agreed to in writing, software
12adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  distributed under the License is distributed on an "AS IS" BASIS,
13adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  See the License for the specific language governing permissions and
15adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project *  limitations under the License.
16adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
17adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
18adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpackage java.util;
19adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
20adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.IOException;
21adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.ObjectInputStream;
22adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.ObjectOutputStream;
23adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.ObjectStreamField;
24adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectimport java.io.Serializable;
25c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamathimport java.nio.charset.StandardCharsets;
26162b0775772fa66b7eb634760a8159a60c1ddceaElliott Hughesimport libcore.icu.ICU;
27adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
28adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project/**
294c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * {@code Locale} represents a language/country/variant combination. Locales are used to
304c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * alter the presentation of information such as numbers or dates to suit the conventions
314c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * in the region they describe.
324c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes *
334c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * <p>The language codes are two-letter lowercase ISO language codes (such as "en") as defined by
344c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * <a href="http://en.wikipedia.org/wiki/ISO_639-1">ISO 639-1</a>.
354c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * The country codes are two-letter uppercase ISO country codes (such as "US") as defined by
364c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3">ISO 3166-1</a>.
374c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * The variant codes are unspecified.
384c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes *
394c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * <p>Note that Java uses several deprecated two-letter codes. The Hebrew ("he") language
404c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes * code is rewritten as "iw", Indonesian ("id") as "in", and Yiddish ("yi") as "ji". This
41d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * rewriting happens even if you construct your own {@code Locale} object, not just for
42d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * instances returned by the various lookup methods.
434c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes *
44936306df62d7d44a806fbeb789c6432e7c325981smain@google.com * <a name="available_locales"></a><h3>Available locales</h3>
45d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * <p>This class' constructors do no error checking. You can create a {@code Locale} for languages
46d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * and countries that don't exist, and you can create instances for combinations that don't
47d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * exist (such as "de_US" for "German as spoken in the US").
48d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes *
49d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * <p>Note that locale data is not necessarily available for any of the locales pre-defined as
50d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * constants in this class except for en_US, which is the only locale Java guarantees is always
51d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * available.
52d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes *
53d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * <p>It is also a mistake to assume that all devices have the same locales available.
54d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * A device sold in the US will almost certainly support en_US and es_US, but not necessarily
55d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * any locales with the same language but different countries (such as en_GB or es_ES),
56d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * nor any locales for other languages (such as de_DE). The opposite may well be true for a device
57d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * sold in Europe.
584c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes *
5928fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * <p>You can use {@link Locale#getDefault} to get an appropriate locale for the <i>user</i> of the
6028fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * device you're running on, or {@link Locale#getAvailableLocales} to get a list of all the locales
613106a99ccbe2e2a25bb66266d0ee42d98dd18099Elliott Hughes * available on the device you're running on.
623106a99ccbe2e2a25bb66266d0ee42d98dd18099Elliott Hughes *
63936306df62d7d44a806fbeb789c6432e7c325981smain@google.com * <a name="locale_data"></a><h3>Locale data</h3>
64d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * <p>Note that locale data comes solely from ICU. User-supplied locale service providers (using
65d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * the {@code java.text.spi} or {@code java.util.spi} mechanisms) are not supported.
66d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes *
67d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * <p>Here are the versions of ICU (and the corresponding CLDR and Unicode versions) used in
68d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * various Android releases:
69d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * <table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY="">
70413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes * <tr><td>Android 1.5 (Cupcake)/Android 1.6 (Donut)/Android 2.0 (Eclair)</td>
71413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td>ICU 3.8</td>
72413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-5">CLDR 1.5</a></td>
73413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode5.0.0/">Unicode 5.0</a></td></tr>
74413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes * <tr><td>Android 2.2 (Froyo)</td>
75413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td>ICU 4.2</td>
76413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-7">CLDR 1.7</a></td>
77413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode5.1.0/">Unicode 5.1</a></td></tr>
78413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes * <tr><td>Android 2.3 (Gingerbread)/Android 3.0 (Honeycomb)</td>
79413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td>ICU 4.4</td>
80413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-8">CLDR 1.8</a></td>
81413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode5.2.0/">Unicode 5.2</a></td></tr>
82413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes * <tr><td>Android 4.0 (Ice Cream Sandwich)</td>
83582a8e343e0fa159954eb8b70b8c4e06147bc60eElliott Hughes *     <td><a href="http://site.icu-project.org/download/46">ICU 4.6</a></td>
84413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-9">CLDR 1.9</a></td>
85413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
86413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes * <tr><td>Android 4.1 (Jelly Bean)</td>
87582a8e343e0fa159954eb8b70b8c4e06147bc60eElliott Hughes *     <td><a href="http://site.icu-project.org/download/48">ICU 4.8</a></td>
88413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-2-0">CLDR 2.0</a></td>
89413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr>
90413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes * <tr><td>Android 4.3 (Jelly Bean MR2)</td>
91582a8e343e0fa159954eb8b70b8c4e06147bc60eElliott Hughes *     <td><a href="http://site.icu-project.org/download/50">ICU 50</a></td>
92413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-22-1">CLDR 22.1</a></td>
93413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr>
94084a11c99172f083b0887438428a0df91065a1edElliott Hughes * <tr><td>Android 4.4 (KitKat)</td>
95582a8e343e0fa159954eb8b70b8c4e06147bc60eElliott Hughes *     <td><a href="http://site.icu-project.org/download/51">ICU 51</a></td>
96413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-23">CLDR 23</a></td>
97413d4592ee114eac81014af4b6347e73873ce8ceElliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr>
9876e916456153c0fed205c9d706fba7f2dedccc8fElliott Hughes * <tr><td>Android 5.0 (Lollipop)</td>
99582a8e343e0fa159954eb8b70b8c4e06147bc60eElliott Hughes *     <td><a href="http://site.icu-project.org/download/53">ICU 53</a></td>
100582a8e343e0fa159954eb8b70b8c4e06147bc60eElliott Hughes *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-25">CLDR 25</a></td>
101f6ab0423b298d2251bf7a0c6ad6d5639481a3929Elliott Hughes *     <td><a href="http://www.unicode.org/versions/Unicode6.3.0/">Unicode 6.3</a></td></tr>
1026068576078b1465d15750ebd0275ad20b0d895a2Narayan Kamath * <tr><td>Android 6.0 (Marshmallow)</td>
10387f8f39e2fc964638abc5b87ba12487cfbfa0ccbNarayan Kamath *     <td><a href="http://site.icu-project.org/download/55">ICU 55.1</a></td>
10487f8f39e2fc964638abc5b87ba12487cfbfa0ccbNarayan Kamath *     <td><a href="http://cldr.unicode.org/index/downloads/cldr-27">CLDR 27.0.1</a></td>
10587f8f39e2fc964638abc5b87ba12487cfbfa0ccbNarayan Kamath *     <td><a href="http://www.unicode.org/versions/Unicode7.0.0/">Unicode 7.0</a></td></tr>
106d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes * </table>
107d2d7abef3e9b73a57cdf1f2afd678d7f48945679Elliott Hughes *
108936306df62d7d44a806fbeb789c6432e7c325981smain@google.com * <a name="default_locale"></a><h3>Be wary of the default locale</h3>
1093106a99ccbe2e2a25bb66266d0ee42d98dd18099Elliott Hughes * <p>Note that there are many convenience methods that automatically use the default locale, but
11028fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * using them may lead to subtle bugs.
11128fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes *
11228fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * <p>The default locale is appropriate for tasks that involve presenting data to the user. In
11328fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * this case, you want to use the user's date/time formats, number
11428fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * formats, rules for conversion to lowercase, and so on. In this case, it's safe to use the
11528fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * convenience methods.
11628fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes *
11728fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * <p>The default locale is <i>not</i> appropriate for machine-readable output. The best choice
11828fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * there is usually {@code Locale.US}&nbsp;&ndash; this locale is guaranteed to be available on all
11928fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * devices, and the fact that it has no surprising special cases and is frequently used (especially
12028fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * for computer-computer communication) means that it tends to be the most efficient choice too.
12128fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes *
12228fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * <p>A common mistake is to implicitly use the default locale when producing output meant to be
12328fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * machine-readable. This tends to work on the developer's test devices (especially because so many
12428fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * developers use en_US), but fails when run on a device whose user is in a more complex locale.
12528fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes *
12628fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * <p>For example, if you're formatting integers some locales will use non-ASCII decimal
127ef415fba11d8588a8dba48b4afdde420e5dcdcccElliott Hughes * digits. As another example, if you're formatting floating-point numbers some locales will use
12828fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * {@code ','} as the decimal point and {@code '.'} for digit grouping. That's correct for
12928fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * human-readable output, but likely to cause problems if presented to another
13028fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * computer ({@link Double#parseDouble} can't parse such a number, for example).
13128fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * You should also be wary of the {@link String#toLowerCase} and
13228fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * {@link String#toUpperCase} overloads that don't take a {@code Locale}: in Turkey, for example,
13328fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * the characters {@code 'i'} and {@code 'I'} won't be converted to {@code 'I'} and {@code 'i'}.
13428fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * This is the correct behavior for Turkish text (such as user input), but inappropriate for, say,
13528fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes * HTTP headers.
136adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project */
137adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Projectpublic final class Locale implements Cloneable, Serializable {
138f5597e626ecf7949d249dea08c1a2964d890ec11Jesse Wilson
139adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private static final long serialVersionUID = 9149081749638150636L;
140adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
141adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
142adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for en_CA.
143adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
144a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale CANADA = new Locale(true, "en", "CA");
145adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
146adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
147adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for fr_CA.
148adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
149a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale CANADA_FRENCH = new Locale(true, "fr", "CA");
150adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
151adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
152adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for zh_CN.
153adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
154a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale CHINA = new Locale(true, "zh", "CN");
155adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
156adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
157adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for zh.
158adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
159a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale CHINESE = new Locale(true, "zh", "");
160adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
161adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
162adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for en.
163adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
164a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale ENGLISH = new Locale(true, "en", "");
165adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
166adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
167adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for fr_FR.
168adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
169a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale FRANCE = new Locale(true, "fr", "FR");
170adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
171adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
172adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for fr.
173adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
174a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale FRENCH = new Locale(true, "fr", "");
175adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
176adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
177adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for de.
178adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
179a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale GERMAN = new Locale(true, "de", "");
180adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
181adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
182adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for de_DE.
183adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
184a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale GERMANY = new Locale(true, "de", "DE");
185adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
186adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
187adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for it.
188adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
189a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale ITALIAN = new Locale(true, "it", "");
190adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
191adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
192adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for it_IT.
193adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
194a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale ITALY = new Locale(true, "it", "IT");
195adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
196adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
197adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for ja_JP.
198adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
199a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale JAPAN = new Locale(true, "ja", "JP");
200adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
201adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
202adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for ja.
203adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
204a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale JAPANESE = new Locale(true, "ja", "");
205adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
206adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
207adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for ko_KR.
208adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
209a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale KOREA = new Locale(true, "ko", "KR");
210adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
211adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
212adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for ko.
213adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
214a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale KOREAN = new Locale(true, "ko", "");
215adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
216adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
217adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for zh_CN.
218adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
219a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale PRC = new Locale(true, "zh", "CN");
220adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
221adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
22231df07d33bf4730c76d5f11b802e8e2bd40baba9Elliott Hughes     * Locale constant for the root locale. The root locale has an empty language,
22331df07d33bf4730c76d5f11b802e8e2bd40baba9Elliott Hughes     * country, and variant.
224f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     *
22531df07d33bf4730c76d5f11b802e8e2bd40baba9Elliott Hughes     * @since 1.6
22631df07d33bf4730c76d5f11b802e8e2bd40baba9Elliott Hughes     */
227a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale ROOT = new Locale(true, "", "");
22831df07d33bf4730c76d5f11b802e8e2bd40baba9Elliott Hughes
22931df07d33bf4730c76d5f11b802e8e2bd40baba9Elliott Hughes    /**
230adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for zh_CN.
231adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
232a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale SIMPLIFIED_CHINESE = new Locale(true, "zh", "CN");
233adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
234adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
235adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for zh_TW.
236adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
237a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale TAIWAN = new Locale(true, "zh", "TW");
238adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
239adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
240adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for zh_TW.
241adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
242a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale TRADITIONAL_CHINESE = new Locale(true, "zh", "TW");
243adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
244adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
245adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for en_GB.
246adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
247a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale UK = new Locale(true, "en", "GB");
248adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
249adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
250adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Locale constant for en_US.
251adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
252a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    public static final Locale US = new Locale(true, "en", "US");
253a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson
254a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson    /**
255c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * BCP-47 extension identifier (or "singleton") for the private
256c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * use extension.
257c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
258c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * See {@link #getExtension(char)} and {@link Builder#setExtension(char, String)}.
259c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
260c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
261c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
262c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public static final char PRIVATE_USE_EXTENSION = 'x';
263c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
264c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
265c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * BCP-47 extension identifier (or "singleton") for the unicode locale extension.
266c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
267c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
268c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * See {@link #getExtension(char)} and {@link Builder#setExtension(char, String)}.
269c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
270c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
271c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
272c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public static final char UNICODE_LOCALE_EXTENSION = 'u';
273c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
274c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
275bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     * ISO 639-3 generic code for undetermined languages.
276bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     */
277bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    private static final String UNDETERMINED_LANGUAGE = "und";
278bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
27961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
28061908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath    /**
28161908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath     * Map of grandfathered language tags to their modern replacements.
28261908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath     */
28361908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath    private static final TreeMap<String, String> GRANDFATHERED_LOCALES;
28461908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
2855eba1f238e37e3fef73b492badb950225150cdcfNarayan Kamath    static {
28661908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
28761908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
28861908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        // From http://tools.ietf.org/html/bcp47
28961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        //
29061908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        // grandfathered = irregular           ; non-redundant tags registered
29161908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        //               / regular             ; during the RFC 3066 era
29261908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        //  irregular =
29361908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("en-GB-oed", "en-GB-x-oed");
29461908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-ami", "ami");
29561908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-bnn", "bnn");
29661908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-default", "en-x-i-default");
29761908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-enochian", "und-x-i-enochian");
29861908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-hak", "hak");
29961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-klingon", "tlh");
30061908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-lux", "lb");
30161908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-mingo", "see-x-i-mingo");
30261908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-navajo", "nv");
30361908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-pwn", "pwn");
30461908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-tao", "tao");
30561908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-tay", "tay");
30661908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("i-tsu", "tsu");
30761908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("sgn-BE-FR", "sfb");
30861908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("sgn-BE-NL", "vgt");
30961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("sgn-CH-DE", "sgg");
31061908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
31161908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        // regular =
31261908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("art-lojban", "jbo");
31361908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("cel-gaulish", "xtg-x-cel-gaulish");
31461908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("no-bok", "nb");
31561908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("no-nyn", "nn");
31661908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("zh-guoyu", "cmn");
31761908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("zh-hakka", "hak");
31861908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("zh-min", "nan-x-zh-min");
31961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("zh-min-nan", "nan");
32061908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        GRANDFATHERED_LOCALES.put("zh-xiang", "hsn");
3215f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root    }
32261908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
3235f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root    private static class NoImagePreloadHolder {
3245f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root        /**
3255f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root         * The default locale, returned by {@code Locale.getDefault()}.
3265f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root         * Initialize the default locale from the system properties.
3275f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root         */
3285f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root        private static Locale defaultLocale = Locale.getDefaultLocaleFromSystemProperties();
32961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath    }
33061908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
33161908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath    /**
33261908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath     * Returns the default locale from system properties.
33361908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath     *
33461908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath     * @hide visible for testing.
33561908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath     */
33661908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath    public static Locale getDefaultLocaleFromSystemProperties() {
33761908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        final String languageTag = System.getProperty("user.locale", "");
33861908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
33961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        final Locale defaultLocale;
34061908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        if (!languageTag.isEmpty()) {
34161908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath            defaultLocale = Locale.forLanguageTag(languageTag);
34261908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        } else {
34361908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath            String language = System.getProperty("user.language", "en");
34461908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath            String region = System.getProperty("user.region", "US");
34561908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath            String variant = System.getProperty("user.variant", "");
34661908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath            defaultLocale = new Locale(language, region, variant);
34761908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        }
34861908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath
34961908bb6523ff12b53867e7bce15a672eca46e77Narayan Kamath        return defaultLocale;
350adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
351f5597e626ecf7949d249dea08c1a2964d890ec11Jesse Wilson
352c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
353c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * A class that helps construct {@link Locale} instances.
354c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
355c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Unlike the public {@code Locale} constructors, the methods of this class
356c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * perform much stricter checks on their input.
357c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
358c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Validity checks on the {@code language}, {@code country}, {@code variant}
359c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * and {@code extension} values are carried out as per the
360c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> specification.
361c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
362c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * In addition, we treat the <a href="http://www.unicode.org/reports/tr35/">
363c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Unicode locale extension</a> specially and provide methods to manipulate
364c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * the structured state (keywords and attributes) specified therein.
365c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
366c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
367c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
368c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public static final class Builder {
369c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        private String language;
370c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        private String region;
371c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        private String variant;
372c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        private String script;
373c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
374c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        private final Set<String> attributes;
375c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        private final Map<String, String> keywords;
376c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        private final Map<Character, String> extensions;
377c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
378c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder() {
379c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            language = region = variant = script = "";
380c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
381c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // NOTE: We use sorted maps in the builder & the locale class itself
382c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // because serialized forms of the unicode locale extension (and
383c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // of the extension map itself) are specified to be in alphabetic
384c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // order of keys.
385c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            attributes = new TreeSet<String>();
386c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            keywords = new TreeMap<String, String>();
387c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            extensions = new TreeMap<Character, String>();
388c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
389c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
390c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
391c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Sets the locale language. If {@code language} is {@code null} or empty, the
392c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * previous value is cleared.
393c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
394c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * As per BCP-47, the language must be between 2 and 3 ASCII characters
395c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * in length and must only contain characters in the range {@code [a-zA-Z]}.
396c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
397c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * This value is usually an <a href="http://www.loc.gov/standards/iso639-2/">
398c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * ISO-639-2</a> alpha-2 or alpha-3 code, though no explicit checks are
399c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * carried out that it's a valid code in that namespace.
400c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
401c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Values are normalized to lower case.
402c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
403c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Note that we don't support BCP-47 "extlang" languages because they were
404c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * only ever used to substitute for a lack of 3 letter language codes.
405c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
406c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if the language was invalid.
407c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
408c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setLanguage(String language) {
409bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            this.language = normalizeAndValidateLanguage(language, true /* strict */);
41012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            return this;
41112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
41212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
413bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        private static String normalizeAndValidateLanguage(String language, boolean strict) {
414c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (language == null || language.isEmpty()) {
41512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                return "";
416c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
417c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
418c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String lowercaseLanguage = language.toLowerCase(Locale.ROOT);
419c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!isValidBcp47Alpha(lowercaseLanguage, 2, 3)) {
420bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (strict) {
421bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    throw new IllformedLocaleException("Invalid language: " + language);
422bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                } else {
423bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    return UNDETERMINED_LANGUAGE;
424bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
425c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
426c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
42712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            return lowercaseLanguage;
428c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
429c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
430c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
431c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Set the state of this builder to the parsed contents of the BCP-47 language
432c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * tag {@code languageTag}.
433c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
434c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * This method is equivalent to a call to {@link #clear} if {@code languageTag}
435c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * is {@code null} or empty.
436c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
437c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * <b>NOTE:</b> In contrast to {@link Locale#forLanguageTag(String)}, which
438c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * simply ignores malformed input, this method will throw an exception if
439c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * its input is malformed.
440c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
441c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code languageTag} is not a well formed
442c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         BCP-47 tag.
443c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
444c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setLanguageTag(String languageTag) {
445c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (languageTag == null || languageTag.isEmpty()) {
446c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                clear();
447c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                return this;
448c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
449c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
450bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            final Locale fromIcu = forLanguageTag(languageTag, true /* strict */);
451c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // When we ask ICU for strict parsing, it might return a null locale
452c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // if the language tag is malformed.
453c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (fromIcu == null) {
454c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new IllformedLocaleException("Invalid languageTag: " + languageTag);
455c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
456c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
457c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            setLocale(fromIcu);
458c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
459c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
460c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
461c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
462c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Sets the locale region. If {@code region} is {@code null} or empty, the
463c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * previous value is cleared.
464c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
465c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * As per BCP-47, the region must either be a 2 character ISO-3166-1 code
466c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * (each character in the range [a-zA-Z]) OR a 3 digit UN M.49 code.
467c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
468c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Values are normalized to upper case.
469c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
470c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code} region is invalid.
471c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
472c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setRegion(String region) {
473bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            this.region = normalizeAndValidateRegion(region, true /* strict */);
47412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            return this;
47512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
47612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
477bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        private static String normalizeAndValidateRegion(String region, boolean strict) {
478c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (region == null || region.isEmpty()) {
47912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                return "";
480c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
481c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
482c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String uppercaseRegion = region.toUpperCase(Locale.ROOT);
483c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!isValidBcp47Alpha(uppercaseRegion, 2, 2) &&
484c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    !isUnM49AreaCode(uppercaseRegion)) {
485bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (strict) {
486bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    throw new IllformedLocaleException("Invalid region: " + region);
487bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                } else {
488bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    return "";
489bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
490c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
491c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
49212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            return uppercaseRegion;
493c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
494c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
495c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
496c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Sets the locale variant. If {@code variant} is {@code null} or empty,
497c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * the previous value is cleared.
498c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
499c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * The input string my consist of one or more variants separated by
500c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * valid separators ('-' or '_').
501c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
502c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * As per BCP-47, each variant must be between 5 and 8 alphanumeric characters
503c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * in length (each character in the range {@code [a-zA-Z0-9]}) but
504c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * can be exactly 4 characters in length if the first character is a digit.
505c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
506c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Note that this is a much stricter interpretation of {@code variant}
507c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * than the public {@code Locale} constructors. The latter allowed free form
508c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * variants.
509c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
510c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Variants are case sensitive and all separators are normalized to {@code '_'}.
511c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
512c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code} variant is invalid.
513c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
514c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setVariant(String variant) {
51512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            this.variant = normalizeAndValidateVariant(variant);
51612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            return this;
51712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
51812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
51912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        private static String normalizeAndValidateVariant(String variant) {
520c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (variant == null || variant.isEmpty()) {
52112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                return "";
522c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
523c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
524c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Note that unlike extensions, we canonicalize to lower case alphabets
525c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // and underscores instead of hyphens.
526c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String normalizedVariant = variant.replace('-', '_');
527c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            String[] subTags = normalizedVariant.split("_");
528c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
529c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            for (String subTag : subTags) {
530bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (!isValidVariantSubtag(subTag)) {
531c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    throw new IllformedLocaleException("Invalid variant: " + variant);
532c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                }
533c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
534c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
53512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            return normalizedVariant;
536c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
537c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
538bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        private static boolean isValidVariantSubtag(String subTag) {
539bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            // The BCP-47 spec states that :
540bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            // - Subtags can be between [5, 8] alphanumeric chars in length.
541bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            // - Subtags that start with a number are allowed to be 4 chars in length.
542bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (subTag.length() >= 5 && subTag.length() <= 8) {
543bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (isAsciiAlphaNum(subTag)) {
544bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    return true;
545bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
546bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            } else if (subTag.length() == 4) {
547bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                final char firstChar = subTag.charAt(0);
548bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if ((firstChar >= '0' && firstChar <= '9') && isAsciiAlphaNum(subTag)) {
549bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    return true;
550bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
551bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
552bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
553bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            return false;
554bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
555bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
556c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
557c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Sets the locale script. If {@code script} is {@code null} or empty,
558c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * the previous value is cleared.
559c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
560c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * As per BCP-47, the script must be 4 characters in length, and
561c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * each character in the range {@code [a-zA-Z]}.
562c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
563c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * A script usually represents a valid ISO 15924 script code, though no
564c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * other registry or validity checks are performed.
565c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
566c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Scripts are normalized to title cased values.
567c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
568c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code script} is invalid.
569c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
570c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setScript(String script) {
571bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            this.script = normalizeAndValidateScript(script, true /* strict */);
572bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            return this;
573bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
574bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
575bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        private static String normalizeAndValidateScript(String script, boolean strict) {
576c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (script == null || script.isEmpty()) {
577bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                return "";
578c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
579c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
580c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!isValidBcp47Alpha(script, 4, 4)) {
581bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (strict) {
582bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    throw new IllformedLocaleException("Invalid script: " + script);
583bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                } else {
584bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    return "";
585bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
586c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
587c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
588bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            return titleCaseAsciiWord(script);
589c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
590c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
591c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
592c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Sets the state of the builder to the {@link Locale} represented by
593c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@code locale}.
594c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
595c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Note that the locale's language, region and variant are validated as per
596c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * the rules specified in {@link #setLanguage}, {@link #setRegion} and
597c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@link #setVariant}.
598c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
599c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * All existing builder state is discarded.
600c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
601c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code locale} is invalid.
602c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws NullPointerException if {@code locale} is null.
603c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
604c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setLocale(Locale locale) {
605c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (locale == null) {
606c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new NullPointerException("locale == null");
607c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
608c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
609c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Make copies of the existing values so that we don't partially
610c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // update the state if we encounter an error.
611c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String backupLanguage = language;
612c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String backupRegion = region;
613c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String backupVariant = variant;
614c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
615c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            try {
616c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                setLanguage(locale.getLanguage());
617c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                setRegion(locale.getCountry());
618c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                setVariant(locale.getVariant());
619c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            } catch (IllformedLocaleException ifle) {
620c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                language = backupLanguage;
621c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                region = backupRegion;
622c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                variant = backupVariant;
623c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
624c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw ifle;
625c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
626c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
627c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // The following values can be set only via the builder class, so
628c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // there's no need to normalize them or check their validity.
629c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
630c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            this.script = locale.getScript();
631c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
632c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            extensions.clear();
633c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            extensions.putAll(locale.extensions);
634c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
635c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            keywords.clear();
636c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            keywords.putAll(locale.unicodeKeywords);
637c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
638c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            attributes.clear();
639c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            attributes.addAll(locale.unicodeAttributes);
640c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
641c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
642c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
643c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
644c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
645c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Adds the specified attribute to the list of attributes in the unicode
646c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * locale extension.
647c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
648c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Attributes must be between 3 and 8 characters in length, and each character
649c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * must be in the range {@code [a-zA-Z0-9]}.
650c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
651c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Attributes are normalized to lower case values. All added attributes and
652c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * keywords are combined to form a complete unicode locale extension on
653c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@link Locale} objects built by this builder, and accessible via
654c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@link Locale#getExtension(char)} with the {@link Locale#UNICODE_LOCALE_EXTENSION}
655c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * key.
656c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
657c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code attribute} is invalid.
658c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws NullPointerException if {@code attribute} is null.
659c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
660c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder addUnicodeLocaleAttribute(String attribute) {
661c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (attribute == null) {
662c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new NullPointerException("attribute == null");
663c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
664c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
665c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String lowercaseAttribute = attribute.toLowerCase(Locale.ROOT);
666c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!isValidBcp47Alphanum(lowercaseAttribute, 3, 8)) {
667c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new IllformedLocaleException("Invalid locale attribute: " + attribute);
668c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
669c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
670c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            attributes.add(lowercaseAttribute);
671c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
672c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
673c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
674c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
675c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
676c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Removes an attribute from the list of attributes in the unicode locale
677c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * extension.
678c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
679c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@code attribute} must be valid as per the rules specified in
680c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@link #addUnicodeLocaleAttribute}.
681c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
682c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * This method has no effect if {@code attribute} hasn't already been
683c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * added.
684c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
685c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code attribute} is invalid.
686c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws NullPointerException if {@code attribute} is null.
687c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
688c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder removeUnicodeLocaleAttribute(String attribute) {
689c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (attribute == null) {
690c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new NullPointerException("attribute == null");
691c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
692c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
693c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Weirdly, remove is specified to check whether the attribute
694c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // is valid, so we have to perform the full alphanumeric check here.
695c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String lowercaseAttribute = attribute.toLowerCase(Locale.ROOT);
696c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!isValidBcp47Alphanum(lowercaseAttribute, 3, 8)) {
697c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new IllformedLocaleException("Invalid locale attribute: " + attribute);
698c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
699c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
700c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            attributes.remove(attribute);
701c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
702c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
703c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
704c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
705c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Sets the extension identified by {@code key} to {@code value}.
706c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
707c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@code key} must be in the range {@code [a-zA-Z0-9]}.
708c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
709c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * If {@code value} is {@code null} or empty, the extension is removed.
710c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
711c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * In the general case, {@code value} must be a series of subtags separated
712c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * by ({@code "-"} or {@code "_"}). Each subtag must be between
713c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * 2 and 8 characters in length, and each character in the subtag must be in
714c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * the range {@code [a-zA-Z0-9]}.
715c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
716c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * <p>
717c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * There are two special cases :
718c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * <li>
719c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *     <ul>
720c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         The unicode locale extension
721c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         ({@code key == 'u'}, {@link Locale#UNICODE_LOCALE_EXTENSION}) : Setting
722c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         the unicode locale extension results in all existing keyword and attribute
723c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         state being replaced by the parsed result of {@code value}. For example,
724c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         {@code  builder.setExtension('u', "baaaz-baaar-fo-baar-ba-baaz")}
725c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         is equivalent to:
726c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         <pre>
727c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *             builder.addUnicodeLocaleAttribute("baaaz");
728c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *             builder.addUnicodeLocaleAttribute("baaar");
729c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *             builder.setUnicodeLocaleKeyword("fo", "baar");
730c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *             builder.setUnicodeLocaleKeyword("ba", "baaa");
731c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         </pre>
732c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *     </ul>
733c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *     <ul>
734c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         The private use extension
735c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         ({@code key == 'x'}, {@link Locale#PRIVATE_USE_EXTENSION}) : Each subtag in a
736c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         private use extension can be between 1 and 8 characters in length (in contrast
737c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         to a minimum length of 2 for all other extensions).
738c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *     </ul>
739c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * </li>
740c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
741c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code value} is invalid.
742c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
743c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setExtension(char key, String value) {
744c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (value == null || value.isEmpty()) {
745c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                extensions.remove(key);
746c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                return this;
747c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
748c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
749c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String normalizedValue = value.toLowerCase(Locale.ROOT).replace('_', '-');
750c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String[] subtags = normalizedValue.split("-");
7512824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            final char normalizedKey = Character.toLowerCase(key);
752c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
753c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Lengths for subtags in the private use extension should be [1, 8] chars.
754c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // For all other extensions, they should be [2, 8] chars.
755c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            //
756c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // http://www.rfc-editor.org/rfc/bcp/bcp47.txt
7572824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            final int minimumLength = (normalizedKey == PRIVATE_USE_EXTENSION) ? 1 : 2;
758c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            for (String subtag : subtags) {
759c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                if (!isValidBcp47Alphanum(subtag, minimumLength, 8)) {
760c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    throw new IllformedLocaleException(
761c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                            "Invalid private use extension : " + value);
762c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                }
763c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
764c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
765c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // We need to take special action in the case of unicode extensions,
766c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // since we claim to understand their keywords and attributes.
7672824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            if (normalizedKey == UNICODE_LOCALE_EXTENSION) {
768c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                // First clear existing attributes and keywords.
769c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                extensions.clear();
770c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                attributes.clear();
771c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
772c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                parseUnicodeExtension(subtags, keywords, attributes);
773c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            } else {
7742824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                extensions.put(normalizedKey, normalizedValue);
775c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
776c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
777c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
778c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
779c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
780c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
781c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Clears all extensions from this builder. Note that this also implicitly
782c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * clears all state related to the unicode locale extension; all attributes
783c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * and keywords set by {@link #addUnicodeLocaleAttribute} and
784c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@link #setUnicodeLocaleKeyword} are cleared.
785c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
786c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder clearExtensions() {
787c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            extensions.clear();
788c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            attributes.clear();
789c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            keywords.clear();
790c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
791c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
792c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
793c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
794c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Adds a key / type pair to the list of unicode locale extension keys.
795c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
796c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@code key} must be 2 characters in length, and each character must be
797c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * in the range {@code [a-zA-Z0-9]}.
798c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
799c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {#code type} can either be empty, or a series of one or more subtags
800c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * separated by a separator ({@code "-"} or {@code "_"}). Each subtag must
801c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * be between 3 and 8 characters in length and each character in the subtag
802c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * must be in the range {@code [a-zA-Z0-9]}.
803c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
804c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Note that the type is normalized to lower case, and all separators
805c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * are normalized to {@code "-"}. All added attributes and
806c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * keywords are combined to form a complete unicode locale extension on
807c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@link Locale} objects built by this builder, and accessible via
808c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * {@link Locale#getExtension(char)} with the {@link Locale#UNICODE_LOCALE_EXTENSION}
809c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * key.
810c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *
811c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * @throws IllformedLocaleException if {@code key} or {@code value} are
812c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         *         invalid.
813c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
814c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder setUnicodeLocaleKeyword(String key, String type) {
815c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (key == null) {
816c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new NullPointerException("key == null");
817c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
818c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
819c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (type == null && keywords != null) {
820c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                keywords.remove(key);
821c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                return this;
822c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
823c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
824c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String lowerCaseKey = key.toLowerCase(Locale.ROOT);
825c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // The key must be exactly two alphanumeric characters.
826c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (lowerCaseKey.length() != 2 || !isAsciiAlphaNum(lowerCaseKey)) {
827c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new IllformedLocaleException("Invalid unicode locale keyword: " + key);
828c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
829c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
830c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // The type can be one or more alphanumeric strings of length [3, 8] characters,
831c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // separated by a separator char, which is one of "_" or "-". Though the spec
832c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // doesn't require it, we normalize all "_" to "-" to make the rest of our
833c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // processing easier.
834c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final String lowerCaseType = type.toLowerCase(Locale.ROOT).replace("_", "-");
835c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!isValidTypeList(lowerCaseType)) {
836c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                throw new IllformedLocaleException("Invalid unicode locale type: " + type);
837c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
838c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
839c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Everything checks out fine, add the <key, type> mapping to the list.
840c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            keywords.put(lowerCaseKey, lowerCaseType);
841c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
842c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
843c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
844c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
845c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
846c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Clears all existing state from this builder.
847c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
848c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Builder clear() {
849c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            clearExtensions();
850c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            language = region = variant = script = "";
851c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
852c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return this;
853c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
854c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
855c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        /**
856c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * Constructs a locale from the existing state of the builder. Note that this
857c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * method is guaranteed to succeed since field validity checks are performed
858c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         * at the point of setting them.
859c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath         */
860c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        public Locale build() {
861c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // NOTE: We need to make a copy of attributes, keywords and extensions
862c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // because the RI allows this builder to reused.
863c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return new Locale(language, region, variant, script,
864c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    attributes, keywords, extensions,
86512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                    true /* has validated fields */);
866c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
867c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
868c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
869c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
870c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns a locale for a given BCP-47 language tag. This method is more
871c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * lenient than {@link Builder#setLanguageTag}. For a given language tag, parsing
872a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes     * will proceed up to the first malformed subtag. All subsequent tags are discarded.
873a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes     * Note that language tags use {@code -} rather than {@code _}, for example {@code en-US}.
874c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
875c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @throws NullPointerException if {@code languageTag} is {@code null}.
876c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
877c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
878c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
879c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public static Locale forLanguageTag(String languageTag) {
880c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (languageTag == null) {
881c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            throw new NullPointerException("languageTag == null");
882c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
883c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
884bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        return forLanguageTag(languageTag, false /* strict */);
885c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
886c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
887adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private transient String countryCode;
888adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private transient String languageCode;
889adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private transient String variantCode;
890c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private transient String scriptCode;
891c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
892c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /* Sorted, Unmodifiable */
893c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private transient Set<String> unicodeAttributes;
894c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /* Sorted, Unmodifiable */
895c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private transient Map<String, String> unicodeKeywords;
896c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /* Sorted, Unmodifiable */
897c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private transient Map<Character, String> extensions;
898adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
899757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes    /**
90012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * Whether this instance was constructed from a builder. We can make
90112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * stronger assumptions about the validity of Locale fields if this was
90212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * constructed by a builder.
90312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     */
90412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private transient final boolean hasValidatedFields;
90512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
90612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private transient String cachedToStringResult;
90712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private transient String cachedLanguageTag;
90812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private transient String cachedIcuLocaleId;
90912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
91012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    /**
911a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson     * There's a circular dependency between toLowerCase/toUpperCase and
912a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson     * Locale.US. Work around this by avoiding these methods when constructing
913a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson     * the built-in locales.
914757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes     */
91512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private Locale(boolean hasValidatedFields, String lowerCaseLanguageCode,
91612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            String upperCaseCountryCode) {
917a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson        this.languageCode = lowerCaseLanguageCode;
918a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson        this.countryCode = upperCaseCountryCode;
919a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson        this.variantCode = "";
920c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.scriptCode = "";
921c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
922c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.unicodeAttributes = Collections.EMPTY_SET;
923c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.unicodeKeywords = Collections.EMPTY_MAP;
924c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.extensions = Collections.EMPTY_MAP;
92512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
92612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        this.hasValidatedFields = hasValidatedFields;
927757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes    }
928adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
929adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
930adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Constructs a new {@code Locale} using the specified language.
931adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
932adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public Locale(String language) {
933c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this(language, "", "", "", Collections.EMPTY_SET, Collections.EMPTY_MAP,
93412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                Collections.EMPTY_MAP, false /* has validated fields */);
935adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
936adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
937adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
938adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Constructs a new {@code Locale} using the specified language and country codes.
939adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
940adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public Locale(String language, String country) {
941c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this(language, country, "", "", Collections.EMPTY_SET, Collections.EMPTY_MAP,
94212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                Collections.EMPTY_MAP, false /* has validated fields */);
943adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
944adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
945adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
946c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Required by libcore.icu.ICU.
947c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
948c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @hide
949adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
950c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public Locale(String language, String country, String variant, String scriptCode,
951c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            /* nonnull */ Set<String> unicodeAttributes,
952c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            /* nonnull */ Map<String, String> unicodeKeywords,
953c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            /* nonnull */ Map<Character, String> extensions,
95412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            boolean hasValidatedFields) {
955adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (language == null || country == null || variant == null) {
956c2d0a1f1bd2c6414c29dd44fe240dcf1f45e59b9Elliott Hughes            throw new NullPointerException("language=" + language +
957c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    ",country=" + country +
958c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    ",variant=" + variant);
959f5597e626ecf7949d249dea08c1a2964d890ec11Jesse Wilson        }
960a695e8fafadd2591cd148e78f19bc6d7c15121bbJesse Wilson
96112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (hasValidatedFields) {
96212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            this.languageCode = adjustLanguageCode(language);
96312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            this.countryCode = country;
96412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            this.variantCode = variant;
96512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        } else {
966c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (language.isEmpty() && country.isEmpty()) {
967c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                languageCode = "";
968c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                countryCode = "";
969c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                variantCode = variant;
970c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            } else {
971c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                languageCode = adjustLanguageCode(language);
972c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                countryCode = country.toUpperCase(Locale.US);
973c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                variantCode = variant;
974c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
975adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
976adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
977c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.scriptCode = scriptCode;
978c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
97912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (hasValidatedFields) {
980c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Set<String> attribsCopy = new TreeSet<String>(unicodeAttributes);
981a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            Map<String, String> keywordsCopy = new TreeMap<String, String>(unicodeKeywords);
982a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            Map<Character, String> extensionsCopy = new TreeMap<Character, String>(extensions);
983c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
984c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // We need to transform the list of attributes & keywords set on the
985c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // builder to a unicode locale extension. i.e, if we have any keywords
986c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // or attributes set, Locale#getExtension('u') should return a well
987c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // formed extension.
988a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            addUnicodeExtensionToExtensionsMap(attribsCopy, keywordsCopy, extensionsCopy);
989c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
990c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            this.unicodeAttributes = Collections.unmodifiableSet(attribsCopy);
991c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            this.unicodeKeywords = Collections.unmodifiableMap(keywordsCopy);
992c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            this.extensions = Collections.unmodifiableMap(extensionsCopy);
99312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        } else {
9942824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath
9952824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            // The locales ja_JP_JP and th_TH_TH are ill formed since their variant is too
9962824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            // short, however they have been used to represent a locale with the japanese imperial
9972824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            // calendar and thai numbering respectively. We add an extension in their constructor
9982824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            // to modernize them.
9992824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            if ("ja".equals(language) && "JP".equals(country) && "JP".equals(variant)) {
10002824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
10012824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                keywordsCopy.put("ca", "japanese");
10022824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                unicodeKeywords = keywordsCopy;
10032824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            } else if ("th".equals(language) && "TH".equals(country) && "TH".equals(variant)) {
10042824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                Map<String, String> keywordsCopy = new TreeMap<>(unicodeKeywords);
10052824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                keywordsCopy.put("nu", "thai");
10062824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                unicodeKeywords = keywordsCopy;
10072824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            }
10082824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath
10092824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            if (!unicodeKeywords.isEmpty() || !unicodeAttributes.isEmpty()) {
10102824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                Map<Character, String> extensionsCopy = new TreeMap<>(extensions);
10112824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                addUnicodeExtensionToExtensionsMap(unicodeAttributes, unicodeKeywords, extensionsCopy);
10122824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                extensions = extensionsCopy;
10132824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            }
10142824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath
101512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            this.unicodeAttributes = unicodeAttributes;
101612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            this.unicodeKeywords = unicodeKeywords;
101712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            this.extensions = extensions;
1018c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
101912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
102012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        this.hasValidatedFields = hasValidatedFields;
1021c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1022adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1023c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1024c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Constructs a new {@code Locale} using the specified language, country,
1025c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * and variant codes.
1026c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1027c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public Locale(String language, String country, String variant) {
1028c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this(language, country, variant, "", Collections.EMPTY_SET,
1029c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                Collections.EMPTY_MAP, Collections.EMPTY_MAP,
103012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                false /* has validated fields */);
1031adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1032adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
10332f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson    @Override public Object clone() {
1034adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        try {
1035adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return super.clone();
1036adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        } catch (CloneNotSupportedException e) {
10372f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson            throw new AssertionError(e);
1038adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1039adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1040adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1041adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
10422f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns true if {@code object} is a locale with the same language,
10432f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * country and variant.
1044adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
10452f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson    @Override public boolean equals(Object object) {
1046adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (object == this) {
1047adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return true;
1048adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1049adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (object instanceof Locale) {
1050adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            Locale o = (Locale) object;
1051adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return languageCode.equals(o.languageCode)
1052adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project                    && countryCode.equals(o.countryCode)
1053c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    && variantCode.equals(o.variantCode)
1054c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    && scriptCode.equals(o.scriptCode)
1055c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    && extensions.equals(o.extensions);
1056c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1057adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1058adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return false;
1059adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1060adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
10614c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes    /**
10622f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns the system's installed locales. This array always includes {@code
10632f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Locale.US}, and usually several others. Most locale-sensitive classes
10642f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * offer their own {@code getAvailableLocales} method, which should be
10652f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * preferred over this general purpose method.
1066f33eae7e84eb6d3b0f4e86b59605bb3de73009f3Elliott Hughes     *
10672f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * @see java.text.BreakIterator#getAvailableLocales()
10682f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * @see java.text.Collator#getAvailableLocales()
10692f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * @see java.text.DateFormat#getAvailableLocales()
10702f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * @see java.text.DateFormatSymbols#getAvailableLocales()
10712f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * @see java.text.DecimalFormatSymbols#getAvailableLocales()
10722f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * @see java.text.NumberFormat#getAvailableLocales()
10732f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * @see java.util.Calendar#getAvailableLocales()
10744c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes     */
10754c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes    public static Locale[] getAvailableLocales() {
1076757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        return ICU.getAvailableLocales();
10774c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes    }
1078adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1079adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
10802f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns the country code for this locale, or {@code ""} if this locale
10812f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * doesn't correspond to a specific country.
1082adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1083adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public String getCountry() {
1084adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return countryCode;
1085adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1086adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1087adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
10882f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns the user's preferred locale. This may have been overridden for
10892f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * this process with {@link #setDefault}.
1090f5597e626ecf7949d249dea08c1a2964d890ec11Jesse Wilson     *
10912f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * <p>Since the user's locale changes dynamically, avoid caching this value.
10922f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Instead, use this method to look it up for each use.
1093adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1094adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public static Locale getDefault() {
10955f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root        return NoImagePreloadHolder.defaultLocale;
1096adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1097adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1098adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
10992f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Equivalent to {@code getDisplayCountry(Locale.getDefault())}.
1100adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1101adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public final String getDisplayCountry() {
1102adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return getDisplayCountry(getDefault());
1103adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1104adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1105adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
11062f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns the name of this locale's country, localized to {@code locale}.
11072f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns the empty string if this locale does not correspond to a specific
11082f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * country.
1109adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
11102e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    public String getDisplayCountry(Locale locale) {
11112f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson        if (countryCode.isEmpty()) {
11122f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson            return "";
1113adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1114a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes
1115bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        final String normalizedRegion = Builder.normalizeAndValidateRegion(
1116bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                countryCode, false /* strict */);
1117bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (normalizedRegion.isEmpty()) {
1118a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            return countryCode;
1119a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        }
1120a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes
1121a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        String result = ICU.getDisplayCountry(this, locale);
11222e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
1123a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            result = ICU.getDisplayCountry(this, Locale.getDefault());
1124adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
11252e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        return result;
11262e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    }
1127adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1128adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
11292f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Equivalent to {@code getDisplayLanguage(Locale.getDefault())}.
1130adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1131adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public final String getDisplayLanguage() {
1132adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return getDisplayLanguage(getDefault());
1133adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1134adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1135adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
11362f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns the name of this locale's language, localized to {@code locale}.
11372f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * If the language name is unknown, the language code is returned.
1138adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
11392e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    public String getDisplayLanguage(Locale locale) {
11402f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson        if (languageCode.isEmpty()) {
11412f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson            return "";
1142adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
114350e50d57783060196a1f8e367616f2109c9edb4eElliott Hughes
1144a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // Hacks for backward compatibility.
1145a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        //
1146a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // Our language tag will contain "und" if the languageCode is invalid
1147a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // or missing. ICU will then return "langue indéterminée" or the equivalent
1148a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // display language for the indeterminate language code.
1149a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        //
1150a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // Sigh... ugh... and what not.
1151bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        final String normalizedLanguage = Builder.normalizeAndValidateLanguage(
1152bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                languageCode, false /* strict */);
1153bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (UNDETERMINED_LANGUAGE.equals(normalizedLanguage)) {
1154a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            return languageCode;
115550e50d57783060196a1f8e367616f2109c9edb4eElliott Hughes        }
115650e50d57783060196a1f8e367616f2109c9edb4eElliott Hughes
1157a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // TODO: We need a new hack or a complete fix for http://b/8049507 --- We would
1158a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // cover the frameworks' tracks when they were using "tl" instead of "fil".
1159a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        String result = ICU.getDisplayLanguage(this, locale);
11602e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
1161a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            result = ICU.getDisplayLanguage(this, Locale.getDefault());
1162adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
11632e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        return result;
11642e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    }
1165adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1166adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
11672f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Equivalent to {@code getDisplayName(Locale.getDefault())}.
1168adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1169adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public final String getDisplayName() {
1170adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return getDisplayName(getDefault());
1171adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1172adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1173adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
11742f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Returns this locale's language name, country name, and variant, localized
11752f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * to {@code locale}. The exact output form depends on whether this locale
11769acf622d505661055339de7fa3f8a86cd2b61f20Narayan Kamath     * corresponds to a specific language, script, country and variant.
117728fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     *
117828fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <p>For example:
117928fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <ul>
118028fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <li>{@code new Locale("en").getDisplayName(Locale.US)} -> {@code English}
118128fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <li>{@code new Locale("en", "US").getDisplayName(Locale.US)} -> {@code English (United States)}
118228fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.US)} -> {@code English (United States,Computer)}
11839acf622d505661055339de7fa3f8a86cd2b61f20Narayan Kamath     * <li>{@code Locale.fromLanguageTag("zh-Hant-CN").getDisplayName(Locale.US)} -> {@code Chinese (Traditional Han,China)}
118428fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <li>{@code new Locale("en").getDisplayName(Locale.FRANCE)} -> {@code anglais}
118528fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <li>{@code new Locale("en", "US").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis)}
118628fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis,informatique)}.
118728fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * </ul>
1188adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1189adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public String getDisplayName(Locale locale) {
1190adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        int count = 0;
1191a389b4a499f40379b0b204d7ba1c2057663d95c0Jesse Wilson        StringBuilder buffer = new StringBuilder();
11920ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes        if (!languageCode.isEmpty()) {
11930ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            String displayLanguage = getDisplayLanguage(locale);
11940ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            buffer.append(displayLanguage.isEmpty() ? languageCode : displayLanguage);
11950ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            ++count;
1196adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1197c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (!scriptCode.isEmpty()) {
1198c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (count == 1) {
1199c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                buffer.append(" (");
1200c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1201c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            String displayScript = getDisplayScript(locale);
12029acf622d505661055339de7fa3f8a86cd2b61f20Narayan Kamath            buffer.append(displayScript.isEmpty() ? scriptCode : displayScript);
1203c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            ++count;
1204c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
12050ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes        if (!countryCode.isEmpty()) {
1206adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            if (count == 1) {
1207757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes                buffer.append(" (");
12089acf622d505661055339de7fa3f8a86cd2b61f20Narayan Kamath            } else if (count == 2) {
12099acf622d505661055339de7fa3f8a86cd2b61f20Narayan Kamath                buffer.append(",");
1210adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
12110ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            String displayCountry = getDisplayCountry(locale);
12120ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            buffer.append(displayCountry.isEmpty() ? countryCode : displayCountry);
12130ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            ++count;
1214adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
12150ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes        if (!variantCode.isEmpty()) {
1216adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            if (count == 1) {
1217757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes                buffer.append(" (");
12189acf622d505661055339de7fa3f8a86cd2b61f20Narayan Kamath            } else if (count == 2 || count == 3) {
1219757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes                buffer.append(",");
1220adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            }
12210ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            String displayVariant = getDisplayVariant(locale);
12220ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            buffer.append(displayVariant.isEmpty() ? variantCode : displayVariant);
12230ade1b6059b1f8cec27fa65c6ee18f13f2e0cebdElliott Hughes            ++count;
1224adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1225adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        if (count > 1) {
1226757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes            buffer.append(")");
1227adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1228adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return buffer.toString();
1229adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1230adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1231adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
123228fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * Returns the full variant name in the default {@code Locale} for the variant code of
1233adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * this {@code Locale}. If there is no matching variant name, the variant code is
1234adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * returned.
1235e8a958066d95a4e15a9834e8b9067d106efd9b53Elliott Hughes     *
1236e8a958066d95a4e15a9834e8b9067d106efd9b53Elliott Hughes     * @since 1.7
1237adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1238adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public final String getDisplayVariant() {
1239adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return getDisplayVariant(getDefault());
1240adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1241adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1242adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
124328fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * Returns the full variant name in the specified {@code Locale} for the variant code
1244adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * of this {@code Locale}. If there is no matching variant name, the variant code is
1245adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * returned.
1246e8a958066d95a4e15a9834e8b9067d106efd9b53Elliott Hughes     *
1247e8a958066d95a4e15a9834e8b9067d106efd9b53Elliott Hughes     * @since 1.7
1248adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
12492e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    public String getDisplayVariant(Locale locale) {
1250a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        if (variantCode.isEmpty()) {
1251a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            return "";
1252a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        }
1253a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes
1254a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        try {
1255a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            Builder.normalizeAndValidateVariant(variantCode);
1256a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        } catch (IllformedLocaleException ilfe) {
1257adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            return variantCode;
1258adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1259a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes
1260a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        String result = ICU.getDisplayVariant(this, locale);
12612e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
1262a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            result = ICU.getDisplayVariant(this, Locale.getDefault());
1263a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        }
1264a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes
1265a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // The "old style" locale constructors allow us to pass in variants that aren't
1266a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // valid BCP-47 variant subtags. When that happens, toLanguageTag will not emit
1267a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // them. Note that we know variantCode.length() > 0 due to the isEmpty check at
1268a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // the beginning of this function.
1269a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        if (result.isEmpty()) {
1270a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            return variantCode;
1271adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
12722e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        return result;
12732e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    }
1274adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1275adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
127621f09462b23338161c3d3d777200e229f6f152ddElliott Hughes     * Returns the three-letter ISO 3166 country code which corresponds to the country
1277adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * code for this {@code Locale}.
127821f09462b23338161c3d3d777200e229f6f152ddElliott Hughes     * @throws MissingResourceException if there's no 3-letter country code for this locale.
1279adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
128028fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes    public String getISO3Country() {
1281ca6c2dfd21fef61f179223fb710db791802068d5Narayan Kamath        // The results of getISO3Country do not depend on the languageCode,
1282ca6c2dfd21fef61f179223fb710db791802068d5Narayan Kamath        // so we pass an arbitrarily selected language code here. This guards
1283ca6c2dfd21fef61f179223fb710db791802068d5Narayan Kamath        // against errors caused by malformed or invalid language codes.
1284ca6c2dfd21fef61f179223fb710db791802068d5Narayan Kamath        String code = ICU.getISO3Country("en-" + countryCode);
128521f09462b23338161c3d3d777200e229f6f152ddElliott Hughes        if (!countryCode.isEmpty() && code.isEmpty()) {
128621f09462b23338161c3d3d777200e229f6f152ddElliott Hughes            throw new MissingResourceException("No 3-letter country code for locale: " + this, "FormatData_" + this, "ShortCountry");
1287adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
128821f09462b23338161c3d3d777200e229f6f152ddElliott Hughes        return code;
12892e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    }
1290adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1291adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
129221f09462b23338161c3d3d777200e229f6f152ddElliott Hughes     * Returns the three-letter ISO 639-2/T language code which corresponds to the language
1293adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * code for this {@code Locale}.
129421f09462b23338161c3d3d777200e229f6f152ddElliott Hughes     * @throws MissingResourceException if there's no 3-letter language code for this locale.
1295adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
129628fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes    public String getISO3Language() {
1297a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // For backward compatibility, we must return "" for an empty language
1298a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // code and not "und" which is the accurate ISO-639-3 code for an
1299a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        // undetermined language.
1300a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        if (languageCode.isEmpty()) {
1301a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            return "";
1302a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        }
1303a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes
1304ca6c2dfd21fef61f179223fb710db791802068d5Narayan Kamath        // The results of getISO3Language do not depend on the country code
1305ca6c2dfd21fef61f179223fb710db791802068d5Narayan Kamath        // or any of the other locale fields, so we pass just the language here.
1306ca6c2dfd21fef61f179223fb710db791802068d5Narayan Kamath        String code = ICU.getISO3Language(languageCode);
130721f09462b23338161c3d3d777200e229f6f152ddElliott Hughes        if (!languageCode.isEmpty() && code.isEmpty()) {
130821f09462b23338161c3d3d777200e229f6f152ddElliott Hughes            throw new MissingResourceException("No 3-letter language code for locale: " + this, "FormatData_" + this, "ShortLanguage");
1309adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
131021f09462b23338161c3d3d777200e229f6f152ddElliott Hughes        return code;
13112e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    }
1312adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1313adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
131421f09462b23338161c3d3d777200e229f6f152ddElliott Hughes     * Returns an array of strings containing all the two-letter ISO 3166 country codes that can be
131528fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * used as the country code when constructing a {@code Locale}.
1316adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1317adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public static String[] getISOCountries() {
1318757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        return ICU.getISOCountries();
1319adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1320adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1321adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
132221f09462b23338161c3d3d777200e229f6f152ddElliott Hughes     * Returns an array of strings containing all the two-letter ISO 639-1 language codes that can be
132328fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * used as the language code when constructing a {@code Locale}.
1324f5597e626ecf7949d249dea08c1a2964d890ec11Jesse Wilson     */
13252e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    public static String[] getISOLanguages() {
1326757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        return ICU.getISOLanguages();
13272e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    }
1328adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1329adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
133028fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * Returns the language code for this {@code Locale} or the empty string if no language
1331adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * was set.
1332adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1333adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public String getLanguage() {
1334adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return languageCode;
1335adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1336adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1337adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
133828fe896e582d9ab920571dad758fcdb1f80cf47fElliott Hughes     * Returns the variant code for this {@code Locale} or an empty {@code String} if no variant
1339adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * was set.
1340adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1341adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public String getVariant() {
1342adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return variantCode;
1343adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1344adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1345c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1346c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns the script code for this {@code Locale} or an empty {@code String} if no script
1347c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * was set.
1348c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1349c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * If set, the script code will be a title cased string of length 4, as per the ISO 15924
1350c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * specification.
1351c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1352c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1353c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1354c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public String getScript() {
1355c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return scriptCode;
1356c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1357c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1358c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1359c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Equivalent to {@code getDisplayScript(Locale.getDefault()))}
1360c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1361c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1362c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1363c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public String getDisplayScript() {
1364c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return getDisplayScript(getDefault());
1365c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1366c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1367c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1368c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns the name of this locale's script code, localized to {@link Locale}. If the
1369c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * script code is unknown, the return value of this method is the same as that of
1370c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * {@link #getScript()}.
1371c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1372c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1373c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1374c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public String getDisplayScript(Locale locale) {
1375c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (scriptCode.isEmpty()) {
1376c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return "";
1377c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1378c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1379a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        String result = ICU.getDisplayScript(this, locale);
1380c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
1381a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            result = ICU.getDisplayScript(this, Locale.getDefault());
1382c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1383c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1384c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return result;
1385c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1386c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1387c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1388c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1389c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns a well formed BCP-47 language tag that identifies this locale.
1390c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1391c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Note that this locale itself might consist of ill formed fields, since the
1392c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * public {@code Locale} constructors do not perform validity checks to maintain
1393c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * backwards compatibility. When this is the case, this method will either replace
1394c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * ill formed fields with standard BCP-47 subtags (For eg. "und" (undetermined)
1395c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * for invalid languages) or omit them altogether.
1396c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1397c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Additionally, ill formed variants will result in the remainder of the tag
1398c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * (both variants and extensions) being moved to the private use extension,
1399c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * where they will appear after a subtag whose value is {@code "lvariant"}.
1400c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1401c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * It's also important to note that the BCP-47 tag is well formed in the sense
1402a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes     * that it is unambiguously parseable into its specified components. We do not
1403c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * require that any of the components are registered with the applicable registries.
1404c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * For example, we do not require scripts to be a registered ISO 15924 scripts or
1405c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * languages to appear in the ISO-639-2 code list.
1406c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1407c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1408c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1409c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public String toLanguageTag() {
1410c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (cachedLanguageTag == null) {
141112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            cachedLanguageTag = makeLanguageTag();
1412c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1413c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1414c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return cachedLanguageTag;
1415c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1416c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1417c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
141812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * Constructs a valid BCP-47 language tag from locale fields. Additional validation
141912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * is required when this Locale was not constructed using a Builder and variants
142012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * set this way are treated specially.
142112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     *
142212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * In both cases, we convert empty language tags to "und", omit invalid country tags
142312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * and perform a special case conversion of "no-NO-NY" to "nn-NO".
142412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     */
142512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private String makeLanguageTag() {
142612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // We only need to revalidate the language, country and variant because
142712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // the rest of the fields can only be set via the builder which validates
142812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // them anyway.
142912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        String language = "";
143012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        String region = "";
143112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        String variant = "";
143212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        String illFormedVariantSubtags = "";
143312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
143412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (hasValidatedFields) {
143512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            language = languageCode;
143612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            region = countryCode;
143712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            // Note that we are required to normalize hyphens to underscores
143812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            // in the builder, but we must use hyphens in the BCP-47 language tag.
143912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            variant = variantCode.replace('_', '-');
144012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        } else {
1441bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            language = Builder.normalizeAndValidateLanguage(languageCode, false /* strict */);
1442bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            region = Builder.normalizeAndValidateRegion(countryCode, false /* strict */);
144312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
144412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            try {
144512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                variant = Builder.normalizeAndValidateVariant(variantCode);
144612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            } catch (IllformedLocaleException ilfe) {
144712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                // If our variant is ill formed, we must attempt to split it into
144812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                // its constituent subtags and preserve the well formed bits and
144912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                // move the rest to the private use extension (if they're well
145012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                // formed extension subtags).
145112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                String split[] = splitIllformedVariant(variantCode);
145212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
145312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                variant = split[0];
145412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                illFormedVariantSubtags = split[1];
145512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            }
145612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
145712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
145812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (language.isEmpty()) {
1459bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            language = UNDETERMINED_LANGUAGE;
146012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
146112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
146212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if ("no".equals(language) && "NO".equals(region) && "NY".equals(variant)) {
146312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            language = "nn";
146412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            region = "NO";
146512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            variant = "";
146612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
146712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
146812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        final StringBuilder sb = new StringBuilder(16);
146912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        sb.append(language);
147012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
147112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (!scriptCode.isEmpty()) {
147212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append('-');
147312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append(scriptCode);
147412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
147512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
147612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (!region.isEmpty()) {
147712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append('-');
147812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append(region);
147912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
148012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
148112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (!variant.isEmpty()) {
148212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append('-');
148312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append(variant);
148412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
148512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
148612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // Extensions (optional, omitted if empty). Note that we don't
148712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // emit the private use extension here, but add it in the end.
148812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        for (Map.Entry<Character, String> extension : extensions.entrySet()) {
148912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            if (!extension.getKey().equals('x')) {
149012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                sb.append('-').append(extension.getKey());
149112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                sb.append('-').append(extension.getValue());
149212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            }
149312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
149412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
149512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // The private use extension comes right at the very end.
149612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        final String privateUse = extensions.get('x');
149712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (privateUse != null) {
149812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append("-x-");
149912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append(privateUse);
150012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
150112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
150212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // If we have any ill-formed variant subtags, we append them to the
150312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // private use extension (or add a private use extension if one doesn't
150412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // exist).
150512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (!illFormedVariantSubtags.isEmpty()) {
150612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            if (privateUse == null) {
150712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                sb.append("-x-lvariant-");
150812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            } else {
150912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                sb.append('-');
151012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            }
151112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            sb.append(illFormedVariantSubtags);
151212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
151312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
151412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        return sb.toString();
151512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    }
151612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
151712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    /**
151812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * Splits ill formed variants into a set of valid variant subtags (which
151912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * can be used directly in language tag construction) and a set of invalid
152012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * variant subtags (which can be appended to the private use extension),
152112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * provided that each subtag is a valid private use extension subtag.
152212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     *
152312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * This method returns a two element String array. The first element is a string
152412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * containing the concatenation of valid variant subtags which can be appended
152512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * to a BCP-47 tag directly and the second containing the concatenation of
152612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * invalid variant subtags which can be appended to the private use extension
152712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * directly.
152812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     *
152912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * This method assumes that {@code variant} contains at least one ill formed
153012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * variant subtag.
153112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     */
153212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private static String[] splitIllformedVariant(String variant) {
153312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        final String normalizedVariant = variant.replace('_', '-');
153412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        final String[] subTags = normalizedVariant.split("-");
153512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
153612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        final String[] split = new String[] { "", "" };
153712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
153812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // First go through the list of variant subtags and check if they're
153912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // valid private use extension subtags. If they're not, we will omit
154012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // the first such subtag and all subtags after.
154112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        //
154212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // NOTE: |firstInvalidSubtag| is the index of the first variant
154312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // subtag we decide to omit altogether, whereas |firstIllformedSubtag| is the
154412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // index of the first subtag we decide to append to the private use extension.
154512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        //
154612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // In other words:
154712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // [0, firstIllformedSubtag) => expressed as variant subtags.
154812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // [firstIllformedSubtag, firstInvalidSubtag) => expressed as private use
154912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // extension subtags.
155012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // [firstInvalidSubtag, subTags.length) => omitted.
155112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        int firstInvalidSubtag = subTags.length;
155212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        for (int i = 0; i < subTags.length; ++i) {
155312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            if (!isValidBcp47Alphanum(subTags[i], 1, 8)) {
155412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                firstInvalidSubtag = i;
155512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                break;
155612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            }
155712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
155812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
155912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        if (firstInvalidSubtag == 0) {
156012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            return split;
156112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
156212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
156312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // We now consider each subtag that could potentially be appended to
156412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        // the private use extension and check if it's valid.
156512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        int firstIllformedSubtag = firstInvalidSubtag;
156612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        for (int i = 0; i < firstInvalidSubtag; ++i) {
156712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            final String subTag = subTags[i];
156812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            // The BCP-47 spec states that :
156912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            // - Subtags can be between [5, 8] alphanumeric chars in length.
157012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            // - Subtags that start with a number are allowed to be 4 chars in length.
157112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            if (subTag.length() >= 5 && subTag.length() <= 8) {
157212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                if (!isAsciiAlphaNum(subTag)) {
157312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                    firstIllformedSubtag = i;
157412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                }
157512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            } else if (subTag.length() == 4) {
157612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                final char firstChar = subTag.charAt(0);
157712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                if (!(firstChar >= '0' && firstChar <= '9') || !isAsciiAlphaNum(subTag)) {
157812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                    firstIllformedSubtag = i;
157912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                }
158012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            } else {
158112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                firstIllformedSubtag = i;
158212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            }
158312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
158412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
158512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        split[0] = concatenateRange(subTags, 0, firstIllformedSubtag);
158612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        split[1] = concatenateRange(subTags, firstIllformedSubtag, firstInvalidSubtag);
158712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
158812ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        return split;
158912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    }
159012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
159112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    /**
159212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * Builds a string by concatenating array elements within the range [start, end).
159312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     * The supplied range is assumed to be valid and no checks are performed.
159412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath     */
159512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    private static String concatenateRange(String[] array, int start, int end) {
159612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        StringBuilder builder = new StringBuilder(32);
159712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        for (int i = start; i < end; ++i) {
1598bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (i != start) {
159912ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath                builder.append('-');
160012ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            }
160112ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath            builder.append(array[i]);
160212ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        }
160312ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
160412ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath        return builder.toString();
160512ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    }
160612ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath
160712ca8820818b604c6fc30f025857ec443c83d4a3Narayan Kamath    /**
1608c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns the set of BCP-47 extensions this locale contains.
1609c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1610c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * See <a href="https://tools.ietf.org/html/bcp47#section-2.1">
1611c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *     the IETF BCP-47 specification</a> (Section 2.2.6) for details.
1612c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1613c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1614c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1615c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public Set<Character> getExtensionKeys() {
1616c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return extensions.keySet();
1617c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1618c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1619c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1620c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns the BCP-47 extension whose key is {@code extensionKey}, or {@code null}
1621c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * if this locale does not contain the extension.
1622c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1623c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Individual Keywords and attributes for the unicode
1624c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * locale extension can be fetched using {@link #getUnicodeLocaleAttributes()},
1625c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * {@link #getUnicodeLocaleKeys()}  and {@link #getUnicodeLocaleType}.
1626c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1627c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1628c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1629c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public String getExtension(char extensionKey) {
1630c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return extensions.get(extensionKey);
1631c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1632c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1633c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1634c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns the {@code type} for the specified unicode locale extension {@code key}.
1635c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1636c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * For more information about types and keywords, see {@link Builder#setUnicodeLocaleKeyword}
1637c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a>
1638c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1639c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1640c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1641c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public String getUnicodeLocaleType(String keyWord) {
1642c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return unicodeKeywords.get(keyWord);
1643c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1644c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1645c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1646c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns the set of unicode locale extension attributes this locale contains.
1647c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1648c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * For more information about attributes, see {@link Builder#addUnicodeLocaleAttribute}
1649c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a>
1650c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1651c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1652c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1653c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public Set<String> getUnicodeLocaleAttributes() {
1654c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return unicodeAttributes;
1655c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1656c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1657c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1658c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Returns the set of unicode locale extension keywords this locale contains.
1659c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1660c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * For more information about types and keywords, see {@link Builder#setUnicodeLocaleKeyword}
1661c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a>
1662c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1663c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @since 1.7
1664c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1665c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public Set<String> getUnicodeLocaleKeys() {
1666c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return unicodeKeywords.keySet();
1667c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1668c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1669adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    @Override
1670adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public synchronized int hashCode() {
1671c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return countryCode.hashCode()
1672c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                + languageCode.hashCode() + variantCode.hashCode()
1673c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                + scriptCode.hashCode() + extensions.hashCode();
1674adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1675adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1676adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
16772f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Overrides the default locale. This does not affect system configuration,
16782f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * and attempts to override the system-provided default locale may
16792f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * themselves be overridden by actual changes to the system configuration.
16802f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * Code that calls this method is usually incorrect, and should be fixed by
16812f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * passing the appropriate locale to each locale-sensitive method that's
16822f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson     * called.
1683adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1684adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public synchronized static void setDefault(Locale locale) {
16852f633269e71f3fb0b6248cc695cf37c5cbaf7e60Jesse Wilson        if (locale == null) {
1686c2d0a1f1bd2c6414c29dd44fe240dcf1f45e59b9Elliott Hughes            throw new NullPointerException("locale == null");
1687adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
1688a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        String languageTag = locale.toLanguageTag();
16895f612d5ca50f6eb41e42ce00de13ef86a51dc08eKenny Root        NoImagePreloadHolder.defaultLocale = locale;
1690a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        ICU.setDefaultLocale(languageTag);
1691adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1692adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1693adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    /**
1694adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * Returns the string representation of this {@code Locale}. It consists of the
16954c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes     * language code, country code and variant separated by underscores.
16964c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes     * If the language is missing the string begins
1697adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     * with an underscore. If the country is missing there are 2 underscores
16984c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes     * between the language and the variant. The variant cannot stand alone
16994c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes     * without a language and/or country code: in this case this method would
17004c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes     * return the empty string.
1701f5597e626ecf7949d249dea08c1a2964d890ec11Jesse Wilson     *
17024c2b8db255a9225079f5a1a942b76be1c242b7a7Elliott Hughes     * <p>Examples: "en", "en_US", "_US", "en__POSIX", "en_US_POSIX"
1703adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project     */
1704adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    @Override
1705adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    public final String toString() {
17062e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        String result = cachedToStringResult;
170757ff19856a9486babe99270a690950aa1652f903Elliott Hughes        if (result == null) {
1708a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes            result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode,
1709a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes                                                        scriptCode, extensions);
171057ff19856a9486babe99270a690950aa1652f903Elliott Hughes        }
171157ff19856a9486babe99270a690950aa1652f903Elliott Hughes        return result;
17122e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes    }
17132e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes
1714c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static String toNewString(String languageCode, String countryCode,
1715c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            String variantCode, String scriptCode, Map<Character, String> extensions) {
17162e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        // The string form of a locale that only has a variant is the empty string.
17172e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        if (languageCode.length() == 0 && countryCode.length() == 0) {
17182e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes            return "";
17192e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        }
1720c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
17212e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        // Otherwise, the output format is "ll_cc_variant", where language and country are always
17222e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        // two letters, but the variant is an arbitrary length. A size of 11 characters has room
17232e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        // for "en_US_POSIX", the largest "common" value. (In practice, the string form is almost
17242e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        // always 5 characters: "ll_cc".)
17252e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        StringBuilder result = new StringBuilder(11);
1726adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        result.append(languageCode);
1727c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1728a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes        final boolean hasScriptOrExtensions = !scriptCode.isEmpty() || !extensions.isEmpty();
1729c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1730c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (!countryCode.isEmpty() || !variantCode.isEmpty() || hasScriptOrExtensions) {
1731adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project            result.append('_');
1732adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
17332e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        result.append(countryCode);
1734c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (!variantCode.isEmpty() || hasScriptOrExtensions) {
17352e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes            result.append('_');
1736adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        }
17372e3a41defb42a97b463194d859d2d4088a600fd8Elliott Hughes        result.append(variantCode);
1738c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1739c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (hasScriptOrExtensions) {
1740c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!variantCode.isEmpty()) {
1741c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                result.append('_');
1742c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1743c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1744c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Note that this is notably different from the BCP-47 spec (for
1745c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // backwards compatibility). We are forced to append a "#" before the script tag.
1746c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // and also put the script code right at the end.
1747c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            result.append("#");
1748c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!scriptCode.isEmpty() ) {
1749c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                result.append(scriptCode);
1750c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1751c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1752c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Note the use of "-" instead of "_" before the extensions.
1753c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!extensions.isEmpty()) {
1754c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                if (!scriptCode.isEmpty()) {
1755c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    result.append('-');
1756c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                }
1757c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                result.append(serializeExtensions(extensions));
1758c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1759c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1760c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1761adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        return result.toString();
1762adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1763adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1764adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private static final ObjectStreamField[] serialPersistentFields = {
176528eb98ecd43c27702e85b0561e040e2da10320a6Elliott Hughes        new ObjectStreamField("country", String.class),
1766e26ba79900d471d02d656f686926918ef7dc751fElliott Hughes        new ObjectStreamField("hashcode", int.class),
176728eb98ecd43c27702e85b0561e040e2da10320a6Elliott Hughes        new ObjectStreamField("language", String.class),
176828eb98ecd43c27702e85b0561e040e2da10320a6Elliott Hughes        new ObjectStreamField("variant", String.class),
1769c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        new ObjectStreamField("script", String.class),
1770c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        new ObjectStreamField("extensions", String.class),
177128eb98ecd43c27702e85b0561e040e2da10320a6Elliott Hughes    };
1772adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1773adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    private void writeObject(ObjectOutputStream stream) throws IOException {
1774adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        ObjectOutputStream.PutField fields = stream.putFields();
1775757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        fields.put("country", countryCode);
1776757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        fields.put("hashcode", -1);
1777757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        fields.put("language", languageCode);
1778757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        fields.put("variant", variantCode);
1779c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        fields.put("script", scriptCode);
1780c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1781c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (!extensions.isEmpty()) {
1782c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            fields.put("extensions", serializeExtensions(extensions));
1783c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1784c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1785adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        stream.writeFields();
1786adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
1787adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project
1788757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
1789adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project        ObjectInputStream.GetField fields = stream.readFields();
1790757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        countryCode = (String) fields.get("country", "");
1791757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        languageCode = (String) fields.get("language", "");
1792757a7942eed2b0aa457f8517a0259d2ac82c5b18Elliott Hughes        variantCode = (String) fields.get("variant", "");
1793c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        scriptCode = (String) fields.get("script", "");
1794c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1795c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.unicodeKeywords = Collections.EMPTY_MAP;
1796c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.unicodeAttributes = Collections.EMPTY_SET;
1797c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.extensions = Collections.EMPTY_MAP;
1798c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1799c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        String extensions = (String) fields.get("extensions", null);
1800c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (extensions != null) {
1801c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            readExtensions(extensions);
1802c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1803c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1804c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1805c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private void readExtensions(String extensions) {
1806c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        Map<Character, String> extensionsMap = new TreeMap<Character, String>();
1807c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        parseSerializedExtensions(extensions, extensionsMap);
1808c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        this.extensions = Collections.unmodifiableMap(extensionsMap);
1809c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1810c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (extensionsMap.containsKey(UNICODE_LOCALE_EXTENSION)) {
1811c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            String unicodeExtension = extensionsMap.get(UNICODE_LOCALE_EXTENSION);
1812c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            String[] subTags = unicodeExtension.split("-");
1813c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1814c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Map<String, String> unicodeKeywords = new TreeMap<String, String>();
1815c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Set<String> unicodeAttributes = new TreeSet<String>();
1816c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            parseUnicodeExtension(subTags, unicodeKeywords, unicodeAttributes);
1817c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1818c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            this.unicodeKeywords = Collections.unmodifiableMap(unicodeKeywords);
1819c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            this.unicodeAttributes = Collections.unmodifiableSet(unicodeAttributes);
1820c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1821c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1822c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1823c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1824c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * The serialized form for extensions is straightforward. It's simply
1825c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * of the form key1-value1-key2-value2 where each value might in turn contain
1826c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * multiple subtags separated by hyphens. Each key is guaranteed to be a single
1827c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * character in length.
1828c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1829c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * This method assumes that {@code extensionsMap} is non-empty.
1830c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1831c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Visible for testing.
1832c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1833c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @hide
1834c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1835c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public static String serializeExtensions(Map<Character, String> extensionsMap) {
1836c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        Iterator<Map.Entry<Character, String>> entryIterator = extensionsMap.entrySet().iterator();
1837c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        StringBuilder sb = new StringBuilder(64);
1838c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1839c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        while (true) {
1840c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final Map.Entry<Character, String> entry = entryIterator.next();
1841c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            sb.append(entry.getKey());
1842c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            sb.append('-');
1843c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            sb.append(entry.getValue());
1844c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1845c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (entryIterator.hasNext()) {
1846c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                sb.append('-');
1847c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            } else {
1848c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                break;
1849c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1850c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1851c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1852c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return sb.toString();
1853c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1854c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1855c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1856c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Visible for testing.
1857c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
1858c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @hide
1859c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1860c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public static void parseSerializedExtensions(String extString, Map<Character, String> outputMap) {
1861c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // This probably isn't the most efficient approach, but it's the
1862c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // most straightforward to code.
1863c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        //
1864c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // Start by splitting the string on "-". We will then keep track of
1865c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // where each of the extension keys (single characters) appear in the
1866c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // original string and then use those indices to construct substrings
1867c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // representing the values.
1868c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        final String[] subTags = extString.split("-");
1869c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        final int[] typeStartIndices = new int[subTags.length / 2];
1870c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1871c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        int length = 0;
1872c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        int count = 0;
1873c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (String subTag : subTags) {
1874c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (subTag.length() > 0) {
1875c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                // Account for the length of the "-" at the end of each subtag.
1876c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                length += (subTag.length() + 1);
1877c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1878c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1879c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (subTag.length() == 1) {
1880c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                typeStartIndices[count++] = length;
1881c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1882c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1883c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1884c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (int i = 0; i < count; ++i) {
1885c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final int valueStart = typeStartIndices[i];
1886c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // Since the start Index points to the beginning of the next type
1887c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // ....prev-k-next.....
1888c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            //            |_ here
1889c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // (idx - 2) is the index of the next key
1890c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            // (idx - 3) is the (non inclusive) end of the previous type.
1891c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final int valueEnd = (i == (count - 1)) ?
1892c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    extString.length() : (typeStartIndices[i + 1] - 3);
1893c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1894c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            outputMap.put(extString.charAt(typeStartIndices[i] - 2),
1895c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    extString.substring(valueStart, valueEnd));
1896c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1897c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1898c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1899c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1900c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1901c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * A UN M.49 is a 3 digit numeric code.
1902c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1903c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static boolean isUnM49AreaCode(String code) {
1904c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (code.length() != 3) {
1905c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return false;
1906c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1907c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1908c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (int i = 0; i < 3; ++i) {
1909c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final char character = code.charAt(i);
1910c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!(character >= '0' && character <= '9')) {
1911c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                return false;
1912c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1913c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1914c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1915c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return true;
1916c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1917c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1918c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /*
1919c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Checks whether a given string is an ASCII alphanumeric string.
1920c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1921c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static boolean isAsciiAlphaNum(String string) {
1922c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (int i = 0; i < string.length(); i++) {
1923c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final char character = string.charAt(i);
1924c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!(character >= 'a' && character <= 'z' ||
1925c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    character >= 'A' && character <= 'Z' ||
1926c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    character >= '0' && character <= '9')) {
1927c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                return false;
1928c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1929c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1930c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1931c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return true;
1932c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1933c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1934a94266074c7b82720fd2cecfb37ab8da85f1b296Elliott Hughes    private static boolean isValidBcp47Alpha(String string, int lowerBound, int upperBound) {
1935c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        final int length = string.length();
1936c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (length < lowerBound || length > upperBound) {
1937c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return false;
1938c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1939c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1940c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (int i = 0; i < length; ++i) {
1941c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            final char character = string.charAt(i);
1942c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!(character >= 'a' && character <= 'z' ||
1943c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    character >= 'A' && character <= 'Z')) {
1944c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                return false;
1945c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1946c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1947c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1948c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return true;
1949c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1950c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1951c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static boolean isValidBcp47Alphanum(String attributeOrType,
1952c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            int lowerBound, int upperBound) {
1953c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (attributeOrType.length() < lowerBound || attributeOrType.length() > upperBound) {
1954c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return false;
1955c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1956c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1957c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return isAsciiAlphaNum(attributeOrType);
1958c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1959c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1960c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static String titleCaseAsciiWord(String word) {
1961c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        try {
1962c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            byte[] chars = word.toLowerCase(Locale.ROOT).getBytes(StandardCharsets.US_ASCII);
1963c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            chars[0] = (byte) ((int) chars[0] + 'A' - 'a');
1964c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return new String(chars, StandardCharsets.US_ASCII);
1965c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        } catch (UnsupportedOperationException uoe) {
1966c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            throw new AssertionError(uoe);
1967c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1968c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1969c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1970c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
1971c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * A type list must contain one or more alphanumeric subtags whose lengths
1972c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * are between 3 and 8.
1973c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
1974c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static boolean isValidTypeList(String lowerCaseTypeList) {
1975c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        final String[] splitList = lowerCaseTypeList.split("-");
1976c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (String type : splitList) {
1977c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!isValidBcp47Alphanum(type, 3, 8)) {
1978c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                return false;
1979c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
1980c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1981c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1982c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return true;
1983c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
1984c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1985c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static void addUnicodeExtensionToExtensionsMap(
1986c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Set<String> attributes, Map<String, String> keywords,
1987c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Map<Character, String> extensions) {
1988c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (attributes.isEmpty() && keywords.isEmpty()) {
1989c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            return;
1990c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
1991c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1992c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // Assume that the common case is a low number of keywords & attributes
1993c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // (usually one or two).
1994c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        final StringBuilder sb = new StringBuilder(32);
1995c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
1996c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // All attributes must appear before keywords, in lexical order.
1997c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (!attributes.isEmpty()) {
1998c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Iterator<String> attributesIterator = attributes.iterator();
1999c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            while (true) {
2000c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                sb.append(attributesIterator.next());
2001c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                if (attributesIterator.hasNext()) {
2002c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    sb.append('-');
2003c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                } else {
2004c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    break;
2005c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                }
2006c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
2007c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
2008c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2009c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (!keywords.isEmpty()) {
2010c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (!attributes.isEmpty()) {
2011c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                sb.append('-');
2012c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
2013c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2014c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Iterator<Map.Entry<String, String>> keywordsIterator = keywords.entrySet().iterator();
2015c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            while (true) {
2016c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                final Map.Entry<String, String> keyWord = keywordsIterator.next();
2017c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                sb.append(keyWord.getKey());
2018bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (!keyWord.getValue().isEmpty()) {
2019bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    sb.append('-');
2020bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    sb.append(keyWord.getValue());
2021bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
2022c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                if (keywordsIterator.hasNext()) {
2023c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    sb.append('-');
2024c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                } else {
2025c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    break;
2026c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                }
2027c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
2028c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
2029c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2030c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        extensions.put(UNICODE_LOCALE_EXTENSION, sb.toString());
2031c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
2032c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2033c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
2034c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * This extension is described by http://www.unicode.org/reports/tr35/#RFC5234
2035c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * unicode_locale_extensions = sep "u" (1*(sep keyword) / 1*(sep attribute) *(sep keyword)).
2036c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
2037c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * It must contain at least one keyword or attribute and attributes (if any)
2038c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * must appear before keywords. Attributes can't appear after keywords because
2039c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * they will be indistinguishable from a subtag of the keyword type.
2040c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
2041c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Visible for testing.
2042c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     *
2043c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * @hide
2044c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
2045c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    public static void parseUnicodeExtension(String[] subtags,
2046c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            Map<String, String> keywords, Set<String> attributes)  {
2047c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        String lastKeyword = null;
2048c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        List<String> subtagsForKeyword = new ArrayList<String>();
2049c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (String subtag : subtags) {
2050c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (subtag.length() == 2) {
2051c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                if (subtagsForKeyword.size() > 0) {
2052c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    keywords.put(lastKeyword, joinBcp47Subtags(subtagsForKeyword));
2053c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    subtagsForKeyword.clear();
2054c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                }
2055c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2056c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                lastKeyword = subtag;
2057c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            } else if (subtag.length() > 2) {
2058c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                if (lastKeyword == null) {
2059c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    attributes.add(subtag);
2060c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                } else {
2061c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                    subtagsForKeyword.add(subtag);
2062c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                }
2063c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
2064c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
2065c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2066c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (subtagsForKeyword.size() > 0) {
2067c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            keywords.put(lastKeyword, joinBcp47Subtags(subtagsForKeyword));
2068386d30705bc91f8dd82843544f033595478b083bNarayan Kamath        } else if (lastKeyword != null) {
2069bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            keywords.put(lastKeyword, "");
2070c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
2071c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
2072c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2073c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    /**
2074c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * Joins a list of subtags into a BCP-47 tag using the standard separator
2075c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     * ("-").
2076c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath     */
2077c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    private static String joinBcp47Subtags(List<String> strings) {
2078c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        final int size = strings.size();
2079c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2080c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        StringBuilder sb = new StringBuilder(strings.get(0).length());
2081c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        for (int i = 0; i < size; ++i) {
2082c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            sb.append(strings.get(i));
2083c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            if (i != size - 1) {
2084c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath                sb.append('-');
2085c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            }
2086c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
2087c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2088c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return sb.toString();
2089c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath    }
2090c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2091a29f321f2d347bfc0f0d7886fc74dcbbb4cd2432Narayan Kamath    /**
2092a29f321f2d347bfc0f0d7886fc74dcbbb4cd2432Narayan Kamath     * @hide for internal use only.
2093a29f321f2d347bfc0f0d7886fc74dcbbb4cd2432Narayan Kamath     */
2094a29f321f2d347bfc0f0d7886fc74dcbbb4cd2432Narayan Kamath    public static String adjustLanguageCode(String languageCode) {
2095c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        String adjusted = languageCode.toLowerCase(Locale.US);
2096c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // Map new language codes to the obsolete language
2097c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        // codes so the correct resource bundles will be used.
2098c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        if (languageCode.equals("he")) {
2099c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            adjusted = "iw";
2100c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        } else if (languageCode.equals("id")) {
2101c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            adjusted = "in";
2102c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        } else if (languageCode.equals("yi")) {
2103c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath            adjusted = "ji";
2104c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        }
2105c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath
2106c5b1eb191102a20bc0626aea955aba417e337fbcNarayan Kamath        return adjusted;
2107adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project    }
2108bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2109bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    private static String convertGrandfatheredTag(String original) {
2110bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        final String converted = GRANDFATHERED_LOCALES.get(original);
2111bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        return converted != null ? converted : original;
2112bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    }
2113bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2114bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    /**
2115bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     * Scans elements of {@code subtags} in the range {@code [startIndex, endIndex)}
2116bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     * and appends valid variant subtags upto the first invalid subtag  (if any) to
21174ddaa41a0b03823742021992d2da86b78f5af034Narayan Kamath     * {@code normalizedVariants}.
2118bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     */
2119bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    private static void extractVariantSubtags(String[] subtags, int startIndex, int endIndex,
2120bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            List<String> normalizedVariants) {
2121bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        for (int i = startIndex; i < endIndex; i++) {
2122bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            final String subtag = subtags[i];
2123bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2124bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (Builder.isValidVariantSubtag(subtag)) {
21254ddaa41a0b03823742021992d2da86b78f5af034Narayan Kamath                normalizedVariants.add(subtag);
2126bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            } else {
2127bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                break;
2128bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2129bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2130bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    }
2131bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2132bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    /**
2133bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     * Scans elements of {@code subtags} in the range {@code [startIndex, endIndex)}
2134bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     * and inserts valid extensions into {@code extensions}. The scan is aborted
2135bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     * when an invalid extension is encountered. Returns the index of the first
2136bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     * unparsable element of {@code subtags}.
2137bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath     */
2138bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    private static int extractExtensions(String[] subtags, int startIndex, int endIndex,
2139bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            Map<Character, String> extensions) {
2140bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        int privateUseExtensionIndex = -1;
2141bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        int extensionKeyIndex = -1;
2142bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2143bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        int i = startIndex;
2144bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        for (; i < endIndex; i++) {
2145bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            final String subtag = subtags[i];
2146bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2147bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            final boolean parsingPrivateUse = (privateUseExtensionIndex != -1) &&
2148bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    (extensionKeyIndex == privateUseExtensionIndex);
2149bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2150bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            // Note that private use extensions allow subtags of length 1.
2151bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            // Private use extensions *must* come last, so there's no ambiguity
2152bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            // in that case.
2153bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (subtag.length() == 1 && !parsingPrivateUse) {
2154bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // Emit the last extension we encountered if any. First check
2155bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // whether we encountered two keys in a row (which is an error).
2156bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // Also checks if we already have an extension with the same key,
2157bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // which is again an error.
2158bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (extensionKeyIndex != -1) {
2159bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    if ((i - 1) == extensionKeyIndex) {
2160bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                        return extensionKeyIndex;
2161bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    }
2162bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
21632824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                    final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
2164bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    if (extensions.containsKey(key.charAt(0))) {
2165bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                        return extensionKeyIndex;
2166bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    }
2167bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2168bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    final String value = concatenateRange(subtags, extensionKeyIndex + 1, i);
2169bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    extensions.put(key.charAt(0), value.toLowerCase(Locale.ROOT));
2170bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
2171bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2172bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // Mark the start of the next extension. Also keep track of whether this
2173bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // is a private use extension, and throw an error if it doesn't come last.
2174bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                extensionKeyIndex = i;
21752824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath                if ("x".equals(subtag.toLowerCase(Locale.ROOT))) {
2176bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    privateUseExtensionIndex = i;
2177bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                } else if (privateUseExtensionIndex != -1) {
2178bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    // The private use extension must come last.
2179bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    return privateUseExtensionIndex;
2180bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
2181bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            } else if (extensionKeyIndex != -1) {
2182bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // We must have encountered a valid key in order to start parsing
2183bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // its subtags.
2184bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (!isValidBcp47Alphanum(subtag, parsingPrivateUse ? 1 : 2, 8)) {
2185bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    return i;
2186bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
2187bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            } else {
2188bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                // Encountered a value without a preceding key.
2189bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                return i;
2190bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2191bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2192bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2193bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (extensionKeyIndex != -1) {
2194bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if ((i - 1) == extensionKeyIndex) {
2195bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                return extensionKeyIndex;
2196bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2197bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
21982824711e40ba28c03bdcc0cd329459e868e981d8Narayan Kamath            final String key = subtags[extensionKeyIndex].toLowerCase(Locale.ROOT);
2199bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (extensions.containsKey(key.charAt(0))) {
2200bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                return extensionKeyIndex;
2201bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2202bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2203bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            final String value = concatenateRange(subtags, extensionKeyIndex + 1, i);
2204bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            extensions.put(key.charAt(0), value.toLowerCase(Locale.ROOT));
2205bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2206bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2207bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        return i;
2208bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    }
2209bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2210bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    private static Locale forLanguageTag(/* @Nonnull */ String tag, boolean strict) {
2211bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        final String converted = convertGrandfatheredTag(tag);
2212bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        final String[] subtags = converted.split("-");
2213bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2214bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        int lastSubtag = subtags.length;
2215bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        for (int i = 0; i < subtags.length; ++i) {
2216bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            final String subtag = subtags[i];
2217bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (subtag.isEmpty() || subtag.length() > 8) {
2218bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (strict) {
2219bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    throw new IllformedLocaleException("Invalid subtag at index: " + i
2220bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                            + " in tag: " + tag);
2221bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                } else {
2222bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    lastSubtag = (i - 1);
2223bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
2224bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2225bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                break;
2226bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2227bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2228bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2229bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        final String languageCode = Builder.normalizeAndValidateLanguage(subtags[0], strict);
2230bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        String scriptCode = "";
2231bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        int nextSubtag = 1;
2232bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (lastSubtag > nextSubtag) {
2233bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            scriptCode = Builder.normalizeAndValidateScript(subtags[nextSubtag], false /* strict */);
2234bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (!scriptCode.isEmpty()) {
2235bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                nextSubtag++;
2236bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2237bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2238bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2239bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        String regionCode = "";
2240bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (lastSubtag > nextSubtag) {
2241bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            regionCode = Builder.normalizeAndValidateRegion(subtags[nextSubtag], false /* strict */);
2242bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (!regionCode.isEmpty()) {
2243bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                nextSubtag++;
2244bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2245bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2246bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2247bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        List<String> variants = null;
2248bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (lastSubtag > nextSubtag) {
2249bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            variants = new ArrayList<String>();
2250bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            extractVariantSubtags(subtags, nextSubtag, lastSubtag, variants);
2251bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            nextSubtag += variants.size();
2252bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2253bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2254bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        Map<Character, String> extensions = Collections.EMPTY_MAP;
2255bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (lastSubtag > nextSubtag) {
2256bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            extensions = new TreeMap<Character, String>();
2257bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            nextSubtag = extractExtensions(subtags, nextSubtag, lastSubtag, extensions);
2258bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2259bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2260bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (nextSubtag != lastSubtag) {
2261bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            if (strict) {
2262bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                throw new IllformedLocaleException("Unparseable subtag: " + subtags[nextSubtag]
2263bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                        + " from language tag: " + tag);
2264bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2265bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2266bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2267bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        Set<String> unicodeKeywords = Collections.EMPTY_SET;
2268bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        Map<String, String> unicodeAttributes = Collections.EMPTY_MAP;
2269bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (extensions.containsKey(UNICODE_LOCALE_EXTENSION)) {
2270bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            unicodeKeywords = new TreeSet<String>();
2271bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            unicodeAttributes = new TreeMap<String, String>();
2272bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            parseUnicodeExtension(extensions.get(UNICODE_LOCALE_EXTENSION).split("-"),
2273bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    unicodeAttributes, unicodeKeywords);
2274bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2275bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2276bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        String variantCode = "";
2277bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        if (variants != null && !variants.isEmpty()) {
2278bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            StringBuilder variantsBuilder = new StringBuilder(variants.size() * 8);
2279bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            for (int i = 0; i < variants.size(); ++i) {
2280bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                if (i != 0) {
2281bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                    variantsBuilder.append('_');
2282bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                }
2283bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                variantsBuilder.append(variants.get(i));
2284bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            }
2285bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath            variantCode = variantsBuilder.toString();
2286bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        }
2287bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath
2288bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath        return new Locale(languageCode, regionCode, variantCode, scriptCode,
2289bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath                unicodeKeywords, unicodeAttributes, extensions, true /* has validated fields */);
2290bf098bb6345b0917d645d809f4d703e6a0c904f4Narayan Kamath    }
2291adc854b798c1cfe3bfd4c27d68d5cee38ca617daThe Android Open Source Project}
2292