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