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