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