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