0001: /*
0002: ******************************************************************************
0003: * Copyright (C) 2003-2006, International Business Machines Corporation and *
0004: * others. All Rights Reserved. *
0005: ******************************************************************************
0006: */
0007:
0008: package com.ibm.icu.util;
0009:
0010: import java.io.Serializable;
0011: import java.util.Collections;
0012: import java.util.Comparator;
0013: import java.util.HashMap;
0014: import java.util.Iterator;
0015: import java.util.Locale;
0016: import java.util.Map;
0017: import java.util.MissingResourceException;
0018: import java.util.TreeMap;
0019: import java.lang.ref.SoftReference;
0020:
0021: import com.ibm.icu.impl.LocaleUtility;
0022: import com.ibm.icu.impl.ICUResourceBundle;
0023: import com.ibm.icu.lang.UCharacter;
0024:
0025: /**
0026: * A class analogous to {@link java.util.Locale} that provides additional
0027: * support for ICU protocol. In ICU 3.0 this class is enhanced to support
0028: * RFC 3066 language identifiers.
0029: *
0030: * <p>Many classes and services in ICU follow a factory idiom, in
0031: * which a factory method or object responds to a client request with
0032: * an object. The request includes a locale (the <i>requested</i>
0033: * locale), and the returned object is constructed using data for that
0034: * locale. The system may lack data for the requested locale, in
0035: * which case the locale fallback mechanism will be invoked until a
0036: * populated locale is found (the <i>valid</i> locale). Furthermore,
0037: * even when a populated locale is found (the <i>valid</i> locale),
0038: * further fallback may be required to reach a locale containing the
0039: * specific data required by the service (the <i>actual</i> locale).
0040: *
0041: * <p>ULocale performs <b>'normalization'</b> and <b>'canonicalization'</b> of locale ids.
0042: * Normalization 'cleans up' ICU locale ids as follows:
0043: * <ul>
0044: * <li>language, script, country, variant, and keywords are properly cased<br>
0045: * (lower, title, upper, upper, and lower case respectively)</li>
0046: * <li>hyphens used as separators are converted to underscores</li>
0047: * <li>three-letter language and country ids are converted to two-letter
0048: * equivalents where available</li>
0049: * <li>surrounding spaces are removed from keywords and values</li>
0050: * <li>if there are multiple keywords, they are put in sorted order</li>
0051: * </ul>
0052: * Canonicalization additionally performs the following:
0053: * <ul>
0054: * <li>POSIX ids are converted to ICU format IDs</li>
0055: * <li>'grandfathered' 3066 ids are converted to ICU standard form</li>
0056: * <li>'PREEURO' and 'EURO' variants are converted to currency keyword form, with the currency
0057: * id appropriate to the country of the locale (for PREEURO) or EUR (for EURO).
0058: * </ul>
0059: * All ULocale constructors automatically normalize the locale id. To handle
0060: * POSIX ids, <code>canonicalize</code> can be called to convert the id
0061: * to canonical form, or the <code>canonicalInstance</code> factory method
0062: * can be called.</p>
0063: *
0064: * <p>This class provides selectors {@link #VALID_LOCALE} and {@link
0065: * #ACTUAL_LOCALE} intended for use in methods named
0066: * <tt>getLocale()</tt>. These methods exist in several ICU classes,
0067: * including {@link com.ibm.icu.util.Calendar}, {@link
0068: * com.ibm.icu.util.Currency}, {@link com.ibm.icu.text.UFormat},
0069: * {@link com.ibm.icu.text.BreakIterator}, {@link
0070: * com.ibm.icu.text.Collator}, {@link
0071: * com.ibm.icu.text.DateFormatSymbols}, and {@link
0072: * com.ibm.icu.text.DecimalFormatSymbols} and their subclasses, if
0073: * any. Once an object of one of these classes has been created,
0074: * <tt>getLocale()</tt> may be called on it to determine the valid and
0075: * actual locale arrived at during the object's construction.
0076: *
0077: * <p>Note: The <tt>getLocale()</tt> method will be implemented in ICU
0078: * 3.0; ICU 2.8 contains a partial preview implementation. The
0079: * <i>actual</i> locale is returned correctly, but the <i>valid</i>
0080: * locale is not, in most cases.
0081: *
0082: * @see java.util.Locale
0083: * @author weiv
0084: * @author Alan Liu
0085: * @author Ram Viswanadha
0086: * @stable ICU 2.8
0087: */
0088: public final class ULocale implements Serializable {
0089: // using serialver from jdk1.4.2_05
0090: private static final long serialVersionUID = 3715177670352309217L;
0091:
0092: /**
0093: * Useful constant for language.
0094: * @stable ICU 3.0
0095: */
0096: public static final ULocale ENGLISH = new ULocale("en",
0097: Locale.ENGLISH);
0098:
0099: /**
0100: * Useful constant for language.
0101: * @stable ICU 3.0
0102: */
0103: public static final ULocale FRENCH = new ULocale("fr",
0104: Locale.FRENCH);
0105:
0106: /**
0107: * Useful constant for language.
0108: * @stable ICU 3.0
0109: */
0110: public static final ULocale GERMAN = new ULocale("de",
0111: Locale.GERMAN);
0112:
0113: /**
0114: * Useful constant for language.
0115: * @stable ICU 3.0
0116: */
0117: public static final ULocale ITALIAN = new ULocale("it",
0118: Locale.ITALIAN);
0119:
0120: /**
0121: * Useful constant for language.
0122: * @stable ICU 3.0
0123: */
0124: public static final ULocale JAPANESE = new ULocale("ja",
0125: Locale.JAPANESE);
0126:
0127: /**
0128: * Useful constant for language.
0129: * @stable ICU 3.0
0130: */
0131: public static final ULocale KOREAN = new ULocale("ko",
0132: Locale.KOREAN);
0133:
0134: /**
0135: * Useful constant for language.
0136: * @stable ICU 3.0
0137: */
0138: public static final ULocale CHINESE = new ULocale("zh",
0139: Locale.CHINESE);
0140:
0141: /**
0142: * Useful constant for language.
0143: * @stable ICU 3.0
0144: */
0145: public static final ULocale SIMPLIFIED_CHINESE = new ULocale(
0146: "zh_Hans", Locale.CHINESE);
0147:
0148: /**
0149: * Useful constant for language.
0150: * @stable ICU 3.0
0151: */
0152: public static final ULocale TRADITIONAL_CHINESE = new ULocale(
0153: "zh_Hant", Locale.CHINESE);
0154:
0155: /**
0156: * Useful constant for country/region.
0157: * @stable ICU 3.0
0158: */
0159: public static final ULocale FRANCE = new ULocale("fr_FR",
0160: Locale.FRANCE);
0161:
0162: /**
0163: * Useful constant for country/region.
0164: * @stable ICU 3.0
0165: */
0166: public static final ULocale GERMANY = new ULocale("de_DE",
0167: Locale.GERMANY);
0168:
0169: /**
0170: * Useful constant for country/region.
0171: * @stable ICU 3.0
0172: */
0173: public static final ULocale ITALY = new ULocale("it_IT",
0174: Locale.ITALY);
0175:
0176: /**
0177: * Useful constant for country/region.
0178: * @stable ICU 3.0
0179: */
0180: public static final ULocale JAPAN = new ULocale("ja_JP",
0181: Locale.JAPAN);
0182:
0183: /**
0184: * Useful constant for country/region.
0185: * @stable ICU 3.0
0186: */
0187: public static final ULocale KOREA = new ULocale("ko_KR",
0188: Locale.KOREA);
0189:
0190: /**
0191: * Useful constant for country/region.
0192: * @stable ICU 3.0
0193: */
0194: public static final ULocale CHINA = new ULocale("zh_Hans_CN",
0195: Locale.CHINA);
0196:
0197: /**
0198: * Useful constant for country/region.
0199: * @stable ICU 3.0
0200: */
0201: public static final ULocale PRC = CHINA;
0202:
0203: /**
0204: * Useful constant for country/region.
0205: * @stable ICU 3.0
0206: */
0207: public static final ULocale TAIWAN = new ULocale("zh_Hant_TW",
0208: Locale.TAIWAN);
0209:
0210: /**
0211: * Useful constant for country/region.
0212: * @stable ICU 3.0
0213: */
0214: public static final ULocale UK = new ULocale("en_GB", Locale.UK);
0215:
0216: /**
0217: * Useful constant for country/region.
0218: * @stable ICU 3.0
0219: */
0220: public static final ULocale US = new ULocale("en_US", Locale.US);
0221:
0222: /**
0223: * Useful constant for country/region.
0224: * @stable ICU 3.0
0225: */
0226: public static final ULocale CANADA = new ULocale("en_CA",
0227: Locale.CANADA);
0228:
0229: /**
0230: * Useful constant for country/region.
0231: * @stable ICU 3.0
0232: */
0233: public static final ULocale CANADA_FRENCH = new ULocale("fr_CA",
0234: Locale.CANADA_FRENCH);
0235:
0236: /**
0237: * Handy constant.
0238: */
0239: private static final String EMPTY_STRING = "";
0240:
0241: // Used in both ULocale and IDParser, so moved up here.
0242: private static final char UNDERSCORE = '_';
0243:
0244: // default empty locale
0245: private static final Locale EMPTY_LOCALE = new Locale("", "");
0246:
0247: /**
0248: * The root ULocale.
0249: * @stable ICU 2.8
0250: */
0251: public static final ULocale ROOT = new ULocale("root", EMPTY_LOCALE);
0252:
0253: private static final HashMap CACHE = new HashMap(20);
0254: static {
0255: CACHE.put(EMPTY_LOCALE, ROOT);
0256: CACHE.put(Locale.ENGLISH, ENGLISH);
0257: CACHE.put(Locale.FRENCH, FRENCH);
0258: CACHE.put(Locale.GERMAN, GERMAN);
0259: CACHE.put(Locale.ITALIAN, ITALIAN);
0260: CACHE.put(Locale.JAPANESE, JAPANESE);
0261: CACHE.put(Locale.KOREAN, KOREAN);
0262: CACHE.put(Locale.CHINESE, CHINESE);
0263: CACHE.put(Locale.SIMPLIFIED_CHINESE, SIMPLIFIED_CHINESE);
0264: CACHE.put(Locale.TRADITIONAL_CHINESE, TRADITIONAL_CHINESE);
0265: CACHE.put(Locale.FRANCE, FRANCE);
0266: CACHE.put(Locale.GERMANY, GERMANY);
0267: CACHE.put(Locale.ITALY, ITALY);
0268: CACHE.put(Locale.JAPAN, JAPAN);
0269: CACHE.put(Locale.KOREA, KOREA);
0270: CACHE.put(Locale.CHINA, CHINA);
0271: CACHE.put(Locale.TAIWAN, TAIWAN);
0272: CACHE.put(Locale.UK, UK);
0273: CACHE.put(Locale.US, US);
0274: CACHE.put(Locale.CANADA, CANADA);
0275: CACHE.put(Locale.CANADA_FRENCH, CANADA_FRENCH);
0276: }
0277:
0278: /**
0279: * Cache the locale.
0280: */
0281: private transient Locale locale;
0282:
0283: /**
0284: * The raw localeID that we were passed in.
0285: */
0286: private String localeID;
0287:
0288: /**
0289: * Tables used in normalizing portions of the id.
0290: */
0291: /* tables updated per http://lcweb.loc.gov/standards/iso639-2/
0292: to include the revisions up to 2001/7/27 *CWB*/
0293: /* The 3 character codes are the terminology codes like RFC 3066.
0294: This is compatible with prior ICU codes */
0295: /* "in" "iw" "ji" "jw" & "sh" have been withdrawn but are still in
0296: the table but now at the end of the table because
0297: 3 character codes are duplicates. This avoids bad searches
0298: going from 3 to 2 character codes.*/
0299: /* The range qaa-qtz is reserved for local use. */
0300:
0301: private static String[] _languages;
0302: private static String[] _replacementLanguages;
0303: private static String[] _obsoleteLanguages;
0304: private static String[] _languages3;
0305: private static String[] _obsoleteLanguages3;
0306:
0307: // Avoid initializing languages tables unless we have to.
0308: private static void initLanguageTables() {
0309: if (_languages == null) {
0310:
0311: /* This list MUST be in sorted order, and MUST contain the two-letter codes
0312: if one exists otherwise use the three letter code */
0313: String[] tempLanguages = { "aa", "ab", "ace", "ach", "ada",
0314: "ady", "ae", "af", "afa", "afh", "ak", "akk",
0315: "ale", "alg", "am", "an", "ang", "apa", "ar",
0316: "arc", "arn", "arp", "art", "arw", "as", "ast",
0317: "ath", "aus", "av", "awa", "ay", "az", "ba", "bad",
0318: "bai", "bal", "ban", "bas", "bat", "be", "bej",
0319: "bem", "ber", "bg", "bh", "bho", "bi", "bik",
0320: "bin", "bla", "bm", "bn", "bnt", "bo", "br", "bra",
0321: "bs", "btk", "bua", "bug", "byn", "ca", "cad",
0322: "cai", "car", "cau", "ce", "ceb", "cel", "ch",
0323: "chb", "chg", "chk", "chm", "chn", "cho", "chp",
0324: "chr", "chy", "cmc", "co", "cop", "cpe", "cpf",
0325: "cpp", "cr", "crh", "crp", "cs", "csb", "cu",
0326: "cus", "cv", "cy", "da", "dak", "dar", "day", "de",
0327: "del", "den", "dgr", "din", "doi", "dra", "dsb",
0328: "dua", "dum", "dv", "dyu", "dz", "ee", "efi",
0329: "egy", "eka", "el", "elx", "en", "enm", "eo", "es",
0330: "et", "eu", "ewo", "fa", "fan", "fat", "ff", "fi",
0331: "fiu", "fj", "fo", "fon", "fr", "frm", "fro",
0332: "fur", "fy", "ga", "gaa", "gay", "gba", "gd",
0333: "gem", "gez", "gil", "gl", "gmh", "gn", "goh",
0334: "gon", "gor", "got", "grb", "grc", "gu", "gv",
0335: "gwi", "ha", "hai", "haw", "he", "hi", "hil",
0336: "him", "hit", "hmn", "ho", "hr", "hsb", "ht", "hu",
0337: "hup", "hy", "hz", "ia", "iba", "id", "ie", "ig",
0338: "ii", "ijo", "ik", "ilo", "inc", "ine", "inh",
0339: "io", "ira", "iro", "is", "it", "iu", "ja", "jbo",
0340: "jpr", "jrb", "jv", "ka", "kaa", "kab", "kac",
0341: "kam", "kar", "kaw", "kbd", "kg", "kha", "khi",
0342: "kho", "ki", "kj", "kk", "kl", "km", "kmb", "kn",
0343: "ko", "kok", "kos", "kpe", "kr", "krc", "kro",
0344: "kru", "ks", "ku", "kum", "kut", "kv", "kw", "ky",
0345: "la", "lad", "lah", "lam", "lb", "lez", "lg", "li",
0346: "ln", "lo", "lol", "loz", "lt", "lu", "lua", "lui",
0347: "lun", "luo", "lus", "lv", "mad", "mag", "mai",
0348: "mak", "man", "map", "mas", "mdf", "mdr", "men",
0349: "mg", "mga", "mh", "mi", "mic", "min", "mis", "mk",
0350: "mkh", "ml", "mn", "mnc", "mni", "mno", "mo",
0351: "moh", "mos", "mr", "ms", "mt", "mul", "mun",
0352: "mus", "mwr", "my", "myn", "myv", "na", "nah",
0353: "nai", "nap", "nb", "nd", "nds", "ne", "new", "ng",
0354: "nia", "nic", "niu", "nl", "nn", "no", "nog",
0355: "non", "nr", "nso", "nub", "nv", "nwc", "ny",
0356: "nym", "nyn", "nyo", "nzi", "oc", "oj", "om", "or",
0357: "os", "osa", "ota", "oto", "pa", "paa", "pag",
0358: "pal", "pam", "pap", "pau", "peo", "phi", "phn",
0359: "pi", "pl", "pon", "pra", "pro", "ps", "pt", "qu",
0360: "raj", "rap", "rar", "rm", "rn", "ro", "roa",
0361: "rom", "ru", "rup", "rw", "sa", "sad", "sah",
0362: "sai", "sal", "sam", "sas", "sat", "sc", "sco",
0363: "sd", "se", "sel", "sem", "sg", "sga", "sgn",
0364: "shn", "si", "sid", "sio", "sit", "sk", "sl",
0365: "sla", "sm", "sma", "smi", "smj", "smn", "sms",
0366: "sn", "snk", "so", "sog", "son", "sq", "sr", "srr",
0367: "ss", "ssa", "st", "su", "suk", "sus", "sux", "sv",
0368: "sw", "syr", "ta", "tai", "te", "tem", "ter",
0369: "tet", "tg", "th", "ti", "tig", "tiv", "tk", "tkl",
0370: "tl", "tlh", "tli", "tmh", "tn", "to", "tog",
0371: "tpi", "tr", "ts", "tsi", "tt", "tum", "tup",
0372: "tut", "tvl", "tw", "ty", "tyv", "udm", "ug",
0373: "uga", "uk", "umb", "und", "ur", "uz", "vai", "ve",
0374: "vi", "vo", "vot", "wa", "wak", "wal", "war",
0375: "was", "wen", "wo", "xal", "xh", "yao", "yap",
0376: "yi", "yo", "ypk", "za", "zap", "zen", "zh", "znd",
0377: "zu", "zun", };
0378:
0379: String[] tempReplacementLanguages = { "id", "he", "yi",
0380: "jv", "sr", "nb",/* replacement language codes */
0381: };
0382:
0383: String[] tempObsoleteLanguages = { "in", "iw", "ji", "jw",
0384: "sh", "no", /* obsolete language codes */
0385: };
0386:
0387: /* This list MUST contain a three-letter code for every two-letter code in the
0388: list above, and they MUST ne in the same order (i.e., the same language must
0389: be in the same place in both lists)! */
0390: String[] tempLanguages3 = {
0391: /*"aa", "ab", "ace", "ach", "ada", "ady", "ae", "af", "afa", */
0392: "aar", "abk", "ace", "ach", "ada", "ady", "ave", "afr",
0393: "afa",
0394: /*"afh", "ak", "akk", "ale", "alg", "am", "an", "ang", "apa", */
0395: "afh", "aka", "akk", "ale", "alg", "amh", "arg",
0396: "ang", "apa",
0397: /*"ar", "arc", "arn", "arp", "art", "arw", "as", "ast", */
0398: "ara", "arc", "arn", "arp", "art", "arw", "asm",
0399: "ast",
0400: /*"ath", "aus", "av", "awa", "ay", "az", "ba", "bad", */
0401: "ath", "aus", "ava", "awa", "aym", "aze", "bak",
0402: "bad",
0403: /*"bai", "bal", "ban", "bas", "bat", "be", "bej", */
0404: "bai", "bal", "ban", "bas", "bat", "bel", "bej",
0405: /*"bem", "ber", "bg", "bh", "bho", "bi", "bik", "bin", */
0406: "bem", "ber", "bul", "bih", "bho", "bis", "bik",
0407: "bin",
0408: /*"bla", "bm", "bn", "bnt", "bo", "br", "bra", "bs", */
0409: "bla", "bam", "ben", "bnt", "bod", "bre", "bra",
0410: "bos",
0411: /*"btk", "bua", "bug", "byn", "ca", "cad", "cai", "car", "cau", */
0412: "btk", "bua", "bug", "byn", "cat", "cad", "cai",
0413: "car", "cau",
0414: /*"ce", "ceb", "cel", "ch", "chb", "chg", "chk", "chm", */
0415: "che", "ceb", "cel", "cha", "chb", "chg", "chk",
0416: "chm",
0417: /*"chn", "cho", "chp", "chr", "chy", "cmc", "co", "cop", */
0418: "chn", "cho", "chp", "chr", "chy", "cmc", "cos",
0419: "cop",
0420: /*"cpe", "cpf", "cpp", "cr", "crh", "crp", "cs", "csb", "cu", "cus", */
0421: "cpe", "cpf", "cpp", "cre", "crh", "crp", "ces",
0422: "csb", "chu", "cus",
0423: /*"cv", "cy", "da", "dak", "dar", "day", "de", "del", "den", */
0424: "chv", "cym", "dan", "dak", "dar", "day", "deu",
0425: "del", "den",
0426: /*"dgr", "din", "doi", "dra", "dsb", "dua", "dum", "dv", "dyu", */
0427: "dgr", "din", "doi", "dra", "dsb", "dua", "dum",
0428: "div", "dyu",
0429: /*"dz", "ee", "efi", "egy", "eka", "el", "elx", "en", */
0430: "dzo", "ewe", "efi", "egy", "eka", "ell", "elx",
0431: "eng",
0432: /*"enm", "eo", "es", "et", "eu", "ewo", "fa", */
0433: "enm", "epo", "spa", "est", "eus", "ewo", "fas",
0434: /*"fan", "fat", "ff", "fi", "fiu", "fj", "fo", "fon", */
0435: "fan", "fat", "ful", "fin", "fiu", "fij", "fao",
0436: "fon",
0437: /*"fr", "frm", "fro", "fur", "fy", "ga", "gaa", "gay", */
0438: "fra", "frm", "fro", "fur", "fry", "gle", "gaa",
0439: "gay",
0440: /*"gba", "gd", "gem", "gez", "gil", "gl", "gmh", "gn", */
0441: "gba", "gla", "gem", "gez", "gil", "glg", "gmh",
0442: "grn",
0443: /*"goh", "gon", "gor", "got", "grb", "grc", "gu", "gv", */
0444: "goh", "gon", "gor", "got", "grb", "grc", "guj",
0445: "glv",
0446: /*"gwi", "ha", "hai", "haw", "he", "hi", "hil", "him", */
0447: "gwi", "hau", "hai", "haw", "heb", "hin", "hil",
0448: "him",
0449: /*"hit", "hmn", "ho", "hr", "hsb", "ht", "hu", "hup", "hy", "hz", */
0450: "hit", "hmn", "hmo", "hrv", "hsb", "hat", "hun",
0451: "hup", "hye", "her",
0452: /*"ia", "iba", "id", "ie", "ig", "ii", "ijo", "ik", */
0453: "ina", "iba", "ind", "ile", "ibo", "iii", "ijo",
0454: "ipk",
0455: /*"ilo", "inc", "ine", "inh", "io", "ira", "iro", "is", "it", */
0456: "ilo", "inc", "ine", "inh", "ido", "ira", "iro",
0457: "isl", "ita",
0458: /*"iu", "ja", "jbo", "jpr", "jrb", "jv", "ka", "kaa", "kab", */
0459: "iku", "jpn", "jbo", "jpr", "jrb", "jaw", "kat",
0460: "kaa", "kab",
0461: /*"kac", "kam", "kar", "kaw", "kbd", "kg", "kha", "khi", */
0462: "kac", "kam", "kar", "kaw", "kbd", "kon", "kha",
0463: "khi",
0464: /*"kho", "ki", "kj", "kk", "kl", "km", "kmb", "kn", */
0465: "kho", "kik", "kua", "kaz", "kal", "khm", "kmb",
0466: "kan",
0467: /*"ko", "kok", "kos", "kpe", "kr", "krc", "kro", "kru", "ks", */
0468: "kor", "kok", "kos", "kpe", "kau", "krc", "kro",
0469: "kru", "kas",
0470: /*"ku", "kum", "kut", "kv", "kw", "ky", "la", "lad", */
0471: "kur", "kum", "kut", "kom", "cor", "kir", "lat",
0472: "lad",
0473: /*"lah", "lam", "lb", "lez", "lg", "li", "ln", "lo", "lol", */
0474: "lah", "lam", "ltz", "lez", "lug", "lim", "lin",
0475: "lao", "lol",
0476: /*"loz", "lt", "lu", "lua", "lui", "lun", "luo", "lus", */
0477: "loz", "lit", "lub", "lua", "lui", "lun", "luo",
0478: "lus",
0479: /*"lv", "mad", "mag", "mai", "mak", "man", "map", "mas", */
0480: "lav", "mad", "mag", "mai", "mak", "man", "map",
0481: "mas",
0482: /*"mdf", "mdr", "men", "mg", "mga", "mh", "mi", "mic", "min", */
0483: "mdf", "mdr", "men", "mlg", "mga", "mah", "mri",
0484: "mic", "min",
0485: /*"mis", "mk", "mkh", "ml", "mn", "mnc", "mni", "mno", */
0486: "mis", "mkd", "mkh", "mal", "mon", "mnc", "mni",
0487: "mno",
0488: /*"mo", "moh", "mos", "mr", "ms", "mt", "mul", "mun", */
0489: "mol", "moh", "mos", "mar", "msa", "mlt", "mul",
0490: "mun",
0491: /*"mus", "mwr", "my", "myn", "myv", "na", "nah", "nai", "nap", */
0492: "mus", "mwr", "mya", "myn", "myv", "nau", "nah",
0493: "nai", "nap",
0494: /*"nb", "nd", "nds", "ne", "new", "ng", "nia", "nic", */
0495: "nob", "nde", "nds", "nep", "new", "ndo", "nia",
0496: "nic",
0497: /*"niu", "nl", "nn", "no", "nog", "non", "nr", "nso", "nub", */
0498: "niu", "nld", "nno", "nor", "nog", "non", "nbl",
0499: "nso", "nub",
0500: /*"nv", "nwc", "ny", "nym", "nyn", "nyo", "nzi", "oc", "oj", */
0501: "nav", "nwc", "nya", "nym", "nyn", "nyo", "nzi",
0502: "oci", "oji",
0503: /*"om", "or", "os", "osa", "ota", "oto", "pa", "paa", */
0504: "orm", "ori", "oss", "osa", "ota", "oto", "pan",
0505: "paa",
0506: /*"pag", "pal", "pam", "pap", "pau", "peo", "phi", "phn", */
0507: "pag", "pal", "pam", "pap", "pau", "peo", "phi",
0508: "phn",
0509: /*"pi", "pl", "pon", "pra", "pro", "ps", "pt", "qu", */
0510: "pli", "pol", "pon", "pra", "pro", "pus", "por",
0511: "que",
0512: /*"raj", "rap", "rar", "rm", "rn", "ro", "roa", "rom", */
0513: "raj", "rap", "rar", "roh", "run", "ron", "roa",
0514: "rom",
0515: /*"ru", "rup", "rw", "sa", "sad", "sah", "sai", "sal", "sam", */
0516: "rus", "rup", "kin", "san", "sad", "sah", "sai",
0517: "sal", "sam",
0518: /*"sas", "sat", "sc", "sco", "sd", "se", "sel", "sem", */
0519: "sas", "sat", "srd", "sco", "snd", "sme", "sel",
0520: "sem",
0521: /*"sg", "sga", "sgn", "shn", "si", "sid", "sio", "sit", */
0522: "sag", "sga", "sgn", "shn", "sin", "sid", "sio",
0523: "sit",
0524: /*"sk", "sl", "sla", "sm", "sma", "smi", "smj", "smn", */
0525: "slk", "slv", "sla", "smo", "sma", "smi", "smj",
0526: "smn",
0527: /*"sms", "sn", "snk", "so", "sog", "son", "sq", "sr", */
0528: "sms", "sna", "snk", "som", "sog", "son", "sqi",
0529: "srp",
0530: /*"srr", "ss", "ssa", "st", "su", "suk", "sus", "sux", */
0531: "srr", "ssw", "ssa", "sot", "sun", "suk", "sus",
0532: "sux",
0533: /*"sv", "sw", "syr", "ta", "tai", "te", "tem", "ter", */
0534: "swe", "swa", "syr", "tam", "tai", "tel", "tem",
0535: "ter",
0536: /*"tet", "tg", "th", "ti", "tig", "tiv", "tk", "tkl", */
0537: "tet", "tgk", "tha", "tir", "tig", "tiv", "tuk",
0538: "tkl",
0539: /*"tl", "tlh", "tli", "tmh", "tn", "to", "tog", "tpi", "tr", */
0540: "tgl", "tlh", "tli", "tmh", "tsn", "ton", "tog",
0541: "tpi", "tur",
0542: /*"ts", "tsi", "tt", "tum", "tup", "tut", "tvl", "tw", */
0543: "tso", "tsi", "tat", "tum", "tup", "tut", "tvl",
0544: "twi",
0545: /*"ty", "tyv", "udm", "ug", "uga", "uk", "umb", "und", "ur", */
0546: "tah", "tyv", "udm", "uig", "uga", "ukr", "umb",
0547: "und", "urd",
0548: /*"uz", "vai", "ve", "vi", "vo", "vot", "wa", "wak", */
0549: "uzb", "vai", "ven", "vie", "vol", "vot", "wln",
0550: "wak",
0551: /*"wal", "war", "was", "wen", "wo", "xal", "xh", "yao", "yap", */
0552: "wal", "war", "was", "wen", "wol", "xal", "xho",
0553: "yao", "yap",
0554: /*"yi", "yo", "ypk", "za", "zap", "zen", "zh", "znd", */
0555: "yid", "yor", "ypk", "zha", "zap", "zen", "zho",
0556: "znd",
0557: /*"zu", "zun", */
0558: "zul", "zun", };
0559:
0560: String[] tempObsoleteLanguages3 = {
0561: /* "in", "iw", "ji", "jw", "sh", */
0562: "ind", "heb", "yid", "jaw", "srp", };
0563:
0564: synchronized (ULocale.class) {
0565: if (_languages == null) {
0566: _languages = tempLanguages;
0567: _replacementLanguages = tempReplacementLanguages;
0568: _obsoleteLanguages = tempObsoleteLanguages;
0569: _languages3 = tempLanguages3;
0570: _obsoleteLanguages3 = tempObsoleteLanguages3;
0571: }
0572: }
0573: }
0574: }
0575:
0576: private static String[] _countries;
0577: private static String[] _deprecatedCountries;
0578: private static String[] _replacementCountries;
0579: private static String[] _obsoleteCountries;
0580: private static String[] _countries3;
0581: private static String[] _obsoleteCountries3;
0582:
0583: // Avoid initializing country tables unless we have to.
0584: private static void initCountryTables() {
0585: if (_countries == null) {
0586: /* ZR(ZAR) is now CD(COD) and FX(FXX) is PS(PSE) as per
0587: http://www.evertype.com/standards/iso3166/iso3166-1-en.html
0588: added new codes keeping the old ones for compatibility
0589: updated to include 1999/12/03 revisions *CWB*/
0590:
0591: /* RO(ROM) is now RO(ROU) according to
0592: http://www.iso.org/iso/en/prods-services/iso3166ma/03updates-on-iso-3166/nlv3e-rou.html
0593: */
0594:
0595: /* This list MUST be in sorted order, and MUST contain only two-letter codes! */
0596: String[] tempCountries = { "AD", "AE", "AF", "AG", "AI",
0597: "AL", "AM", "AN", "AO", "AQ", "AR", "AS", "AT",
0598: "AU", "AW", "AZ", "BA", "BB", "BD", "BE", "BF",
0599: "BG", "BH", "BI", "BJ", "BM", "BN", "BO", "BR",
0600: "BS", "BT", "BV", "BW", "BY", "BZ", "CA", "CC",
0601: "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM",
0602: "CN", "CO", "CR", "CU", "CV", "CX", "CY", "CZ",
0603: "DE", "DJ", "DK", "DM", "DO", "DZ", "EC", "EE",
0604: "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK",
0605: "FM", "FO", "FR", "GA", "GB", "GD", "GE", "GF",
0606: "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR",
0607: "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN",
0608: "HR", "HT", "HU", "ID", "IE", "IL", "IN", "IO",
0609: "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE",
0610: "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW",
0611: "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR",
0612: "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD",
0613: "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP",
0614: "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX",
0615: "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI",
0616: "NL", "NO", "NP", "NR", "NU", "NZ", "OM", "PA",
0617: "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN",
0618: "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO",
0619: "RU", "RW", "SA", "SB", "SC", "SD", "SE", "SG",
0620: "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO",
0621: "SR", "ST", "SV", "SY", "SZ", "TC", "TD", "TF",
0622: "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO",
0623: "TR", "TT", "TV", "TW", "TZ", "UA", "UG", "UM",
0624: "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI",
0625: "VN", "VU", "WF", "WS", "YE", "YT", "YU", "ZA",
0626: "ZM", "ZW", };
0627:
0628: /* this table is used for 3 letter codes */
0629: String[] tempObsoleteCountries = { "FX", "RO", "TP", "ZR", /* obsolete country codes */
0630: };
0631:
0632: String[] tempDeprecatedCountries = { "BU", "DY", "FX",
0633: "HV", "NH", "RH", "TP", "YU", "ZR" /* deprecated country list */
0634: };
0635: String[] tempReplacementCountries = {
0636: /* "BU", "DY", "FX", "HV", "NH", "RH", "TP", "YU", "ZR" */
0637: "MM", "BJ", "FR", "BF", "VU", "ZW", "TL", "CS", "CD", /* replacement country codes */
0638: };
0639:
0640: /* This list MUST contain a three-letter code for every two-letter code in
0641: the above list, and they MUST be listed in the same order! */
0642: String[] tempCountries3 = {
0643: /*"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AN", */
0644: "AND", "ARE", "AFG", "ATG", "AIA", "ALB", "ARM", "ANT",
0645: /*"AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AZ", */
0646: "AGO", "ATA", "ARG", "ASM", "AUT", "AUS", "ABW", "AZE",
0647: /*"BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", */
0648: "BIH", "BRB", "BGD", "BEL", "BFA", "BGR", "BHR", "BDI",
0649: /*"BJ", "BM", "BN", "BO", "BR", "BS", "BT", "BV", */
0650: "BEN", "BMU", "BRN", "BOL", "BRA", "BHS", "BTN", "BVT",
0651: /*"BW", "BY", "BZ", "CA", "CC", "CD", "CF", "CG", */
0652: "BWA", "BLR", "BLZ", "CAN", "CCK", "COD", "CAF", "COG",
0653: /*"CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", */
0654: "CHE", "CIV", "COK", "CHL", "CMR", "CHN", "COL", "CRI",
0655: /*"CU", "CV", "CX", "CY", "CZ", "DE", "DJ", "DK", */
0656: "CUB", "CPV", "CXR", "CYP", "CZE", "DEU", "DJI", "DNK",
0657: /*"DM", "DO", "DZ", "EC", "EE", "EG", "EH", "ER", */
0658: "DMA", "DOM", "DZA", "ECU", "EST", "EGY", "ESH", "ERI",
0659: /*"ES", "ET", "FI", "FJ", "FK", "FM", "FO", "FR", */
0660: "ESP", "ETH", "FIN", "FJI", "FLK", "FSM", "FRO", "FRA",
0661: /*"GA", "GB", "GD", "GE", "GF", "GH", "GI", "GL", */
0662: "GAB", "GBR", "GRD", "GEO", "GUF", "GHA", "GIB", "GRL",
0663: /*"GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", */
0664: "GMB", "GIN", "GLP", "GNQ", "GRC", "SGS", "GTM", "GUM",
0665: /*"GW", "GY", "HK", "HM", "HN", "HR", "HT", "HU", */
0666: "GNB", "GUY", "HKG", "HMD", "HND", "HRV", "HTI", "HUN",
0667: /*"ID", "IE", "IL", "IN", "IO", "IQ", "IR", "IS", */
0668: "IDN", "IRL", "ISR", "IND", "IOT", "IRQ", "IRN", "ISL",
0669: /*"IT", "JM", "JO", "JP", "KE", "KG", "KH", "KI", */
0670: "ITA", "JAM", "JOR", "JPN", "KEN", "KGZ", "KHM", "KIR",
0671: /*"KM", "KN", "KP", "KR", "KW", "KY", "KZ", "LA", */
0672: "COM", "KNA", "PRK", "KOR", "KWT", "CYM", "KAZ", "LAO",
0673: /*"LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", */
0674: "LBN", "LCA", "LIE", "LKA", "LBR", "LSO", "LTU", "LUX",
0675: /*"LV", "LY", "MA", "MC", "MD", "MG", "MH", "MK", */
0676: "LVA", "LBY", "MAR", "MCO", "MDA", "MDG", "MHL", "MKD",
0677: /*"ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", */
0678: "MLI", "MMR", "MNG", "MAC", "MNP", "MTQ", "MRT", "MSR",
0679: /*"MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", */
0680: "MLT", "MUS", "MDV", "MWI", "MEX", "MYS", "MOZ", "NAM",
0681: /*"NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", */
0682: "NCL", "NER", "NFK", "NGA", "NIC", "NLD", "NOR", "NPL",
0683: /*"NR", "NU", "NZ", "OM", "PA", "PE", "PF", "PG", */
0684: "NRU", "NIU", "NZL", "OMN", "PAN", "PER", "PYF", "PNG",
0685: /*"PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", */
0686: "PHL", "PAK", "POL", "SPM", "PCN", "PRI", "PSE", "PRT",
0687: /*"PW", "PY", "QA", "RE", "RO", "RU", "RW", "SA", */
0688: "PLW", "PRY", "QAT", "REU", "ROU", "RUS", "RWA", "SAU",
0689: /*"SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", */
0690: "SLB", "SYC", "SDN", "SWE", "SGP", "SHN", "SVN", "SJM",
0691: /*"SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", */
0692: "SVK", "SLE", "SMR", "SEN", "SOM", "SUR", "STP", "SLV",
0693: /*"SY", "SZ", "TC", "TD", "TF", "TG", "TH", "TJ", */
0694: "SYR", "SWZ", "TCA", "TCD", "ATF", "TGO", "THA", "TJK",
0695: /*"TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", */
0696: "TKL", "TLS", "TKM", "TUN", "TON", "TUR", "TTO", "TUV",
0697: /*"TW", "TZ", "UA", "UG", "UM", "US", "UY", "UZ", */
0698: "TWN", "TZA", "UKR", "UGA", "UMI", "USA", "URY", "UZB",
0699: /*"VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", */
0700: "VAT", "VCT", "VEN", "VGB", "VIR", "VNM", "VUT", "WLF",
0701: /*"WS", "YE", "YT", "YU", "ZA", "ZM", "ZW", */
0702: "WSM", "YEM", "MYT", "YUG", "ZAF", "ZMB", "ZWE", };
0703:
0704: String[] tempObsoleteCountries3 = {
0705: /*"FX", "RO", "TP", "ZR", */
0706: "FXX", "ROM", "TMP", "ZAR", };
0707:
0708: synchronized (ULocale.class) {
0709: if (_countries == null) {
0710: _countries = tempCountries;
0711: _deprecatedCountries = tempDeprecatedCountries;
0712: _replacementCountries = tempReplacementCountries;
0713: _obsoleteCountries = tempObsoleteCountries;
0714: _countries3 = tempCountries3;
0715: _obsoleteCountries3 = tempObsoleteCountries3;
0716: }
0717: }
0718: }
0719: }
0720:
0721: private static String[][] _variantsToKeywords;
0722:
0723: private static void initVariantsTable() {
0724: if (_variantsToKeywords == null) {
0725: /**
0726: * This table lists pairs of locale ids for canonicalization. The
0727: * The first item is the normalized id, the second item is the
0728: * canonicalized id.
0729: */
0730: String[][] tempVariantsToKeywords = {
0731: // { EMPTY_STRING, "en_US_POSIX", null, null }, /* .NET name */
0732: { "C", "en_US_POSIX", null, null }, /* POSIX name */
0733: { "art_LOJBAN", "jbo", null, null }, /* registered name */
0734: { "az_AZ_CYRL", "az_Cyrl_AZ", null, null }, /* .NET name */
0735: { "az_AZ_LATN", "az_Latn_AZ", null, null }, /* .NET name */
0736: { "ca_ES_PREEURO", "ca_ES", "currency", "ESP" },
0737: { "cel_GAULISH", "cel__GAULISH", null, null }, /* registered name */
0738: { "de_1901", "de__1901", null, null }, /* registered name */
0739: { "de_1906", "de__1906", null, null }, /* registered name */
0740: { "de__PHONEBOOK", "de", "collation", "phonebook" },
0741: { "de_AT_PREEURO", "de_AT", "currency", "ATS" },
0742: { "de_DE_PREEURO", "de_DE", "currency", "DEM" },
0743: { "de_LU_PREEURO", "de_LU", "currency", "EUR" },
0744: { "el_GR_PREEURO", "el_GR", "currency", "GRD" },
0745: { "en_BOONT", "en__BOONT", null, null }, /* registered name */
0746: { "en_SCOUSE", "en__SCOUSE", null, null }, /* registered name */
0747: { "en_BE_PREEURO", "en_BE", "currency", "BEF" },
0748: { "en_IE_PREEURO", "en_IE", "currency", "IEP" },
0749: { "es__TRADITIONAL", "es", "collation",
0750: "traditional" },
0751: { "es_ES_PREEURO", "es_ES", "currency", "ESP" },
0752: { "eu_ES_PREEURO", "eu_ES", "currency", "ESP" },
0753: { "fi_FI_PREEURO", "fi_FI", "currency", "FIM" },
0754: { "fr_BE_PREEURO", "fr_BE", "currency", "BEF" },
0755: { "fr_FR_PREEURO", "fr_FR", "currency", "FRF" },
0756: { "fr_LU_PREEURO", "fr_LU", "currency", "LUF" },
0757: { "ga_IE_PREEURO", "ga_IE", "currency", "IEP" },
0758: { "gl_ES_PREEURO", "gl_ES", "currency", "ESP" },
0759: { "hi__DIRECT", "hi", "collation", "direct" },
0760: { "it_IT_PREEURO", "it_IT", "currency", "ITL" },
0761: { "ja_JP_TRADITIONAL", "ja_JP", "calendar",
0762: "japanese" },
0763: // { "nb_NO_NY", "nn_NO", null, null },
0764: { "nl_BE_PREEURO", "nl_BE", "currency", "BEF" },
0765: { "nl_NL_PREEURO", "nl_NL", "currency", "NLG" },
0766: { "pt_PT_PREEURO", "pt_PT", "currency", "PTE" },
0767: { "sl_ROZAJ", "sl__ROZAJ", null, null }, /* registered name */
0768: { "sr_SP_CYRL", "sr_Cyrl_CS", null, null }, /* .NET name */
0769: { "sr_SP_LATN", "sr_Latn_CS", null, null }, /* .NET name */
0770: { "sr_YU_CYRILLIC", "sr_Cyrl_CS", null, null }, /* Linux name */
0771: { "uz_UZ_CYRILLIC", "uz_Cyrl_UZ", null, null }, /* Linux name */
0772: { "uz_UZ_CYRL", "uz_Cyrl_UZ", null, null }, /* .NET name */
0773: { "uz_UZ_LATN", "uz_Latn_UZ", null, null }, /* .NET name */
0774: { "zh_CHS", "zh_Hans", null, null }, /* .NET name */
0775: { "zh_CHT", "zh_Hant", null, null }, /* .NET name */
0776: { "zh_GAN", "zh__GAN", null, null }, /* registered name */
0777: { "zh_GUOYU", "zh", null, null }, /* registered name */
0778: { "zh_HAKKA", "zh__HAKKA", null, null }, /* registered name */
0779: { "zh_MIN", "zh__MIN", null, null }, /* registered name */
0780: { "zh_MIN_NAN", "zh__MINNAN", null, null }, /* registered name */
0781: { "zh_WUU", "zh__WUU", null, null }, /* registered name */
0782: { "zh_XIANG", "zh__XIANG", null, null }, /* registered name */
0783: { "zh_YUE", "zh__YUE", null, null }, /* registered name */
0784: { "th_TH_TRADITIONAL", "th_TH", "calendar",
0785: "buddhist" },
0786: { "zh_TW_STROKE", "zh_TW", "collation", "stroke" },
0787: { "zh__PINYIN", "zh", "collation", "pinyin" } };
0788:
0789: synchronized (ULocale.class) {
0790: if (_variantsToKeywords == null) {
0791: _variantsToKeywords = tempVariantsToKeywords;
0792: }
0793: }
0794: }
0795: }
0796:
0797: /**
0798: * Private constructor used by static initializers.
0799: */
0800: private ULocale(String localeID, Locale locale) {
0801: this .localeID = localeID;
0802: this .locale = locale;
0803: }
0804:
0805: /**
0806: * Construct a ULocale object from a {@link java.util.Locale}.
0807: * @param loc a JDK locale
0808: * @stable ICU 2.8
0809: * @internal
0810: */
0811: private ULocale(Locale loc) {
0812: this .localeID = getName(loc.toString());
0813: this .locale = loc;
0814: }
0815:
0816: /**
0817: * Return a ULocale object for a {@link java.util.Locale}.
0818: * The ULocale is canonicalized.
0819: * @param loc a JDK locale
0820: * @stable ICU 3.2
0821: */
0822: public static ULocale forLocale(Locale loc) {
0823: if (loc == null) {
0824: return null;
0825: }
0826: if (loc.toString().length() == 0) {
0827: return ROOT;
0828: }
0829: ULocale result = (ULocale) CACHE.get(loc);
0830: if (result == null && defaultULocale != null
0831: && loc == defaultULocale.locale) {
0832: result = defaultULocale;
0833: } else {
0834: result = new ULocale(loc.toString(), loc);
0835: }
0836: return result;
0837: }
0838:
0839: /**
0840: * Construct a ULocale from a RFC 3066 locale ID. The locale ID consists
0841: * of optional language, script, country, and variant fields in that order,
0842: * separated by underscores, followed by an optional keyword list. The
0843: * script, if present, is four characters long-- this distinguishes it
0844: * from a country code, which is two characters long. Other fields
0845: * are distinguished by position as indicated by the underscores. The
0846: * start of the keyword list is indicated by '@', and consists of one
0847: * or more keyword/value pairs separated by commas.
0848: * <p>
0849: * This constructor does not canonicalize the localeID.
0850: *
0851: * @param localeID string representation of the locale, e.g:
0852: * "en_US", "sy_Cyrl_YU", "zh__pinyin", "es_ES@currency=EUR,collation=traditional"
0853: * @stable ICU 2.8
0854: */
0855: public ULocale(String localeID) {
0856: this .localeID = getName(localeID);
0857: }
0858:
0859: /**
0860: * Convenience overload of ULocale(String, String, String) for
0861: * compatibility with java.util.Locale.
0862: * @see #ULocale(String, String, String)
0863: * @stable ICU 3.4
0864: */
0865: public ULocale(String a, String b) {
0866: this (a, b, null);
0867: }
0868:
0869: /**
0870: * Construct a ULocale from a localeID constructed from the three 'fields' a, b, and c. These
0871: * fields are concatenated using underscores to form a localeID of
0872: * the form a_b_c, which is then handled like the localeID passed
0873: * to <code>ULocale(String localeID)</code>.
0874: *
0875: * <p>Java locale strings consisting of language, country, and
0876: * variant will be handled by this form, since the country code
0877: * (being shorter than four letters long) will not be interpreted
0878: * as a script code. If a script code is present, the final
0879: * argument ('c') will be interpreted as the country code. It is
0880: * recommended that this constructor only be used to ease porting,
0881: * and that clients instead use the single-argument constructor
0882: * when constructing a ULocale from a localeID.
0883: * @param a first component of the locale id
0884: * @param b second component of the locale id
0885: * @param c third component of the locale id
0886: * @see #ULocale(String)
0887: * @stable ICU 3.0
0888: */
0889: public ULocale(String a, String b, String c) {
0890: localeID = getName(lscvToID(a, b, c, EMPTY_STRING));
0891: }
0892:
0893: /**
0894: * Create a ULocale from the id by first canonicalizing the id.
0895: * @param nonCanonicalID the locale id to canonicalize
0896: * @return the locale created from the canonical version of the ID.
0897: * @stable ICU 3.0
0898: */
0899: public static ULocale createCanonical(String nonCanonicalID) {
0900: return new ULocale(canonicalize(nonCanonicalID), (Locale) null);
0901: }
0902:
0903: private static String lscvToID(String lang, String script,
0904: String country, String variant) {
0905: StringBuffer buf = new StringBuffer();
0906:
0907: if (lang != null && lang.length() > 0) {
0908: buf.append(lang);
0909: }
0910: if (script != null && script.length() > 0) {
0911: buf.append(UNDERSCORE);
0912: buf.append(script);
0913: }
0914: if (country != null && country.length() > 0) {
0915: buf.append(UNDERSCORE);
0916: buf.append(country);
0917: }
0918: if (variant != null && variant.length() > 0) {
0919: if (country == null || country.length() == 0) {
0920: buf.append(UNDERSCORE);
0921: }
0922: buf.append(UNDERSCORE);
0923: buf.append(variant);
0924: }
0925: return buf.toString();
0926: }
0927:
0928: /**
0929: * Convert this ULocale object to a {@link java.util.Locale}.
0930: * @return a JDK locale that either exactly represents this object
0931: * or is the closest approximation.
0932: * @stable ICU 2.8
0933: */
0934: public Locale toLocale() {
0935: if (locale == null) {
0936: String[] names = new IDParser(localeID)
0937: .getLanguageScriptCountryVariant();
0938: locale = new Locale(names[0], names[2], names[3]);
0939: }
0940: return locale;
0941: }
0942:
0943: /**
0944: * Keep our own default ULocale.
0945: */
0946: private static ULocale defaultULocale;
0947:
0948: /**
0949: * Returns the current default ULocale.
0950: * @stable ICU 2.8
0951: */
0952: public static ULocale getDefault() {
0953: synchronized (ULocale.class) {
0954: Locale defaultLocale = Locale.getDefault();
0955: if (defaultULocale == null
0956: || defaultULocale.toLocale() != defaultLocale) {
0957: defaultULocale = new ULocale(defaultLocale);
0958: }
0959: return defaultULocale;
0960: }
0961: }
0962:
0963: /**
0964: * Sets the default ULocale. This also sets the default Locale.
0965: * If the caller does not have write permission to the
0966: * user.language property, a security exception will be thrown,
0967: * and the default ULocale will remain unchanged.
0968: * @param newLocale the new default locale
0969: * @throws SecurityException
0970: * if a security manager exists and its
0971: * <code>checkPermission</code> method doesn't allow the operation.
0972: * @throws NullPointerException if <code>newLocale</code> is null
0973: * @see SecurityManager#checkPermission
0974: * @see java.util.PropertyPermission
0975: * @stable ICU 3.0
0976: */
0977: public static synchronized void setDefault(ULocale newLocale) {
0978: Locale.setDefault(newLocale.toLocale());
0979: defaultULocale = newLocale;
0980: }
0981:
0982: /**
0983: * This is for compatibility with Locale-- in actuality, since ULocale is
0984: * immutable, there is no reason to clone it, so this API returns 'this'.
0985: * @stable ICU 3.0
0986: */
0987: public Object clone() {
0988: return this ;
0989: }
0990:
0991: /**
0992: * Returns the hashCode.
0993: * @stable ICU 3.0
0994: */
0995: public int hashCode() {
0996: return localeID.hashCode();
0997: }
0998:
0999: /**
1000: * Returns true if the other object is another ULocale with the
1001: * same full name, or is a String localeID that matches the full name.
1002: * Note that since names are not canonicalized, two ULocales that
1003: * function identically might not compare equal.
1004: *
1005: * @return true if this Locale is equal to the specified object.
1006: * @stable ICU 3.0
1007: */
1008: public boolean equals(Object obj) {
1009: if (this == obj) {
1010: return true;
1011: }
1012: if (obj instanceof String) {
1013: return localeID.equals((String) obj);
1014: }
1015: if (obj instanceof ULocale) {
1016: return localeID.equals(((ULocale) obj).localeID);
1017: }
1018: return false;
1019: }
1020:
1021: /**
1022: * Returns a list of all installed locales.
1023: * @stable ICU 3.0
1024: */
1025: public static ULocale[] getAvailableLocales() {
1026: return ICUResourceBundle.getAvailableULocales();
1027: }
1028:
1029: /**
1030: * Returns a list of all 2-letter country codes defined in ISO 3166.
1031: * Can be used to create Locales.
1032: * @stable ICU 3.0
1033: */
1034: public static String[] getISOCountries() {
1035: initCountryTables();
1036: return (String[]) _countries.clone();
1037: }
1038:
1039: /**
1040: * Returns a list of all 2-letter language codes defined in ISO 639.
1041: * Can be used to create Locales.
1042: * [NOTE: ISO 639 is not a stable standard-- some languages' codes have changed.
1043: * The list this function returns includes both the new and the old codes for the
1044: * languages whose codes have changed.]
1045: * @stable ICU 3.0
1046: */
1047: public static String[] getISOLanguages() {
1048: initLanguageTables();
1049: return (String[]) _languages.clone();
1050: }
1051:
1052: /**
1053: * Returns the language code for this locale, which will either be the empty string
1054: * or a lowercase ISO 639 code.
1055: * @see #getDisplayLanguage
1056: * @stable ICU 3.0
1057: */
1058: public String getLanguage() {
1059: return getLanguage(localeID);
1060: }
1061:
1062: /**
1063: * Returns the language code for the locale ID,
1064: * which will either be the empty string
1065: * or a lowercase ISO 639 code.
1066: * @see #getDisplayLanguage
1067: * @stable ICU 3.0
1068: */
1069: public static String getLanguage(String localeID) {
1070: return new IDParser(localeID).getLanguage();
1071: }
1072:
1073: /**
1074: * Returns the script code for this locale, which might be the empty string.
1075: * @see #getDisplayScript
1076: * @stable ICU 3.0
1077: */
1078: public String getScript() {
1079: return getScript(localeID);
1080: }
1081:
1082: /**
1083: * Returns the script code for the specified locale, which might be the empty string.
1084: * @see #getDisplayScript
1085: * @stable ICU 3.0
1086: */
1087: public static String getScript(String localeID) {
1088: return new IDParser(localeID).getScript();
1089: }
1090:
1091: /**
1092: * Returns the country/region code for this locale, which will either be the empty string
1093: * or an uppercase ISO 3166 2-letter code.
1094: * @see #getDisplayCountry
1095: * @stable ICU 3.0
1096: */
1097: public String getCountry() {
1098: return getCountry(localeID);
1099: }
1100:
1101: /**
1102: * Returns the country/region code for this locale, which will either be the empty string
1103: * or an uppercase ISO 3166 2-letter code.
1104: * @param localeID
1105: * @see #getDisplayCountry
1106: * @stable ICU 3.0
1107: */
1108: public static String getCountry(String localeID) {
1109: return new IDParser(localeID).getCountry();
1110: }
1111:
1112: /**
1113: * Returns the variant code for this locale, which might be the empty string.
1114: * @see #getDisplayVariant
1115: * @stable ICU 3.0
1116: */
1117: public String getVariant() {
1118: return getVariant(localeID);
1119: }
1120:
1121: /**
1122: * Returns the variant code for the specified locale, which might be the empty string.
1123: * @see #getDisplayVariant
1124: * @stable ICU 3.0
1125: */
1126: public static String getVariant(String localeID) {
1127: return new IDParser(localeID).getVariant();
1128: }
1129:
1130: /**
1131: * Returns the fallback locale for the specified locale, which might be the empty string.
1132: * @stable ICU 3.2
1133: */
1134: public static String getFallback(String localeID) {
1135: return getFallbackString(getName(localeID));
1136: }
1137:
1138: /**
1139: * Returns the fallback locale for this locale. If this locale is root, returns null.
1140: * @stable ICU 3.2
1141: */
1142: public ULocale getFallback() {
1143: if (localeID.length() == 0 || localeID.charAt(0) == '@') {
1144: return null;
1145: }
1146: return new ULocale(getFallbackString(localeID), (Locale) null);
1147: }
1148:
1149: /**
1150: * Return the given (canonical) locale id minus the last part before the tags.
1151: */
1152: private static String getFallbackString(String fallback) {
1153: int limit = fallback.indexOf('@');
1154: if (limit == -1) {
1155: limit = fallback.length();
1156: }
1157: int start = fallback.lastIndexOf('_', limit);
1158: if (start == -1) {
1159: start = 0;
1160: }
1161: return fallback.substring(0, start) + fallback.substring(limit);
1162: }
1163:
1164: /**
1165: * Returns the (normalized) base name for this locale.
1166: * @return the base name as a String.
1167: * @stable ICU 3.0
1168: */
1169: public String getBaseName() {
1170: return getBaseName(localeID);
1171: }
1172:
1173: /**
1174: * Returns the (normalized) base name for the specified locale.
1175: * @param localeID the locale ID as a string
1176: * @return the base name as a String.
1177: * @stable ICU 3.0
1178: */
1179: public static String getBaseName(String localeID) {
1180: if (localeID.indexOf('@') == -1) {
1181: return localeID;
1182: }
1183: return new IDParser(localeID).getBaseName();
1184: }
1185:
1186: /**
1187: * Returns the (normalized) full name for this locale.
1188: *
1189: * @return String the full name of the localeID
1190: * @stable ICU 3.0
1191: */
1192: public String getName() {
1193: return localeID; // always normalized
1194: }
1195:
1196: /**
1197: * Returns the (normalized) full name for the specified locale.
1198: *
1199: * @param localeID the localeID as a string
1200: * @return String the full name of the localeID
1201: * @stable ICU 3.0
1202: */
1203: public static String getName(String localeID) {
1204: Map cache = (Map) nameCacheRef.get();
1205: if (cache == null) {
1206: cache = Collections.synchronizedMap(new HashMap());
1207: nameCacheRef = new SoftReference(cache);
1208: }
1209: String name = (String) cache.get(localeID);
1210: if (name == null) {
1211: name = new IDParser(localeID).getName();
1212: cache.put(localeID, name);
1213: }
1214: return name;
1215: }
1216:
1217: private static SoftReference nameCacheRef = new SoftReference(
1218: Collections.synchronizedMap(new HashMap()));
1219:
1220: /**
1221: * Returns a string representation of this object.
1222: * @stable ICU 3.0
1223: */
1224: public String toString() {
1225: return localeID;
1226: }
1227:
1228: /**
1229: * Returns an iterator over keywords for this locale. If there
1230: * are no keywords, returns null.
1231: * @return iterator over keywords, or null if there are no keywords.
1232: * @stable ICU 3.0
1233: */
1234: public Iterator getKeywords() {
1235: return getKeywords(localeID);
1236: }
1237:
1238: /**
1239: * Returns an iterator over keywords for the specified locale. If there
1240: * are no keywords, returns null.
1241: * @return an iterator over the keywords in the specified locale, or null
1242: * if there are no keywords.
1243: * @stable ICU 3.0
1244: */
1245: public static Iterator getKeywords(String localeID) {
1246: return new IDParser(localeID).getKeywords();
1247: }
1248:
1249: /**
1250: * Returns the value for a keyword in this locale. If the keyword is not defined, returns null.
1251: * @param keywordName name of the keyword whose value is desired. Case insensitive.
1252: * @return the value of the keyword, or null.
1253: * @stable ICU 3.0
1254: */
1255: public String getKeywordValue(String keywordName) {
1256: return getKeywordValue(localeID, keywordName);
1257: }
1258:
1259: /**
1260: * Returns the value for a keyword in the specified locale. If the keyword is not defined, returns null.
1261: * The locale name does not need to be normalized.
1262: * @param keywordName name of the keyword whose value is desired. Case insensitive.
1263: * @return String the value of the keyword as a string
1264: * @stable ICU 3.0
1265: */
1266: public static String getKeywordValue(String localeID,
1267: String keywordName) {
1268: return new IDParser(localeID).getKeywordValue(keywordName);
1269: }
1270:
1271: /**
1272: * Utility class to parse and normalize locale ids (including POSIX style)
1273: */
1274: private static final class IDParser {
1275: private char[] id;
1276: private int index;
1277: private char[] buffer;
1278: private int blen;
1279: // um, don't handle POSIX ids unless we request it. why not? well... because.
1280: private boolean canonicalize;
1281: private boolean hadCountry;
1282:
1283: // used when canonicalizing
1284: Map keywords;
1285: String baseName;
1286:
1287: /**
1288: * Parsing constants.
1289: */
1290: private static final char KEYWORD_SEPARATOR = '@';
1291: private static final char HYPHEN = '-';
1292: private static final char KEYWORD_ASSIGN = '=';
1293: private static final char COMMA = ',';
1294: private static final char ITEM_SEPARATOR = ';';
1295: private static final char DOT = '.';
1296:
1297: private IDParser(String localeID) {
1298: this (localeID, false);
1299: }
1300:
1301: private IDParser(String localeID, boolean canonicalize) {
1302: id = localeID.toCharArray();
1303: index = 0;
1304: buffer = new char[id.length + 5];
1305: blen = 0;
1306: this .canonicalize = canonicalize;
1307: }
1308:
1309: private void reset() {
1310: index = blen = 0;
1311: }
1312:
1313: // utilities for working on text in the buffer
1314:
1315: /**
1316: * Append c to the buffer.
1317: */
1318: private void append(char c) {
1319: try {
1320: buffer[blen] = c;
1321: } catch (IndexOutOfBoundsException e) {
1322: if (buffer.length > 512) {
1323: // something is seriously wrong, let this go
1324: throw e;
1325: }
1326: char[] nbuffer = new char[buffer.length * 2];
1327: System.arraycopy(buffer, 0, nbuffer, 0, buffer.length);
1328: nbuffer[blen] = c;
1329: buffer = nbuffer;
1330: }
1331: ++blen;
1332: }
1333:
1334: private void addSeparator() {
1335: append(UNDERSCORE);
1336: }
1337:
1338: /**
1339: * Returns the text in the buffer from start to blen as a String.
1340: */
1341: private String getString(int start) {
1342: if (start == blen) {
1343: return EMPTY_STRING;
1344: }
1345: return new String(buffer, start, blen - start);
1346: }
1347:
1348: /**
1349: * Set the length of the buffer to pos, then append the string.
1350: */
1351: private void set(int pos, String s) {
1352: this .blen = pos; // no safety
1353: append(s);
1354: }
1355:
1356: /**
1357: * Append the string to the buffer.
1358: */
1359: private void append(String s) {
1360: for (int i = 0; i < s.length(); ++i) {
1361: append(s.charAt(i));
1362: }
1363: }
1364:
1365: // utilities for parsing text out of the id
1366:
1367: /**
1368: * Character to indicate no more text is available in the id.
1369: */
1370: private static final char DONE = '\uffff';
1371:
1372: /**
1373: * Returns the character at index in the id, and advance index. The returned character
1374: * is DONE if index was at the limit of the buffer. The index is advanced regardless
1375: * so that decrementing the index will always 'unget' the last character returned.
1376: */
1377: private char next() {
1378: if (index == id.length) {
1379: index++;
1380: return DONE;
1381: }
1382:
1383: return id[index++];
1384: }
1385:
1386: /**
1387: * Advance index until the next terminator or id separator, and leave it there.
1388: */
1389: private void skipUntilTerminatorOrIDSeparator() {
1390: while (!isTerminatorOrIDSeparator(next())) {
1391: }
1392: --index;
1393: }
1394:
1395: /**
1396: * Returns true if the character at index in the id is a terminator.
1397: */
1398: private boolean atTerminator() {
1399: return index >= id.length || isTerminator(id[index]);
1400: }
1401:
1402: /**
1403: * Returns true if the character is an id separator (underscore or hyphen).
1404: */
1405: private boolean isIDSeparator(char c) {
1406: return c == UNDERSCORE || c == HYPHEN;
1407: }
1408:
1409: /**
1410: * Returns true if the character is a terminator (keyword separator, dot, or DONE).
1411: * Dot is a terminator because of the POSIX form, where dot precedes the codepage.
1412: */
1413: private boolean isTerminator(char c) {
1414: // always terminate at DOT, even if not handling POSIX. It's an error...
1415: return c == KEYWORD_SEPARATOR || c == DONE || c == DOT;
1416: }
1417:
1418: /**
1419: * Returns true if the character is a terminator or id separator.
1420: */
1421: private boolean isTerminatorOrIDSeparator(char c) {
1422: return c == KEYWORD_SEPARATOR || c == UNDERSCORE
1423: || c == HYPHEN || c == DONE || c == DOT;
1424: }
1425:
1426: /**
1427: * Returns true if the start of the buffer has an experimental or private language
1428: * prefix, the pattern '[ixIX][-_].' shows the syntax checked.
1429: */
1430: private boolean haveExperimentalLanguagePrefix() {
1431: if (id.length > 2) {
1432: char c = id[1];
1433: if (c == HYPHEN || c == UNDERSCORE) {
1434: c = id[0];
1435: return c == 'x' || c == 'X' || c == 'i' || c == 'I';
1436: }
1437: }
1438: return false;
1439: }
1440:
1441: /**
1442: * Returns true if a value separator occurs at or after index.
1443: */
1444: private boolean haveKeywordAssign() {
1445: // assume it is safe to start from index
1446: for (int i = index; i < id.length; ++i) {
1447: if (id[i] == KEYWORD_ASSIGN) {
1448: return true;
1449: }
1450: }
1451: return false;
1452: }
1453:
1454: /**
1455: * Advance index past language, and accumulate normalized language code in buffer.
1456: * Index must be at 0 when this is called. Index is left at a terminator or id
1457: * separator. Returns the start of the language code in the buffer.
1458: */
1459: private int parseLanguage() {
1460: if (haveExperimentalLanguagePrefix()) {
1461: append(Character.toLowerCase(id[0]));
1462: append(HYPHEN);
1463: index = 2;
1464: }
1465:
1466: char c;
1467: while (!isTerminatorOrIDSeparator(c = next())) {
1468: append(Character.toLowerCase(c));
1469: }
1470: --index; // unget
1471:
1472: if (blen == 3) {
1473: initLanguageTables();
1474:
1475: /* convert 3 character code to 2 character code if possible *CWB*/
1476: String lang = getString(0);
1477: int offset = findIndex(_languages3, lang);
1478: if (offset >= 0) {
1479: set(0, _languages[offset]);
1480: } else {
1481: offset = findIndex(_obsoleteLanguages3, lang);
1482: if (offset >= 0) {
1483: set(0, _obsoleteLanguages[offset]);
1484: }
1485: }
1486: }
1487:
1488: return 0;
1489: }
1490:
1491: /**
1492: * Advance index past language. Index must be at 0 when this is called. Index
1493: * is left at a terminator or id separator.
1494: */
1495: private void skipLanguage() {
1496: if (haveExperimentalLanguagePrefix()) {
1497: index = 2;
1498: }
1499: skipUntilTerminatorOrIDSeparator();
1500: }
1501:
1502: /**
1503: * Advance index past script, and accumulate normalized script in buffer.
1504: * Index must be immediately after the language.
1505: * If the item at this position is not a script (is not four characters
1506: * long) leave index and buffer unchanged. Otherwise index is left at
1507: * a terminator or id separator. Returns the start of the script code
1508: * in the buffer (this may be equal to the buffer length, if there is no
1509: * script).
1510: */
1511: private int parseScript() {
1512: if (!atTerminator()) {
1513: int oldIndex = index; // save original index
1514: ++index;
1515:
1516: int oldBlen = blen; // get before append hyphen, if we truncate everything is undone
1517: char c;
1518: while (!isTerminatorOrIDSeparator(c = next())) {
1519: if (blen == oldBlen) { // first pass
1520: addSeparator();
1521: append(Character.toUpperCase(c));
1522: } else {
1523: append(Character.toLowerCase(c));
1524: }
1525: }
1526: --index; // unget
1527:
1528: /* If it's not exactly 4 characters long, then it's not a script. */
1529: if (index - oldIndex != 5) { // +1 to account for separator
1530: index = oldIndex;
1531: blen = oldBlen;
1532: } else {
1533: oldBlen++; // index past hyphen, for clients who want to extract just the script
1534: }
1535:
1536: return oldBlen;
1537: }
1538: return blen;
1539: }
1540:
1541: /**
1542: * Advance index past script.
1543: * Index must be immediately after the language and IDSeparator.
1544: * If the item at this position is not a script (is not four characters
1545: * long) leave index. Otherwise index is left at a terminator or
1546: * id separator.
1547: */
1548: private void skipScript() {
1549: if (!atTerminator()) {
1550: int oldIndex = index;
1551: ++index;
1552:
1553: skipUntilTerminatorOrIDSeparator();
1554: if (index - oldIndex != 5) { // +1 to account for separator
1555: index = oldIndex;
1556: }
1557: }
1558: }
1559:
1560: /**
1561: * Advance index past country, and accumulate normalized country in buffer.
1562: * Index must be immediately after the script (if there is one, else language)
1563: * and IDSeparator. Return the start of the country code in the buffer.
1564: */
1565: private int parseCountry() {
1566: if (!atTerminator()) {
1567: ++index;
1568:
1569: int oldBlen = blen;
1570: char c;
1571: while (!isTerminatorOrIDSeparator(c = next())) {
1572: if (oldBlen == blen) { // first, add hyphen
1573: hadCountry = true; // we have a country, let variant parsing know
1574: addSeparator();
1575: ++oldBlen; // increment past hyphen
1576: }
1577: append(Character.toUpperCase(c));
1578: }
1579: --index; // unget
1580:
1581: if (blen - oldBlen == 3) {
1582: initCountryTables();
1583:
1584: /* convert 3 character code to 2 character code if possible *CWB*/
1585: int offset = findIndex(_countries3,
1586: getString(oldBlen));
1587: if (offset >= 0) {
1588: set(oldBlen, _countries[offset]);
1589: } else {
1590: offset = findIndex(_obsoleteCountries3,
1591: getString(oldBlen));
1592: if (offset >= 0) {
1593: set(oldBlen, _obsoleteCountries[offset]);
1594: }
1595: }
1596: }
1597:
1598: return oldBlen;
1599: }
1600:
1601: return blen;
1602: }
1603:
1604: /**
1605: * Advance index past country.
1606: * Index must be immediately after the script (if there is one, else language)
1607: * and IDSeparator.
1608: */
1609: private void skipCountry() {
1610: if (!atTerminator()) {
1611: ++index;
1612: skipUntilTerminatorOrIDSeparator();
1613: }
1614: }
1615:
1616: /**
1617: * Advance index past variant, and accumulate normalized variant in buffer. This ignores
1618: * the codepage information from POSIX ids. Index must be immediately after the country
1619: * or script. Index is left at the keyword separator or at the end of the text. Return
1620: * the start of the variant code in the buffer.
1621: *
1622: * In standard form, we can have the following forms:
1623: * ll__VVVV
1624: * ll_CC_VVVV
1625: * ll_Ssss_VVVV
1626: * ll_Ssss_CC_VVVV
1627: *
1628: * This also handles POSIX ids, which can have the following forms (pppp is code page id):
1629: * ll_CC.pppp --> ll_CC
1630: * ll_CC.pppp@VVVV --> ll_CC_VVVV
1631: * ll_CC@VVVV --> ll_CC_VVVV
1632: *
1633: * We identify this use of '@' in POSIX ids by looking for an '=' following
1634: * the '@'. If there is one, we consider '@' to start a keyword list, instead of
1635: * being part of a POSIX id.
1636: *
1637: * Note: since it was decided that we want an option to not handle POSIX ids, this
1638: * becomes a bit more complex.
1639: */
1640: private int parseVariant() {
1641: int oldBlen = blen;
1642:
1643: boolean start = true;
1644: boolean needSeparator = true;
1645: boolean skipping = false;
1646: char c;
1647: while ((c = next()) != DONE) {
1648: if (c == DOT) {
1649: start = false;
1650: skipping = true;
1651: } else if (c == KEYWORD_SEPARATOR) {
1652: if (haveKeywordAssign()) {
1653: break;
1654: }
1655: skipping = false;
1656: start = false;
1657: needSeparator = true; // add another underscore if we have more text
1658: } else if (start) {
1659: start = false;
1660: } else if (!skipping) {
1661: if (needSeparator) {
1662: boolean incOldBlen = blen == oldBlen; // need to skip separators
1663: needSeparator = false;
1664: if (incOldBlen && !hadCountry) { // no country, we'll need two
1665: addSeparator();
1666: ++oldBlen; // for sure
1667: }
1668: addSeparator();
1669: if (incOldBlen) { // only for the first separator
1670: ++oldBlen;
1671: }
1672: }
1673: c = Character.toUpperCase(c);
1674: if (c == HYPHEN || c == COMMA) {
1675: c = UNDERSCORE;
1676: }
1677: append(c);
1678: }
1679: }
1680: --index; // unget
1681:
1682: return oldBlen;
1683: }
1684:
1685: // no need for skipvariant, to get the keywords we'll just scan directly for
1686: // the keyword separator
1687:
1688: /**
1689: * Returns the normalized language id, or the empty string.
1690: */
1691: public String getLanguage() {
1692: reset();
1693: return getString(parseLanguage());
1694: }
1695:
1696: /**
1697: * Returns the normalized script id, or the empty string.
1698: */
1699: public String getScript() {
1700: reset();
1701: skipLanguage();
1702: return getString(parseScript());
1703: }
1704:
1705: /**
1706: * return the normalized country id, or the empty string.
1707: */
1708: public String getCountry() {
1709: reset();
1710: skipLanguage();
1711: skipScript();
1712: return getString(parseCountry());
1713: }
1714:
1715: /**
1716: * Returns the normalized variant id, or the empty string.
1717: */
1718: public String getVariant() {
1719: reset();
1720: skipLanguage();
1721: skipScript();
1722: skipCountry();
1723: return getString(parseVariant());
1724: }
1725:
1726: /**
1727: * Returns the language, script, country, and variant as separate strings.
1728: */
1729: public String[] getLanguageScriptCountryVariant() {
1730: reset();
1731: return new String[] { getString(parseLanguage()),
1732: getString(parseScript()),
1733: getString(parseCountry()),
1734: getString(parseVariant()) };
1735: }
1736:
1737: public void setBaseName(String baseName) {
1738: this .baseName = baseName;
1739: }
1740:
1741: public void parseBaseName() {
1742: if (baseName != null) {
1743: set(0, baseName);
1744: } else {
1745: reset();
1746: parseLanguage();
1747: parseScript();
1748: parseCountry();
1749: parseVariant();
1750:
1751: // catch unwanted trailing underscore after country if there was no variant
1752: if (blen > 1 && buffer[blen - 1] == UNDERSCORE) {
1753: --blen;
1754: }
1755: }
1756: }
1757:
1758: /**
1759: * Returns the normalized base form of the locale id. The base
1760: * form does not include keywords.
1761: */
1762: public String getBaseName() {
1763: if (baseName != null) {
1764: return baseName;
1765: }
1766: parseBaseName();
1767: return getString(0);
1768: }
1769:
1770: /**
1771: * Returns the normalized full form of the locale id. The full
1772: * form includes keywords if they are present.
1773: */
1774: public String getName() {
1775: parseBaseName();
1776: parseKeywords();
1777: return getString(0);
1778: }
1779:
1780: // keyword utilities
1781:
1782: /**
1783: * If we have keywords, advance index to the start of the keywords and return true,
1784: * otherwise return false.
1785: */
1786: private boolean setToKeywordStart() {
1787: for (int i = index; i < id.length; ++i) {
1788: if (id[i] == KEYWORD_SEPARATOR) {
1789: if (canonicalize) {
1790: for (int j = ++i; j < id.length; ++j) { // increment i past separator for return
1791: if (id[j] == KEYWORD_ASSIGN) {
1792: index = i;
1793: return true;
1794: }
1795: }
1796: } else {
1797: if (++i < id.length) {
1798: index = i;
1799: return true;
1800: }
1801: }
1802: break;
1803: }
1804: }
1805: return false;
1806: }
1807:
1808: private static boolean isDoneOrKeywordAssign(char c) {
1809: return c == DONE || c == KEYWORD_ASSIGN;
1810: }
1811:
1812: private static boolean isDoneOrItemSeparator(char c) {
1813: return c == DONE || c == ITEM_SEPARATOR;
1814: }
1815:
1816: private String getKeyword() {
1817: int start = index;
1818: while (!isDoneOrKeywordAssign(next())) {
1819: }
1820: --index;
1821: return new String(id, start, index - start).trim()
1822: .toLowerCase();
1823: }
1824:
1825: private String getValue() {
1826: int start = index;
1827: while (!isDoneOrItemSeparator(next())) {
1828: }
1829: --index;
1830: return new String(id, start, index - start).trim(); // leave case alone
1831: }
1832:
1833: private Comparator getKeyComparator() {
1834: final Comparator comp = new Comparator() {
1835: public int compare(Object lhs, Object rhs) {
1836: return ((String) lhs).compareTo((String) rhs);
1837: }
1838: };
1839: return comp;
1840: }
1841:
1842: /**
1843: * Returns a map of the keywords and values, or null if there are none.
1844: */
1845: private Map getKeywordMap() {
1846: if (keywords == null) {
1847: TreeMap m = null;
1848: if (setToKeywordStart()) {
1849: // trim spaces and convert to lower case, both keywords and values.
1850: do {
1851: String key = getKeyword();
1852: if (key.length() == 0) {
1853: break;
1854: }
1855: char c = next();
1856: if (c != KEYWORD_ASSIGN) {
1857: // throw new IllegalArgumentException("key '" + key + "' missing a value.");
1858: if (c == DONE) {
1859: break;
1860: } else {
1861: continue;
1862: }
1863: }
1864: String value = getValue();
1865: if (value.length() == 0) {
1866: // throw new IllegalArgumentException("key '" + key + "' missing a value.");
1867: continue;
1868: }
1869: if (m == null) {
1870: m = new TreeMap(getKeyComparator());
1871: } else if (m.containsKey(key)) {
1872: // throw new IllegalArgumentException("key '" + key + "' already has a value.");
1873: continue;
1874: }
1875: m.put(key, value);
1876: } while (next() == ITEM_SEPARATOR);
1877: }
1878: keywords = m != null ? m : Collections.EMPTY_MAP;
1879: }
1880:
1881: return keywords;
1882: }
1883:
1884: /**
1885: * Parse the keywords and return start of the string in the buffer.
1886: */
1887: private int parseKeywords() {
1888: int oldBlen = blen;
1889: Map m = getKeywordMap();
1890: if (!m.isEmpty()) {
1891: Iterator iter = m.entrySet().iterator();
1892: boolean first = true;
1893: while (iter.hasNext()) {
1894: append(first ? KEYWORD_SEPARATOR : ITEM_SEPARATOR);
1895: first = false;
1896: Map.Entry e = (Map.Entry) iter.next();
1897: append((String) e.getKey());
1898: append(KEYWORD_ASSIGN);
1899: append((String) e.getValue());
1900: }
1901: if (blen != oldBlen) {
1902: ++oldBlen;
1903: }
1904: }
1905: return oldBlen;
1906: }
1907:
1908: /**
1909: * Returns an iterator over the keywords, or null if we have an empty map.
1910: */
1911: public Iterator getKeywords() {
1912: Map m = getKeywordMap();
1913: return m.isEmpty() ? null : m.keySet().iterator();
1914: }
1915:
1916: /**
1917: * Returns the value for the named keyword, or null if the keyword is not
1918: * present.
1919: */
1920: public String getKeywordValue(String keywordName) {
1921: Map m = getKeywordMap();
1922: return m.isEmpty() ? null : (String) m.get(keywordName
1923: .trim().toLowerCase());
1924: }
1925:
1926: /**
1927: * Set the keyword value only if it is not already set to something else.
1928: */
1929: public void defaultKeywordValue(String keywordName, String value) {
1930: setKeywordValue(keywordName, value, false);
1931: }
1932:
1933: /**
1934: * Set the value for the named keyword, or unset it if value is null. If
1935: * keywordName itself is null, unset all keywords. If keywordName is not null,
1936: * value must not be null.
1937: */
1938: public void setKeywordValue(String keywordName, String value) {
1939: setKeywordValue(keywordName, value, true);
1940: }
1941:
1942: /**
1943: * Set the value for the named keyword, or unset it if value is null. If
1944: * keywordName itself is null, unset all keywords. If keywordName is not null,
1945: * value must not be null. If reset is true, ignore any previous value for
1946: * the keyword, otherwise do not change the keyword (including removal of
1947: * one or all keywords).
1948: */
1949: private void setKeywordValue(String keywordName, String value,
1950: boolean reset) {
1951: if (keywordName == null) {
1952: if (reset) {
1953: // force new map, ignore value
1954: keywords = Collections.EMPTY_MAP;
1955: }
1956: } else {
1957: keywordName = keywordName.trim().toLowerCase();
1958: if (keywordName.length() == 0) {
1959: throw new IllegalArgumentException(
1960: "keyword must not be empty");
1961: }
1962: if (value != null) {
1963: value = value.trim();
1964: if (value.length() == 0) {
1965: throw new IllegalArgumentException(
1966: "value must not be empty");
1967: }
1968: }
1969: Map m = getKeywordMap();
1970: if (m.isEmpty()) { // it is EMPTY_MAP
1971: if (value != null) {
1972: // force new map
1973: keywords = new TreeMap(getKeyComparator());
1974: keywords.put(keywordName, value.trim());
1975: }
1976: } else {
1977: if (reset || !m.containsKey(keywordName)) {
1978: if (value != null) {
1979: m.put(keywordName, value);
1980: } else {
1981: m.remove(keywordName);
1982: if (m.isEmpty()) {
1983: // force new map
1984: keywords = Collections.EMPTY_MAP;
1985: }
1986: }
1987: }
1988: }
1989: }
1990: }
1991: }
1992:
1993: /**
1994: * linear search of the string array. the arrays are unfortunately ordered by the
1995: * two-letter target code, not the three-letter search code, which seems backwards.
1996: */
1997: private static int findIndex(String[] array, String target) {
1998: for (int i = 0; i < array.length; i++) {
1999: if (target.equals(array[i])) {
2000: return i;
2001: }
2002: }
2003: return -1;
2004: }
2005:
2006: /**
2007: * Returns the canonical name for the specified locale ID. This is used to convert POSIX
2008: * and other grandfathered IDs to standard ICU form.
2009: * @param localeID the locale id
2010: * @return the canonicalized id
2011: * @stable ICU 3.0
2012: */
2013: public static String canonicalize(String localeID) {
2014: IDParser parser = new IDParser(localeID, true);
2015: String baseName = parser.getBaseName();
2016: boolean foundVariant = false;
2017:
2018: // formerly, we always set to en_US_POSIX if the basename was empty, but
2019: // now we require that the entire id be empty, so that "@foo=bar"
2020: // will pass through unchanged.
2021: // {dlf} I'd rather keep "" unchanged.
2022: if (localeID.equals("")) {
2023: return "";
2024: // return "en_US_POSIX";
2025: }
2026:
2027: // we have an ID in the form xx_Yyyy_ZZ_KKKKK
2028:
2029: initVariantsTable();
2030:
2031: /* See if this is an already known locale */
2032: for (int i = 0; i < _variantsToKeywords.length; i++) {
2033: if (_variantsToKeywords[i][0].equals(baseName)) {
2034: foundVariant = true;
2035:
2036: String[] vals = _variantsToKeywords[i];
2037: parser.setBaseName(vals[1]);
2038: if (vals[2] != null) {
2039: parser.defaultKeywordValue(vals[2], vals[3]);
2040: }
2041: break;
2042: }
2043: }
2044:
2045: /* convert the Euro variant to appropriate ID */
2046: if (!foundVariant) {
2047: int idx = baseName.indexOf("_EURO");
2048: if (idx > -1) {
2049: parser.setBaseName(baseName.substring(0, idx));
2050: parser.defaultKeywordValue("currency", "EUR");
2051: }
2052: }
2053:
2054: /* total mondo hack for Norwegian, fortunately the main NY case is handled earlier */
2055: if (!foundVariant) {
2056: if (parser.getLanguage().equals("nb")
2057: && parser.getVariant().equals("NY")) {
2058: parser.setBaseName(lscvToID("nn", parser.getScript(),
2059: parser.getCountry(), null));
2060: }
2061: }
2062:
2063: return parser.getName();
2064: }
2065:
2066: /**
2067: * Given a keyword and a value, return a new locale with an updated
2068: * keyword and value. If keyword is null, this removes all keywords from the locale id.
2069: * Otherwise, if the value is null, this removes the value for this keyword from the
2070: * locale id. Otherwise, this adds/replaces the value for this keyword in the locale id.
2071: * The keyword and value must not be empty.
2072: * @param keyword the keyword to add/remove, or null to remove all keywords.
2073: * @param value the value to add/set, or null to remove this particular keyword.
2074: * @return the updated locale
2075: * @stable ICU 3.2
2076: */
2077: public ULocale setKeywordValue(String keyword, String value) {
2078: return new ULocale(setKeywordValue(localeID, keyword, value),
2079: (Locale) null);
2080: }
2081:
2082: /**
2083: * Given a locale id, a keyword, and a value, return a new locale id with an updated
2084: * keyword and value. If keyword is null, this removes all keywords from the locale id.
2085: * Otherwise, if the value is null, this removes the value for this keyword from the
2086: * locale id. Otherwise, this adds/replaces the value for this keyword in the locale id.
2087: * The keyword and value must not be empty.
2088: * @param localeID the locale id to modify
2089: * @param keyword the keyword to add/remove, or null to remove all keywords.
2090: * @param value the value to add/set, or null to remove this particular keyword.
2091: * @return the updated locale id
2092: * @stable ICU 3.2
2093: */
2094: public static String setKeywordValue(String localeID,
2095: String keyword, String value) {
2096: IDParser parser = new IDParser(localeID);
2097: parser.setKeywordValue(keyword, value);
2098: return parser.getName();
2099: }
2100:
2101: /**
2102: * Given a locale id, a keyword, and a value, return a new locale id with an updated
2103: * keyword and value, if the keyword does not already have a value. The keyword and
2104: * value must not be null or empty.
2105: * @param localeID the locale id to modify
2106: * @param keyword the keyword to add, if not already present
2107: * @param value the value to add, if not already present
2108: * @return the updated locale id
2109: * @internal
2110: */
2111: private static String defaultKeywordValue(String localeID,
2112: String keyword, String value) {
2113: IDParser parser = new IDParser(localeID);
2114: parser.defaultKeywordValue(keyword, value);
2115: return parser.getName();
2116: }
2117:
2118: /**
2119: * Returns a three-letter abbreviation for this locale's language. If the locale
2120: * doesn't specify a language, returns the empty string. Otherwise, returns
2121: * a lowercase ISO 639-2/T language code.
2122: * The ISO 639-2 language codes can be found on-line at
2123: * <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
2124: * @exception MissingResourceException Throws MissingResourceException if the
2125: * three-letter language abbreviation is not available for this locale.
2126: * @stable ICU 3.0
2127: */
2128: public String getISO3Language() {
2129: return getISO3Language(localeID);
2130: }
2131:
2132: /**
2133: * Returns a three-letter abbreviation for this locale's language. If the locale
2134: * doesn't specify a language, returns the empty string. Otherwise, returns
2135: * a lowercase ISO 639-2/T language code.
2136: * The ISO 639-2 language codes can be found on-line at
2137: * <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
2138: * @exception MissingResourceException Throws MissingResourceException if the
2139: * three-letter language abbreviation is not available for this locale.
2140: * @stable ICU 3.0
2141: */
2142: public static String getISO3Language(String localeID) {
2143: initLanguageTables();
2144:
2145: String language = getLanguage(localeID);
2146: int offset = findIndex(_languages, language);
2147: if (offset >= 0) {
2148: return _languages3[offset];
2149: } else {
2150: offset = findIndex(_obsoleteLanguages, language);
2151: if (offset >= 0) {
2152: return _obsoleteLanguages3[offset];
2153: }
2154: }
2155: return EMPTY_STRING;
2156: }
2157:
2158: /**
2159: * Returns a three-letter abbreviation for this locale's country/region. If the locale
2160: * doesn't specify a country, returns the empty string. Otherwise, returns
2161: * an uppercase ISO 3166 3-letter country code.
2162: * @exception MissingResourceException Throws MissingResourceException if the
2163: * three-letter country abbreviation is not available for this locale.
2164: * @stable ICU 3.0
2165: */
2166: public String getISO3Country() {
2167: return getISO3Country(localeID);
2168: }
2169:
2170: /**
2171: * Returns a three-letter abbreviation for this locale's country/region. If the locale
2172: * doesn't specify a country, returns the empty string. Otherwise, returns
2173: * an uppercase ISO 3166 3-letter country code.
2174: * @exception MissingResourceException Throws MissingResourceException if the
2175: * three-letter country abbreviation is not available for this locale.
2176: * @stable ICU 3.0
2177: */
2178: public static String getISO3Country(String localeID) {
2179: initCountryTables();
2180:
2181: String country = getCountry(localeID);
2182: int offset = findIndex(_countries, country);
2183: if (offset >= 0) {
2184: return _countries3[offset];
2185: } else {
2186: offset = findIndex(_obsoleteCountries, country);
2187: if (offset >= 0) {
2188: return _obsoleteCountries3[offset];
2189: }
2190: }
2191: return EMPTY_STRING;
2192: }
2193:
2194: // display names
2195:
2196: /**
2197: * Utility to fetch locale display data from resource bundle tables.
2198: */
2199: private static String getTableString(String tableName,
2200: String subtableName, String item, String displayLocaleID) {
2201: if (item.length() > 0) {
2202: try {
2203: ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle
2204: .getBundleInstance(
2205: ICUResourceBundle.ICU_BASE_NAME,
2206: displayLocaleID);
2207: return getTableString(tableName, subtableName, item,
2208: bundle);
2209: } catch (Exception e) {
2210: // System.out.println("gtsu: " + e.getMessage());
2211: }
2212: }
2213: return item;
2214: }
2215:
2216: /**
2217: * Utility to fetch locale display data from resource bundle tables.
2218: */
2219: private static String getTableString(String tableName,
2220: String subtableName, String item, ICUResourceBundle bundle) {
2221: // System.out.println("gts table: " + tableName +
2222: // " subtable: " + subtableName +
2223: // " item: " + item +
2224: // " bundle: " + bundle.getULocale());
2225: try {
2226: for (;;) {
2227: // special case currency
2228: if ("currency".equals(subtableName)) {
2229: ICUResourceBundle table = bundle
2230: .getWithFallback("Currencies");
2231: table = table.getWithFallback(item);
2232: return table.getString(1);
2233: } else {
2234: ICUResourceBundle table = bundle
2235: .getWithFallback(tableName);
2236: try {
2237: if (subtableName != null) {
2238: table = table.getWithFallback(subtableName);
2239: }
2240: return table.getStringWithFallback(item);
2241: } catch (MissingResourceException e) {
2242:
2243: if (subtableName == null) {
2244: try {
2245: // may be a deprecated code
2246: String currentName = null;
2247: if (tableName.equals("Countries")) {
2248: currentName = getCurrentCountryID(item);
2249: } else if (tableName
2250: .equals("Languages")) {
2251: currentName = getCurrentLanguageID(item);
2252: }
2253: return table
2254: .getStringWithFallback(currentName);
2255: } catch (MissingResourceException ex) {/* fall through*/
2256: }
2257: }
2258:
2259: // still can't figure out ?.. try the fallback mechanism
2260: String fallbackLocale = table.getWithFallback(
2261: "Fallback").getString();
2262: if (fallbackLocale.length() == 0) {
2263: fallbackLocale = "root";
2264: }
2265: // System.out.println("bundle: " + bundle.getULocale() + " fallback: " + fallbackLocale);
2266: if (fallbackLocale
2267: .equals(table.getULocale().localeID)) {
2268: return item;
2269: }
2270: bundle = (ICUResourceBundle) UResourceBundle
2271: .getBundleInstance(
2272: ICUResourceBundle.ICU_BASE_NAME,
2273: fallbackLocale);
2274: // System.out.println("fallback from " + table.getULocale() + " to " + fallbackLocale +
2275: // ", got bundle " + bundle.getULocale());
2276: }
2277: }
2278: }
2279: } catch (Exception e) {
2280: // System.out.println("gtsi: " + e.getMessage());
2281: }
2282: return item;
2283: }
2284:
2285: /**
2286: * Returns this locale's language localized for display in the default locale.
2287: * @return the localized language name.
2288: * @stable ICU 3.0
2289: */
2290: public String getDisplayLanguage() {
2291: return getDisplayLanguageInternal(localeID,
2292: getDefault().localeID);
2293: }
2294:
2295: /**
2296: * Returns this locale's language localized for display in the provided locale.
2297: * @param displayLocale the locale in which to display the name.
2298: * @return the localized language name.
2299: * @stable ICU 3.0
2300: */
2301: public String getDisplayLanguage(ULocale displayLocale) {
2302: return getDisplayLanguageInternal(localeID,
2303: displayLocale.localeID);
2304: }
2305:
2306: /**
2307: * Returns a locale's language localized for display in the provided locale.
2308: * This is a cover for the ICU4C API.
2309: * @param localeID the id of the locale whose language will be displayed
2310: * @param displayLocaleID the id of the locale in which to display the name.
2311: * @return the localized language name.
2312: * @stable ICU 3.0
2313: */
2314: public static String getDisplayLanguage(String localeID,
2315: String displayLocaleID) {
2316: return getDisplayLanguageInternal(localeID,
2317: getName(displayLocaleID));
2318: }
2319:
2320: /**
2321: * Returns a locale's language localized for display in the provided locale.
2322: * This is a cover for the ICU4C API.
2323: * @param localeID the id of the locale whose language will be displayed.
2324: * @param displayLocale the locale in which to display the name.
2325: * @return the localized language name.
2326: * @stable ICU 3.0
2327: */
2328: public static String getDisplayLanguage(String localeID,
2329: ULocale displayLocale) {
2330: return getDisplayLanguageInternal(localeID,
2331: displayLocale.localeID);
2332: }
2333:
2334: static String getCurrentCountryID(String oldID) {
2335: initCountryTables();
2336: int offset = findIndex(_deprecatedCountries, oldID);
2337: if (offset >= 0) {
2338: return _replacementCountries[offset];
2339: }
2340: return oldID;
2341: }
2342:
2343: static String getCurrentLanguageID(String oldID) {
2344: initLanguageTables();
2345: int offset = findIndex(_obsoleteLanguages, oldID);
2346: if (offset >= 0) {
2347: return _replacementLanguages[offset];
2348: }
2349: return oldID;
2350: }
2351:
2352: // displayLocaleID is canonical, localeID need not be since parsing will fix this.
2353: private static String getDisplayLanguageInternal(String localeID,
2354: String displayLocaleID) {
2355: return getTableString("Languages", null, new IDParser(localeID)
2356: .getLanguage(), displayLocaleID);
2357: }
2358:
2359: /**
2360: * Returns this locale's script localized for display in the default locale.
2361: * @return the localized script name.
2362: * @stable ICU 3.0
2363: */
2364: public String getDisplayScript() {
2365: return getDisplayScriptInternal(localeID, getDefault().localeID);
2366: }
2367:
2368: /**
2369: * Returns this locale's script localized for display in the provided locale.
2370: * @param displayLocale the locale in which to display the name.
2371: * @return the localized script name.
2372: * @stable ICU 3.0
2373: */
2374: public String getDisplayScript(ULocale displayLocale) {
2375: return getDisplayScriptInternal(localeID,
2376: displayLocale.localeID);
2377: }
2378:
2379: /**
2380: * Returns a locale's script localized for display in the provided locale.
2381: * This is a cover for the ICU4C API.
2382: * @param localeID the id of the locale whose script will be displayed
2383: * @param displayLocaleID the id of the locale in which to display the name.
2384: * @return the localized script name.
2385: * @stable ICU 3.0
2386: */
2387: public static String getDisplayScript(String localeID,
2388: String displayLocaleID) {
2389: return getDisplayScriptInternal(localeID,
2390: getName(displayLocaleID));
2391: }
2392:
2393: /**
2394: * Returns a locale's script localized for display in the provided locale.
2395: * @param localeID the id of the locale whose script will be displayed.
2396: * @param displayLocale the locale in which to display the name.
2397: * @return the localized script name.
2398: * @stable ICU 3.0
2399: */
2400: public static String getDisplayScript(String localeID,
2401: ULocale displayLocale) {
2402: return getDisplayScriptInternal(localeID,
2403: displayLocale.localeID);
2404: }
2405:
2406: // displayLocaleID is canonical, localeID need not be since parsing will fix this.
2407: private static String getDisplayScriptInternal(String localeID,
2408: String displayLocaleID) {
2409: return getTableString("Scripts", null, new IDParser(localeID)
2410: .getScript(), displayLocaleID);
2411: }
2412:
2413: /**
2414: * Returns this locale's country localized for display in the default locale.
2415: * @return the localized country name.
2416: * @stable ICU 3.0
2417: */
2418: public String getDisplayCountry() {
2419: return getDisplayCountryInternal(localeID,
2420: getDefault().localeID);
2421: }
2422:
2423: /**
2424: * Returns this locale's country localized for display in the provided locale.
2425: * @param displayLocale the locale in which to display the name.
2426: * @return the localized country name.
2427: * @stable ICU 3.0
2428: */
2429: public String getDisplayCountry(ULocale displayLocale) {
2430: return getDisplayCountryInternal(localeID,
2431: displayLocale.localeID);
2432: }
2433:
2434: /**
2435: * Returns a locale's country localized for display in the provided locale.
2436: * This is a cover for the ICU4C API.
2437: * @param localeID the id of the locale whose country will be displayed
2438: * @param displayLocaleID the id of the locale in which to display the name.
2439: * @return the localized country name.
2440: * @stable ICU 3.0
2441: */
2442: public static String getDisplayCountry(String localeID,
2443: String displayLocaleID) {
2444: return getDisplayCountryInternal(localeID,
2445: getName(displayLocaleID));
2446: }
2447:
2448: /**
2449: * Returns a locale's country localized for display in the provided locale.
2450: * This is a cover for the ICU4C API.
2451: * @param localeID the id of the locale whose country will be displayed.
2452: * @param displayLocale the locale in which to display the name.
2453: * @return the localized country name.
2454: * @stable ICU 3.0
2455: */
2456: public static String getDisplayCountry(String localeID,
2457: ULocale displayLocale) {
2458: return getDisplayCountryInternal(localeID,
2459: displayLocale.localeID);
2460: }
2461:
2462: // displayLocaleID is canonical, localeID need not be since parsing will fix this.
2463: private static String getDisplayCountryInternal(String localeID,
2464: String displayLocaleID) {
2465: return getTableString("Countries", null, new IDParser(localeID)
2466: .getCountry(), displayLocaleID);
2467: }
2468:
2469: /**
2470: * Returns this locale's variant localized for display in the default locale.
2471: * @return the localized variant name.
2472: * @stable ICU 3.0
2473: */
2474: public String getDisplayVariant() {
2475: return getDisplayVariantInternal(localeID,
2476: getDefault().localeID);
2477: }
2478:
2479: /**
2480: * Returns this locale's variant localized for display in the provided locale.
2481: * @param displayLocale the locale in which to display the name.
2482: * @return the localized variant name.
2483: * @stable ICU 3.0
2484: */
2485: public String getDisplayVariant(ULocale displayLocale) {
2486: return getDisplayVariantInternal(localeID,
2487: displayLocale.localeID);
2488: }
2489:
2490: /**
2491: * Returns a locale's variant localized for display in the provided locale.
2492: * This is a cover for the ICU4C API.
2493: * @param localeID the id of the locale whose variant will be displayed
2494: * @param displayLocaleID the id of the locale in which to display the name.
2495: * @return the localized variant name.
2496: * @stable ICU 3.0
2497: */
2498: public static String getDisplayVariant(String localeID,
2499: String displayLocaleID) {
2500: return getDisplayVariantInternal(localeID,
2501: getName(displayLocaleID));
2502: }
2503:
2504: /**
2505: * Returns a locale's variant localized for display in the provided locale.
2506: * This is a cover for the ICU4C API.
2507: * @param localeID the id of the locale whose variant will be displayed.
2508: * @param displayLocale the locale in which to display the name.
2509: * @return the localized variant name.
2510: * @stable ICU 3.0
2511: */
2512: public static String getDisplayVariant(String localeID,
2513: ULocale displayLocale) {
2514: return getDisplayVariantInternal(localeID,
2515: displayLocale.localeID);
2516: }
2517:
2518: // displayLocaleID is canonical, localeID need not be since parsing will fix this.
2519: private static String getDisplayVariantInternal(String localeID,
2520: String displayLocaleID) {
2521: return getTableString("Variants", null, new IDParser(localeID)
2522: .getVariant(), displayLocaleID);
2523: }
2524:
2525: /**
2526: * Returns a keyword localized for display in the default locale.
2527: * @param keyword the keyword to be displayed.
2528: * @return the localized keyword name.
2529: * @see #getKeywords
2530: * @stable ICU 3.0
2531: */
2532: public static String getDisplayKeyword(String keyword) {
2533: return getDisplayKeywordInternal(keyword, getDefault().localeID);
2534: }
2535:
2536: /**
2537: * Returns a keyword localized for display in the specified locale.
2538: * @param keyword the keyword to be displayed.
2539: * @param displayLocaleID the id of the locale in which to display the keyword.
2540: * @return the localized keyword name.
2541: * @see #getKeywords
2542: * @stable ICU 3.0
2543: */
2544: public static String getDisplayKeyword(String keyword,
2545: String displayLocaleID) {
2546: return getDisplayKeywordInternal(keyword,
2547: getName(displayLocaleID));
2548: }
2549:
2550: /**
2551: * Returns a keyword localized for display in the specified locale.
2552: * @param keyword the keyword to be displayed.
2553: * @param displayLocale the locale in which to display the keyword.
2554: * @return the localized keyword name.
2555: * @see #getKeywords
2556: * @stable ICU 3.0
2557: */
2558: public static String getDisplayKeyword(String keyword,
2559: ULocale displayLocale) {
2560: return getDisplayKeywordInternal(keyword,
2561: displayLocale.localeID);
2562: }
2563:
2564: // displayLocaleID is canonical, localeID need not be since parsing will fix this.
2565: private static String getDisplayKeywordInternal(String keyword,
2566: String displayLocaleID) {
2567: return getTableString("Keys", null, keyword.trim()
2568: .toLowerCase(), displayLocaleID);
2569: }
2570:
2571: /**
2572: * Returns a keyword value localized for display in the default locale.
2573: * @param keyword the keyword whose value is to be displayed.
2574: * @return the localized value name.
2575: * @stable ICU 3.0
2576: */
2577: public String getDisplayKeywordValue(String keyword) {
2578: return getDisplayKeywordValueInternal(localeID, keyword,
2579: getDefault().localeID);
2580: }
2581:
2582: /**
2583: * Returns a keyword value localized for display in the specified locale.
2584: * @param keyword the keyword whose value is to be displayed.
2585: * @param displayLocale the locale in which to display the value.
2586: * @return the localized value name.
2587: * @stable ICU 3.0
2588: */
2589: public String getDisplayKeywordValue(String keyword,
2590: ULocale displayLocale) {
2591: return getDisplayKeywordValueInternal(localeID, keyword,
2592: displayLocale.localeID);
2593: }
2594:
2595: /**
2596: * Returns a keyword value localized for display in the specified locale.
2597: * This is a cover for the ICU4C API.
2598: * @param localeID the id of the locale whose keyword value is to be displayed.
2599: * @param keyword the keyword whose value is to be displayed.
2600: * @param displayLocaleID the id of the locale in which to display the value.
2601: * @return the localized value name.
2602: * @stable ICU 3.0
2603: */
2604: public static String getDisplayKeywordValue(String localeID,
2605: String keyword, String displayLocaleID) {
2606: return getDisplayKeywordValueInternal(localeID, keyword,
2607: getName(displayLocaleID));
2608: }
2609:
2610: /**
2611: * Returns a keyword value localized for display in the specified locale.
2612: * This is a cover for the ICU4C API.
2613: * @param localeID the id of the locale whose keyword value is to be displayed.
2614: * @param keyword the keyword whose value is to be displayed.
2615: * @param displayLocale the id of the locale in which to display the value.
2616: * @return the localized value name.
2617: * @stable ICU 3.0
2618: */
2619: public static String getDisplayKeywordValue(String localeID,
2620: String keyword, ULocale displayLocale) {
2621: return getDisplayKeywordValueInternal(localeID, keyword,
2622: displayLocale.localeID);
2623: }
2624:
2625: // displayLocaleID is canonical, localeID need not be since parsing will fix this.
2626: private static String getDisplayKeywordValueInternal(
2627: String localeID, String keyword, String displayLocaleID) {
2628: keyword = keyword.trim().toLowerCase();
2629: String value = new IDParser(localeID).getKeywordValue(keyword);
2630: return getTableString("Types", keyword, value, displayLocaleID);
2631: }
2632:
2633: /**
2634: * Returns this locale name localized for display in the default locale.
2635: * @return the localized locale name.
2636: * @stable ICU 3.0
2637: */
2638: public String getDisplayName() {
2639: return getDisplayNameInternal(localeID, getDefault().localeID);
2640: }
2641:
2642: /**
2643: * Returns this locale name localized for display in the provided locale.
2644: * @param displayLocale the locale in which to display the locale name.
2645: * @return the localized locale name.
2646: * @stable ICU 3.0
2647: */
2648: public String getDisplayName(ULocale displayLocale) {
2649: return getDisplayNameInternal(localeID, displayLocale.localeID);
2650: }
2651:
2652: /**
2653: * Returns the locale ID localized for display in the provided locale.
2654: * This is a cover for the ICU4C API.
2655: * @param localeID the locale whose name is to be displayed.
2656: * @param displayLocaleID the id of the locale in which to display the locale name.
2657: * @return the localized locale name.
2658: * @stable ICU 3.0
2659: */
2660: public static String getDisplayName(String localeID,
2661: String displayLocaleID) {
2662: return getDisplayNameInternal(localeID,
2663: getName(displayLocaleID));
2664: }
2665:
2666: /**
2667: * Returns the locale ID localized for display in the provided locale.
2668: * This is a cover for the ICU4C API.
2669: * @param localeID the locale whose name is to be displayed.
2670: * @param displayLocale the locale in which to display the locale name.
2671: * @return the localized locale name.
2672: * @stable ICU 3.0
2673: */
2674: public static String getDisplayName(String localeID,
2675: ULocale displayLocale) {
2676: return getDisplayNameInternal(localeID, displayLocale.localeID);
2677: }
2678:
2679: // displayLocaleID is canonical, localeID need not be since parsing will fix this.
2680: private static String getDisplayNameInternal(String localeID,
2681: String displayLocaleID) {
2682: // lang
2683: // lang (script, country, variant, keyword=value, ...)
2684: // script, country, variant, keyword=value, ...
2685:
2686: final String[] tableNames = { "Languages", "Scripts",
2687: "Countries", "Variants" };
2688:
2689: ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle
2690: .getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
2691: displayLocaleID);
2692:
2693: StringBuffer buf = new StringBuffer();
2694:
2695: IDParser parser = new IDParser(localeID);
2696: String[] names = parser.getLanguageScriptCountryVariant();
2697:
2698: boolean haveLanguage = names[0].length() > 0;
2699: boolean openParen = false;
2700: for (int i = 0; i < names.length; ++i) {
2701: String name = names[i];
2702: if (name.length() > 0) {
2703: name = getTableString(tableNames[i], null, name, bundle);
2704: if (buf.length() > 0) { // need a separator
2705: if (haveLanguage & !openParen) {
2706: buf.append(" (");
2707: openParen = true;
2708: } else {
2709: buf.append(", ");
2710: }
2711: }
2712: buf.append(name);
2713: }
2714: }
2715:
2716: Map m = parser.getKeywordMap();
2717: if (!m.isEmpty()) {
2718: Iterator keys = m.entrySet().iterator();
2719: while (keys.hasNext()) {
2720: if (buf.length() > 0) {
2721: if (haveLanguage & !openParen) {
2722: buf.append(" (");
2723: openParen = true;
2724: } else {
2725: buf.append(", ");
2726: }
2727: }
2728: Map.Entry e = (Map.Entry) keys.next();
2729: String key = (String) e.getKey();
2730: String val = (String) e.getValue();
2731: buf.append(getTableString("Keys", null, key, bundle));
2732: buf.append("=");
2733: buf.append(getTableString("Types", key, val, bundle));
2734: }
2735: }
2736:
2737: if (openParen) {
2738: buf.append(")");
2739: }
2740:
2741: return buf.toString();
2742: }
2743:
2744: /**
2745: * Selector for <tt>getLocale()</tt> indicating the locale of the
2746: * resource containing the data. This is always at or above the
2747: * valid locale. If the valid locale does not contain the
2748: * specific data being requested, then the actual locale will be
2749: * above the valid locale. If the object was not constructed from
2750: * locale data, then the valid locale is <i>null</i>.
2751: *
2752: * @draft ICU 2.8 (retain)
2753: * @provisional This API might change or be removed in a future release.
2754: */
2755: public static Type ACTUAL_LOCALE = new Type(0);
2756:
2757: /**
2758: * Selector for <tt>getLocale()</tt> indicating the most specific
2759: * locale for which any data exists. This is always at or above
2760: * the requested locale, and at or below the actual locale. If
2761: * the requested locale does not correspond to any resource data,
2762: * then the valid locale will be above the requested locale. If
2763: * the object was not constructed from locale data, then the
2764: * actual locale is <i>null</i>.
2765: *
2766: * <p>Note: The valid locale will be returned correctly in ICU
2767: * 3.0 or later. In ICU 2.8, it is not returned correctly.
2768: * @draft ICU 2.8 (retain)
2769: * @provisional This API might change or be removed in a future release.
2770: */
2771: public static Type VALID_LOCALE = new Type(1);
2772:
2773: /**
2774: * Opaque selector enum for <tt>getLocale()</tt>.
2775: * @see com.ibm.icu.util.ULocale
2776: * @see com.ibm.icu.util.ULocale#ACTUAL_LOCALE
2777: * @see com.ibm.icu.util.ULocale#VALID_LOCALE
2778: * @draft ICU 2.8 (retainAll)
2779: * @provisional This API might change or be removed in a future release.
2780: */
2781: public static final class Type {
2782: private int localeType;
2783:
2784: private Type(int type) {
2785: localeType = type;
2786: }
2787: }
2788:
2789: /**
2790: * Based on a HTTP formatted list of acceptable locales, determine an available locale for the user.
2791: * NullPointerException is thrown if acceptLanguageList or availableLocales is
2792: * null. If fallback is non-null, it will contain true if a fallback locale (one
2793: * not in the acceptLanguageList) was returned. The value on entry is ignored.
2794: * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
2795: * if a ROOT locale was used as a fallback (because nothing else in
2796: * availableLocales matched). No ULocale array element should be null; behavior
2797: * is undefined if this is the case.
2798: * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
2799: * @param availableLocales list of available locales. One of these will be returned.
2800: * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
2801: * @return one of the locales from the availableLocales list, or null if none match
2802: * @draft ICU 3.4
2803: * @provisional This API might change or be removed in a future release.
2804: */
2805:
2806: public static ULocale acceptLanguage(String acceptLanguageList,
2807: ULocale[] availableLocales, boolean[] fallback) {
2808: /**
2809: * @internal ICU 3.4
2810: */
2811: class ULocaleAcceptLanguageQ implements Comparable {
2812: private double q;
2813: private double serial;
2814:
2815: public ULocaleAcceptLanguageQ(double theq, int theserial) {
2816: q = theq;
2817: serial = theserial;
2818: }
2819:
2820: public int compareTo(Object o) {
2821: ULocaleAcceptLanguageQ other = (ULocaleAcceptLanguageQ) o;
2822: if (q > other.q) { // reverse - to sort in descending order
2823: return -1;
2824: } else if (q < other.q) {
2825: return 1;
2826: }
2827: if (serial < other.serial) {
2828: return -1;
2829: } else if (serial > other.serial) {
2830: return 1;
2831: } else {
2832: return 0; // same object
2833: }
2834: }
2835: }
2836:
2837: // 1st: parse out the acceptLanguageList into an array
2838:
2839: TreeMap map = new TreeMap();
2840:
2841: final int l = acceptLanguageList.length();
2842: int n;
2843: int last = -1;
2844: for (n = 0; n < l; n++) {
2845: int itemEnd = acceptLanguageList.indexOf(',', n);
2846: if (itemEnd == -1) {
2847: itemEnd = l;
2848: }
2849: int paramEnd = acceptLanguageList.indexOf(';', n);
2850: double q = 1.0;
2851:
2852: if ((paramEnd != -1) && (paramEnd < itemEnd)) {
2853: /* semicolon (;) is closer than end (,) */
2854: int t = paramEnd + 1;
2855: while (UCharacter.isWhitespace(acceptLanguageList
2856: .charAt(t))) {
2857: t++;
2858: }
2859: if (acceptLanguageList.charAt(t) == 'q') {
2860: t++;
2861: }
2862: while (UCharacter.isWhitespace(acceptLanguageList
2863: .charAt(t))) {
2864: t++;
2865: }
2866: if (acceptLanguageList.charAt(t) == '=') {
2867: t++;
2868: }
2869: while (UCharacter.isWhitespace(acceptLanguageList
2870: .charAt(t))) {
2871: t++;
2872: }
2873: try {
2874: String val = acceptLanguageList.substring(t,
2875: itemEnd).trim();
2876: q = Double.parseDouble(val);
2877: } catch (NumberFormatException nfe) {
2878: q = 1.0;
2879: }
2880: } else {
2881: q = 1.0; //default
2882: paramEnd = itemEnd;
2883: }
2884:
2885: String loc = acceptLanguageList.substring(n, paramEnd)
2886: .trim();
2887: int serial = map.size();
2888: ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(
2889: q, serial);
2890: map.put(entry, new ULocale(canonicalize(loc))); // sort in reverse order.. 1.0, 0.9, 0.8 .. etc
2891: n = itemEnd; // get next item. (n++ will skip over delimiter)
2892: }
2893:
2894: // 2. pull out the map
2895: ULocale acceptList[] = (ULocale[]) map.values().toArray(
2896: new ULocale[map.size()]);
2897:
2898: // 3. call the real function
2899: return acceptLanguage(acceptList, availableLocales, fallback);
2900: }
2901:
2902: /**
2903: * Based on a list of acceptable locales, determine an available locale for the user.
2904: * NullPointerException is thrown if acceptLanguageList or availableLocales is
2905: * null. If fallback is non-null, it will contain true if a fallback locale (one
2906: * not in the acceptLanguageList) was returned. The value on entry is ignored.
2907: * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
2908: * if a ROOT locale was used as a fallback (because nothing else in
2909: * availableLocales matched). No ULocale array element should be null; behavior
2910: * is undefined if this is the case.
2911: * @param acceptLanguageList list of acceptable locales
2912: * @param availableLocales list of available locales. One of these will be returned.
2913: * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
2914: * @return one of the locales from the availableLocales list, or null if none match
2915: * @draft ICU 3.4
2916: * @provisional This API might change or be removed in a future release.
2917: */
2918:
2919: public static ULocale acceptLanguage(ULocale[] acceptLanguageList,
2920: ULocale[] availableLocales, boolean[] fallback) {
2921: // fallbacklist
2922: int i, j;
2923: if (fallback != null) {
2924: fallback[0] = true;
2925: }
2926: for (i = 0; i < acceptLanguageList.length; i++) {
2927: ULocale aLocale = acceptLanguageList[i];
2928: boolean[] setFallback = fallback;
2929: do {
2930: for (j = 0; j < availableLocales.length; j++) {
2931: if (availableLocales[j].equals(aLocale)) {
2932: if (setFallback != null) {
2933: setFallback[0] = false; // first time with this locale - not a fallback.
2934: }
2935: return availableLocales[j];
2936: }
2937: }
2938: Locale loc = aLocale.toLocale();
2939: Locale parent = LocaleUtility.fallback(loc);
2940: if (parent != null) {
2941: aLocale = new ULocale(parent);
2942: } else {
2943: aLocale = null;
2944: }
2945: setFallback = null; // Do not set fallback in later iterations
2946: } while (aLocale != null);
2947: }
2948: return null;
2949: }
2950:
2951: /**
2952: * Based on a HTTP formatted list of acceptable locales, determine an available locale for the user.
2953: * NullPointerException is thrown if acceptLanguageList or availableLocales is
2954: * null. If fallback is non-null, it will contain true if a fallback locale (one
2955: * not in the acceptLanguageList) was returned. The value on entry is ignored.
2956: * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
2957: * if a ROOT locale was used as a fallback (because nothing else in
2958: * availableLocales matched). No ULocale array element should be null; behavior
2959: * is undefined if this is the case.
2960: * This function will choose a locale from the ULocale.getAvailableLocales() list as available.
2961: * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales
2962: * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
2963: * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
2964: * @draft ICU 3.4
2965: * @provisional This API might change or be removed in a future release.
2966: */
2967:
2968: public static ULocale acceptLanguage(String acceptLanguageList,
2969: boolean[] fallback) {
2970: return acceptLanguage(acceptLanguageList, ULocale
2971: .getAvailableLocales(), fallback);
2972: }
2973:
2974: /**
2975: * Based on an ordered array of acceptable locales, determine an available locale for the user.
2976: * NullPointerException is thrown if acceptLanguageList or availableLocales is
2977: * null. If fallback is non-null, it will contain true if a fallback locale (one
2978: * not in the acceptLanguageList) was returned. The value on entry is ignored.
2979: * ULocale will be one of the locales in availableLocales, or the ROOT ULocale if
2980: * if a ROOT locale was used as a fallback (because nothing else in
2981: * availableLocales matched). No ULocale array element should be null; behavior
2982: * is undefined if this is the case.
2983: * This function will choose a locale from the ULocale.getAvailableLocales() list as available.
2984: * @param acceptLanguageList ordered array of acceptable locales (preferred are listed first)
2985: * @param fallback if non-null, a 1-element array containing a boolean to be set with the fallback status
2986: * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match
2987: * @draft ICU 3.4
2988: * @provisional This API might change or be removed in a future release.
2989: */
2990:
2991: public static ULocale acceptLanguage(ULocale[] acceptLanguageList,
2992: boolean[] fallback) {
2993: return acceptLanguage(acceptLanguageList, ULocale
2994: .getAvailableLocales(), fallback);
2995: }
2996: }
|