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