]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/dev/eclipse/plugins/com.ibm.icu.base/src/com/ibm/icu/util/ULocale.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / dev / eclipse / plugins / com.ibm.icu.base / src / com / ibm / icu / util / ULocale.java
1 /*\r
2 ******************************************************************************\r
3 * Copyright (C) 2003-2008, 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.lang.ref.SoftReference;\r
12 import java.text.ParseException;\r
13 import java.util.Collections;\r
14 import java.util.Comparator;\r
15 import java.util.HashMap;\r
16 import java.util.Iterator;\r
17 import java.util.Locale;\r
18 import java.util.Map;\r
19 import java.util.MissingResourceException;\r
20 import java.util.TreeMap;\r
21 \r
22 //import com.ibm.icu.impl.SimpleCache;\r
23 //import com.ibm.icu.impl.ICUResourceBundle;\r
24 //import com.ibm.icu.impl.LocaleUtility;\r
25 \r
26 /**\r
27  * A class analogous to {@link java.util.Locale} that provides additional\r
28  * support for ICU protocol.  In ICU 3.0 this class is enhanced to support\r
29  * RFC 3066 language identifiers.\r
30  *\r
31  * <p>Many classes and services in ICU follow a factory idiom, in\r
32  * which a factory method or object responds to a client request with\r
33  * an object.  The request includes a locale (the <i>requested</i>\r
34  * locale), and the returned object is constructed using data for that\r
35  * locale.  The system may lack data for the requested locale, in\r
36  * which case the locale fallback mechanism will be invoked until a\r
37  * populated locale is found (the <i>valid</i> locale).  Furthermore,\r
38  * even when a populated locale is found (the <i>valid</i> locale),\r
39  * further fallback may be required to reach a locale containing the\r
40  * specific data required by the service (the <i>actual</i> locale).\r
41  *\r
42  * <p>ULocale performs <b>'normalization'</b> and <b>'canonicalization'</b> of locale ids.\r
43  * Normalization 'cleans up' ICU locale ids as follows:\r
44  * <ul>\r
45  * <li>language, script, country, variant, and keywords are properly cased<br>\r
46  * (lower, title, upper, upper, and lower case respectively)</li>\r
47  * <li>hyphens used as separators are converted to underscores</li>\r
48  * <li>three-letter language and country ids are converted to two-letter\r
49  * equivalents where available</li>\r
50  * <li>surrounding spaces are removed from keywords and values</li>\r
51  * <li>if there are multiple keywords, they are put in sorted order</li>\r
52  * </ul>\r
53  * Canonicalization additionally performs the following:\r
54  * <ul>\r
55  * <li>POSIX ids are converted to ICU format IDs</li>\r
56  * <li>'grandfathered' 3066 ids are converted to ICU standard form</li>\r
57  * <li>'PREEURO' and 'EURO' variants are converted to currency keyword form, with the currency\r
58  * id appropriate to the country of the locale (for PREEURO) or EUR (for EURO).\r
59  * </ul>\r
60  * All ULocale constructors automatically normalize the locale id.  To handle\r
61  * POSIX ids, <code>canonicalize</code> can be called to convert the id\r
62  * to canonical form, or the <code>canonicalInstance</code> factory method\r
63  * can be called.</p>\r
64  *\r
65  * <p>This class provides selectors {@link #VALID_LOCALE} and {@link\r
66  * #ACTUAL_LOCALE} intended for use in methods named\r
67  * <tt>getLocale()</tt>.  These methods exist in several ICU classes,\r
68  * including {@link com.ibm.icu.util.Calendar}, {@link\r
69  * com.ibm.icu.util.Currency}, {@link com.ibm.icu.text.UFormat},\r
70  * {@link com.ibm.icu.text.BreakIterator}, {@link\r
71  * com.ibm.icu.text.Collator}, {@link\r
72  * com.ibm.icu.text.DateFormatSymbols}, and {@link\r
73  * com.ibm.icu.text.DecimalFormatSymbols} and their subclasses, if\r
74  * any.  Once an object of one of these classes has been created,\r
75  * <tt>getLocale()</tt> may be called on it to determine the valid and\r
76  * actual locale arrived at during the object's construction.\r
77  *\r
78  * <p>Note: The <tt>getLocale()</tt> method will be implemented in ICU\r
79  * 3.0; ICU 2.8 contains a partial preview implementation.  The\r
80  * <i>actual</i> locale is returned correctly, but the <i>valid</i>\r
81  * locale is not, in most cases.\r
82  *\r
83  * @see java.util.Locale\r
84  * @author weiv\r
85  * @author Alan Liu\r
86  * @author Ram Viswanadha\r
87  * @stable ICU 2.8 \r
88  */\r
89 public final class ULocale implements Serializable {\r
90     // using serialver from jdk1.4.2_05\r
91     private static final long serialVersionUID = 3715177670352309217L;\r
92 \r
93     /** \r
94      * Useful constant for language.\r
95      * @stable ICU 3.0\r
96      */\r
97     public static final ULocale ENGLISH = new ULocale("en", Locale.ENGLISH);\r
98 \r
99     /** \r
100      * Useful constant for language.\r
101      * @stable ICU 3.0\r
102      */\r
103     public static final ULocale FRENCH = new ULocale("fr", Locale.FRENCH);\r
104 \r
105     /** \r
106      * Useful constant for language.\r
107      * @stable ICU 3.0\r
108      */\r
109     public static final ULocale GERMAN = new ULocale("de", Locale.GERMAN);\r
110 \r
111     /** \r
112      * Useful constant for language.\r
113      * @stable ICU 3.0\r
114      */\r
115     public static final ULocale ITALIAN = new ULocale("it", Locale.ITALIAN);\r
116 \r
117     /** \r
118      * Useful constant for language.\r
119      * @stable ICU 3.0\r
120      */\r
121     public static final ULocale JAPANESE = new ULocale("ja", Locale.JAPANESE);\r
122 \r
123     /** \r
124      * Useful constant for language.\r
125      * @stable ICU 3.0\r
126      */\r
127     public static final ULocale KOREAN = new ULocale("ko", Locale.KOREAN);\r
128 \r
129     /** \r
130      * Useful constant for language.\r
131      * @stable ICU 3.0\r
132      */\r
133     public static final ULocale CHINESE = new ULocale("zh", Locale.CHINESE);\r
134 \r
135     /** \r
136      * Useful constant for language.\r
137      * @stable ICU 3.0\r
138      */\r
139     public static final ULocale SIMPLIFIED_CHINESE = new ULocale("zh_Hans", Locale.CHINESE);\r
140 \r
141     /** \r
142      * Useful constant for language.\r
143      * @stable ICU 3.0\r
144      */\r
145     public static final ULocale TRADITIONAL_CHINESE = new ULocale("zh_Hant", Locale.CHINESE);\r
146 \r
147     /** \r
148      * Useful constant for country/region.\r
149      * @stable ICU 3.0\r
150      */\r
151     public static final ULocale FRANCE = new ULocale("fr_FR", Locale.FRANCE);\r
152 \r
153     /** \r
154      * Useful constant for country/region.\r
155      * @stable ICU 3.0\r
156      */\r
157     public static final ULocale GERMANY = new ULocale("de_DE", Locale.GERMANY);\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 ITALY = new ULocale("it_IT", Locale.ITALY);\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 JAPAN = new ULocale("ja_JP", Locale.JAPAN);\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 KOREA = new ULocale("ko_KR", Locale.KOREA);\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 CHINA = new ULocale("zh_Hans_CN", Locale.CHINA);\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 PRC = CHINA;\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 TAIWAN = new ULocale("zh_Hant_TW", Locale.TAIWAN);\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 UK = new ULocale("en_GB", Locale.UK);\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 US = new ULocale("en_US", Locale.US);\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 CANADA = new ULocale("en_CA", Locale.CANADA);\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 CANADA_FRENCH = new ULocale("fr_CA", Locale.CANADA_FRENCH);\r
218 \r
219     /**\r
220      * Handy constant.\r
221      */\r
222     private static final String EMPTY_STRING = "";\r
223 \r
224     // Used in both ULocale and IDParser, so moved up here.\r
225     private static final char UNDERSCORE            = '_';\r
226 \r
227     // default empty locale\r
228     private static final Locale EMPTY_LOCALE = new Locale("", "");\r
229 \r
230     /**\r
231      * The root ULocale.\r
232      * @stable ICU 2.8\r
233      */ \r
234     public static final ULocale ROOT = new ULocale("root", EMPTY_LOCALE);\r
235     \r
236 //    private static final SimpleCache CACHE = new SimpleCache();\r
237     private static final HashMap CACHE = new HashMap(20);\r
238 \r
239     /**\r
240      * Cache the locale.\r
241      */\r
242     private transient Locale locale;\r
243 \r
244     /**\r
245      * The raw localeID that we were passed in.\r
246      */\r
247     private String localeID;\r
248 \r
249     /**\r
250      * Tables used in normalizing portions of the id.\r
251      */\r
252     /* tables updated per http://lcweb.loc.gov/standards/iso639-2/ \r
253        to include the revisions up to 2001/7/27 *CWB*/\r
254     /* The 3 character codes are the terminology codes like RFC 3066.  \r
255        This is compatible with prior ICU codes */\r
256     /* "in" "iw" "ji" "jw" & "sh" have been withdrawn but are still in \r
257        the table but now at the end of the table because \r
258        3 character codes are duplicates.  This avoids bad searches\r
259        going from 3 to 2 character codes.*/\r
260     /* The range qaa-qtz is reserved for local use. */\r
261 \r
262     private static String[] _languages;\r
263     private static String[] _replacementLanguages;\r
264     private static String[] _obsoleteLanguages;\r
265     private static String[] _languages3;\r
266     private static String[] _obsoleteLanguages3;\r
267 \r
268     // Avoid initializing languages tables unless we have to.\r
269     private static void initLanguageTables() {\r
270         if (_languages == null) {\r
271 \r
272             /* This list MUST be in sorted order, and MUST contain the two-letter codes\r
273                if one exists otherwise use the three letter code */\r
274             String[] tempLanguages = {\r
275                 "aa",  "ab",  "ace", "ach", "ada", "ady", "ae",  "af",  "afa",\r
276                 "afh", "ak",  "akk", "ale", "alg", "am",  "an",  "ang", "apa",\r
277                 "ar",  "arc", "arn", "arp", "art", "arw", "as",  "ast",\r
278                 "ath", "aus", "av",  "awa", "ay",  "az",  "ba",  "bad",\r
279                 "bai", "bal", "ban", "bas", "bat", "be",  "bej",\r
280                 "bem", "ber", "bg",  "bh",  "bho", "bi",  "bik", "bin",\r
281                 "bla", "bm",  "bn",  "bnt", "bo",  "br",  "bra", "bs",\r
282                 "btk", "bua", "bug", "byn", "ca",  "cad", "cai", "car", "cau",\r
283                 "ce",  "ceb", "cel", "ch",  "chb", "chg", "chk", "chm",\r
284                 "chn", "cho", "chp", "chr", "chy", "cmc", "co",  "cop",\r
285                 "cpe", "cpf", "cpp", "cr",  "crh", "crp", "cs",  "csb", "cu",  "cus",\r
286                 "cv",  "cy",  "da",  "dak", "dar", "day", "de",  "del", "den",\r
287                 "dgr", "din", "doi", "dra", "dsb", "dua", "dum", "dv",  "dyu",\r
288                 "dz",  "ee",  "efi", "egy", "eka", "el",  "elx", "en",\r
289                 "enm", "eo",  "es",  "et",  "eu",  "ewo", "fa",\r
290                 "fan", "fat", "ff",  "fi",  "fiu", "fj",  "fo",  "fon",\r
291                 "fr",  "frm", "fro", "fur", "fy",  "ga",  "gaa", "gay",\r
292                 "gba", "gd",  "gem", "gez", "gil", "gl",  "gmh", "gn",\r
293                 "goh", "gon", "gor", "got", "grb", "grc", "gu",  "gv",\r
294                 "gwi", "ha",  "hai", "haw", "he",  "hi",  "hil", "him",\r
295                 "hit", "hmn", "ho",  "hr",  "hsb", "ht",  "hu",  "hup", "hy",  "hz",\r
296                 "ia",  "iba", "id",  "ie",  "ig",  "ii",  "ijo", "ik",\r
297                 "ilo", "inc", "ine", "inh", "io",  "ira", "iro", "is",  "it",\r
298                 "iu",  "ja",  "jbo", "jpr", "jrb", "jv",  "ka",  "kaa", "kab",\r
299                 "kac", "kam", "kar", "kaw", "kbd", "kg",  "kha", "khi",\r
300                 "kho", "ki",  "kj",  "kk",  "kl",  "km",  "kmb", "kn",\r
301                 "ko",  "kok", "kos", "kpe", "kr",  "krc", "kro", "kru", "ks",\r
302                 "ku",  "kum", "kut", "kv",  "kw",  "ky",  "la",  "lad",\r
303                 "lah", "lam", "lb",  "lez", "lg",  "li",  "ln",  "lo",  "lol",\r
304                 "loz", "lt",  "lu",  "lua", "lui", "lun", "luo", "lus",\r
305                 "lv",  "mad", "mag", "mai", "mak", "man", "map", "mas",\r
306                 "mdf", "mdr", "men", "mg",  "mga", "mh",  "mi",  "mic", "min",\r
307                 "mis", "mk",  "mkh", "ml",  "mn",  "mnc", "mni", "mno",\r
308                 "mo",  "moh", "mos", "mr",  "ms",  "mt",  "mul", "mun",\r
309                 "mus", "mwr", "my",  "myn", "myv", "na",  "nah", "nai", "nap",\r
310                 "nb",  "nd",  "nds", "ne",  "new", "ng",  "nia", "nic",\r
311                 "niu", "nl",  "nn",  "no",  "nog", "non", "nr",  "nso", "nub",\r
312                 "nv",  "nwc", "ny",  "nym", "nyn", "nyo", "nzi", "oc",  "oj",\r
313                 "om",  "or",  "os",  "osa", "ota", "oto", "pa",  "paa",\r
314                 "pag", "pal", "pam", "pap", "pau", "peo", "phi", "phn",\r
315                 "pi",  "pl",  "pon", "pra", "pro", "ps",  "pt",  "qu",\r
316                 "raj", "rap", "rar", "rm",  "rn",  "ro",  "roa", "rom",\r
317                 "ru",  "rup", "rw",  "sa",  "sad", "sah", "sai", "sal", "sam",\r
318                 "sas", "sat", "sc",  "sco", "sd",  "se",  "sel", "sem",\r
319                 "sg",  "sga", "sgn", "shn", "si",  "sid", "sio", "sit",\r
320                 "sk",  "sl",  "sla", "sm",  "sma", "smi", "smj", "smn",\r
321                 "sms", "sn",  "snk", "so",  "sog", "son", "sq",  "sr",\r
322                 "srr", "ss",  "ssa", "st",  "su",  "suk", "sus", "sux",\r
323                 "sv",  "sw",  "syr", "ta",  "tai", "te",  "tem", "ter",\r
324                 "tet", "tg",  "th",  "ti",  "tig", "tiv", "tk",  "tkl",\r
325                 "tl",  "tlh", "tli", "tmh", "tn",  "to",  "tog", "tpi", "tr",\r
326                 "ts",  "tsi", "tt",  "tum", "tup", "tut", "tvl", "tw",\r
327                 "ty",  "tyv", "udm", "ug",  "uga", "uk",  "umb", "und", "ur",\r
328                 "uz",  "vai", "ve",  "vi",  "vo",  "vot", "wa",  "wak",\r
329                 "wal", "war", "was", "wen", "wo",  "xal", "xh",  "yao", "yap",\r
330                 "yi",  "yo",  "ypk", "za",  "zap", "zen", "zh",  "znd",\r
331                 "zu",  "zun", \r
332             };\r
333 \r
334             String[] tempReplacementLanguages = {\r
335                 "id", "he", "yi", "jv", "sr", "nb",/* replacement language codes */\r
336             };\r
337 \r
338             String[] tempObsoleteLanguages = {\r
339                 "in", "iw", "ji", "jw", "sh", "no",    /* obsolete language codes */         \r
340             };\r
341 \r
342             /* This list MUST contain a three-letter code for every two-letter code in the\r
343                list above, and they MUST ne in the same order (i.e., the same language must\r
344                be in the same place in both lists)! */\r
345             String[] tempLanguages3 = {\r
346                 /*"aa",  "ab",  "ace", "ach", "ada", "ady", "ae",  "af",  "afa",    */\r
347                 "aar", "abk", "ace", "ach", "ada", "ady", "ave", "afr", "afa",\r
348                 /*"afh", "ak",  "akk", "ale", "alg", "am",  "an",  "ang", "apa",    */\r
349                 "afh", "aka", "akk", "ale", "alg", "amh", "arg", "ang", "apa",\r
350                 /*"ar",  "arc", "arn", "arp", "art", "arw", "as",  "ast",    */\r
351                 "ara", "arc", "arn", "arp", "art", "arw", "asm", "ast",\r
352                 /*"ath", "aus", "av",  "awa", "ay",  "az",  "ba",  "bad",    */\r
353                 "ath", "aus", "ava", "awa", "aym", "aze", "bak", "bad",\r
354                 /*"bai", "bal", "ban", "bas", "bat", "be",  "bej",    */\r
355                 "bai", "bal", "ban", "bas", "bat", "bel", "bej",\r
356                 /*"bem", "ber", "bg",  "bh",  "bho", "bi",  "bik", "bin",    */\r
357                 "bem", "ber", "bul", "bih", "bho", "bis", "bik", "bin",\r
358                 /*"bla", "bm",  "bn",  "bnt", "bo",  "br",  "bra", "bs",     */\r
359                 "bla", "bam",  "ben", "bnt", "bod", "bre", "bra", "bos",\r
360                 /*"btk", "bua", "bug", "byn", "ca",  "cad", "cai", "car", "cau",    */\r
361                 "btk", "bua", "bug", "byn", "cat", "cad", "cai", "car", "cau",\r
362                 /*"ce",  "ceb", "cel", "ch",  "chb", "chg", "chk", "chm",    */\r
363                 "che", "ceb", "cel", "cha", "chb", "chg", "chk", "chm",\r
364                 /*"chn", "cho", "chp", "chr", "chy", "cmc", "co",  "cop",    */\r
365                 "chn", "cho", "chp", "chr", "chy", "cmc", "cos", "cop",\r
366                 /*"cpe", "cpf", "cpp", "cr",  "crh", "crp", "cs",  "csb", "cu",  "cus",    */\r
367                 "cpe", "cpf", "cpp", "cre", "crh", "crp", "ces", "csb", "chu", "cus",\r
368                 /*"cv",  "cy",  "da",  "dak", "dar", "day", "de",  "del", "den",    */\r
369                 "chv", "cym", "dan", "dak", "dar", "day", "deu", "del", "den",\r
370                 /*"dgr", "din", "doi", "dra", "dsb", "dua", "dum", "dv",  "dyu",    */\r
371                 "dgr", "din", "doi", "dra", "dsb", "dua", "dum", "div", "dyu",\r
372                 /*"dz",  "ee",  "efi", "egy", "eka", "el",  "elx", "en",     */\r
373                 "dzo", "ewe", "efi", "egy", "eka", "ell", "elx", "eng",\r
374                 /*"enm", "eo",  "es",  "et",  "eu",  "ewo", "fa",     */\r
375                 "enm", "epo", "spa", "est", "eus", "ewo", "fas",\r
376                 /*"fan", "fat", "ff",  "fi",  "fiu", "fj",  "fo",  "fon",    */\r
377                 "fan", "fat", "ful", "fin", "fiu", "fij", "fao", "fon",\r
378                 /*"fr",  "frm", "fro", "fur", "fy",  "ga",  "gaa", "gay",    */\r
379                 "fra", "frm", "fro", "fur", "fry", "gle", "gaa", "gay",\r
380                 /*"gba", "gd",  "gem", "gez", "gil", "gl",  "gmh", "gn",     */\r
381                 "gba", "gla", "gem", "gez", "gil", "glg", "gmh", "grn",\r
382                 /*"goh", "gon", "gor", "got", "grb", "grc", "gu",  "gv",     */\r
383                 "goh", "gon", "gor", "got", "grb", "grc", "guj", "glv",\r
384                 /*"gwi", "ha",  "hai", "haw", "he",  "hi",  "hil", "him",    */\r
385                 "gwi", "hau", "hai", "haw", "heb", "hin", "hil", "him",\r
386                 /*"hit", "hmn", "ho",  "hr",  "hsb", "ht",  "hu",  "hup", "hy",  "hz",     */\r
387                 "hit", "hmn", "hmo", "hrv", "hsb", "hat", "hun", "hup", "hye", "her",\r
388                 /*"ia",  "iba", "id",  "ie",  "ig",  "ii",  "ijo", "ik",     */\r
389                 "ina", "iba", "ind", "ile", "ibo", "iii", "ijo", "ipk",\r
390                 /*"ilo", "inc", "ine", "inh", "io",  "ira", "iro", "is",  "it",      */\r
391                 "ilo", "inc", "ine", "inh", "ido", "ira", "iro", "isl", "ita",\r
392                 /*"iu",  "ja",  "jbo", "jpr", "jrb", "jv",  "ka",  "kaa", "kab",   */\r
393                 "iku", "jpn", "jbo", "jpr", "jrb", "jaw", "kat", "kaa", "kab",\r
394                 /*"kac", "kam", "kar", "kaw", "kbd", "kg",  "kha", "khi",    */\r
395                 "kac", "kam", "kar", "kaw", "kbd", "kon", "kha", "khi",\r
396                 /*"kho", "ki",  "kj",  "kk",  "kl",  "km",  "kmb", "kn",     */\r
397                 "kho", "kik", "kua", "kaz", "kal", "khm", "kmb", "kan",\r
398                 /*"ko",  "kok", "kos", "kpe", "kr",  "krc", "kro", "kru", "ks",     */\r
399                 "kor", "kok", "kos", "kpe", "kau", "krc", "kro", "kru", "kas",\r
400                 /*"ku",  "kum", "kut", "kv",  "kw",  "ky",  "la",  "lad",    */\r
401                 "kur", "kum", "kut", "kom", "cor", "kir", "lat", "lad",\r
402                 /*"lah", "lam", "lb",  "lez", "lg",  "li",  "ln",  "lo",  "lol",    */\r
403                 "lah", "lam", "ltz", "lez", "lug", "lim", "lin", "lao", "lol",\r
404                 /*"loz", "lt",  "lu",  "lua", "lui", "lun", "luo", "lus",    */\r
405                 "loz", "lit", "lub", "lua", "lui", "lun", "luo", "lus",\r
406                 /*"lv",  "mad", "mag", "mai", "mak", "man", "map", "mas",    */\r
407                 "lav", "mad", "mag", "mai", "mak", "man", "map", "mas",\r
408                 /*"mdf", "mdr", "men", "mg",  "mga", "mh",  "mi",  "mic", "min",    */\r
409                 "mdf", "mdr", "men", "mlg", "mga", "mah", "mri", "mic", "min",\r
410                 /*"mis", "mk",  "mkh", "ml",  "mn",  "mnc", "mni", "mno",    */\r
411                 "mis", "mkd", "mkh", "mal", "mon", "mnc", "mni", "mno",\r
412                 /*"mo",  "moh", "mos", "mr",  "ms",  "mt",  "mul", "mun",    */\r
413                 "mol", "moh", "mos", "mar", "msa", "mlt", "mul", "mun",\r
414                 /*"mus", "mwr", "my",  "myn", "myv", "na",  "nah", "nai", "nap",    */\r
415                 "mus", "mwr", "mya", "myn", "myv", "nau", "nah", "nai", "nap",\r
416                 /*"nb",  "nd",  "nds", "ne",  "new", "ng",  "nia", "nic",    */\r
417                 "nob", "nde", "nds", "nep", "new", "ndo", "nia", "nic",\r
418                 /*"niu", "nl",  "nn",  "no",  "nog", "non", "nr",  "nso", "nub",    */\r
419                 "niu", "nld", "nno", "nor", "nog", "non", "nbl", "nso", "nub",\r
420                 /*"nv",  "nwc", "ny",  "nym", "nyn", "nyo", "nzi", "oc",  "oj",     */\r
421                 "nav", "nwc", "nya", "nym", "nyn", "nyo", "nzi", "oci", "oji",\r
422                 /*"om",  "or",  "os",  "osa", "ota", "oto", "pa",  "paa",    */\r
423                 "orm", "ori", "oss", "osa", "ota", "oto", "pan", "paa",\r
424                 /*"pag", "pal", "pam", "pap", "pau", "peo", "phi", "phn",    */\r
425                 "pag", "pal", "pam", "pap", "pau", "peo", "phi", "phn",\r
426                 /*"pi",  "pl",  "pon", "pra", "pro", "ps",  "pt",  "qu",     */\r
427                 "pli", "pol", "pon", "pra", "pro", "pus", "por", "que",\r
428                 /*"raj", "rap", "rar", "rm",  "rn",  "ro",  "roa", "rom",    */\r
429                 "raj", "rap", "rar", "roh", "run", "ron", "roa", "rom",\r
430                 /*"ru",  "rup", "rw",  "sa",  "sad", "sah", "sai", "sal", "sam",    */\r
431                 "rus", "rup", "kin", "san", "sad", "sah", "sai", "sal", "sam",\r
432                 /*"sas", "sat", "sc",  "sco", "sd",  "se",  "sel", "sem",    */\r
433                 "sas", "sat", "srd", "sco", "snd", "sme", "sel", "sem",\r
434                 /*"sg",  "sga", "sgn", "shn", "si",  "sid", "sio", "sit",    */\r
435                 "sag", "sga", "sgn", "shn", "sin", "sid", "sio", "sit",\r
436                 /*"sk",  "sl",  "sla", "sm",  "sma", "smi", "smj", "smn",    */\r
437                 "slk", "slv", "sla", "smo", "sma", "smi", "smj", "smn",\r
438                 /*"sms", "sn",  "snk", "so",  "sog", "son", "sq",  "sr",     */\r
439                 "sms", "sna", "snk", "som", "sog", "son", "sqi", "srp",\r
440                 /*"srr", "ss",  "ssa", "st",  "su",  "suk", "sus", "sux",    */\r
441                 "srr", "ssw", "ssa", "sot", "sun", "suk", "sus", "sux",\r
442                 /*"sv",  "sw",  "syr", "ta",  "tai", "te",  "tem", "ter",    */\r
443                 "swe", "swa", "syr", "tam", "tai", "tel", "tem", "ter",\r
444                 /*"tet", "tg",  "th",  "ti",  "tig", "tiv", "tk",  "tkl",    */\r
445                 "tet", "tgk", "tha", "tir", "tig", "tiv", "tuk", "tkl",\r
446                 /*"tl",  "tlh", "tli", "tmh", "tn",  "to",  "tog", "tpi", "tr",     */\r
447                 "tgl", "tlh", "tli", "tmh", "tsn", "ton", "tog", "tpi", "tur",\r
448                 /*"ts",  "tsi", "tt",  "tum", "tup", "tut", "tvl", "tw",     */\r
449                 "tso", "tsi", "tat", "tum", "tup", "tut", "tvl", "twi",\r
450                 /*"ty",  "tyv", "udm", "ug",  "uga", "uk",  "umb", "und", "ur",     */\r
451                 "tah", "tyv", "udm", "uig", "uga", "ukr", "umb", "und", "urd",\r
452                 /*"uz",  "vai", "ve",  "vi",  "vo",  "vot", "wa",  "wak",    */\r
453                 "uzb", "vai", "ven", "vie", "vol", "vot", "wln", "wak",\r
454                 /*"wal", "war", "was", "wen", "wo",  "xal", "xh",  "yao", "yap",    */\r
455                 "wal", "war", "was", "wen", "wol", "xal", "xho", "yao", "yap",\r
456                 /*"yi",  "yo",  "ypk", "za",  "zap", "zen", "zh",  "znd",    */\r
457                 "yid", "yor", "ypk", "zha", "zap", "zen", "zho", "znd",\r
458                 /*"zu",  "zun",                                              */\r
459                 "zul", "zun",  \r
460             };\r
461     \r
462             String[] tempObsoleteLanguages3 = {\r
463                 /* "in",  "iw",  "ji",  "jw",  "sh", */\r
464                 "ind", "heb", "yid", "jaw", "srp", \r
465             };\r
466 \r
467             synchronized (ULocale.class) {\r
468                 if (_languages == null) {\r
469                     _languages = tempLanguages;\r
470                     _replacementLanguages = tempReplacementLanguages;\r
471                     _obsoleteLanguages = tempObsoleteLanguages;\r
472                     _languages3 = tempLanguages3;\r
473                     _obsoleteLanguages3 = tempObsoleteLanguages3;\r
474                 }\r
475             }\r
476         }\r
477     }\r
478 \r
479     private static String[] _countries;\r
480     private static String[] _deprecatedCountries;\r
481     private static String[] _replacementCountries;\r
482     private static String[] _obsoleteCountries;\r
483     private static String[] _countries3;\r
484     private static String[] _obsoleteCountries3;  \r
485 \r
486     // Avoid initializing country tables unless we have to.\r
487     private static void initCountryTables() {    \r
488         if (_countries == null) {\r
489             /* ZR(ZAR) is now CD(COD) and FX(FXX) is PS(PSE) as per\r
490                http://www.evertype.com/standards/iso3166/iso3166-1-en.html \r
491                added new codes keeping the old ones for compatibility\r
492                updated to include 1999/12/03 revisions *CWB*/\r
493     \r
494             /* RO(ROM) is now RO(ROU) according to \r
495                http://www.iso.org/iso/en/prods-services/iso3166ma/03updates-on-iso-3166/nlv3e-rou.html\r
496             */\r
497     \r
498             /* This list MUST be in sorted order, and MUST contain only two-letter codes! */\r
499             String[] tempCountries = {\r
500                 "AD",  "AE",  "AF",  "AG",  "AI",  "AL",  "AM",  "AN",\r
501                 "AO",  "AQ",  "AR",  "AS",  "AT",  "AU",  "AW",  "AX",  "AZ",\r
502                 "BA",  "BB",  "BD",  "BE",  "BF",  "BG",  "BH",  "BI",\r
503                 "BJ",  "BL",  "BM",  "BN",  "BO",  "BR",  "BS",  "BT",  "BV",\r
504                 "BW",  "BY",  "BZ",  "CA",  "CC",  "CD",  "CF",  "CG",\r
505                 "CH",  "CI",  "CK",  "CL",  "CM",  "CN",  "CO",  "CR",\r
506                 "CU",  "CV",  "CX",  "CY",  "CZ",  "DE",  "DJ",  "DK",\r
507                 "DM",  "DO",  "DZ",  "EC",  "EE",  "EG",  "EH",  "ER",\r
508                 "ES",  "ET",  "FI",  "FJ",  "FK",  "FM",  "FO",  "FR",\r
509                 "GA",  "GB",  "GD",  "GE",  "GF",  "GG",  "GH",  "GI",  "GL",\r
510                 "GM",  "GN",  "GP",  "GQ",  "GR",  "GS",  "GT",  "GU",\r
511                 "GW",  "GY",  "HK",  "HM",  "HN",  "HR",  "HT",  "HU",\r
512                 "ID",  "IE",  "IL",  "IM",  "IN",  "IO",  "IQ",  "IR",  "IS",\r
513                 "IT",  "JE",  "JM",  "JO",  "JP",  "KE",  "KG",  "KH",  "KI",\r
514                 "KM",  "KN",  "KP",  "KR",  "KW",  "KY",  "KZ",  "LA",\r
515                 "LB",  "LC",  "LI",  "LK",  "LR",  "LS",  "LT",  "LU",\r
516                 "LV",  "LY",  "MA",  "MC",  "MD",  "ME",  "MF",  "MG",  "MH",  "MK",\r
517                 "ML",  "MM",  "MN",  "MO",  "MP",  "MQ",  "MR",  "MS",\r
518                 "MT",  "MU",  "MV",  "MW",  "MX",  "MY",  "MZ",  "NA",\r
519                 "NC",  "NE",  "NF",  "NG",  "NI",  "NL",  "NO",  "NP",\r
520                 "NR",  "NU",  "NZ",  "OM",  "PA",  "PE",  "PF",  "PG",\r
521                 "PH",  "PK",  "PL",  "PM",  "PN",  "PR",  "PS",  "PT",\r
522                 "PW",  "PY",  "QA",  "RE",  "RO",  "RS",  "RU",  "RW",  "SA",\r
523                 "SB",  "SC",  "SD",  "SE",  "SG",  "SH",  "SI",  "SJ",\r
524                 "SK",  "SL",  "SM",  "SN",  "SO",  "SR",  "ST",  "SV",\r
525                 "SY",  "SZ",  "TC",  "TD",  "TF",  "TG",  "TH",  "TJ",\r
526                 "TK",  "TL",  "TM",  "TN",  "TO",  "TR",  "TT",  "TV",\r
527                 "TW",  "TZ",  "UA",  "UG",  "UM",  "US",  "UY",  "UZ",\r
528                 "VA",  "VC",  "VE",  "VG",  "VI",  "VN",  "VU",  "WF",\r
529                 "WS",  "YE",  "YT",  "ZA",  "ZM",  "ZW",\r
530             };\r
531 \r
532             /* this table is used for 3 letter codes */\r
533             String[] tempObsoleteCountries = {\r
534                 "FX",  "CS",  "RO",  "TP",  "YU",  "ZR",  /* obsolete country codes */      \r
535             };\r
536             \r
537             String[] tempDeprecatedCountries = {\r
538                "BU", "CS", "DY", "FX", "HV", "NH", "RH", "TP", "YU", "ZR" /* deprecated country list */\r
539             };\r
540             String[] tempReplacementCountries = {\r
541            /*  "BU", "CS", "DY", "FX", "HV", "NH", "RH", "TP", "YU", "ZR" */\r
542                "MM", "RS", "BJ", "FR", "BF", "VU", "ZW", "TL", "RS", "CD",   /* replacement country codes */      \r
543             };\r
544     \r
545             /* This list MUST contain a three-letter code for every two-letter code in\r
546                the above list, and they MUST be listed in the same order! */\r
547             String[] tempCountries3 = {\r
548                 /*  "AD",  "AE",  "AF",  "AG",  "AI",  "AL",  "AM",  "AN",     */\r
549                     "AND", "ARE", "AFG", "ATG", "AIA", "ALB", "ARM", "ANT",\r
550                 /*  "AO",  "AQ",  "AR",  "AS",  "AT",  "AU",  "AW",  "AX",  "AZ",     */\r
551                     "AGO", "ATA", "ARG", "ASM", "AUT", "AUS", "ABW", "ALA", "AZE",\r
552                 /*  "BA",  "BB",  "BD",  "BE",  "BF",  "BG",  "BH",  "BI",     */\r
553                     "BIH", "BRB", "BGD", "BEL", "BFA", "BGR", "BHR", "BDI",\r
554                 /*  "BJ",  "BL",  "BM",  "BN",  "BO",  "BR",  "BS",  "BT",  "BV",     */\r
555                     "BEN", "BLM", "BMU", "BRN", "BOL", "BRA", "BHS", "BTN", "BVT",\r
556                 /*  "BW",  "BY",  "BZ",  "CA",  "CC",  "CD",  "CF",  "CG",     */\r
557                     "BWA", "BLR", "BLZ", "CAN", "CCK", "COD", "CAF", "COG",\r
558                 /*  "CH",  "CI",  "CK",  "CL",  "CM",  "CN",  "CO",  "CR",     */\r
559                     "CHE", "CIV", "COK", "CHL", "CMR", "CHN", "COL", "CRI",\r
560                 /*  "CU",  "CV",  "CX",  "CY",  "CZ",  "DE",  "DJ",  "DK",     */\r
561                     "CUB", "CPV", "CXR", "CYP", "CZE", "DEU", "DJI", "DNK",\r
562                 /*  "DM",  "DO",  "DZ",  "EC",  "EE",  "EG",  "EH",  "ER",     */\r
563                     "DMA", "DOM", "DZA", "ECU", "EST", "EGY", "ESH", "ERI",\r
564                 /*  "ES",  "ET",  "FI",  "FJ",  "FK",  "FM",  "FO",  "FR",     */\r
565                     "ESP", "ETH", "FIN", "FJI", "FLK", "FSM", "FRO", "FRA",\r
566                 /*  "GA",  "GB",  "GD",  "GE",  "GF",  "GG",  "GH",  "GI",  "GL",     */\r
567                     "GAB", "GBR", "GRD", "GEO", "GUF", "GGY", "GHA", "GIB", "GRL",\r
568                 /*  "GM",  "GN",  "GP",  "GQ",  "GR",  "GS",  "GT",  "GU",     */\r
569                     "GMB", "GIN", "GLP", "GNQ", "GRC", "SGS", "GTM", "GUM",\r
570                 /*  "GW",  "GY",  "HK",  "HM",  "HN",  "HR",  "HT",  "HU",     */\r
571                     "GNB", "GUY", "HKG", "HMD", "HND", "HRV", "HTI", "HUN",\r
572                 /*  "ID",  "IE",  "IL",  "IM",  "IN",  "IO",  "IQ",  "IR",  "IS" */\r
573                     "IDN", "IRL", "ISR", "IMN", "IND", "IOT", "IRQ", "IRN", "ISL",\r
574                 /*  "IT",  "JE",  "JM",  "JO",  "JP",  "KE",  "KG",  "KH",  "KI",     */\r
575                     "ITA", "JEY", "JAM", "JOR", "JPN", "KEN", "KGZ", "KHM", "KIR",\r
576                 /*  "KM",  "KN",  "KP",  "KR",  "KW",  "KY",  "KZ",  "LA",     */\r
577                     "COM", "KNA", "PRK", "KOR", "KWT", "CYM", "KAZ", "LAO",\r
578                 /*  "LB",  "LC",  "LI",  "LK",  "LR",  "LS",  "LT",  "LU",     */\r
579                     "LBN", "LCA", "LIE", "LKA", "LBR", "LSO", "LTU", "LUX",\r
580                 /*  "LV",  "LY",  "MA",  "MC",  "MD",  "ME",  "MF",  "MG",  "MH",  "MK",     */\r
581                     "LVA", "LBY", "MAR", "MCO", "MDA", "MNE", "MAF", "MDG", "MHL", "MKD",\r
582                 /*  "ML",  "MM",  "MN",  "MO",  "MP",  "MQ",  "MR",  "MS",     */\r
583                     "MLI", "MMR", "MNG", "MAC", "MNP", "MTQ", "MRT", "MSR",\r
584                 /*  "MT",  "MU",  "MV",  "MW",  "MX",  "MY",  "MZ",  "NA",     */\r
585                     "MLT", "MUS", "MDV", "MWI", "MEX", "MYS", "MOZ", "NAM",\r
586                 /*  "NC",  "NE",  "NF",  "NG",  "NI",  "NL",  "NO",  "NP",     */\r
587                     "NCL", "NER", "NFK", "NGA", "NIC", "NLD", "NOR", "NPL",\r
588                 /*  "NR",  "NU",  "NZ",  "OM",  "PA",  "PE",  "PF",  "PG",     */\r
589                     "NRU", "NIU", "NZL", "OMN", "PAN", "PER", "PYF", "PNG",\r
590                 /*  "PH",  "PK",  "PL",  "PM",  "PN",  "PR",  "PS",  "PT",     */\r
591                     "PHL", "PAK", "POL", "SPM", "PCN", "PRI", "PSE", "PRT",\r
592                 /*  "PW",  "PY",  "QA",  "RE",  "RO",  "RS",  "RU",  "RW",  "SA",     */\r
593                     "PLW", "PRY", "QAT", "REU", "ROU", "SRB", "RUS", "RWA", "SAU",\r
594                 /*  "SB",  "SC",  "SD",  "SE",  "SG",  "SH",  "SI",  "SJ",     */\r
595                     "SLB", "SYC", "SDN", "SWE", "SGP", "SHN", "SVN", "SJM",\r
596                 /*  "SK",  "SL",  "SM",  "SN",  "SO",  "SR",  "ST",  "SV",     */\r
597                     "SVK", "SLE", "SMR", "SEN", "SOM", "SUR", "STP", "SLV",\r
598                 /*  "SY",  "SZ",  "TC",  "TD",  "TF",  "TG",  "TH",  "TJ",     */\r
599                     "SYR", "SWZ", "TCA", "TCD", "ATF", "TGO", "THA", "TJK",\r
600                 /*  "TK",  "TL",  "TM",  "TN",  "TO",  "TR",  "TT",  "TV",     */\r
601                     "TKL", "TLS", "TKM", "TUN", "TON", "TUR", "TTO", "TUV",\r
602                 /*  "TW",  "TZ",  "UA",  "UG",  "UM",  "US",  "UY",  "UZ",     */\r
603                     "TWN", "TZA", "UKR", "UGA", "UMI", "USA", "URY", "UZB",\r
604                 /*  "VA",  "VC",  "VE",  "VG",  "VI",  "VN",  "VU",  "WF",     */\r
605                     "VAT", "VCT", "VEN", "VGB", "VIR", "VNM", "VUT", "WLF",\r
606                 /*  "WS",  "YE",  "YT",  "ZA",  "ZM",  "ZW"          */\r
607                     "WSM", "YEM", "MYT", "ZAF", "ZMB", "ZWE",\r
608             };\r
609     \r
610             String[] tempObsoleteCountries3 = {\r
611                 /*"FX",  "CS",  "RO",  "TP",  "YU",  "ZR",   */\r
612                 "FXX", "SCG", "ROM", "TMP", "YUG", "ZAR",    \r
613             };\r
614 \r
615             synchronized (ULocale.class) {\r
616                 if (_countries == null) {\r
617                     _countries = tempCountries;\r
618                     _deprecatedCountries = tempDeprecatedCountries;\r
619                     _replacementCountries = tempReplacementCountries;\r
620                     _obsoleteCountries = tempObsoleteCountries;\r
621                     _countries3 = tempCountries3;\r
622                     _obsoleteCountries3 = tempObsoleteCountries3;\r
623                 }\r
624             }\r
625         }\r
626     }\r
627 \r
628     private static String[][] CANONICALIZE_MAP;\r
629     private static String[][] variantsToKeywords;\r
630 \r
631     private static void initCANONICALIZE_MAP() {\r
632         if (CANONICALIZE_MAP == null) {\r
633             /**\r
634              * This table lists pairs of locale ids for canonicalization.  The\r
635              * The 1st item is the normalized id. The 2nd item is the\r
636              * canonicalized id. The 3rd is the keyword. The 4th is the keyword value.\r
637              */\r
638             String[][] tempCANONICALIZE_MAP = {\r
639 //              { EMPTY_STRING,     "en_US_POSIX", null, null }, /* .NET name */\r
640                 { "C",              "en_US_POSIX", null, null }, /* POSIX name */\r
641                 { "art_LOJBAN",     "jbo", null, null }, /* registered name */\r
642                 { "az_AZ_CYRL",     "az_Cyrl_AZ", null, null }, /* .NET name */\r
643                 { "az_AZ_LATN",     "az_Latn_AZ", null, null }, /* .NET name */\r
644                 { "ca_ES_PREEURO",  "ca_ES", "currency", "ESP" },\r
645                 { "cel_GAULISH",    "cel__GAULISH", null, null }, /* registered name */\r
646                 { "de_1901",        "de__1901", null, null }, /* registered name */\r
647                 { "de_1906",        "de__1906", null, null }, /* registered name */\r
648                 { "de__PHONEBOOK",  "de", "collation", "phonebook" }, /* Old ICU name */\r
649                 { "de_AT_PREEURO",  "de_AT", "currency", "ATS" },\r
650                 { "de_DE_PREEURO",  "de_DE", "currency", "DEM" },\r
651                 { "de_LU_PREEURO",  "de_LU", "currency", "EUR" },\r
652                 { "el_GR_PREEURO",  "el_GR", "currency", "GRD" },\r
653                 { "en_BOONT",       "en__BOONT", null, null }, /* registered name */\r
654                 { "en_SCOUSE",      "en__SCOUSE", null, null }, /* registered name */\r
655                 { "en_BE_PREEURO",  "en_BE", "currency", "BEF" },\r
656                 { "en_IE_PREEURO",  "en_IE", "currency", "IEP" },\r
657                 { "es__TRADITIONAL", "es", "collation", "traditional" }, /* Old ICU name */\r
658                 { "es_ES_PREEURO",  "es_ES", "currency", "ESP" },\r
659                 { "eu_ES_PREEURO",  "eu_ES", "currency", "ESP" },\r
660                 { "fi_FI_PREEURO",  "fi_FI", "currency", "FIM" },\r
661                 { "fr_BE_PREEURO",  "fr_BE", "currency", "BEF" },\r
662                 { "fr_FR_PREEURO",  "fr_FR", "currency", "FRF" },\r
663                 { "fr_LU_PREEURO",  "fr_LU", "currency", "LUF" },\r
664                 { "ga_IE_PREEURO",  "ga_IE", "currency", "IEP" },\r
665                 { "gl_ES_PREEURO",  "gl_ES", "currency", "ESP" },\r
666                 { "hi__DIRECT",     "hi", "collation", "direct" }, /* Old ICU name */\r
667                 { "it_IT_PREEURO",  "it_IT", "currency", "ITL" },\r
668                 { "ja_JP_TRADITIONAL", "ja_JP", "calendar", "japanese" },\r
669 //              { "nb_NO_NY",       "nn_NO", null, null },\r
670                 { "nl_BE_PREEURO",  "nl_BE", "currency", "BEF" },\r
671                 { "nl_NL_PREEURO",  "nl_NL", "currency", "NLG" },\r
672                 { "pt_PT_PREEURO",  "pt_PT", "currency", "PTE" },\r
673                 { "sl_ROZAJ",       "sl__ROZAJ", null, null }, /* registered name */\r
674                 { "sr_SP_CYRL",     "sr_Cyrl_RS", null, null }, /* .NET name */\r
675                 { "sr_SP_LATN",     "sr_Latn_RS", null, null }, /* .NET name */\r
676                 { "sr_YU_CYRILLIC", "sr_Cyrl_RS", null, null }, /* Linux name */\r
677                 { "th_TH_TRADITIONAL", "th_TH", "calendar", "buddhist" }, /* Old ICU name */\r
678                 { "uz_UZ_CYRILLIC", "uz_Cyrl_UZ", null, null }, /* Linux name */\r
679                 { "uz_UZ_CYRL",     "uz_Cyrl_UZ", null, null }, /* .NET name */\r
680                 { "uz_UZ_LATN",     "uz_Latn_UZ", null, null }, /* .NET name */\r
681                 { "zh_CHS",         "zh_Hans", null, null }, /* .NET name */\r
682                 { "zh_CHT",         "zh_Hant", null, null }, /* .NET name */\r
683                 { "zh_GAN",         "zh__GAN", null, null }, /* registered name */\r
684                 { "zh_GUOYU",       "zh", null, null }, /* registered name */\r
685                 { "zh_HAKKA",       "zh__HAKKA", null, null }, /* registered name */\r
686                 { "zh_MIN",         "zh__MIN", null, null }, /* registered name */\r
687                 { "zh_MIN_NAN",     "zh__MINNAN", null, null }, /* registered name */\r
688                 { "zh_WUU",         "zh__WUU", null, null }, /* registered name */\r
689                 { "zh_XIANG",       "zh__XIANG", null, null }, /* registered name */\r
690                 { "zh_YUE",         "zh__YUE", null, null } /* registered name */\r
691             };\r
692     \r
693             synchronized (ULocale.class) {\r
694                 if (CANONICALIZE_MAP == null) {\r
695                     CANONICALIZE_MAP = tempCANONICALIZE_MAP;\r
696                 }\r
697             }\r
698         }\r
699         if (variantsToKeywords == null) {\r
700             /**\r
701              * This table lists pairs of locale ids for canonicalization.  The\r
702              * The first item is the normalized variant id.\r
703              */\r
704             String[][] tempVariantsToKeywords = {\r
705                     { "EURO",   "currency", "EUR" },\r
706                     { "PINYIN", "collation", "pinyin" }, /* Solaris variant */\r
707                     { "STROKE", "collation", "stroke" }  /* Solaris variant */\r
708             };\r
709     \r
710             synchronized (ULocale.class) {\r
711                 if (variantsToKeywords == null) {\r
712                     variantsToKeywords = tempVariantsToKeywords;\r
713                 }\r
714             }\r
715         }\r
716     }\r
717 \r
718     /*\r
719      * This table is used for mapping between ICU and special Java\r
720      * locales.  When an ICU locale matches <minumum base> with\r
721      * <keyword>/<value>, the ICU locale is mapped to <Java> locale.\r
722      * For example, both ja_JP@calendar=japanese and ja@calendar=japanese\r
723      * are mapped to Java locale "ja_JP_JP".  ICU locale "nn" is mapped\r
724      * to Java locale "no_NO_NY".\r
725      */\r
726     private static final String[][] _javaLocaleMap = {\r
727     //  { <Java>,       <ICU base>, <keyword>,  <value>,    <minimum base>\r
728         { "ja_JP_JP",   "ja_JP",    "calendar", "japanese", "ja"},\r
729         { "no_NO_NY",   "nn_NO",    null,       null,       "nn"},\r
730     //  { "th_TH_TH",   "th_TH",    ??,         ??,         "th"} //TODO\r
731     };\r
732 \r
733     /**\r
734      * Private constructor used by static initializers.\r
735      */\r
736     private ULocale(String localeID, Locale locale) {\r
737         this.localeID = localeID;\r
738         this.locale = locale;\r
739     }\r
740 \r
741     /**\r
742      * Construct a ULocale object from a {@link java.util.Locale}.\r
743      * @param loc a JDK locale\r
744      * @stable ICU 2.8\r
745      * @internal\r
746      */\r
747     private ULocale(Locale loc) {\r
748         this.localeID = getName(forLocale(loc).toString());\r
749         this.locale = loc;\r
750     }\r
751 \r
752     /**\r
753      * Return a ULocale object for a {@link java.util.Locale}.\r
754      * The ULocale is canonicalized.\r
755      * @param loc a JDK locale\r
756      * @stable ICU 3.2\r
757      */\r
758     public static ULocale forLocale(Locale loc) {\r
759         if (loc == null) {\r
760             return null;\r
761         }\r
762         ULocale result = (ULocale)CACHE.get(loc);\r
763         if (result == null) {\r
764             if (defaultULocale != null && loc == defaultULocale.locale) {\r
765             result = defaultULocale;\r
766         } else {\r
767                 String locStr = loc.toString();\r
768                 if (locStr.length() == 0) {\r
769                     result = ROOT;\r
770                 } else {\r
771                     for (int i = 0; i < _javaLocaleMap.length; i++) {\r
772                         if (_javaLocaleMap[i][0].equals(locStr)) {\r
773                             IDParser p = new IDParser(_javaLocaleMap[i][1]);\r
774                             p.setKeywordValue(_javaLocaleMap[i][2], _javaLocaleMap[i][3]);\r
775                             locStr = p.getName();\r
776                             break;\r
777                         }\r
778                     }\r
779                     result = new ULocale(locStr, loc);\r
780                 }\r
781             }\r
782             CACHE.put(loc, result);\r
783         }\r
784         return result;\r
785     }\r
786 \r
787     /**\r
788      * Construct a ULocale from a RFC 3066 locale ID. The locale ID consists\r
789      * of optional language, script, country, and variant fields in that order, \r
790      * separated by underscores, followed by an optional keyword list.  The\r
791      * script, if present, is four characters long-- this distinguishes it\r
792      * from a country code, which is two characters long.  Other fields\r
793      * are distinguished by position as indicated by the underscores.  The\r
794      * start of the keyword list is indicated by '@', and consists of two\r
795      * or more keyword/value pairs separated by semicolons(';').\r
796      * <p>\r
797      * This constructor does not canonicalize the localeID.  So, for\r
798      * example, "zh__pinyin" remains unchanged instead of converting\r
799      * to "zh@collation=pinyin".  By default ICU only recognizes the\r
800      * latter as specifying pinyin collation.  Use {@link #createCanonical}\r
801      * or {@link #canonicalize} if you need to canonicalize the localeID.\r
802      * \r
803      * @param localeID string representation of the locale, e.g:\r
804      * "en_US", "sy_Cyrl_YU", "zh__pinyin", "es_ES@currency=EUR;collation=traditional"\r
805      * @stable ICU 2.8\r
806      */ \r
807     public ULocale(String localeID) {\r
808         this.localeID = getName(localeID);\r
809     }\r
810 \r
811     /**\r
812      * Convenience overload of ULocale(String, String, String) for \r
813      * compatibility with java.util.Locale.\r
814      * @see #ULocale(String, String, String)\r
815      * @stable ICU 3.4\r
816      */\r
817     public ULocale(String a, String b) {\r
818         this(a, b, null);\r
819     }\r
820 \r
821     /**\r
822      * Construct a ULocale from a localeID constructed from the three 'fields' a, b, and c.  These\r
823      * fields are concatenated using underscores to form a localeID of\r
824      * the form a_b_c, which is then handled like the localeID passed\r
825      * to <code>ULocale(String localeID)</code>.  \r
826      *\r
827      * <p>Java locale strings consisting of language, country, and\r
828      * variant will be handled by this form, since the country code\r
829      * (being shorter than four letters long) will not be interpreted\r
830      * as a script code.  If a script code is present, the final\r
831      * argument ('c') will be interpreted as the country code.  It is\r
832      * recommended that this constructor only be used to ease porting,\r
833      * and that clients instead use the single-argument constructor\r
834      * when constructing a ULocale from a localeID.\r
835      * @param a first component of the locale id\r
836      * @param b second component of the locale id\r
837      * @param c third component of the locale id\r
838      * @see #ULocale(String)\r
839      * @stable ICU 3.0 \r
840      */\r
841     public ULocale(String a, String b, String c) {\r
842         localeID = getName(lscvToID(a, b, c, EMPTY_STRING));\r
843     }\r
844 \r
845     /**\r
846      * Create a ULocale from the id by first canonicalizing the id.\r
847      * @param nonCanonicalID the locale id to canonicalize\r
848      * @return the locale created from the canonical version of the ID.\r
849      * @stable ICU 3.0\r
850      */\r
851     public static ULocale createCanonical(String nonCanonicalID) {\r
852         return new ULocale(canonicalize(nonCanonicalID), (Locale)null);\r
853     }\r
854 \r
855     private static String lscvToID(String lang, String script, String country, String variant) {\r
856         StringBuffer buf = new StringBuffer();\r
857      \r
858         if (lang != null && lang.length() > 0) {\r
859             buf.append(lang);\r
860         }\r
861         if (script != null && script.length() > 0) {\r
862             buf.append(UNDERSCORE);\r
863             buf.append(script);\r
864         }\r
865         if (country != null && country.length() > 0) {\r
866             buf.append(UNDERSCORE);\r
867             buf.append(country);\r
868         }\r
869         if (variant != null && variant.length() > 0) {\r
870             if (country == null || country.length() == 0) {\r
871                 buf.append(UNDERSCORE);\r
872             }\r
873             buf.append(UNDERSCORE);\r
874             buf.append(variant);\r
875         }\r
876         return buf.toString();\r
877     }\r
878 \r
879     /**\r
880      * Convert this ULocale object to a {@link java.util.Locale}.\r
881      * @return a JDK locale that either exactly represents this object\r
882      * or is the closest approximation.\r
883      * @stable ICU 2.8\r
884      */\r
885     public Locale toLocale() {\r
886         if (locale == null) {\r
887             IDParser p = new IDParser(localeID);\r
888             String base = p.getBaseName();\r
889             for (int i = 0; i < _javaLocaleMap.length; i++) {\r
890                 if (base.equals(_javaLocaleMap[i][1]) || base.equals(_javaLocaleMap[i][4])) {\r
891                     if (_javaLocaleMap[i][2] != null) {\r
892                         String val = p.getKeywordValue(_javaLocaleMap[i][2]);\r
893                         if (val != null && val.equals(_javaLocaleMap[i][3])) {\r
894                             p = new IDParser(_javaLocaleMap[i][0]);\r
895                             break;\r
896                         }\r
897                     } else {\r
898                         p = new IDParser(_javaLocaleMap[i][0]);\r
899                         break;\r
900                     }\r
901                 }\r
902             }\r
903             String[] names = p.getLanguageScriptCountryVariant();\r
904             locale = new Locale(names[0], names[2], names[3]);\r
905         }\r
906         return locale;\r
907     }\r
908 \r
909     private static Locale toLocale(String localeID) {\r
910         IDParser p = new IDParser(localeID);\r
911         String base = p.getBaseName();\r
912         for (int i = 0; i < _javaLocaleMap.length; i++) {\r
913             if (base.equals(_javaLocaleMap[i][1]) || base.equals(_javaLocaleMap[i][4])) {\r
914                 if (_javaLocaleMap[i][2] != null) {\r
915                     String val = p.getKeywordValue(_javaLocaleMap[i][2]);\r
916                     if (val != null && val.equals(_javaLocaleMap[i][3])) {\r
917                         p = new IDParser(_javaLocaleMap[i][0]);\r
918                         break;\r
919                     }\r
920                 } else {\r
921                     p = new IDParser(_javaLocaleMap[i][0]);\r
922                     break;\r
923                 }\r
924             }\r
925         }\r
926         String[] names = p.getLanguageScriptCountryVariant();\r
927         return new Locale(names[0], names[2], names[3]);\r
928     }\r
929 \r
930 \r
931     private static SoftReference nameCacheRef = new SoftReference(Collections.synchronizedMap(new HashMap()));\r
932     /**\r
933      * Keep our own default ULocale.\r
934      */\r
935     private static Locale defaultLocale = Locale.getDefault();\r
936     private static ULocale defaultULocale = new ULocale(defaultLocale);\r
937 \r
938     /**\r
939      * Returns the current default ULocale.\r
940      * @stable ICU 2.8\r
941      */ \r
942     public static ULocale getDefault() {\r
943         synchronized (ULocale.class) {\r
944             Locale currentDefault = Locale.getDefault();\r
945             if (defaultLocale != currentDefault) {\r
946                 defaultLocale = currentDefault;\r
947                 defaultULocale = new ULocale(defaultLocale);\r
948             }\r
949             return defaultULocale;\r
950         }\r
951     }\r
952 \r
953     /**\r
954      * Sets the default ULocale.  This also sets the default Locale.\r
955      * If the caller does not have write permission to the\r
956      * user.language property, a security exception will be thrown,\r
957      * and the default ULocale will remain unchanged.\r
958      * @param newLocale the new default locale\r
959      * @throws SecurityException\r
960      *        if a security manager exists and its\r
961      *        <code>checkPermission</code> method doesn't allow the operation.\r
962      * @throws NullPointerException if <code>newLocale</code> is null\r
963      * @see SecurityManager#checkPermission(java.security.Permission)\r
964      * @see java.util.PropertyPermission\r
965      * @stable ICU 3.0 \r
966      */\r
967     public static synchronized void setDefault(ULocale newLocale){\r
968         Locale.setDefault(newLocale.toLocale());\r
969         defaultULocale = newLocale;\r
970     }\r
971     \r
972     /**\r
973      * This is for compatibility with Locale-- in actuality, since ULocale is\r
974      * immutable, there is no reason to clone it, so this API returns 'this'.\r
975      * @stable ICU 3.0\r
976      */\r
977     public Object clone() {\r
978         return this;\r
979     }\r
980 \r
981     /**\r
982      * Returns the hashCode.\r
983      * @stable ICU 3.0\r
984      */\r
985     public int hashCode() {\r
986         return localeID.hashCode();\r
987     }\r
988     \r
989     /**\r
990      * Returns true if the other object is another ULocale with the\r
991      * same full name, or is a String localeID that matches the full name.\r
992      * Note that since names are not canonicalized, two ULocales that\r
993      * function identically might not compare equal.\r
994      *\r
995      * @return true if this Locale is equal to the specified object.\r
996      * @stable ICU 3.0 \r
997      */\r
998     public boolean equals(Object obj) {\r
999         if (this == obj) {\r
1000             return true;\r
1001         }\r
1002         if (obj instanceof String) {\r
1003             return localeID.equals((String)obj);   \r
1004         }\r
1005         if (obj instanceof ULocale) {\r
1006             return localeID.equals(((ULocale)obj).localeID);\r
1007         }\r
1008         return false;\r
1009     }\r
1010 \r
1011     private static ULocale[] ulocales;\r
1012 \r
1013     /**\r
1014      * Returns a list of all installed locales.\r
1015      * @stable ICU 3.0\r
1016      */\r
1017     public static ULocale[] getAvailableLocales() {\r
1018 //        return ICUResourceBundle.getAvailableULocales();\r
1019         if (ulocales == null) {\r
1020             Locale[] locales = Locale.getAvailableLocales();\r
1021             ULocale[] ul = new ULocale[locales.length];\r
1022             for (int i = 0; i < locales.length; ++i) {\r
1023                     ul[i] = ULocale.forLocale(locales[i]);\r
1024             }\r
1025             ulocales = ul;\r
1026         }\r
1027         return (ULocale[])ulocales.clone();\r
1028     }\r
1029 \r
1030     /**\r
1031      * Returns a list of all 2-letter country codes defined in ISO 3166.\r
1032      * Can be used to create Locales.\r
1033      * @stable ICU 3.0\r
1034      */\r
1035     public static String[] getISOCountries() {\r
1036         initCountryTables();\r
1037         return (String[])_countries.clone();\r
1038     }\r
1039 \r
1040     /**\r
1041      * Returns a list of all 2-letter language codes defined in ISO 639.\r
1042      * Can be used to create Locales.\r
1043      * [NOTE:  ISO 639 is not a stable standard-- some languages' codes have changed.\r
1044      * The list this function returns includes both the new and the old codes for the\r
1045      * languages whose codes have changed.]\r
1046      * @stable ICU 3.0\r
1047      */\r
1048     public static String[] getISOLanguages() {\r
1049         initLanguageTables();\r
1050         return (String[])_languages.clone();\r
1051     }\r
1052 \r
1053     /**\r
1054      * Returns the language code for this locale, which will either be the empty string\r
1055      * or a lowercase ISO 639 code.\r
1056      * @see #getDisplayLanguage()\r
1057      * @see #getDisplayLanguage(ULocale)\r
1058      * @stable ICU 3.0\r
1059      */\r
1060     public String getLanguage() {\r
1061         return getLanguage(localeID);\r
1062     }\r
1063     \r
1064     /**\r
1065      * Returns the language code for the locale ID,\r
1066      * which will either be the empty string\r
1067      * or a lowercase ISO 639 code.\r
1068      * @see #getDisplayLanguage()\r
1069      * @see #getDisplayLanguage(ULocale)\r
1070      * @stable ICU 3.0\r
1071      */\r
1072     public static String getLanguage(String localeID) {\r
1073         return new IDParser(localeID).getLanguage();\r
1074     }\r
1075      \r
1076     /**\r
1077      * Returns the script code for this locale, which might be the empty string.\r
1078      * @see #getDisplayScript()\r
1079      * @see #getDisplayScript(ULocale)\r
1080      * @stable ICU 3.0\r
1081      */\r
1082     public String getScript() {\r
1083         return getScript(localeID);\r
1084     }\r
1085 \r
1086     /**\r
1087      * Returns the script code for the specified locale, which might be the empty string.\r
1088      * @see #getDisplayScript()\r
1089      * @see #getDisplayScript(ULocale)\r
1090      * @stable ICU 3.0\r
1091      */\r
1092     public static String getScript(String localeID) {\r
1093         return new IDParser(localeID).getScript();\r
1094     }\r
1095     \r
1096     /**\r
1097      * Returns the country/region code for this locale, which will either be the empty string\r
1098      * or an uppercase ISO 3166 2-letter code.\r
1099      * @see #getDisplayCountry()\r
1100      * @see #getDisplayCountry(ULocale)\r
1101      * @stable ICU 3.0\r
1102      */\r
1103     public String getCountry() {\r
1104         return getCountry(localeID);\r
1105     }\r
1106 \r
1107     /**\r
1108      * Returns the country/region code for this locale, which will either be the empty string\r
1109      * or an uppercase ISO 3166 2-letter code.\r
1110      * @param localeID\r
1111      * @see #getDisplayCountry()\r
1112      * @see #getDisplayCountry(ULocale)\r
1113      * @stable ICU 3.0\r
1114      */\r
1115     public static String getCountry(String localeID) {\r
1116         return new IDParser(localeID).getCountry();\r
1117     }\r
1118     \r
1119     /**\r
1120      * Returns the variant code for this locale, which might be the empty string.\r
1121      * @see #getDisplayVariant()\r
1122      * @see #getDisplayVariant(ULocale)\r
1123      * @stable ICU 3.0\r
1124      */\r
1125     public String getVariant() {\r
1126         return getVariant(localeID);\r
1127     }\r
1128 \r
1129     /**\r
1130      * Returns the variant code for the specified locale, which might be the empty string.\r
1131      * @see #getDisplayVariant()\r
1132      * @see #getDisplayVariant(ULocale)\r
1133      * @stable ICU 3.0\r
1134      */\r
1135     public static String getVariant(String localeID) {\r
1136         return new IDParser(localeID).getVariant();\r
1137     }\r
1138 \r
1139     /**\r
1140      * Returns the fallback locale for the specified locale, which might be the empty string.\r
1141      * @stable ICU 3.2\r
1142      */\r
1143     public static String getFallback(String localeID) {\r
1144         return getFallbackString(getName(localeID));\r
1145     }\r
1146 \r
1147     /**\r
1148      * Returns the fallback locale for this locale.  If this locale is root, returns null.\r
1149      * @stable ICU 3.2\r
1150      */\r
1151     public ULocale getFallback() {\r
1152         if (localeID.length() == 0 || localeID.charAt(0) == '@') {\r
1153             return null;\r
1154         }\r
1155         return new ULocale(getFallbackString(localeID), (Locale)null);\r
1156     }\r
1157 \r
1158     /**\r
1159      * Return the given (canonical) locale id minus the last part before the tags.\r
1160      */\r
1161     private static String getFallbackString(String fallback) {\r
1162         int limit = fallback.indexOf('@');\r
1163         if (limit == -1) {\r
1164             limit = fallback.length();\r
1165         }\r
1166         int start = fallback.lastIndexOf('_', limit);\r
1167         if (start == -1) {\r
1168             start = 0;\r
1169         }\r
1170         return fallback.substring(0, start) + fallback.substring(limit);\r
1171     }\r
1172 \r
1173     /**\r
1174      * Returns the (normalized) base name for this locale.\r
1175      * @return the base name as a String.\r
1176      * @stable ICU 3.0\r
1177      */\r
1178     public String getBaseName() {\r
1179         return getBaseName(localeID);\r
1180     }\r
1181     \r
1182     /**\r
1183      * Returns the (normalized) base name for the specified locale.\r
1184      * @param localeID the locale ID as a string\r
1185      * @return the base name as a String.\r
1186      * @stable ICU 3.0\r
1187      */\r
1188     public static String getBaseName(String localeID){\r
1189         if (localeID.indexOf('@') == -1) {\r
1190             return localeID;\r
1191         }\r
1192         return new IDParser(localeID).getBaseName();\r
1193     }\r
1194 \r
1195     /**\r
1196      * Returns the (normalized) full name for this locale.\r
1197      *\r
1198      * @return String the full name of the localeID\r
1199      * @stable ICU 3.0\r
1200      */ \r
1201     public String getName() {\r
1202         return localeID; // always normalized\r
1203     }\r
1204 \r
1205     /**\r
1206      * Returns the (normalized) full name for the specified locale.\r
1207      *\r
1208      * @param localeID the localeID as a string\r
1209      * @return String the full name of the localeID\r
1210      * @stable ICU 3.0\r
1211      */\r
1212     public static String getName(String localeID){\r
1213         Map cache = (Map)nameCacheRef.get();\r
1214         if (cache == null) {\r
1215             cache = Collections.synchronizedMap(new HashMap());\r
1216             nameCacheRef = new SoftReference(cache);\r
1217         }\r
1218         String name = (String)cache.get(localeID);\r
1219         if (name == null) {\r
1220             name = new IDParser(localeID).getName();\r
1221             cache.put(localeID, name);\r
1222         }\r
1223         return name;\r
1224     }\r
1225 \r
1226     /**\r
1227      * Returns a string representation of this object.\r
1228      * @stable ICU 3.0\r
1229      */\r
1230     public String toString() {\r
1231         return localeID;\r
1232     }\r
1233 \r
1234     /**\r
1235      * Returns an iterator over keywords for this locale.  If there \r
1236      * are no keywords, returns null.\r
1237      * @return iterator over keywords, or null if there are no keywords.\r
1238      * @stable ICU 3.0\r
1239      */\r
1240     public Iterator getKeywords() {\r
1241         return getKeywords(localeID);\r
1242     }\r
1243 \r
1244     /**\r
1245      * Returns an iterator over keywords for the specified locale.  If there \r
1246      * are no keywords, returns null.\r
1247      * @return an iterator over the keywords in the specified locale, or null\r
1248      * if there are no keywords.\r
1249      * @stable ICU 3.0\r
1250      */\r
1251     public static Iterator getKeywords(String localeID){\r
1252         return new IDParser(localeID).getKeywords();\r
1253     }\r
1254 \r
1255     /**\r
1256      * Returns the value for a keyword in this locale. If the keyword is not defined, returns null.\r
1257      * @param keywordName name of the keyword whose value is desired. Case insensitive.\r
1258      * @return the value of the keyword, or null.\r
1259      * @stable ICU 3.0\r
1260      */\r
1261     public String getKeywordValue(String keywordName){\r
1262         return getKeywordValue(localeID, keywordName);\r
1263     }\r
1264     \r
1265     /**\r
1266      * Returns the value for a keyword in the specified locale. If the keyword is not defined, returns null. \r
1267      * The locale name does not need to be normalized.\r
1268      * @param keywordName name of the keyword whose value is desired. Case insensitive.\r
1269      * @return String the value of the keyword as a string\r
1270      * @stable ICU 3.0\r
1271      */\r
1272     public static String getKeywordValue(String localeID, String keywordName) {\r
1273         return new IDParser(localeID).getKeywordValue(keywordName);\r
1274     }\r
1275 \r
1276     /**\r
1277      * Utility class to parse and normalize locale ids (including POSIX style)\r
1278      */\r
1279     private static final class IDParser {\r
1280         private char[] id;\r
1281         private int index;\r
1282         private char[] buffer;\r
1283         private int blen;\r
1284         // um, don't handle POSIX ids unless we request it.  why not?  well... because.\r
1285         private boolean canonicalize;\r
1286         private boolean hadCountry;\r
1287 \r
1288         // used when canonicalizing\r
1289         Map keywords;\r
1290         String baseName;\r
1291 \r
1292         /**\r
1293          * Parsing constants.\r
1294          */\r
1295         private static final char KEYWORD_SEPARATOR     = '@';\r
1296         private static final char HYPHEN                = '-';\r
1297         private static final char KEYWORD_ASSIGN        = '=';\r
1298         private static final char COMMA                 = ',';\r
1299         private static final char ITEM_SEPARATOR        = ';';\r
1300         private static final char DOT                   = '.';\r
1301 \r
1302         private IDParser(String localeID) {\r
1303             this(localeID, false);\r
1304         }\r
1305 \r
1306         private IDParser(String localeID, boolean canonicalize) {\r
1307             id = localeID.toCharArray();\r
1308             index = 0;\r
1309             buffer = new char[id.length + 5];\r
1310             blen = 0;\r
1311             this.canonicalize = canonicalize;\r
1312         }\r
1313 \r
1314         private void reset() {\r
1315             index = blen = 0;\r
1316         }\r
1317 \r
1318         // utilities for working on text in the buffer\r
1319 \r
1320         /**\r
1321          * Append c to the buffer.\r
1322          */\r
1323         private void append(char c) {\r
1324             try {\r
1325                 buffer[blen] = c;\r
1326             }\r
1327             catch (IndexOutOfBoundsException e) {\r
1328                 if (buffer.length > 512) {\r
1329                     // something is seriously wrong, let this go\r
1330                     throw e;\r
1331                 }\r
1332                 char[] nbuffer = new char[buffer.length * 2];\r
1333                 System.arraycopy(buffer, 0, nbuffer, 0, buffer.length);\r
1334                 nbuffer[blen] = c;\r
1335                 buffer = nbuffer;\r
1336             }\r
1337             ++blen;\r
1338         }\r
1339 \r
1340         private void addSeparator() {\r
1341             append(UNDERSCORE);\r
1342         }\r
1343 \r
1344         /**\r
1345          * Returns the text in the buffer from start to blen as a String.\r
1346          */\r
1347         private String getString(int start) {\r
1348             if (start == blen) {\r
1349                 return EMPTY_STRING;\r
1350             }\r
1351             return new String(buffer, start, blen-start);\r
1352         }\r
1353 \r
1354         /**\r
1355          * Set the length of the buffer to pos, then append the string.\r
1356          */\r
1357         private void set(int pos, String s) {\r
1358             this.blen = pos; // no safety\r
1359             append(s);\r
1360         }\r
1361 \r
1362         /**\r
1363          * Append the string to the buffer.\r
1364          */\r
1365         private void append(String s) {\r
1366             for (int i = 0; i < s.length(); ++i) {\r
1367                 append(s.charAt(i));\r
1368             }\r
1369         }\r
1370 \r
1371         // utilities for parsing text out of the id\r
1372 \r
1373         /**\r
1374          * Character to indicate no more text is available in the id.\r
1375          */\r
1376         private static final char DONE = '\uffff';\r
1377 \r
1378         /**\r
1379          * Returns the character at index in the id, and advance index.  The returned character\r
1380          * is DONE if index was at the limit of the buffer.  The index is advanced regardless\r
1381          * so that decrementing the index will always 'unget' the last character returned.\r
1382          */\r
1383         private char next() {\r
1384             if (index == id.length) {\r
1385                 index++;\r
1386                 return DONE; \r
1387             }\r
1388 \r
1389             return id[index++];\r
1390         }\r
1391 \r
1392         /**\r
1393          * Advance index until the next terminator or id separator, and leave it there.\r
1394          */\r
1395         private void skipUntilTerminatorOrIDSeparator() {\r
1396             while (!isTerminatorOrIDSeparator(next())) {\r
1397             }\r
1398             --index;\r
1399         }\r
1400 \r
1401         /**\r
1402          * Returns true if the character at index in the id is a terminator.\r
1403          */\r
1404         private boolean atTerminator() {\r
1405             return index >= id.length || isTerminator(id[index]);\r
1406         }\r
1407 \r
1408         /*\r
1409          * Returns true if the character is an id separator (underscore or hyphen).\r
1410          */\r
1411 /*        private boolean isIDSeparator(char c) {\r
1412             return c == UNDERSCORE || c == HYPHEN;\r
1413         }*/\r
1414 \r
1415         /**\r
1416          * Returns true if the character is a terminator (keyword separator, dot, or DONE).\r
1417          * Dot is a terminator because of the POSIX form, where dot precedes the codepage.\r
1418          */\r
1419         private boolean isTerminator(char c) {\r
1420             // always terminate at DOT, even if not handling POSIX.  It's an error...\r
1421             return c == KEYWORD_SEPARATOR || c == DONE || c == DOT;\r
1422         }\r
1423 \r
1424         /**\r
1425          * Returns true if the character is a terminator or id separator.\r
1426          */\r
1427         private boolean isTerminatorOrIDSeparator(char c) {\r
1428             return c == KEYWORD_SEPARATOR || c == UNDERSCORE || c == HYPHEN || \r
1429                 c == DONE || c == DOT;   \r
1430         }\r
1431 \r
1432         /**\r
1433          * Returns true if the start of the buffer has an experimental or private language \r
1434          * prefix, the pattern '[ixIX][-_].' shows the syntax checked.\r
1435          */\r
1436         private boolean haveExperimentalLanguagePrefix() {\r
1437             if (id.length > 2) {\r
1438                 char c = id[1];\r
1439                 if (c == HYPHEN || c == UNDERSCORE) {\r
1440                     c = id[0];\r
1441                     return c == 'x' || c == 'X' || c == 'i' || c == 'I';\r
1442                 }\r
1443             }\r
1444             return false;\r
1445         }\r
1446 \r
1447         /**\r
1448          * Returns true if a value separator occurs at or after index.\r
1449          */\r
1450         private boolean haveKeywordAssign() {\r
1451             // assume it is safe to start from index\r
1452             for (int i = index; i < id.length; ++i) {\r
1453                 if (id[i] == KEYWORD_ASSIGN) {\r
1454                     return true;\r
1455                 }\r
1456             }\r
1457             return false;\r
1458         }\r
1459 \r
1460         /**\r
1461          * Advance index past language, and accumulate normalized language code in buffer.\r
1462          * Index must be at 0 when this is called.  Index is left at a terminator or id \r
1463          * separator.  Returns the start of the language code in the buffer.\r
1464          */\r
1465         private int parseLanguage() {\r
1466             if (haveExperimentalLanguagePrefix()) {\r
1467                 append(Character.toLowerCase(id[0]));\r
1468                 append(HYPHEN);\r
1469                 index = 2;\r
1470             }\r
1471         \r
1472             char c;\r
1473             while(!isTerminatorOrIDSeparator(c = next())) {\r
1474                 append(Character.toLowerCase(c));\r
1475             }\r
1476             --index; // unget\r
1477 \r
1478             if (blen == 3) {\r
1479                 initLanguageTables();\r
1480 \r
1481                 /* convert 3 character code to 2 character code if possible *CWB*/\r
1482                 String lang = getString(0);\r
1483                 int offset = findIndex(_languages3, lang);\r
1484                 if (offset >= 0) {\r
1485                     set(0, _languages[offset]);\r
1486                 } else {\r
1487                     offset = findIndex(_obsoleteLanguages3, lang);\r
1488                     if (offset >= 0) {\r
1489                         set(0, _obsoleteLanguages[offset]);\r
1490                     }\r
1491                 }\r
1492             }\r
1493 \r
1494             return 0;\r
1495         }\r
1496 \r
1497         /**\r
1498          * Advance index past language.  Index must be at 0 when this is called.  Index\r
1499          * is left at a terminator or id separator.\r
1500          */\r
1501         private void skipLanguage() {\r
1502             if (haveExperimentalLanguagePrefix()) {\r
1503                 index = 2;\r
1504             }\r
1505             skipUntilTerminatorOrIDSeparator();\r
1506         }\r
1507 \r
1508         /**\r
1509          * Advance index past script, and accumulate normalized script in buffer.\r
1510          * Index must be immediately after the language.\r
1511          * If the item at this position is not a script (is not four characters\r
1512          * long) leave index and buffer unchanged.  Otherwise index is left at\r
1513          * a terminator or id separator.  Returns the start of the script code\r
1514          * in the buffer (this may be equal to the buffer length, if there is no\r
1515          * script).\r
1516          */\r
1517         private int parseScript() {\r
1518             if (!atTerminator()) {\r
1519                 int oldIndex = index; // save original index\r
1520                 ++index;\r
1521 \r
1522                 int oldBlen = blen; // get before append hyphen, if we truncate everything is undone\r
1523                 char c;\r
1524                 while(!isTerminatorOrIDSeparator(c = next())) {\r
1525                     if (blen == oldBlen) { // first pass\r
1526                         addSeparator();\r
1527                         append(Character.toUpperCase(c));\r
1528                     } else {\r
1529                         append(Character.toLowerCase(c));\r
1530                     }\r
1531                 }\r
1532                 --index; // unget\r
1533 \r
1534                 /* If it's not exactly 4 characters long, then it's not a script. */\r
1535                 if (index - oldIndex != 5) { // +1 to account for separator\r
1536                     index = oldIndex;\r
1537                     blen = oldBlen;\r
1538                 } else {\r
1539                     oldBlen++; // index past hyphen, for clients who want to extract just the script\r
1540                 }\r
1541 \r
1542                 return oldBlen;\r
1543             }\r
1544             return blen;\r
1545         }\r
1546 \r
1547         /**\r
1548          * Advance index past script.\r
1549          * Index must be immediately after the language and IDSeparator.\r
1550          * If the item at this position is not a script (is not four characters\r
1551          * long) leave index.  Otherwise index is left at a terminator or\r
1552          * id separator.\r
1553          */\r
1554         private void skipScript() {\r
1555             if (!atTerminator()) {\r
1556                 int oldIndex = index;\r
1557                 ++index;\r
1558 \r
1559                 skipUntilTerminatorOrIDSeparator();\r
1560                 if (index - oldIndex != 5) { // +1 to account for separator\r
1561                     index = oldIndex;\r
1562                 }\r
1563             }\r
1564         }\r
1565 \r
1566         /**\r
1567          * Advance index past country, and accumulate normalized country in buffer.\r
1568          * Index must be immediately after the script (if there is one, else language)\r
1569          * and IDSeparator.  Return the start of the country code in the buffer.\r
1570          */\r
1571         private int parseCountry() {\r
1572             if (!atTerminator()) {\r
1573                 int oldIndex = index;\r
1574                 ++index;\r
1575 \r
1576                 int oldBlen = blen;\r
1577                 char c;\r
1578                 while (!isTerminatorOrIDSeparator(c = next())) {\r
1579                     if (oldBlen == blen) { // first, add hyphen\r
1580                         hadCountry = true; // we have a country, let variant parsing know\r
1581                         addSeparator();\r
1582                         ++oldBlen; // increment past hyphen\r
1583                     }\r
1584                     append(Character.toUpperCase(c));\r
1585                 }\r
1586                 --index; // unget\r
1587 \r
1588                 int charsAppended = blen - oldBlen;\r
1589 \r
1590                 if (charsAppended == 0) {\r
1591                     // Do nothing.\r
1592                 }\r
1593                 else if (charsAppended < 2 || charsAppended > 3) {\r
1594                     // It's not a country, so return index and blen to\r
1595                     // their previous values.\r
1596                     index = oldIndex;\r
1597                     --oldBlen;\r
1598                     blen = oldBlen;\r
1599                     hadCountry = false;\r
1600                 }\r
1601                 else if (charsAppended == 3) {\r
1602                     initCountryTables();\r
1603 \r
1604                     /* convert 3 character code to 2 character code if possible *CWB*/\r
1605                     int offset = findIndex(_countries3, getString(oldBlen));\r
1606                     if (offset >= 0) {\r
1607                         set(oldBlen, _countries[offset]);\r
1608                     } else {\r
1609                         offset = findIndex(_obsoleteCountries3, getString(oldBlen));\r
1610                         if (offset >= 0) {\r
1611                             set(oldBlen, _obsoleteCountries[offset]);\r
1612                         }\r
1613                     }\r
1614                 }\r
1615 \r
1616                 return oldBlen;\r
1617             }\r
1618 \r
1619             return blen;\r
1620         }  \r
1621 \r
1622         /**\r
1623          * Advance index past country.\r
1624          * Index must be immediately after the script (if there is one, else language)\r
1625          * and IDSeparator.\r
1626          */\r
1627         private void skipCountry() {\r
1628             if (!atTerminator()) {\r
1629                 ++index;\r
1630                 /* \r
1631                  * Save the index point after the separator, since the format\r
1632                  * requires two separators if the country is not present.\r
1633                  */\r
1634                 int oldIndex = index;\r
1635 \r
1636                 skipUntilTerminatorOrIDSeparator();\r
1637                 int charsSkipped = index - oldIndex;\r
1638                 if (charsSkipped < 2 || charsSkipped > 3) {\r
1639                     index = oldIndex;\r
1640                 }\r
1641             }\r
1642         }\r
1643 \r
1644         /**\r
1645          * Advance index past variant, and accumulate normalized variant in buffer.  This ignores\r
1646          * the codepage information from POSIX ids.  Index must be immediately after the country\r
1647          * or script.  Index is left at the keyword separator or at the end of the text.  Return\r
1648          * the start of the variant code in the buffer.\r
1649          *\r
1650          * In standard form, we can have the following forms:\r
1651          * ll__VVVV\r
1652          * ll_CC_VVVV\r
1653          * ll_Ssss_VVVV\r
1654          * ll_Ssss_CC_VVVV\r
1655          *\r
1656          * This also handles POSIX ids, which can have the following forms (pppp is code page id):\r
1657          * ll_CC.pppp          --> ll_CC\r
1658          * ll_CC.pppp@VVVV     --> ll_CC_VVVV\r
1659          * ll_CC@VVVV          --> ll_CC_VVVV\r
1660          *\r
1661          * We identify this use of '@' in POSIX ids by looking for an '=' following\r
1662          * the '@'.  If there is one, we consider '@' to start a keyword list, instead of\r
1663          * being part of a POSIX id.\r
1664          *\r
1665          * Note:  since it was decided that we want an option to not handle POSIX ids, this\r
1666          * becomes a bit more complex.\r
1667          */\r
1668         private int parseVariant() {\r
1669             int oldBlen = blen;\r
1670 \r
1671             boolean start = true;\r
1672             boolean needSeparator = true;\r
1673             boolean skipping = false;\r
1674             char c;\r
1675             while ((c = next()) != DONE) {\r
1676                 if (c == DOT) {\r
1677                     start = false;\r
1678                     skipping = true;\r
1679                 } else if (c == KEYWORD_SEPARATOR) {\r
1680                     if (haveKeywordAssign()) {\r
1681                         break;\r
1682                     }\r
1683                     skipping = false;\r
1684                     start = false;\r
1685                     needSeparator = true; // add another underscore if we have more text\r
1686                 } else if (start) {\r
1687                     start = false;\r
1688                 } else if (!skipping) {\r
1689                     if (needSeparator) {\r
1690                         boolean incOldBlen = blen == oldBlen; // need to skip separators\r
1691                         needSeparator = false;\r
1692                         if (incOldBlen && !hadCountry) { // no country, we'll need two\r
1693                             addSeparator();\r
1694                             ++oldBlen; // for sure\r
1695                         }\r
1696                         addSeparator();\r
1697                         if (incOldBlen) { // only for the first separator\r
1698                             ++oldBlen;\r
1699                         }\r
1700                     }\r
1701                     c = Character.toUpperCase(c);\r
1702                     if (c == HYPHEN || c == COMMA) {\r
1703                         c = UNDERSCORE;\r
1704                     }\r
1705                     append(c);\r
1706                 }\r
1707             }\r
1708             --index; // unget\r
1709             \r
1710             return oldBlen;\r
1711         }\r
1712 \r
1713         // no need for skipvariant, to get the keywords we'll just scan directly for \r
1714         // the keyword separator\r
1715 \r
1716         /**\r
1717          * Returns the normalized language id, or the empty string.\r
1718          */\r
1719         public String getLanguage() {\r
1720             reset();\r
1721             return getString(parseLanguage());\r
1722         }\r
1723    \r
1724         /**\r
1725          * Returns the normalized script id, or the empty string.\r
1726          */\r
1727         public String getScript() {\r
1728             reset();\r
1729             skipLanguage();\r
1730             return getString(parseScript());\r
1731         }\r
1732     \r
1733         /**\r
1734          * return the normalized country id, or the empty string.\r
1735          */\r
1736         public String getCountry() {\r
1737             reset();\r
1738             skipLanguage();\r
1739             skipScript();\r
1740             return getString(parseCountry());\r
1741         }\r
1742 \r
1743         /**\r
1744          * Returns the normalized variant id, or the empty string.\r
1745          */\r
1746         public String getVariant() {\r
1747             reset();\r
1748             skipLanguage();\r
1749             skipScript();\r
1750             skipCountry();\r
1751             return getString(parseVariant());\r
1752         }\r
1753 \r
1754         /**\r
1755          * Returns the language, script, country, and variant as separate strings.\r
1756          */\r
1757         public String[] getLanguageScriptCountryVariant() {\r
1758             reset();\r
1759             return new String[] {\r
1760                 getString(parseLanguage()),\r
1761                 getString(parseScript()),\r
1762                 getString(parseCountry()),\r
1763                 getString(parseVariant())\r
1764             };\r
1765         }\r
1766 \r
1767         public void setBaseName(String baseName) {\r
1768             this.baseName = baseName;\r
1769         }\r
1770 \r
1771         public void parseBaseName() {\r
1772             if (baseName != null) {\r
1773                 set(0, baseName);\r
1774             } else {\r
1775                 reset();\r
1776                 parseLanguage();\r
1777                 parseScript();\r
1778                 parseCountry();\r
1779                 parseVariant();\r
1780             \r
1781                 // catch unwanted trailing underscore after country if there was no variant\r
1782                 if (blen > 1 && buffer[blen-1] == UNDERSCORE) {\r
1783                     --blen;\r
1784                 }\r
1785             }\r
1786         }\r
1787 \r
1788         /**\r
1789          * Returns the normalized base form of the locale id.  The base\r
1790          * form does not include keywords.\r
1791          */\r
1792         public String getBaseName() {\r
1793             if (baseName != null) {\r
1794                 return baseName;\r
1795             }\r
1796             parseBaseName();\r
1797             return getString(0);\r
1798         }\r
1799 \r
1800         /**\r
1801          * Returns the normalized full form of the locale id.  The full\r
1802          * form includes keywords if they are present.\r
1803          */\r
1804         public String getName() {\r
1805             parseBaseName();\r
1806             parseKeywords();\r
1807             return getString(0);\r
1808         }\r
1809 \r
1810         // keyword utilities\r
1811 \r
1812         /**\r
1813          * If we have keywords, advance index to the start of the keywords and return true, \r
1814          * otherwise return false.\r
1815          */\r
1816         private boolean setToKeywordStart() {\r
1817             for (int i = index; i < id.length; ++i) {\r
1818                 if (id[i] == KEYWORD_SEPARATOR) {\r
1819                     if (canonicalize) {\r
1820                         for (int j = ++i; j < id.length; ++j) { // increment i past separator for return\r
1821                             if (id[j] == KEYWORD_ASSIGN) {\r
1822                                 index = i;\r
1823                                 return true;\r
1824                             }\r
1825                         }\r
1826                     } else {\r
1827                         if (++i < id.length) {\r
1828                             index = i;\r
1829                             return true;\r
1830                         }\r
1831                     }\r
1832                     break;\r
1833                 }\r
1834             }\r
1835             return false;\r
1836         }\r
1837         \r
1838         private static boolean isDoneOrKeywordAssign(char c) {\r
1839             return c == DONE || c == KEYWORD_ASSIGN;\r
1840         }\r
1841 \r
1842         private static boolean isDoneOrItemSeparator(char c) {\r
1843             return c == DONE || c == ITEM_SEPARATOR;\r
1844         }\r
1845 \r
1846         private String getKeyword() {\r
1847             int start = index;\r
1848             while (!isDoneOrKeywordAssign(next())) {\r
1849             }\r
1850             --index;\r
1851             return new String(id, start, index-start).trim().toLowerCase();\r
1852         }\r
1853 \r
1854         private String getValue() {\r
1855             int start = index;\r
1856             while (!isDoneOrItemSeparator(next())) {\r
1857             }\r
1858             --index;\r
1859             return new String(id, start, index-start).trim(); // leave case alone\r
1860         }\r
1861 \r
1862         private Comparator getKeyComparator() {\r
1863             final Comparator comp = new Comparator() {\r
1864                     public int compare(Object lhs, Object rhs) {\r
1865                         return ((String)lhs).compareTo((String)rhs);\r
1866                     }\r
1867                 };\r
1868             return comp;\r
1869         }\r
1870 \r
1871         /**\r
1872          * Returns a map of the keywords and values, or null if there are none.\r
1873          */\r
1874         private Map getKeywordMap() {\r
1875             if (keywords == null) {\r
1876                 TreeMap m = null;\r
1877                 if (setToKeywordStart()) {\r
1878                     // trim spaces and convert to lower case, both keywords and values.\r
1879                     do {\r
1880                         String key = getKeyword();\r
1881                         if (key.length() == 0) {\r
1882                             break;\r
1883                         }\r
1884                         char c = next();\r
1885                         if (c != KEYWORD_ASSIGN) {\r
1886                             // throw new IllegalArgumentException("key '" + key + "' missing a value.");\r
1887                             if (c == DONE) {\r
1888                                 break;\r
1889                             } else {\r
1890                                 continue;\r
1891                             }\r
1892                         }\r
1893                         String value = getValue();\r
1894                         if (value.length() == 0) {\r
1895                             // throw new IllegalArgumentException("key '" + key + "' missing a value.");\r
1896                             continue;\r
1897                         }\r
1898                         if (m == null) {\r
1899                             m = new TreeMap(getKeyComparator());\r
1900                         } else if (m.containsKey(key)) {\r
1901                             // throw new IllegalArgumentException("key '" + key + "' already has a value.");\r
1902                             continue;\r
1903                         }\r
1904                         m.put(key, value);\r
1905                     } while (next() == ITEM_SEPARATOR);\r
1906                 }               \r
1907                 keywords = m != null ? m : Collections.EMPTY_MAP;\r
1908             }\r
1909 \r
1910             return keywords;\r
1911         }\r
1912 \r
1913         /**\r
1914          * Parse the keywords and return start of the string in the buffer.\r
1915          */\r
1916         private int parseKeywords() {\r
1917             int oldBlen = blen;\r
1918             Map m = getKeywordMap();\r
1919             if (!m.isEmpty()) {\r
1920                 Iterator iter = m.entrySet().iterator();\r
1921                 boolean first = true;\r
1922                 while (iter.hasNext()) {\r
1923                     append(first ? KEYWORD_SEPARATOR : ITEM_SEPARATOR);\r
1924                     first = false;\r
1925                     Map.Entry e = (Map.Entry)iter.next();\r
1926                     append((String)e.getKey());\r
1927                     append(KEYWORD_ASSIGN);\r
1928                     append((String)e.getValue());\r
1929                 }\r
1930                 if (blen != oldBlen) {\r
1931                     ++oldBlen;\r
1932                 }\r
1933             }\r
1934             return oldBlen;\r
1935         }\r
1936 \r
1937         /**\r
1938          * Returns an iterator over the keywords, or null if we have an empty map.\r
1939          */\r
1940         public Iterator getKeywords() {\r
1941             Map m = getKeywordMap();\r
1942             return m.isEmpty() ? null : m.keySet().iterator();\r
1943         }\r
1944 \r
1945         /**\r
1946          * Returns the value for the named keyword, or null if the keyword is not\r
1947          * present.\r
1948          */\r
1949         public String getKeywordValue(String keywordName) {\r
1950             Map m = getKeywordMap();\r
1951             return m.isEmpty() ? null : (String)m.get(keywordName.trim().toLowerCase());\r
1952         }\r
1953 \r
1954         /**\r
1955          * Set the keyword value only if it is not already set to something else.\r
1956          */\r
1957         public void defaultKeywordValue(String keywordName, String value) {\r
1958             setKeywordValue(keywordName, value, false);\r
1959         }\r
1960             \r
1961         /**\r
1962          * Set the value for the named keyword, or unset it if value is null.  If\r
1963          * keywordName itself is null, unset all keywords.  If keywordName is not null,\r
1964          * value must not be null.\r
1965          */\r
1966         public void setKeywordValue(String keywordName, String value) {\r
1967             setKeywordValue(keywordName, value, true);\r
1968         }\r
1969 \r
1970         /**\r
1971          * Set the value for the named keyword, or unset it if value is null.  If\r
1972          * keywordName itself is null, unset all keywords.  If keywordName is not null,\r
1973          * value must not be null.  If reset is true, ignore any previous value for \r
1974          * the keyword, otherwise do not change the keyword (including removal of\r
1975          * one or all keywords).\r
1976          */\r
1977         private void setKeywordValue(String keywordName, String value, boolean reset) {\r
1978             if (keywordName == null) {\r
1979                 if (reset) {\r
1980                     // force new map, ignore value\r
1981                     keywords = Collections.EMPTY_MAP;\r
1982                 }\r
1983             } else {\r
1984                 keywordName = keywordName.trim().toLowerCase();\r
1985                 if (keywordName.length() == 0) {\r
1986                     throw new IllegalArgumentException("keyword must not be empty");\r
1987                 }\r
1988                 if (value != null) {\r
1989                     value = value.trim();\r
1990                     if (value.length() == 0) {\r
1991                         throw new IllegalArgumentException("value must not be empty");\r
1992                     }\r
1993                 }\r
1994                 Map m = getKeywordMap();\r
1995                 if (m.isEmpty()) { // it is EMPTY_MAP\r
1996                     if (value != null) {\r
1997                         // force new map\r
1998                         keywords = new TreeMap(getKeyComparator());\r
1999                         keywords.put(keywordName, value.trim());\r
2000                     }\r
2001                 } else {\r
2002                     if (reset || !m.containsKey(keywordName)) {\r
2003                         if (value != null) {\r
2004                             m.put(keywordName, value);\r
2005                         } else {\r
2006                             m.remove(keywordName);\r
2007                             if (m.isEmpty()) {\r
2008                                 // force new map\r
2009                                 keywords = Collections.EMPTY_MAP;\r
2010                             }\r
2011                         }\r
2012                     }\r
2013                 }\r
2014             }\r
2015         }\r
2016     }\r
2017 \r
2018     /**\r
2019      * linear search of the string array. the arrays are unfortunately ordered by the\r
2020      * two-letter target code, not the three-letter search code, which seems backwards.\r
2021      */\r
2022     private static int findIndex(String[] array, String target){\r
2023         for (int i = 0; i < array.length; i++) {\r
2024             if (target.equals(array[i])) {\r
2025                 return i;\r
2026             }\r
2027         }\r
2028         return -1;\r
2029     }    \r
2030 \r
2031     /**\r
2032      * Returns the canonical name for the specified locale ID.  This is used to convert POSIX\r
2033      * and other grandfathered IDs to standard ICU form.\r
2034      * @param localeID the locale id\r
2035      * @return the canonicalized id\r
2036      * @stable ICU 3.0\r
2037      */\r
2038     public static String canonicalize(String localeID){\r
2039         IDParser parser = new IDParser(localeID, true);\r
2040         String baseName = parser.getBaseName();\r
2041         boolean foundVariant = false;\r
2042       \r
2043         // formerly, we always set to en_US_POSIX if the basename was empty, but\r
2044         // now we require that the entire id be empty, so that "@foo=bar"\r
2045         // will pass through unchanged.\r
2046         // {dlf} I'd rather keep "" unchanged.\r
2047         if (localeID.equals("")) {\r
2048             return "";\r
2049 //              return "en_US_POSIX";\r
2050         }\r
2051 \r
2052         // we have an ID in the form xx_Yyyy_ZZ_KKKKK\r
2053 \r
2054         initCANONICALIZE_MAP();\r
2055 \r
2056         /* convert the variants to appropriate ID */\r
2057         for (int i = 0; i < variantsToKeywords.length; i++) {\r
2058             String[] vals = variantsToKeywords[i];\r
2059             int idx = baseName.lastIndexOf("_" + vals[0]);\r
2060             if (idx > -1) {\r
2061                 foundVariant = true;\r
2062 \r
2063                 baseName = baseName.substring(0, idx);\r
2064                 if (baseName.endsWith("_")) {\r
2065                     baseName = baseName.substring(0, --idx);\r
2066                 }\r
2067                 parser.setBaseName(baseName);\r
2068                 parser.defaultKeywordValue(vals[1], vals[2]);\r
2069                 break;\r
2070             }\r
2071         }\r
2072 \r
2073         /* See if this is an already known locale */\r
2074         for (int i = 0; i < CANONICALIZE_MAP.length; i++) {\r
2075             if (CANONICALIZE_MAP[i][0].equals(baseName)) {\r
2076                 foundVariant = true;\r
2077 \r
2078                 String[] vals = CANONICALIZE_MAP[i];\r
2079                 parser.setBaseName(vals[1]);\r
2080                 if (vals[2] != null) {\r
2081                     parser.defaultKeywordValue(vals[2], vals[3]);\r
2082                 }\r
2083                 break;\r
2084             }\r
2085         }\r
2086 \r
2087         /* total mondo hack for Norwegian, fortunately the main NY case is handled earlier */\r
2088         if (!foundVariant) {\r
2089             if (parser.getLanguage().equals("nb") && parser.getVariant().equals("NY")) {\r
2090                 parser.setBaseName(lscvToID("nn", parser.getScript(), parser.getCountry(), null));\r
2091             }\r
2092         }\r
2093 \r
2094         return parser.getName();\r
2095     }\r
2096     \r
2097     /**\r
2098      * Given a keyword and a value, return a new locale with an updated\r
2099      * keyword and value.  If keyword is null, this removes all keywords from the locale id.\r
2100      * Otherwise, if the value is null, this removes the value for this keyword from the\r
2101      * locale id.  Otherwise, this adds/replaces the value for this keyword in the locale id.\r
2102      * The keyword and value must not be empty.\r
2103      * @param keyword the keyword to add/remove, or null to remove all keywords.\r
2104      * @param value the value to add/set, or null to remove this particular keyword.\r
2105      * @return the updated locale\r
2106      * @stable ICU 3.2\r
2107      */\r
2108     public ULocale setKeywordValue(String keyword, String value) {\r
2109         return new ULocale(setKeywordValue(localeID, keyword, value), (Locale)null);\r
2110     }\r
2111 \r
2112     /**\r
2113      * Given a locale id, a keyword, and a value, return a new locale id with an updated\r
2114      * keyword and value.  If keyword is null, this removes all keywords from the locale id.\r
2115      * Otherwise, if the value is null, this removes the value for this keyword from the\r
2116      * locale id.  Otherwise, this adds/replaces the value for this keyword in the locale id.\r
2117      * The keyword and value must not be empty.\r
2118      * @param localeID the locale id to modify\r
2119      * @param keyword the keyword to add/remove, or null to remove all keywords.\r
2120      * @param value the value to add/set, or null to remove this particular keyword.\r
2121      * @return the updated locale id\r
2122      * @stable ICU 3.2\r
2123      */\r
2124     public static String setKeywordValue(String localeID, String keyword, String value) {\r
2125         IDParser parser = new IDParser(localeID);\r
2126         parser.setKeywordValue(keyword, value);\r
2127         return parser.getName();\r
2128     }\r
2129 \r
2130     /*\r
2131      * Given a locale id, a keyword, and a value, return a new locale id with an updated\r
2132      * keyword and value, if the keyword does not already have a value.  The keyword and\r
2133      * value must not be null or empty.\r
2134      * @param localeID the locale id to modify\r
2135      * @param keyword the keyword to add, if not already present\r
2136      * @param value the value to add, if not already present\r
2137      * @return the updated locale id\r
2138      * @internal\r
2139      */\r
2140 /*    private static String defaultKeywordValue(String localeID, String keyword, String value) {\r
2141         IDParser parser = new IDParser(localeID);\r
2142         parser.defaultKeywordValue(keyword, value);\r
2143         return parser.getName();\r
2144     }*/\r
2145 \r
2146     /**\r
2147      * Returns a three-letter abbreviation for this locale's language.  If the locale\r
2148      * doesn't specify a language, returns the empty string.  Otherwise, returns\r
2149      * a lowercase ISO 639-2/T language code.\r
2150      * The ISO 639-2 language codes can be found on-line at\r
2151      *   <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>\r
2152      * @exception MissingResourceException Throws MissingResourceException if the\r
2153      * three-letter language abbreviation is not available for this locale.\r
2154      * @stable ICU 3.0\r
2155      */\r
2156     public String getISO3Language(){\r
2157         return getISO3Language(localeID);\r
2158     }\r
2159 \r
2160     /**\r
2161      * Returns a three-letter abbreviation for this locale's language.  If the locale\r
2162      * doesn't specify a language, returns the empty string.  Otherwise, returns\r
2163      * a lowercase ISO 639-2/T language code.\r
2164      * The ISO 639-2 language codes can be found on-line at\r
2165      *   <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>\r
2166      * @exception MissingResourceException Throws MissingResourceException if the\r
2167      * three-letter language abbreviation is not available for this locale.\r
2168      * @stable ICU 3.0\r
2169      */\r
2170     public static String getISO3Language(String localeID){\r
2171         initLanguageTables();\r
2172 \r
2173         String language = getLanguage(localeID);\r
2174         int offset = findIndex(_languages, language);\r
2175         if(offset>=0){\r
2176             return _languages3[offset];\r
2177         } else {\r
2178             offset = findIndex(_obsoleteLanguages, language);\r
2179             if (offset >= 0) {\r
2180                 return _obsoleteLanguages3[offset];\r
2181             }\r
2182         }\r
2183         return EMPTY_STRING;\r
2184     }\r
2185     \r
2186     /**\r
2187      * Returns a three-letter abbreviation for this locale's country/region.  If the locale\r
2188      * doesn't specify a country, returns the empty string.  Otherwise, returns\r
2189      * an uppercase ISO 3166 3-letter country code.\r
2190      * @exception MissingResourceException Throws MissingResourceException if the\r
2191      * three-letter country abbreviation is not available for this locale.\r
2192      * @stable ICU 3.0\r
2193      */\r
2194     public String getISO3Country(){\r
2195         return getISO3Country(localeID);\r
2196     }\r
2197     /**\r
2198      * Returns a three-letter abbreviation for this locale's country/region.  If the locale\r
2199      * doesn't specify a country, returns the empty string.  Otherwise, returns\r
2200      * an uppercase ISO 3166 3-letter country code.\r
2201      * @exception MissingResourceException Throws MissingResourceException if the\r
2202      * three-letter country abbreviation is not available for this locale.\r
2203      * @stable ICU 3.0\r
2204      */\r
2205     public static String getISO3Country(String localeID){\r
2206         initCountryTables();\r
2207 \r
2208         String country = getCountry(localeID);\r
2209         int offset = findIndex(_countries, country);\r
2210         if(offset>=0){\r
2211             return _countries3[offset];\r
2212         }else{\r
2213             offset = findIndex(_obsoleteCountries, country);\r
2214             if(offset>=0){\r
2215                 return _obsoleteCountries3[offset];   \r
2216             }\r
2217         }\r
2218         return EMPTY_STRING;\r
2219     }\r
2220     \r
2221     // display names\r
2222 \r
2223 //    /**\r
2224 //     * Utility to fetch locale display data from resource bundle tables.\r
2225 //     */\r
2226 //    private static String getTableString(String tableName, String subtableName, String item, String displayLocaleID) {\r
2227 //        if (item.length() > 0) {\r
2228 //            try {\r
2229 //                ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.\r
2230 //                  getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, displayLocaleID);\r
2231 //                return getTableString(tableName, subtableName, item, bundle);\r
2232 //            } catch (Exception e) {\r
2233 ////              System.out.println("gtsu: " + e.getMessage());\r
2234 //            }\r
2235 //        }\r
2236 //        return item;\r
2237 //    }\r
2238 //        \r
2239 //    /**\r
2240 //     * Utility to fetch locale display data from resource bundle tables.\r
2241 //     */\r
2242 //    private static String getTableString(String tableName, String subtableName, String item, ICUResourceBundle bundle) {\r
2243 ////      System.out.println("gts table: " + tableName + \r
2244 ////                         " subtable: " + subtableName +\r
2245 ////                         " item: " + item +\r
2246 ////                         " bundle: " + bundle.getULocale());\r
2247 //        try {\r
2248 //            for (;;) {\r
2249 //                // special case currency\r
2250 //                if ("currency".equals(subtableName)) {\r
2251 //                    ICUResourceBundle table = bundle.getWithFallback("Currencies");\r
2252 //                    table = table.getWithFallback(item);\r
2253 //                    return table.getString(1);\r
2254 //                } else {\r
2255 //                    ICUResourceBundle table = bundle.getWithFallback(tableName);\r
2256 //                    try {\r
2257 //                        if (subtableName != null) {\r
2258 //                            table = table.getWithFallback(subtableName);\r
2259 //                        }\r
2260 //                        return table.getStringWithFallback(item);\r
2261 //                    }\r
2262 //                    catch (MissingResourceException e) {\r
2263 //                        \r
2264 //                        if(subtableName==null){\r
2265 //                            try{\r
2266 //                                // may be a deprecated code\r
2267 //                                String currentName = null;\r
2268 //                                if(tableName.equals("Countries")){\r
2269 //                                    currentName = getCurrentCountryID(item);\r
2270 //                                }else if(tableName.equals("Languages")){\r
2271 //                                    currentName = getCurrentLanguageID(item);\r
2272 //                                }\r
2273 //                                return table.getStringWithFallback(currentName);\r
2274 //                            }catch (MissingResourceException ex){/* fall through*/}\r
2275 //                        }\r
2276 //                        \r
2277 //                        // still can't figure out ?.. try the fallback mechanism\r
2278 //                        String fallbackLocale = table.getWithFallback("Fallback").getString();\r
2279 //                        if (fallbackLocale.length() == 0) {\r
2280 //                            fallbackLocale = "root";\r
2281 //                        }\r
2282 ////                      System.out.println("bundle: " + bundle.getULocale() + " fallback: " + fallbackLocale);\r
2283 //                        if(fallbackLocale.equals(table.getULocale().localeID)){\r
2284 //                            return item;\r
2285 //                        }\r
2286 //                        bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, \r
2287 //                                                                                      fallbackLocale);\r
2288 ////                          System.out.println("fallback from " + table.getULocale() + " to " + fallbackLocale + \r
2289 ////                                             ", got bundle " + bundle.getULocale());                      \r
2290 //                    }\r
2291 //                }\r
2292 //            }\r
2293 //        }\r
2294 //        catch (Exception e) {\r
2295 ////          System.out.println("gtsi: " + e.getMessage());\r
2296 //        }\r
2297 //        return item;\r
2298 //    }\r
2299 \r
2300     /**\r
2301      * Returns this locale's language localized for display in the default locale.\r
2302      * @return the localized language name.\r
2303      * @stable ICU 3.0\r
2304      */\r
2305     public String getDisplayLanguage() {\r
2306         return getDisplayLanguageInternal(localeID, getDefault().localeID);\r
2307     }\r
2308 \r
2309     /**\r
2310      * Returns this locale's language localized for display in the provided locale.\r
2311      * @param displayLocale the locale in which to display the name.\r
2312      * @return the localized language name.\r
2313      * @stable ICU 3.0\r
2314      */\r
2315     public String getDisplayLanguage(ULocale displayLocale) {\r
2316         return getDisplayLanguageInternal(localeID, displayLocale.localeID);\r
2317     }\r
2318     \r
2319     /**\r
2320      * Returns a locale's language localized for display in the provided locale.\r
2321      * This is a cover for the ICU4C API.\r
2322      * @param localeID the id of the locale whose language will be displayed\r
2323      * @param displayLocaleID the id of the locale in which to display the name.\r
2324      * @return the localized language name.\r
2325      * @stable ICU 3.0\r
2326      */\r
2327     public static String getDisplayLanguage(String localeID, String displayLocaleID) {\r
2328         return getDisplayLanguageInternal(localeID, getName(displayLocaleID));\r
2329     }\r
2330 \r
2331     /**\r
2332      * Returns a locale's language localized for display in the provided locale.\r
2333      * This is a cover for the ICU4C API.\r
2334      * @param localeID the id of the locale whose language will be displayed.\r
2335      * @param displayLocale the locale in which to display the name.\r
2336      * @return the localized language name.\r
2337      * @stable ICU 3.0\r
2338      */\r
2339     public static String getDisplayLanguage(String localeID, ULocale displayLocale) {\r
2340         return getDisplayLanguageInternal(localeID, displayLocale.localeID);\r
2341     } \r
2342 \r
2343     static String getCurrentCountryID(String oldID){\r
2344         initCountryTables();\r
2345         int offset = findIndex(_deprecatedCountries, oldID);\r
2346         if (offset >= 0) {\r
2347             return _replacementCountries[offset];\r
2348         }\r
2349         return oldID;\r
2350     }\r
2351     static String getCurrentLanguageID(String oldID){\r
2352         initLanguageTables();\r
2353         int offset = findIndex(_obsoleteLanguages, oldID);\r
2354         if (offset >= 0) {\r
2355             return _replacementLanguages[offset];\r
2356         }\r
2357         return oldID;        \r
2358     }\r
2359 \r
2360 \r
2361     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
2362     private static String getDisplayLanguageInternal(String localeID, String displayLocaleID) {\r
2363 //        return getTableString("Languages", null, new IDParser(localeID).getLanguage(), displayLocaleID);\r
2364         return toLocale(localeID).getDisplayLanguage(toLocale(displayLocaleID));\r
2365     }\r
2366  \r
2367     /**\r
2368      * Returns this locale's script localized for display in the default locale.\r
2369      * @return the localized script name.\r
2370      * @stable ICU 3.0\r
2371      */\r
2372     public String getDisplayScript() {\r
2373         return getDisplayScriptInternal(localeID, getDefault().localeID);\r
2374     }\r
2375 \r
2376     /**\r
2377      * Returns this locale's script localized for display in the provided locale.\r
2378      * @param displayLocale the locale in which to display the name.\r
2379      * @return the localized script name.\r
2380      * @stable ICU 3.0\r
2381      */\r
2382     public String getDisplayScript(ULocale displayLocale) {\r
2383         return getDisplayScriptInternal(localeID, displayLocale.localeID);\r
2384     }\r
2385     \r
2386     /**\r
2387      * Returns a locale's script localized for display in the provided locale.\r
2388      * This is a cover for the ICU4C API.\r
2389      * @param localeID the id of the locale whose script will be displayed\r
2390      * @param displayLocaleID the id of the locale in which to display the name.\r
2391      * @return the localized script name.\r
2392      * @stable ICU 3.0\r
2393      */\r
2394     public static String getDisplayScript(String localeID, String displayLocaleID) {\r
2395         return getDisplayScriptInternal(localeID, getName(displayLocaleID));\r
2396     }\r
2397 \r
2398     /**\r
2399      * Returns a locale's script localized for display in the provided locale.\r
2400      * @param localeID the id of the locale whose script will be displayed.\r
2401      * @param displayLocale the locale in which to display the name.\r
2402      * @return the localized script name.\r
2403      * @stable ICU 3.0\r
2404      */\r
2405     public static String getDisplayScript(String localeID, ULocale displayLocale) {\r
2406         return getDisplayScriptInternal(localeID, displayLocale.localeID);\r
2407     }\r
2408 \r
2409     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
2410     private static String getDisplayScriptInternal(String localeID, String displayLocaleID) {\r
2411 //        return getTableString("Scripts", null, new IDParser(localeID).getScript(), displayLocaleID);\r
2412         return new IDParser(localeID).getScript();\r
2413     }\r
2414 \r
2415     /**\r
2416      * Returns this locale's country localized for display in the default locale.\r
2417      * @return the localized country name.\r
2418      * @stable ICU 3.0\r
2419      */\r
2420     public String getDisplayCountry() {\r
2421         return getDisplayCountryInternal(localeID, getDefault().localeID);\r
2422     }\r
2423     \r
2424     /**\r
2425      * Returns this locale's country localized for display in the provided locale.\r
2426      * @param displayLocale the locale in which to display the name.\r
2427      * @return the localized country name.\r
2428      * @stable ICU 3.0\r
2429      */\r
2430     public String getDisplayCountry(ULocale displayLocale){\r
2431         return getDisplayCountryInternal(localeID, displayLocale.localeID);   \r
2432     }\r
2433     \r
2434     /**\r
2435      * Returns a locale's country localized for display in the provided locale.\r
2436      * This is a cover for the ICU4C API.\r
2437      * @param localeID the id of the locale whose country will be displayed\r
2438      * @param displayLocaleID the id of the locale in which to display the name.\r
2439      * @return the localized country name.\r
2440      * @stable ICU 3.0\r
2441      */\r
2442     public static String getDisplayCountry(String localeID, String displayLocaleID) {\r
2443         return getDisplayCountryInternal(localeID, getName(displayLocaleID));\r
2444     }\r
2445 \r
2446     /**\r
2447      * Returns a locale's country localized for display in the provided locale.\r
2448      * This is a cover for the ICU4C API.\r
2449      * @param localeID the id of the locale whose country will be displayed.\r
2450      * @param displayLocale the locale in which to display the name.\r
2451      * @return the localized country name.\r
2452      * @stable ICU 3.0\r
2453      */\r
2454     public static String getDisplayCountry(String localeID, ULocale displayLocale) {\r
2455         return getDisplayCountryInternal(localeID, displayLocale.localeID);\r
2456     }\r
2457 \r
2458     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
2459     private static String getDisplayCountryInternal(String localeID, String displayLocaleID) {\r
2460 //        return getTableString("Countries", null,  new IDParser(localeID).getCountry(), displayLocaleID);\r
2461         return toLocale(localeID).getDisplayCountry(toLocale(displayLocaleID));\r
2462     }\r
2463     \r
2464     /**\r
2465      * Returns this locale's variant localized for display in the default locale.\r
2466      * @return the localized variant name.\r
2467      * @stable ICU 3.0\r
2468      */\r
2469     public String getDisplayVariant() {\r
2470         return getDisplayVariantInternal(localeID, getDefault().localeID);   \r
2471     }\r
2472 \r
2473     /**\r
2474      * Returns this locale's variant localized for display in the provided locale.\r
2475      * @param displayLocale the locale in which to display the name.\r
2476      * @return the localized variant name.\r
2477      * @stable ICU 3.0\r
2478      */\r
2479     public String getDisplayVariant(ULocale displayLocale) {\r
2480         return getDisplayVariantInternal(localeID, displayLocale.localeID);   \r
2481     }\r
2482     \r
2483     /**\r
2484      * Returns a locale's variant localized for display in the provided locale.\r
2485      * This is a cover for the ICU4C API.\r
2486      * @param localeID the id of the locale whose variant will be displayed\r
2487      * @param displayLocaleID the id of the locale in which to display the name.\r
2488      * @return the localized variant name.\r
2489      * @stable ICU 3.0\r
2490      */\r
2491     public static String getDisplayVariant(String localeID, String displayLocaleID){\r
2492         return getDisplayVariantInternal(localeID, getName(displayLocaleID));\r
2493     }\r
2494     \r
2495     /**\r
2496      * Returns a locale's variant localized for display in the provided locale.\r
2497      * This is a cover for the ICU4C API.\r
2498      * @param localeID the id of the locale whose variant will be displayed.\r
2499      * @param displayLocale the locale in which to display the name.\r
2500      * @return the localized variant name.\r
2501      * @stable ICU 3.0\r
2502      */\r
2503     public static String getDisplayVariant(String localeID, ULocale displayLocale) {\r
2504         return getDisplayVariantInternal(localeID, displayLocale.localeID);\r
2505     }\r
2506 \r
2507     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
2508     private static String getDisplayVariantInternal(String localeID, String displayLocaleID) {\r
2509 //        return getTableString("Variants", null, new IDParser(localeID).getVariant(), displayLocaleID);\r
2510         return toLocale(localeID).getDisplayVariant(toLocale(displayLocaleID));\r
2511     }\r
2512 \r
2513     /**\r
2514      * Returns a keyword localized for display in the default locale.\r
2515      * @param keyword the keyword to be displayed.\r
2516      * @return the localized keyword name.\r
2517      * @see #getKeywords()\r
2518      * @stable ICU 3.0\r
2519      */\r
2520     public static String getDisplayKeyword(String keyword) {\r
2521         return getDisplayKeywordInternal(keyword, getDefault().localeID);   \r
2522     }\r
2523     \r
2524     /**\r
2525      * Returns a keyword localized for display in the specified locale.\r
2526      * @param keyword the keyword to be displayed.\r
2527      * @param displayLocaleID the id of the locale in which to display the keyword.\r
2528      * @return the localized keyword name.\r
2529      * @see #getKeywords(String)\r
2530      * @stable ICU 3.0\r
2531      */\r
2532     public static String getDisplayKeyword(String keyword, String displayLocaleID) {\r
2533         return getDisplayKeywordInternal(keyword, getName(displayLocaleID));   \r
2534     }\r
2535 \r
2536     /**\r
2537      * Returns a keyword localized for display in the specified locale.\r
2538      * @param keyword the keyword to be displayed.\r
2539      * @param displayLocale the locale in which to display the keyword.\r
2540      * @return the localized keyword name.\r
2541      * @see #getKeywords(String)\r
2542      * @stable ICU 3.0\r
2543      */\r
2544     public static String getDisplayKeyword(String keyword, ULocale displayLocale) {\r
2545         return getDisplayKeywordInternal(keyword, displayLocale.localeID);\r
2546     }\r
2547 \r
2548     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
2549     private static String getDisplayKeywordInternal(String keyword, String displayLocaleID) {\r
2550 //        return getTableString("Keys", null, keyword.trim().toLowerCase(), displayLocaleID);\r
2551         return keyword.trim().toLowerCase(); \r
2552     }\r
2553 \r
2554     /**\r
2555      * Returns a keyword value localized for display in the default locale.\r
2556      * @param keyword the keyword whose value is to be displayed.\r
2557      * @return the localized value name.\r
2558      * @stable ICU 3.0\r
2559      */\r
2560     public String getDisplayKeywordValue(String keyword) {\r
2561         return getDisplayKeywordValueInternal(localeID, keyword, getDefault().localeID);\r
2562     }\r
2563     \r
2564     /**\r
2565      * Returns a keyword value localized for display in the specified locale.\r
2566      * @param keyword the keyword whose value is to be displayed.\r
2567      * @param displayLocale the locale in which to display the value.\r
2568      * @return the localized value name.\r
2569      * @stable ICU 3.0\r
2570      */\r
2571     public String getDisplayKeywordValue(String keyword, ULocale displayLocale) {\r
2572         return getDisplayKeywordValueInternal(localeID, keyword, displayLocale.localeID);   \r
2573     }\r
2574 \r
2575     /**\r
2576      * Returns a keyword value localized for display in the specified locale.\r
2577      * This is a cover for the ICU4C API.\r
2578      * @param localeID the id of the locale whose keyword value is to be displayed.\r
2579      * @param keyword the keyword whose value is to be displayed.\r
2580      * @param displayLocaleID the id of the locale in which to display the value.\r
2581      * @return the localized value name.\r
2582      * @stable ICU 3.0\r
2583      */\r
2584     public static String getDisplayKeywordValue(String localeID, String keyword, String displayLocaleID) {\r
2585         return getDisplayKeywordValueInternal(localeID, keyword, getName(displayLocaleID));\r
2586     }\r
2587 \r
2588     /**\r
2589      * Returns a keyword value localized for display in the specified locale.\r
2590      * This is a cover for the ICU4C API.\r
2591      * @param localeID the id of the locale whose keyword value is to be displayed.\r
2592      * @param keyword the keyword whose value is to be displayed.\r
2593      * @param displayLocale the id of the locale in which to display the value.\r
2594      * @return the localized value name.\r
2595      * @stable ICU 3.0\r
2596      */\r
2597     public static String getDisplayKeywordValue(String localeID, String keyword, ULocale displayLocale) {\r
2598         return getDisplayKeywordValueInternal(localeID, keyword, displayLocale.localeID);\r
2599     }\r
2600 \r
2601     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
2602     private static String getDisplayKeywordValueInternal(String localeID, String keyword, String displayLocaleID) {\r
2603         keyword = keyword.trim().toLowerCase();\r
2604         String value = new IDParser(localeID).getKeywordValue(keyword);\r
2605 //        return getTableString("Types", keyword, value, displayLocaleID);\r
2606         return value;\r
2607     }\r
2608     \r
2609     /**\r
2610      * Returns this locale name localized for display in the default locale.\r
2611      * @return the localized locale name.\r
2612      * @stable ICU 3.0\r
2613      */\r
2614     public String getDisplayName() {\r
2615         return getDisplayNameInternal(localeID, getDefault().localeID);\r
2616     }\r
2617     \r
2618     /**\r
2619      * Returns this locale name localized for display in the provided locale.\r
2620      * @param displayLocale the locale in which to display the locale name.\r
2621      * @return the localized locale name.\r
2622      * @stable ICU 3.0\r
2623      */\r
2624     public String getDisplayName(ULocale displayLocale) {\r
2625         return getDisplayNameInternal(localeID, displayLocale.localeID);\r
2626     }\r
2627     \r
2628     /**\r
2629      * Returns the locale ID localized for display in the provided locale.\r
2630      * This is a cover for the ICU4C API.\r
2631      * @param localeID the locale whose name is to be displayed.\r
2632      * @param displayLocaleID the id of the locale in which to display the locale name.\r
2633      * @return the localized locale name.\r
2634      * @stable ICU 3.0\r
2635      */\r
2636     public static String getDisplayName(String localeID, String displayLocaleID) {\r
2637         return getDisplayNameInternal(localeID, getName(displayLocaleID));\r
2638     }\r
2639 \r
2640     /**\r
2641      * Returns the locale ID localized for display in the provided locale.\r
2642      * This is a cover for the ICU4C API.\r
2643      * @param localeID the locale whose name is to be displayed.\r
2644      * @param displayLocale the locale in which to display the locale name.\r
2645      * @return the localized locale name.\r
2646      * @stable ICU 3.0\r
2647      */\r
2648     public static String getDisplayName(String localeID, ULocale displayLocale) {\r
2649         return getDisplayNameInternal(localeID, displayLocale.localeID);\r
2650     }\r
2651 \r
2652     // displayLocaleID is canonical, localeID need not be since parsing will fix this.\r
2653     private static String getDisplayNameInternal(String localeID, String displayLocaleID) {\r
2654         // lang\r
2655         // lang (script, country, variant, keyword=value, ...)\r
2656         // script, country, variant, keyword=value, ...\r
2657 \r
2658 //        final String[] tableNames = { "Languages", "Scripts", "Countries", "Variants" };\r
2659 //\r
2660 //        ICUResourceBundle bundle = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, displayLocaleID);\r
2661 \r
2662         StringBuffer buf = new StringBuffer();\r
2663 \r
2664         IDParser parser = new IDParser(localeID);\r
2665         String[] names = parser.getLanguageScriptCountryVariant();\r
2666 \r
2667         Locale locale = toLocale(localeID);\r
2668         Locale dispLocale = toLocale(displayLocaleID);\r
2669 \r
2670         boolean haveLanguage = names[0].length() > 0;\r
2671         boolean openParen = false;\r
2672         for (int i = 0; i < names.length; ++i) {\r
2673             String name = names[i];\r
2674             if (name.length() > 0) {\r
2675 //                name = getTableString(tableNames[i], null, name, bundle);\r
2676 \r
2677                 switch (i) {\r
2678                 case 0: name = locale.getDisplayLanguage(dispLocale); break;\r
2679                 case 1: break;\r
2680                 case 2: name = locale.getDisplayCountry(dispLocale); break;\r
2681                 case 3: name = locale.getDisplayVariant(dispLocale); break;\r
2682                 }\r
2683 \r
2684                 if (buf.length() > 0) { // need a separator\r
2685                     if (haveLanguage & !openParen) {\r
2686                         buf.append(" (");\r
2687                         openParen = true;\r
2688                     } else {\r
2689                         buf.append(", ");\r
2690                     }\r
2691                 }\r
2692                 buf.append(name);\r
2693             }\r
2694         }\r
2695 \r
2696         Map m = parser.getKeywordMap();\r
2697         if (!m.isEmpty()) {\r
2698             Iterator keys = m.entrySet().iterator();\r
2699             while (keys.hasNext()) {\r
2700                 if (buf.length() > 0) {\r
2701                     if (haveLanguage & !openParen) {\r
2702                         buf.append(" (");\r
2703                         openParen = true;\r
2704                     } else {\r
2705                         buf.append(", ");\r
2706                     }\r
2707                 }\r
2708                 Map.Entry e = (Map.Entry)keys.next();\r
2709                 String key = (String)e.getKey();\r
2710                 String val = (String)e.getValue();\r
2711 //                buf.append(getTableString("Keys", null, key, bundle));\r
2712 //                buf.append("=");\r
2713 //                buf.append(getTableString("Types", key, val, bundle));\r
2714                 buf.append(key);\r
2715                 buf.append("=");\r
2716                 buf.append(val);\r
2717             }\r
2718         }\r
2719 \r
2720         if (openParen) {\r
2721             buf.append(")");\r
2722         }\r
2723             \r
2724         return buf.toString();\r
2725     }\r
2726 \r
2727 //    /**\r
2728 //     * Returns this locale's layout orientation for characters.  The possible\r
2729 //     * values are "left-to-right", "right-to-left", "top-to-bottom" or\r
2730 //     * "bottom-to-top".\r
2731 //     * @return The locale's layout orientation for characters.\r
2732 //     * @draft ICU 4.0\r
2733 //     * @provisional This API might change or be removed in a future release.\r
2734 //     */\r
2735 //    public String getCharacterOrientation() {\r
2736 //        return getTableString("layout", null, "characters", getName());\r
2737 //    }\r
2738 //\r
2739 //    /**\r
2740 //     * Returns this locale's layout orientation for lines.  The possible\r
2741 //     * values are "left-to-right", "right-to-left", "top-to-bottom" or\r
2742 //     * "bottom-to-top".\r
2743 //     * @return The locale's layout orientation for lines.\r
2744 //     * @draft ICU 4.0\r
2745 //     * @provisional This API might change or be removed in a future release.\r
2746 //     */\r
2747 //    public String getLineOrientation() {\r
2748 //        return getTableString("layout", null, "lines", getName());\r
2749 //    }\r
2750 \r
2751     /** \r
2752      * Selector for <tt>getLocale()</tt> indicating the locale of the\r
2753      * resource containing the data.  This is always at or above the\r
2754      * valid locale.  If the valid locale does not contain the\r
2755      * specific data being requested, then the actual locale will be\r
2756      * above the valid locale.  If the object was not constructed from\r
2757      * locale data, then the valid locale is <i>null</i>.\r
2758      *\r
2759      * @draft ICU 2.8 (retain)\r
2760      * @provisional This API might change or be removed in a future release.\r
2761      */\r
2762     public static Type ACTUAL_LOCALE = new Type();\r
2763 \r
2764     /** \r
2765      * Selector for <tt>getLocale()</tt> indicating the most specific\r
2766      * locale for which any data exists.  This is always at or above\r
2767      * the requested locale, and at or below the actual locale.  If\r
2768      * the requested locale does not correspond to any resource data,\r
2769      * then the valid locale will be above the requested locale.  If\r
2770      * the object was not constructed from locale data, then the\r
2771      * actual locale is <i>null</i>.\r
2772      *\r
2773      * <p>Note: The valid locale will be returned correctly in ICU\r
2774      * 3.0 or later.  In ICU 2.8, it is not returned correctly.\r
2775      * @draft ICU 2.8 (retain)\r
2776      * @provisional This API might change or be removed in a future release.\r
2777      */ \r
2778     public static Type VALID_LOCALE = new Type();\r
2779 \r
2780     /**\r
2781      * Opaque selector enum for <tt>getLocale()</tt>.\r
2782      * @see com.ibm.icu.util.ULocale\r
2783      * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE\r
2784      * @see com.ibm.icu.util.ULocale#VALID_LOCALE\r
2785      * @draft ICU 2.8 (retainAll)\r
2786      * @provisional This API might change or be removed in a future release.\r
2787      */\r
2788     public static final class Type {\r
2789         private Type() {}\r
2790     }\r
2791 \r
2792   /**\r
2793     * Based on a HTTP formatted list of acceptable locales, determine an available locale for the user.\r
2794     * NullPointerException is thrown if acceptLanguageList or availableLocales is\r
2795     * null.  If fallback is non-null, it will contain true if a fallback locale (one\r
2796     * not in the acceptLanguageList) was returned.  The value on entry is ignored. \r
2797     * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if\r
2798     * if a ROOT locale was used as a fallback (because nothing else in\r
2799     * availableLocales matched).  No ULocale array element should be null; behavior\r
2800     * is undefined if this is the case.\r
2801     * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales\r
2802     * @param availableLocales list of available locales. One of these will be returned.\r
2803     * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status\r
2804     * @return one of the locales from the availableLocales list, or null if none match\r
2805     * @stable ICU 3.4\r
2806     */\r
2807 \r
2808     public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales, \r
2809                                          boolean[] fallback) {\r
2810         if (acceptLanguageList == null) {\r
2811             throw new NullPointerException();\r
2812         }\r
2813         ULocale acceptList[] = null;\r
2814         try {\r
2815             acceptList = parseAcceptLanguage(acceptLanguageList, true);\r
2816         } catch (ParseException pe) {\r
2817             acceptList = null;\r
2818         }\r
2819         if (acceptList == null) {\r
2820             return null;\r
2821         }\r
2822         return acceptLanguage(acceptList, availableLocales, fallback);\r
2823     }\r
2824 \r
2825     /**\r
2826     * Based on a list of acceptable locales, determine an available locale for the user.\r
2827     * NullPointerException is thrown if acceptLanguageList or availableLocales is\r
2828     * null.  If fallback is non-null, it will contain true if a fallback locale (one\r
2829     * not in the acceptLanguageList) was returned.  The value on entry is ignored. \r
2830     * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if\r
2831     * if a ROOT locale was used as a fallback (because nothing else in\r
2832     * availableLocales matched).  No ULocale array element should be null; behavior\r
2833     * is undefined if this is the case.\r
2834     * @param acceptLanguageList list of acceptable locales\r
2835     * @param availableLocales list of available locales. One of these will be returned.\r
2836     * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status\r
2837     * @return one of the locales from the availableLocales list, or null if none match\r
2838     * @stable ICU 3.4\r
2839     */\r
2840 \r
2841     public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[]\r
2842     availableLocales, boolean[] fallback) {\r
2843         // fallbacklist\r
2844         int i,j;\r
2845         if(fallback != null) {\r
2846             fallback[0]=true;\r
2847         }\r
2848         for(i=0;i<acceptLanguageList.length;i++) {\r
2849             ULocale aLocale = acceptLanguageList[i];\r
2850             boolean[] setFallback = fallback;\r
2851             do {\r
2852                 for(j=0;j<availableLocales.length;j++) {\r
2853                     if(availableLocales[j].equals(aLocale)) {\r
2854                         if(setFallback != null) {\r
2855                             setFallback[0]=false; // first time with this locale - not a fallback.\r
2856                         }\r
2857                         return availableLocales[j];\r
2858                     }\r
2859                 }\r
2860                 Locale loc = aLocale.toLocale();\r
2861 //                Locale parent = LocaleUtility.fallback(loc);\r
2862                 Locale parent = fallback(loc);\r
2863                 if(parent != null) {\r
2864                     aLocale = new ULocale(parent);\r
2865                 } else {\r
2866                     aLocale = null;\r
2867                 }\r
2868                 setFallback = null; // Do not set fallback in later iterations\r
2869             } while (aLocale != null);\r
2870         }\r
2871         return null;\r
2872     }\r
2873 \r
2874     // copied from com.ibm.icu.impl.LocaleUtility\r
2875     private static Locale fallback(Locale loc) {\r
2876 \r
2877         // Split the locale into parts and remove the rightmost part\r
2878         String[] parts = new String[]\r
2879             { loc.getLanguage(), loc.getCountry(), loc.getVariant() };\r
2880         int i;\r
2881         for (i=2; i>=0; --i) {\r
2882             if (parts[i].length() != 0) {\r
2883                 parts[i] = "";\r
2884                 break;\r
2885             }\r
2886         }\r
2887         if (i<0) {\r
2888             return null; // All parts were empty\r
2889         }\r
2890         return new Locale(parts[0], parts[1], parts[2]);\r
2891     }\r
2892 \r
2893     /**\r
2894     * Based on a HTTP formatted list of acceptable locales, determine an available locale for the user.\r
2895     * NullPointerException is thrown if acceptLanguageList or availableLocales is\r
2896     * null.  If fallback is non-null, it will contain true if a fallback locale (one\r
2897     * not in the acceptLanguageList) was returned.  The value on entry is ignored. \r
2898     * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if\r
2899     * if a ROOT locale was used as a fallback (because nothing else in\r
2900     * availableLocales matched).  No ULocale array element should be null; behavior\r
2901     * is undefined if this is the case.\r
2902     * This function will choose a locale from the ULocale.getAvailableLocales() list as available.\r
2903     * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales\r
2904     * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status\r
2905     * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match\r
2906     * @stable ICU 3.4\r
2907     */\r
2908 \r
2909     public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) {\r
2910         return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),\r
2911                                 fallback);\r
2912     }\r
2913 \r
2914    /**\r
2915     * Based on an ordered array of acceptable locales, determine an available locale for the user.\r
2916     * NullPointerException is thrown if acceptLanguageList or availableLocales is\r
2917     * null.  If fallback is non-null, it will contain true if a fallback locale (one\r
2918     * not in the acceptLanguageList) was returned.  The value on entry is ignored. \r
2919     * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if\r
2920     * if a ROOT locale was used as a fallback (because nothing else in\r
2921     * availableLocales matched).  No ULocale array element should be null; behavior\r
2922     * is undefined if this is the case.\r
2923     * This function will choose a locale from the ULocale.getAvailableLocales() list as available.\r
2924     * @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)\r
2925     * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status\r
2926     * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match\r
2927     * @stable ICU 3.4\r
2928     */\r
2929 \r
2930     public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[]\r
2931                                          fallback) {\r
2932         return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(),\r
2933                 fallback);\r
2934     }\r
2935 \r
2936     /**\r
2937      * Package local method used for parsing Accept-Language string\r
2938      * @internal ICU 3.8\r
2939      */\r
2940     static ULocale[] parseAcceptLanguage(String acceptLanguage, boolean isLenient) throws ParseException {\r
2941         /**\r
2942          * @internal ICU 3.4\r
2943          */\r
2944         class ULocaleAcceptLanguageQ implements Comparable {\r
2945             private double q;\r
2946             private double serial;\r
2947             public ULocaleAcceptLanguageQ(double theq, int theserial) {\r
2948                 q = theq;\r
2949                 serial = theserial;\r
2950             }\r
2951             public int compareTo(Object o) {\r
2952                 ULocaleAcceptLanguageQ other = (ULocaleAcceptLanguageQ) o;\r
2953                 if (q > other.q) { // reverse - to sort in descending order\r
2954                     return -1;\r
2955                 } else if (q < other.q) {\r
2956                     return 1;\r
2957                 }\r
2958                 if (serial < other.serial) {\r
2959                     return -1;\r
2960                 } else if (serial > other.serial) {\r
2961                     return 1;\r
2962                 } else {\r
2963                     return 0; // same object\r
2964                 }\r
2965             }\r
2966         }\r
2967 \r
2968         // parse out the acceptLanguage into an array\r
2969         TreeMap map = new TreeMap();\r
2970         StringBuffer languageRangeBuf = new StringBuffer();\r
2971         StringBuffer qvalBuf = new StringBuffer();\r
2972         int state = 0;\r
2973         acceptLanguage += ","; // append comma to simplify the parsing code\r
2974         int n;\r
2975         boolean subTag = false;\r
2976         boolean q1 = false;\r
2977         for (n = 0; n < acceptLanguage.length(); n++) {\r
2978             boolean gotLanguageQ = false;\r
2979             char c = acceptLanguage.charAt(n);\r
2980             switch (state) {\r
2981             case 0: // before language-range start\r
2982                 if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {\r
2983                     // in language-range\r
2984                     languageRangeBuf.append(c);\r
2985                     state = 1;\r
2986                     subTag = false;\r
2987                 } else if (c == '*') {\r
2988                     languageRangeBuf.append(c);\r
2989                     state = 2;\r
2990                 } else if (c != ' ' && c != '\t') {\r
2991                     // invalid character\r
2992                     state = -1;\r
2993                 }\r
2994                 break;\r
2995             case 1: // in language-range\r
2996                 if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {\r
2997                     languageRangeBuf.append(c);\r
2998                 } else if (c == '-') {\r
2999                     subTag = true;\r
3000                     languageRangeBuf.append(c);\r
3001                 } else if (c == '_') {\r
3002                     if (isLenient) {\r
3003                         subTag = true;\r
3004                         languageRangeBuf.append(c);\r
3005                     } else {\r
3006                         state = -1;\r
3007                     }\r
3008                 } else if ('0' <= c && c <= '9') {\r
3009                     if (subTag) {\r
3010                         languageRangeBuf.append(c);                        \r
3011                     } else {\r
3012                         // DIGIT is allowed only in language sub tag\r
3013                         state = -1;\r
3014                     }\r
3015                 } else if (c == ',') {\r
3016                     // language-q end\r
3017                     gotLanguageQ = true;\r
3018                 } else if (c == ' ' || c == '\t') {\r
3019                     // language-range end\r
3020                     state = 3;\r
3021                 } else if (c == ';') {\r
3022                     // before q\r
3023                     state = 4;\r
3024                 } else {\r
3025                     // invalid character for language-range\r
3026                     state = -1;\r
3027                 }\r
3028                 break;\r
3029             case 2: // saw wild card range\r
3030                 if (c == ',') {\r
3031                     // language-q end\r
3032                     gotLanguageQ = true;\r
3033                 } else if (c == ' ' || c == '\t') {\r
3034                     // language-range end\r
3035                     state = 3;\r
3036                 } else if (c == ';') {\r
3037                     // before q\r
3038                     state = 4;\r
3039                 } else {\r
3040                     // invalid\r
3041                     state = -1;\r
3042                 }\r
3043                 break;\r
3044             case 3: // language-range end\r
3045                 if (c == ',') {\r
3046                     // language-q end\r
3047                     gotLanguageQ = true;\r
3048                 } else if (c == ';') {\r
3049                     // before q\r
3050                     state =4;\r
3051                 } else if (c != ' ' && c != '\t') {\r
3052                     // invalid\r
3053                     state = -1;\r
3054                 }\r
3055                 break;\r
3056             case 4: // before q\r
3057                 if (c == 'q') {\r
3058                     // before equal\r
3059                     state = 5;\r
3060                 } else if (c != ' ' && c != '\t') {\r
3061                     // invalid\r
3062                     state = -1;\r
3063                 }\r
3064                 break;\r
3065             case 5: // before equal\r
3066                 if (c == '=') {\r
3067                     // before q value\r
3068                     state = 6;\r
3069                 } else if (c != ' ' && c != '\t') {\r
3070                     // invalid\r
3071                     state = -1;\r
3072                 }\r
3073                 break;\r
3074             case 6: // before q value\r
3075                 if (c == '0') {\r
3076                     // q value start with 0\r
3077                     q1 = false;\r
3078                     qvalBuf.append(c);\r
3079                     state = 7;\r
3080                 } else if (c == '1') {\r
3081                     // q value start with 1\r
3082                     qvalBuf.append(c);\r
3083                     state = 7;\r
3084                 } else if (c == '.') {\r
3085                     if (isLenient) {\r
3086                         qvalBuf.append(c);\r
3087                         state = 8;\r
3088                     } else {\r
3089                         state = -1;\r
3090                     }\r
3091                 } else if (c != ' ' && c != '\t') {\r
3092                     // invalid\r
3093                     state = -1;\r
3094                 }\r
3095                 break;\r
3096             case 7: // q value start\r
3097                 if (c == '.') {\r
3098                     // before q value fraction part\r
3099                     qvalBuf.append(c);\r
3100                     state = 8;\r
3101                 } else if (c == ',') {\r
3102                     // language-q end\r
3103                     gotLanguageQ = true;\r
3104                 } else if (c == ' ' || c == '\t') {\r
3105                     // after q value\r
3106                     state = 10;\r
3107                 } else {\r
3108                     // invalid\r
3109                     state = -1;\r
3110                 }\r
3111                 break;\r
3112             case 8: // before q value fraction part\r
3113                 if ('0' <= c || c <= '9') {\r
3114                     if (q1 && c != '0' && !isLenient) {\r
3115                         // if q value starts with 1, the fraction part must be 0\r
3116                         state = -1;\r
3117                     } else {\r
3118                         // in q value fraction part\r
3119                         qvalBuf.append(c);\r
3120                         state = 9;\r
3121                     }\r
3122                 } else {\r
3123                     // invalid\r
3124                     state = -1;\r
3125                 }\r
3126                 break;\r
3127             case 9: // in q value fraction part\r
3128                 if ('0' <= c && c <= '9') {\r
3129                     if (q1 && c != '0') {\r
3130                         // if q value starts with 1, the fraction part must be 0\r
3131                         state = -1;\r
3132                     } else {\r
3133                         qvalBuf.append(c);\r
3134                     }\r
3135                 } else if (c == ',') {\r
3136                     // language-q end\r
3137                     gotLanguageQ = true;\r
3138                 } else if (c == ' ' || c == '\t') {\r
3139                     // after q value\r
3140                     state = 10;\r
3141                 } else {\r
3142                     // invalid\r
3143                     state = -1;\r
3144                 }\r
3145                 break;\r
3146             case 10: // after q value\r
3147                 if (c == ',') {\r
3148                     // language-q end\r
3149                     gotLanguageQ = true;\r
3150                 } else if (c != ' ' && c != '\t') {\r
3151                     // invalid\r
3152                     state = -1;\r
3153                 }\r
3154                 break;\r
3155             }\r
3156             if (state == -1) {\r
3157                 // error state\r
3158                 throw new ParseException("Invalid Accept-Language", n);\r
3159             }\r
3160             if (gotLanguageQ) {\r
3161                 double q = 1.0;\r
3162                 if (qvalBuf.length() != 0) {\r
3163                     try {\r
3164                         q = Double.parseDouble(qvalBuf.toString());\r
3165                     } catch (NumberFormatException nfe) {\r
3166                         // Already validated, so it should never happen\r
3167                         q = 1.0;\r
3168                     }\r
3169                     if (q > 1.0) {\r
3170                         q = 1.0;\r
3171                     }\r
3172                 }\r
3173                 if (languageRangeBuf.charAt(0) != '*') {\r
3174                     int serial = map.size();\r
3175                     ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q, serial);\r
3176                     map.put(entry, new ULocale(canonicalize(languageRangeBuf.toString()))); // sort in reverse order..   1.0, 0.9, 0.8 .. etc                    \r
3177                 }\r
3178 \r
3179                 // reset buffer and parse state\r
3180                 languageRangeBuf.setLength(0);\r
3181                 qvalBuf.setLength(0);\r
3182                 state = 0;\r
3183             }\r
3184         }\r
3185         if (state != 0) {\r
3186             // Well, the parser should handle all cases.  So just in case.\r
3187             throw new ParseException("Invalid AcceptlLanguage", n);\r
3188         }\r
3189 \r
3190         // pull out the map \r
3191         ULocale acceptList[] = (ULocale[])map.values().toArray(new ULocale[map.size()]);\r
3192         return acceptList;\r
3193     }\r
3194 \r
3195 //    private static final String UNDEFINED_LANGUAGE = "und";\r
3196 //    private static final String UNDEFINED_SCRIPT = "Zzzz";\r
3197 //    private static final String UNDEFINED_REGION = "ZZ";\r
3198 //\r
3199 //    /**\r
3200 //     * Supply most likely subtags to the given locale\r
3201 //     * @param loc The input locale\r
3202 //     * @return A ULocale with most likely subtags filled in.\r
3203 //     * @internal\r
3204 //     * @deprecated This API is ICU internal only.\r
3205 //     */\r
3206 //    public static ULocale addLikelySubtag(ULocale loc) {\r
3207 //        return addLikelySubtags(loc);\r
3208 //    }\r
3209 //\r
3210 //    /**\r
3211 //     * Add the likely subtags for a provided locale ID, per the algorithm described\r
3212 //     * in the following CLDR technical report:\r
3213 //     *\r
3214 //     *   http://www.unicode.org/reports/tr35/#Likely_Subtags\r
3215 //     *\r
3216 //     * If the provided ULocale instance is already in the maximal form, or there is no\r
3217 //     * data available available for maximization, it will be returned.  For example,\r
3218 //     * "und-Zzzz" cannot be maximized, since there is no reasonable maximization.\r
3219 //     * Otherwise, a new ULocale instance with the maximal form is returned.\r
3220 //     * \r
3221 //     * Examples:\r
3222 //     *\r
3223 //     * "en" maximizes to "en_Latn_US"\r
3224 //     *\r
3225 //     * "de" maximizes to "de_Latn_US"\r
3226 //     *\r
3227 //     * "sr" maximizes to "sr_Cyrl_RS"\r
3228 //     *\r
3229 //     * "sh" maximizes to "sr_Latn_RS" (Note this will not reverse.)\r
3230 //     *\r
3231 //     * "zh_Hani" maximizes to "zh_Hans_CN" (Note this will not reverse.)\r
3232 //     *\r
3233 //     * @param loc The ULocale to maximize\r
3234 //     * @return The maximized ULocale instance.\r
3235 //     * @draft ICU 4.0\r
3236 //     * @provisional This API might change or be removed in a future release.\r
3237 //     */\r
3238 //    public static ULocale\r
3239 //    addLikelySubtags(ULocale loc)\r
3240 //    {\r
3241 //        String[] tags = new String[3];\r
3242 //        String trailing = null;\r
3243 //  \r
3244 //        int trailingIndex = parseTagString(\r
3245 //            loc.localeID,\r
3246 //            tags);\r
3247 //\r
3248 //        if (trailingIndex < loc.localeID.length()) {\r
3249 //            trailing = loc.localeID.substring(trailingIndex);\r
3250 //        }\r
3251 //\r
3252 //        String newLocaleID =\r
3253 //            createLikelySubtagsString(\r
3254 //                (String)tags[0],\r
3255 //                (String)tags[1],\r
3256 //                (String)tags[2],\r
3257 //                trailing);\r
3258 //\r
3259 //        return newLocaleID == null ? loc : new ULocale(newLocaleID);\r
3260 //    }\r
3261 //\r
3262 //    /**\r
3263 //     * Minimize the subtags for a provided locale ID, per the algorithm described\r
3264 //     * in the following CLDR technical report:\r
3265 //     *\r
3266 //     *   http://www.unicode.org/reports/tr35/#Likely_Subtags\r
3267 //     *\r
3268 //     * If the provided ULocale instance is already in the minimal form, or there\r
3269 //     * is no data available for minimization, it will be returned.  Since the\r
3270 //     * minimization algorithm relies on proper maximization, see the comments\r
3271 //     * for addLikelySubtags for reasons why there might not be any data.\r
3272 //     *\r
3273 //     * Examples:\r
3274 //     *\r
3275 //     * "en_Latn_US" minimizes to "en"\r
3276 //     *\r
3277 //     * "de_Latn_US" minimizes to "de"\r
3278 //     *\r
3279 //     * "sr_Cyrl_RS" minimizes to "sr"\r
3280 //     *\r
3281 //     * "zh_Hant_TW" minimizes to "zh_TW" (The region is preferred to the\r
3282 //     * script, and minimizing to "zh" would imply "zh_Hans_CN".)\r
3283 //     *\r
3284 //     * @param loc The ULocale to minimize\r
3285 //     * @return The minimized ULocale instance.\r
3286 //     * @draft ICU 4.0\r
3287 //     * @provisional This API might change or be removed in a future release.\r
3288 //     */\r
3289 //    public static ULocale\r
3290 //    minimizeSubtags(ULocale loc)\r
3291 //    {\r
3292 //        String[] tags = new String[3];\r
3293 //\r
3294 //        int trailingIndex = parseTagString(\r
3295 //                loc.localeID,\r
3296 //                tags);\r
3297 //\r
3298 //        String originalLang = (String)tags[0];\r
3299 //        String originalScript = (String)tags[1];\r
3300 //        String originalRegion = (String)tags[2];\r
3301 //        String originalTrailing = null;\r
3302 //\r
3303 //        if (trailingIndex < loc.localeID.length()) {\r
3304 //            /*\r
3305 //             * Create a String that contains everything\r
3306 //             * after the language, script, and region.\r
3307 //             */\r
3308 //            originalTrailing = loc.localeID.substring(trailingIndex);\r
3309 //        }\r
3310 //\r
3311 //        /**\r
3312 //         * First, we need to first get the maximization\r
3313 //         * by adding any likely subtags.\r
3314 //         **/\r
3315 //        String maximizedLocaleID =\r
3316 //            createLikelySubtagsString(\r
3317 //                originalLang,\r
3318 //                originalScript,\r
3319 //                originalRegion,\r
3320 //                null);\r
3321 //\r
3322 //        /**\r
3323 //         * If maximization fails, there's nothing\r
3324 //         * we can do.\r
3325 //         **/\r
3326 //        if (isEmptyString(maximizedLocaleID)) {\r
3327 //            return loc;\r
3328 //        }\r
3329 //        else {\r
3330 //            /**\r
3331 //             * Start first with just the language.\r
3332 //             **/\r
3333 //            String tag =\r
3334 //                createLikelySubtagsString(\r
3335 //                    originalLang,\r
3336 //                    null,\r
3337 //                    null,\r
3338 //                    null);\r
3339 //\r
3340 //            if (tag.equals(maximizedLocaleID)) {\r
3341 //                String newLocaleID =\r
3342 //                    createTagString(\r
3343 //                        originalLang,\r
3344 //                        null,\r
3345 //                        null,\r
3346 //                        originalTrailing);\r
3347 //\r
3348 //                return new ULocale(newLocaleID);\r
3349 //            }\r
3350 //        }\r
3351 //\r
3352 //        /**\r
3353 //         * Next, try the language and region.\r
3354 //         **/\r
3355 //        if (originalRegion.length() != 0) {\r
3356 //\r
3357 //            String tag =\r
3358 //                createLikelySubtagsString(\r
3359 //                    originalLang,\r
3360 //                    null,\r
3361 //                    originalRegion,\r
3362 //                    null);\r
3363 //\r
3364 //            if (tag.equals(maximizedLocaleID)) {\r
3365 //                String newLocaleID =\r
3366 //                    createTagString(\r
3367 //                        originalLang,\r
3368 //                        null,\r
3369 //                        originalRegion,\r
3370 //                        originalTrailing);\r
3371 //\r
3372 //                return new ULocale(newLocaleID);\r
3373 //            }\r
3374 //        }\r
3375 //\r
3376 //        /**\r
3377 //         * Finally, try the language and script.  This is our last chance,\r
3378 //         * since trying with all three subtags would only yield the\r
3379 //         * maximal version that we already have.\r
3380 //         **/\r
3381 //        if (originalRegion.length() != 0 &&\r
3382 //            originalScript.length() != 0) {\r
3383 //\r
3384 //            String tag =\r
3385 //                createLikelySubtagsString(\r
3386 //                    originalLang,\r
3387 //                    originalScript,\r
3388 //                    null,\r
3389 //                    null);\r
3390 //\r
3391 //            if (tag.equals(maximizedLocaleID)) {\r
3392 //                String newLocaleID =\r
3393 //                    createTagString(\r
3394 //                        originalLang,\r
3395 //                        originalScript,\r
3396 //                        null,\r
3397 //                        originalTrailing);\r
3398 //\r
3399 //                return new ULocale(newLocaleID);\r
3400 //            }\r
3401 //        }\r
3402 //\r
3403 //        return loc;\r
3404 //    }\r
3405 //\r
3406 //    /**\r
3407 //     * A trivial utility function that checks for a null\r
3408 //     * reference or checks the length of the supplied String.\r
3409 //     *\r
3410 //     *   @param string The string to check\r
3411 //     *\r
3412 //     *   @return true if the String is empty, or if the reference is null.\r
3413 //     */\r
3414 //    private static boolean isEmptyString(String string) {\r
3415 //      return string == null || string.length() == 0;\r
3416 //    }\r
3417 //    \r
3418 //    /**\r
3419 //     * Append a tag to a StringBuffer, adding the separator if necessary.The tag must\r
3420 //     * not be a zero-length string.\r
3421 //     *\r
3422 //     * @param tag The tag to add.\r
3423 //     * @param buffer The output buffer.\r
3424 //     **/\r
3425 //    private static void\r
3426 //    appendTag(\r
3427 //        String tag,\r
3428 //        StringBuffer buffer) {\r
3429 //    \r
3430 //        if (buffer.length() != 0) {\r
3431 //            buffer.append(UNDERSCORE);\r
3432 //        }\r
3433 //    \r
3434 //        buffer.append(tag);\r
3435 //    }\r
3436 //    \r
3437 //    /**\r
3438 //     * Create a tag string from the supplied parameters.  The lang, script and region\r
3439 //     * parameters may be null references.\r
3440 //     *\r
3441 //     * If any of the language, script or region parameters are empty, and the alternateTags\r
3442 //     * parameter is not null, it will be parsed for potential language, script and region tags\r
3443 //     * to be used when constructing the new tag.  If the alternateTags parameter is null, or\r
3444 //     * it contains no language tag, the default tag for the unknown language is used.\r
3445 //     *\r
3446 //     * @param lang The language tag to use.\r
3447 //     * @param script The script tag to use.\r
3448 //     * @param region The region tag to use.\r
3449 //     * @param trailing Any trailing data to append to the new tag.\r
3450 //     * @param alternateTags A string containing any alternate tags.\r
3451 //     * @return The new tag string.\r
3452 //     **/\r
3453 //    private static String\r
3454 //    createTagString(\r
3455 //        String lang,\r
3456 //        String script,\r
3457 //        String region,\r
3458 //        String trailing,\r
3459 //        String alternateTags) {\r
3460 //\r
3461 //        IDParser parser = null;\r
3462 //        boolean regionAppended = false;\r
3463 //\r
3464 //        StringBuffer tag = new StringBuffer();\r
3465 //    \r
3466 //        if (!isEmptyString(lang)) {\r
3467 //            appendTag(\r
3468 //                lang,\r
3469 //                tag);\r
3470 //        }\r
3471 //        else if (isEmptyString(alternateTags)) {\r
3472 //            /*\r
3473 //             * Append the value for an unknown language, if\r
3474 //             * we found no language.\r
3475 //             */\r
3476 //            appendTag(\r
3477 //                UNDEFINED_LANGUAGE,\r
3478 //                tag);\r
3479 //        }\r
3480 //        else {\r
3481 //            parser = new IDParser(alternateTags);\r
3482 //    \r
3483 //            String alternateLang = parser.getLanguage();\r
3484 //    \r
3485 //            /*\r
3486 //             * Append the value for an unknown language, if\r
3487 //             * we found no language.\r
3488 //             */\r
3489 //            appendTag(\r
3490 //                !isEmptyString(alternateLang) ? alternateLang : UNDEFINED_LANGUAGE,\r
3491 //                tag);\r
3492 //        }\r
3493 //    \r
3494 //        if (!isEmptyString(script)) {\r
3495 //            appendTag(\r
3496 //                script,\r
3497 //                tag);\r
3498 //        }\r
3499 //        else if (!isEmptyString(alternateTags)) {\r
3500 //            /*\r
3501 //             * Parse the alternateTags string for the script.\r
3502 //             */\r
3503 //            if (parser == null) {\r
3504 //                parser = new IDParser(alternateTags);\r
3505 //            }\r
3506 //    \r
3507 //            String alternateScript = parser.getScript();\r
3508 //    \r
3509 //            if (!isEmptyString(alternateScript)) {\r
3510 //                appendTag(\r
3511 //                    alternateScript,\r
3512 //                    tag);\r
3513 //            }\r
3514 //        }\r
3515 //    \r
3516 //        if (!isEmptyString(region)) {\r
3517 //            appendTag(\r
3518 //                region,\r
3519 //                tag);\r
3520 //\r
3521 //            regionAppended = true;\r
3522 //        }\r
3523 //        else if (!isEmptyString(alternateTags)) {\r
3524 //            /*\r
3525 //             * Parse the alternateTags string for the region.\r
3526 //             */\r
3527 //            if (parser == null) {\r
3528 //                parser = new IDParser(alternateTags);\r
3529 //            }\r
3530 //    \r
3531 //            String alternateRegion = parser.getCountry();\r
3532 //    \r
3533 //            if (!isEmptyString(alternateRegion)) {\r
3534 //                appendTag(\r
3535 //                    alternateRegion,\r
3536 //                    tag);\r
3537 //\r
3538 //                regionAppended = true;\r
3539 //            }\r
3540 //        }\r
3541 //    \r
3542 //        if (trailing != null && trailing.length() > 1) {\r
3543 //            /*\r
3544 //             * The current ICU format expects two underscores\r
3545 //             * will separate the variant from the preceeding\r
3546 //             * parts of the tag, if there is no region.\r
3547 //             */\r
3548 //            int separators = 0;\r
3549 //\r
3550 //            if (trailing.charAt(0) == UNDERSCORE) { \r
3551 //                if (trailing.charAt(1) == UNDERSCORE) {\r
3552 //                    separators = 2;\r
3553 //                }\r
3554 //                }\r
3555 //                else {\r
3556 //                    separators = 1;\r
3557 //                }\r
3558 //\r
3559 //            if (regionAppended) {\r
3560 //                /*\r
3561 //                 * If we appended a region, we may need to strip\r
3562 //                 * the extra separator from the variant portion.\r
3563 //                 */\r
3564 //                if (separators == 2) {\r
3565 //                    tag.append(trailing.substring(1));\r
3566 //                }\r
3567 //                else {\r
3568 //                    tag.append(trailing);\r
3569 //                }\r
3570 //            }\r
3571 //            else {\r
3572 //                /*\r
3573 //                 * If we did not append a region, we may need to add\r
3574 //                 * an extra separator to the variant portion.\r
3575 //                 */\r
3576 //                if (separators == 1) {\r
3577 //                    tag.append(UNDERSCORE);\r
3578 //                }\r
3579 //                tag.append(trailing);\r
3580 //            }\r
3581 //        }\r
3582 //    \r
3583 //        return tag.toString();\r
3584 //    }\r
3585 //    \r
3586 //    /**\r
3587 //     * Create a tag string from the supplied parameters.  The lang, script and region\r
3588 //     * parameters may be null references.If the lang parameter is an empty string, the\r
3589 //     * default value for an unknown language is written to the output buffer.\r
3590 //     *\r
3591 //     * @param lang The language tag to use.\r
3592 //     * @param script The script tag to use.\r
3593 //     * @param region The region tag to use.\r
3594 //     * @param trailing Any trailing data to append to the new tag.\r
3595 //     * @return The new String.\r
3596 //     **/\r
3597 //    static String\r
3598 //    createTagString(\r
3599 //            String lang,\r
3600 //            String script,\r
3601 //            String region,\r
3602 //            String trailing) {\r
3603 //    \r
3604 //        return createTagString(\r
3605 //                    lang,\r
3606 //                    script,\r
3607 //                    region,\r
3608 //                    trailing,\r
3609 //                    null);\r
3610 //    }\r
3611 //    \r
3612 //    /**\r
3613 //     * Parse the language, script, and region subtags from a tag string, and return the results.\r
3614 //     *\r
3615 //     * This function does not return the canonical strings for the unknown script and region.\r
3616 //     *\r
3617 //     * @param localeID The locale ID to parse.\r
3618 //     * @param tags An array of three String references to return the subtag strings.\r
3619 //     * @return The number of chars of the localeID parameter consumed.\r
3620 //     **/\r
3621 //    private static int\r
3622 //    parseTagString(\r
3623 //        String localeID,\r
3624 //        String tags[])\r
3625 //    {\r
3626 //        IDParser parser = new IDParser(localeID);\r
3627 //    \r
3628 //        String lang = parser.getLanguage();\r
3629 //        String script = parser.getScript();\r
3630 //        String region = parser.getCountry();\r
3631 //    \r
3632 //        if (isEmptyString(lang)) {\r
3633 //            tags[0] = UNDEFINED_LANGUAGE;\r
3634 //        }\r
3635 //        else {\r
3636 //            tags[0] = lang;\r
3637 //        }\r
3638 //    \r
3639 //        if (script.equals(UNDEFINED_SCRIPT)) {\r
3640 //            tags[1] = "";\r
3641 //        }\r
3642 //        else {\r
3643 //            tags[1] = script;\r
3644 //        }\r
3645 //        \r
3646 //        if (region.equals(UNDEFINED_REGION)) {\r
3647 //            tags[2] = "";\r
3648 //        }\r
3649 //        else {\r
3650 //            tags[2] = region;\r
3651 //        }\r
3652 //    \r
3653 //        /*\r
3654 //         * Search for the variant.  If there is one, then return the index of\r
3655 //         * the preceeding separator.\r
3656 //         * If there's no variant, search for the keyword delimiter,\r
3657 //         * and return its index.  Otherwise, return the length of the\r
3658 //         * string.\r
3659 //         * \r
3660 //         * $TOTO(dbertoni) we need to take into account that we might\r
3661 //         * find a part of the language as the variant, since it can\r
3662 //         * can have a variant portion that is long enough to contain\r
3663 //         * the same characters as the variant. \r
3664 //         */\r
3665 //        String variant = parser.getVariant();\r
3666 //    \r
3667 //        if (!isEmptyString(variant)){\r
3668 //            int index = localeID.indexOf(variant); \r
3669 //\r
3670 //            \r
3671 //            return  index > 0 ? index - 1 : index;\r
3672 //        }\r
3673 //        else\r
3674 //        {\r
3675 //            int index = localeID.indexOf('@');\r
3676 //    \r
3677 //            return index == -1 ? localeID.length() : index;\r
3678 //        }\r
3679 //    }\r
3680 //    \r
3681 //    private static String\r
3682 //    lookupLikelySubtags(String localeId) {\r
3683 //        UResourceBundle bundle =\r
3684 //            UResourceBundle.getBundleInstance(\r
3685 //                    ICUResourceBundle.ICU_BASE_NAME, "likelySubtags");\r
3686 //        try {\r
3687 //            return bundle.getString(localeId);\r
3688 //        }\r
3689 //        catch(MissingResourceException e) {\r
3690 //            return null;\r
3691 //        }\r
3692 //    }\r
3693 //\r
3694 //    private static String\r
3695 //    createLikelySubtagsString(\r
3696 //        String lang,\r
3697 //        String script,\r
3698 //        String region,\r
3699 //        String variants) {\r
3700 //    \r
3701 //        /**\r
3702 //         * Try the language with the script and region first.\r
3703 //         **/\r
3704 //        if (!isEmptyString(script) && !isEmptyString(region)) {\r
3705 //    \r
3706 //            String searchTag =\r
3707 //                createTagString(\r
3708 //                    lang,\r
3709 //                    script,\r
3710 //                    region,\r
3711 //                    null);\r
3712 //    \r
3713 //            String likelySubtags = lookupLikelySubtags(searchTag);\r
3714 //\r
3715 //            /*\r
3716 //            if (likelySubtags == null) {\r
3717 //                if (likelySubtags2 != null) {\r
3718 //                    System.err.println("Tag mismatch: \"(null)\" \"" + likelySubtags2 + "\"");\r
3719 //                }\r
3720 //            }\r
3721 //            else if (likelySubtags2 == null) {\r
3722 //                System.err.println("Tag mismatch: \"" + likelySubtags + "\" \"(null)\"");\r
3723 //            }\r
3724 //            else if (!likelySubtags.equals(likelySubtags2)) {\r
3725 //                System.err.println("Tag mismatch: \"" + likelySubtags + "\" \"" + likelySubtags2 + "\"");\r
3726 //            }\r
3727 //            */\r
3728 //            if (likelySubtags != null) {\r
3729 //                // Always use the language tag from the\r
3730 //                // maximal string, since it may be more\r
3731 //                // specific than the one provided.\r
3732 //                return createTagString(\r
3733 //                            null,\r
3734 //                            null,\r
3735 //                            null,\r
3736 //                            variants,\r
3737 //                            likelySubtags);\r
3738 //            }\r
3739 //        }\r
3740 //    \r
3741 //        /**\r
3742 //         * Try the language with just the script.\r
3743 //         **/\r
3744 //        if (!isEmptyString(script)) {\r
3745 //    \r
3746 //            String searchTag =\r
3747 //                createTagString(\r
3748 //                    lang,\r
3749 //                    script,\r
3750 //                    null,\r
3751 //                    null);\r
3752 //    \r
3753 //            String likelySubtags = lookupLikelySubtags(searchTag);    \r
3754 //            if (likelySubtags != null) {\r
3755 //                // Always use the language tag from the\r
3756 //                // maximal string, since it may be more\r
3757 //                // specific than the one provided.\r
3758 //                return createTagString(\r
3759 //                            null,\r
3760 //                            null,\r
3761 //                            region,\r
3762 //                            variants,\r
3763 //                            likelySubtags);\r
3764 //            }\r
3765 //        }\r
3766 //    \r
3767 //        /**\r
3768 //         * Try the language with just the region.\r
3769 //         **/\r
3770 //        if (!isEmptyString(region)) {\r
3771 //    \r
3772 //            String searchTag =\r
3773 //                createTagString(\r
3774 //                    lang,\r
3775 //                    null,\r
3776 //                    region,\r
3777 //                    null);\r
3778 //    \r
3779 //            String likelySubtags = lookupLikelySubtags(searchTag);    \r
3780 //    \r
3781 //            if (likelySubtags != null) {\r
3782 //                // Always use the language tag from the\r
3783 //                // maximal string, since it may be more\r
3784 //                // specific than the one provided.\r
3785 //                return createTagString(\r
3786 //                            null,\r
3787 //                            script,\r
3788 //                            null,\r
3789 //                            variants,\r
3790 //                            likelySubtags);\r
3791 //            }\r
3792 //        }\r
3793 //    \r
3794 //        /**\r
3795 //         * Finally, try just the language.\r
3796 //         **/\r
3797 //        {\r
3798 //            String searchTag =\r
3799 //                createTagString(\r
3800 //                    lang,\r
3801 //                    null,\r
3802 //                    null,\r
3803 //                    null);\r
3804 //    \r
3805 //            String likelySubtags = lookupLikelySubtags(searchTag);    \r
3806 //  \r
3807 //            if (likelySubtags != null) {\r
3808 //                // Always use the language tag from the\r
3809 //                // maximal string, since it may be more\r
3810 //                // specific than the one provided.\r
3811 //                return createTagString(\r
3812 //                            null,\r
3813 //                            script,\r
3814 //                            region,\r
3815 //                            variants,\r
3816 //                            likelySubtags);\r
3817 //            }\r
3818 //        }\r
3819 //    \r
3820 //        return null;\r
3821 //    }\r
3822 }\r