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