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