• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4  ******************************************************************************
5  * Copyright (C) 2003-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  ******************************************************************************
8  */
9 
10 package com.ibm.icu.util;
11 
12 import java.io.Serializable;
13 import java.lang.reflect.InvocationTargetException;
14 import java.lang.reflect.Method;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Locale;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.MissingResourceException;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.TreeMap;
30 import java.util.TreeSet;
31 import java.util.regex.Pattern;
32 
33 import com.ibm.icu.impl.CacheBase;
34 import com.ibm.icu.impl.ICUData;
35 import com.ibm.icu.impl.ICUResourceBundle;
36 import com.ibm.icu.impl.ICUResourceTableAccess;
37 import com.ibm.icu.impl.LocaleIDParser;
38 import com.ibm.icu.impl.LocaleIDs;
39 import com.ibm.icu.impl.SoftCache;
40 import com.ibm.icu.impl.Utility;
41 import com.ibm.icu.impl.locale.AsciiUtil;
42 import com.ibm.icu.impl.locale.BaseLocale;
43 import com.ibm.icu.impl.locale.Extension;
44 import com.ibm.icu.impl.locale.InternalLocaleBuilder;
45 import com.ibm.icu.impl.locale.KeyTypeData;
46 import com.ibm.icu.impl.locale.LanguageTag;
47 import com.ibm.icu.impl.locale.LocaleExtensions;
48 import com.ibm.icu.impl.locale.LocaleSyntaxException;
49 import com.ibm.icu.impl.locale.ParseStatus;
50 import com.ibm.icu.impl.locale.UnicodeLocaleExtension;
51 import com.ibm.icu.lang.UScript;
52 import com.ibm.icu.text.LocaleDisplayNames;
53 import com.ibm.icu.text.LocaleDisplayNames.DialectHandling;
54 
55 /**
56  * {@icuenhanced java.util.Locale}.{@icu _usage_}
57  *
58  * A class analogous to {@link java.util.Locale} that provides additional
59  * support for ICU protocol.  In ICU 3.0 this class is enhanced to support
60  * RFC 3066 language identifiers.
61  *
62  * <p>Many classes and services in ICU follow a factory idiom, in
63  * which a factory method or object responds to a client request with
64  * an object.  The request includes a locale (the <i>requested</i>
65  * locale), and the returned object is constructed using data for that
66  * locale.  The system may lack data for the requested locale, in
67  * which case the locale fallback mechanism will be invoked until a
68  * populated locale is found (the <i>valid</i> locale).  Furthermore,
69  * even when a populated locale is found (the <i>valid</i> locale),
70  * further fallback may be required to reach a locale containing the
71  * specific data required by the service (the <i>actual</i> locale).
72  *
73  * <p>ULocale performs <b>'normalization'</b> and <b>'canonicalization'</b> of locale ids.
74  * Normalization 'cleans up' ICU locale ids as follows:
75  * <ul>
76  * <li>language, script, country, variant, and keywords are properly cased<br>
77  * (lower, title, upper, upper, and lower case respectively)</li>
78  * <li>hyphens used as separators are converted to underscores</li>
79  * <li>three-letter language and country ids are converted to two-letter
80  * equivalents where available</li>
81  * <li>surrounding spaces are removed from keywords and values</li>
82  * <li>if there are multiple keywords, they are put in sorted order</li>
83  * </ul>
84  * Canonicalization additionally performs the following:
85  * <ul>
86  * <li>POSIX ids are converted to ICU format IDs</li>
87  * <li>Legacy language tags (marked as “Type: grandfathered” in BCP 47)
88  * are converted to ICU standard form</li>
89  * </ul>
90  * All ULocale constructors automatically normalize the locale id.  To handle
91  * POSIX ids, <code>canonicalize</code> can be called to convert the id
92  * to canonical form, or the <code>canonicalInstance</code> factory method
93  * can be called.
94  *
95  * <p>This class provides selectors {@link #VALID_LOCALE} and {@link
96  * #ACTUAL_LOCALE} intended for use in methods named
97  * <tt>getLocale()</tt>.  These methods exist in several ICU classes,
98  * including {@link com.ibm.icu.util.Calendar}, {@link
99  * com.ibm.icu.util.Currency}, {@link com.ibm.icu.text.UFormat},
100  * {@link com.ibm.icu.text.BreakIterator},
101  * {@link com.ibm.icu.text.Collator},
102  * {@link com.ibm.icu.text.DateFormatSymbols}, and {@link
103  * com.ibm.icu.text.DecimalFormatSymbols} and their subclasses, if
104  * any. Once an object of one of these classes has been created,
105  * <tt>getLocale()</tt> may be called on it to determine the valid and
106  * actual locale arrived at during the object's construction.
107  *
108  * <p>Note: The <i>actual</i> locale is returned correctly, but the <i>valid</i>
109  * locale is not, in most cases.
110  *
111  * @see java.util.Locale
112  * @author weiv
113  * @author Alan Liu
114  * @author Ram Viswanadha
115  * @stable ICU 2.8
116  */
117 @SuppressWarnings("javadoc")    // com.ibm.icu.text.Collator is in another project
118 public final class ULocale implements Serializable, Comparable<ULocale> {
119     // using serialver from jdk1.4.2_05
120     private static final long serialVersionUID = 3715177670352309217L;
121 
122     private static final Pattern UND_PATTERN = Pattern.compile("^und(?=$|[_-])", Pattern.CASE_INSENSITIVE);
123 
124     private static CacheBase<String, String, Void> nameCache = new SoftCache<String, String, Void>() {
125         @Override
126         protected String createInstance(String tmpLocaleID, Void unused) {
127             return new LocaleIDParser(tmpLocaleID).getName();
128         }
129     };
130 
131     /**
132      * Types for {@link ULocale#getAvailableLocalesByType}
133      *
134      * @stable ICU 65
135      */
136     public static enum AvailableType {
137         /**
138          * Locales that return data when passed to ICU APIs,
139          * but not including legacy or alias locales.
140          *
141          * @stable ICU 65
142          */
143         DEFAULT,
144 
145         /**
146          * Legacy or alias locales that return data when passed to ICU APIs.
147          * Examples of supported legacy or alias locales:
148          *
149          * <ul>
150          * <li>iw (alias to he)
151          * <li>mo (alias to ro)
152          * <li>zh_CN (alias to zh_Hans_CN)
153          * <li>sr_BA (alias to sr_Cyrl_BA)
154          * <li>ars (alias to ar_SA)
155          * </ul>
156          *
157          * The locales in this set are disjoint from the ones in
158          * DEFAULT. To get both sets at the same time, use
159          * WITH_LEGACY_ALIASES.
160          *
161          * @stable ICU 65
162          */
163         ONLY_LEGACY_ALIASES,
164 
165         /**
166          * The union of the locales in DEFAULT and ONLY_LEGACY_ALIASES.
167          *
168          * @stable ICU 65
169          */
170         WITH_LEGACY_ALIASES,
171     }
172 
173     /**
174      * Useful constant for language.
175      * @stable ICU 3.0
176      */
177     public static final ULocale ENGLISH = new ULocale("en", Locale.ENGLISH);
178 
179     /**
180      * Useful constant for language.
181      * @stable ICU 3.0
182      */
183     public static final ULocale FRENCH = new ULocale("fr", Locale.FRENCH);
184 
185     /**
186      * Useful constant for language.
187      * @stable ICU 3.0
188      */
189     public static final ULocale GERMAN = new ULocale("de", Locale.GERMAN);
190 
191     /**
192      * Useful constant for language.
193      * @stable ICU 3.0
194      */
195     public static final ULocale ITALIAN = new ULocale("it", Locale.ITALIAN);
196 
197     /**
198      * Useful constant for language.
199      * @stable ICU 3.0
200      */
201     public static final ULocale JAPANESE = new ULocale("ja", Locale.JAPANESE);
202 
203     /**
204      * Useful constant for language.
205      * @stable ICU 3.0
206      */
207     public static final ULocale KOREAN = new ULocale("ko", Locale.KOREAN);
208 
209     /**
210      * Useful constant for language.
211      * @stable ICU 3.0
212      */
213     public static final ULocale CHINESE = new ULocale("zh", Locale.CHINESE);
214 
215 
216     // Special note about static initializer for
217     //   - SIMPLIFIED_CHINESE
218     //   - TRADTIONAL_CHINESE
219     //   - CHINA
220     //   - TAIWAN
221     //
222     // Equivalent JDK Locale for ULocale.SIMPLIFIED_CHINESE is different
223     // by JRE version. JRE 7 or later supports a script tag "Hans", while
224     // JRE 6 or older does not. JDK's Locale.SIMPLIFIED_CHINESE is actually
225     // zh_CN, not zh_Hans. This is same in Java 7 or later versions.
226     //
227     // ULocale#toLocale() implementation create a Locale with a script tag.
228     // When a new ULocale is constructed with the single arg
229     // constructor, the volatile field 'Locale locale' is initialized by
230     // #toLocale() method.
231     //
232     // Because we cannot hardcode corresponding JDK Locale representation below,
233     // SIMPLIFIED_CHINESE is constructed without JDK Locale argument, and
234     // #toLocale() is used for resolving the best matching JDK Locale at runtime.
235     //
236     // The same thing applies to TRADITIONAL_CHINESE.
237 
238     /**
239      * Useful constant for language.
240      * @stable ICU 3.0
241      */
242     public static final ULocale SIMPLIFIED_CHINESE = new ULocale("zh_Hans");
243 
244 
245     /**
246      * Useful constant for language.
247      * @stable ICU 3.0
248      */
249     public static final ULocale TRADITIONAL_CHINESE = new ULocale("zh_Hant");
250 
251     /**
252      * Useful constant for country/region.
253      * @stable ICU 3.0
254      */
255     public static final ULocale FRANCE = new ULocale("fr_FR", Locale.FRANCE);
256 
257     /**
258      * Useful constant for country/region.
259      * @stable ICU 3.0
260      */
261     public static final ULocale GERMANY = new ULocale("de_DE", Locale.GERMANY);
262 
263     /**
264      * Useful constant for country/region.
265      * @stable ICU 3.0
266      */
267     public static final ULocale ITALY = new ULocale("it_IT", Locale.ITALY);
268 
269     /**
270      * Useful constant for country/region.
271      * @stable ICU 3.0
272      */
273     public static final ULocale JAPAN = new ULocale("ja_JP", Locale.JAPAN);
274 
275     /**
276      * Useful constant for country/region.
277      * @stable ICU 3.0
278      */
279     public static final ULocale KOREA = new ULocale("ko_KR", Locale.KOREA);
280 
281     /**
282      * Useful constant for country/region.
283      * @stable ICU 3.0
284      */
285     public static final ULocale CHINA = new ULocale("zh_Hans_CN");
286 
287     /**
288      * Useful constant for country/region.
289      * @stable ICU 3.0
290      */
291     public static final ULocale PRC = CHINA;
292 
293     /**
294      * Useful constant for country/region.
295      * @stable ICU 3.0
296      */
297     public static final ULocale TAIWAN = new ULocale("zh_Hant_TW");
298 
299     /**
300      * Useful constant for country/region.
301      * @stable ICU 3.0
302      */
303     public static final ULocale UK = new ULocale("en_GB", Locale.UK);
304 
305     /**
306      * Useful constant for country/region.
307      * @stable ICU 3.0
308      */
309     public static final ULocale US = new ULocale("en_US", Locale.US);
310 
311     /**
312      * Useful constant for country/region.
313      * @stable ICU 3.0
314      */
315     public static final ULocale CANADA = new ULocale("en_CA", Locale.CANADA);
316 
317     /**
318      * Useful constant for country/region.
319      * @stable ICU 3.0
320      */
321     public static final ULocale CANADA_FRENCH = new ULocale("fr_CA", Locale.CANADA_FRENCH);
322 
323     /**
324      * Handy constant.
325      */
326     private static final String EMPTY_STRING = "";
327 
328     // Used in both ULocale and LocaleIDParser, so moved up here.
329     private static final char UNDERSCORE            = '_';
330 
331     // default empty locale
332     private static final Locale EMPTY_LOCALE = new Locale("", "");
333 
334     // special keyword key for Unicode locale attributes
335     private static final String LOCALE_ATTRIBUTE_KEY = "attribute";
336 
337     /**
338      * The root ULocale.
339      * @stable ICU 2.8
340      */
341     public static final ULocale ROOT = new ULocale("", EMPTY_LOCALE);
342 
343     /**
344      * Enum for locale categories. These locale categories are used to get/set the default locale for
345      * the specific functionality represented by the category.
346      * @stable ICU 49
347      */
348     public enum Category {
349         /**
350          * Category used to represent the default locale for displaying user interfaces.
351          * @stable ICU 49
352          */
353         DISPLAY,
354         /**
355          * Category used to represent the default locale for formatting date, number and/or currency.
356          * @stable ICU 49
357          */
358         FORMAT
359     }
360 
361     private static final SoftCache<Locale, ULocale, Void> CACHE = new SoftCache<Locale, ULocale, Void>() {
362         @Override
363         protected ULocale createInstance(Locale key, Void unused) {
364             return JDKLocaleHelper.toULocale(key);
365         }
366     };
367 
368     /**
369      * Cache the locale.
370      */
371     private transient volatile Locale locale;
372 
373     /**
374      * The raw localeID that we were passed in.
375      */
376     private String localeID;
377 
378     /**
379      * Cache the locale data container fields.
380      * In future, we want to use them as the primary locale identifier storage.
381      */
382     private transient volatile BaseLocale baseLocale;
383     private transient volatile LocaleExtensions extensions;
384 
385     /**
386      * This table lists pairs of locale ids for canonicalization.
387      * The 1st item is the normalized id. The 2nd item is the
388      * canonicalized id.
389      */
390     private static String[][] CANONICALIZE_MAP = {
391         { "art__LOJBAN",    "jbo" }, /* registered name */
392         { "cel__GAULISH",   "cel__GAULISH" }, /* registered name */
393         { "de__1901",       "de__1901" }, /* registered name */
394         { "de__1906",       "de__1906" }, /* registered name */
395         { "en__BOONT",      "en__BOONT" }, /* registered name */
396         { "en__SCOUSE",     "en__SCOUSE" }, /* registered name */
397         { "hy__AREVELA",    "hy", null, null }, /* Registered IANA variant */
398         { "hy__AREVMDA",    "hyw", null, null }, /* Registered IANA variant */
399         { "sl__ROZAJ",      "sl__ROZAJ" }, /* registered name */
400         { "zh__GUOYU",      "zh" }, /* registered name */
401         { "zh__HAKKA",      "hak" }, /* registered name */
402         { "zh__XIANG",      "hsn" }, /* registered name */
403         // Three letter subtags won't be treated as variants.
404         { "zh_GAN",         "gan" }, /* registered name */
405         { "zh_MIN",         "zh__MIN" }, /* registered name */
406         { "zh_MIN_NAN",     "nan" }, /* registered name */
407         { "zh_WUU",         "wuu" }, /* registered name */
408         { "zh_YUE",         "yue" } /* registered name */
409     };
410 
411     /**
412      * Private constructor used by static initializers.
413      */
ULocale(String localeID, Locale locale)414     private ULocale(String localeID, Locale locale) {
415         this.localeID = localeID;
416         this.locale = locale;
417     }
418 
419     /**
420      * {@icu} Returns a ULocale object for a {@link java.util.Locale}.
421      * The ULocale is canonicalized.
422      * @param loc a {@link java.util.Locale}
423      * @stable ICU 3.2
424      */
forLocale(Locale loc)425     public static ULocale forLocale(Locale loc) {
426         if (loc == null) {
427             return null;
428         }
429         return CACHE.getInstance(loc, null /* unused */);
430     }
431 
432     /**
433      * {@icu} Constructs a ULocale from a RFC 3066 locale ID. The locale ID consists
434      * of optional language, script, country, and variant fields in that order,
435      * separated by underscores, followed by an optional keyword list.  The
436      * script, if present, is four characters long-- this distinguishes it
437      * from a country code, which is two characters long.  Other fields
438      * are distinguished by position as indicated by the underscores.  The
439      * start of the keyword list is indicated by '@', and consists of two
440      * or more keyword/value pairs separated by semicolons(';').
441      *
442      * <p>This constructor does not canonicalize the localeID.  So, for
443      * example, "zh__pinyin" remains unchanged instead of converting
444      * to "zh@collation=pinyin".  By default ICU only recognizes the
445      * latter as specifying pinyin collation.  Use {@link #createCanonical}
446      * or {@link #canonicalize} if you need to canonicalize the localeID.
447      *
448      * @param localeID string representation of the locale, e.g:
449      * "en_US", "sy_Cyrl_YU", "zh__pinyin", "es_ES@currency=EUR;collation=traditional"
450      * @stable ICU 2.8
451      */
ULocale(String localeID)452     public ULocale(String localeID) {
453         this.localeID = getName(localeID);
454     }
455 
456     /**
457      * Convenience overload of ULocale(String, String, String) for
458      * compatibility with java.util.Locale.
459      * @see #ULocale(String, String, String)
460      * @stable ICU 3.4
461      */
ULocale(String a, String b)462     public ULocale(String a, String b) {
463         this(a, b, null);
464     }
465 
466     /**
467      * Constructs a ULocale from a localeID constructed from the three 'fields' a, b, and
468      * c.  These fields are concatenated using underscores to form a localeID of the form
469      * a_b_c, which is then handled like the localeID passed to <code>ULocale(String
470      * localeID)</code>.
471      *
472      * <p>Java locale strings consisting of language, country, and
473      * variant will be handled by this form, since the country code
474      * (being shorter than four letters long) will not be interpreted
475      * as a script code.  If a script code is present, the final
476      * argument ('c') will be interpreted as the country code.  It is
477      * recommended that this constructor only be used to ease porting,
478      * and that clients instead use the single-argument constructor
479      * when constructing a ULocale from a localeID.
480      * @param a first component of the locale id
481      * @param b second component of the locale id
482      * @param c third component of the locale id
483      * @see #ULocale(String)
484      * @stable ICU 3.0
485      */
ULocale(String a, String b, String c)486     public ULocale(String a, String b, String c) {
487         localeID = getName(lscvToID(a, b, c, EMPTY_STRING));
488     }
489 
490     /**
491      * {@icu} Creates a ULocale from the id by first canonicalizing the id according to CLDR.
492      * @param nonCanonicalID the locale id to canonicalize
493      * @return the locale created from the canonical version of the ID.
494      * @stable ICU 3.0
495      */
createCanonical(String nonCanonicalID)496     public static ULocale createCanonical(String nonCanonicalID) {
497         return new ULocale(canonicalize(nonCanonicalID), (Locale)null);
498     }
499 
500     /**
501      * Creates a ULocale from the locale by first canonicalizing the locale according to CLDR.
502      * @param locale the ULocale to canonicalize
503      * @return the ULocale created from the canonical version of the ULocale.
504      * @stable ICU 67
505      */
createCanonical(ULocale locale)506     public static ULocale createCanonical(ULocale locale) {
507         return createCanonical(locale.getName());
508     }
509 
lscvToID(String lang, String script, String country, String variant)510     private static String lscvToID(String lang, String script, String country, String variant) {
511         StringBuilder buf = new StringBuilder();
512 
513         if (lang != null && lang.length() > 0) {
514             buf.append(lang);
515         }
516         if (script != null && script.length() > 0) {
517             buf.append(UNDERSCORE);
518             buf.append(script);
519         }
520         if (country != null && country.length() > 0) {
521             buf.append(UNDERSCORE);
522             buf.append(country);
523         }
524         if (variant != null && variant.length() > 0) {
525             if (country == null || country.length() == 0) {
526                 buf.append(UNDERSCORE);
527             }
528             buf.append(UNDERSCORE);
529             buf.append(variant);
530         }
531         return buf.toString();
532     }
533 
534     /**
535      * {@icu} Converts this ULocale object to a {@link java.util.Locale}.
536      * @return a {@link java.util.Locale} that either exactly represents this object
537      * or is the closest approximation.
538      * @stable ICU 2.8
539      */
toLocale()540     public Locale toLocale() {
541         if (locale == null) {
542             locale = JDKLocaleHelper.toLocale(this);
543         }
544         return locale;
545     }
546 
547     /**
548      * Keep our own default ULocale.
549      */
550     private static Locale defaultLocale = Locale.getDefault();
551     private static ULocale defaultULocale;
552 
553     private static Locale[] defaultCategoryLocales = new Locale[Category.values().length];
554     private static ULocale[] defaultCategoryULocales = new ULocale[Category.values().length];
555 
556     static {
557         defaultULocale = forLocale(defaultLocale);
558 
559         if (JDKLocaleHelper.hasLocaleCategories()) {
560             for (Category cat: Category.values()) {
561                 int idx = cat.ordinal();
562                 defaultCategoryLocales[idx] = JDKLocaleHelper.getDefault(cat);
563                 defaultCategoryULocales[idx] = forLocale(defaultCategoryLocales[idx]);
564             }
565         } else {
566             // Android API level 21..23 does not have separate category locales,
567             // use the non-category default for all.
568             for (Category cat: Category.values()) {
569                 int idx = cat.ordinal();
570                 defaultCategoryLocales[idx] = defaultLocale;
571                 defaultCategoryULocales[idx] = defaultULocale;
572             }
573         }
574     }
575 
576     /**
577      * Returns the current default ULocale.
578      * <p>
579      * The default ULocale is synchronized to the default Java Locale. This method checks
580      * the current default Java Locale and returns an equivalent ULocale.
581      *
582      * @return the default ULocale.
583      * @stable ICU 2.8
584      */
getDefault()585     public static ULocale getDefault() {
586         synchronized (ULocale.class) {
587             if (defaultULocale == null) {
588                 // When Java's default locale has extensions (such as ja-JP-u-ca-japanese),
589                 // Locale -> ULocale mapping requires BCP47 keyword mapping data that is currently
590                 // stored in a resource bundle. However, UResourceBundle currently requires
591                 // non-null default ULocale. For now, this implementation returns ULocale.ROOT
592                 // to avoid the problem.
593 
594                 // TODO: Consider moving BCP47 mapping data out of resource bundle later.
595 
596                 return ULocale.ROOT;
597             }
598             Locale currentDefault = Locale.getDefault();
599             if (!defaultLocale.equals(currentDefault)) {
600                 defaultLocale = currentDefault;
601                 defaultULocale = forLocale(currentDefault);
602 
603                 if (!JDKLocaleHelper.hasLocaleCategories()) {
604                     // Detected Java default Locale change.
605                     // We need to update category defaults to match
606                     // Java 7's behavior on Android API level 21..23.
607                     for (Category cat : Category.values()) {
608                         int idx = cat.ordinal();
609                         defaultCategoryLocales[idx] = currentDefault;
610                         defaultCategoryULocales[idx] = forLocale(currentDefault);
611                     }
612                 }            }
613             return defaultULocale;
614         }
615     }
616 
617     /**
618      * Sets the default ULocale.  This also sets the default Locale.
619      * If the caller does not have write permission to the
620      * user.language property, a security exception will be thrown,
621      * and the default ULocale will remain unchanged.
622      * <p>
623      * By setting the default ULocale with this method, all of the default category locales
624      * are also set to the specified default ULocale.
625      * @param newLocale the new default locale
626      * @throws SecurityException if a security manager exists and its
627      *        <code>checkPermission</code> method doesn't allow the operation.
628      * @throws NullPointerException if <code>newLocale</code> is null
629      * @see SecurityManager#checkPermission(java.security.Permission)
630      * @see java.util.PropertyPermission
631      * @see ULocale#setDefault(Category, ULocale)
632      * @stable ICU 3.0
633      */
setDefault(ULocale newLocale)634     public static synchronized void setDefault(ULocale newLocale){
635         defaultLocale = newLocale.toLocale();
636         Locale.setDefault(defaultLocale);
637         defaultULocale = newLocale;
638         // This method also updates all category default locales
639         for (Category cat : Category.values()) {
640             setDefault(cat, newLocale);
641         }
642     }
643 
644     /**
645      * Returns the current default ULocale for the specified category.
646      *
647      * @param category the category
648      * @return the default ULocale for the specified category.
649      * @stable ICU 49
650      */
getDefault(Category category)651     public static ULocale getDefault(Category category) {
652         synchronized (ULocale.class) {
653             int idx = category.ordinal();
654             if (defaultCategoryULocales[idx] == null) {
655                 // Just in case this method is called during ULocale class
656                 // initialization. Unlike getDefault(), we do not have
657                 // cyclic dependency for category default.
658                 return ULocale.ROOT;
659             }
660             if (JDKLocaleHelper.hasLocaleCategories()) {
661                 Locale currentCategoryDefault = JDKLocaleHelper.getDefault(category);
662                 if (!defaultCategoryLocales[idx].equals(currentCategoryDefault)) {
663                     defaultCategoryLocales[idx] = currentCategoryDefault;
664                     defaultCategoryULocales[idx] = forLocale(currentCategoryDefault);
665                 }
666             } else {
667                 // java.util.Locale.setDefault(Locale) in Java 7 updates
668                 // category locale defaults. On Android API level 21..23
669                 // ICU4J checks if the default locale has changed and update
670                 // category ULocales here if necessary.
671 
672                 // Note: When java.util.Locale.setDefault(Locale) is called
673                 // with a Locale same with the previous one, Java 7 still
674                 // updates category locale defaults. On Android API level 21..23
675                 // there is no good way to detect the event, ICU4J simply
676                 // checks if the default Java Locale has changed since last
677                 // time.
678 
679                 Locale currentDefault = Locale.getDefault();
680                 if (!defaultLocale.equals(currentDefault)) {
681                     defaultLocale = currentDefault;
682                     defaultULocale = forLocale(currentDefault);
683 
684                     for (Category cat : Category.values()) {
685                         int tmpIdx = cat.ordinal();
686                         defaultCategoryLocales[tmpIdx] = currentDefault;
687                         defaultCategoryULocales[tmpIdx] = forLocale(currentDefault);
688                     }
689                 }
690 
691                 // No synchronization with JDK Locale, because category default
692                 // is not supported in Android API level 21..23.
693             }
694             return defaultCategoryULocales[idx];
695         }
696     }
697 
698     /**
699      * Sets the default <code>ULocale</code> for the specified <code>Category</code>.
700      * This also sets the default <code>Locale</code> for the specified <code>Category</code>
701      * of the JVM. If the caller does not have write permission to the
702      * user.language property, a security exception will be thrown,
703      * and the default ULocale for the specified Category will remain unchanged.
704      *
705      * @param category the specified category to set the default locale
706      * @param newLocale the new default locale
707      * @see SecurityManager#checkPermission(java.security.Permission)
708      * @see java.util.PropertyPermission
709      * @stable ICU 49
710      */
setDefault(Category category, ULocale newLocale)711     public static synchronized void setDefault(Category category, ULocale newLocale) {
712         Locale newJavaDefault = newLocale.toLocale();
713         int idx = category.ordinal();
714         defaultCategoryULocales[idx] = newLocale;
715         defaultCategoryLocales[idx] = newJavaDefault;
716         JDKLocaleHelper.setDefault(category, newJavaDefault);
717     }
718 
719     /**
720      * This is for compatibility with Locale-- in actuality, since ULocale is
721      * immutable, there is no reason to clone it, so this API returns 'this'.
722      * @stable ICU 2.8
723      */
724     @Override
clone()725     public Object clone() {
726         return this;
727     }
728 
729     /**
730      * Returns the hashCode.
731      * @return a hash code value for this object.
732      * @stable ICU 2.8
733      */
734     @Override
hashCode()735     public int hashCode() {
736         return localeID.hashCode();
737     }
738 
739     /**
740      * Returns true if the other object is another ULocale with the
741      * same full name.
742      * Note that since names are not canonicalized, two ULocales that
743      * function identically might not compare equal.
744      *
745      * @return true if this Locale is equal to the specified object.
746      * @stable ICU 2.8
747      */
748     @Override
equals(Object obj)749     public boolean equals(Object obj) {
750         if (this == obj) {
751             return true;
752         }
753         if (obj instanceof ULocale) {
754             return localeID.equals(((ULocale)obj).localeID);
755         }
756         return false;
757     }
758 
759     /**
760      * Compares two ULocale for ordering.
761      * <p><b>Note:</b> The order might change in future.
762      *
763      * @param other the ULocale to be compared.
764      * @return a negative integer, zero, or a positive integer as this ULocale is less than, equal to, or greater
765      * than the specified ULocale.
766      * @throws NullPointerException if <code>other</code> is null.
767      *
768      * @stable ICU 53
769      */
770     @Override
compareTo(ULocale other)771     public int compareTo(ULocale other) {
772         if (this == other) {
773             return 0;
774         }
775 
776         int cmp = 0;
777 
778         // Language
779         cmp = getLanguage().compareTo(other.getLanguage());
780         if (cmp == 0) {
781             // Script
782             cmp = getScript().compareTo(other.getScript());
783             if (cmp == 0) {
784                 // Region
785                 cmp = getCountry().compareTo(other.getCountry());
786                 if (cmp == 0) {
787                     // Variant
788                     cmp = getVariant().compareTo(other.getVariant());
789                     if (cmp == 0) {
790                         // Keywords
791                         Iterator<String> thisKwdItr = getKeywords();
792                         Iterator<String> otherKwdItr = other.getKeywords();
793 
794                         if (thisKwdItr == null) {
795                             cmp = otherKwdItr == null ? 0 : -1;
796                         } else if (otherKwdItr == null) {
797                             cmp = 1;
798                         } else {
799                             // Both have keywords
800                             while (cmp == 0 && thisKwdItr.hasNext()) {
801                                 if (!otherKwdItr.hasNext()) {
802                                     cmp = 1;
803                                     break;
804                                 }
805                                 // Compare keyword keys
806                                 String thisKey = thisKwdItr.next();
807                                 String otherKey = otherKwdItr.next();
808                                 cmp = thisKey.compareTo(otherKey);
809                                 if (cmp == 0) {
810                                     // Compare keyword values
811                                     String thisVal = getKeywordValue(thisKey);
812                                     String otherVal = other.getKeywordValue(otherKey);
813                                     if (thisVal == null) {
814                                         cmp = otherVal == null ? 0 : -1;
815                                     } else if (otherVal == null) {
816                                         cmp = 1;
817                                     } else {
818                                         cmp = thisVal.compareTo(otherVal);
819                                     }
820                                 }
821                             }
822                             if (cmp == 0 && otherKwdItr.hasNext()) {
823                                 cmp = -1;
824                             }
825                         }
826                     }
827                 }
828             }
829         }
830 
831         // Normalize the result value:
832         // Note: String.compareTo() may return value other than -1, 0, 1.
833         // A value other than those are OK by the definition, but we don't want
834         // associate any semantics other than negative/zero/positive.
835         return (cmp < 0) ? -1 : ((cmp > 0) ? 1 : 0);
836     }
837 
838     /**
839      * {@icunote} Unlike the Locale API, this returns an array of <code>ULocale</code>,
840      * not <code>Locale</code>.
841      *
842      * <p>Returns a list of all installed locales. This is equivalent to calling
843      * {@link #getAvailableLocalesByType} with AvialableType.DEFAULT.
844      *
845      * @stable ICU 3.0
846      */
getAvailableLocales()847     public static ULocale[] getAvailableLocales() {
848         return ICUResourceBundle.getAvailableULocales().clone();
849     }
850 
851     /**
852      * Returns a list of all installed locales according to the specified type.
853      *
854      * @stable ICU 65
855      */
getAvailableLocalesByType(AvailableType type)856     public static Collection<ULocale> getAvailableLocalesByType(AvailableType type) {
857         if (type == null) {
858             throw new IllegalArgumentException();
859         }
860         List<ULocale> result;
861         if (type == ULocale.AvailableType.WITH_LEGACY_ALIASES) {
862             result = new ArrayList<>();
863             Collections.addAll(result,
864                     ICUResourceBundle.getAvailableULocales(ULocale.AvailableType.DEFAULT));
865             Collections.addAll(result,
866                     ICUResourceBundle.getAvailableULocales(ULocale.AvailableType.ONLY_LEGACY_ALIASES));
867         } else {
868             result = Arrays.asList(ICUResourceBundle.getAvailableULocales(type));
869         }
870         return Collections.unmodifiableList(result);
871     }
872 
873     /**
874      * Returns a list of all 2-letter country codes defined in ISO 3166.
875      * Can be used to create Locales.
876      * @stable ICU 3.0
877      */
getISOCountries()878     public static String[] getISOCountries() {
879         return LocaleIDs.getISOCountries();
880     }
881 
882     /**
883      * Returns a list of all 2-letter language codes defined in ISO 639.
884      * Can be used to create Locales.
885      * [NOTE:  ISO 639 is not a stable standard-- some languages' codes have changed.
886      * The list this function returns includes both the new and the old codes for the
887      * languages whose codes have changed.]
888      * @stable ICU 3.0
889      */
getISOLanguages()890     public static String[] getISOLanguages() {
891         return LocaleIDs.getISOLanguages();
892     }
893 
894     /**
895      * Returns the language code for this locale, which will either be the empty string
896      * or a lowercase ISO 639 code.
897      * @see #getDisplayLanguage()
898      * @see #getDisplayLanguage(ULocale)
899      * @stable ICU 3.0
900      */
getLanguage()901     public String getLanguage() {
902         return base().getLanguage();
903     }
904 
905     /**
906      * Returns the language code for the locale ID,
907      * which will either be the empty string
908      * or a lowercase ISO 639 code.
909      * @see #getDisplayLanguage()
910      * @see #getDisplayLanguage(ULocale)
911      * @stable ICU 3.0
912      */
getLanguage(String localeID)913     public static String getLanguage(String localeID) {
914         return new LocaleIDParser(localeID).getLanguage();
915     }
916 
917     /**
918      * Returns the script code for this locale, which might be the empty string.
919      * @see #getDisplayScript()
920      * @see #getDisplayScript(ULocale)
921      * @stable ICU 3.0
922      */
getScript()923     public String getScript() {
924         return base().getScript();
925     }
926 
927     /**
928      * {@icu} Returns the script code for the specified locale, which might be the empty
929      * string.
930      * @see #getDisplayScript()
931      * @see #getDisplayScript(ULocale)
932      * @stable ICU 3.0
933      */
getScript(String localeID)934     public static String getScript(String localeID) {
935         return new LocaleIDParser(localeID).getScript();
936     }
937 
938     /**
939      * Returns the country/region code for this locale, which will either be the empty string
940      * or an uppercase ISO 3166 2-letter code.
941      * @see #getDisplayCountry()
942      * @see #getDisplayCountry(ULocale)
943      * @stable ICU 3.0
944      */
getCountry()945     public String getCountry() {
946         return base().getRegion();
947     }
948 
949     /**
950      * {@icu} Returns the country/region code for this locale, which will either be the empty string
951      * or an uppercase ISO 3166 2-letter code.
952      * @param localeID The locale identification string.
953      * @see #getDisplayCountry()
954      * @see #getDisplayCountry(ULocale)
955      * @stable ICU 3.0
956      */
getCountry(String localeID)957     public static String getCountry(String localeID) {
958         return new LocaleIDParser(localeID).getCountry();
959     }
960 
961     /**
962      * {@icu} Get the region to use for supplemental data lookup.
963      * Uses
964      * (1) any region specified by locale tag "rg"; if none then
965      * (2) any unicode_region_tag in the locale ID; if none then
966      * (3) if inferRegion is TRUE, the region suggested by
967      *     getLikelySubtags on the localeID.
968      * If no region is found, returns empty string ""
969      *
970      * @param locale
971      *     The locale (includes any keywords) from which
972      *     to get the region to use for supplemental data.
973      * @param inferRegion
974      *     If TRUE, will try to infer region from other
975      *     locale elements if not found any other way.
976      * @return
977      *     String with region to use ("" if none found).
978      * @internal ICU 57
979      * @deprecated This API is ICU internal only.
980      */
981     @Deprecated
getRegionForSupplementalData( ULocale locale, boolean inferRegion)982     public static String getRegionForSupplementalData(
983                             ULocale locale, boolean inferRegion) {
984         String region = locale.getKeywordValue("rg");
985         if (region != null && region.length() == 6) {
986             String regionUpper = AsciiUtil.toUpperString(region);
987             if (regionUpper.endsWith("ZZZZ")) {
988             	return regionUpper.substring(0,2);
989             }
990         }
991         region = locale.getCountry();
992         if (region.length() == 0 && inferRegion) {
993             ULocale maximized = addLikelySubtags(locale);
994             region = maximized.getCountry();
995         }
996         return region;
997     }
998 
999     /**
1000      * Returns the variant code for this locale, which might be the empty string.
1001      * @see #getDisplayVariant()
1002      * @see #getDisplayVariant(ULocale)
1003      * @stable ICU 3.0
1004      */
getVariant()1005     public String getVariant() {
1006         return base().getVariant();
1007     }
1008 
1009     /**
1010      * {@icu} Returns the variant code for the specified locale, which might be the empty string.
1011      * @see #getDisplayVariant()
1012      * @see #getDisplayVariant(ULocale)
1013      * @stable ICU 3.0
1014      */
getVariant(String localeID)1015     public static String getVariant(String localeID) {
1016         return new LocaleIDParser(localeID).getVariant();
1017     }
1018 
1019     /**
1020      * {@icu} Returns the fallback locale for the specified locale, which might be the
1021      * empty string.
1022      * @stable ICU 3.2
1023      */
getFallback(String localeID)1024     public static String getFallback(String localeID) {
1025         return getFallbackString(getName(localeID));
1026     }
1027 
1028     /**
1029      * {@icu} Returns the fallback locale for this locale.  If this locale is root,
1030      * returns null.
1031      * @stable ICU 3.2
1032      */
getFallback()1033     public ULocale getFallback() {
1034         if (localeID.length() == 0 || localeID.charAt(0) == '@') {
1035             return null;
1036         }
1037         return new ULocale(getFallbackString(localeID), (Locale)null);
1038     }
1039 
1040     /**
1041      * Returns the given (canonical) locale id minus the last part before the tags.
1042      */
getFallbackString(String fallback)1043     private static String getFallbackString(String fallback) {
1044         int extStart = fallback.indexOf('@');
1045         if (extStart == -1) {
1046             extStart = fallback.length();
1047         }
1048         int last = fallback.lastIndexOf('_', extStart);
1049         if (last == -1) {
1050             last = 0;
1051         } else {
1052             // truncate empty segment
1053             while (last > 0) {
1054                 if (fallback.charAt(last - 1) != '_') {
1055                     break;
1056                 }
1057                 last--;
1058             }
1059         }
1060         return fallback.substring(0, last) + fallback.substring(extStart);
1061     }
1062 
1063     /**
1064      * {@icu} Returns the (normalized) base name for this locale,
1065      * like {@link #getName()}, but without keywords.
1066      *
1067      * @return the base name as a String.
1068      * @stable ICU 3.0
1069      */
getBaseName()1070     public String getBaseName() {
1071         return getBaseName(localeID);
1072     }
1073 
1074     /**
1075      * {@icu} Returns the (normalized) base name for the specified locale,
1076      * like {@link #getName(String)}, but without keywords.
1077      *
1078      * @param localeID the locale ID as a string
1079      * @return the base name as a String.
1080      * @stable ICU 3.0
1081      */
getBaseName(String localeID)1082     public static String getBaseName(String localeID){
1083         if (localeID.indexOf('@') == -1) {
1084             return localeID;
1085         }
1086         return new LocaleIDParser(localeID).getBaseName();
1087     }
1088 
1089     /**
1090      * {@icu} Returns the (normalized) full name for this locale.
1091      *
1092      * @return String the full name of the localeID
1093      * @stable ICU 3.0
1094      */
getName()1095     public String getName() {
1096         return localeID; // always normalized
1097     }
1098 
1099     /**
1100      * Gets the shortest length subtag's size.
1101      *
1102      * @param localeID
1103      * @return The size of the shortest length subtag
1104      **/
getShortestSubtagLength(String localeID)1105     private static int getShortestSubtagLength(String localeID) {
1106         int localeIDLength = localeID.length();
1107         int length = localeIDLength;
1108         boolean reset = true;
1109         int tmpLength = 0;
1110 
1111         for (int i = 0; i < localeIDLength; i++) {
1112             if (localeID.charAt(i) != '_' && localeID.charAt(i) != '-') {
1113                 if (reset) {
1114                     reset = false;
1115                     tmpLength = 0;
1116                 }
1117                 tmpLength++;
1118             } else {
1119                 if (tmpLength != 0 && tmpLength < length) {
1120                     length = tmpLength;
1121                 }
1122                 reset = true;
1123             }
1124         }
1125 
1126         return length;
1127     }
1128 
1129     /**
1130      * {@icu} Returns the (normalized) full name for the specified locale.
1131      *
1132      * @param localeID the localeID as a string
1133      * @return String the full name of the localeID
1134      * @stable ICU 3.0
1135      */
getName(String localeID)1136     public static String getName(String localeID){
1137         String tmpLocaleID;
1138         // Convert BCP47 id if necessary
1139         if (localeID != null && !localeID.contains("@") && getShortestSubtagLength(localeID) == 1) {
1140             tmpLocaleID = forLanguageTag(localeID).getName();
1141             if (tmpLocaleID.length() == 0) {
1142                 tmpLocaleID = localeID;
1143             }
1144         } else if ("root".equalsIgnoreCase(localeID)) {
1145             tmpLocaleID = EMPTY_STRING;
1146         } else {
1147             tmpLocaleID = UND_PATTERN.matcher(localeID).replaceFirst(EMPTY_STRING);
1148         }
1149         return nameCache.getInstance(tmpLocaleID, null /* unused */);
1150     }
1151 
1152     /**
1153      * Returns a string representation of this object.
1154      * @return a string representation of the object.
1155      * @stable ICU 2.8
1156      */
1157     @Override
toString()1158     public String toString() {
1159         return localeID;
1160     }
1161 
1162     /**
1163      * {@icu} Returns an iterator over keywords for this locale.  If there
1164      * are no keywords, returns null.
1165      * @return iterator over keywords, or null if there are no keywords.
1166      * @stable ICU 3.0
1167      */
getKeywords()1168     public Iterator<String> getKeywords() {
1169         return getKeywords(localeID);
1170     }
1171 
1172     /**
1173      * {@icu} Returns an iterator over keywords for the specified locale.  If there
1174      * are no keywords, returns null.
1175      * @return an iterator over the keywords in the specified locale, or null
1176      * if there are no keywords.
1177      * @stable ICU 3.0
1178      */
getKeywords(String localeID)1179     public static Iterator<String> getKeywords(String localeID){
1180         return new LocaleIDParser(localeID).getKeywords();
1181     }
1182 
1183     /**
1184      * {@icu} Returns the value for a keyword in this locale. If the keyword is not
1185      * defined, returns null.
1186      * @param keywordName name of the keyword whose value is desired. Case insensitive.
1187      * @return the value of the keyword, or null.
1188      * @stable ICU 3.0
1189      */
getKeywordValue(String keywordName)1190     public String getKeywordValue(String keywordName){
1191         return getKeywordValue(localeID, keywordName);
1192     }
1193 
1194     /**
1195      * {@icu} Returns the value for a keyword in the specified locale. If the keyword is
1196      * not defined, returns null.  The locale name does not need to be normalized.
1197      * @param keywordName name of the keyword whose value is desired. Case insensitive.
1198      * @return String the value of the keyword as a string
1199      * @stable ICU 3.0
1200      */
getKeywordValue(String localeID, String keywordName)1201     public static String getKeywordValue(String localeID, String keywordName) {
1202         return new LocaleIDParser(localeID).getKeywordValue(keywordName);
1203     }
1204 
1205     static private class AliasReplacer {
1206         /**
1207          * @param language language subtag to be replaced. Cannot be null but could be empty.
1208          * @param script script subtag to be replaced. Cannot be null but could be empty.
1209          * @param region region subtag to be replaced. Cannot be null but could be empty.
1210          * @param variants variant subtags to be replaced. Cannot be null but could be empty.
1211          * @param extensions extensions in string to be replaced. Cannot be null but could be empty.
1212          */
AliasReplacer(String language, String script, String region, String variants, String extensions)1213         public AliasReplacer(String language, String script, String region,
1214                 String variants, String extensions) {
1215 
1216             assert language != null;
1217             assert script != null;
1218             assert region != null;
1219             assert variants != null;
1220             assert extensions != null;
1221             this.language = language;
1222             this.script = script;
1223             this.region = region;
1224             if (!variants.isEmpty()) {
1225                 this.variants =
1226                     new ArrayList<>(Arrays.asList(variants.split("_")));
1227             }
1228             this.extensions = extensions;
1229         }
1230 
1231         private String language;
1232         private String script;
1233         private String region;
1234         private List<String> variants;
1235         private String extensions;
1236 
replace()1237         public String replace() {
1238             boolean changed = false;
1239             loadAliasData();
1240             int count = 0;
1241             while (true) {
1242                 if (count++ > 10) {
1243                     // Throw exception when we loop through too many time
1244                     // stop to avoid infinity loop cauesd by incorrect data
1245                     // in resource.
1246                     throw new IllegalArgumentException(
1247                         "Have problem to resolve locale alias of " +
1248                         lscvToID(language, script, region,
1249                             ((variants == null) ? "" : Utility.joinStrings("_", variants))) +
1250                         extensions);
1251                 }
1252                 // Anytime we replace something, we need to start over again.
1253                 //                      lang  REGION  variant
1254                 if (    replaceLanguage(true,  true,  true) ||
1255                         replaceLanguage(true,  true,  false) ||
1256                         replaceLanguage(true,  false, true) ||
1257                         replaceLanguage(true,  false, false) ||
1258                         replaceLanguage(false, false, true) ||
1259                         replaceRegion() ||
1260                         replaceScript() ||
1261                         replaceVariant()) {
1262                     // Some values in data is changed, try to match from the
1263                     // beginning again.
1264                     changed = true;
1265                     continue;
1266                 }
1267                 // Nothing changed in this iteration, break out the loop
1268                 break;
1269             }  // while(1)
1270             if (extensions == null && !changed) {
1271                 return null;
1272             }
1273             String result =  lscvToID(language, script, region,
1274                     ((variants == null) ? "" : Utility.joinStrings("_", variants)));
1275             if (extensions != null) {
1276                 boolean keywordChanged = false;
1277                 ULocale temp = new ULocale(result + extensions);
1278                 Iterator<String> keywords = temp.getKeywords();
1279                 while (keywords != null && keywords.hasNext()) {
1280                     String key = keywords.next();
1281                     if (key.equals("rg") || key.equals("sd") || key.equals("t")) {
1282                         String value = temp.getKeywordValue(key);
1283                         String replacement = key.equals("t") ?
1284                             replaceTransformedExtensions(value) :
1285                             replaceSubdivision(value);
1286                         if (replacement != null) {
1287                             temp = temp.setKeywordValue(key, replacement);
1288                             keywordChanged = true;
1289                         }
1290                     }
1291                 }
1292                 if (keywordChanged) {
1293                     extensions = temp.getName().substring(temp.getBaseName().length());
1294                     changed = true;
1295                 }
1296                 result += extensions;
1297             }
1298             if (changed) {
1299                 return result;
1300             }
1301             // Nothing changed in any iteration of the loop.
1302             return null;
1303         };
1304 
1305         private static boolean aliasDataIsLoaded = false;
1306         private static Map<String, String> languageAliasMap = null;
1307         private static Map<String, String> scriptAliasMap = null;
1308         private static Map<String, List<String>> territoryAliasMap = null;
1309         private static Map<String, String> variantAliasMap = null;
1310         private static Map<String, String> subdivisionAliasMap = null;
1311 
1312         /*
1313          * Initializes the alias data from the ICU resource bundles. The alias
1314          * data contains alias of language, country, script and variants.
1315          *
1316          * If the alias data has already loaded, then this method simply
1317          * returns without doing anything meaningful.
1318          *
1319          */
loadAliasData()1320         private static synchronized void loadAliasData() {
1321             if (aliasDataIsLoaded) {
1322                 return;
1323             }
1324             languageAliasMap = new HashMap<>();
1325             scriptAliasMap = new HashMap<>();
1326             territoryAliasMap = new HashMap<>();
1327             variantAliasMap = new HashMap<>();
1328             subdivisionAliasMap = new HashMap<>();
1329 
1330             UResourceBundle metadata = UResourceBundle.getBundleInstance(
1331                 ICUData.ICU_BASE_NAME, "metadata",
1332                 ICUResourceBundle.ICU_DATA_CLASS_LOADER);
1333             UResourceBundle metadataAlias = metadata.get("alias");
1334             UResourceBundle languageAlias = metadataAlias.get("language");
1335             UResourceBundle scriptAlias = metadataAlias.get("script");
1336             UResourceBundle territoryAlias = metadataAlias.get("territory");
1337             UResourceBundle variantAlias = metadataAlias.get("variant");
1338             UResourceBundle subdivisionAlias = metadataAlias.get("subdivision");
1339 
1340             for (int i = 0 ; i < languageAlias.getSize(); i++) {
1341                 UResourceBundle res = languageAlias.get(i);
1342                 String aliasFrom = res.getKey();
1343                 String aliasTo = res.get("replacement").getString();
1344                 Locale testLocale = new Locale(aliasFrom);
1345                 // if there are script in the aliasFrom
1346                 // or we have both a und as language and a region code.
1347                 if (    ! testLocale.getScript().isEmpty() ||
1348                         (aliasFrom.startsWith("und") && ! testLocale.getCountry().isEmpty())) {
1349                     throw new IllegalArgumentException(
1350                         "key [" + aliasFrom +
1351                         "] in alias:language contains unsupported fields combination.");
1352                 }
1353                 languageAliasMap.put(aliasFrom, aliasTo);
1354             }
1355             for (int i = 0 ; i < scriptAlias.getSize(); i++) {
1356                 UResourceBundle res = scriptAlias.get(i);
1357                 String aliasFrom = res.getKey();
1358                 String aliasTo = res.get("replacement").getString();
1359                 if (aliasFrom.length() != 4) {
1360                     throw new IllegalArgumentException(
1361                         "Incorrect key [" + aliasFrom + "] in alias:script.");
1362                 }
1363                 scriptAliasMap.put(aliasFrom, aliasTo);
1364             }
1365             for (int i = 0 ; i < territoryAlias.getSize(); i++) {
1366                 UResourceBundle res = territoryAlias.get(i);
1367                 String aliasFrom = res.getKey();
1368                 String aliasTo = res.get("replacement").getString();
1369                 if (aliasFrom.length() < 2 || aliasFrom.length() > 3) {
1370                     throw new IllegalArgumentException(
1371                         "Incorrect key [" + aliasFrom + "] in alias:territory.");
1372                 }
1373                 territoryAliasMap.put(aliasFrom,
1374                     new ArrayList<>(Arrays.asList(aliasTo.split(" "))));
1375             }
1376             for (int i = 0 ; i < variantAlias.getSize(); i++) {
1377                 UResourceBundle res = variantAlias.get(i);
1378                 String aliasFrom = res.getKey();
1379                 String aliasTo = res.get("replacement").getString();
1380                 if (    aliasFrom.length() < 4 ||
1381                         aliasFrom.length() > 8 ||
1382                         (aliasFrom.length() == 4 &&
1383                          (aliasFrom.charAt(0) < '0' || aliasFrom.charAt(0) > '9'))) {
1384                     throw new IllegalArgumentException(
1385                         "Incorrect key [" + aliasFrom + "] in alias:variant.");
1386                 }
1387                 if (    aliasTo.length() < 4 ||
1388                         aliasTo.length() > 8 ||
1389                         (aliasTo.length() == 4 &&
1390                          (aliasTo.charAt(0) < '0' || aliasTo.charAt(0) > '9'))) {
1391                     throw new IllegalArgumentException(
1392                         "Incorrect variant [" + aliasTo + "] for the key [" + aliasFrom +
1393                         "] in alias:variant.");
1394                 }
1395                 variantAliasMap.put(aliasFrom, aliasTo);
1396             }
1397             for (int i = 0 ; i < subdivisionAlias.getSize(); i++) {
1398                 UResourceBundle res = subdivisionAlias.get(i);
1399                 String aliasFrom = res.getKey();
1400                 String aliasTo = res.get("replacement").getString().split(" ")[0];
1401                 if (aliasFrom.length() < 3 || aliasFrom.length() > 8) {
1402                     throw new IllegalArgumentException(
1403                         "Incorrect key [" + aliasFrom + "] in alias:territory.");
1404                 }
1405                 if (aliasTo.length() == 2) {
1406                     // Add 'zzzz' based on changes to UTS #35 for CLDR-14312.
1407                     aliasTo += "zzzz";
1408                 } else if (aliasTo.length() < 2 || aliasTo.length() > 8) {
1409                     throw new IllegalArgumentException(
1410                         "Incorrect value [" + aliasTo + "] in alias:territory.");
1411                 }
1412                 subdivisionAliasMap.put(aliasFrom, aliasTo);
1413             }
1414 
1415             aliasDataIsLoaded = true;
1416         }
1417 
generateKey( String language, String region, String variant)1418         private static String generateKey(
1419                 String language, String region, String variant) {
1420             assert variant == null || variant.length() >= 4;
1421             StringBuilder buf = new StringBuilder();
1422             buf.append(language);
1423             if (region != null && !region.isEmpty()) {
1424                 buf.append(UNDERSCORE);
1425                 buf.append(region);
1426             }
1427             if (variant != null && !variant.isEmpty()) {
1428                 buf.append(UNDERSCORE);
1429                 buf.append(variant);
1430             }
1431             return buf.toString();
1432         }
1433 
1434         /**
1435          * If replacement is neither null nor empty and input is either null or empty,
1436          * return replacement.
1437          * If replacement is neither null nor empty but input is not empty, return input.
1438          * If replacement is either null or empty and type is either null or empty,
1439          * return input.
1440          * Otherwise return null.
1441          *   replacement  input  type   return
1442          *       AAA       ""     *      AAA
1443          *       AAA       BBB    *      BBB
1444          *       ""        CCC    ""     CCC
1445          *       ""        *  i   DDD    ""
1446          */
deleteOrReplace( String input, String type, String replacement)1447         private static String deleteOrReplace(
1448                 String input, String type, String replacement) {
1449             return (replacement != null && !replacement.isEmpty()) ?
1450                     ((input == null || input.isEmpty()) ? replacement : input) :
1451                     ((type == null || type.isEmpty()) ? input : null);
1452         }
1453 
replaceLanguage(boolean checkLanguage, boolean checkRegion, boolean checkVariants)1454         private boolean replaceLanguage(boolean checkLanguage,
1455                 boolean checkRegion, boolean checkVariants) {
1456             if (    (checkRegion && (region == null || region.isEmpty())) ||
1457                     (checkVariants && (variants == null))) {
1458                 // Nothing to search
1459                 return false;
1460             }
1461             int variantSize = checkVariants ? variants.size() : 1;
1462             // Since we may have more than one variant, we need to loop through
1463             // them.
1464             String searchLanguage = checkLanguage ? language : UNDEFINED_LANGUAGE;
1465             String searchRegion = checkRegion ? region : null;
1466             String searchVariant = null;
1467             for (int variantIndex = 0; variantIndex < variantSize; ++variantIndex) {
1468                 if (checkVariants) {
1469                     searchVariant = variants.get(variantIndex);
1470                 }
1471                 if (searchVariant != null && searchVariant.length() < 4) {
1472                     // Do not consider  ill-formed variant subtag.
1473                     searchVariant = null;
1474                 }
1475                 String typeKey = generateKey(
1476                     searchLanguage, searchRegion, searchVariant);
1477                 String replacement = languageAliasMap.get(typeKey);
1478                 if (replacement == null) {
1479                     // Found no replacement data.
1480                     continue;
1481                 }
1482                 String replacedScript = null;
1483                 String replacedRegion = null;
1484                 String replacedVariant = null;
1485                 String replacedExtensions = null;
1486                 String replacedLanguage = null;
1487 
1488                 if (replacement.indexOf('_') < 0) {
1489                     replacedLanguage = replacement.equals(UNDEFINED_LANGUAGE) ?
1490                         language : replacement;
1491                 } else {
1492                     String[] replacementFields = replacement.split("_");
1493                     replacedLanguage = replacementFields[0];
1494                     int index = 1;
1495 
1496                     if (replacedLanguage.equals(UNDEFINED_LANGUAGE)) {
1497                         replacedLanguage = language;
1498                     }
1499                     int consumed = replacementFields[0].length() + 1;
1500                     while (replacementFields.length > index) {
1501                         String field = replacementFields[index];
1502                         int len = field.length();
1503                         if (1 == len) {
1504                             replacedExtensions = replacement.substring(consumed);
1505                             break;
1506                         } else if (len >= 2 && len <= 3) {
1507                             assert replacedRegion == null;
1508                             replacedRegion = field;
1509                         } else if (len >= 5 && len <= 8) {
1510                             assert replacedVariant == null;
1511                             replacedVariant = field;
1512                         } else if (len == 4) {
1513                             if (field.charAt(0) >= '0' && field.charAt(0) <= '9') {
1514                                 assert replacedVariant == null;
1515                                 replacedVariant = field;
1516                             } else {
1517                                 assert replacedScript == null;
1518                                 replacedScript = field;
1519                             }
1520                         }
1521                         index++;
1522                         consumed += len + 1;
1523                     }
1524                 }
1525 
1526                 replacedScript = deleteOrReplace(script, null, replacedScript);
1527                 replacedRegion = deleteOrReplace(region, searchRegion, replacedRegion);
1528                 replacedVariant = deleteOrReplace(searchVariant, searchVariant, replacedVariant);
1529 
1530                 if (    this.language.equals(replacedLanguage) &&
1531                         this.script.equals(replacedScript) &&
1532                         this.region.equals(replacedRegion) &&
1533                         Objects.equals(searchVariant, replacedVariant) &&
1534                         replacedExtensions == null) {
1535                     // Replacement produce no changes on search.
1536                     // For example, apply pa_IN=> pa_Guru_IN on pa_Guru_IN.
1537                     continue;
1538                 }
1539                 this.language = replacedLanguage;
1540                 this.script = replacedScript;
1541                 this.region = replacedRegion;
1542                 if (searchVariant != null && !searchVariant.isEmpty()) {
1543                     if (replacedVariant != null && !replacedVariant.isEmpty()) {
1544                         this.variants.set(variantIndex, replacedVariant);
1545                     } else {
1546                         this.variants.remove(variantIndex);
1547                         if (this.variants.isEmpty()) {
1548                             this.variants = null;
1549                         }
1550                     }
1551                 }
1552                 if (replacedExtensions != null && !replacedExtensions.isEmpty()) {
1553                     // DO NOTHING
1554                     // UTS35 does not specifiy what should we do if we have extensions in the
1555                     // replacement. Currently we know only the following 4 "BCP47 LegacyRules" have
1556                     // extensions in them languageAlias:
1557                     //  i_default => en_x_i_default
1558                     //  i_enochian => und_x_i_enochian
1559                     //  i_mingo => see_x_i_mingo
1560                     //  zh_min => nan_x_zh_min
1561                     // But all of them are already changed by code inside LanguageTag before
1562                     // hitting this code.
1563                 }
1564                 // Something in search changed by language alias data.
1565                 return true;
1566             }
1567             // Nothing changed in search by language alias data.
1568             return false;
1569         }
1570 
replaceRegion()1571         private boolean replaceRegion() {
1572             if (region == null || region.isEmpty()) return false;
1573             List<String> replacement = territoryAliasMap.get(region);
1574             if (replacement == null) {
1575                 // Found no replacement data for this region.
1576                 return false;
1577             }
1578             String replacedRegion;
1579             if (replacement.size() > 1) {
1580                 String regionOfLanguageAndScript =
1581                     ULocale.addLikelySubtags(
1582                         new ULocale(this.language, this.script, null))
1583                     .getCountry();
1584                 replacedRegion = replacement.contains(regionOfLanguageAndScript) ?
1585                     regionOfLanguageAndScript : replacement.get(0);
1586             } else {
1587                 replacedRegion = replacement.get(0);
1588             }
1589             assert !this.region.equals(replacedRegion);
1590             this.region = replacedRegion;
1591             // The region is changed by data in territory alias.
1592             return true;
1593         }
1594 
replaceScript()1595         private boolean replaceScript() {
1596             if (script == null || script.isEmpty()) return false;
1597             String replacement = scriptAliasMap.get(script);
1598             if (replacement == null) {
1599                 // Found no replacement data for this script.
1600                 return false;
1601             }
1602             assert !this.script.equals(replacement);
1603             this.script = replacement;
1604             // The script is changed by data in script alias.
1605             return true;
1606         }
1607 
replaceVariant()1608         private boolean replaceVariant() {
1609             if (variants == null) return false;
1610             for (int i = 0; i < variants.size(); i++) {
1611                  String variant = variants.get(i);
1612                  String replacement = variantAliasMap.get(variant);
1613                  if (replacement == null) {
1614                      // Found no replacement data for this variant.
1615                      continue;
1616                  }
1617                  assert replacement.length() >= 4;
1618                  assert replacement.length() <= 8;
1619                  assert replacement.length() != 4 ||
1620                      ( replacement.charAt(0) >= '0' && replacement.charAt(0) <= '9');
1621                  if (!variant.equals(replacement)) {
1622                      variants.set(i, replacement);
1623                      // Special hack to handle hepburn-heploc => alalc97
1624                      if (variant.equals("heploc")) {
1625                          variants.remove("hepburn");
1626                          if (variants.isEmpty()) {
1627                              variants = null;
1628                          }
1629                      }
1630                      return true;
1631                  }
1632             }
1633             return false;
1634         }
1635 
replaceSubdivision(String subdivision)1636         private String replaceSubdivision(String subdivision) {
1637             return subdivisionAliasMap.get(subdivision);
1638         }
1639 
replaceTransformedExtensions(String extensions)1640         private String replaceTransformedExtensions(String extensions) {
1641             StringBuilder builder = new StringBuilder();
1642             List<String> subtags = new ArrayList<>(Arrays.asList(extensions.split(LanguageTag.SEP)));
1643             List<String> tfields = new ArrayList<>();
1644             int processedLength = 0;
1645             int tlangLength = 0;
1646             String tkey = "";
1647             for (String subtag : subtags) {
1648                 if (LanguageTag.isTKey(subtag)) {
1649                     if (tlangLength == 0) {
1650                         // Found the first tkey. Record the total length of the preceding
1651                         // tlang subtags. -1 if there is no tlang before the first tkey.
1652                         tlangLength = processedLength-1;
1653                     }
1654                     if (builder.length() > 0) {
1655                         // Finish & store the previous tkey with its tvalue subtags.
1656                         tfields.add(builder.toString());
1657                         builder.setLength(0);
1658                     }
1659                     // Start collecting subtags for this new tkey.
1660                     tkey = subtag;
1661                     builder.append(subtag);
1662                 } else {
1663                     if (tlangLength != 0) {
1664                         builder.append(LanguageTag.SEP).append(toUnicodeLocaleType(tkey, subtag));
1665                     }
1666                 }
1667                 processedLength += subtag.length() + 1;
1668             }
1669             if (builder.length() > 0) {
1670                 // Finish & store the previous=last tkey with its tvalue subtags.
1671                 tfields.add(builder.toString());
1672                 builder.setLength(0);
1673             }
1674             String tlang = (tlangLength > 0) ? extensions.substring(0, tlangLength) :
1675                 ((tfields.size() == 0) ? extensions :  "");
1676             if (tlang.length() > 0) {
1677                 String canonicalized = ULocale.createCanonical(
1678                     ULocale.forLanguageTag(extensions)).toLanguageTag();
1679                 builder.append(AsciiUtil.toLowerString(canonicalized));
1680             }
1681 
1682             if (tfields.size() > 0) {
1683                 if (builder.length() > 0) {
1684                     builder.append(LanguageTag.SEP);
1685                 }
1686                 // tfields are sorted by alphabetical order of their keys
1687                 Collections.sort(tfields);
1688                 builder.append(Utility.joinStrings(LanguageTag.SEP, tfields));
1689             }
1690             return builder.toString();
1691         }
1692     };
1693 
1694     /**
1695      * {@icu} Returns the canonical name according to CLDR for the specified locale ID.
1696      * This is used to convert POSIX and other legacy IDs to standard ICU form.
1697      * @param localeID the locale id
1698      * @return the canonicalized id
1699      * @stable ICU 3.0
1700      */
canonicalize(String localeID)1701     public static String canonicalize(String localeID){
1702         LocaleIDParser parser = new LocaleIDParser(localeID, true);
1703         String baseName = parser.getBaseName();
1704         boolean foundVariant = false;
1705 
1706         if (localeID.equals("")) {
1707             return "";
1708         }
1709 
1710         // we have an ID in the form xx_Yyyy_ZZ_KKKKK
1711 
1712         /* See if this is an already known locale */
1713         for (int i = 0; i < CANONICALIZE_MAP.length; i++) {
1714             String[] vals = CANONICALIZE_MAP[i];
1715             if (vals[0].equals(baseName)) {
1716                 foundVariant = true;
1717 
1718                 parser.setBaseName(vals[1]);
1719                 break;
1720             }
1721         }
1722 
1723         /* total mondo hack for Norwegian, fortunately the main NY case is handled earlier */
1724         if (!foundVariant) {
1725             if (parser.getLanguage().equals("nb") && parser.getVariant().equals("NY")) {
1726                 parser.setBaseName(lscvToID("nn", parser.getScript(), parser.getCountry(), null));
1727             }
1728         }
1729 
1730         String name = parser.getName();
1731         if (!isKnownCanonicalizedLocale(name)) {
1732             AliasReplacer replacer = new AliasReplacer(
1733                 parser.getLanguage(), parser.getScript(), parser.getCountry(),
1734                 AsciiUtil.toLowerString(parser.getVariant()),
1735                 parser.getName().substring(parser.getBaseName().length()));
1736             String replaced = replacer.replace();
1737             if (replaced != null) {
1738                 parser =  new LocaleIDParser(replaced);
1739             }
1740         }
1741 
1742         return parser.getName();
1743     }
1744 
isKnownCanonicalizedLocale(String name)1745     private static synchronized boolean isKnownCanonicalizedLocale(String name) {
1746         if (name.equals("c") || name.equals("en") || name.equals("en_US")) {
1747             return true;
1748         }
1749         if (gKnownCanonicalizedCases == null) {
1750             List<String> items = Arrays.asList(
1751                 "af", "af_ZA", "am", "am_ET", "ar", "ar_001", "as", "as_IN", "az", "az_AZ",
1752                 "be", "be_BY", "bg", "bg_BG", "bn", "bn_IN", "bs", "bs_BA", "ca", "ca_ES",
1753                 "cs", "cs_CZ", "cy", "cy_GB", "da", "da_DK", "de", "de_DE", "el", "el_GR",
1754                 "en", "en_GB", "en_US", "es", "es_419", "es_ES", "et", "et_EE", "eu",
1755                 "eu_ES", "fa", "fa_IR", "fi", "fi_FI", "fil", "fil_PH", "fr", "fr_FR",
1756                 "ga", "ga_IE", "gl", "gl_ES", "gu", "gu_IN", "he", "he_IL", "hi", "hi_IN",
1757                 "hr", "hr_HR", "hu", "hu_HU", "hy", "hy_AM", "id", "id_ID", "is", "is_IS",
1758                 "it", "it_IT", "ja", "ja_JP", "jv", "jv_ID", "ka", "ka_GE", "kk", "kk_KZ",
1759                 "km", "km_KH", "kn", "kn_IN", "ko", "ko_KR", "ky", "ky_KG", "lo", "lo_LA",
1760                 "lt", "lt_LT", "lv", "lv_LV", "mk", "mk_MK", "ml", "ml_IN", "mn", "mn_MN",
1761                 "mr", "mr_IN", "ms", "ms_MY", "my", "my_MM", "nb", "nb_NO", "ne", "ne_NP",
1762                 "nl", "nl_NL", "no", "or", "or_IN", "pa", "pa_IN", "pl", "pl_PL", "ps", "ps_AF",
1763                 "pt", "pt_BR", "pt_PT", "ro", "ro_RO", "ru", "ru_RU", "sd", "sd_IN", "si",
1764                 "si_LK", "sk", "sk_SK", "sl", "sl_SI", "so", "so_SO", "sq", "sq_AL", "sr",
1765                 "sr_Cyrl_RS", "sr_Latn", "sr_RS", "sv", "sv_SE", "sw", "sw_TZ", "ta",
1766                 "ta_IN", "te", "te_IN", "th", "th_TH", "tk", "tk_TM", "tr", "tr_TR", "uk",
1767                 "uk_UA", "ur", "ur_PK", "uz", "uz_UZ", "vi", "vi_VN", "yue", "yue_Hant",
1768                 "yue_Hant_HK", "yue_HK", "zh", "zh_CN", "zh_Hans", "zh_Hans_CN", "zh_Hant",
1769                 "zh_Hant_TW", "zh_TW", "zu", "zu_ZA");
1770             gKnownCanonicalizedCases = new HashSet<>(items);
1771 
1772         }
1773         return gKnownCanonicalizedCases.contains(name);
1774     }
1775 
1776     private static Set<String> gKnownCanonicalizedCases = null;
1777 
1778     /**
1779      * {@icu} Given a keyword and a value, return a new locale with an updated
1780      * keyword and value.  If the keyword is null, this removes all keywords from the locale id.
1781      * Otherwise, if the value is null, this removes the value for this keyword from the
1782      * locale id.  Otherwise, this adds/replaces the value for this keyword in the locale id.
1783      * The keyword and value must not be empty.
1784      *
1785      * <p>Related: {@link #getBaseName()} returns the locale ID string with all keywords removed.
1786      *
1787      * @param keyword the keyword to add/remove, or null to remove all keywords.
1788      * @param value the value to add/set, or null to remove this particular keyword.
1789      * @return the updated locale
1790      * @stable ICU 3.2
1791      */
setKeywordValue(String keyword, String value)1792     public ULocale setKeywordValue(String keyword, String value) {
1793         return new ULocale(setKeywordValue(localeID, keyword, value), (Locale)null);
1794     }
1795 
1796     /**
1797      * Given a locale id, a keyword, and a value, return a new locale id with an updated
1798      * keyword and value.  If the keyword is null, this removes all keywords from the locale id.
1799      * Otherwise, if the value is null, this removes the value for this keyword from the
1800      * locale id.  Otherwise, this adds/replaces the value for this keyword in the locale id.
1801      * The keyword and value must not be empty.
1802      *
1803      * <p>Related: {@link #getBaseName(String)} returns the locale ID string with all keywords removed.
1804      *
1805      * @param localeID the locale id to modify
1806      * @param keyword the keyword to add/remove, or null to remove all keywords.
1807      * @param value the value to add/set, or null to remove this particular keyword.
1808      * @return the updated locale id
1809      * @stable ICU 3.2
1810      */
setKeywordValue(String localeID, String keyword, String value)1811     public static String setKeywordValue(String localeID, String keyword, String value) {
1812         LocaleIDParser parser = new LocaleIDParser(localeID);
1813         parser.setKeywordValue(keyword, value);
1814         return parser.getName();
1815     }
1816 
1817     /*
1818      * Given a locale id, a keyword, and a value, return a new locale id with an updated
1819      * keyword and value, if the keyword does not already have a value.  The keyword and
1820      * value must not be null or empty.
1821      * @param localeID the locale id to modify
1822      * @param keyword the keyword to add, if not already present
1823      * @param value the value to add, if not already present
1824      * @return the updated locale id
1825      */
1826     /*    private static String defaultKeywordValue(String localeID, String keyword, String value) {
1827         LocaleIDParser parser = new LocaleIDParser(localeID);
1828         parser.defaultKeywordValue(keyword, value);
1829         return parser.getName();
1830     }*/
1831 
1832     /**
1833      * Returns a three-letter abbreviation for this locale's language.  If the locale
1834      * doesn't specify a language, returns the empty string.  Otherwise, returns
1835      * a lowercase ISO 639-2/T language code.
1836      * The ISO 639-2 language codes can be found on-line at
1837      *   <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
1838      * @exception MissingResourceException Throws MissingResourceException if the
1839      * three-letter language abbreviation is not available for this locale.
1840      * @stable ICU 3.0
1841      */
getISO3Language()1842     public String getISO3Language(){
1843         return getISO3Language(localeID);
1844     }
1845 
1846     /**
1847      * {@icu} Returns a three-letter abbreviation for this locale's language.  If the locale
1848      * doesn't specify a language, returns the empty string.  Otherwise, returns
1849      * a lowercase ISO 639-2/T language code.
1850      * The ISO 639-2 language codes can be found on-line at
1851      *   <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
1852      * @exception MissingResourceException Throws MissingResourceException if the
1853      * three-letter language abbreviation is not available for this locale.
1854      * @stable ICU 3.0
1855      */
getISO3Language(String localeID)1856     public static String getISO3Language(String localeID) {
1857         return LocaleIDs.getISO3Language(getLanguage(localeID));
1858     }
1859 
1860     /**
1861      * Returns a three-letter abbreviation for this locale's country/region.  If the locale
1862      * doesn't specify a country, returns the empty string.  Otherwise, returns
1863      * an uppercase ISO 3166 3-letter country code.
1864      * @exception MissingResourceException Throws MissingResourceException if the
1865      * three-letter country abbreviation is not available for this locale.
1866      * @stable ICU 3.0
1867      */
getISO3Country()1868     public String getISO3Country() {
1869         return getISO3Country(localeID);
1870     }
1871 
1872     /**
1873      * {@icu} Returns a three-letter abbreviation for this locale's country/region.  If the locale
1874      * doesn't specify a country, returns the empty string.  Otherwise, returns
1875      * an uppercase ISO 3166 3-letter country code.
1876      * @exception MissingResourceException Throws MissingResourceException if the
1877      * three-letter country abbreviation is not available for this locale.
1878      * @stable ICU 3.0
1879      */
getISO3Country(String localeID)1880     public static String getISO3Country(String localeID) {
1881         return LocaleIDs.getISO3Country(getCountry(localeID));
1882     }
1883 
1884     /**
1885      * Pairs of (language subtag, + or -) for finding out fast if common languages
1886      * are LTR (minus) or RTL (plus).
1887      */
1888     private static final String LANG_DIR_STRING =
1889             "root-en-es-pt-zh-ja-ko-de-fr-it-ar+he+fa+ru-nl-pl-th-tr-";
1890 
1891     /**
1892      * {@icu} Returns whether this locale's script is written right-to-left.
1893      * If there is no script subtag, then the likely script is used,
1894      * see {@link #addLikelySubtags(ULocale)}.
1895      * If no likely script is known, then false is returned.
1896      *
1897      * <p>A script is right-to-left according to the CLDR script metadata
1898      * which corresponds to whether the script's letters have Bidi_Class=R or AL.
1899      *
1900      * <p>Returns true for "ar" and "en-Hebr", false for "zh" and "fa-Cyrl".
1901      *
1902      * @return true if the locale's script is written right-to-left
1903      * @stable ICU 54
1904      */
isRightToLeft()1905     public boolean isRightToLeft() {
1906         String script = getScript();
1907         if (script.length() == 0) {
1908             // Fastpath: We know the likely scripts and their writing direction
1909             // for some common languages.
1910             String lang = getLanguage();
1911             if (!lang.isEmpty()) {
1912                 int langIndex = LANG_DIR_STRING.indexOf(lang);
1913                 if (langIndex >= 0) {
1914                     switch (LANG_DIR_STRING.charAt(langIndex + lang.length())) {
1915                     case '-': return false;
1916                     case '+': return true;
1917                     default: break;  // partial match of a longer code
1918                     }
1919                 }
1920             }
1921             // Otherwise, find the likely script.
1922             ULocale likely = addLikelySubtags(this);
1923             script = likely.getScript();
1924             if (script.length() == 0) {
1925                 return false;
1926             }
1927         }
1928         int scriptCode = UScript.getCodeFromName(script);
1929         return UScript.isRightToLeft(scriptCode);
1930     }
1931 
1932     // display names
1933 
1934     /**
1935      * Returns this locale's language localized for display in the default <code>DISPLAY</code> locale.
1936      * @return the localized language name.
1937      * @see Category#DISPLAY
1938      * @stable ICU 3.0
1939      */
getDisplayLanguage()1940     public String getDisplayLanguage() {
1941         return getDisplayLanguageInternal(this, getDefault(Category.DISPLAY), false);
1942     }
1943 
1944     /**
1945      * Returns this locale's language localized for display in the provided locale.
1946      * @param displayLocale the locale in which to display the name.
1947      * @return the localized language name.
1948      * @stable ICU 3.0
1949      */
getDisplayLanguage(ULocale displayLocale)1950     public String getDisplayLanguage(ULocale displayLocale) {
1951         return getDisplayLanguageInternal(this, displayLocale, false);
1952     }
1953 
1954     /**
1955      * {@icu} Returns a locale's language localized for display in the provided locale.
1956      * This is a cover for the ICU4C API.
1957      * @param localeID the id of the locale whose language will be displayed
1958      * @param displayLocaleID the id of the locale in which to display the name.
1959      * @return the localized language name.
1960      * @stable ICU 3.0
1961      */
getDisplayLanguage(String localeID, String displayLocaleID)1962     public static String getDisplayLanguage(String localeID, String displayLocaleID) {
1963         return getDisplayLanguageInternal(new ULocale(localeID), new ULocale(displayLocaleID),
1964                 false);
1965     }
1966 
1967     /**
1968      * {@icu} Returns a locale's language localized for display in the provided locale.
1969      * This is a cover for the ICU4C API.
1970      * @param localeID the id of the locale whose language will be displayed.
1971      * @param displayLocale the locale in which to display the name.
1972      * @return the localized language name.
1973      * @stable ICU 3.0
1974      */
getDisplayLanguage(String localeID, ULocale displayLocale)1975     public static String getDisplayLanguage(String localeID, ULocale displayLocale) {
1976         return getDisplayLanguageInternal(new ULocale(localeID), displayLocale, false);
1977     }
1978     /**
1979      * {@icu} Returns this locale's language localized for display in the default <code>DISPLAY</code> locale.
1980      * If a dialect name is present in the data, then it is returned.
1981      * @return the localized language name.
1982      * @see Category#DISPLAY
1983      * @stable ICU 4.4
1984      */
getDisplayLanguageWithDialect()1985     public String getDisplayLanguageWithDialect() {
1986         return getDisplayLanguageInternal(this, getDefault(Category.DISPLAY), true);
1987     }
1988 
1989     /**
1990      * {@icu} Returns this locale's language localized for display in the provided locale.
1991      * If a dialect name is present in the data, then it is returned.
1992      * @param displayLocale the locale in which to display the name.
1993      * @return the localized language name.
1994      * @stable ICU 4.4
1995      */
getDisplayLanguageWithDialect(ULocale displayLocale)1996     public String getDisplayLanguageWithDialect(ULocale displayLocale) {
1997         return getDisplayLanguageInternal(this, displayLocale, true);
1998     }
1999 
2000     /**
2001      * {@icu} Returns a locale's language localized for display in the provided locale.
2002      * If a dialect name is present in the data, then it is returned.
2003      * This is a cover for the ICU4C API.
2004      * @param localeID the id of the locale whose language will be displayed
2005      * @param displayLocaleID the id of the locale in which to display the name.
2006      * @return the localized language name.
2007      * @stable ICU 4.4
2008      */
getDisplayLanguageWithDialect(String localeID, String displayLocaleID)2009     public static String getDisplayLanguageWithDialect(String localeID, String displayLocaleID) {
2010         return getDisplayLanguageInternal(new ULocale(localeID), new ULocale(displayLocaleID),
2011                 true);
2012     }
2013 
2014     /**
2015      * {@icu} Returns a locale's language localized for display in the provided locale.
2016      * If a dialect name is present in the data, then it is returned.
2017      * This is a cover for the ICU4C API.
2018      * @param localeID the id of the locale whose language will be displayed.
2019      * @param displayLocale the locale in which to display the name.
2020      * @return the localized language name.
2021      * @stable ICU 4.4
2022      */
getDisplayLanguageWithDialect(String localeID, ULocale displayLocale)2023     public static String getDisplayLanguageWithDialect(String localeID, ULocale displayLocale) {
2024         return getDisplayLanguageInternal(new ULocale(localeID), displayLocale, true);
2025     }
2026 
getDisplayLanguageInternal(ULocale locale, ULocale displayLocale, boolean useDialect)2027     private static String getDisplayLanguageInternal(ULocale locale, ULocale displayLocale,
2028             boolean useDialect) {
2029         String lang = useDialect ? locale.getBaseName() : locale.getLanguage();
2030         return LocaleDisplayNames.getInstance(displayLocale).languageDisplayName(lang);
2031     }
2032 
2033     /**
2034      * Returns this locale's script localized for display in the default <code>DISPLAY</code> locale.
2035      * @return the localized script name.
2036      * @see Category#DISPLAY
2037      * @stable ICU 3.0
2038      */
getDisplayScript()2039     public String getDisplayScript() {
2040         return getDisplayScriptInternal(this, getDefault(Category.DISPLAY));
2041     }
2042 
2043     /**
2044      * {@icu} Returns this locale's script localized for display in the default <code>DISPLAY</code> locale.
2045      * @return the localized script name.
2046      * @see Category#DISPLAY
2047      * @internal
2048      * @deprecated This API is ICU internal only.
2049      */
2050     @Deprecated
getDisplayScriptInContext()2051     public String getDisplayScriptInContext() {
2052         return getDisplayScriptInContextInternal(this, getDefault(Category.DISPLAY));
2053     }
2054 
2055     /**
2056      * Returns this locale's script localized for display in the provided locale.
2057      * @param displayLocale the locale in which to display the name.
2058      * @return the localized script name.
2059      * @stable ICU 3.0
2060      */
getDisplayScript(ULocale displayLocale)2061     public String getDisplayScript(ULocale displayLocale) {
2062         return getDisplayScriptInternal(this, displayLocale);
2063     }
2064 
2065     /**
2066      * {@icu} Returns this locale's script localized for display in the provided locale.
2067      * @param displayLocale the locale in which to display the name.
2068      * @return the localized script name.
2069      * @internal
2070      * @deprecated This API is ICU internal only.
2071      */
2072     @Deprecated
getDisplayScriptInContext(ULocale displayLocale)2073     public String getDisplayScriptInContext(ULocale displayLocale) {
2074         return getDisplayScriptInContextInternal(this, displayLocale);
2075     }
2076 
2077     /**
2078      * {@icu} Returns a locale's script localized for display in the provided locale.
2079      * This is a cover for the ICU4C API.
2080      * @param localeID the id of the locale whose script will be displayed
2081      * @param displayLocaleID the id of the locale in which to display the name.
2082      * @return the localized script name.
2083      * @stable ICU 3.0
2084      */
getDisplayScript(String localeID, String displayLocaleID)2085     public static String getDisplayScript(String localeID, String displayLocaleID) {
2086         return getDisplayScriptInternal(new ULocale(localeID), new ULocale(displayLocaleID));
2087     }
2088     /**
2089      * {@icu} Returns a locale's script localized for display in the provided locale.
2090      * This is a cover for the ICU4C API.
2091      * @param localeID the id of the locale whose script will be displayed
2092      * @param displayLocaleID the id of the locale in which to display the name.
2093      * @return the localized script name.
2094      * @internal
2095      * @deprecated This API is ICU internal only.
2096      */
2097     @Deprecated
getDisplayScriptInContext(String localeID, String displayLocaleID)2098     public static String getDisplayScriptInContext(String localeID, String displayLocaleID) {
2099         return getDisplayScriptInContextInternal(new ULocale(localeID), new ULocale(displayLocaleID));
2100     }
2101 
2102     /**
2103      * {@icu} Returns a locale's script localized for display in the provided locale.
2104      * @param localeID the id of the locale whose script will be displayed.
2105      * @param displayLocale the locale in which to display the name.
2106      * @return the localized script name.
2107      * @stable ICU 3.0
2108      */
getDisplayScript(String localeID, ULocale displayLocale)2109     public static String getDisplayScript(String localeID, ULocale displayLocale) {
2110         return getDisplayScriptInternal(new ULocale(localeID), displayLocale);
2111     }
2112     /**
2113      * {@icu} Returns a locale's script localized for display in the provided locale.
2114      * @param localeID the id of the locale whose script will be displayed.
2115      * @param displayLocale the locale in which to display the name.
2116      * @return the localized script name.
2117      * @internal
2118      * @deprecated This API is ICU internal only.
2119      */
2120     @Deprecated
getDisplayScriptInContext(String localeID, ULocale displayLocale)2121     public static String getDisplayScriptInContext(String localeID, ULocale displayLocale) {
2122         return getDisplayScriptInContextInternal(new ULocale(localeID), displayLocale);
2123     }
2124 
2125     // displayLocaleID is canonical, localeID need not be since parsing will fix this.
getDisplayScriptInternal(ULocale locale, ULocale displayLocale)2126     private static String getDisplayScriptInternal(ULocale locale, ULocale displayLocale) {
2127         return LocaleDisplayNames.getInstance(displayLocale)
2128                 .scriptDisplayName(locale.getScript());
2129     }
2130 
getDisplayScriptInContextInternal(ULocale locale, ULocale displayLocale)2131     private static String getDisplayScriptInContextInternal(ULocale locale, ULocale displayLocale) {
2132         return LocaleDisplayNames.getInstance(displayLocale)
2133                 .scriptDisplayNameInContext(locale.getScript());
2134     }
2135 
2136     /**
2137      * Returns this locale's country localized for display in the default <code>DISPLAY</code> locale.
2138      * <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
2139      * To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
2140      * @return the localized country name.
2141      * @see Category#DISPLAY
2142      * @stable ICU 3.0
2143      */
getDisplayCountry()2144     public String getDisplayCountry() {
2145         return getDisplayCountryInternal(this, getDefault(Category.DISPLAY));
2146     }
2147 
2148     /**
2149      * Returns this locale's country localized for display in the provided locale.
2150      * <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
2151      * To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
2152      * @param displayLocale the locale in which to display the name.
2153      * @return the localized country name.
2154      * @stable ICU 3.0
2155      */
getDisplayCountry(ULocale displayLocale)2156     public String getDisplayCountry(ULocale displayLocale){
2157         return getDisplayCountryInternal(this, displayLocale);
2158     }
2159 
2160     /**
2161      * {@icu} Returns a locale's country localized for display in the provided locale.
2162      * <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
2163      * To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
2164      * This is a cover for the ICU4C API.
2165      * @param localeID the id of the locale whose country will be displayed
2166      * @param displayLocaleID the id of the locale in which to display the name.
2167      * @return the localized country name.
2168      * @stable ICU 3.0
2169      */
getDisplayCountry(String localeID, String displayLocaleID)2170     public static String getDisplayCountry(String localeID, String displayLocaleID) {
2171         return getDisplayCountryInternal(new ULocale(localeID), new ULocale(displayLocaleID));
2172     }
2173 
2174     /**
2175      * {@icu} Returns a locale's country localized for display in the provided locale.
2176      * <b>Warning: </b>this is for the region part of a valid locale ID; it cannot just be the region code (like "FR").
2177      * To get the display name for a region alone, or for other options, use {@link LocaleDisplayNames} instead.
2178      * This is a cover for the ICU4C API.
2179      * @param localeID the id of the locale whose country will be displayed.
2180      * @param displayLocale the locale in which to display the name.
2181      * @return the localized country name.
2182      * @stable ICU 3.0
2183      */
getDisplayCountry(String localeID, ULocale displayLocale)2184     public static String getDisplayCountry(String localeID, ULocale displayLocale) {
2185         return getDisplayCountryInternal(new ULocale(localeID), displayLocale);
2186     }
2187 
2188     // displayLocaleID is canonical, localeID need not be since parsing will fix this.
getDisplayCountryInternal(ULocale locale, ULocale displayLocale)2189     private static String getDisplayCountryInternal(ULocale locale, ULocale displayLocale) {
2190         return LocaleDisplayNames.getInstance(displayLocale)
2191                 .regionDisplayName(locale.getCountry());
2192     }
2193 
2194     /**
2195      * Returns this locale's variant localized for display in the default <code>DISPLAY</code> locale.
2196      * @return the localized variant name.
2197      * @see Category#DISPLAY
2198      * @stable ICU 3.0
2199      */
getDisplayVariant()2200     public String getDisplayVariant() {
2201         return getDisplayVariantInternal(this, getDefault(Category.DISPLAY));
2202     }
2203 
2204     /**
2205      * Returns this locale's variant localized for display in the provided locale.
2206      * @param displayLocale the locale in which to display the name.
2207      * @return the localized variant name.
2208      * @stable ICU 3.0
2209      */
getDisplayVariant(ULocale displayLocale)2210     public String getDisplayVariant(ULocale displayLocale) {
2211         return getDisplayVariantInternal(this, displayLocale);
2212     }
2213 
2214     /**
2215      * {@icu} Returns a locale's variant localized for display in the provided locale.
2216      * This is a cover for the ICU4C API.
2217      * @param localeID the id of the locale whose variant will be displayed
2218      * @param displayLocaleID the id of the locale in which to display the name.
2219      * @return the localized variant name.
2220      * @stable ICU 3.0
2221      */
getDisplayVariant(String localeID, String displayLocaleID)2222     public static String getDisplayVariant(String localeID, String displayLocaleID){
2223         return getDisplayVariantInternal(new ULocale(localeID), new ULocale(displayLocaleID));
2224     }
2225 
2226     /**
2227      * {@icu} Returns a locale's variant localized for display in the provided locale.
2228      * This is a cover for the ICU4C API.
2229      * @param localeID the id of the locale whose variant will be displayed.
2230      * @param displayLocale the locale in which to display the name.
2231      * @return the localized variant name.
2232      * @stable ICU 3.0
2233      */
getDisplayVariant(String localeID, ULocale displayLocale)2234     public static String getDisplayVariant(String localeID, ULocale displayLocale) {
2235         return getDisplayVariantInternal(new ULocale(localeID), displayLocale);
2236     }
2237 
getDisplayVariantInternal(ULocale locale, ULocale displayLocale)2238     private static String getDisplayVariantInternal(ULocale locale, ULocale displayLocale) {
2239         return LocaleDisplayNames.getInstance(displayLocale)
2240                 .variantDisplayName(locale.getVariant());
2241     }
2242 
2243     /**
2244      * {@icu} Returns a keyword localized for display in the default <code>DISPLAY</code> locale.
2245      * @param keyword the keyword to be displayed.
2246      * @return the localized keyword name.
2247      * @see #getKeywords()
2248      * @see Category#DISPLAY
2249      * @stable ICU 3.0
2250      */
getDisplayKeyword(String keyword)2251     public static String getDisplayKeyword(String keyword) {
2252         return getDisplayKeywordInternal(keyword, getDefault(Category.DISPLAY));
2253     }
2254 
2255     /**
2256      * {@icu} Returns a keyword localized for display in the specified locale.
2257      * @param keyword the keyword to be displayed.
2258      * @param displayLocaleID the id of the locale in which to display the keyword.
2259      * @return the localized keyword name.
2260      * @see #getKeywords(String)
2261      * @stable ICU 3.0
2262      */
getDisplayKeyword(String keyword, String displayLocaleID)2263     public static String getDisplayKeyword(String keyword, String displayLocaleID) {
2264         return getDisplayKeywordInternal(keyword, new ULocale(displayLocaleID));
2265     }
2266 
2267     /**
2268      * {@icu} Returns a keyword localized for display in the specified locale.
2269      * @param keyword the keyword to be displayed.
2270      * @param displayLocale the locale in which to display the keyword.
2271      * @return the localized keyword name.
2272      * @see #getKeywords(String)
2273      * @stable ICU 3.0
2274      */
getDisplayKeyword(String keyword, ULocale displayLocale)2275     public static String getDisplayKeyword(String keyword, ULocale displayLocale) {
2276         return getDisplayKeywordInternal(keyword, displayLocale);
2277     }
2278 
getDisplayKeywordInternal(String keyword, ULocale displayLocale)2279     private static String getDisplayKeywordInternal(String keyword, ULocale displayLocale) {
2280         return LocaleDisplayNames.getInstance(displayLocale).keyDisplayName(keyword);
2281     }
2282 
2283     /**
2284      * {@icu} Returns a keyword value localized for display in the default <code>DISPLAY</code> locale.
2285      * @param keyword the keyword whose value is to be displayed.
2286      * @return the localized value name.
2287      * @see Category#DISPLAY
2288      * @stable ICU 3.0
2289      */
getDisplayKeywordValue(String keyword)2290     public String getDisplayKeywordValue(String keyword) {
2291         return getDisplayKeywordValueInternal(this, keyword, getDefault(Category.DISPLAY));
2292     }
2293 
2294     /**
2295      * {@icu} Returns a keyword value localized for display in the specified locale.
2296      * @param keyword the keyword whose value is to be displayed.
2297      * @param displayLocale the locale in which to display the value.
2298      * @return the localized value name.
2299      * @stable ICU 3.0
2300      */
getDisplayKeywordValue(String keyword, ULocale displayLocale)2301     public String getDisplayKeywordValue(String keyword, ULocale displayLocale) {
2302         return getDisplayKeywordValueInternal(this, keyword, displayLocale);
2303     }
2304 
2305     /**
2306      * {@icu} Returns a keyword value localized for display in the specified locale.
2307      * This is a cover for the ICU4C API.
2308      * @param localeID the id of the locale whose keyword value is to be displayed.
2309      * @param keyword the keyword whose value is to be displayed.
2310      * @param displayLocaleID the id of the locale in which to display the value.
2311      * @return the localized value name.
2312      * @stable ICU 3.0
2313      */
getDisplayKeywordValue(String localeID, String keyword, String displayLocaleID)2314     public static String getDisplayKeywordValue(String localeID, String keyword,
2315             String displayLocaleID) {
2316         return getDisplayKeywordValueInternal(new ULocale(localeID), keyword,
2317                 new ULocale(displayLocaleID));
2318     }
2319 
2320     /**
2321      * {@icu} Returns a keyword value localized for display in the specified locale.
2322      * This is a cover for the ICU4C API.
2323      * @param localeID the id of the locale whose keyword value is to be displayed.
2324      * @param keyword the keyword whose value is to be displayed.
2325      * @param displayLocale the id of the locale in which to display the value.
2326      * @return the localized value name.
2327      * @stable ICU 3.0
2328      */
getDisplayKeywordValue(String localeID, String keyword, ULocale displayLocale)2329     public static String getDisplayKeywordValue(String localeID, String keyword,
2330             ULocale displayLocale) {
2331         return getDisplayKeywordValueInternal(new ULocale(localeID), keyword, displayLocale);
2332     }
2333 
2334     // displayLocaleID is canonical, localeID need not be since parsing will fix this.
getDisplayKeywordValueInternal(ULocale locale, String keyword, ULocale displayLocale)2335     private static String getDisplayKeywordValueInternal(ULocale locale, String keyword,
2336             ULocale displayLocale) {
2337         keyword = AsciiUtil.toLowerString(keyword.trim());
2338         String value = locale.getKeywordValue(keyword);
2339         return LocaleDisplayNames.getInstance(displayLocale).keyValueDisplayName(keyword, value);
2340     }
2341 
2342     /**
2343      * Returns this locale name localized for display in the default <code>DISPLAY</code> locale.
2344      * @return the localized locale name.
2345      * @see Category#DISPLAY
2346      * @stable ICU 3.0
2347      */
getDisplayName()2348     public String getDisplayName() {
2349         return getDisplayNameInternal(this, getDefault(Category.DISPLAY));
2350     }
2351 
2352     /**
2353      * Returns this locale name localized for display in the provided locale.
2354      * @param displayLocale the locale in which to display the locale name.
2355      * @return the localized locale name.
2356      * @stable ICU 3.0
2357      */
getDisplayName(ULocale displayLocale)2358     public String getDisplayName(ULocale displayLocale) {
2359         return getDisplayNameInternal(this, displayLocale);
2360     }
2361 
2362     /**
2363      * {@icu} Returns the locale ID localized for display in the provided locale.
2364      * This is a cover for the ICU4C API.
2365      * @param localeID the locale whose name is to be displayed.
2366      * @param displayLocaleID the id of the locale in which to display the locale name.
2367      * @return the localized locale name.
2368      * @stable ICU 3.0
2369      */
getDisplayName(String localeID, String displayLocaleID)2370     public static String getDisplayName(String localeID, String displayLocaleID) {
2371         return getDisplayNameInternal(new ULocale(localeID), new ULocale(displayLocaleID));
2372     }
2373 
2374     /**
2375      * {@icu} Returns the locale ID localized for display in the provided locale.
2376      * This is a cover for the ICU4C API.
2377      * @param localeID the locale whose name is to be displayed.
2378      * @param displayLocale the locale in which to display the locale name.
2379      * @return the localized locale name.
2380      * @stable ICU 3.0
2381      */
getDisplayName(String localeID, ULocale displayLocale)2382     public static String getDisplayName(String localeID, ULocale displayLocale) {
2383         return getDisplayNameInternal(new ULocale(localeID), displayLocale);
2384     }
2385 
getDisplayNameInternal(ULocale locale, ULocale displayLocale)2386     private static String getDisplayNameInternal(ULocale locale, ULocale displayLocale) {
2387         return LocaleDisplayNames.getInstance(displayLocale).localeDisplayName(locale);
2388     }
2389 
2390     /**
2391      * {@icu} Returns this locale name localized for display in the default <code>DISPLAY</code> locale.
2392      * If a dialect name is present in the locale data, then it is returned.
2393      * @return the localized locale name.
2394      * @see Category#DISPLAY
2395      * @stable ICU 4.4
2396      */
getDisplayNameWithDialect()2397     public String getDisplayNameWithDialect() {
2398         return getDisplayNameWithDialectInternal(this, getDefault(Category.DISPLAY));
2399     }
2400 
2401     /**
2402      * {@icu} Returns this locale name localized for display in the provided locale.
2403      * If a dialect name is present in the locale data, then it is returned.
2404      * @param displayLocale the locale in which to display the locale name.
2405      * @return the localized locale name.
2406      * @stable ICU 4.4
2407      */
getDisplayNameWithDialect(ULocale displayLocale)2408     public String getDisplayNameWithDialect(ULocale displayLocale) {
2409         return getDisplayNameWithDialectInternal(this, displayLocale);
2410     }
2411 
2412     /**
2413      * {@icu} Returns the locale ID localized for display in the provided locale.
2414      * If a dialect name is present in the locale data, then it is returned.
2415      * This is a cover for the ICU4C API.
2416      * @param localeID the locale whose name is to be displayed.
2417      * @param displayLocaleID the id of the locale in which to display the locale name.
2418      * @return the localized locale name.
2419      * @stable ICU 4.4
2420      */
getDisplayNameWithDialect(String localeID, String displayLocaleID)2421     public static String getDisplayNameWithDialect(String localeID, String displayLocaleID) {
2422         return getDisplayNameWithDialectInternal(new ULocale(localeID),
2423                 new ULocale(displayLocaleID));
2424     }
2425 
2426     /**
2427      * {@icu} Returns the locale ID localized for display in the provided locale.
2428      * If a dialect name is present in the locale data, then it is returned.
2429      * This is a cover for the ICU4C API.
2430      * @param localeID the locale whose name is to be displayed.
2431      * @param displayLocale the locale in which to display the locale name.
2432      * @return the localized locale name.
2433      * @stable ICU 4.4
2434      */
getDisplayNameWithDialect(String localeID, ULocale displayLocale)2435     public static String getDisplayNameWithDialect(String localeID, ULocale displayLocale) {
2436         return getDisplayNameWithDialectInternal(new ULocale(localeID), displayLocale);
2437     }
2438 
getDisplayNameWithDialectInternal(ULocale locale, ULocale displayLocale)2439     private static String getDisplayNameWithDialectInternal(ULocale locale, ULocale displayLocale) {
2440         return LocaleDisplayNames.getInstance(displayLocale, DialectHandling.DIALECT_NAMES)
2441                 .localeDisplayName(locale);
2442     }
2443 
2444     /**
2445      * {@icu} Returns this locale's layout orientation for characters.  The possible
2446      * values are "left-to-right", "right-to-left", "top-to-bottom" or
2447      * "bottom-to-top".
2448      * @return The locale's layout orientation for characters.
2449      * @stable ICU 4.0
2450      */
getCharacterOrientation()2451     public String getCharacterOrientation() {
2452         return ICUResourceTableAccess.getTableString(ICUData.ICU_BASE_NAME, this,
2453                 "layout", "characters", "characters");
2454     }
2455 
2456     /**
2457      * {@icu} Returns this locale's layout orientation for lines.  The possible
2458      * values are "left-to-right", "right-to-left", "top-to-bottom" or
2459      * "bottom-to-top".
2460      * @return The locale's layout orientation for lines.
2461      * @stable ICU 4.0
2462      */
getLineOrientation()2463     public String getLineOrientation() {
2464         return ICUResourceTableAccess.getTableString(ICUData.ICU_BASE_NAME, this,
2465                 "layout", "lines", "lines");
2466     }
2467 
2468     /**
2469      * {@icu} Selector for <tt>getLocale()</tt> indicating the locale of the
2470      * resource containing the data.  This is always at or above the
2471      * valid locale.  If the valid locale does not contain the
2472      * specific data being requested, then the actual locale will be
2473      * above the valid locale.  If the object was not constructed from
2474      * locale data, then the valid locale is <i>null</i>.
2475      *
2476      * @draft ICU 2.8 (retain)
2477      */
2478     public static Type ACTUAL_LOCALE = new Type();
2479 
2480     /**
2481      * {@icu} Selector for <tt>getLocale()</tt> indicating the most specific
2482      * locale for which any data exists.  This is always at or above
2483      * the requested locale, and at or below the actual locale.  If
2484      * the requested locale does not correspond to any resource data,
2485      * then the valid locale will be above the requested locale.  If
2486      * the object was not constructed from locale data, then the
2487      * actual locale is <i>null</i>.
2488      *
2489      * <p>Note: The valid locale will be returned correctly in ICU
2490      * 3.0 or later.  In ICU 2.8, it is not returned correctly.
2491      * @draft ICU 2.8 (retain)
2492      */
2493     public static Type VALID_LOCALE = new Type();
2494 
2495     /**
2496      * Opaque selector enum for <tt>getLocale()</tt>.
2497      * @see com.ibm.icu.util.ULocale
2498      * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE
2499      * @see com.ibm.icu.util.ULocale#VALID_LOCALE
2500      * @draft ICU 2.8 (retainAll)
2501      */
2502     public static final class Type {
Type()2503         private Type() {}
2504     }
2505 
2506     /**
2507      * {@icu} Based on a HTTP formatted list of acceptable locales, determine an available
2508      * locale for the user.  NullPointerException is thrown if acceptLanguageList or
2509      * availableLocales is null.  If fallback is non-null, it will contain true if a
2510      * fallback locale (one not in the acceptLanguageList) was returned.  The value on
2511      * entry is ignored.  ULocale will be one of the locales in availableLocales, or the
2512      * ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
2513      * availableLocales matched).  No ULocale array element should be null; behavior is
2514      * undefined if this is the case.
2515      *
2516      * <p>This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}.
2517      *
2518      * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
2519      * @param availableLocales list of available locales. One of these will be returned.
2520      * @param fallback if non-null, a 1-element array containing a boolean to be set with
2521      * the fallback status
2522      * @return one of the locales from the availableLocales list, or null if none match
2523      * @stable ICU 3.4
2524      * @see LocaleMatcher
2525      * @see LocalePriorityList
2526      */
acceptLanguage(String acceptLanguageList, ULocale[] availableLocales, boolean[] fallback)2527     public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales,
2528             boolean[] fallback) {
2529         if (fallback != null) {
2530             fallback[0] = true;
2531         }
2532         LocalePriorityList desired;
2533         try {
2534             desired = LocalePriorityList.add(acceptLanguageList).build();
2535         } catch (IllegalArgumentException e) {
2536             return null;
2537         }
2538         LocaleMatcher.Builder builder = LocaleMatcher.builder();
2539         for (ULocale locale : availableLocales) {
2540             builder.addSupportedULocale(locale);
2541         }
2542         LocaleMatcher matcher = builder.build();
2543         LocaleMatcher.Result result = matcher.getBestMatchResult(desired);
2544         if (result.getDesiredIndex() >= 0) {
2545             if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
2546                 fallback[0] = false;
2547             }
2548             return result.getSupportedULocale();
2549         }
2550         return null;
2551     }
2552 
2553     /**
2554      * {@icu} Based on a list of acceptable locales, determine an available locale for the
2555      * user.  NullPointerException is thrown if acceptLanguageList or availableLocales is
2556      * null.  If fallback is non-null, it will contain true if a fallback locale (one not
2557      * in the acceptLanguageList) was returned.  The value on entry is ignored.  ULocale
2558      * will be one of the locales in availableLocales, or the ROOT ULocale if if a ROOT
2559      * locale was used as a fallback (because nothing else in availableLocales matched).
2560      * No ULocale array element should be null; behavior is undefined if this is the case.
2561      *
2562      * <p>This is a thin wrapper over {@link LocaleMatcher}.
2563      *
2564      * @param acceptLanguageList list of acceptable locales
2565      * @param availableLocales list of available locales. One of these will be returned.
2566      * @param fallback if non-null, a 1-element array containing a boolean to be set with
2567      * the fallback status
2568      * @return one of the locales from the availableLocales list, or null if none match
2569      * @stable ICU 3.4
2570      * @see LocaleMatcher
2571      */
2572 
acceptLanguage(ULocale[] acceptLanguageList, ULocale[] availableLocales, boolean[] fallback)2573     public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[] availableLocales,
2574             boolean[] fallback) {
2575         if (fallback != null) {
2576             fallback[0] = true;
2577         }
2578         LocaleMatcher.Builder builder = LocaleMatcher.builder();
2579         for (ULocale locale : availableLocales) {
2580             builder.addSupportedULocale(locale);
2581         }
2582         LocaleMatcher matcher = builder.build();
2583         LocaleMatcher.Result result;
2584         if (acceptLanguageList.length == 1) {
2585             result = matcher.getBestMatchResult(acceptLanguageList[0]);
2586         } else {
2587             result = matcher.getBestMatchResult(Arrays.asList(acceptLanguageList));
2588         }
2589         if (result.getDesiredIndex() >= 0) {
2590             if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) {
2591                 fallback[0] = false;
2592             }
2593             return result.getSupportedULocale();
2594         }
2595         return null;
2596     }
2597 
2598     /**
2599      * {@icu} Based on a HTTP formatted list of acceptable locales, determine an available
2600      * locale for the user.  NullPointerException is thrown if acceptLanguageList or
2601      * availableLocales is null.  If fallback is non-null, it will contain true if a
2602      * fallback locale (one not in the acceptLanguageList) was returned.  The value on
2603      * entry is ignored.  ULocale will be one of the locales in availableLocales, or the
2604      * ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
2605      * availableLocales matched).  No ULocale array element should be null; behavior is
2606      * undefined if this is the case.  This function will choose a locale from the
2607      * ULocale.getAvailableLocales() list as available.
2608      *
2609      * <p>This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}.
2610      *
2611      * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
2612      * @param fallback if non-null, a 1-element array containing a boolean to be set with
2613      * the fallback status
2614      * @return one of the locales from the ULocale.getAvailableLocales() list, or null if
2615      * none match
2616      * @stable ICU 3.4
2617      * @see LocaleMatcher
2618      * @see LocalePriorityList
2619      */
acceptLanguage(String acceptLanguageList, boolean[] fallback)2620     public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) {
2621         return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
2622                 fallback);
2623     }
2624 
2625     /**
2626      * {@icu} Based on an ordered array of acceptable locales, determine an available
2627      * locale for the user.  NullPointerException is thrown if acceptLanguageList or
2628      * availableLocales is null.  If fallback is non-null, it will contain true if a
2629      * fallback locale (one not in the acceptLanguageList) was returned.  The value on
2630      * entry is ignored.  ULocale will be one of the locales in availableLocales, or the
2631      * ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in
2632      * availableLocales matched).  No ULocale array element should be null; behavior is
2633      * undefined if this is the case.  This function will choose a locale from the
2634      * ULocale.getAvailableLocales() list as available.
2635      *
2636      * <p>This is a thin wrapper over {@link LocaleMatcher}.
2637      *
2638      * @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)
2639      * @param fallback if non-null, a 1-element array containing a boolean to be set with
2640      * the fallback status
2641      * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
2642      * @stable ICU 3.4
2643      * @see LocaleMatcher
2644      */
acceptLanguage(ULocale[] acceptLanguageList, boolean[] fallback)2645     public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[] fallback) {
2646         return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),
2647                 fallback);
2648     }
2649 
2650     private static final String UNDEFINED_LANGUAGE = "und";
2651     private static final String UNDEFINED_SCRIPT = "Zzzz";
2652     private static final String UNDEFINED_REGION = "ZZ";
2653 
2654     /**
2655      * {@icu} Adds the likely subtags for a provided locale ID, per the algorithm
2656      * described in the following CLDR technical report:
2657      *
2658      *   http://www.unicode.org/reports/tr35/#Likely_Subtags
2659      *
2660      * If the provided ULocale instance is already in the maximal form, or there is no
2661      * data available available for maximization, it will be returned.  For example,
2662      * "und-Zzzz" cannot be maximized, since there is no reasonable maximization.
2663      * Otherwise, a new ULocale instance with the maximal form is returned.
2664      *
2665      * Examples:
2666      *
2667      * "en" maximizes to "en_Latn_US"
2668      *
2669      * "de" maximizes to "de_Latn_US"
2670      *
2671      * "sr" maximizes to "sr_Cyrl_RS"
2672      *
2673      * "sh" maximizes to "sr_Latn_RS" (Note this will not reverse.)
2674      *
2675      * "zh_Hani" maximizes to "zh_Hans_CN" (Note this will not reverse.)
2676      *
2677      * @param loc The ULocale to maximize
2678      * @return The maximized ULocale instance.
2679      * @stable ICU 4.0
2680      */
addLikelySubtags(ULocale loc)2681     public static ULocale addLikelySubtags(ULocale loc) {
2682         String[] tags = new String[3];
2683         String trailing = null;
2684 
2685         int trailingIndex = parseTagString(
2686                 loc.localeID,
2687                 tags);
2688 
2689         if (trailingIndex < loc.localeID.length()) {
2690             trailing = loc.localeID.substring(trailingIndex);
2691         }
2692 
2693         String newLocaleID =
2694                 createLikelySubtagsString(
2695                         tags[0],
2696                         tags[1],
2697                         tags[2],
2698                         trailing);
2699 
2700         return newLocaleID == null ? loc : new ULocale(newLocaleID);
2701     }
2702 
2703     /**
2704      * {@icu} Minimizes the subtags for a provided locale ID, per the algorithm described
2705      * in the following CLDR technical report:<blockquote>
2706      *
2707      *   <a href="http://www.unicode.org/reports/tr35/#Likely_Subtags"
2708      *>http://www.unicode.org/reports/tr35/#Likely_Subtags</a></blockquote>
2709      *
2710      * If the provided ULocale instance is already in the minimal form, or there
2711      * is no data available for minimization, it will be returned.  Since the
2712      * minimization algorithm relies on proper maximization, see the comments
2713      * for addLikelySubtags for reasons why there might not be any data.
2714      *
2715      * Examples:<pre>
2716      *
2717      * "en_Latn_US" minimizes to "en"
2718      *
2719      * "de_Latn_US" minimizes to "de"
2720      *
2721      * "sr_Cyrl_RS" minimizes to "sr"
2722      *
2723      * "zh_Hant_TW" minimizes to "zh_TW" (The region is preferred to the
2724      * script, and minimizing to "zh" would imply "zh_Hans_CN".) </pre>
2725      *
2726      * @param loc The ULocale to minimize
2727      * @return The minimized ULocale instance.
2728      * @stable ICU 4.0
2729      */
minimizeSubtags(ULocale loc)2730     public static ULocale minimizeSubtags(ULocale loc) {
2731         return minimizeSubtags(loc, Minimize.FAVOR_REGION);
2732     }
2733 
2734     /**
2735      * Options for minimizeSubtags.
2736      * @internal
2737      * @deprecated This API is ICU internal only.
2738      */
2739     @Deprecated
2740     public enum Minimize {
2741         /**
2742          * Favor including the script, when either the region <b>or</b> the script could be suppressed, but not both.
2743          * @internal
2744          * @deprecated This API is ICU internal only.
2745          */
2746         @Deprecated
2747         FAVOR_SCRIPT,
2748         /**
2749          * Favor including the region, when either the region <b>or</b> the script could be suppressed, but not both.
2750          * @internal
2751          * @deprecated This API is ICU internal only.
2752          */
2753         @Deprecated
2754         FAVOR_REGION
2755     }
2756 
2757     /**
2758      * {@icu} Minimizes the subtags for a provided locale ID, per the algorithm described
2759      * in the following CLDR technical report:<blockquote>
2760      *
2761      *   <a href="http://www.unicode.org/reports/tr35/#Likely_Subtags"
2762      *>http://www.unicode.org/reports/tr35/#Likely_Subtags</a></blockquote>
2763      *
2764      * If the provided ULocale instance is already in the minimal form, or there
2765      * is no data available for minimization, it will be returned.  Since the
2766      * minimization algorithm relies on proper maximization, see the comments
2767      * for addLikelySubtags for reasons why there might not be any data.
2768      *
2769      * Examples:<pre>
2770      *
2771      * "en_Latn_US" minimizes to "en"
2772      *
2773      * "de_Latn_US" minimizes to "de"
2774      *
2775      * "sr_Cyrl_RS" minimizes to "sr"
2776      *
2777      * "zh_Hant_TW" minimizes to "zh_TW" if fieldToFavor == {@link Minimize#FAVOR_REGION}
2778      * "zh_Hant_TW" minimizes to "zh_Hant" if fieldToFavor == {@link Minimize#FAVOR_SCRIPT}
2779      * </pre>
2780      * The fieldToFavor only has an effect if either the region or the script could be suppressed, but not both.
2781      * @param loc The ULocale to minimize
2782      * @param fieldToFavor Indicate which should be preferred, when either the region <b>or</b> the script could be suppressed, but not both.
2783      * @return The minimized ULocale instance.
2784      * @internal
2785      * @deprecated This API is ICU internal only.
2786      */
2787     @Deprecated
minimizeSubtags(ULocale loc, Minimize fieldToFavor)2788     public static ULocale minimizeSubtags(ULocale loc, Minimize fieldToFavor) {
2789         String[] tags = new String[3];
2790 
2791         int trailingIndex = parseTagString(
2792                 loc.localeID,
2793                 tags);
2794 
2795         String originalLang = tags[0];
2796         String originalScript = tags[1];
2797         String originalRegion = tags[2];
2798         String originalTrailing = null;
2799 
2800         if (trailingIndex < loc.localeID.length()) {
2801             /*
2802              * Create a String that contains everything
2803              * after the language, script, and region.
2804              */
2805             originalTrailing = loc.localeID.substring(trailingIndex);
2806         }
2807 
2808         /**
2809          * First, we need to first get the maximization
2810          * by adding any likely subtags.
2811          **/
2812         String maximizedLocaleID =
2813                 createLikelySubtagsString(
2814                         originalLang,
2815                         originalScript,
2816                         originalRegion,
2817                         null);
2818 
2819         /**
2820          * If maximization fails, there's nothing
2821          * we can do.
2822          **/
2823         if (isEmptyString(maximizedLocaleID)) {
2824             return loc;
2825         }
2826         else {
2827             /**
2828              * Start first with just the language.
2829              **/
2830             String tag =
2831                     createLikelySubtagsString(
2832                             originalLang,
2833                             null,
2834                             null,
2835                             null);
2836 
2837             if (tag.equals(maximizedLocaleID)) {
2838                 String newLocaleID =
2839                         createTagString(
2840                                 originalLang,
2841                                 null,
2842                                 null,
2843                                 originalTrailing);
2844 
2845                 return new ULocale(newLocaleID);
2846             }
2847         }
2848 
2849         /**
2850          * Next, try the language and region.
2851          **/
2852         if (fieldToFavor == Minimize.FAVOR_REGION) {
2853             if (originalRegion.length() != 0) {
2854                 String tag =
2855                         createLikelySubtagsString(
2856                                 originalLang,
2857                                 null,
2858                                 originalRegion,
2859                                 null);
2860 
2861                 if (tag.equals(maximizedLocaleID)) {
2862                     String newLocaleID =
2863                             createTagString(
2864                                     originalLang,
2865                                     null,
2866                                     originalRegion,
2867                                     originalTrailing);
2868 
2869                     return new ULocale(newLocaleID);
2870                 }
2871             }
2872             if (originalScript.length() != 0){
2873                 String tag =
2874                         createLikelySubtagsString(
2875                                 originalLang,
2876                                 originalScript,
2877                                 null,
2878                                 null);
2879 
2880                 if (tag.equals(maximizedLocaleID)) {
2881                     String newLocaleID =
2882                             createTagString(
2883                                     originalLang,
2884                                     originalScript,
2885                                     null,
2886                                     originalTrailing);
2887 
2888                     return new ULocale(newLocaleID);
2889                 }
2890             }
2891         } else { // FAVOR_SCRIPT, so
2892             if (originalScript.length() != 0){
2893                 String tag =
2894                         createLikelySubtagsString(
2895                                 originalLang,
2896                                 originalScript,
2897                                 null,
2898                                 null);
2899 
2900                 if (tag.equals(maximizedLocaleID)) {
2901                     String newLocaleID =
2902                             createTagString(
2903                                     originalLang,
2904                                     originalScript,
2905                                     null,
2906                                     originalTrailing);
2907 
2908                     return new ULocale(newLocaleID);
2909                 }
2910             }
2911             if (originalRegion.length() != 0) {
2912                 String tag =
2913                         createLikelySubtagsString(
2914                                 originalLang,
2915                                 null,
2916                                 originalRegion,
2917                                 null);
2918 
2919                 if (tag.equals(maximizedLocaleID)) {
2920                     String newLocaleID =
2921                             createTagString(
2922                                     originalLang,
2923                                     null,
2924                                     originalRegion,
2925                                     originalTrailing);
2926 
2927                     return new ULocale(newLocaleID);
2928                 }
2929             }
2930         }
2931         return loc;
2932     }
2933 
2934     /**
2935      * A trivial utility function that checks for a null
2936      * reference or checks the length of the supplied String.
2937      *
2938      *   @param string The string to check
2939      *
2940      *   @return true if the String is empty, or if the reference is null.
2941      */
isEmptyString(String string)2942     private static boolean isEmptyString(String string) {
2943         return string == null || string.length() == 0;
2944     }
2945 
2946     /**
2947      * Append a tag to a StringBuilder, adding the separator if necessary.The tag must
2948      * not be a zero-length string.
2949      *
2950      * @param tag The tag to add.
2951      * @param buffer The output buffer.
2952      **/
appendTag(String tag, StringBuilder buffer)2953     private static void appendTag(String tag, StringBuilder buffer) {
2954         if (buffer.length() != 0) {
2955             buffer.append(UNDERSCORE);
2956         }
2957 
2958         buffer.append(tag);
2959     }
2960 
2961     /**
2962      * Create a tag string from the supplied parameters.  The lang, script and region
2963      * parameters may be null references.
2964      *
2965      * If any of the language, script or region parameters are empty, and the alternateTags
2966      * parameter is not null, it will be parsed for potential language, script and region tags
2967      * to be used when constructing the new tag.  If the alternateTags parameter is null, or
2968      * it contains no language tag, the default tag for the unknown language is used.
2969      *
2970      * @param lang The language tag to use.
2971      * @param script The script tag to use.
2972      * @param region The region tag to use.
2973      * @param trailing Any trailing data to append to the new tag.
2974      * @param alternateTags A string containing any alternate tags.
2975      * @return The new tag string.
2976      **/
createTagString(String lang, String script, String region, String trailing, String alternateTags)2977     private static String createTagString(String lang, String script, String region,
2978             String trailing, String alternateTags) {
2979 
2980         LocaleIDParser parser = null;
2981         boolean regionAppended = false;
2982 
2983         StringBuilder tag = new StringBuilder();
2984 
2985         if (!isEmptyString(lang)) {
2986             appendTag(
2987                     lang,
2988                     tag);
2989         }
2990         else if (isEmptyString(alternateTags)) {
2991             /*
2992              * Append the value for an unknown language, if
2993              * we found no language.
2994              */
2995             appendTag(
2996                     UNDEFINED_LANGUAGE,
2997                     tag);
2998         }
2999         else {
3000             parser = new LocaleIDParser(alternateTags);
3001 
3002             String alternateLang = parser.getLanguage();
3003 
3004             /*
3005              * Append the value for an unknown language, if
3006              * we found no language.
3007              */
3008             appendTag(
3009                     !isEmptyString(alternateLang) ? alternateLang : UNDEFINED_LANGUAGE,
3010                             tag);
3011         }
3012 
3013         if (!isEmptyString(script)) {
3014             appendTag(
3015                     script,
3016                     tag);
3017         }
3018         else if (!isEmptyString(alternateTags)) {
3019             /*
3020              * Parse the alternateTags string for the script.
3021              */
3022             if (parser == null) {
3023                 parser = new LocaleIDParser(alternateTags);
3024             }
3025 
3026             String alternateScript = parser.getScript();
3027 
3028             if (!isEmptyString(alternateScript)) {
3029                 appendTag(
3030                         alternateScript,
3031                         tag);
3032             }
3033         }
3034 
3035         if (!isEmptyString(region)) {
3036             appendTag(
3037                     region,
3038                     tag);
3039 
3040             regionAppended = true;
3041         }
3042         else if (!isEmptyString(alternateTags)) {
3043             /*
3044              * Parse the alternateTags string for the region.
3045              */
3046             if (parser == null) {
3047                 parser = new LocaleIDParser(alternateTags);
3048             }
3049 
3050             String alternateRegion = parser.getCountry();
3051 
3052             if (!isEmptyString(alternateRegion)) {
3053                 appendTag(
3054                         alternateRegion,
3055                         tag);
3056 
3057                 regionAppended = true;
3058             }
3059         }
3060 
3061         if (trailing != null && trailing.length() > 1) {
3062             /*
3063              * The current ICU format expects two underscores
3064              * will separate the variant from the preceeding
3065              * parts of the tag, if there is no region.
3066              */
3067             int separators = 0;
3068 
3069             if (trailing.charAt(0) == UNDERSCORE) {
3070                 if (trailing.charAt(1) == UNDERSCORE) {
3071                     separators = 2;
3072                 }
3073             }
3074             else {
3075                 separators = 1;
3076             }
3077 
3078             if (regionAppended) {
3079                 /*
3080                  * If we appended a region, we may need to strip
3081                  * the extra separator from the variant portion.
3082                  */
3083                 if (separators == 2) {
3084                     tag.append(trailing.substring(1));
3085                 }
3086                 else {
3087                     tag.append(trailing);
3088                 }
3089             }
3090             else {
3091                 /*
3092                  * If we did not append a region, we may need to add
3093                  * an extra separator to the variant portion.
3094                  */
3095                 if (separators == 1) {
3096                     tag.append(UNDERSCORE);
3097                 }
3098                 tag.append(trailing);
3099             }
3100         }
3101 
3102         return tag.toString();
3103     }
3104 
3105     /**
3106      * Create a tag string from the supplied parameters.  The lang, script and region
3107      * parameters may be null references.If the lang parameter is an empty string, the
3108      * default value for an unknown language is written to the output buffer.
3109      *
3110      * @param lang The language tag to use.
3111      * @param script The script tag to use.
3112      * @param region The region tag to use.
3113      * @param trailing Any trailing data to append to the new tag.
3114      * @return The new String.
3115      **/
createTagString(String lang, String script, String region, String trailing)3116     static String createTagString(String lang, String script, String region, String trailing) {
3117         return createTagString(lang, script, region, trailing, null);
3118     }
3119 
3120     /**
3121      * Parse the language, script, and region subtags from a tag string, and return the results.
3122      *
3123      * This function does not return the canonical strings for the unknown script and region.
3124      *
3125      * @param localeID The locale ID to parse.
3126      * @param tags An array of three String references to return the subtag strings.
3127      * @return The number of chars of the localeID parameter consumed.
3128      **/
parseTagString(String localeID, String tags[])3129     private static int parseTagString(String localeID, String tags[]) {
3130         LocaleIDParser parser = new LocaleIDParser(localeID);
3131 
3132         String lang = parser.getLanguage();
3133         String script = parser.getScript();
3134         String region = parser.getCountry();
3135 
3136         if (isEmptyString(lang)) {
3137             tags[0] = UNDEFINED_LANGUAGE;
3138         }
3139         else {
3140             tags[0] = lang;
3141         }
3142 
3143         if (script.equals(UNDEFINED_SCRIPT)) {
3144             tags[1] = "";
3145         }
3146         else {
3147             tags[1] = script;
3148         }
3149 
3150         if (region.equals(UNDEFINED_REGION)) {
3151             tags[2] = "";
3152         }
3153         else {
3154             tags[2] = region;
3155         }
3156 
3157         /*
3158          * Search for the variant.  If there is one, then return the index of
3159          * the preceeding separator.
3160          * If there's no variant, search for the keyword delimiter,
3161          * and return its index.  Otherwise, return the length of the
3162          * string.
3163          *
3164          * $TOTO(dbertoni) we need to take into account that we might
3165          * find a part of the language as the variant, since it can
3166          * can have a variant portion that is long enough to contain
3167          * the same characters as the variant.
3168          */
3169         String variant = parser.getVariant();
3170 
3171         if (!isEmptyString(variant)){
3172             int index = localeID.indexOf(variant);
3173 
3174 
3175             return  index > 0 ? index - 1 : index;
3176         }
3177         else
3178         {
3179             int index = localeID.indexOf('@');
3180 
3181             return index == -1 ? localeID.length() : index;
3182         }
3183     }
3184 
lookupLikelySubtags(String localeId)3185     private static String lookupLikelySubtags(String localeId) {
3186         UResourceBundle bundle =
3187                 UResourceBundle.getBundleInstance(
3188                         ICUData.ICU_BASE_NAME, "likelySubtags");
3189         try {
3190             return bundle.getString(localeId);
3191         }
3192         catch(MissingResourceException e) {
3193             return null;
3194         }
3195     }
3196 
createLikelySubtagsString(String lang, String script, String region, String variants)3197     private static String createLikelySubtagsString(String lang, String script, String region,
3198             String variants) {
3199 
3200         /**
3201          * Try the language with the script and region first.
3202          */
3203         if (!isEmptyString(script) && !isEmptyString(region)) {
3204 
3205             String searchTag =
3206                     createTagString(
3207                             lang,
3208                             script,
3209                             region,
3210                             null);
3211 
3212             String likelySubtags = lookupLikelySubtags(searchTag);
3213 
3214             /*
3215             if (likelySubtags == null) {
3216                 if (likelySubtags2 != null) {
3217                     System.err.println("Tag mismatch: \"(null)\" \"" + likelySubtags2 + "\"");
3218                 }
3219             }
3220             else if (likelySubtags2 == null) {
3221                 System.err.println("Tag mismatch: \"" + likelySubtags + "\" \"(null)\"");
3222             }
3223             else if (!likelySubtags.equals(likelySubtags2)) {
3224                 System.err.println("Tag mismatch: \"" + likelySubtags + "\" \"" + likelySubtags2
3225                     + "\"");
3226             }
3227              */
3228             if (likelySubtags != null) {
3229                 // Always use the language tag from the
3230                 // maximal string, since it may be more
3231                 // specific than the one provided.
3232                 return createTagString(
3233                         null,
3234                         null,
3235                         null,
3236                         variants,
3237                         likelySubtags);
3238             }
3239         }
3240 
3241         /**
3242          * Try the language with just the script.
3243          **/
3244         if (!isEmptyString(script)) {
3245 
3246             String searchTag =
3247                     createTagString(
3248                             lang,
3249                             script,
3250                             null,
3251                             null);
3252 
3253             String likelySubtags = lookupLikelySubtags(searchTag);
3254             if (likelySubtags != null) {
3255                 // Always use the language tag from the
3256                 // maximal string, since it may be more
3257                 // specific than the one provided.
3258                 return createTagString(
3259                         null,
3260                         null,
3261                         region,
3262                         variants,
3263                         likelySubtags);
3264             }
3265         }
3266 
3267         /**
3268          * Try the language with just the region.
3269          **/
3270         if (!isEmptyString(region)) {
3271 
3272             String searchTag =
3273                     createTagString(
3274                             lang,
3275                             null,
3276                             region,
3277                             null);
3278 
3279             String likelySubtags = lookupLikelySubtags(searchTag);
3280 
3281             if (likelySubtags != null) {
3282                 // Always use the language tag from the
3283                 // maximal string, since it may be more
3284                 // specific than the one provided.
3285                 return createTagString(
3286                         null,
3287                         script,
3288                         null,
3289                         variants,
3290                         likelySubtags);
3291             }
3292         }
3293 
3294         /**
3295          * Finally, try just the language.
3296          **/
3297         {
3298             String searchTag =
3299                     createTagString(
3300                             lang,
3301                             null,
3302                             null,
3303                             null);
3304 
3305             String likelySubtags = lookupLikelySubtags(searchTag);
3306 
3307             if (likelySubtags != null) {
3308                 // Always use the language tag from the
3309                 // maximal string, since it may be more
3310                 // specific than the one provided.
3311                 return createTagString(
3312                         null,
3313                         script,
3314                         region,
3315                         variants,
3316                         likelySubtags);
3317             }
3318         }
3319 
3320         return null;
3321     }
3322 
3323     // --------------------------------
3324     //      BCP47/OpenJDK APIs
3325     // --------------------------------
3326 
3327     /**
3328      * The key for the private use locale extension ('x').
3329      *
3330      * @see #getExtension(char)
3331      * @see Builder#setExtension(char, String)
3332      *
3333      * @stable ICU 4.2
3334      */
3335     public static final char PRIVATE_USE_EXTENSION = 'x';
3336 
3337     /**
3338      * The key for Unicode locale extension ('u').
3339      *
3340      * @see #getExtension(char)
3341      * @see Builder#setExtension(char, String)
3342      *
3343      * @stable ICU 4.2
3344      */
3345     public static final char UNICODE_LOCALE_EXTENSION = 'u';
3346 
3347     /**
3348      * Returns the extension (or private use) value associated with
3349      * the specified key, or null if there is no extension
3350      * associated with the key. To be well-formed, the key must be one
3351      * of <code>[0-9A-Za-z]</code>. Keys are case-insensitive, so
3352      * for example 'z' and 'Z' represent the same extension.
3353      *
3354      * @param key the extension key
3355      * @return The extension, or null if this locale defines no
3356      * extension for the specified key.
3357      * @throws IllegalArgumentException if key is not well-formed
3358      * @see #PRIVATE_USE_EXTENSION
3359      * @see #UNICODE_LOCALE_EXTENSION
3360      *
3361      * @stable ICU 4.2
3362      */
getExtension(char key)3363     public String getExtension(char key) {
3364         if (!LocaleExtensions.isValidKey(key)) {
3365             throw new IllegalArgumentException("Invalid extension key: " + key);
3366         }
3367         return extensions().getExtensionValue(key);
3368     }
3369 
3370     /**
3371      * Returns the set of extension keys associated with this locale, or the
3372      * empty set if it has no extensions. The returned set is unmodifiable.
3373      * The keys will all be lower-case.
3374      *
3375      * @return the set of extension keys, or the empty set if this locale has
3376      * no extensions
3377      * @stable ICU 4.2
3378      */
getExtensionKeys()3379     public Set<Character> getExtensionKeys() {
3380         return extensions().getKeys();
3381     }
3382 
3383     /**
3384      * Returns the set of unicode locale attributes associated with
3385      * this locale, or the empty set if it has no attributes. The
3386      * returned set is unmodifiable.
3387      *
3388      * @return The set of attributes.
3389      * @stable ICU 4.6
3390      */
getUnicodeLocaleAttributes()3391     public Set<String> getUnicodeLocaleAttributes() {
3392         return extensions().getUnicodeLocaleAttributes();
3393     }
3394 
3395     /**
3396      * Returns the Unicode locale type associated with the specified Unicode locale key
3397      * for this locale. Returns the empty string for keys that are defined with no type.
3398      * Returns null if the key is not defined. Keys are case-insensitive. The key must
3399      * be two alphanumeric characters ([0-9a-zA-Z]), or an IllegalArgumentException is
3400      * thrown.
3401      *
3402      * @param key the Unicode locale key
3403      * @return The Unicode locale type associated with the key, or null if the
3404      * locale does not define the key.
3405      * @throws IllegalArgumentException if the key is not well-formed
3406      * @throws NullPointerException if <code>key</code> is null
3407      *
3408      * @stable ICU 4.4
3409      */
getUnicodeLocaleType(String key)3410     public String getUnicodeLocaleType(String key) {
3411         if (!LocaleExtensions.isValidUnicodeLocaleKey(key)) {
3412             throw new IllegalArgumentException("Invalid Unicode locale key: " + key);
3413         }
3414         return extensions().getUnicodeLocaleType(key);
3415     }
3416 
3417     /**
3418      * Returns the set of Unicode locale keys defined by this locale, or the empty set if
3419      * this locale has none.  The returned set is immutable.  Keys are all lower case.
3420      *
3421      * @return The set of Unicode locale keys, or the empty set if this locale has
3422      * no Unicode locale keywords.
3423      *
3424      * @stable ICU 4.4
3425      */
getUnicodeLocaleKeys()3426     public Set<String> getUnicodeLocaleKeys() {
3427         return extensions().getUnicodeLocaleKeys();
3428     }
3429 
3430     /**
3431      * Returns a well-formed IETF BCP 47 language tag representing
3432      * this locale.
3433      *
3434      * <p>If this <code>ULocale</code> has a language, script, country, or
3435      * variant that does not satisfy the IETF BCP 47 language tag
3436      * syntax requirements, this method handles these fields as
3437      * described below:
3438      *
3439      * <p><b>Language:</b> If language is empty, or not well-formed
3440      * (for example "a" or "e2"), it will be emitted as "und" (Undetermined).
3441      *
3442      * <p><b>Script:</b> If script is not well-formed (for example "12"
3443      * or "Latin"), it will be omitted.
3444      *
3445      * <p><b>Country:</b> If country is not well-formed (for example "12"
3446      * or "USA"), it will be omitted.
3447      *
3448      * <p><b>Variant:</b> If variant <b>is</b> well-formed, each sub-segment
3449      * (delimited by '-' or '_') is emitted as a subtag.  Otherwise:
3450      * <ul>
3451      *
3452      * <li>if all sub-segments match <code>[0-9a-zA-Z]{1,8}</code>
3453      * (for example "WIN" or "Oracle_JDK_Standard_Edition"), the first
3454      * ill-formed sub-segment and all following will be appended to
3455      * the private use subtag.  The first appended subtag will be
3456      * "lvariant", followed by the sub-segments in order, separated by
3457      * hyphen. For example, "x-lvariant-WIN",
3458      * "Oracle-x-lvariant-JDK-Standard-Edition".
3459      *
3460      * <li>if any sub-segment does not match
3461      * <code>[0-9a-zA-Z]{1,8}</code>, the variant will be truncated
3462      * and the problematic sub-segment and all following sub-segments
3463      * will be omitted.  If the remainder is non-empty, it will be
3464      * emitted as a private use subtag as above (even if the remainder
3465      * turns out to be well-formed).  For example,
3466      * "Solaris_isjustthecoolestthing" is emitted as
3467      * "x-lvariant-Solaris", not as "solaris".</li></ul>
3468      *
3469      * <p><b>Note:</b> Although the language tag created by this
3470      * method is well-formed (satisfies the syntax requirements
3471      * defined by the IETF BCP 47 specification), it is not
3472      * necessarily a valid BCP 47 language tag.  For example,
3473      * <pre>
3474      *   new Locale("xx", "YY").toLanguageTag();</pre>
3475      *
3476      * will return "xx-YY", but the language subtag "xx" and the
3477      * region subtag "YY" are invalid because they are not registered
3478      * in the IANA Language Subtag Registry.
3479      *
3480      * @return a BCP47 language tag representing the locale
3481      * @see #forLanguageTag(String)
3482      *
3483      * @stable ICU 4.2
3484      */
toLanguageTag()3485     public String toLanguageTag() {
3486         BaseLocale base = base();
3487         LocaleExtensions exts = extensions();
3488 
3489         if (base.getVariant().equalsIgnoreCase("POSIX")) {
3490             // special handling for variant POSIX
3491             base = BaseLocale.getInstance(base.getLanguage(), base.getScript(), base.getRegion(), "");
3492             if (exts.getUnicodeLocaleType("va") == null) {
3493                 // add va-posix
3494                 InternalLocaleBuilder ilocbld = new InternalLocaleBuilder();
3495                 try {
3496                     ilocbld.setLocale(BaseLocale.ROOT, exts);
3497                     ilocbld.setUnicodeLocaleKeyword("va", "posix");
3498                     exts = ilocbld.getLocaleExtensions();
3499                 } catch (LocaleSyntaxException e) {
3500                     // this should not happen
3501                     throw new RuntimeException(e);
3502                 }
3503             }
3504         }
3505 
3506         LanguageTag tag = LanguageTag.parseLocale(base, exts);
3507 
3508         StringBuilder buf = new StringBuilder();
3509         String subtag = tag.getLanguage();
3510         if (subtag.length() > 0) {
3511             buf.append(LanguageTag.canonicalizeLanguage(subtag));
3512         }
3513 
3514         subtag = tag.getScript();
3515         if (subtag.length() > 0) {
3516             buf.append(LanguageTag.SEP);
3517             buf.append(LanguageTag.canonicalizeScript(subtag));
3518         }
3519 
3520         subtag = tag.getRegion();
3521         if (subtag.length() > 0) {
3522             buf.append(LanguageTag.SEP);
3523             buf.append(LanguageTag.canonicalizeRegion(subtag));
3524         }
3525 
3526         List<String>subtags = tag.getVariants();
3527         // ICU-20478: Sort variants per UTS35.
3528         ArrayList<String> variants = new ArrayList<>(subtags);
3529         Collections.sort(variants);
3530         for (String s : variants) {
3531             buf.append(LanguageTag.SEP);
3532             buf.append(LanguageTag.canonicalizeVariant(s));
3533         }
3534 
3535         subtags = tag.getExtensions();
3536         for (String s : subtags) {
3537             buf.append(LanguageTag.SEP);
3538             buf.append(LanguageTag.canonicalizeExtension(s));
3539         }
3540 
3541         subtag = tag.getPrivateuse();
3542         if (subtag.length() > 0) {
3543             if (buf.length() == 0) {
3544                buf.append(UNDEFINED_LANGUAGE);
3545             }
3546             buf.append(LanguageTag.SEP);
3547             buf.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP);
3548             buf.append(LanguageTag.canonicalizePrivateuse(subtag));
3549         }
3550 
3551         return buf.toString();
3552     }
3553 
3554     /**
3555      * Returns a locale for the specified IETF BCP 47 language tag string.
3556      *
3557      * <p>If the specified language tag contains any ill-formed subtags,
3558      * the first such subtag and all following subtags are ignored.  Compare
3559      * to {@link ULocale.Builder#setLanguageTag} which throws an exception
3560      * in this case.
3561      *
3562      * <p>The following <b>conversions</b> are performed:
3563      * <ul>
3564      *
3565      * <li>The language code "und" is mapped to language "".
3566      *
3567      * <li>The portion of a private use subtag prefixed by "lvariant",
3568      * if any, is removed and appended to the variant field in the
3569      * result locale (without case normalization).  If it is then
3570      * empty, the private use subtag is discarded:
3571      *
3572      * <pre>
3573      *     ULocale loc;
3574      *     loc = ULocale.forLanguageTag("en-US-x-lvariant-icu4j);
3575      *     loc.getVariant(); // returns "ICU4J"
3576      *     loc.getExtension('x'); // returns null
3577      *
3578      *     loc = Locale.forLanguageTag("de-icu4j-x-URP-lvariant-Abc-Def");
3579      *     loc.getVariant(); // returns "ICU4J_ABC_DEF"
3580      *     loc.getExtension('x'); // returns "urp"
3581      * </pre>
3582      *
3583      * <li>When the languageTag argument contains an extlang subtag,
3584      * the first such subtag is used as the language, and the primary
3585      * language subtag and other extlang subtags are ignored:
3586      *
3587      * <pre>
3588      *     ULocale.forLanguageTag("ar-aao").getLanguage(); // returns "aao"
3589      *     ULocale.forLanguageTag("en-abc-def-us").toString(); // returns "abc_US"
3590      * </pre>
3591      *
3592      * <li>Case is normalized. Language is normalized to lower case,
3593      * script to title case, country to upper case, variant to upper case,
3594      * and extensions to lower case.
3595      *
3596      * </ul>
3597      *
3598      * <p>This implements the 'Language-Tag' production of BCP 47, and so
3599      * supports legacy language tags (marked as “Type: grandfathered” in BCP 47)
3600      * (regular and irregular) as well as private use language tags.
3601      *
3602      * <p>Stand-alone private use tags are represented as empty language and extension 'x-whatever',
3603      * and legacy tags are converted to their canonical replacements where they exist.
3604      *
3605      * <p>Note that a few legacy tags have no modern replacement;
3606      * these will be converted using the fallback described in
3607      * the first paragraph, so some information might be lost.
3608      *
3609      * <p><b>Note</b>: there is no guarantee that <code>toLanguageTag</code>
3610      * and <code>forLanguageTag</code> will round-trip.
3611      *
3612      * @param languageTag the language tag
3613      * @return The locale that best represents the language tag.
3614      * @throws NullPointerException if <code>languageTag</code> is <code>null</code>
3615      * @see #toLanguageTag()
3616      * @see ULocale.Builder#setLanguageTag(String)
3617      *
3618      * @stable ICU 4.2
3619      */
forLanguageTag(String languageTag)3620     public static ULocale forLanguageTag(String languageTag) {
3621         LanguageTag tag = LanguageTag.parse(languageTag, null);
3622         InternalLocaleBuilder bldr = new InternalLocaleBuilder();
3623         bldr.setLanguageTag(tag);
3624         return getInstance(bldr.getBaseLocale(), bldr.getLocaleExtensions());
3625     }
3626 
3627     /**
3628      * {@icu} Converts the specified keyword (legacy key, or BCP 47 Unicode locale
3629      * extension key) to the equivalent BCP 47 Unicode locale extension key.
3630      * For example, BCP 47 Unicode locale extension key "co" is returned for
3631      * the input keyword "collation".
3632      * <p>
3633      * When the specified keyword is unknown, but satisfies the BCP syntax,
3634      * then the lower-case version of the input keyword will be returned.
3635      * For example,
3636      * <code>toUnicodeLocaleKey("ZZ")</code> returns "zz".
3637      *
3638      * @param keyword       the input locale keyword (either legacy key
3639      *                      such as "collation" or BCP 47 Unicode locale extension
3640      *                      key such as "co").
3641      * @return              the well-formed BCP 47 Unicode locale extension key,
3642      *                      or null if the specified locale keyword cannot be mapped
3643      *                      to a well-formed BCP 47 Unicode locale extension key.
3644      * @see #toLegacyKey(String)
3645      * @stable ICU 54
3646      */
toUnicodeLocaleKey(String keyword)3647     public static String toUnicodeLocaleKey(String keyword) {
3648         String bcpKey = KeyTypeData.toBcpKey(keyword);
3649         if (bcpKey == null && UnicodeLocaleExtension.isKey(keyword)) {
3650             // unknown keyword, but syntax is fine..
3651             bcpKey = AsciiUtil.toLowerString(keyword);
3652         }
3653         return bcpKey;
3654     }
3655 
3656     /**
3657      * {@icu} Converts the specified keyword value (legacy type, or BCP 47
3658      * Unicode locale extension type) to the well-formed BCP 47 Unicode locale
3659      * extension type for the specified keyword (category). For example, BCP 47
3660      * Unicode locale extension type "phonebk" is returned for the input
3661      * keyword value "phonebook", with the keyword "collation" (or "co").
3662      * <p>
3663      * When the specified keyword is not recognized, but the specified value
3664      * satisfies the syntax of the BCP 47 Unicode locale extension type,
3665      * or when the specified keyword allows 'variable' type and the specified
3666      * value satisfies the syntax, the lower-case version of the input value
3667      * will be returned. For example,
3668      * <code>toUnicodeLocaleType("Foo", "Bar")</code> returns "bar",
3669      * <code>toUnicodeLocaleType("variableTop", "00A4")</code> returns "00a4".
3670      *
3671      * @param keyword       the locale keyword (either legacy key such as
3672      *                      "collation" or BCP 47 Unicode locale extension
3673      *                      key such as "co").
3674      * @param value         the locale keyword value (either legacy type
3675      *                      such as "phonebook" or BCP 47 Unicode locale extension
3676      *                      type such as "phonebk").
3677      * @return              the well-formed BCP47 Unicode locale extension type,
3678      *                      or null if the locale keyword value cannot be mapped to
3679      *                      a well-formed BCP 47 Unicode locale extension type.
3680      * @see #toLegacyType(String, String)
3681      * @stable ICU 54
3682      */
toUnicodeLocaleType(String keyword, String value)3683     public static String toUnicodeLocaleType(String keyword, String value) {
3684         String bcpType = KeyTypeData.toBcpType(keyword, value, null, null);
3685         if (bcpType == null && UnicodeLocaleExtension.isType(value)) {
3686             // unknown keyword, but syntax is fine..
3687             bcpType = AsciiUtil.toLowerString(value);
3688         }
3689         return bcpType;
3690     }
3691 
3692     /**
3693      * {@icu} Converts the specified keyword (BCP 47 Unicode locale extension key, or
3694      * legacy key) to the legacy key. For example, legacy key "collation" is
3695      * returned for the input BCP 47 Unicode locale extension key "co".
3696      *
3697      * @param keyword       the input locale keyword (either BCP 47 Unicode locale
3698      *                      extension key or legacy key).
3699      * @return              the well-formed legacy key, or null if the specified
3700      *                      keyword cannot be mapped to a well-formed legacy key.
3701      * @see #toUnicodeLocaleKey(String)
3702      * @stable ICU 54
3703      */
toLegacyKey(String keyword)3704     public static String toLegacyKey(String keyword) {
3705         String legacyKey = KeyTypeData.toLegacyKey(keyword);
3706         if (legacyKey == null) {
3707             // Checks if the specified locale key is well-formed with the legacy locale syntax.
3708             //
3709             // Note:
3710             //  Neither ICU nor LDML/CLDR provides the definition of keyword syntax.
3711             //  However, a key should not contain '=' obviously. For now, all existing
3712             //  keys are using ASCII alphabetic letters only. We won't add any new key
3713             //  that is not compatible with the BCP 47 syntax. Therefore, we assume
3714             //  a valid key consist from [0-9a-zA-Z], no symbols.
3715             if (keyword.matches("[0-9a-zA-Z]+")) {
3716                 legacyKey = AsciiUtil.toLowerString(keyword);
3717             }
3718         }
3719         return legacyKey;
3720     }
3721 
3722     /**
3723      * {@icu} Converts the specified keyword value (BCP 47 Unicode locale extension type,
3724      * or legacy type or type alias) to the canonical legacy type. For example,
3725      * the legacy type "phonebook" is returned for the input BCP 47 Unicode
3726      * locale extension type "phonebk" with the keyword "collation" (or "co").
3727      * <p>
3728      * When the specified keyword is not recognized, but the specified value
3729      * satisfies the syntax of legacy key, or when the specified keyword
3730      * allows 'variable' type and the specified value satisfies the syntax,
3731      * the lower-case version of the input value will be returned.
3732      * For example,
3733      * <code>toLegacyType("Foo", "Bar")</code> returns "bar",
3734      * <code>toLegacyType("vt", "00A4")</code> returns "00a4".
3735      *
3736      * @param keyword       the locale keyword (either legacy keyword such as
3737      *                      "collation" or BCP 47 Unicode locale extension
3738      *                      key such as "co").
3739      * @param value         the locale keyword value (either BCP 47 Unicode locale
3740      *                      extension type such as "phonebk" or legacy keyword value
3741      *                      such as "phonebook").
3742      * @return              the well-formed legacy type, or null if the specified
3743      *                      keyword value cannot be mapped to a well-formed legacy
3744      *                      type.
3745      * @see #toUnicodeLocaleType(String, String)
3746      * @stable ICU 54
3747      */
toLegacyType(String keyword, String value)3748     public static String toLegacyType(String keyword, String value) {
3749         String legacyType = KeyTypeData.toLegacyType(keyword, value, null, null);
3750         if (legacyType == null) {
3751             // Checks if the specified locale type is well-formed with the legacy locale syntax.
3752             //
3753             // Note:
3754             //  Neither ICU nor LDML/CLDR provides the definition of keyword syntax.
3755             //  However, a type should not contain '=' obviously. For now, all existing
3756             //  types are using ASCII alphabetic letters with a few symbol letters. We won't
3757             //  add any new type that is not compatible with the BCP 47 syntax except timezone
3758             //  IDs. For now, we assume a valid type start with [0-9a-zA-Z], but may contain
3759             //  '-' '_' '/' in the middle.
3760             if (value.matches("[0-9a-zA-Z]+([_/\\-][0-9a-zA-Z]+)*")) {
3761                 legacyType = AsciiUtil.toLowerString(value);
3762             }
3763         }
3764         return legacyType;
3765     }
3766 
3767     /**
3768      * <code>Builder</code> is used to build instances of <code>ULocale</code>
3769      * from values configured by the setters.  Unlike the <code>ULocale</code>
3770      * constructors, the <code>Builder</code> checks if a value configured by a
3771      * setter satisfies the syntax requirements defined by the <code>ULocale</code>
3772      * class.  A <code>ULocale</code> object created by a <code>Builder</code> is
3773      * well-formed and can be transformed to a well-formed IETF BCP 47 language tag
3774      * without losing information.
3775      *
3776      * <p><b>Note:</b> The <code>ULocale</code> class does not provide any
3777      * syntactic restrictions on variant, while BCP 47 requires each variant
3778      * subtag to be 5 to 8 alphanumerics or a single numeric followed by 3
3779      * alphanumerics.  The method <code>setVariant</code> throws
3780      * <code>IllformedLocaleException</code> for a variant that does not satisfy
3781      * this restriction. If it is necessary to support such a variant, use a
3782      * ULocale constructor.  However, keep in mind that a <code>ULocale</code>
3783      * object created this way might lose the variant information when
3784      * transformed to a BCP 47 language tag.
3785      *
3786      * <p>The following example shows how to create a <code>Locale</code> object
3787      * with the <code>Builder</code>.
3788      * <blockquote>
3789      * <pre>
3790      *     ULocale aLocale = new Builder().setLanguage("sr").setScript("Latn").setRegion("RS").build();
3791      * </pre>
3792      * </blockquote>
3793      *
3794      * <p>Builders can be reused; <code>clear()</code> resets all
3795      * fields to their default values.
3796      *
3797      * @see ULocale#toLanguageTag()
3798      *
3799      * @stable ICU 4.2
3800      */
3801     public static final class Builder {
3802 
3803         private final InternalLocaleBuilder _locbld;
3804 
3805         /**
3806          * Constructs an empty Builder. The default value of all
3807          * fields, extensions, and private use information is the
3808          * empty string.
3809          *
3810          * @stable ICU 4.2
3811          */
Builder()3812         public Builder() {
3813             _locbld = new InternalLocaleBuilder();
3814         }
3815 
3816         /**
3817          * Resets the <code>Builder</code> to match the provided
3818          * <code>locale</code>.  Existing state is discarded.
3819          *
3820          * <p>All fields of the locale must be well-formed, see {@link Locale}.
3821          *
3822          * <p>Locales with any ill-formed fields cause
3823          * <code>IllformedLocaleException</code> to be thrown.
3824          *
3825          * @param locale the locale
3826          * @return This builder.
3827          * @throws IllformedLocaleException if <code>locale</code> has
3828          * any ill-formed fields.
3829          * @throws NullPointerException if <code>locale</code> is null.
3830          *
3831          * @stable ICU 4.2
3832          */
setLocale(ULocale locale)3833         public Builder setLocale(ULocale locale) {
3834             try {
3835                 _locbld.setLocale(locale.base(), locale.extensions());
3836             } catch (LocaleSyntaxException e) {
3837                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
3838             }
3839             return this;
3840         }
3841 
3842         /**
3843          * Resets the Builder to match the provided IETF BCP 47
3844          * language tag.  Discards the existing state.  Null and the
3845          * empty string cause the builder to be reset, like {@link
3846          * #clear}.  Legacy tags (see {@link
3847          * ULocale#forLanguageTag}) are converted to their canonical
3848          * form before being processed.  Otherwise, the language tag
3849          * must be well-formed (see {@link ULocale}) or an exception is
3850          * thrown (unlike <code>ULocale.forLanguageTag</code>, which
3851          * just discards ill-formed and following portions of the
3852          * tag).
3853          *
3854          * @param languageTag the language tag
3855          * @return This builder.
3856          * @throws IllformedLocaleException if <code>languageTag</code> is ill-formed
3857          * @see ULocale#forLanguageTag(String)
3858          *
3859          * @stable ICU 4.2
3860          */
setLanguageTag(String languageTag)3861         public Builder setLanguageTag(String languageTag) {
3862             ParseStatus sts = new ParseStatus();
3863             LanguageTag tag = LanguageTag.parse(languageTag, sts);
3864             if (sts.isError()) {
3865                 throw new IllformedLocaleException(sts.getErrorMessage(), sts.getErrorIndex());
3866             }
3867             _locbld.setLanguageTag(tag);
3868 
3869             return this;
3870         }
3871 
3872         /**
3873          * Sets the language.  If <code>language</code> is the empty string or
3874          * null, the language in this <code>Builder</code> is removed.  Otherwise,
3875          * the language must be <a href="./Locale.html#def_language">well-formed</a>
3876          * or an exception is thrown.
3877          *
3878          * <p>The typical language value is a two or three-letter language
3879          * code as defined in ISO639.
3880          *
3881          * @param language the language
3882          * @return This builder.
3883          * @throws IllformedLocaleException if <code>language</code> is ill-formed
3884          *
3885          * @stable ICU 4.2
3886          */
setLanguage(String language)3887         public Builder setLanguage(String language) {
3888             try {
3889                 _locbld.setLanguage(language);
3890             } catch (LocaleSyntaxException e) {
3891                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
3892             }
3893             return this;
3894         }
3895 
3896         /**
3897          * Sets the script. If <code>script</code> is null or the empty string,
3898          * the script in this <code>Builder</code> is removed.
3899          * Otherwise, the script must be well-formed or an exception is thrown.
3900          *
3901          * <p>The typical script value is a four-letter script code as defined by ISO 15924.
3902          *
3903          * @param script the script
3904          * @return This builder.
3905          * @throws IllformedLocaleException if <code>script</code> is ill-formed
3906          *
3907          * @stable ICU 4.2
3908          */
setScript(String script)3909         public Builder setScript(String script) {
3910             try {
3911                 _locbld.setScript(script);
3912             } catch (LocaleSyntaxException e) {
3913                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
3914             }
3915             return this;
3916         }
3917 
3918         /**
3919          * Sets the region.  If region is null or the empty string, the region
3920          * in this <code>Builder</code> is removed.  Otherwise,
3921          * the region must be well-formed or an exception is thrown.
3922          *
3923          * <p>The typical region value is a two-letter ISO 3166 code or a
3924          * three-digit UN M.49 area code.
3925          *
3926          * <p>The country value in the <code>Locale</code> created by the
3927          * <code>Builder</code> is always normalized to upper case.
3928          *
3929          * @param region the region
3930          * @return This builder.
3931          * @throws IllformedLocaleException if <code>region</code> is ill-formed
3932          *
3933          * @stable ICU 4.2
3934          */
setRegion(String region)3935         public Builder setRegion(String region) {
3936             try {
3937                 _locbld.setRegion(region);
3938             } catch (LocaleSyntaxException e) {
3939                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
3940             }
3941             return this;
3942         }
3943 
3944         /**
3945          * Sets the variant.  If variant is null or the empty string, the
3946          * variant in this <code>Builder</code> is removed.  Otherwise, it
3947          * must consist of one or more well-formed subtags, or an exception is thrown.
3948          *
3949          * <p><b>Note:</b> This method checks if <code>variant</code>
3950          * satisfies the IETF BCP 47 variant subtag's syntax requirements,
3951          * and normalizes the value to lowercase letters.  However,
3952          * the <code>ULocale</code> class does not impose any syntactic
3953          * restriction on variant.  To set such a variant,
3954          * use a ULocale constructor.
3955          *
3956          * @param variant the variant
3957          * @return This builder.
3958          * @throws IllformedLocaleException if <code>variant</code> is ill-formed
3959          *
3960          * @stable ICU 4.2
3961          */
setVariant(String variant)3962         public Builder setVariant(String variant) {
3963             try {
3964                 _locbld.setVariant(variant);
3965             } catch (LocaleSyntaxException e) {
3966                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
3967             }
3968             return this;
3969         }
3970 
3971         /**
3972          * Sets the extension for the given key. If the value is null or the
3973          * empty string, the extension is removed.  Otherwise, the extension
3974          * must be well-formed or an exception is thrown.
3975          *
3976          * <p><b>Note:</b> The key {@link ULocale#UNICODE_LOCALE_EXTENSION
3977          * UNICODE_LOCALE_EXTENSION} ('u') is used for the Unicode locale extension.
3978          * Setting a value for this key replaces any existing Unicode locale key/type
3979          * pairs with those defined in the extension.
3980          *
3981          * <p><b>Note:</b> The key {@link ULocale#PRIVATE_USE_EXTENSION
3982          * PRIVATE_USE_EXTENSION} ('x') is used for the private use code. To be
3983          * well-formed, the value for this key needs only to have subtags of one to
3984          * eight alphanumeric characters, not two to eight as in the general case.
3985          *
3986          * @param key the extension key
3987          * @param value the extension value
3988          * @return This builder.
3989          * @throws IllformedLocaleException if <code>key</code> is illegal
3990          * or <code>value</code> is ill-formed
3991          * @see #setUnicodeLocaleKeyword(String, String)
3992          *
3993          * @stable ICU 4.2
3994          */
setExtension(char key, String value)3995         public Builder setExtension(char key, String value) {
3996             try {
3997                 _locbld.setExtension(key, value);
3998             } catch (LocaleSyntaxException e) {
3999                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
4000             }
4001             return this;
4002         }
4003 
4004         /**
4005          * Sets the Unicode locale keyword type for the given key.  If the type
4006          * is null, the Unicode keyword is removed.  Otherwise, the key must be
4007          * non-null and both key and type must be well-formed or an exception
4008          * is thrown.
4009          *
4010          * <p>Keys and types are converted to lower case.
4011          *
4012          * <p><b>Note</b>:Setting the 'u' extension via {@link #setExtension}
4013          * replaces all Unicode locale keywords with those defined in the
4014          * extension.
4015          *
4016          * @param key the Unicode locale key
4017          * @param type the Unicode locale type
4018          * @return This builder.
4019          * @throws IllformedLocaleException if <code>key</code> or <code>type</code>
4020          * is ill-formed
4021          * @throws NullPointerException if <code>key</code> is null
4022          * @see #setExtension(char, String)
4023          *
4024          * @stable ICU 4.4
4025          */
setUnicodeLocaleKeyword(String key, String type)4026         public Builder setUnicodeLocaleKeyword(String key, String type) {
4027             try {
4028                 _locbld.setUnicodeLocaleKeyword(key, type);
4029             } catch (LocaleSyntaxException e) {
4030                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
4031             }
4032             return this;
4033         }
4034 
4035         /**
4036          * Adds a unicode locale attribute, if not already present, otherwise
4037          * has no effect.  The attribute must not be null and must be well-formed
4038          * or an exception is thrown.
4039          *
4040          * @param attribute the attribute
4041          * @return This builder.
4042          * @throws NullPointerException if <code>attribute</code> is null
4043          * @throws IllformedLocaleException if <code>attribute</code> is ill-formed
4044          * @see #setExtension(char, String)
4045          *
4046          * @stable ICU 4.6
4047          */
addUnicodeLocaleAttribute(String attribute)4048         public Builder addUnicodeLocaleAttribute(String attribute) {
4049             try {
4050                 _locbld.addUnicodeLocaleAttribute(attribute);
4051             } catch (LocaleSyntaxException e) {
4052                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
4053             }
4054             return this;
4055         }
4056 
4057         /**
4058          * Removes a unicode locale attribute, if present, otherwise has no
4059          * effect.  The attribute must not be null and must be well-formed
4060          * or an exception is thrown.
4061          *
4062          * <p>Attribute comparison for removal is case-insensitive.
4063          *
4064          * @param attribute the attribute
4065          * @return This builder.
4066          * @throws NullPointerException if <code>attribute</code> is null
4067          * @throws IllformedLocaleException if <code>attribute</code> is ill-formed
4068          * @see #setExtension(char, String)
4069          *
4070          * @stable ICU 4.6
4071          */
removeUnicodeLocaleAttribute(String attribute)4072         public Builder removeUnicodeLocaleAttribute(String attribute) {
4073             try {
4074                 _locbld.removeUnicodeLocaleAttribute(attribute);
4075             } catch (LocaleSyntaxException e) {
4076                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());
4077             }
4078             return this;
4079         }
4080 
4081         /**
4082          * Resets the builder to its initial, empty state.
4083          *
4084          * @return this builder
4085          *
4086          * @stable ICU 4.2
4087          */
clear()4088         public Builder clear() {
4089             _locbld.clear();
4090             return this;
4091         }
4092 
4093         /**
4094          * Resets the extensions to their initial, empty state.
4095          * Language, script, region and variant are unchanged.
4096          *
4097          * @return this builder
4098          * @see #setExtension(char, String)
4099          *
4100          * @stable ICU 4.2
4101          */
clearExtensions()4102         public Builder clearExtensions() {
4103             _locbld.clearExtensions();
4104             return this;
4105         }
4106 
4107         /**
4108          * Returns an instance of <code>ULocale</code> created from the fields set
4109          * on this builder.
4110          *
4111          * @return a new Locale
4112          *
4113          * @stable ICU 4.4
4114          */
build()4115         public ULocale build() {
4116             return getInstance(_locbld.getBaseLocale(), _locbld.getLocaleExtensions());
4117         }
4118     }
4119 
getInstance(BaseLocale base, LocaleExtensions exts)4120     private static ULocale getInstance(BaseLocale base, LocaleExtensions exts) {
4121         String id = lscvToID(base.getLanguage(), base.getScript(), base.getRegion(),
4122                 base.getVariant());
4123 
4124         Set<Character> extKeys = exts.getKeys();
4125         if (!extKeys.isEmpty()) {
4126             // legacy locale ID assume Unicode locale keywords and
4127             // other extensions are at the same level.
4128             // e.g. @a=ext-for-aa;calendar=japanese;m=ext-for-mm;x=priv-use
4129 
4130             TreeMap<String, String> kwds = new TreeMap<>();
4131             for (Character key : extKeys) {
4132                 Extension ext = exts.getExtension(key);
4133                 if (ext instanceof UnicodeLocaleExtension) {
4134                     UnicodeLocaleExtension uext = (UnicodeLocaleExtension)ext;
4135                     Set<String> ukeys = uext.getUnicodeLocaleKeys();
4136                     for (String bcpKey : ukeys) {
4137                         String bcpType = uext.getUnicodeLocaleType(bcpKey);
4138                         // convert to legacy key/type
4139                         String lkey = toLegacyKey(bcpKey);
4140                         String ltype = toLegacyType(bcpKey, ((bcpType.length() == 0) ? "yes" : bcpType)); // use "yes" as the value of typeless keywords
4141                         // special handling for u-va-posix, since this is a variant, not a keyword
4142                         if (lkey.equals("va") && ltype.equals("posix") && base.getVariant().length() == 0) {
4143                             id = id + "_POSIX";
4144                         } else {
4145                             kwds.put(lkey, ltype);
4146                         }
4147                     }
4148                     // Mapping Unicode locale attribute to the special keyword, attribute=xxx-yyy
4149                     Set<String> uattributes = uext.getUnicodeLocaleAttributes();
4150                     if (uattributes.size() > 0) {
4151                         StringBuilder attrbuf = new StringBuilder();
4152                         for (String attr : uattributes) {
4153                             if (attrbuf.length() > 0) {
4154                                 attrbuf.append('-');
4155                             }
4156                             attrbuf.append(attr);
4157                         }
4158                         kwds.put(LOCALE_ATTRIBUTE_KEY, attrbuf.toString());
4159                     }
4160                 } else {
4161                     kwds.put(String.valueOf(key), ext.getValue());
4162                 }
4163             }
4164 
4165             if (!kwds.isEmpty()) {
4166                 StringBuilder buf = new StringBuilder(id);
4167                 buf.append("@");
4168                 Set<Map.Entry<String, String>> kset = kwds.entrySet();
4169                 boolean insertSep = false;
4170                 for (Map.Entry<String, String> kwd : kset) {
4171                     if (insertSep) {
4172                         buf.append(";");
4173                     } else {
4174                         insertSep = true;
4175                     }
4176                     buf.append(kwd.getKey());
4177                     buf.append("=");
4178                     buf.append(kwd.getValue());
4179                 }
4180 
4181                 id = buf.toString();
4182             }
4183         }
4184         return new ULocale(id);
4185     }
4186 
base()4187     private BaseLocale base() {
4188         if (baseLocale == null) {
4189             String language, script, region, variant;
4190             language = script = region = variant = "";
4191             if (!equals(ULocale.ROOT)) {
4192                 LocaleIDParser lp = new LocaleIDParser(localeID);
4193                 language = lp.getLanguage();
4194                 script = lp.getScript();
4195                 region = lp.getCountry();
4196                 variant = lp.getVariant();
4197             }
4198             baseLocale = BaseLocale.getInstance(language, script, region, variant);
4199         }
4200         return baseLocale;
4201     }
4202 
extensions()4203     private LocaleExtensions extensions() {
4204         if (extensions == null) {
4205             Iterator<String> kwitr = getKeywords();
4206             if (kwitr == null) {
4207                 extensions = LocaleExtensions.EMPTY_EXTENSIONS;
4208             } else {
4209                 InternalLocaleBuilder intbld = new InternalLocaleBuilder();
4210                 while (kwitr.hasNext()) {
4211                     String key = kwitr.next();
4212                     if (key.equals(LOCALE_ATTRIBUTE_KEY)) {
4213                         // special keyword used for representing Unicode locale attributes
4214                         String[] uattributes = getKeywordValue(key).split("[-_]");
4215                         for (String uattr : uattributes) {
4216                             try {
4217                                 intbld.addUnicodeLocaleAttribute(uattr);
4218                             } catch (LocaleSyntaxException e) {
4219                                 // ignore and fall through
4220                             }
4221                         }
4222                     } else if (key.length() >= 2) {
4223                         String bcpKey = toUnicodeLocaleKey(key);
4224                         String bcpType = toUnicodeLocaleType(key, getKeywordValue(key));
4225                         if (bcpKey != null && bcpType != null) {
4226                             try {
4227                                 intbld.setUnicodeLocaleKeyword(bcpKey, bcpType);
4228                             } catch (LocaleSyntaxException e) {
4229                                 // ignore and fall through
4230                             }
4231                         }
4232                     } else if (key.length() == 1 && (key.charAt(0) != UNICODE_LOCALE_EXTENSION)) {
4233                         try  {
4234                             intbld.setExtension(key.charAt(0), getKeywordValue(key).replace("_",
4235                                     LanguageTag.SEP));
4236                         } catch (LocaleSyntaxException e) {
4237                             // ignore and fall through
4238                         }
4239                     }
4240                 }
4241                 extensions = intbld.getLocaleExtensions();
4242             }
4243         }
4244         return extensions;
4245     }
4246 
4247     /*
4248      * JDK Locale Helper
4249      */
4250     private static final class JDKLocaleHelper {
4251         // Java 7 has java.util.Locale.Category.
4252         // Android API level 21..23 do not yet have it; only API level 24 (Nougat) adds it.
4253         // https://developer.android.com/reference/java/util/Locale.Category
4254         private static boolean hasLocaleCategories = false;
4255 
4256         private static Method mGetDefault;
4257         private static Method mSetDefault;
4258         private static Object eDISPLAY;
4259         private static Object eFORMAT;
4260 
4261         static {
4262             do {
4263                 try {
4264                     Class<?> cCategory = null;
4265                     Class<?>[] classes = Locale.class.getDeclaredClasses();
4266                     for (Class<?> c : classes) {
4267                         if (c.getName().equals("java.util.Locale$Category")) {
4268                             cCategory = c;
4269                             break;
4270                         }
4271                     }
4272                     if (cCategory == null) {
4273                         break;
4274                     }
4275                     mGetDefault = Locale.class.getDeclaredMethod("getDefault", cCategory);
4276                     mSetDefault = Locale.class.getDeclaredMethod("setDefault", cCategory, Locale.class);
4277 
4278                     Method mName = cCategory.getMethod("name", (Class[]) null);
4279                     Object[] enumConstants = cCategory.getEnumConstants();
4280                     for (Object e : enumConstants) {
4281                         String catVal = (String)mName.invoke(e, (Object[])null);
4282                         if (catVal.equals("DISPLAY")) {
4283                             eDISPLAY = e;
4284                         } else if (catVal.equals("FORMAT")) {
4285                             eFORMAT = e;
4286                         }
4287                     }
4288                     if (eDISPLAY == null || eFORMAT == null) {
4289                         break;
4290                     }
4291 
4292                     hasLocaleCategories = true;
4293                 } catch (NoSuchMethodException e) {
4294                 } catch (IllegalArgumentException e) {
4295                 } catch (IllegalAccessException e) {
4296                 } catch (InvocationTargetException e) {
4297                 } catch (SecurityException e) {
4298                     // TODO : report?
4299                 }
4300             } while (false);
4301         }
4302 
JDKLocaleHelper()4303         private JDKLocaleHelper() {
4304         }
4305 
hasLocaleCategories()4306         public static boolean hasLocaleCategories() {
4307             return hasLocaleCategories;
4308         }
4309 
toULocale(Locale loc)4310         public static ULocale toULocale(Locale loc) {
4311             String language = loc.getLanguage();
4312             String script = "";
4313             String country = loc.getCountry();
4314             String variant = loc.getVariant();
4315 
4316             Set<String> attributes = null;
4317             Map<String, String> keywords = null;
4318 
4319             script = loc.getScript();
4320             Set<Character> extKeys = loc.getExtensionKeys();
4321             if (!extKeys.isEmpty()) {
4322                 for (Character extKey : extKeys) {
4323                     if (extKey.charValue() == 'u') {
4324                         // Found Unicode locale extension
4325 
4326                         // attributes
4327                         @SuppressWarnings("unchecked")
4328                         Set<String> uAttributes = loc.getUnicodeLocaleAttributes();
4329                         if (!uAttributes.isEmpty()) {
4330                             attributes = new TreeSet<>();
4331                             for (String attr : uAttributes) {
4332                                 attributes.add(attr);
4333                             }
4334                         }
4335 
4336                         // keywords
4337                         Set<String> uKeys = loc.getUnicodeLocaleKeys();
4338                         for (String kwKey : uKeys) {
4339                             String kwVal = loc.getUnicodeLocaleType(kwKey);
4340                             if (kwVal != null) {
4341                                 if (kwKey.equals("va")) {
4342                                     // va-* is interpreted as a variant
4343                                     variant = (variant.length() == 0) ? kwVal : kwVal + "_" + variant;
4344                                 } else {
4345                                     if (keywords == null) {
4346                                         keywords = new TreeMap<>();
4347                                     }
4348                                     keywords.put(kwKey, kwVal);
4349                                 }
4350                             }
4351                         }
4352                     } else {
4353                         String extVal = loc.getExtension(extKey);
4354                         if (extVal != null) {
4355                             if (keywords == null) {
4356                                 keywords = new TreeMap<>();
4357                             }
4358                             keywords.put(String.valueOf(extKey), extVal);
4359                         }
4360                     }
4361                 }
4362             }
4363 
4364             // JDK locale no_NO_NY is not interpreted as Nynorsk by ICU,
4365             // and it should be transformed to nn_NO.
4366 
4367             // Note: JDK7+ unerstand both no_NO_NY and nn_NO. When convert
4368             // ICU locale to JDK, we do not need to map nn_NO back to no_NO_NY.
4369 
4370             if (language.equals("no") && country.equals("NO") && variant.equals("NY")) {
4371                 language = "nn";
4372                 variant = "";
4373             }
4374 
4375             // Constructing ID
4376             StringBuilder buf = new StringBuilder(language);
4377 
4378             if (script.length() > 0) {
4379                 buf.append('_');
4380                 buf.append(script);
4381             }
4382 
4383             if (country.length() > 0) {
4384                 buf.append('_');
4385                 buf.append(country);
4386             }
4387 
4388             if (variant.length() > 0) {
4389                 if (country.length() == 0) {
4390                     buf.append('_');
4391                 }
4392                 buf.append('_');
4393                 buf.append(variant);
4394             }
4395 
4396             if (attributes != null) {
4397                 // transform Unicode attributes into a keyword
4398                 StringBuilder attrBuf = new StringBuilder();
4399                 for (String attr : attributes) {
4400                     if (attrBuf.length() != 0) {
4401                         attrBuf.append('-');
4402                     }
4403                     attrBuf.append(attr);
4404                 }
4405                 if (keywords == null) {
4406                     keywords = new TreeMap<>();
4407                 }
4408                 keywords.put(LOCALE_ATTRIBUTE_KEY, attrBuf.toString());
4409             }
4410 
4411             if (keywords != null) {
4412                 buf.append('@');
4413                 boolean addSep = false;
4414                 for (Entry<String, String> kwEntry : keywords.entrySet()) {
4415                     String kwKey = kwEntry.getKey();
4416                     String kwVal = kwEntry.getValue();
4417 
4418                     if (kwKey.length() != 1) {
4419                         // Unicode locale key
4420                         kwKey = toLegacyKey(kwKey);
4421                         // use "yes" as the value of typeless keywords
4422                         kwVal = toLegacyType(kwKey, ((kwVal.length() == 0) ? "yes" : kwVal));
4423                     }
4424 
4425                     if (addSep) {
4426                         buf.append(';');
4427                     } else {
4428                         addSep = true;
4429                     }
4430                     buf.append(kwKey);
4431                     buf.append('=');
4432                     buf.append(kwVal);
4433                 }
4434             }
4435 
4436             return new ULocale(getName(buf.toString()), loc);
4437         }
4438 
toLocale(ULocale uloc)4439         public static Locale toLocale(ULocale uloc) {
4440             Locale loc = null;
4441             String ulocStr = uloc.getName();
4442             if (uloc.getScript().length() > 0 || ulocStr.contains("@")) {
4443                 // With script or keywords available, the best way
4444                 // to get a mapped Locale is to go through a language tag.
4445                 // A Locale with script or keywords can only have variants
4446                 // that is 1 to 8 alphanum. If this ULocale has a variant
4447                 // subtag not satisfying the criteria, the variant subtag
4448                 // will be lost.
4449                 String tag = uloc.toLanguageTag();
4450 
4451                 // Workaround for variant casing problem:
4452                 //
4453                 // The variant field in ICU is case insensitive and normalized
4454                 // to upper case letters by getVariant(), while
4455                 // the variant field in JDK Locale is case sensitive.
4456                 // ULocale#toLanguageTag use lower case characters for
4457                 // BCP 47 variant and private use x-lvariant.
4458                 //
4459                 // Locale#forLanguageTag in JDK preserves character casing
4460                 // for variant. Because ICU always normalizes variant to
4461                 // upper case, we convert language tag to upper case here.
4462                 tag = AsciiUtil.toUpperString(tag);
4463                 loc = Locale.forLanguageTag(tag);
4464             }
4465             if (loc == null) {
4466                 // Without script or keywords, use a Locale constructor,
4467                 // so we can preserve any ill-formed variants.
4468                 loc = new Locale(uloc.getLanguage(), uloc.getCountry(), uloc.getVariant());
4469             }
4470             return loc;
4471         }
4472 
getDefault(Category category)4473         public static Locale getDefault(Category category) {
4474             if (hasLocaleCategories) {
4475                 Object cat = null;
4476                 switch (category) {
4477                 case DISPLAY:
4478                     cat = eDISPLAY;
4479                     break;
4480                 case FORMAT:
4481                     cat = eFORMAT;
4482                     break;
4483                 }
4484                 if (cat != null) {
4485                     try {
4486                         return (Locale)mGetDefault.invoke(null, cat);
4487                     } catch (InvocationTargetException e) {
4488                         // fall through - use the base default
4489                     } catch (IllegalArgumentException e) {
4490                         // fall through - use the base default
4491                     } catch (IllegalAccessException e) {
4492                         // fall through - use the base default
4493                     }
4494                 }
4495             }
4496             return Locale.getDefault();
4497         }
4498 
setDefault(Category category, Locale newLocale)4499         public static void setDefault(Category category, Locale newLocale) {
4500             if (hasLocaleCategories) {
4501                 Object cat = null;
4502                 switch (category) {
4503                 case DISPLAY:
4504                     cat = eDISPLAY;
4505                     break;
4506                 case FORMAT:
4507                     cat = eFORMAT;
4508                     break;
4509                 }
4510                 if (cat != null) {
4511                     try {
4512                         mSetDefault.invoke(null, cat, newLocale);
4513                     } catch (InvocationTargetException e) {
4514                         // fall through - no effects
4515                     } catch (IllegalArgumentException e) {
4516                         // fall through - no effects
4517                     } catch (IllegalAccessException e) {
4518                         // fall through - no effects
4519                     }
4520                 }
4521             }
4522         }
4523     }
4524 }
4525