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