]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/main/classes/core/src/com/ibm/icu/util/ULocale.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / main / classes / core / src / com / ibm / icu / util / ULocale.java
1 /*\r
2 ******************************************************************************\r
3 * Copyright (C) 2003-2010, International Business Machines Corporation and   *\r
4 * others. All Rights Reserved.                                               *\r
5 ******************************************************************************\r
6 */\r
7 \r
8 package com.ibm.icu.util;\r
9 \r
10 import java.io.Serializable;\r
11 import java.text.ParseException;\r
12 import java.util.Iterator;\r
13 import java.util.Locale;\r
14 import java.util.Map;\r
15 import java.util.MissingResourceException;\r
16 import java.util.Set;\r
17 import java.util.TreeMap;\r
18 \r
19 import com.ibm.icu.impl.ICUCache;\r
20 import com.ibm.icu.impl.ICUResourceBundle;\r
21 import com.ibm.icu.impl.ICUResourceTableAccess;\r
22 import com.ibm.icu.impl.LocaleIDParser;\r
23 import com.ibm.icu.impl.LocaleIDs;\r
24 import com.ibm.icu.impl.LocaleUtility;\r
25 import com.ibm.icu.impl.SimpleCache;\r
26 import com.ibm.icu.impl.locale.AsciiUtil;\r
27 import com.ibm.icu.impl.locale.BaseLocale;\r
28 import com.ibm.icu.impl.locale.Extension;\r
29 import com.ibm.icu.impl.locale.InternalLocaleBuilder;\r
30 import com.ibm.icu.impl.locale.LanguageTag;\r
31 import com.ibm.icu.impl.locale.LocaleExtensions;\r
32 import com.ibm.icu.impl.locale.LocaleSyntaxException;\r
33 import com.ibm.icu.impl.locale.UnicodeLocaleExtension;\r
34 import com.ibm.icu.text.LocaleDisplayNames;\r
35 import com.ibm.icu.text.LocaleDisplayNames.DialectHandling;\r
36 \r
37 /**\r
38  * {@icuenhanced java.util.Locale}.{@icu _usage_}\r
39  *\r
40  * A class analogous to {@link java.util.Locale} that provides additional\r
41  * support for ICU protocol.  In ICU 3.0 this class is enhanced to support\r
42  * RFC 3066 language identifiers.\r
43  *\r
44  * <p>Many classes and services in ICU follow a factory idiom, in\r
45  * which a factory method or object responds to a client request with\r
46  * an object.  The request includes a locale (the <i>requested</i>\r
47  * locale), and the returned object is constructed using data for that\r
48  * locale.  The system may lack data for the requested locale, in\r
49  * which case the locale fallback mechanism will be invoked until a\r
50  * populated locale is found (the <i>valid</i> locale).  Furthermore,\r
51  * even when a populated locale is found (the <i>valid</i> locale),\r
52  * further fallback may be required to reach a locale containing the\r
53  * specific data required by the service (the <i>actual</i> locale).\r
54  *\r
55  * <p>ULocale performs <b>'normalization'</b> and <b>'canonicalization'</b> of locale ids.\r
56  * Normalization 'cleans up' ICU locale ids as follows:\r
57  * <ul>\r
58  * <li>language, script, country, variant, and keywords are properly cased<br>\r
59  * (lower, title, upper, upper, and lower case respectively)</li>\r
60  * <li>hyphens used as separators are converted to underscores</li>\r
61  * <li>three-letter language and country ids are converted to two-letter\r
62  * equivalents where available</li>\r
63  * <li>surrounding spaces are removed from keywords and values</li>\r
64  * <li>if there are multiple keywords, they are put in sorted order</li>\r
65  * </ul>\r
66  * Canonicalization additionally performs the following:\r
67  * <ul>\r
68  * <li>POSIX ids are converted to ICU format IDs</li>\r
69  * <li>'grandfathered' 3066 ids are converted to ICU standard form</li>\r
70  * <li>'PREEURO' and 'EURO' variants are converted to currency keyword form,\r
71  * with the currency\r
72  * id appropriate to the country of the locale (for PREEURO) or EUR (for EURO).\r
73  * </ul>\r
74  * All ULocale constructors automatically normalize the locale id.  To handle\r
75  * POSIX ids, <code>canonicalize</code> can be called to convert the id\r
76  * to canonical form, or the <code>canonicalInstance</code> factory method\r
77  * can be called.</p>\r
78  *\r
79  * <p>This class provides selectors {@link #VALID_LOCALE} and {@link\r
80  * #ACTUAL_LOCALE} intended for use in methods named\r
81  * <tt>getLocale()</tt>.  These methods exist in several ICU classes,\r
82  * including {@link com.ibm.icu.util.Calendar}, {@link\r
83  * com.ibm.icu.util.Currency}, {@link com.ibm.icu.text.UFormat},\r
84  * {@link com.ibm.icu.text.BreakIterator},\r
85  * <a href="../text/Collator.html" title="class in com.ibm.icu.text"><code>Collator</code></a>,\r
86  * {@link com.ibm.icu.text.DateFormatSymbols}, and {@link\r
87  * com.ibm.icu.text.DecimalFormatSymbols} and their subclasses, if\r
88  * any.  Once an object of one of these classes has been created,\r
89  * <tt>getLocale()</tt> may be called on it to determine the valid and\r
90  * actual locale arrived at during the object's construction.\r
91  *\r
92  * <p>Note: The <i>actual</i> locale is returned correctly, but the <i>valid</i>\r
93  * locale is not, in most cases.\r
94  *\r
95  * @see java.util.Locale\r
96  * @author weiv\r
97  * @author Alan Liu\r
98  * @author Ram Viswanadha\r
99  * @stable ICU 2.8\r
100  */\r
101 public final class ULocale implements Serializable {\r
102     // using serialver from jdk1.4.2_05\r
103     private static final long serialVersionUID = 3715177670352309217L;\r
104 \r
105     /**\r
106      * Useful constant for language.\r
107      * @stable ICU 3.0\r
108      */\r
109     public static final ULocale ENGLISH = new ULocale("en", Locale.ENGLISH);\r
110 \r
111     /**\r
112      * Useful constant for language.\r
113      * @stable ICU 3.0\r
114      */\r
115     public static final ULocale FRENCH = new ULocale("fr", Locale.FRENCH);\r
116 \r
117     /**\r
118      * Useful constant for language.\r
119      * @stable ICU 3.0\r
120      */\r
121     public static final ULocale GERMAN = new ULocale("de", Locale.GERMAN);\r
122 \r
123     /**\r
124      * Useful constant for language.\r
125      * @stable ICU 3.0\r
126      */\r
127     public static final ULocale ITALIAN = new ULocale("it", Locale.ITALIAN);\r
128 \r
129     /**\r
130      * Useful constant for language.\r
131      * @stable ICU 3.0\r
132      */\r
133     public static final ULocale JAPANESE = new ULocale("ja", Locale.JAPANESE);\r
134 \r
135     /**\r
136      * Useful constant for language.\r
137      * @stable ICU 3.0\r
138      */\r
139     public static final ULocale KOREAN = new ULocale("ko", Locale.KOREAN);\r
140 \r
141     /**\r
142      * Useful constant for language.\r
143      * @stable ICU 3.0\r
144      */\r
145     public static final ULocale CHINESE = new ULocale("zh", Locale.CHINESE);\r
146 \r
147     /**\r
148      * Useful constant for language.\r
149      * @stable ICU 3.0\r
150      */\r
151     public static final ULocale SIMPLIFIED_CHINESE = new ULocale("zh_Hans", Locale.CHINESE);\r
152 \r
153     /**\r
154      * Useful constant for language.\r
155      * @stable ICU 3.0\r
156      */\r
157     public static final ULocale TRADITIONAL_CHINESE = new ULocale("zh_Hant", Locale.CHINESE);\r
158 \r
159     /**\r
160      * Useful constant for country/region.\r
161      * @stable ICU 3.0\r
162      */\r
163     public static final ULocale FRANCE = new ULocale("fr_FR", Locale.FRANCE);\r
164 \r
165     /**\r
166      * Useful constant for country/region.\r
167      * @stable ICU 3.0\r
168      */\r
169     public static final ULocale GERMANY = new ULocale("de_DE", Locale.GERMANY);\r
170 \r
171     /**\r
172      * Useful constant for country/region.\r
173      * @stable ICU 3.0\r
174      */\r
175     public static final ULocale ITALY = new ULocale("it_IT", Locale.ITALY);\r
176 \r
177     /**\r
178      * Useful constant for country/region.\r
179      * @stable ICU 3.0\r
180      */\r
181     public static final ULocale JAPAN = new ULocale("ja_JP", Locale.JAPAN);\r
182 \r
183     /**\r
184      * Useful constant for country/region.\r
185      * @stable ICU 3.0\r
186      */\r
187     public static final ULocale KOREA = new ULocale("ko_KR", Locale.KOREA);\r
188 \r
189     /**\r
190      * Useful constant for country/region.\r
191      * @stable ICU 3.0\r
192      */\r
193     public static final ULocale CHINA = new ULocale("zh_Hans_CN", Locale.CHINA);\r
194 \r
195     /**\r
196      * Useful constant for country/region.\r
197      * @stable ICU 3.0\r
198      */\r
199     public static final ULocale PRC = CHINA;\r
200 \r
201     /**\r
202      * Useful constant for country/region.\r
203      * @stable ICU 3.0\r
204      */\r
205     public static final ULocale TAIWAN = new ULocale("zh_Hant_TW", Locale.TAIWAN);\r
206 \r
207     /**\r
208      * Useful constant for country/region.\r
209      * @stable ICU 3.0\r
210      */\r
211     public static final ULocale UK = new ULocale("en_GB", Locale.UK);\r
212 \r
213     /**\r
214      * Useful constant for country/region.\r
215      * @stable ICU 3.0\r
216      */\r
217     public static final ULocale US = new ULocale("en_US", Locale.US);\r
218 \r
219     /**\r
220      * Useful constant for country/region.\r
221      * @stable ICU 3.0\r
222      */\r
223     public static final ULocale CANADA = new ULocale("en_CA", Locale.CANADA);\r
224 \r
225     /**\r
226      * Useful constant for country/region.\r
227      * @stable ICU 3.0\r
228      */\r
229     public static final ULocale CANADA_FRENCH = new ULocale("fr_CA", Locale.CANADA_FRENCH);\r
230 \r
231     /**\r
232      * Handy constant.\r
233      */\r
234     private static final String EMPTY_STRING = "";\r
235 \r
236     // Used in both ULocale and LocaleIDParser, so moved up here.\r
237     private static final char UNDERSCORE            = '_';\r
238 \r
239     // default empty locale\r
240     private static final Locale EMPTY_LOCALE = new Locale("", "");\r
241 \r
242     /**\r
243      * The root ULocale.\r
244      * @stable ICU 2.8\r
245      */\r
246     public static final ULocale ROOT = new ULocale("", EMPTY_LOCALE);\r
247 \r
248     private static final SimpleCache<Locale, ULocale> CACHE = new SimpleCache<Locale, ULocale>();\r
249 \r
250     /**\r
251      * Cache the locale.\r
252      */\r
253     private transient volatile Locale locale;\r
254 \r
255     /**\r
256      * The raw localeID that we were passed in.\r
257      */\r
258     private String localeID;\r
259 \r
260     /**\r
261      * Cache the locale data container fields.\r
262      * In future, we want to use them as the primary locale identifier storage.\r
263      */\r
264     private transient volatile BaseLocale baseLocale;\r
265     private transient volatile LocaleExtensions extensions;\r
266 \r
267 \r
268     private static String[][] CANONICALIZE_MAP;\r
269     private static String[][] variantsToKeywords;\r
270 \r
271     private static void initCANONICALIZE_MAP() {\r
272         if (CANONICALIZE_MAP == null) {\r
273             /**\r
274              * This table lists pairs of locale ids for canonicalization.  The\r
275              * The 1st item is the normalized id. The 2nd item is the\r
276              * canonicalized id. The 3rd is the keyword. The 4th is the keyword value.\r
277              */\r
278             String[][] tempCANONICALIZE_MAP = {\r
279 //              { EMPTY_STRING,     "en_US_POSIX", null, null }, /* .NET name */\r
280                 { "C",              "en_US_POSIX", null, null }, /* POSIX name */\r
281                 { "art_LOJBAN",     "jbo", null, null }, /* registered name */\r
282                 { "az_AZ_CYRL",     "az_Cyrl_AZ", null, null }, /* .NET name */\r
283                 { "az_AZ_LATN",     "az_Latn_AZ", null, null }, /* .NET name */\r
284                 { "ca_ES_PREEURO",  "ca_ES", "currency", "ESP" },\r
285                 { "cel_GAULISH",    "cel__GAULISH", null, null }, /* registered name */\r
286                 { "de_1901",        "de__1901", null, null }, /* registered name */\r
287                 { "de_1906",        "de__1906", null, null }, /* registered name */\r
288                 { "de__PHONEBOOK",  "de", "collation", "phonebook" }, /* Old ICU name */\r
289                 { "de_AT_PREEURO",  "de_AT", "currency", "ATS" },\r
290                 { "de_DE_PREEURO",  "de_DE", "currency", "DEM" },\r
291                 { "de_LU_PREEURO",  "de_LU", "currency", "EUR" },\r
292                 { "el_GR_PREEURO",  "el_GR", "currency", "GRD" },\r
293                 { "en_BOONT",       "en__BOONT", null, null }, /* registered name */\r
294                 { "en_SCOUSE",      "en__SCOUSE", null, null }, /* registered name */\r
295                 { "en_BE_PREEURO",  "en_BE", "currency", "BEF" },\r
296                 { "en_IE_PREEURO",  "en_IE", "currency", "IEP" },\r
297                 { "es__TRADITIONAL", "es", "collation", "traditional" }, /* Old ICU name */\r
298                 { "es_ES_PREEURO",  "es_ES", "currency", "ESP" },\r
299                 { "eu_ES_PREEURO",  "eu_ES", "currency", "ESP" },\r
300                 { "fi_FI_PREEURO",  "fi_FI", "currency", "FIM" },\r
301                 { "fr_BE_PREEURO",  "fr_BE", "currency", "BEF" },\r
302                 { "fr_FR_PREEURO",  "fr_FR", "currency", "FRF" },\r
303                 { "fr_LU_PREEURO",  "fr_LU", "currency", "LUF" },\r
304                 { "ga_IE_PREEURO",  "ga_IE", "currency", "IEP" },\r
305                 { "gl_ES_PREEURO",  "gl_ES", "currency", "ESP" },\r
306                 { "hi__DIRECT",     "hi", "collation", "direct" }, /* Old ICU name */\r
307                 { "it_IT_PREEURO",  "it_IT", "currency", "ITL" },\r
308                 { "ja_JP_TRADITIONAL", "ja_JP", "calendar", "japanese" },\r
309 //              { "nb_NO_NY",       "nn_NO", null, null },\r
310                 { "nl_BE_PREEURO",  "nl_BE", "currency", "BEF" },\r
311                 { "nl_NL_PREEURO",  "nl_NL", "currency", "NLG" },\r
312                 { "pt_PT_PREEURO",  "pt_PT", "currency", "PTE" },\r
313                 { "sl_ROZAJ",       "sl__ROZAJ", null, null }, /* registered name */\r
314                 { "sr_SP_CYRL",     "sr_Cyrl_RS", null, null }, /* .NET name */\r
315                 { "sr_SP_LATN",     "sr_Latn_RS", null, null }, /* .NET name */\r
316                 { "sr_YU_CYRILLIC", "sr_Cyrl_RS", null, null }, /* Linux name */\r
317                 { "th_TH_TRADITIONAL", "th_TH", "calendar", "buddhist" }, /* Old ICU name */\r
318                 { "uz_UZ_CYRILLIC", "uz_Cyrl_UZ", null, null }, /* Linux name */\r
319                 { "uz_UZ_CYRL",     "uz_Cyrl_UZ", null, null }, /* .NET name */\r
320                 { "uz_UZ_LATN",     "uz_Latn_UZ", null, null }, /* .NET name */\r
321                 { "zh_CHS",         "zh_Hans", null, null }, /* .NET name */\r
322                 { "zh_CHT",         "zh_Hant", null, null }, /* .NET name */\r
323                 { "zh_GAN",         "zh__GAN", null, null }, /* registered name */\r
324                 { "zh_GUOYU",       "zh", null, null }, /* registered name */\r
325                 { "zh_HAKKA",       "zh__HAKKA", null, null }, /* registered name */\r
326                 { "zh_MIN",         "zh__MIN", null, null }, /* registered name */\r
327                 { "zh_MIN_NAN",     "zh__MINNAN", null, null }, /* registered name */\r
328                 { "zh_WUU",         "zh__WUU", null, null }, /* registered name */\r
329                 { "zh_XIANG",       "zh__XIANG", null, null }, /* registered name */\r
330                 { "zh_YUE",         "zh__YUE", null, null } /* registered name */\r
331             };\r
332 \r
333             synchronized (ULocale.class) {\r
334                 if (CANONICALIZE_MAP == null) {\r
335                     CANONICALIZE_MAP = tempCANONICALIZE_MAP;\r
336                 }\r
337             }\r
338         }\r
339         if (variantsToKeywords == null) {\r
340             /**\r
341              * This table lists pairs of locale ids for canonicalization.  The\r
342              * The first item is the normalized variant id.\r
343              */\r
344             String[][] tempVariantsToKeywords = {\r
345                     { "EURO",   "currency", "EUR" },\r
346                     { "PINYIN", "collation", "pinyin" }, /* Solaris variant */\r
347                     { "STROKE", "collation", "stroke" }  /* Solaris variant */\r
348             };\r
349 \r
350             synchronized (ULocale.class) {\r
351                 if (variantsToKeywords == null) {\r
352                     variantsToKeywords = tempVariantsToKeywords;\r
353                 }\r
354             }\r
355         }\r
356     }\r
357 \r
358     /*\r
359      * This table is used for mapping between ICU and special Java\r
360      * locales.  When an ICU locale matches <minumum base> with\r
361      * <keyword>/<value>, the ICU locale is mapped to <Java> locale.\r
362      * For example, both ja_JP@calendar=japanese and ja@calendar=japanese\r
363      * are mapped to Java locale "ja_JP_JP".  ICU locale "nn" is mapped\r
364      * to Java locale "no_NO_NY".\r
365      */\r
366     private static final String[][] _javaLocaleMap = {\r
367     //  { <Java>,       <ICU base>, <keyword>,  <value>,    <minimum base>\r
368         { "ja_JP_JP",   "ja_JP",    "calendar", "japanese", "ja"},\r
369         { "no_NO_NY",   "nn_NO",    null,       null,       "nn"},\r
370         { "th_TH_TH",   "th_TH",    "numbers",  "thai",     "th"},\r
371     };\r
372 \r
373     /**\r
374      * Private constructor used by static initializers.\r
375      */\r
376     private ULocale(String localeID, Locale locale) {\r
377         this.localeID = localeID;\r
378         this.locale = locale;\r
379     }\r
380 \r
381     /**\r
382      * Construct a ULocale object from a {@link java.util.Locale}.\r
383      * @param loc a JDK locale\r
384      */\r
385     private ULocale(Locale loc) {\r
386         this.localeID = getName(forLocale(loc).toString());\r
387         this.locale = loc;\r
388     }\r
389 \r
390     /**\r
391      * {@icu} Returns a ULocale object for a {@link java.util.Locale}.\r
392      * The ULocale is canonicalized.\r
393      * @param loc a JDK locale\r
394      * @stable ICU 3.2\r
395      */\r
396     public static ULocale forLocale(Locale loc) {\r
397         if (loc == null) {\r
398             return null;\r
399         }\r
400         ULocale result = CACHE.get(loc);\r
401         if (result == null) {\r
402             if (defaultULocale != null && loc == defaultULocale.locale) {\r
403             result = defaultULocale;\r
404         } else {\r
405                 String locStr = loc.toString();\r
406                 if (locStr.length() == 0) {\r
407                     result = ROOT;\r
408                 } else {\r
409                     for (int i = 0; i < _javaLocaleMap.length; i++) {\r
410                         if (_javaLocaleMap[i][0].equals(locStr)) {\r
411                             LocaleIDParser p = new LocaleIDParser(_javaLocaleMap[i][1]);\r
412                             p.setKeywordValue(_javaLocaleMap[i][2], _javaLocaleMap[i][3]);\r
413                             locStr = p.getName();\r
414                             break;\r
415                         }\r
416                     }\r
417                     result = new ULocale(locStr, loc);\r
418                 }\r
419             }\r
420             CACHE.put(loc, result);\r
421         }\r
422         return result;\r
423     }\r
424 \r
425     /**\r
426      * {@icu} Constructs a ULocale from a RFC 3066 locale ID. The locale ID consists\r
427      * of optional language, script, country, and variant fields in that order,\r
428      * separated by underscores, followed by an optional keyword list.  The\r
429      * script, if present, is four characters long-- this distinguishes it\r
430      * from a country code, which is two characters long.  Other fields\r
431      * are distinguished by position as indicated by the underscores.  The\r
432      * start of the keyword list is indicated by '@', and consists of two\r
433      * or more keyword/value pairs separated by semicolons(';').\r
434      * \r
435      * <p>This constructor does not canonicalize the localeID.  So, for\r
436      * example, "zh__pinyin" remains unchanged instead of converting\r
437      * to "zh@collation=pinyin".  By default ICU only recognizes the\r
438      * latter as specifying pinyin collation.  Use {@link #createCanonical}\r
439      * or {@link #canonicalize} if you need to canonicalize the localeID.\r
440      *\r
441      * @param localeID string representation of the locale, e.g:\r
442      * "en_US", "sy_Cyrl_YU", "zh__pinyin", "es_ES@currency=EUR;collation=traditional"\r
443      * @stable ICU 2.8\r
444      */\r
445     public ULocale(String localeID) {\r
446         this.localeID = getName(localeID);\r
447     }\r
448 \r
449     /**\r
450      * Convenience overload of ULocale(String, String, String) for\r
451      * compatibility with java.util.Locale.\r
452      * @see #ULocale(String, String, String)\r
453      * @stable ICU 3.4\r
454      */\r
455     public ULocale(String a, String b) {\r
456         this(a, b, null);\r
457     }\r
458 \r
459     /**\r
460      * Constructs a ULocale from a localeID constructed from the three 'fields' a, b, and\r
461      * c.  These fields are concatenated using underscores to form a localeID of the form\r
462      * a_b_c, which is then handled like the localeID passed to <code>ULocale(String\r
463      * localeID)</code>.\r
464      *\r
465      * <p>Java locale strings consisting of language, country, and\r
466      * variant will be handled by this form, since the country code\r
467      * (being shorter than four letters long) will not be interpreted\r
468      * as a script code.  If a script code is present, the final\r
469      * argument ('c') will be interpreted as the country code.  It is\r
470      * recommended that this constructor only be used to ease porting,\r
471      * and that clients instead use the single-argument constructor\r
472      * when constructing a ULocale from a localeID.\r
473      * @param a first component of the locale id\r
474      * @param b second component of the locale id\r
475      * @param c third component of the locale id\r
476      * @see #ULocale(String)\r
477      * @stable ICU 3.0\r
478      */\r
479     public ULocale(String a, String b, String c) {\r
480         localeID = getName(lscvToID(a, b, c, EMPTY_STRING));\r
481     }\r
482 \r
483     /**\r
484      * {@icu} Creates a ULocale from the id by first canonicalizing the id.\r
485      * @param nonCanonicalID the locale id to canonicalize\r
486      * @return the locale created from the canonical version of the ID.\r
487      * @stable ICU 3.0\r
488      */\r
489     public static ULocale createCanonical(String nonCanonicalID) {\r
490         return new ULocale(canonicalize(nonCanonicalID), (Locale)null);\r
491     }\r
492 \r
493     private static String lscvToID(String lang, String script, String country, String variant) {\r
494         StringBuilder buf = new StringBuilder();\r
495 \r
496         if (lang != null && lang.length() > 0) {\r
497             buf.append(lang);\r
498         }\r
499         if (script != null && script.length() > 0) {\r
500             buf.append(UNDERSCORE);\r
501             buf.append(script);\r
502         }\r
503         if (country != null && country.length() > 0) {\r
504             buf.append(UNDERSCORE);\r
505             buf.append(country);\r
506         }\r
507         if (variant != null && variant.length() > 0) {\r
508             if (country == null || country.length() == 0) {\r
509                 buf.append(UNDERSCORE);\r
510             }\r
511             buf.append(UNDERSCORE);\r
512             buf.append(variant);\r
513         }\r
514         return buf.toString();\r
515     }\r
516 \r
517     /**\r
518      * {@icu} Converts this ULocale object to a {@link java.util.Locale}.\r
519      * @return a JDK locale that either exactly represents this object\r
520      * or is the closest approximation.\r
521      * @stable ICU 2.8\r
522      */\r
523     public Locale toLocale() {\r
524         if (locale == null) {\r
525             LocaleIDParser p = new LocaleIDParser(localeID);\r
526             String base = p.getBaseName();\r
527             for (int i = 0; i < _javaLocaleMap.length; i++) {\r
528                 if (base.equals(_javaLocaleMap[i][1]) || base.equals(_javaLocaleMap[i][4])) {\r
529                     if (_javaLocaleMap[i][2] != null) {\r
530                         String val = p.getKeywordValue(_javaLocaleMap[i][2]);\r
531                         if (val != null && val.equals(_javaLocaleMap[i][3])) {\r
532                             p = new LocaleIDParser(_javaLocaleMap[i][0]);\r
533                             break;\r
534                         }\r
535                     } else {\r
536                         p = new LocaleIDParser(_javaLocaleMap[i][0]);\r
537                         break;\r
538                     }\r
539                 }\r
540             }\r
541             String[] names = p.getLanguageScriptCountryVariant();\r
542             locale = new Locale(names[0], names[2], names[3]);\r
543         }\r
544         return locale;\r
545     }\r
546 \r
547     private static ICUCache<String, String> nameCache = new SimpleCache<String, String>();\r
548     /**\r
549      * Keep our own default ULocale.\r
550      */\r
551     private static Locale defaultLocale = Locale.getDefault();\r
552     private static ULocale defaultULocale = new ULocale(defaultLocale);\r
553 \r
554     /**\r
555      * Returns the current default ULocale.\r
556      * @stable ICU 2.8\r
557      */\r
558     public static ULocale getDefault() {\r
559         synchronized (ULocale.class) {\r
560             Locale currentDefault = Locale.getDefault();\r
561             if (!defaultLocale.equals(currentDefault)) {\r
562                 defaultLocale = currentDefault;\r
563                 defaultULocale = new ULocale(defaultLocale);\r
564             }\r
565             return defaultULocale;\r
566         }\r
567     }\r
568 \r
569     /**\r
570      * {@icu} Sets the default ULocale.  This also sets the default Locale.\r
571      * If the caller does not have write permission to the\r
572      * user.language property, a security exception will be thrown,\r
573      * and the default ULocale will remain unchanged.\r
574      * @param newLocale the new default locale\r
575      * @throws SecurityException if a security manager exists and its\r
576      *        <code>checkPermission</code> method doesn't allow the operation.\r
577      * @throws NullPointerException if <code>newLocale</code> is null\r
578      * @see SecurityManager#checkPermission(java.security.Permission)\r
579      * @see java.util.PropertyPermission\r
580      * @stable ICU 3.0\r
581      */\r
582     public static synchronized void setDefault(ULocale newLocale){\r
583         Locale.setDefault(newLocale.toLocale());\r
584         defaultULocale = newLocale;\r
585     }\r
586 \r
587     /**\r
588      * This is for compatibility with Locale-- in actuality, since ULocale is\r
589      * immutable, there is no reason to clone it, so this API returns 'this'.\r
590      * @stable ICU 3.0\r
591      */\r
592     public Object clone() {\r
593         return this;\r
594     }\r
595 \r
596     /**\r
597      * Returns the hashCode.\r
598      * @stable ICU 3.0\r
599      */\r
600     public int hashCode() {\r
601         return localeID.hashCode();\r
602     }\r
603 \r
604     /**\r
605      * Returns true if the other object is another ULocale with the\r
606      * same full name, or is a String localeID that matches the full name.\r
607      * Note that since names are not canonicalized, two ULocales that\r
608      * function identically might not compare equal.\r
609      *\r
610      * @return true if this Locale is equal to the specified object.\r
611      * @stable ICU 3.0\r
612      */\r
613     public boolean equals(Object obj) {\r
614         if (this == obj) {\r
615             return true;\r
616         }\r
617         if (obj instanceof String) {\r
618             return localeID.equals((String)obj);\r
619         }\r
620         if (obj instanceof ULocale) {\r
621             return localeID.equals(((ULocale)obj).localeID);\r
622         }\r
623         return false;\r
624     }\r
625 \r
626     /**\r
627      * {@icunote} Unlike the Locale API, this returns an array of <code>ULocale</code>,\r
628      * not <code>Locale</code>.  Returns a list of all installed locales.\r
629      * @stable ICU 3.0\r
630      */\r
631     public static ULocale[] getAvailableLocales() {\r
632         return ICUResourceBundle.getAvailableULocales();\r
633     }\r
634 \r
635     /**\r
636      * Returns a list of all 2-letter country codes defined in ISO 3166.\r
637      * Can be used to create Locales.\r
638      * @stable ICU 3.0\r
639      */\r
640     public static String[] getISOCountries() {\r
641         return LocaleIDs.getISOCountries();\r
642     }\r
643 \r
644     /**\r
645      * Returns a list of all 2-letter language codes defined in ISO 639.\r
646      * Can be used to create Locales.\r
647      * [NOTE:  ISO 639 is not a stable standard-- some languages' codes have changed.\r
648      * The list this function returns includes both the new and the old codes for the\r
649      * languages whose codes have changed.]\r
650      * @stable ICU 3.0\r
651      */\r
652     public static String[] getISOLanguages() {\r
653         return LocaleIDs.getISOLanguages();\r
654     }\r
655 \r
656     /**\r
657      * Returns the language code for this locale, which will either be the empty string\r
658      * or a lowercase ISO 639 code.\r
659      * @see #getDisplayLanguage()\r
660      * @see #getDisplayLanguage(ULocale)\r
661      * @stable ICU 3.0\r
662      */\r
663     public String getLanguage() {\r
664         return getLanguage(localeID);\r
665     }\r
666 \r
667     /**\r
668      * Returns the language code for the locale ID,\r
669      * which will either be the empty string\r
670      * or a lowercase ISO 639 code.\r
671      * @see #getDisplayLanguage()\r
672      * @see #getDisplayLanguage(ULocale)\r
673      * @stable ICU 3.0\r
674      */\r
675     public static String getLanguage(String localeID) {\r
676         return new LocaleIDParser(localeID).getLanguage();\r
677     }\r
678 \r
679     /**\r
680      * {@icu} Returns the script code for this locale, which might be the empty string.\r
681      * @see #getDisplayScript()\r
682      * @see #getDisplayScript(ULocale)\r
683      * @stable ICU 3.0\r
684      */\r
685     public String getScript() {\r
686         return getScript(localeID);\r
687     }\r
688 \r
689     /**\r
690      * {@icu} Returns the script code for the specified locale, which might be the empty\r
691      * string.\r
692      * @see #getDisplayScript()\r
693      * @see #getDisplayScript(ULocale)\r
694      * @stable ICU 3.0\r
695      */\r
696     public static String getScript(String localeID) {\r
697         return new LocaleIDParser(localeID).getScript();\r
698     }\r
699 \r
700     /**\r
701      * Returns the country/region code for this locale, which will either be the empty string\r
702      * or an uppercase ISO 3166 2-letter code.\r
703      * @see #getDisplayCountry()\r
704      * @see #getDisplayCountry(ULocale)\r
705      * @stable ICU 3.0\r
706      */\r
707     public String getCountry() {\r
708         return getCountry(localeID);\r
709     }\r
710 \r
711     /**\r
712      * Returns the country/region code for this locale, which will either be the empty string\r
713      * or an uppercase ISO 3166 2-letter code.\r
714      * @param localeID The locale identification string.\r
715      * @see #getDisplayCountry()\r
716      * @see #getDisplayCountry(ULocale)\r
717      * @stable ICU 3.0\r
718      */\r
719     public static String getCountry(String localeID) {\r
720         return new LocaleIDParser(localeID).getCountry();\r
721     }\r
722 \r
723     /**\r
724      * Returns the variant code for this locale, which might be the empty string.\r
725      * @see #getDisplayVariant()\r
726      * @see #getDisplayVariant(ULocale)\r
727      * @stable ICU 3.0\r
728      */\r
729     public String getVariant() {\r
730         return getVariant(localeID);\r
731     }\r
732 \r
733     /**\r
734      * Returns the variant code for the specified locale, which might be the empty string.\r
735      * @see #getDisplayVariant()\r
736      * @see #getDisplayVariant(ULocale)\r
737      * @stable ICU 3.0\r
738      */\r
739     public static String getVariant(String localeID) {\r
740         return new LocaleIDParser(localeID).getVariant();\r
741     }\r
742 \r
743     /**\r
744      * {@icu} Returns the fallback locale for the specified locale, which might be the\r
745      * empty string.\r
746      * @stable ICU 3.2\r
747      */\r
748     public static String getFallback(String localeID) {\r
749         return getFallbackString(getName(localeID));\r
750     }\r
751 \r
752     /**\r
753      * {@icu} Returns the fallback locale for this locale.  If this locale is root,\r
754      * returns null.\r
755      * @stable ICU 3.2\r
756      */\r
757     public ULocale getFallback() {\r
758         if (localeID.length() == 0 || localeID.charAt(0) == '@') {\r
759             return null;\r
760         }\r
761         return new ULocale(getFallbackString(localeID), (Locale)null);\r
762     }\r
763 \r
764     /**\r
765      * Returns the given (canonical) locale id minus the last part before the tags.\r
766      */\r
767     private static String getFallbackString(String fallback) {\r
768         int extStart = fallback.indexOf('@');\r
769         if (extStart == -1) {\r
770             extStart = fallback.length();\r
771         }\r
772         int last = fallback.lastIndexOf('_', extStart);\r
773         if (last == -1) {\r
774             last = 0;\r
775         } else {\r
776             // truncate empty segment\r
777             while (last > 0) {\r
778                 if (fallback.charAt(last - 1) != '_') {\r
779                     break;\r
780                 }\r
781                 last--;\r
782             }\r
783         }\r
784         return fallback.substring(0, last) + fallback.substring(extStart);\r
785     }\r
786 \r
787     /**\r
788      * {@icu} Returns the (normalized) base name for this locale.\r
789      * @return the base name as a String.\r
790      * @stable ICU 3.0\r
791      */\r
792     public String getBaseName() {\r
793         return getBaseName(localeID);\r
794     }\r
795 \r
796     /**\r
797      * {@icu} Returns the (normalized) base name for the specified locale.\r
798      * @param localeID the locale ID as a string\r
799      * @return the base name as a String.\r
800      * @stable ICU 3.0\r
801      */\r
802     public static String getBaseName(String localeID){\r
803         if (localeID.indexOf('@') == -1) {\r
804             return localeID;\r
805         }\r
806         return new LocaleIDParser(localeID).getBaseName();\r
807     }\r
808 \r
809     /**\r
810      * {@icu} Returns the (normalized) full name for this locale.\r
811      *\r
812      * @return String the full name of the localeID\r
813      * @stable ICU 3.0\r
814      */\r
815     public String getName() {\r
816         return localeID; // always normalized\r
817     }\r
818 \r
819     /**\r
820      * {@icu} Returns the (normalized) full name for the specified locale.\r
821      *\r
822      * @param localeID the localeID as a string\r
823      * @return String the full name of the localeID\r
824      * @stable ICU 3.0\r
825      */\r
826     public static String getName(String localeID){\r
827         String name = nameCache.get(localeID);\r
828         if (name == null) {\r
829             name = new LocaleIDParser(localeID).getName();\r
830             nameCache.put(localeID, name);\r
831         }\r
832         return name;\r
833     }\r
834 \r
835     /**\r
836      * Returns a string representation of this object.\r
837      * @stable ICU 3.0\r
838      */\r
839     public String toString() {\r
840         return localeID;\r
841     }\r
842 \r
843     /**\r
844      * {@icu} Returns an iterator over keywords for this locale.  If there\r
845      * are no keywords, returns null.\r
846      * @return iterator over keywords, or null if there are no keywords.\r
847      * @stable ICU 3.0\r
848      */\r
849     public Iterator<String> getKeywords() {\r
850         return getKeywords(localeID);\r
851     }\r
852 \r
853     /**\r
854      * {@icu} Returns an iterator over keywords for the specified locale.  If there\r
855      * are no keywords, returns null.\r
856      * @return an iterator over the keywords in the specified locale, or null\r
857      * if there are no keywords.\r
858      * @stable ICU 3.0\r
859      */\r
860     public static Iterator<String> getKeywords(String localeID){\r
861         return new LocaleIDParser(localeID).getKeywords();\r
862     }\r
863 \r
864     /**\r
865      * {@icu} Returns the value for a keyword in this locale. If the keyword is not\r
866      * defined, returns null.\r
867      * @param keywordName name of the keyword whose value is desired. Case insensitive.\r
868      * @return the value of the keyword, or null.\r
869      * @stable ICU 3.0\r
870      */\r
871     public String getKeywordValue(String keywordName){\r
872         return getKeywordValue(localeID, keywordName);\r
873     }\r
874 \r
875     /**\r
876      * {@icu} Returns the value for a keyword in the specified locale. If the keyword is\r
877      * not defined, returns null.  The locale name does not need to be normalized.\r
878      * @param keywordName name of the keyword whose value is desired. Case insensitive.\r
879      * @return String the value of the keyword as a string\r
880      * @stable ICU 3.0\r
881      */\r
882     public static String getKeywordValue(String localeID, String keywordName) {\r
883         return new LocaleIDParser(localeID).getKeywordValue(keywordName);\r
884     }\r
885 \r
886     /**\r
887      * {@icu} Returns the canonical name for the specified locale ID.  This is used to\r
888      * convert POSIX and other grandfathered IDs to standard ICU form.\r
889      * @param localeID the locale id\r
890      * @return the canonicalized id\r
891      * @stable ICU 3.0\r
892      */\r
893     public static String canonicalize(String localeID){\r
894         LocaleIDParser parser = new LocaleIDParser(localeID, true);\r
895         String baseName = parser.getBaseName();\r
896         boolean foundVariant = false;\r
897 \r
898         // formerly, we always set to en_US_POSIX if the basename was empty, but\r
899         // now we require that the entire id be empty, so that "@foo=bar"\r
900         // will pass through unchanged.\r
901         // {dlf} I'd rather keep "" unchanged.\r
902         if (localeID.equals("")) {\r
903             return "";\r
904 //              return "en_US_POSIX";\r
905         }\r
906 \r
907         // we have an ID in the form xx_Yyyy_ZZ_KKKKK\r
908 \r
909         initCANONICALIZE_MAP();\r
910 \r
911         /* convert the variants to appropriate ID */\r
912         for (int i = 0; i < variantsToKeywords.length; i++) {\r
913             String[] vals = variantsToKeywords[i];\r
914             int idx = baseName.lastIndexOf("_" + vals[0]);\r
915             if (idx > -1) {\r
916                 foundVariant = true;\r
917 \r
918                 baseName = baseName.substring(0, idx);\r
919                 if (baseName.endsWith("_")) {\r
920                     baseName = baseName.substring(0, --idx);\r
921                 }\r
922                 parser.setBaseName(baseName);\r
923                 parser.defaultKeywordValue(vals[1], vals[2]);\r
924                 break;\r
925             }\r
926         }\r
927 \r
928         /* See if this is an already known locale */\r
929         for (int i = 0; i < CANONICALIZE_MAP.length; i++) {\r
930             if (CANONICALIZE_MAP[i][0].equals(baseName)) {\r
931                 foundVariant = true;\r
932 \r
933                 String[] vals = CANONICALIZE_MAP[i];\r
934                 parser.setBaseName(vals[1]);\r
935                 if (vals[2] != null) {\r
936                     parser.defaultKeywordValue(vals[2], vals[3]);\r
937                 }\r
938                 break;\r
939             }\r
940         }\r
941 \r
942         /* total mondo hack for Norwegian, fortunately the main NY case is handled earlier */\r
943         if (!foundVariant) {\r
944             if (parser.getLanguage().equals("nb") && parser.getVariant().equals("NY")) {\r
945                 parser.setBaseName(lscvToID("nn", parser.getScript(), parser.getCountry(), null));\r
946             }\r
947         }\r
948 \r
949         return parser.getName();\r
950     }\r
951 \r
952     /**\r
953      * Given a keyword and a value, return a new locale with an updated\r
954      * keyword and value.  If keyword is null, this removes all keywords from the locale id.\r
955      * Otherwise, if the value is null, this removes the value for this keyword from the\r
956      * locale id.  Otherwise, this adds/replaces the value for this keyword in the locale id.\r
957      * The keyword and value must not be empty.\r
958      * @param keyword the keyword to add/remove, or null to remove all keywords.\r
959      * @param value the value to add/set, or null to remove this particular keyword.\r
960      * @return the updated locale\r
961      * @stable ICU 3.2\r
962      */\r
963     public ULocale setKeywordValue(String keyword, String value) {\r
964         return new ULocale(setKeywordValue(localeID, keyword, value), (Locale)null);\r
965     }\r
966 \r
967     /**\r
968      * Given a locale id, a keyword, and a value, return a new locale id with an updated\r
969      * keyword and value.  If keyword is null, this removes all keywords from the locale id.\r
970      * Otherwise, if the value is null, this removes the value for this keyword from the\r
971      * locale id.  Otherwise, this adds/replaces the value for this keyword in the locale id.\r
972      * The keyword and value must not be empty.\r
973      * @param localeID the locale id to modify\r
974      * @param keyword the keyword to add/remove, or null to remove all keywords.\r
975      * @param value the value to add/set, or null to remove this particular keyword.\r
976      * @return the updated locale id\r
977      * @stable ICU 3.2\r
978      */\r
979     public static String setKeywordValue(String localeID, String keyword, String value) {\r
980         LocaleIDParser parser = new LocaleIDParser(localeID);\r
981         parser.setKeywordValue(keyword, value);\r
982         return parser.getName();\r
983     }\r
984 \r
985     /*\r
986      * Given a locale id, a keyword, and a value, return a new locale id with an updated\r
987      * keyword and value, if the keyword does not already have a value.  The keyword and\r
988      * value must not be null or empty.\r
989      * @param localeID the locale id to modify\r
990      * @param keyword the keyword to add, if not already present\r
991      * @param value the value to add, if not already present\r
992      * @return the updated locale id\r
993      */\r
994 /*    private static String defaultKeywordValue(String localeID, String keyword, String value) {\r
995         LocaleIDParser parser = new LocaleIDParser(localeID);\r
996         parser.defaultKeywordValue(keyword, value);\r
997         return parser.getName();\r
998     }*/\r
999 \r
1000     /**\r
1001      * Returns a three-letter abbreviation for this locale's language.  If the locale\r
1002      * doesn't specify a language, returns the empty string.  Otherwise, returns\r
1003      * a lowercase ISO 639-2/T language code.\r
1004      * The ISO 639-2 language codes can be found on-line at\r
1005      *   <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>\r
1006      * @exception MissingResourceException Throws MissingResourceException if the\r
1007      * three-letter language abbreviation is not available for this locale.\r
1008      * @stable ICU 3.0\r
1009      */\r
1010     public String getISO3Language(){\r
1011         return getISO3Language(localeID);\r
1012     }\r
1013 \r
1014     /**\r
1015      * Returns a three-letter abbreviation for this locale's language.  If the locale\r
1016      * doesn't specify a language, returns the empty string.  Otherwise, returns\r
1017      * a lowercase ISO 639-2/T language code.\r
1018      * The ISO 639-2 language codes can be found on-line at\r
1019      *   <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>\r
1020      * @exception MissingResourceException Throws MissingResourceException if the\r
1021      * three-letter language abbreviation is not available for this locale.\r
1022      * @stable ICU 3.0\r
1023      */\r
1024     public static String getISO3Language(String localeID) {\r
1025         return LocaleIDs.getISO3Language(getLanguage(localeID));\r
1026     }\r
1027 \r
1028     /**\r
1029      * Returns a three-letter abbreviation for this locale's country/region.  If the locale\r
1030      * doesn't specify a country, returns the empty string.  Otherwise, returns\r
1031      * an uppercase ISO 3166 3-letter country code.\r
1032      * @exception MissingResourceException Throws MissingResourceException if the\r
1033      * three-letter country abbreviation is not available for this locale.\r
1034      * @stable ICU 3.0\r
1035      */\r
1036     public String getISO3Country() {\r
1037         return getISO3Country(localeID);\r
1038     }\r
1039 \r
1040     /**\r
1041      * Returns a three-letter abbreviation for this locale's country/region.  If the locale\r
1042      * doesn't specify a country, returns the empty string.  Otherwise, returns\r
1043      * an uppercase ISO 3166 3-letter country code.\r
1044      * @exception MissingResourceException Throws MissingResourceException if the\r
1045      * three-letter country abbreviation is not available for this locale.\r
1046      * @stable ICU 3.0\r
1047      */\r
1048     public static String getISO3Country(String localeID) {\r
1049         return LocaleIDs.getISO3Country(getCountry(localeID));\r
1050     }\r
1051 \r
1052     // display names\r
1053 \r
1054     /**\r
1055      * Returns this locale's language localized for display in the default locale.\r
1056      * @return the localized language name.\r
1057      * @stable ICU 3.0\r
1058      */\r
1059     public String getDisplayLanguage() {\r
1060         return getDisplayLanguageInternal(this, getDefault(), false);\r
1061     }\r
1062 \r
1063     /**\r
1064      * {@icu} Returns this locale's language localized for display in the provided locale.\r
1065      * @param displayLocale the locale in which to display the name.\r
1066      * @return the localized language name.\r
1067      * @stable ICU 3.0\r
1068      */\r
1069     public String getDisplayLanguage(ULocale displayLocale) {\r
1070         return getDisplayLanguageInternal(this, displayLocale, false);\r
1071     }\r
1072 \r
1073     /**\r
1074      * Returns a locale's language localized for display in the provided locale.\r
1075      * This is a cover for the ICU4C API.\r
1076      * @param localeID the id of the locale whose language will be displayed\r
1077      * @param displayLocaleID the id of the locale in which to display the name.\r
1078      * @return the localized language name.\r
1079      * @stable ICU 3.0\r
1080      */\r
1081     public static String getDisplayLanguage(String localeID, String displayLocaleID) {\r
1082         return getDisplayLanguageInternal(new ULocale(localeID), new ULocale(displayLocaleID),\r
1083                 false);\r
1084     }\r
1085 \r
1086     /**\r
1087      * Returns a locale's language localized for display in the provided locale.\r
1088      * This is a cover for the ICU4C API.\r
1089      * @param localeID the id of the locale whose language will be displayed.\r
1090      * @param displayLocale the locale in which to display the name.\r
1091      * @return the localized language name.\r
1092      * @stable ICU 3.0\r
1093      */\r
1094     public static String getDisplayLanguage(String localeID, ULocale displayLocale) {\r
1095         return getDisplayLanguageInternal(new ULocale(localeID), displayLocale, false);\r
1096     }\r
1097     /**\r
1098      * {@icu} Returns this locale's language localized for display in the default locale.\r
1099      * If a dialect name is present in the data, then it is returned.\r
1100      * @return the localized language name.\r
1101      * @draft ICU 4.4\r
1102      * @provisional This API might change or be removed in a future release.\r
1103      */\r
1104     public String getDisplayLanguageWithDialect() {\r
1105         return getDisplayLanguageInternal(this, getDefault(), true);\r
1106     }\r
1107 \r
1108     /**\r
1109      * {@icu} Returns this locale's language localized for display in the provided locale.\r
1110      * If a dialect name is present in the data, then it is returned.\r
1111      * @param displayLocale the locale in which to display the name.\r
1112      * @return the localized language name.\r
1113      * @draft ICU 4.4\r
1114      * @provisional This API might change or be removed in a future release.\r
1115      */\r
1116     public String getDisplayLanguageWithDialect(ULocale displayLocale) {\r
1117         return getDisplayLanguageInternal(this, displayLocale, true);\r
1118     }\r
1119 \r
1120     /**\r
1121      * {@icu} Returns a locale's language localized for display in the provided locale.\r
1122      * If a dialect name is present in the data, then it is returned.\r
1123      * This is a cover for the ICU4C API.\r
1124      * @param localeID the id of the locale whose language will be displayed\r
1125      * @param displayLocaleID the id of the locale in which to display the name.\r
1126      * @return the localized language name.\r
1127      * @draft ICU 4.4\r
1128      * @provisional This API might change or be removed in a future release.\r
1129      */\r
1130     public static String getDisplayLanguageWithDialect(String localeID, String displayLocaleID) {\r
1131         return getDisplayLanguageInternal(new ULocale(localeID), new ULocale(displayLocaleID),\r
1132                 true);\r
1133     }\r
1134 \r
1135     /**\r
1136      * {@icu} Returns a locale's language localized for display in the provided locale.\r
1137      * If a dialect name is present in the data, then it is returned.\r
1138      * This is a cover for the ICU4C API.\r
1139      * @param localeID the id of the locale whose language will be displayed.\r
1140      * @param displayLocale the locale in which to display the name.\r
1141      * @return the localized language name.\r
1142      * @draft ICU 4.4\r
1143      * @provisional This API might change or be removed in a future release.\r
1144      */\r
1145     public static String getDisplayLanguageWithDialect(String localeID, ULocale displayLocale) {\r
1146         return getDisplayLanguageInternal(new ULocale(localeID), displayLocale, true);\r
1147     }\r
1148 \r
1149     private static String getDisplayLanguageInternal(ULocale locale, ULocale displayLocale,\r
1150             boolean useDialect) {\r
1151         String lang = useDialect ? locale.getBaseName() : locale.getLanguage();\r
1152         return LocaleDisplayNames.getInstance(displayLocale).languageDisplayName(lang);\r
1153     }\r
1154 \r
1155     /**\r
1156      * {@icu} Returns this locale's script localized for display in the default locale.\r
1157      * @return the localized script name.\r
1158      * @stable ICU 3.0\r
1159      */\r
1160     public String getDisplayScript() {\r
1161         return getDisplayScriptInternal(this, getDefault());\r
1162     }\r
1163 \r
1164     /**\r
1165      * {@icu} Returns this locale's script localized for display in the provided locale.\r
1166      * @param displayLocale the locale in which to display the name.\r
1167      * @return the localized script name.\r
1168      * @stable ICU 3.0\r
1169      */\r
1170     public String getDisplayScript(ULocale displayLocale) {\r
1171         return getDisplayScriptInternal(this, displayLocale);\r
1172     }\r
1173 \r
1174     /**\r
1175      * {@icu} Returns a locale's script localized for display in the provided locale.\r
1176      * This is a cover for the ICU4C API.\r
1177      * @param localeID the id of the locale whose script will be displayed\r
1178      * @param displayLocaleID the id of the locale in which to display the name.\r
1179      * @return the localized script name.\r
1180      * @stable ICU 3.0\r
1181      */\r
1182     public static String getDisplayScript(String localeID, String displayLocaleID) {\r
1183         return getDisplayScriptInternal(new ULocale(localeID), new ULocale(displayLocaleID));\r
1184     }\r
1185 \r
1186     /**\r
1187      * {@icu} Returns a locale's script localized for display in the provided locale.\r
1188      * @param localeID the id of the locale whose script will be displayed.\r
1189      * @param displayLocale the locale in which to display the name.\r
1190      * @return the localized script name.\r
1191      * @stable ICU 3.0\r
1192      */\r
1193     public static String getDisplayScript(String localeID, ULocale displayLocale) {\r
1194         return getDisplayScriptInternal(new ULocale(localeID), displayLocale);\r
1195     }\r
1196 \r
1197     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
1198     private static String getDisplayScriptInternal(ULocale locale, ULocale displayLocale) {\r
1199         return LocaleDisplayNames.getInstance(displayLocale)\r
1200             .scriptDisplayName(locale.getScript());\r
1201     }\r
1202 \r
1203     /**\r
1204      * Returns this locale's country localized for display in the default locale.\r
1205      * @return the localized country name.\r
1206      * @stable ICU 3.0\r
1207      */\r
1208     public String getDisplayCountry() {\r
1209         return getDisplayCountryInternal(this, getDefault());\r
1210     }\r
1211 \r
1212     /**\r
1213      * Returns this locale's country localized for display in the provided locale.\r
1214      * @param displayLocale the locale in which to display the name.\r
1215      * @return the localized country name.\r
1216      * @stable ICU 3.0\r
1217      */\r
1218     public String getDisplayCountry(ULocale displayLocale){\r
1219         return getDisplayCountryInternal(this, displayLocale);\r
1220     }\r
1221 \r
1222     /**\r
1223      * Returns a locale's country localized for display in the provided locale.\r
1224      * This is a cover for the ICU4C API.\r
1225      * @param localeID the id of the locale whose country will be displayed\r
1226      * @param displayLocaleID the id of the locale in which to display the name.\r
1227      * @return the localized country name.\r
1228      * @stable ICU 3.0\r
1229      */\r
1230     public static String getDisplayCountry(String localeID, String displayLocaleID) {\r
1231         return getDisplayCountryInternal(new ULocale(localeID), new ULocale(displayLocaleID));\r
1232     }\r
1233 \r
1234     /**\r
1235      * Returns a locale's country localized for display in the provided locale.\r
1236      * This is a cover for the ICU4C API.\r
1237      * @param localeID the id of the locale whose country will be displayed.\r
1238      * @param displayLocale the locale in which to display the name.\r
1239      * @return the localized country name.\r
1240      * @stable ICU 3.0\r
1241      */\r
1242     public static String getDisplayCountry(String localeID, ULocale displayLocale) {\r
1243         return getDisplayCountryInternal(new ULocale(localeID), displayLocale);\r
1244     }\r
1245 \r
1246     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
1247     private static String getDisplayCountryInternal(ULocale locale, ULocale displayLocale) {\r
1248         return LocaleDisplayNames.getInstance(displayLocale)\r
1249             .regionDisplayName(locale.getCountry());\r
1250     }\r
1251 \r
1252     /**\r
1253      * Returns this locale's variant localized for display in the default locale.\r
1254      * @return the localized variant name.\r
1255      * @stable ICU 3.0\r
1256      */\r
1257     public String getDisplayVariant() {\r
1258         return getDisplayVariantInternal(this, getDefault());\r
1259     }\r
1260 \r
1261     /**\r
1262      * Returns this locale's variant localized for display in the provided locale.\r
1263      * @param displayLocale the locale in which to display the name.\r
1264      * @return the localized variant name.\r
1265      * @stable ICU 3.0\r
1266      */\r
1267     public String getDisplayVariant(ULocale displayLocale) {\r
1268         return getDisplayVariantInternal(this, displayLocale);\r
1269     }\r
1270 \r
1271     /**\r
1272      * Returns a locale's variant localized for display in the provided locale.\r
1273      * This is a cover for the ICU4C API.\r
1274      * @param localeID the id of the locale whose variant will be displayed\r
1275      * @param displayLocaleID the id of the locale in which to display the name.\r
1276      * @return the localized variant name.\r
1277      * @stable ICU 3.0\r
1278      */\r
1279     public static String getDisplayVariant(String localeID, String displayLocaleID){\r
1280         return getDisplayVariantInternal(new ULocale(localeID), new ULocale(displayLocaleID));\r
1281     }\r
1282 \r
1283     /**\r
1284      * Returns a locale's variant localized for display in the provided locale.\r
1285      * This is a cover for the ICU4C API.\r
1286      * @param localeID the id of the locale whose variant will be displayed.\r
1287      * @param displayLocale the locale in which to display the name.\r
1288      * @return the localized variant name.\r
1289      * @stable ICU 3.0\r
1290      */\r
1291     public static String getDisplayVariant(String localeID, ULocale displayLocale) {\r
1292         return getDisplayVariantInternal(new ULocale(localeID), displayLocale);\r
1293     }\r
1294 \r
1295     private static String getDisplayVariantInternal(ULocale locale, ULocale displayLocale) {\r
1296         return LocaleDisplayNames.getInstance(displayLocale)\r
1297             .variantDisplayName(locale.getVariant());\r
1298     }\r
1299 \r
1300     /**\r
1301      * {@icu} Returns a keyword localized for display in the default locale.\r
1302      * @param keyword the keyword to be displayed.\r
1303      * @return the localized keyword name.\r
1304      * @see #getKeywords()\r
1305      * @stable ICU 3.0\r
1306      */\r
1307     public static String getDisplayKeyword(String keyword) {\r
1308         return getDisplayKeywordInternal(keyword, getDefault());\r
1309     }\r
1310 \r
1311     /**\r
1312      * {@icu} Returns a keyword localized for display in the specified locale.\r
1313      * @param keyword the keyword to be displayed.\r
1314      * @param displayLocaleID the id of the locale in which to display the keyword.\r
1315      * @return the localized keyword name.\r
1316      * @see #getKeywords(String)\r
1317      * @stable ICU 3.0\r
1318      */\r
1319     public static String getDisplayKeyword(String keyword, String displayLocaleID) {\r
1320         return getDisplayKeywordInternal(keyword, new ULocale(displayLocaleID));\r
1321     }\r
1322 \r
1323     /**\r
1324      * {@icu} Returns a keyword localized for display in the specified locale.\r
1325      * @param keyword the keyword to be displayed.\r
1326      * @param displayLocale the locale in which to display the keyword.\r
1327      * @return the localized keyword name.\r
1328      * @see #getKeywords(String)\r
1329      * @stable ICU 3.0\r
1330      */\r
1331     public static String getDisplayKeyword(String keyword, ULocale displayLocale) {\r
1332         return getDisplayKeywordInternal(keyword, displayLocale);\r
1333     }\r
1334 \r
1335     private static String getDisplayKeywordInternal(String keyword, ULocale displayLocale) {\r
1336         return LocaleDisplayNames.getInstance(displayLocale).keyDisplayName(keyword);\r
1337     }\r
1338 \r
1339     /**\r
1340      * {@icu} Returns a keyword value localized for display in the default locale.\r
1341      * @param keyword the keyword whose value is to be displayed.\r
1342      * @return the localized value name.\r
1343      * @stable ICU 3.0\r
1344      */\r
1345     public String getDisplayKeywordValue(String keyword) {\r
1346         return getDisplayKeywordValueInternal(this, keyword, getDefault());\r
1347     }\r
1348 \r
1349     /**\r
1350      * {@icu} Returns a keyword value localized for display in the specified locale.\r
1351      * @param keyword the keyword whose value is to be displayed.\r
1352      * @param displayLocale the locale in which to display the value.\r
1353      * @return the localized value name.\r
1354      * @stable ICU 3.0\r
1355      */\r
1356     public String getDisplayKeywordValue(String keyword, ULocale displayLocale) {\r
1357         return getDisplayKeywordValueInternal(this, keyword, displayLocale);\r
1358     }\r
1359 \r
1360     /**\r
1361      * {@icu} Returns a keyword value localized for display in the specified locale.\r
1362      * This is a cover for the ICU4C API.\r
1363      * @param localeID the id of the locale whose keyword value is to be displayed.\r
1364      * @param keyword the keyword whose value is to be displayed.\r
1365      * @param displayLocaleID the id of the locale in which to display the value.\r
1366      * @return the localized value name.\r
1367      * @stable ICU 3.0\r
1368      */\r
1369     public static String getDisplayKeywordValue(String localeID, String keyword,\r
1370             String displayLocaleID) {\r
1371         return getDisplayKeywordValueInternal(new ULocale(localeID), keyword,\r
1372                 new ULocale(displayLocaleID));\r
1373     }\r
1374 \r
1375     /**\r
1376      * {@icu} Returns a keyword value localized for display in the specified locale.\r
1377      * This is a cover for the ICU4C API.\r
1378      * @param localeID the id of the locale whose keyword value is to be displayed.\r
1379      * @param keyword the keyword whose value is to be displayed.\r
1380      * @param displayLocale the id of the locale in which to display the value.\r
1381      * @return the localized value name.\r
1382      * @stable ICU 3.0\r
1383      */\r
1384     public static String getDisplayKeywordValue(String localeID, String keyword,\r
1385             ULocale displayLocale) {\r
1386         return getDisplayKeywordValueInternal(new ULocale(localeID), keyword, displayLocale);\r
1387     }\r
1388 \r
1389     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
1390     private static String getDisplayKeywordValueInternal(ULocale locale, String keyword,\r
1391             ULocale displayLocale) {\r
1392         keyword = AsciiUtil.toLowerString(keyword.trim());\r
1393         String value = locale.getKeywordValue(keyword);\r
1394         return LocaleDisplayNames.getInstance(displayLocale).keyValueDisplayName(keyword, value);\r
1395     }\r
1396 \r
1397     /**\r
1398      * Returns this locale name localized for display in the default locale.\r
1399      * @return the localized locale name.\r
1400      * @stable ICU 3.0\r
1401      */\r
1402     public String getDisplayName() {\r
1403         return getDisplayNameInternal(this, getDefault());\r
1404     }\r
1405 \r
1406     /**\r
1407      * Returns this locale name localized for display in the provided locale.\r
1408      * @param displayLocale the locale in which to display the locale name.\r
1409      * @return the localized locale name.\r
1410      * @stable ICU 3.0\r
1411      */\r
1412     public String getDisplayName(ULocale displayLocale) {\r
1413         return getDisplayNameInternal(this, displayLocale);\r
1414     }\r
1415 \r
1416     /**\r
1417      * Returns the locale ID localized for display in the provided locale.\r
1418      * This is a cover for the ICU4C API.\r
1419      * @param localeID the locale whose name is to be displayed.\r
1420      * @param displayLocaleID the id of the locale in which to display the locale name.\r
1421      * @return the localized locale name.\r
1422      * @stable ICU 3.0\r
1423      */\r
1424     public static String getDisplayName(String localeID, String displayLocaleID) {\r
1425         return getDisplayNameInternal(new ULocale(localeID), new ULocale(displayLocaleID));\r
1426     }\r
1427 \r
1428     /**\r
1429      * Returns the locale ID localized for display in the provided locale.\r
1430      * This is a cover for the ICU4C API.\r
1431      * @param localeID the locale whose name is to be displayed.\r
1432      * @param displayLocale the locale in which to display the locale name.\r
1433      * @return the localized locale name.\r
1434      * @stable ICU 3.0\r
1435      */\r
1436     public static String getDisplayName(String localeID, ULocale displayLocale) {\r
1437         return getDisplayNameInternal(new ULocale(localeID), displayLocale);\r
1438     }\r
1439 \r
1440     private static String getDisplayNameInternal(ULocale locale, ULocale displayLocale) {\r
1441         return LocaleDisplayNames.getInstance(displayLocale).localeDisplayName(locale);\r
1442     }\r
1443 \r
1444     /**\r
1445      * {@icu} Returns this locale name localized for display in the default locale.\r
1446      * If a dialect name is present in the locale data, then it is returned.\r
1447      * @return the localized locale name.\r
1448      * @draft ICU 4.4\r
1449      * @provisional This API might change or be removed in a future release.\r
1450      */\r
1451     public String getDisplayNameWithDialect() {\r
1452         return getDisplayNameWithDialectInternal(this, getDefault());\r
1453     }\r
1454 \r
1455     /**\r
1456      * {@icu} Returns this locale name localized for display in the provided locale.\r
1457      * If a dialect name is present in the locale data, then it is returned.\r
1458      * @param displayLocale the locale in which to display the locale name.\r
1459      * @return the localized locale name.\r
1460      * @draft ICU 4.4\r
1461      * @provisional This API might change or be removed in a future release.\r
1462      */\r
1463     public String getDisplayNameWithDialect(ULocale displayLocale) {\r
1464         return getDisplayNameWithDialectInternal(this, displayLocale);\r
1465     }\r
1466 \r
1467     /**\r
1468      * {@icu} Returns the locale ID localized for display in the provided locale.\r
1469      * If a dialect name is present in the locale data, then it is returned.\r
1470      * This is a cover for the ICU4C API.\r
1471      * @param localeID the locale whose name is to be displayed.\r
1472      * @param displayLocaleID the id of the locale in which to display the locale name.\r
1473      * @return the localized locale name.\r
1474      * @draft ICU 4.4\r
1475      * @provisional This API might change or be removed in a future release.\r
1476      */\r
1477     public static String getDisplayNameWithDialect(String localeID, String displayLocaleID) {\r
1478         return getDisplayNameWithDialectInternal(new ULocale(localeID),\r
1479                 new ULocale(displayLocaleID));\r
1480     }\r
1481 \r
1482     /**\r
1483      * {@icu} Returns the locale ID localized for display in the provided locale.\r
1484      * If a dialect name is present in the locale data, then it is returned.\r
1485      * This is a cover for the ICU4C API.\r
1486      * @param localeID the locale whose name is to be displayed.\r
1487      * @param displayLocale the locale in which to display the locale name.\r
1488      * @return the localized locale name.\r
1489      * @draft ICU 4.4\r
1490      * @provisional This API might change or be removed in a future release.\r
1491      */\r
1492     public static String getDisplayNameWithDialect(String localeID, ULocale displayLocale) {\r
1493         return getDisplayNameWithDialectInternal(new ULocale(localeID), displayLocale);\r
1494     }\r
1495 \r
1496     private static String getDisplayNameWithDialectInternal(ULocale locale, ULocale displayLocale) {\r
1497         return LocaleDisplayNames.getInstance(displayLocale, DialectHandling.DIALECT_NAMES)\r
1498             .localeDisplayName(locale);\r
1499     }\r
1500 \r
1501     /**\r
1502      * {@icu} Returns this locale's layout orientation for characters.  The possible\r
1503      * values are "left-to-right", "right-to-left", "top-to-bottom" or\r
1504      * "bottom-to-top".\r
1505      * @return The locale's layout orientation for characters.\r
1506      * @stable ICU 4.0\r
1507      */\r
1508     public String getCharacterOrientation() {\r
1509         return ICUResourceTableAccess.getTableString(ICUResourceBundle.ICU_BASE_NAME, this,\r
1510                 "layout", "characters");\r
1511     }\r
1512 \r
1513     /**\r
1514      * {@icu} Returns this locale's layout orientation for lines.  The possible\r
1515      * values are "left-to-right", "right-to-left", "top-to-bottom" or\r
1516      * "bottom-to-top".\r
1517      * @return The locale's layout orientation for lines.\r
1518      * @stable ICU 4.0\r
1519      */\r
1520     public String getLineOrientation() {\r
1521         return ICUResourceTableAccess.getTableString(ICUResourceBundle.ICU_BASE_NAME, this,\r
1522                 "layout", "lines");\r
1523     }\r
1524 \r
1525     /**\r
1526      * {@icu} Selector for <tt>getLocale()</tt> indicating the locale of the\r
1527      * resource containing the data.  This is always at or above the\r
1528      * valid locale.  If the valid locale does not contain the\r
1529      * specific data being requested, then the actual locale will be\r
1530      * above the valid locale.  If the object was not constructed from\r
1531      * locale data, then the valid locale is <i>null</i>.\r
1532      *\r
1533      * @draft ICU 2.8 (retain)\r
1534      * @provisional This API might change or be removed in a future release.\r
1535      */\r
1536     public static Type ACTUAL_LOCALE = new Type();\r
1537 \r
1538     /**\r
1539      * {@icu} Selector for <tt>getLocale()</tt> indicating the most specific\r
1540      * locale for which any data exists.  This is always at or above\r
1541      * the requested locale, and at or below the actual locale.  If\r
1542      * the requested locale does not correspond to any resource data,\r
1543      * then the valid locale will be above the requested locale.  If\r
1544      * the object was not constructed from locale data, then the\r
1545      * actual locale is <i>null</i>.\r
1546      *\r
1547      * <p>Note: The valid locale will be returned correctly in ICU\r
1548      * 3.0 or later.  In ICU 2.8, it is not returned correctly.\r
1549      * @draft ICU 2.8 (retain)\r
1550      * @provisional This API might change or be removed in a future release.\r
1551      */\r
1552     public static Type VALID_LOCALE = new Type();\r
1553 \r
1554     /**\r
1555      * Opaque selector enum for <tt>getLocale()</tt>.\r
1556      * @see com.ibm.icu.util.ULocale\r
1557      * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE\r
1558      * @see com.ibm.icu.util.ULocale#VALID_LOCALE\r
1559      * @draft ICU 2.8 (retainAll)\r
1560      * @provisional This API might change or be removed in a future release.\r
1561      */\r
1562     public static final class Type {\r
1563         private Type() {}\r
1564     }\r
1565 \r
1566   /**\r
1567     * {@icu} Based on a HTTP formatted list of acceptable locales, determine an available\r
1568     * locale for the user.  NullPointerException is thrown if acceptLanguageList or\r
1569     * availableLocales is null.  If fallback is non-null, it will contain true if a\r
1570     * fallback locale (one not in the acceptLanguageList) was returned.  The value on\r
1571     * entry is ignored.  ULocale will be one of the locales in availableLocales, or the\r
1572     * ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in\r
1573     * availableLocales matched).  No ULocale array element should be null; behavior is\r
1574     * undefined if this is the case.\r
1575     * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales\r
1576     * @param availableLocales list of available locales. One of these will be returned.\r
1577     * @param fallback if non-null, a 1-element array containing a boolean to be set with\r
1578     * the fallback status\r
1579     * @return one of the locales from the availableLocales list, or null if none match\r
1580     * @stable ICU 3.4\r
1581     */\r
1582     public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales,\r
1583                                          boolean[] fallback) {\r
1584         if (acceptLanguageList == null) {\r
1585             throw new NullPointerException();\r
1586         }\r
1587         ULocale acceptList[] = null;\r
1588         try {\r
1589             acceptList = parseAcceptLanguage(acceptLanguageList, true);\r
1590         } catch (ParseException pe) {\r
1591             acceptList = null;\r
1592         }\r
1593         if (acceptList == null) {\r
1594             return null;\r
1595         }\r
1596         return acceptLanguage(acceptList, availableLocales, fallback);\r
1597     }\r
1598 \r
1599     /**\r
1600     * {@icu} Based on a list of acceptable locales, determine an available locale for the\r
1601     * user.  NullPointerException is thrown if acceptLanguageList or availableLocales is\r
1602     * null.  If fallback is non-null, it will contain true if a fallback locale (one not\r
1603     * in the acceptLanguageList) was returned.  The value on entry is ignored.  ULocale\r
1604     * will be one of the locales in availableLocales, or the ROOT ULocale if if a ROOT\r
1605     * locale was used as a fallback (because nothing else in availableLocales matched).\r
1606     * No ULocale array element should be null; behavior is undefined if this is the case.\r
1607     * @param acceptLanguageList list of acceptable locales\r
1608     * @param availableLocales list of available locales. One of these will be returned.\r
1609     * @param fallback if non-null, a 1-element array containing a boolean to be set with\r
1610     * the fallback status\r
1611     * @return one of the locales from the availableLocales list, or null if none match\r
1612     * @stable ICU 3.4\r
1613     */\r
1614 \r
1615     public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[]\r
1616     availableLocales, boolean[] fallback) {\r
1617         // fallbacklist\r
1618         int i,j;\r
1619         if(fallback != null) {\r
1620             fallback[0]=true;\r
1621         }\r
1622         for(i=0;i<acceptLanguageList.length;i++) {\r
1623             ULocale aLocale = acceptLanguageList[i];\r
1624             boolean[] setFallback = fallback;\r
1625             do {\r
1626                 for(j=0;j<availableLocales.length;j++) {\r
1627                     if(availableLocales[j].equals(aLocale)) {\r
1628                         if(setFallback != null) {\r
1629                             setFallback[0]=false; // first time with this locale - not a fallback.\r
1630                         }\r
1631                         return availableLocales[j];\r
1632                     }\r
1633                 }\r
1634                 Locale loc = aLocale.toLocale();\r
1635                 Locale parent = LocaleUtility.fallback(loc);\r
1636                 if(parent != null) {\r
1637                     aLocale = new ULocale(parent);\r
1638                 } else {\r
1639                     aLocale = null;\r
1640                 }\r
1641                 setFallback = null; // Do not set fallback in later iterations\r
1642             } while (aLocale != null);\r
1643         }\r
1644         return null;\r
1645     }\r
1646 \r
1647    /**\r
1648     * {@icu} Based on a HTTP formatted list of acceptable locales, determine an available\r
1649     * locale for the user.  NullPointerException is thrown if acceptLanguageList or\r
1650     * availableLocales is null.  If fallback is non-null, it will contain true if a\r
1651     * fallback locale (one not in the acceptLanguageList) was returned.  The value on\r
1652     * entry is ignored.  ULocale will be one of the locales in availableLocales, or the\r
1653     * ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in\r
1654     * availableLocales matched).  No ULocale array element should be null; behavior is\r
1655     * undefined if this is the case.  This function will choose a locale from the\r
1656     * ULocale.getAvailableLocales() list as available.\r
1657     * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales\r
1658     * @param fallback if non-null, a 1-element array containing a boolean to be set with\r
1659     * the fallback status\r
1660     * @return one of the locales from the ULocale.getAvailableLocales() list, or null if\r
1661     * none match\r
1662     * @stable ICU 3.4\r
1663     */\r
1664     public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) {\r
1665         return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),\r
1666                                 fallback);\r
1667     }\r
1668 \r
1669    /**\r
1670     * {@icu} Based on an ordered array of acceptable locales, determine an available\r
1671     * locale for the user.  NullPointerException is thrown if acceptLanguageList or\r
1672     * availableLocales is null.  If fallback is non-null, it will contain true if a\r
1673     * fallback locale (one not in the acceptLanguageList) was returned.  The value on\r
1674     * entry is ignored.  ULocale will be one of the locales in availableLocales, or the\r
1675     * ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in\r
1676     * availableLocales matched).  No ULocale array element should be null; behavior is\r
1677     * undefined if this is the case.  This function will choose a locale from the\r
1678     * ULocale.getAvailableLocales() list as available.\r
1679     * @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)\r
1680     * @param fallback if non-null, a 1-element array containing a boolean to be set with\r
1681     * the fallback status\r
1682     * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match\r
1683     * @stable ICU 3.4\r
1684     */\r
1685     public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[] fallback) {\r
1686         return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),\r
1687                 fallback);\r
1688     }\r
1689 \r
1690     /**\r
1691      * Package local method used for parsing Accept-Language string\r
1692      */\r
1693     static ULocale[] parseAcceptLanguage(String acceptLanguage, boolean isLenient) \r
1694         throws ParseException {\r
1695         class ULocaleAcceptLanguageQ implements Comparable<ULocaleAcceptLanguageQ> {\r
1696             private double q;\r
1697             private double serial;\r
1698             public ULocaleAcceptLanguageQ(double theq, int theserial) {\r
1699                 q = theq;\r
1700                 serial = theserial;\r
1701             }\r
1702             public int compareTo(ULocaleAcceptLanguageQ other) {\r
1703                 if (q > other.q) { // reverse - to sort in descending order\r
1704                     return -1;\r
1705                 } else if (q < other.q) {\r
1706                     return 1;\r
1707                 }\r
1708                 if (serial < other.serial) {\r
1709                     return -1;\r
1710                 } else if (serial > other.serial) {\r
1711                     return 1;\r
1712                 } else {\r
1713                     return 0; // same object\r
1714                 }\r
1715             }\r
1716         }\r
1717 \r
1718         // parse out the acceptLanguage into an array\r
1719         TreeMap<ULocaleAcceptLanguageQ, ULocale> map = \r
1720             new TreeMap<ULocaleAcceptLanguageQ, ULocale>();\r
1721         StringBuilder languageRangeBuf = new StringBuilder();\r
1722         StringBuilder qvalBuf = new StringBuilder();\r
1723         int state = 0;\r
1724         acceptLanguage += ","; // append comma to simplify the parsing code\r
1725         int n;\r
1726         boolean subTag = false;\r
1727         boolean q1 = false;\r
1728         for (n = 0; n < acceptLanguage.length(); n++) {\r
1729             boolean gotLanguageQ = false;\r
1730             char c = acceptLanguage.charAt(n);\r
1731             switch (state) {\r
1732             case 0: // before language-range start\r
1733                 if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {\r
1734                     // in language-range\r
1735                     languageRangeBuf.append(c);\r
1736                     state = 1;\r
1737                     subTag = false;\r
1738                 } else if (c == '*') {\r
1739                     languageRangeBuf.append(c);\r
1740                     state = 2;\r
1741                 } else if (c != ' ' && c != '\t') {\r
1742                     // invalid character\r
1743                     state = -1;\r
1744                 }\r
1745                 break;\r
1746             case 1: // in language-range\r
1747                 if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {\r
1748                     languageRangeBuf.append(c);\r
1749                 } else if (c == '-') {\r
1750                     subTag = true;\r
1751                     languageRangeBuf.append(c);\r
1752                 } else if (c == '_') {\r
1753                     if (isLenient) {\r
1754                         subTag = true;\r
1755                         languageRangeBuf.append(c);\r
1756                     } else {\r
1757                         state = -1;\r
1758                     }\r
1759                 } else if ('0' <= c && c <= '9') {\r
1760                     if (subTag) {\r
1761                         languageRangeBuf.append(c);\r
1762                     } else {\r
1763                         // DIGIT is allowed only in language sub tag\r
1764                         state = -1;\r
1765                     }\r
1766                 } else if (c == ',') {\r
1767                     // language-q end\r
1768                     gotLanguageQ = true;\r
1769                 } else if (c == ' ' || c == '\t') {\r
1770                     // language-range end\r
1771                     state = 3;\r
1772                 } else if (c == ';') {\r
1773                     // before q\r
1774                     state = 4;\r
1775                 } else {\r
1776                     // invalid character for language-range\r
1777                     state = -1;\r
1778                 }\r
1779                 break;\r
1780             case 2: // saw wild card range\r
1781                 if (c == ',') {\r
1782                     // language-q end\r
1783                     gotLanguageQ = true;\r
1784                 } else if (c == ' ' || c == '\t') {\r
1785                     // language-range end\r
1786                     state = 3;\r
1787                 } else if (c == ';') {\r
1788                     // before q\r
1789                     state = 4;\r
1790                 } else {\r
1791                     // invalid\r
1792                     state = -1;\r
1793                 }\r
1794                 break;\r
1795             case 3: // language-range end\r
1796                 if (c == ',') {\r
1797                     // language-q end\r
1798                     gotLanguageQ = true;\r
1799                 } else if (c == ';') {\r
1800                     // before q\r
1801                     state =4;\r
1802                 } else if (c != ' ' && c != '\t') {\r
1803                     // invalid\r
1804                     state = -1;\r
1805                 }\r
1806                 break;\r
1807             case 4: // before q\r
1808                 if (c == 'q') {\r
1809                     // before equal\r
1810                     state = 5;\r
1811                 } else if (c != ' ' && c != '\t') {\r
1812                     // invalid\r
1813                     state = -1;\r
1814                 }\r
1815                 break;\r
1816             case 5: // before equal\r
1817                 if (c == '=') {\r
1818                     // before q value\r
1819                     state = 6;\r
1820                 } else if (c != ' ' && c != '\t') {\r
1821                     // invalid\r
1822                     state = -1;\r
1823                 }\r
1824                 break;\r
1825             case 6: // before q value\r
1826                 if (c == '0') {\r
1827                     // q value start with 0\r
1828                     q1 = false;\r
1829                     qvalBuf.append(c);\r
1830                     state = 7;\r
1831                 } else if (c == '1') {\r
1832                     // q value start with 1\r
1833                     qvalBuf.append(c);\r
1834                     state = 7;\r
1835                 } else if (c == '.') {\r
1836                     if (isLenient) {\r
1837                         qvalBuf.append(c);\r
1838                         state = 8;\r
1839                     } else {\r
1840                         state = -1;\r
1841                     }\r
1842                 } else if (c != ' ' && c != '\t') {\r
1843                     // invalid\r
1844                     state = -1;\r
1845                 }\r
1846                 break;\r
1847             case 7: // q value start\r
1848                 if (c == '.') {\r
1849                     // before q value fraction part\r
1850                     qvalBuf.append(c);\r
1851                     state = 8;\r
1852                 } else if (c == ',') {\r
1853                     // language-q end\r
1854                     gotLanguageQ = true;\r
1855                 } else if (c == ' ' || c == '\t') {\r
1856                     // after q value\r
1857                     state = 10;\r
1858                 } else {\r
1859                     // invalid\r
1860                     state = -1;\r
1861                 }\r
1862                 break;\r
1863             case 8: // before q value fraction part\r
1864                 if ('0' <= c || c <= '9') {\r
1865                     if (q1 && c != '0' && !isLenient) {\r
1866                         // if q value starts with 1, the fraction part must be 0\r
1867                         state = -1;\r
1868                     } else {\r
1869                         // in q value fraction part\r
1870                         qvalBuf.append(c);\r
1871                         state = 9;\r
1872                     }\r
1873                 } else {\r
1874                     // invalid\r
1875                     state = -1;\r
1876                 }\r
1877                 break;\r
1878             case 9: // in q value fraction part\r
1879                 if ('0' <= c && c <= '9') {\r
1880                     if (q1 && c != '0') {\r
1881                         // if q value starts with 1, the fraction part must be 0\r
1882                         state = -1;\r
1883                     } else {\r
1884                         qvalBuf.append(c);\r
1885                     }\r
1886                 } else if (c == ',') {\r
1887                     // language-q end\r
1888                     gotLanguageQ = true;\r
1889                 } else if (c == ' ' || c == '\t') {\r
1890                     // after q value\r
1891                     state = 10;\r
1892                 } else {\r
1893                     // invalid\r
1894                     state = -1;\r
1895                 }\r
1896                 break;\r
1897             case 10: // after q value\r
1898                 if (c == ',') {\r
1899                     // language-q end\r
1900                     gotLanguageQ = true;\r
1901                 } else if (c != ' ' && c != '\t') {\r
1902                     // invalid\r
1903                     state = -1;\r
1904                 }\r
1905                 break;\r
1906             }\r
1907             if (state == -1) {\r
1908                 // error state\r
1909                 throw new ParseException("Invalid Accept-Language", n);\r
1910             }\r
1911             if (gotLanguageQ) {\r
1912                 double q = 1.0;\r
1913                 if (qvalBuf.length() != 0) {\r
1914                     try {\r
1915                         q = Double.parseDouble(qvalBuf.toString());\r
1916                     } catch (NumberFormatException nfe) {\r
1917                         // Already validated, so it should never happen\r
1918                         q = 1.0;\r
1919                     }\r
1920                     if (q > 1.0) {\r
1921                         q = 1.0;\r
1922                     }\r
1923                 }\r
1924                 if (languageRangeBuf.charAt(0) != '*') {\r
1925                     int serial = map.size();\r
1926                     ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q, serial);\r
1927                     // sort in reverse order..   1.0, 0.9, 0.8 .. etc\r
1928                     map.put(entry, new ULocale(canonicalize(languageRangeBuf.toString())));\r
1929                 }\r
1930 \r
1931                 // reset buffer and parse state\r
1932                 languageRangeBuf.setLength(0);\r
1933                 qvalBuf.setLength(0);\r
1934                 state = 0;\r
1935             }\r
1936         }\r
1937         if (state != 0) {\r
1938             // Well, the parser should handle all cases.  So just in case.\r
1939             throw new ParseException("Invalid AcceptlLanguage", n);\r
1940         }\r
1941 \r
1942         // pull out the map\r
1943         ULocale acceptList[] = map.values().toArray(new ULocale[map.size()]);\r
1944         return acceptList;\r
1945     }\r
1946 \r
1947     private static final String UNDEFINED_LANGUAGE = "und";\r
1948     private static final String UNDEFINED_SCRIPT = "Zzzz";\r
1949     private static final String UNDEFINED_REGION = "ZZ";\r
1950 \r
1951     /**\r
1952      * {@icu} Adds the likely subtags for a provided locale ID, per the algorithm\r
1953      * described in the following CLDR technical report:\r
1954      *\r
1955      *   http://www.unicode.org/reports/tr35/#Likely_Subtags\r
1956      *\r
1957      * If the provided ULocale instance is already in the maximal form, or there is no\r
1958      * data available available for maximization, it will be returned.  For example,\r
1959      * "und-Zzzz" cannot be maximized, since there is no reasonable maximization.\r
1960      * Otherwise, a new ULocale instance with the maximal form is returned.\r
1961      *\r
1962      * Examples:\r
1963      *\r
1964      * "en" maximizes to "en_Latn_US"\r
1965      *\r
1966      * "de" maximizes to "de_Latn_US"\r
1967      *\r
1968      * "sr" maximizes to "sr_Cyrl_RS"\r
1969      *\r
1970      * "sh" maximizes to "sr_Latn_RS" (Note this will not reverse.)\r
1971      *\r
1972      * "zh_Hani" maximizes to "zh_Hans_CN" (Note this will not reverse.)\r
1973      *\r
1974      * @param loc The ULocale to maximize\r
1975      * @return The maximized ULocale instance.\r
1976      * @stable ICU 4.0\r
1977      */\r
1978     public static ULocale addLikelySubtags(ULocale loc) {\r
1979         String[] tags = new String[3];\r
1980         String trailing = null;\r
1981 \r
1982         int trailingIndex = parseTagString(\r
1983             loc.localeID,\r
1984             tags);\r
1985 \r
1986         if (trailingIndex < loc.localeID.length()) {\r
1987             trailing = loc.localeID.substring(trailingIndex);\r
1988         }\r
1989 \r
1990         String newLocaleID =\r
1991             createLikelySubtagsString(\r
1992                 tags[0],\r
1993                 tags[1],\r
1994                 tags[2],\r
1995                 trailing);\r
1996 \r
1997         return newLocaleID == null ? loc : new ULocale(newLocaleID);\r
1998     }\r
1999 \r
2000     /**\r
2001      * {@icu} Minimizes the subtags for a provided locale ID, per the algorithm described\r
2002      * in the following CLDR technical report:<blockquote>\r
2003      *\r
2004      *   <a href="http://www.unicode.org/reports/tr35/#Likely_Subtags"\r
2005      *>http://www.unicode.org/reports/tr35/#Likely_Subtags</a></blockquote>\r
2006      *\r
2007      * If the provided ULocale instance is already in the minimal form, or there\r
2008      * is no data available for minimization, it will be returned.  Since the\r
2009      * minimization algorithm relies on proper maximization, see the comments\r
2010      * for addLikelySubtags for reasons why there might not be any data.\r
2011      *\r
2012      * Examples:<pre>\r
2013      *\r
2014      * "en_Latn_US" minimizes to "en"\r
2015      *\r
2016      * "de_Latn_US" minimizes to "de"\r
2017      *\r
2018      * "sr_Cyrl_RS" minimizes to "sr"\r
2019      *\r
2020      * "zh_Hant_TW" minimizes to "zh_TW" (The region is preferred to the\r
2021      * script, and minimizing to "zh" would imply "zh_Hans_CN".) </pre>\r
2022      *\r
2023      * @param loc The ULocale to minimize\r
2024      * @return The minimized ULocale instance.\r
2025      * @stable ICU 4.0\r
2026      */\r
2027     public static ULocale minimizeSubtags(ULocale loc) {\r
2028         String[] tags = new String[3];\r
2029 \r
2030         int trailingIndex = parseTagString(\r
2031                 loc.localeID,\r
2032                 tags);\r
2033 \r
2034         String originalLang = tags[0];\r
2035         String originalScript = tags[1];\r
2036         String originalRegion = tags[2];\r
2037         String originalTrailing = null;\r
2038 \r
2039         if (trailingIndex < loc.localeID.length()) {\r
2040             /*\r
2041              * Create a String that contains everything\r
2042              * after the language, script, and region.\r
2043              */\r
2044             originalTrailing = loc.localeID.substring(trailingIndex);\r
2045         }\r
2046 \r
2047         /**\r
2048          * First, we need to first get the maximization\r
2049          * by adding any likely subtags.\r
2050          **/\r
2051         String maximizedLocaleID =\r
2052             createLikelySubtagsString(\r
2053                 originalLang,\r
2054                 originalScript,\r
2055                 originalRegion,\r
2056                 null);\r
2057 \r
2058         /**\r
2059          * If maximization fails, there's nothing\r
2060          * we can do.\r
2061          **/\r
2062         if (isEmptyString(maximizedLocaleID)) {\r
2063             return loc;\r
2064         }\r
2065         else {\r
2066             /**\r
2067              * Start first with just the language.\r
2068              **/\r
2069             String tag =\r
2070                 createLikelySubtagsString(\r
2071                     originalLang,\r
2072                     null,\r
2073                     null,\r
2074                     null);\r
2075 \r
2076             if (tag.equals(maximizedLocaleID)) {\r
2077                 String newLocaleID =\r
2078                     createTagString(\r
2079                         originalLang,\r
2080                         null,\r
2081                         null,\r
2082                         originalTrailing);\r
2083 \r
2084                 return new ULocale(newLocaleID);\r
2085             }\r
2086         }\r
2087 \r
2088         /**\r
2089          * Next, try the language and region.\r
2090          **/\r
2091         if (originalRegion.length() != 0) {\r
2092 \r
2093             String tag =\r
2094                 createLikelySubtagsString(\r
2095                     originalLang,\r
2096                     null,\r
2097                     originalRegion,\r
2098                     null);\r
2099 \r
2100             if (tag.equals(maximizedLocaleID)) {\r
2101                 String newLocaleID =\r
2102                     createTagString(\r
2103                         originalLang,\r
2104                         null,\r
2105                         originalRegion,\r
2106                         originalTrailing);\r
2107 \r
2108                 return new ULocale(newLocaleID);\r
2109             }\r
2110         }\r
2111 \r
2112         /**\r
2113          * Finally, try the language and script.  This is our last chance,\r
2114          * since trying with all three subtags would only yield the\r
2115          * maximal version that we already have.\r
2116          **/\r
2117         if (originalRegion.length() != 0 &&\r
2118             originalScript.length() != 0) {\r
2119 \r
2120             String tag =\r
2121                 createLikelySubtagsString(\r
2122                     originalLang,\r
2123                     originalScript,\r
2124                     null,\r
2125                     null);\r
2126 \r
2127             if (tag.equals(maximizedLocaleID)) {\r
2128                 String newLocaleID =\r
2129                     createTagString(\r
2130                         originalLang,\r
2131                         originalScript,\r
2132                         null,\r
2133                         originalTrailing);\r
2134 \r
2135                 return new ULocale(newLocaleID);\r
2136             }\r
2137         }\r
2138 \r
2139         return loc;\r
2140     }\r
2141 \r
2142     /**\r
2143      * A trivial utility function that checks for a null\r
2144      * reference or checks the length of the supplied String.\r
2145      *\r
2146      *   @param string The string to check\r
2147      *\r
2148      *   @return true if the String is empty, or if the reference is null.\r
2149      */\r
2150     private static boolean isEmptyString(String string) {\r
2151       return string == null || string.length() == 0;\r
2152     }\r
2153 \r
2154     /**\r
2155      * Append a tag to a StringBuilder, adding the separator if necessary.The tag must\r
2156      * not be a zero-length string.\r
2157      *\r
2158      * @param tag The tag to add.\r
2159      * @param buffer The output buffer.\r
2160      **/\r
2161     private static void appendTag(String tag, StringBuilder buffer) {\r
2162         if (buffer.length() != 0) {\r
2163             buffer.append(UNDERSCORE);\r
2164         }\r
2165 \r
2166         buffer.append(tag);\r
2167     }\r
2168 \r
2169     /**\r
2170      * Create a tag string from the supplied parameters.  The lang, script and region\r
2171      * parameters may be null references.\r
2172      *\r
2173      * If any of the language, script or region parameters are empty, and the alternateTags\r
2174      * parameter is not null, it will be parsed for potential language, script and region tags\r
2175      * to be used when constructing the new tag.  If the alternateTags parameter is null, or\r
2176      * it contains no language tag, the default tag for the unknown language is used.\r
2177      *\r
2178      * @param lang The language tag to use.\r
2179      * @param script The script tag to use.\r
2180      * @param region The region tag to use.\r
2181      * @param trailing Any trailing data to append to the new tag.\r
2182      * @param alternateTags A string containing any alternate tags.\r
2183      * @return The new tag string.\r
2184      **/\r
2185     private static String createTagString(String lang, String script, String region,\r
2186         String trailing, String alternateTags) {\r
2187 \r
2188         LocaleIDParser parser = null;\r
2189         boolean regionAppended = false;\r
2190 \r
2191         StringBuilder tag = new StringBuilder();\r
2192 \r
2193         if (!isEmptyString(lang)) {\r
2194             appendTag(\r
2195                 lang,\r
2196                 tag);\r
2197         }\r
2198         else if (isEmptyString(alternateTags)) {\r
2199             /*\r
2200              * Append the value for an unknown language, if\r
2201              * we found no language.\r
2202              */\r
2203             appendTag(\r
2204                 UNDEFINED_LANGUAGE,\r
2205                 tag);\r
2206         }\r
2207         else {\r
2208             parser = new LocaleIDParser(alternateTags);\r
2209 \r
2210             String alternateLang = parser.getLanguage();\r
2211 \r
2212             /*\r
2213              * Append the value for an unknown language, if\r
2214              * we found no language.\r
2215              */\r
2216             appendTag(\r
2217                 !isEmptyString(alternateLang) ? alternateLang : UNDEFINED_LANGUAGE,\r
2218                 tag);\r
2219         }\r
2220 \r
2221         if (!isEmptyString(script)) {\r
2222             appendTag(\r
2223                 script,\r
2224                 tag);\r
2225         }\r
2226         else if (!isEmptyString(alternateTags)) {\r
2227             /*\r
2228              * Parse the alternateTags string for the script.\r
2229              */\r
2230             if (parser == null) {\r
2231                 parser = new LocaleIDParser(alternateTags);\r
2232             }\r
2233 \r
2234             String alternateScript = parser.getScript();\r
2235 \r
2236             if (!isEmptyString(alternateScript)) {\r
2237                 appendTag(\r
2238                     alternateScript,\r
2239                     tag);\r
2240             }\r
2241         }\r
2242 \r
2243         if (!isEmptyString(region)) {\r
2244             appendTag(\r
2245                 region,\r
2246                 tag);\r
2247 \r
2248             regionAppended = true;\r
2249         }\r
2250         else if (!isEmptyString(alternateTags)) {\r
2251             /*\r
2252              * Parse the alternateTags string for the region.\r
2253              */\r
2254             if (parser == null) {\r
2255                 parser = new LocaleIDParser(alternateTags);\r
2256             }\r
2257 \r
2258             String alternateRegion = parser.getCountry();\r
2259 \r
2260             if (!isEmptyString(alternateRegion)) {\r
2261                 appendTag(\r
2262                     alternateRegion,\r
2263                     tag);\r
2264 \r
2265                 regionAppended = true;\r
2266             }\r
2267         }\r
2268 \r
2269         if (trailing != null && trailing.length() > 1) {\r
2270             /*\r
2271              * The current ICU format expects two underscores\r
2272              * will separate the variant from the preceeding\r
2273              * parts of the tag, if there is no region.\r
2274              */\r
2275             int separators = 0;\r
2276 \r
2277             if (trailing.charAt(0) == UNDERSCORE) {\r
2278                 if (trailing.charAt(1) == UNDERSCORE) {\r
2279                     separators = 2;\r
2280                 }\r
2281                 }\r
2282                 else {\r
2283                     separators = 1;\r
2284                 }\r
2285 \r
2286             if (regionAppended) {\r
2287                 /*\r
2288                  * If we appended a region, we may need to strip\r
2289                  * the extra separator from the variant portion.\r
2290                  */\r
2291                 if (separators == 2) {\r
2292                     tag.append(trailing.substring(1));\r
2293                 }\r
2294                 else {\r
2295                     tag.append(trailing);\r
2296                 }\r
2297             }\r
2298             else {\r
2299                 /*\r
2300                  * If we did not append a region, we may need to add\r
2301                  * an extra separator to the variant portion.\r
2302                  */\r
2303                 if (separators == 1) {\r
2304                     tag.append(UNDERSCORE);\r
2305                 }\r
2306                 tag.append(trailing);\r
2307             }\r
2308         }\r
2309 \r
2310         return tag.toString();\r
2311     }\r
2312 \r
2313     /**\r
2314      * Create a tag string from the supplied parameters.  The lang, script and region\r
2315      * parameters may be null references.If the lang parameter is an empty string, the\r
2316      * default value for an unknown language is written to the output buffer.\r
2317      *\r
2318      * @param lang The language tag to use.\r
2319      * @param script The script tag to use.\r
2320      * @param region The region tag to use.\r
2321      * @param trailing Any trailing data to append to the new tag.\r
2322      * @return The new String.\r
2323      **/\r
2324     static String createTagString(String lang, String script, String region, String trailing) {\r
2325         return createTagString(lang, script, region, trailing, null);\r
2326     }\r
2327 \r
2328     /**\r
2329      * Parse the language, script, and region subtags from a tag string, and return the results.\r
2330      *\r
2331      * This function does not return the canonical strings for the unknown script and region.\r
2332      *\r
2333      * @param localeID The locale ID to parse.\r
2334      * @param tags An array of three String references to return the subtag strings.\r
2335      * @return The number of chars of the localeID parameter consumed.\r
2336      **/\r
2337     private static int parseTagString(String localeID, String tags[]) {\r
2338         LocaleIDParser parser = new LocaleIDParser(localeID);\r
2339 \r
2340         String lang = parser.getLanguage();\r
2341         String script = parser.getScript();\r
2342         String region = parser.getCountry();\r
2343 \r
2344         if (isEmptyString(lang)) {\r
2345             tags[0] = UNDEFINED_LANGUAGE;\r
2346         }\r
2347         else {\r
2348             tags[0] = lang;\r
2349         }\r
2350 \r
2351         if (script.equals(UNDEFINED_SCRIPT)) {\r
2352             tags[1] = "";\r
2353         }\r
2354         else {\r
2355             tags[1] = script;\r
2356         }\r
2357 \r
2358         if (region.equals(UNDEFINED_REGION)) {\r
2359             tags[2] = "";\r
2360         }\r
2361         else {\r
2362             tags[2] = region;\r
2363         }\r
2364 \r
2365         /*\r
2366          * Search for the variant.  If there is one, then return the index of\r
2367          * the preceeding separator.\r
2368          * If there's no variant, search for the keyword delimiter,\r
2369          * and return its index.  Otherwise, return the length of the\r
2370          * string.\r
2371          *\r
2372          * $TOTO(dbertoni) we need to take into account that we might\r
2373          * find a part of the language as the variant, since it can\r
2374          * can have a variant portion that is long enough to contain\r
2375          * the same characters as the variant.\r
2376          */\r
2377         String variant = parser.getVariant();\r
2378 \r
2379         if (!isEmptyString(variant)){\r
2380             int index = localeID.indexOf(variant);\r
2381 \r
2382 \r
2383             return  index > 0 ? index - 1 : index;\r
2384         }\r
2385         else\r
2386         {\r
2387             int index = localeID.indexOf('@');\r
2388 \r
2389             return index == -1 ? localeID.length() : index;\r
2390         }\r
2391     }\r
2392 \r
2393     private static String lookupLikelySubtags(String localeId) {\r
2394         UResourceBundle bundle =\r
2395             UResourceBundle.getBundleInstance(\r
2396                     ICUResourceBundle.ICU_BASE_NAME, "likelySubtags");\r
2397         try {\r
2398             return bundle.getString(localeId);\r
2399         }\r
2400         catch(MissingResourceException e) {\r
2401             return null;\r
2402         }\r
2403     }\r
2404 \r
2405     private static String createLikelySubtagsString(String lang, String script, String region,\r
2406         String variants) {\r
2407 \r
2408         /**\r
2409          * Try the language with the script and region first.\r
2410          */\r
2411         if (!isEmptyString(script) && !isEmptyString(region)) {\r
2412 \r
2413             String searchTag =\r
2414                 createTagString(\r
2415                     lang,\r
2416                     script,\r
2417                     region,\r
2418                     null);\r
2419 \r
2420             String likelySubtags = lookupLikelySubtags(searchTag);\r
2421 \r
2422             /*\r
2423             if (likelySubtags == null) {\r
2424                 if (likelySubtags2 != null) {\r
2425                     System.err.println("Tag mismatch: \"(null)\" \"" + likelySubtags2 + "\"");\r
2426                 }\r
2427             }\r
2428             else if (likelySubtags2 == null) {\r
2429                 System.err.println("Tag mismatch: \"" + likelySubtags + "\" \"(null)\"");\r
2430             }\r
2431             else if (!likelySubtags.equals(likelySubtags2)) {\r
2432                 System.err.println("Tag mismatch: \"" + likelySubtags + "\" \"" + likelySubtags2 \r
2433                     + "\"");\r
2434             }\r
2435             */\r
2436             if (likelySubtags != null) {\r
2437                 // Always use the language tag from the\r
2438                 // maximal string, since it may be more\r
2439                 // specific than the one provided.\r
2440                 return createTagString(\r
2441                             null,\r
2442                             null,\r
2443                             null,\r
2444                             variants,\r
2445                             likelySubtags);\r
2446             }\r
2447         }\r
2448 \r
2449         /**\r
2450          * Try the language with just the script.\r
2451          **/\r
2452         if (!isEmptyString(script)) {\r
2453 \r
2454             String searchTag =\r
2455                 createTagString(\r
2456                     lang,\r
2457                     script,\r
2458                     null,\r
2459                     null);\r
2460 \r
2461             String likelySubtags = lookupLikelySubtags(searchTag);\r
2462             if (likelySubtags != null) {\r
2463                 // Always use the language tag from the\r
2464                 // maximal string, since it may be more\r
2465                 // specific than the one provided.\r
2466                 return createTagString(\r
2467                             null,\r
2468                             null,\r
2469                             region,\r
2470                             variants,\r
2471                             likelySubtags);\r
2472             }\r
2473         }\r
2474 \r
2475         /**\r
2476          * Try the language with just the region.\r
2477          **/\r
2478         if (!isEmptyString(region)) {\r
2479 \r
2480             String searchTag =\r
2481                 createTagString(\r
2482                     lang,\r
2483                     null,\r
2484                     region,\r
2485                     null);\r
2486 \r
2487             String likelySubtags = lookupLikelySubtags(searchTag);\r
2488 \r
2489             if (likelySubtags != null) {\r
2490                 // Always use the language tag from the\r
2491                 // maximal string, since it may be more\r
2492                 // specific than the one provided.\r
2493                 return createTagString(\r
2494                             null,\r
2495                             script,\r
2496                             null,\r
2497                             variants,\r
2498                             likelySubtags);\r
2499             }\r
2500         }\r
2501 \r
2502         /**\r
2503          * Finally, try just the language.\r
2504          **/\r
2505         {\r
2506             String searchTag =\r
2507                 createTagString(\r
2508                     lang,\r
2509                     null,\r
2510                     null,\r
2511                     null);\r
2512 \r
2513             String likelySubtags = lookupLikelySubtags(searchTag);\r
2514 \r
2515             if (likelySubtags != null) {\r
2516                 // Always use the language tag from the\r
2517                 // maximal string, since it may be more\r
2518                 // specific than the one provided.\r
2519                 return createTagString(\r
2520                             null,\r
2521                             script,\r
2522                             region,\r
2523                             variants,\r
2524                             likelySubtags);\r
2525             }\r
2526         }\r
2527 \r
2528         return null;\r
2529     }\r
2530 \r
2531     // --------------------------------\r
2532     //      BCP47/OpenJDK APIs\r
2533     // --------------------------------\r
2534 \r
2535     /**\r
2536      * {@icu} The key for the private use locale extension ('x').\r
2537      *\r
2538      * @see #getExtension(char)\r
2539      * @see Builder#setExtension(char, String)\r
2540      *\r
2541      * @draft ICU 4.2\r
2542      * @provisional This API might change or be removed in a future release.\r
2543      */\r
2544     public static final char PRIVATE_USE_EXTENSION = 'x';\r
2545 \r
2546     /**\r
2547      * {@icu} The key for Unicode locale extension ('u').\r
2548      *\r
2549      * @see #getExtension(char)\r
2550      * @see Builder#setExtension(char, String)\r
2551      *\r
2552      * @draft ICU 4.2\r
2553      * @provisional This API might change or be removed in a future release.\r
2554      */\r
2555     public static final char UNICODE_LOCALE_EXTENSION = 'u';\r
2556 \r
2557     /**\r
2558      * {@icu} Returns the extension (or private use) value associated with\r
2559      * the specified singleton key, or null if there is no extension\r
2560      * associated with the key.  To be valid, the key must be one\r
2561      * of <code>[0-9A-Za-z]</code>.  Keys are case-insensitive, so\r
2562      * for example 'z' and 'Z' represent the same extension.\r
2563      *\r
2564      * @param key the extension key\r
2565      * @return the extension, or null if this locale defines no\r
2566      * extension for the specified key\r
2567      * @throws IllegalArgumentException if the key is not valid\r
2568      * @see #PRIVATE_USE_EXTENSION\r
2569      * @see #UNICODE_LOCALE_EXTENSION\r
2570      *\r
2571      * @draft ICU 4.2\r
2572      * @provisional This API might change or be removed in a future release.\r
2573      */\r
2574     public String getExtension(char key) {\r
2575         String strKey = String.valueOf(key);\r
2576         if (!LocaleExtensions.isValidKey(strKey)) {\r
2577             throw new IllegalArgumentException("Invalid extension key: " + strKey);\r
2578         }\r
2579         return extensions().getExtensionValue(key);\r
2580     }\r
2581 \r
2582     /**\r
2583      * {@icu} Returns the set of extension keys associated with this locale, or the\r
2584      * empty set if it has no extensions.  The returned set is unmodifiable.\r
2585      *\r
2586      * @return the set of extension keys, or the empty set if this locale has\r
2587      * no extensions\r
2588      *\r
2589      * @draft ICU 4.2\r
2590      * @provisional This API might change or be removed in a future release.\r
2591      */\r
2592     public Set<Character> getExtensionKeys() {\r
2593         return extensions().getKeys();\r
2594     }\r
2595 \r
2596     /**\r
2597      * {@icu} Returns the Unicode locale type associated with the specified Unicode\r
2598      * locale key for this locale.  Unicode locale keywrods are specified\r
2599      * by the 'u' extension and consist of key/type pairs.  The key must be\r
2600      * two alphanumeric characters in length, or an IllegalArgumentException\r
2601      * is thrown.\r
2602      * @param key the Unicode locale key\r
2603      * @return the Unicode locale type associated with the key, or null if the\r
2604      * locale does not define a value for the key.\r
2605      * @throws IllegalArgumentException if the key is not valid.\r
2606      *\r
2607      * @draft ICU 4.4\r
2608      * @provisional This API might change or be removed in a future release.\r
2609      */\r
2610     public String getUnicodeLocaleType(String key) {\r
2611         if (!LocaleExtensions.isValidKey(key)) {\r
2612             throw new IllegalArgumentException("Invalid Unicode locale key: " + key);\r
2613         }\r
2614         return extensions().getUnicodeLocaleType(key);\r
2615     }\r
2616 \r
2617     /**\r
2618      * {@icu} Returns the set of keys for Unicode locale keywords defined by this locale,\r
2619      * or null if this locale has no locale extension.  The returned set is\r
2620      * immutable.\r
2621      *\r
2622      * @return the set of the Unicode locale keys, or null\r
2623      *\r
2624      * @draft ICU 4.4\r
2625      * @provisional This API might change or be removed in a future release.\r
2626      */\r
2627     public Set<String> getUnicodeLocaleKeys() {\r
2628         return extensions().getUnicodeLocaleKeys();\r
2629     }\r
2630 \r
2631     /**\r
2632      * {@icu} Returns a well-formed IETF BCP 47 language tag representing\r
2633      * this locale.\r
2634      *\r
2635      * <p>\r
2636      * If this <code>ULocale</code> object has language, country, or variant\r
2637      * that does not satisfy the IETF BCP 47 language tag syntax requirements,\r
2638      * this method handles these fields as described below:\r
2639      * <p>\r
2640      * <b>Language:</b> If language is empty or ill-formed (for example "a" or "e2"),\r
2641      * it will be emitted as "und" (Undetermined).\r
2642      * <p>\r
2643      * <b>Country:</b> If country is ill-formed (for example "12" or "USA"), it\r
2644      * will be omitted.\r
2645      * <p>\r
2646      * <b>Variant:</b> Variant is treated as consisting of subtags separated by\r
2647      * underscore and converted to lower case letters.  'Well-formed' subtags\r
2648      * consist of either an ASCII letter followed by 4-7 ASCII characters, or an\r
2649      * ASCII digit followed by 3-7 ASCII characters.  If well-formed, the variant\r
2650      * is emitted as each subtag in order (separated by hyphen).  Otherwise:\r
2651      * <ul>\r
2652      * <li>if all sub-segments consist of 1 to 8 ASCII alphanumerics (for example\r
2653      * "WIN", "WINDOWS_XP", "SOLARIS_10"), the first ill-formed variant subtag\r
2654      * and all following sub-segments will be emitted as private use subtags prefixed\r
2655      * by the special private use subtag "variant" followed by each subtag in order\r
2656      * (separated by hyphen).  For example, locale "en_US_WIN" is converted to language\r
2657      * tag "en-US-x-variant-win", locale "de_WINDOWS_XP" is converted to language tag\r
2658      * "de-windows-x-variant-xp".  If this locale has a private use extension value,\r
2659      * the special private use subtags prefixed by "variant" are appended after the\r
2660      * locale's private use value.\r
2661      * <li>if any subtag does not consist of 1 to 8 ASCII alphanumerics, the\r
2662      * variant will be truncated and the problematic subtag and all following\r
2663      * sub-segments will be omitted.  If the remainder is non-empty, it will be\r
2664      * emitted as a private use subtag as above (even if the remainder turns out\r
2665      * to be well-formed).  For example, "Solaris_isjustthecoolestthing" is emitted\r
2666      * as "x-jvariant-Solaris", not as "solaris".</li>\r
2667      * </ul>\r
2668      *\r
2669      * <p><b>Note:</b> Although the language tag created by this method\r
2670      * satisfies the syntax requirements defined by the IETF BCP 47\r
2671      * specification, it is not always a valid BCP 47 language tag.\r
2672      * For example,\r
2673      * <pre>\r
2674      *   new ULocale("xx_YY").toLanguageTag();\r
2675      * </pre>\r
2676      * will return "xx-YY", but the language subtag "xx" and the region subtag "YY"\r
2677      * are invalid because they are not registered in the\r
2678      * <a href="http://www.iana.org/assignments/language-subtag-registry">\r
2679      * IANA Language Subtag Registry</a>.\r
2680      *\r
2681      * @return a BCP47 language tag representing the locale\r
2682      * @see #forLanguageTag(String)\r
2683      *\r
2684      * @draft ICU 4.2\r
2685      * @provisional This API might change or be removed in a future release.\r
2686      */\r
2687     public String toLanguageTag() {\r
2688         LanguageTag tag = LanguageTag.parseLocale(base(), extensions());\r
2689         return tag.getID();\r
2690     }\r
2691 \r
2692     /**\r
2693      * {@icu} Returns a locale for the specified IETF BCP 47 language tag string.\r
2694      * If the specified language tag contains any ill-formed subtags,\r
2695      * the first such subtag and all following subtags are ignored.\r
2696      *\r
2697      * <p>This implements the 'Language-Tag' production of BCP47, and\r
2698      * so supports grandfathered (regular and irregular) as well as\r
2699      * private use language tags.  Stand alone private use tags are\r
2700      * represented as empty language and extension 'x-whatever',\r
2701      * and grandfathered tags are converted to their canonical replacements\r
2702      * where they exist.  Note that a few grandfathered tags have no\r
2703      * modern replacement; these will be converted using the fallback\r
2704      * described above so some information might be lost.\r
2705      *\r
2706      * <p>For a list of grandfathered tags, see the\r
2707      * <a href="http://www.iana.org/assignments/language-subtag-registry">\r
2708      * IANA Language Subtag Registry</a>.\r
2709      *\r
2710      * <p><b>Notes:</b> This method converts private use subtags prefixed\r
2711      * by "variant" to variant field in the result locale.  For example,\r
2712      * the code below will return "POSIX".\r
2713      * <pre>\r
2714      *   ULocale.forLanguageTag("en-US-x-variant-posix).getVariant();\r
2715      * </pre>\r
2716      *\r
2717      * @param languageTag the language tag\r
2718      * @return the locale that best represents the language tag\r
2719      * @exception NullPointerException if <code>languageTag</code> is <code>null</code>\r
2720      * @see #toLanguageTag()\r
2721      *\r
2722      * @draft ICU 4.2\r
2723      * @provisional This API might change or be removed in a future release.\r
2724      */\r
2725     public static ULocale forLanguageTag(String languageTag) {\r
2726         LanguageTag tag = LanguageTag.parse(languageTag, true);\r
2727         return getInstance(tag.getBaseLocale(), tag.getLocaleExtensions());\r
2728     }\r
2729 \r
2730 \r
2731     /**\r
2732      * <code>Builder</code> is used to build instances of <code>ULocale</code>\r
2733      * from values configured by the setter.  Unlike the <code>ULocale</code>\r
2734      * constructors, the <code>Builder</code> checks if a value configured by a\r
2735      * setter satisfies the syntactical requirements defined by the <code>ULocale</code>\r
2736      * class.  A <code>ULocale</code> object created by a <code>Builder</code> is\r
2737      * well-formed and can be transformed to a well-formed IETF BCP 47 language tag\r
2738      * without losing information.\r
2739      *\r
2740      * <p>\r
2741      * <b>Note:</b> The <code>ULocale</code> class does not provide\r
2742      * any syntactical restrictions on variant, while BCP 47\r
2743      * requires each variant subtag to be 5 to 8 alphanumeric letters or a single\r
2744      * numeric letter followed by 3 alphanumeric letters.  By default,\r
2745      * the <code>setVariant</code> method throws <code>IllformedLocaleException</code>\r
2746      * for a variant that does not satisfy the syntax above.  If it is\r
2747      * necessary to support such a variant, you could use the constructor <code>\r
2748      * Builder(boolean isLenientVariant)</code> passing <code>true</code> to\r
2749      * skip the syntax validation for variant.  However, you should keep in\r
2750      * mind that a <code>Locale</code> object created this way might lose\r
2751      * the variant information when transformed to a BCP 47 language tag.\r
2752      *\r
2753      * <p>\r
2754      * The following example shows how to create a <code>ULocale</code> object\r
2755      * with the <code>Builder</code>.\r
2756      * <blockquote>\r
2757      * <pre>\r
2758      *     ULocale aLocale = new Builder().setLanguage("sr").setScript("Latn").setRegion("RS").build();\r
2759      * </pre>\r
2760      * </blockquote>\r
2761      *\r
2762      * <p>Builders can be reused; <code>clear()</code> resets all\r
2763      * fields to their default values.\r
2764      *\r
2765      * @see ULocale#toLanguageTag()\r
2766      *\r
2767      * @draft ICU 4.2\r
2768      * @provisional This API might change or be removed in a future release.\r
2769      */\r
2770     public static final class Builder {\r
2771 \r
2772         private final InternalLocaleBuilder _locbld;\r
2773 \r
2774         /**\r
2775          * Constructs an empty Builder. The default value of all\r
2776          * fields, extensions, and private use information is the\r
2777          * empty string.\r
2778          *\r
2779          * @draft ICU 4.2\r
2780          * @provisional This API might change or be removed in a future release.\r
2781          */\r
2782         public Builder() {\r
2783             this(false);\r
2784         }\r
2785 \r
2786         /**\r
2787          * Constructs an empty Builder with an option whether to allow\r
2788          * <code>setVariant</code> to accept a value that does not\r
2789          * conform to the IETF BCP 47 variant subtag's syntax requirements.\r
2790          *\r
2791          * @param isLenientVariant When true, this <code>Builder</code>\r
2792          * will accept an ill-formed variant.\r
2793          * @see #setVariant(String)\r
2794          *\r
2795          * @draft ICU 4.4\r
2796          * @provisional This API might change or be removed in a future release.\r
2797          */\r
2798         public Builder(boolean isLenientVariant) {\r
2799             _locbld = new InternalLocaleBuilder(isLenientVariant);\r
2800         }\r
2801 \r
2802         /**\r
2803          * Returns true if this <code>Builder</code> accepts a value that does\r
2804          * not conform to the IETF BCP 47 variant subtag's syntax requirements\r
2805          * in <code>setVariant</code>\r
2806          *\r
2807          * @return true if this <code>Build</code> accepts an ill-formed variant.\r
2808          *\r
2809          * @draft ICU 4.4\r
2810          * @provisional This API might change or be removed in a future release.\r
2811          */\r
2812         public boolean isLenientVariant() {\r
2813             return _locbld.isLenientVariant();\r
2814         }\r
2815 \r
2816 \r
2817         /**\r
2818          * Resets the <code>Builder</code> to match the provided <code>locale</code>.\r
2819          * The previous state of the builder is discarded.  Fields that do\r
2820          * not conform to the <code>ULocale</code> class specification, for example,\r
2821          * a single letter language, are ill-formed.\r
2822          *\r
2823          * @param locale the locale\r
2824          * @return this builder\r
2825          * @throws IllformedLocaleException if <code>locale</code> has\r
2826          * any ill-formed fields.\r
2827          *\r
2828          * @draft ICU 4.2\r
2829          * @provisional This API might change or be removed in a future release.\r
2830          */\r
2831         public Builder setLocale(ULocale locale) {\r
2832             try {\r
2833                 _locbld.setLocale(locale.base(), locale.extensions());\r
2834             } catch (LocaleSyntaxException e) {\r
2835                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
2836             }\r
2837             return this;\r
2838         }\r
2839 \r
2840         /**\r
2841          * Resets the builder to match the provided IETF BCP 47 language tag.\r
2842          * The previous state of the builder is discarded.\r
2843          *\r
2844          * @param languageTag the language tag\r
2845          * @return this builder\r
2846          * @throws IllformedLocaleException if <code>languageTag</code> is ill-formed.\r
2847          * @throws NullPointerException if <code>languageTag</code> is null.\r
2848          * @see ULocale#forLanguageTag(String)\r
2849          *\r
2850          * @draft ICU 4.2\r
2851          * @provisional This API might change or be removed in a future release.\r
2852          */\r
2853         public Builder setLanguageTag(String languageTag) {\r
2854             LanguageTag tag = null;\r
2855             try {\r
2856                 tag = LanguageTag.parseStrict(languageTag, _locbld.isLenientVariant());\r
2857             } catch (LocaleSyntaxException e) {\r
2858                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
2859             }\r
2860 \r
2861             try {\r
2862                 _locbld.setLocale(tag.getBaseLocale(),tag.getLocaleExtensions());\r
2863             } catch (LocaleSyntaxException e) {\r
2864                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
2865             }\r
2866 \r
2867             return this;\r
2868         }\r
2869 \r
2870         /**\r
2871          * Sets the language.  If <code>language</code> is the empty string,\r
2872          * the language in this <code>Builder</code> will be removed.\r
2873          * Typical language value is a two or three-letter language\r
2874          * code as defined in ISO639.\r
2875          * Well-formed values are any string of two to eight alpha\r
2876          * letters.  This method accepts upper case alpha letters\r
2877          * [A-Z], but the language value in the <code>ULocale</code>\r
2878          * created by the <code>Builder</code> is always normalized\r
2879          * to lower case letters.\r
2880          *\r
2881          * @param language the language\r
2882          * @return this builder\r
2883          * @throws IllformedLocaleException if <code>language</code> is ill-formed\r
2884          *\r
2885          * @draft ICU 4.2\r
2886          * @provisional This API might change or be removed in a future release.\r
2887          */\r
2888         public Builder setLanguage(String language) {\r
2889             try {\r
2890                 _locbld.setLanguage(language);\r
2891             } catch (LocaleSyntaxException e) {\r
2892                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
2893             }\r
2894             return this;\r
2895         }\r
2896 \r
2897         /**\r
2898          * Sets the script.  If <code>script</code> is the empty string,\r
2899          * the script in this <code>Builder</code> is removed.\r
2900          * Typical script value is a four-letter script code as defined by ISO 15924.\r
2901          * Well-formed values are any string of four alpha letters.\r
2902          * This method accepts both upper and lower case alpha letters [a-zA-Z],\r
2903          * but the script value in the <code>ULocale</code> created by the\r
2904          * <code>Builder</code> is always normalized to title case\r
2905          * (the first letter is upper case and the rest of letters are lower case).\r
2906          *\r
2907          * @param script the script\r
2908          * @return this builder\r
2909          * @throws IllformedLocaleException if <code>script</code> is ill-formed\r
2910          *\r
2911          * @draft ICU 4.2\r
2912          * @provisional This API might change or be removed in a future release.\r
2913          */\r
2914         public Builder setScript(String script) {\r
2915             try {\r
2916                 _locbld.setScript(script);\r
2917             } catch (LocaleSyntaxException e) {\r
2918                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
2919             }\r
2920             return this;\r
2921         }\r
2922 \r
2923         /**\r
2924          * Sets the region.  If region is the empty string, the region\r
2925          * in this <code>Builder</code> is removed.\r
2926          * Typical region value is a two-letter ISO 3166 code or a three-digit UN M.49\r
2927          * area code.  Well-formed values are any two-letter or three-digit string.\r
2928          * This method accepts lower case letters [a-z], but the country value in\r
2929          * the <code>ULocale</code> created by the <code>Builder</code> is always\r
2930          * normalized to upper case.\r
2931          *\r
2932          * @param region the region\r
2933          * @return this builder\r
2934          * @throws IllformedLocaleException if <code>region</code> is ill-formed\r
2935          *\r
2936          * @draft ICU 4.2\r
2937          * @provisional This API might change or be removed in a future release.\r
2938          */\r
2939         public Builder setRegion(String region) {\r
2940             try {\r
2941                 _locbld.setRegion(region);\r
2942             } catch (LocaleSyntaxException e) {\r
2943                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
2944             }\r
2945             return this;\r
2946         }\r
2947 \r
2948         /**\r
2949          * Sets the variant.  If variant is the empty string, the\r
2950          * variant in this <code>Builder</code> is removed.\r
2951          * <p>\r
2952          * <b>Note:</b> By default, this method checks if <code>variant</code>\r
2953          * satisfies the IETF BCP 47 variant subtag's syntax requirements.\r
2954          * However, the <code>ULocale</code> class itself does not impose any syntactical\r
2955          * restriction on variant.  When a <code>Builder</code> is created by the\r
2956          * constructor <code>Builder(boolean isLenientVariant)</code>\r
2957          * with <code>true</code>, this method skips the syntax check.\r
2958          *\r
2959          * @param variant the variant\r
2960          * @return this builder\r
2961          * @throws IllformedLocaleException if <code>variant</code> is ill-formed\r
2962          *\r
2963          * @draft ICU 4.2\r
2964          * @provisional This API might change or be removed in a future release.\r
2965          */\r
2966         public Builder setVariant(String variant) {\r
2967             try {\r
2968                 _locbld.setVariant(variant);\r
2969             } catch (LocaleSyntaxException e) {\r
2970                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
2971             }\r
2972             return this;\r
2973         }\r
2974 \r
2975         /**\r
2976          * Sets the extension for the given key. If the value is the\r
2977          * empty string, the extension is removed. Legal keys are\r
2978          * characters in the ranges <code>[0-9A-Za-z]</code>.  Keys\r
2979          * are case-insensitive, so for example 'z' and 'Z' represent\r
2980          * the same extension. In general, well-formed values are any\r
2981          * series of fields of two to eight alphanumeric characters,\r
2982          * separated by hyphen or underscore.\r
2983          *\r
2984          * <p><b>Note:</b> The key {@link ULocale#UNICODE_LOCALE_EXTENSION\r
2985          * UNICODE_LOCALE_EXTENSION} ('u') is used for the Unicode locale extension.\r
2986          * Setting a value for this key replaces any existing Unicode locale key/type\r
2987          * pairs with those defined in the extension.\r
2988          * To be well-formed, a value for this extension must meet the additional\r
2989          * constraints that each locale key is two alphanumeric characters,\r
2990          * followed by at least one locale type subtag represented by\r
2991          * three to eight alphanumeric characters, and that the keys and types\r
2992          * be legal Unicode locale keys and values.\r
2993          *\r
2994          * <p><b>Note:</b> The key {@link ULocale#PRIVATE_USE_EXTENSION\r
2995          * PRIVATE_USE_EXTENSION} ('x') is used for the private use code. To be\r
2996          * well-formed, the value for this key needs only to have fields of one to\r
2997          * eight alphanumeric characters, not two to eight as in the general case.\r
2998          *\r
2999          * @param key the extension key\r
3000          * @param value the extension value\r
3001          * @return this builder\r
3002          * @throws IllformedLocaleException if <code>key</code> is illegal\r
3003          * or <code>value</code> is ill-formed\r
3004          * @see #setUnicodeLocaleKeyword(String, String)\r
3005          *\r
3006          * @draft ICU 4.2\r
3007          * @provisional This API might change or be removed in a future release.\r
3008          */\r
3009         public Builder setExtension(char key, String value) {\r
3010             try {\r
3011                 _locbld.setExtension(key, value);\r
3012             } catch (LocaleSyntaxException e) {\r
3013                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
3014             }\r
3015             return this;\r
3016         }\r
3017 \r
3018         /**\r
3019          * Sets the Unicode locale keyword type for the given key.  If the\r
3020          * value is the empty string, the Unicode keyword is removed.\r
3021          * Well-formed keys are strings of two alphanumeric characters.\r
3022          * Well-formed types are one or more subtags where each of them is\r
3023          * three to eight alphanumeric characters.\r
3024          * <p>\r
3025          * <b>Note</b>:Setting the 'u' extension replaces all Unicode locale\r
3026          * keywords with those defined in the extension.\r
3027          * @param key the Unicode locale key\r
3028          * @param type the Unicode locale type\r
3029          * @return this builder\r
3030          * @throws IllformedLocaleException if <code>key</code> or <code>type</code>\r
3031          * is ill-formed\r
3032          * @see #setExtension(char, String)\r
3033          *\r
3034          * @draft ICU 4.4\r
3035          * @provisional This API might change or be removed in a future release.\r
3036          */\r
3037         public Builder setUnicodeLocaleKeyword(String key, String type) {\r
3038             try {\r
3039                 _locbld.setUnicodeLocaleExtension(key, type);\r
3040             } catch (LocaleSyntaxException e) {\r
3041                 throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex());\r
3042             }\r
3043             return this;\r
3044         }\r
3045 \r
3046         /**\r
3047          * Resets the builder to its initial, empty state.\r
3048          *\r
3049          * @return this builder\r
3050          *\r
3051          * @draft ICU 4.2\r
3052          * @provisional This API might change or be removed in a future release.\r
3053          */\r
3054         public Builder clear() {\r
3055             _locbld.clear();\r
3056             return this;\r
3057         }\r
3058 \r
3059         /**\r
3060          * Resets the extensions to their initial, empty state.\r
3061          * Language, script, region and variant are unchanged.\r
3062          *\r
3063          * @return this builder\r
3064          * @see #setExtension(char, String)\r
3065          *\r
3066          * @draft ICU 4.2\r
3067          * @provisional This API might change or be removed in a future release.\r
3068          */\r
3069         public Builder clearExtensions() {\r
3070             _locbld.removeLocaleExtensions();\r
3071             return this;\r
3072         }\r
3073 \r
3074         /**\r
3075          * Returns an instance of Locale created from the fields set\r
3076          * on this builder.\r
3077          *\r
3078          * @return a new Locale\r
3079          *\r
3080          * @draft ICU 4.4\r
3081          * @provisional This API might change or be removed in a future release.\r
3082          */\r
3083         public ULocale build() {\r
3084             return getInstance(_locbld.getBaseLocale(), _locbld.getLocaleExtensions());\r
3085         }\r
3086     }\r
3087 \r
3088     private static ULocale getInstance(BaseLocale base, LocaleExtensions exts) {\r
3089         String id = lscvToID(base.getLanguage(), base.getScript(), base.getRegion(),\r
3090                 base.getVariant());\r
3091 \r
3092         Set<Character> extKeys = exts.getKeys();\r
3093         if (!extKeys.isEmpty()) {\r
3094             // legacy locale ID assume Unicode locale keywords and\r
3095             // other extensions are at the same level.\r
3096             // e.g. @a=ext-for-aa;calendar=japanese;m=ext-for-mm;x=priv-use\r
3097 \r
3098             TreeMap<String, String> kwds = new TreeMap<String, String>();\r
3099             for (Character key : extKeys) {\r
3100                 Extension ext = exts.getExtension(key);\r
3101                 if (ext instanceof UnicodeLocaleExtension) {\r
3102                     UnicodeLocaleExtension uext = (UnicodeLocaleExtension)ext;\r
3103                     Set<String> ukeys = uext.getKeys();\r
3104                     for (String bcpKey : ukeys) {\r
3105                         String bcpType = uext.getType(bcpKey);\r
3106                         // convert to legacy key/type\r
3107                         String lkey = bcp47ToLDMLKey(bcpKey);\r
3108                         String ltype = bcp47ToLDMLType(lkey, bcpType);\r
3109                         kwds.put(lkey, ltype);\r
3110                     }\r
3111                 } else {\r
3112                     kwds.put(String.valueOf(key), ext.getValue());\r
3113                 }\r
3114             }\r
3115 \r
3116             if (!kwds.isEmpty()) {\r
3117                 StringBuilder buf = new StringBuilder(id);\r
3118                 buf.append("@");\r
3119                 Set<Map.Entry<String, String>> kset = kwds.entrySet();\r
3120                 boolean insertSep = false;\r
3121                 for (Map.Entry<String, String> kwd : kset) {\r
3122                     if (insertSep) {\r
3123                         buf.append(";");\r
3124                     } else {\r
3125                         insertSep = true;\r
3126                     }\r
3127                     buf.append(kwd.getKey());\r
3128                     buf.append("=");\r
3129                     buf.append(kwd.getValue());\r
3130                 }\r
3131 \r
3132                 id = buf.toString();\r
3133             }\r
3134         }\r
3135         return new ULocale(id);\r
3136     }\r
3137 \r
3138     private BaseLocale base() {\r
3139         if (baseLocale == null) {\r
3140             String language = getLanguage();\r
3141             if (equals(ULocale.ROOT)) {\r
3142                 language = "";\r
3143             }\r
3144             baseLocale = BaseLocale.getInstance(language, getScript(), getCountry(), getVariant());\r
3145         }\r
3146         return baseLocale;\r
3147     }\r
3148 \r
3149     private LocaleExtensions extensions() {\r
3150         if (extensions == null) {\r
3151             Iterator<String> kwitr = getKeywords();\r
3152             if (kwitr == null) {\r
3153                 extensions = LocaleExtensions.EMPTY_EXTENSIONS;\r
3154             } else {\r
3155                 InternalLocaleBuilder intbld = new InternalLocaleBuilder();\r
3156                 while (kwitr.hasNext()) {\r
3157                     String key = kwitr.next();\r
3158                     if (key.length() >= 2) {\r
3159                         String bcpKey = ldmlKeyToBCP47(key);\r
3160                         String bcpType = ldmlTypeToBCP47(key, getKeywordValue(key));\r
3161                         if (bcpKey != null && bcpType != null) {\r
3162                             try {\r
3163                                 intbld.setUnicodeLocaleExtension(bcpKey, bcpType);\r
3164                             } catch (LocaleSyntaxException e) {\r
3165                                 // ignore and fall through\r
3166                             }\r
3167                         }\r
3168                     } else if (key.length() == 1 && (key.charAt(0) != UNICODE_LOCALE_EXTENSION)) {\r
3169                         try  {\r
3170                             intbld.setExtension(key.charAt(0), getKeywordValue(key).replace("_",\r
3171                                     LanguageTag.SEP));\r
3172                         } catch (LocaleSyntaxException e) {\r
3173                             // ignore and fall through\r
3174                         }\r
3175                     }\r
3176                 }\r
3177                 extensions = intbld.getLocaleExtensions();\r
3178             }\r
3179         }\r
3180         return extensions;\r
3181     }\r
3182 \r
3183     //\r
3184     // LDML legacy/BCP47 key and type mapping functions\r
3185     //\r
3186     private static String ldmlKeyToBCP47(String key) {\r
3187         UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(\r
3188                                             ICUResourceBundle.ICU_BASE_NAME,\r
3189                                             "keyTypeData",\r
3190                                             ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
3191         UResourceBundle keyMap = keyTypeData.get("keyMap");\r
3192 \r
3193         // normalize key to lowercase\r
3194         key = AsciiUtil.toLowerString(key);\r
3195         String bcpKey = null;\r
3196         try {\r
3197             bcpKey = keyMap.getString(key);\r
3198         } catch (MissingResourceException mre) {\r
3199             // fall through\r
3200         }\r
3201 \r
3202         if (bcpKey == null) {\r
3203             if (key.length() == 2 && LanguageTag.isExtensionSubtag(key)) {\r
3204                 return key;\r
3205             }\r
3206             return null;\r
3207         }\r
3208         return bcpKey;\r
3209     }\r
3210 \r
3211     private static String bcp47ToLDMLKey(String bcpKey) {\r
3212         UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(\r
3213                                             ICUResourceBundle.ICU_BASE_NAME,\r
3214                                             "keyTypeData",\r
3215                                             ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
3216         UResourceBundle keyMap = keyTypeData.get("keyMap");\r
3217 \r
3218         // normalize bcp key to lowercase\r
3219         bcpKey = AsciiUtil.toLowerString(bcpKey);\r
3220         String key = null;\r
3221         for (int i = 0; i < keyMap.getSize(); i++) {\r
3222             UResourceBundle mapData = keyMap.get(i);\r
3223             if (bcpKey.equals(mapData.getString())) {\r
3224                 key = mapData.getKey();\r
3225                 break;\r
3226             }\r
3227         }\r
3228         if (key == null) {\r
3229             return bcpKey;\r
3230         }\r
3231         return key;\r
3232     }\r
3233 \r
3234     private static String ldmlTypeToBCP47(String key, String type) {\r
3235         UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(\r
3236                                             ICUResourceBundle.ICU_BASE_NAME,\r
3237                                             "keyTypeData",\r
3238                                             ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
3239         UResourceBundle typeMap = keyTypeData.get("typeMap");\r
3240 \r
3241         // keys are case-insensitive, while types are case-sensitive\r
3242         key = AsciiUtil.toLowerString(key);\r
3243         UResourceBundle typeMapForKey = null;\r
3244         String bcpType = null;\r
3245         String typeResKey = key.equals("timezone") ? type.replace('/', ':') : type;\r
3246         try {\r
3247             typeMapForKey = typeMap.get(key);\r
3248             bcpType = typeMapForKey.getString(typeResKey);\r
3249         } catch (MissingResourceException mre) {\r
3250             // fall through\r
3251         }\r
3252 \r
3253         if (bcpType == null && typeMapForKey != null) {\r
3254             // is this type alias?\r
3255             UResourceBundle typeAlias = keyTypeData.get("typeAlias");\r
3256             try {\r
3257                 UResourceBundle typeAliasForKey = typeAlias.get(key);\r
3258                 typeResKey = typeAliasForKey.getString(typeResKey);\r
3259                 bcpType = typeMapForKey.getString(typeResKey.replace('/', ':'));\r
3260             } catch (MissingResourceException mre) {\r
3261                 // fall through\r
3262             }\r
3263         }\r
3264 \r
3265         if (bcpType == null) {\r
3266             int typeLen = type.length();\r
3267             if (typeLen >= 3 && typeLen <= 8 && LanguageTag.isExtensionSubtag(type)) {\r
3268                 return type;\r
3269             }\r
3270             return null;\r
3271         }\r
3272         return bcpType;\r
3273     }\r
3274 \r
3275     private static String bcp47ToLDMLType(String key, String bcpType) {\r
3276         UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(\r
3277                                             ICUResourceBundle.ICU_BASE_NAME,\r
3278                                             "keyTypeData",\r
3279                                             ICUResourceBundle.ICU_DATA_CLASS_LOADER);\r
3280         UResourceBundle typeMap = keyTypeData.get("typeMap");\r
3281 \r
3282         // normalize key/bcpType to lowercase\r
3283         key = AsciiUtil.toLowerString(key);\r
3284         bcpType = AsciiUtil.toLowerString(bcpType);\r
3285 \r
3286         String type = null;\r
3287         try {\r
3288             UResourceBundle typeMapForKey = typeMap.get(key);\r
3289 \r
3290             // Note:    Linear search for time zone ID might be too slow.\r
3291             //          ICU services do not use timezone keywords for now.\r
3292             //          In future, we may need to build the optimized inverse\r
3293             //          lookup table.\r
3294 \r
3295             for (int i = 0; i < typeMapForKey.getSize(); i++) {\r
3296                 UResourceBundle mapData = typeMapForKey.get(i);\r
3297                 if (bcpType.equals(mapData.getString())) {\r
3298                     type = mapData.getKey();\r
3299                     if (key.equals("timezone")) {\r
3300                         type = type.replace(':', '/');\r
3301                     }\r
3302                     break;\r
3303                 }\r
3304             }\r
3305         } catch (MissingResourceException mre) {\r
3306             // fall through\r
3307         }\r
3308 \r
3309         if (type == null) {\r
3310             return bcpType;\r
3311         }\r
3312         return type;\r
3313     }\r
3314 }\r