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} – 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