1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2004-2014, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.util; 10 11 import java.util.ArrayList; 12 import java.util.Arrays; 13 import java.util.BitSet; 14 import java.util.Date; 15 import java.util.HashMap; 16 import java.util.List; 17 import java.util.Map; 18 import java.util.MissingResourceException; 19 import java.util.ResourceBundle; 20 import java.util.Set; 21 22 import com.ibm.icu.impl.Utility; 23 import com.ibm.icu.text.BreakIterator; 24 import com.ibm.icu.text.Collator; 25 import com.ibm.icu.text.DateFormat; 26 import com.ibm.icu.text.NumberFormat; 27 import com.ibm.icu.text.SimpleDateFormat; 28 29 /** 30 * This convenience class provides a mechanism for bundling together different 31 * globalization preferences. It includes: 32 * <ul> 33 * <li>A list of locales/languages in preference order</li> 34 * <li>A territory</li> 35 * <li>A currency</li> 36 * <li>A timezone</li> 37 * <li>A calendar</li> 38 * <li>A collator (for language-sensitive sorting, searching, and matching).</li> 39 * <li>Explicit overrides for date/time formats, etc.</li> 40 * </ul> 41 * The class will heuristically compute implicit, heuristic values for the above 42 * based on available data if explicit values are not supplied. These implicit 43 * values can be presented to users for confirmation, or replacement if the 44 * values are incorrect. 45 * <p> 46 * To reset any explicit field so that it will get heuristic values, pass in 47 * null. For example, myPreferences.setLocale(null); 48 * <p> 49 * All of the heuristics can be customized by subclasses, by overriding 50 * getTerritory(), guessCollator(), etc. 51 * <p> 52 * The class also supplies display names for languages, scripts, territories, 53 * currencies, timezones, etc. These are computed according to the 54 * locale/language preference list. Thus, if the preference is Breton; French; 55 * English, then the display name for a language will be returned in Breton if 56 * available, otherwise in French if available, otherwise in English. 57 * <p> 58 * The codes used to reference territory, currency, etc. are as defined elsewhere 59 * in ICU, and are taken from CLDR (which reflects RFC 3066bis usage, ISO 4217, 60 * and the TZ Timezone database identifiers). 61 * <p> 62 * <b>This is at a prototype stage, and has not incorporated all the design 63 * changes that we would like yet; further feedback is welcome.</b></p> 64 * Note: 65 * <ul> 66 * <li>to get the display name for the first day of the week, use the calendar + 67 * display names.</li> 68 * <li>to get the work days, ask the calendar (when that is available).</li> 69 * <li>to get papersize / measurement system/bidi-orientation, ask the locale 70 * (when that is available there)</li> 71 * <li>to get the field order in a date, and whether a time is 24hour or not, 72 * ask the DateFormat (when that is available there)</li> 73 * <li>it will support HOST locale when it becomes available (it is a special 74 * locale that will ask the services to use the host platform's values).</li> 75 * </ul> 76 * 77 * @draft ICU 3.6 (retainAll) 78 */ 79 80 //TODO: 81 // - Add Holidays 82 // - Add convenience to get/take Locale as well as ULocale. 83 // - Add Lenient datetime formatting when that is available. 84 // - Should this be serializable? 85 // - Other utilities? 86 87 public class GlobalizationPreferences implements Freezable<GlobalizationPreferences> { 88 89 /** 90 * Default constructor 91 * @draft ICU 3.6 92 */ GlobalizationPreferences()93 public GlobalizationPreferences(){} 94 /** 95 * Number Format type 96 * @draft ICU 3.6 97 */ 98 public static final int 99 NF_NUMBER = 0, // NumberFormat.NUMBERSTYLE 100 NF_CURRENCY = 1, // NumberFormat.CURRENCYSTYLE 101 NF_PERCENT = 2, // NumberFormat.PERCENTSTYLE 102 NF_SCIENTIFIC = 3, // NumberFormat.SCIENTIFICSTYLE 103 NF_INTEGER = 4; // NumberFormat.INTEGERSTYLE 104 105 private static final int NF_LIMIT = NF_INTEGER + 1; 106 107 /** 108 * Date Format type 109 * @draft ICU 3.6 110 */ 111 public static final int 112 DF_FULL = DateFormat.FULL, // 0 113 DF_LONG = DateFormat.LONG, // 1 114 DF_MEDIUM = DateFormat.MEDIUM, // 2 115 DF_SHORT = DateFormat.SHORT, // 3 116 DF_NONE = 4; 117 118 private static final int DF_LIMIT = DF_NONE + 1; 119 120 /** 121 * For selecting a choice of display names 122 * @draft ICU 3.6 123 */ 124 public static final int 125 ID_LOCALE = 0, 126 ID_LANGUAGE = 1, 127 ID_SCRIPT = 2, 128 ID_TERRITORY = 3, 129 ID_VARIANT = 4, 130 ID_KEYWORD = 5, 131 ID_KEYWORD_VALUE = 6, 132 ID_CURRENCY = 7, 133 ID_CURRENCY_SYMBOL = 8, 134 ID_TIMEZONE = 9; 135 136 //private static final int ID_LIMIT = ID_TIMEZONE + 1; 137 138 /** 139 * Break iterator type 140 * @draft ICU 3.6 141 */ 142 public static final int 143 BI_CHARACTER = BreakIterator.KIND_CHARACTER, // 0 144 BI_WORD = BreakIterator.KIND_WORD, // 1 145 BI_LINE = BreakIterator.KIND_LINE, // 2 146 BI_SENTENCE = BreakIterator.KIND_SENTENCE, // 3 147 BI_TITLE = BreakIterator.KIND_TITLE; // 4 148 149 private static final int BI_LIMIT = BI_TITLE + 1; 150 151 /** 152 * Sets the language/locale priority list. If other information is 153 * not (yet) available, this is used to to produce a default value 154 * for the appropriate territory, currency, timezone, etc. The 155 * user should be given the opportunity to correct those defaults 156 * in case they are incorrect. 157 * 158 * @param inputLocales list of locales in priority order, eg {"be", "fr"} 159 * for Breton first, then French if that fails. 160 * @return this, for chaining 161 * @draft ICU 3.6 162 */ setLocales(List<ULocale> inputLocales)163 public GlobalizationPreferences setLocales(List<ULocale> inputLocales) { 164 if (isFrozen()) { 165 throw new UnsupportedOperationException("Attempt to modify immutable object"); 166 } 167 locales = processLocales(inputLocales); 168 return this; 169 } 170 171 /** 172 * Get a copy of the language/locale priority list 173 * 174 * @return a copy of the language/locale priority list. 175 * @draft ICU 3.6 176 */ getLocales()177 public List<ULocale> getLocales() { 178 List<ULocale> result; 179 if (locales == null) { 180 result = guessLocales(); 181 } else { 182 result = new ArrayList<>(); 183 result.addAll(locales); 184 } 185 return result; 186 } 187 188 /** 189 * Convenience function for getting the locales in priority order 190 * @param index The index (0..n) of the desired item. 191 * @return desired item. null if index is out of range 192 * @draft ICU 3.6 193 */ getLocale(int index)194 public ULocale getLocale(int index) { 195 List<ULocale> lcls = locales; 196 if (lcls == null) { 197 lcls = guessLocales(); 198 } 199 if (index >= 0 && index < lcls.size()) { 200 return lcls.get(index); 201 } 202 return null; 203 } 204 205 /** 206 * Convenience routine for setting the language/locale priority 207 * list from an array. 208 * 209 * @see #setLocales(List locales) 210 * @param uLocales list of locales in an array 211 * @return this, for chaining 212 * @draft ICU 3.6 213 */ setLocales(ULocale[] uLocales)214 public GlobalizationPreferences setLocales(ULocale[] uLocales) { 215 if (isFrozen()) { 216 throw new UnsupportedOperationException("Attempt to modify immutable object"); 217 } 218 return setLocales(Arrays.asList(uLocales)); 219 } 220 221 /** 222 * Convenience routine for setting the language/locale priority 223 * list from a single locale/language. 224 * 225 * @see #setLocales(List locales) 226 * @param uLocale single locale 227 * @return this, for chaining 228 * @draft ICU 3.6 229 */ setLocale(ULocale uLocale)230 public GlobalizationPreferences setLocale(ULocale uLocale) { 231 if (isFrozen()) { 232 throw new UnsupportedOperationException("Attempt to modify immutable object"); 233 } 234 return setLocales(new ULocale[]{uLocale}); 235 } 236 237 /** 238 * Convenience routine for setting the locale priority list from 239 * an Accept-Language string. 240 * @see #setLocales(List locales) 241 * @param acceptLanguageString Accept-Language list, as defined by 242 * Section 14.4 of the RFC 2616 (HTTP 1.1) 243 * @return this, for chaining 244 * @draft ICU 3.6 245 */ setLocales(String acceptLanguageString)246 public GlobalizationPreferences setLocales(String acceptLanguageString) { 247 if (isFrozen()) { 248 throw new UnsupportedOperationException("Attempt to modify immutable object"); 249 } 250 Set<ULocale> acceptSet = LocalePriorityList.add(acceptLanguageString).build().getULocales(); 251 // processLocales() wants a List even though it only iterates front-to-back. 252 locales = processLocales(new ArrayList<>(acceptSet)); 253 return this; 254 } 255 256 /** 257 * Convenience function to get a ResourceBundle instance using 258 * the specified base name based on the language/locale priority list 259 * stored in this object. 260 * 261 * @param baseName the base name of the resource bundle, a fully qualified 262 * class name 263 * @return a resource bundle for the given base name and locale based on the 264 * language/locale priority list stored in this object 265 * @draft ICU 3.6 266 */ getResourceBundle(String baseName)267 public ResourceBundle getResourceBundle(String baseName) { 268 return getResourceBundle(baseName, null); 269 } 270 271 /** 272 * Convenience function to get a ResourceBundle instance using 273 * the specified base name and class loader based on the language/locale 274 * priority list stored in this object. 275 * 276 * @param baseName the base name of the resource bundle, a fully qualified 277 * class name 278 * @param loader the class object from which to load the resource bundle 279 * @return a resource bundle for the given base name and locale based on the 280 * language/locale priority list stored in this object 281 * @draft ICU 3.6 282 */ getResourceBundle(String baseName, ClassLoader loader)283 public ResourceBundle getResourceBundle(String baseName, ClassLoader loader) { 284 UResourceBundle urb = null; 285 UResourceBundle candidate = null; 286 String actualLocaleName = null; 287 List<ULocale> fallbacks = getLocales(); 288 for (int i = 0; i < fallbacks.size(); i++) { 289 String localeName = (fallbacks.get(i)).toString(); 290 if (actualLocaleName != null && localeName.equals(actualLocaleName)) { 291 // Actual locale name in the previous round may exactly matches 292 // with the next fallback locale 293 urb = candidate; 294 break; 295 } 296 try { 297 if (loader == null) { 298 candidate = UResourceBundle.getBundleInstance(baseName, localeName); 299 } 300 else { 301 candidate = UResourceBundle.getBundleInstance(baseName, localeName, loader); 302 } 303 if (candidate != null) { 304 actualLocaleName = candidate.getULocale().getName(); 305 if (actualLocaleName.equals(localeName)) { 306 urb = candidate; 307 break; 308 } 309 if (urb == null) { 310 // Preserve the available bundle as the last resort 311 urb = candidate; 312 } 313 } 314 } catch (MissingResourceException mre) { 315 actualLocaleName = null; 316 continue; 317 } 318 } 319 if (urb == null) { 320 throw new MissingResourceException("Can't find bundle for base name " 321 + baseName, baseName, ""); 322 } 323 return urb; 324 } 325 326 /** 327 * Sets the territory, which is a valid territory according to for 328 * RFC 3066 (or successor). If not otherwise set, default 329 * currency and timezone values will be set from this. The user 330 * should be given the opportunity to correct those defaults in 331 * case they are incorrect. 332 * 333 * @param territory code 334 * @return this, for chaining 335 * @draft ICU 3.6 336 */ setTerritory(String territory)337 public GlobalizationPreferences setTerritory(String territory) { 338 if (isFrozen()) { 339 throw new UnsupportedOperationException("Attempt to modify immutable object"); 340 } 341 this.territory = territory; // immutable, so don't need to clone 342 return this; 343 } 344 345 /** 346 * Gets the territory setting. If it wasn't explicitly set, it is 347 * computed from the general locale setting. 348 * 349 * @return territory code, explicit or implicit. 350 * @draft ICU 3.6 351 */ getTerritory()352 public String getTerritory() { 353 if (territory == null) { 354 return guessTerritory(); 355 } 356 return territory; // immutable, so don't need to clone 357 } 358 359 /** 360 * Sets the currency code. If this has not been set, uses default for territory. 361 * 362 * @param currency Valid ISO 4217 currency code. 363 * @return this, for chaining 364 * @draft ICU 3.6 365 */ setCurrency(Currency currency)366 public GlobalizationPreferences setCurrency(Currency currency) { 367 if (isFrozen()) { 368 throw new UnsupportedOperationException("Attempt to modify immutable object"); 369 } 370 this.currency = currency; // immutable, so don't need to clone 371 return this; 372 } 373 374 /** 375 * Get a copy of the currency computed according to the settings. 376 * 377 * @return currency code, explicit or implicit. 378 * @draft ICU 3.6 379 */ getCurrency()380 public Currency getCurrency() { 381 if (currency == null) { 382 return guessCurrency(); 383 } 384 return currency; // immutable, so don't have to clone 385 } 386 387 /** 388 * Sets the calendar. If this has not been set, uses default for territory. 389 * 390 * @param calendar arbitrary calendar 391 * @return this, for chaining 392 * @draft ICU 3.6 393 */ setCalendar(Calendar calendar)394 public GlobalizationPreferences setCalendar(Calendar calendar) { 395 if (isFrozen()) { 396 throw new UnsupportedOperationException("Attempt to modify immutable object"); 397 } 398 this.calendar = (Calendar) calendar.clone(); // clone for safety 399 return this; 400 } 401 402 /** 403 * Get a copy of the calendar according to the settings. 404 * 405 * @return calendar explicit or implicit. 406 * @draft ICU 3.6 407 */ getCalendar()408 public Calendar getCalendar() { 409 if (calendar == null) { 410 return guessCalendar(); 411 } 412 Calendar temp = (Calendar) calendar.clone(); // clone for safety 413 temp.setTimeZone(getTimeZone()); 414 temp.setTimeInMillis(System.currentTimeMillis()); 415 return temp; 416 } 417 418 /** 419 * Sets the timezone ID. If this has not been set, uses default for territory. 420 * 421 * @param timezone a valid TZID (see UTS#35). 422 * @return this, for chaining 423 * @draft ICU 3.6 424 */ setTimeZone(TimeZone timezone)425 public GlobalizationPreferences setTimeZone(TimeZone timezone) { 426 if (isFrozen()) { 427 throw new UnsupportedOperationException("Attempt to modify immutable object"); 428 } 429 this.timezone = (TimeZone) timezone.clone(); // clone for safety; 430 return this; 431 } 432 433 /** 434 * Get the timezone. It was either explicitly set, or is 435 * heuristically computed from other settings. 436 * 437 * @return timezone, either implicitly or explicitly set 438 * @draft ICU 3.6 439 */ getTimeZone()440 public TimeZone getTimeZone() { 441 if (timezone == null) { 442 return guessTimeZone(); 443 } 444 return timezone.cloneAsThawed(); // clone for safety 445 } 446 447 /** 448 * Get a copy of the collator according to the settings. 449 * 450 * @return collator explicit or implicit. 451 * @draft ICU 3.6 452 */ getCollator()453 public Collator getCollator() { 454 if (collator == null) { 455 return guessCollator(); 456 } 457 try { 458 return (Collator) collator.clone(); // clone for safety 459 } catch (CloneNotSupportedException e) { 460 throw new ICUCloneNotSupportedException("Error in cloning collator", e); 461 } 462 } 463 464 /** 465 * Explicitly set the collator for this object. 466 * @param collator The collator object to be passed. 467 * @return this, for chaining 468 * @draft ICU 3.6 469 */ setCollator(Collator collator)470 public GlobalizationPreferences setCollator(Collator collator) { 471 if (isFrozen()) { 472 throw new UnsupportedOperationException("Attempt to modify immutable object"); 473 } 474 try { 475 this.collator = (Collator) collator.clone(); // clone for safety 476 } catch (CloneNotSupportedException e) { 477 throw new ICUCloneNotSupportedException("Error in cloning collator", e); 478 } 479 return this; 480 } 481 482 /** 483 * Get a copy of the break iterator for the specified type according to the 484 * settings. 485 * 486 * @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE 487 * @return break iterator explicit or implicit 488 * @draft ICU 3.6 489 */ getBreakIterator(int type)490 public BreakIterator getBreakIterator(int type) { 491 if (type < BI_CHARACTER || type >= BI_LIMIT) { 492 throw new IllegalArgumentException("Illegal break iterator type"); 493 } 494 if (breakIterators == null || breakIterators[type] == null) { 495 return guessBreakIterator(type); 496 } 497 return (BreakIterator) breakIterators[type].clone(); // clone for safety 498 } 499 500 /** 501 * Explicitly set the break iterator for this object. 502 * 503 * @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE 504 * @param iterator a break iterator 505 * @return this, for chaining 506 * @draft ICU 3.6 507 */ setBreakIterator(int type, BreakIterator iterator)508 public GlobalizationPreferences setBreakIterator(int type, BreakIterator iterator) { 509 if (type < BI_CHARACTER || type >= BI_LIMIT) { 510 throw new IllegalArgumentException("Illegal break iterator type"); 511 } 512 if (isFrozen()) { 513 throw new UnsupportedOperationException("Attempt to modify immutable object"); 514 } 515 if (breakIterators == null) 516 breakIterators = new BreakIterator[BI_LIMIT]; 517 breakIterators[type] = (BreakIterator) iterator.clone(); // clone for safety 518 return this; 519 } 520 521 /** 522 * Get the display name for an ID: language, script, territory, currency, timezone... 523 * Uses the language priority list to do so. 524 * 525 * @param id language code, script code, ... 526 * @param type specifies the type of the ID: ID_LANGUAGE, etc. 527 * @return the display name 528 * @draft ICU 3.6 529 */ getDisplayName(String id, int type)530 public String getDisplayName(String id, int type) { 531 String result = id; 532 for (ULocale locale : getLocales()) { 533 if (!isAvailableLocale(locale, TYPE_GENERIC)) { 534 continue; 535 } 536 switch (type) { 537 case ID_LOCALE: 538 result = ULocale.getDisplayName(id, locale); 539 break; 540 case ID_LANGUAGE: 541 result = ULocale.getDisplayLanguage(id, locale); 542 break; 543 case ID_SCRIPT: 544 result = ULocale.getDisplayScript("und-" + id, locale); 545 break; 546 case ID_TERRITORY: 547 result = ULocale.getDisplayCountry("und-" + id, locale); 548 break; 549 case ID_VARIANT: 550 // TODO fix variant parsing 551 result = ULocale.getDisplayVariant("und-QQ-" + id, locale); 552 break; 553 case ID_KEYWORD: 554 result = ULocale.getDisplayKeyword(id, locale); 555 break; 556 case ID_KEYWORD_VALUE: 557 String[] parts = new String[2]; 558 Utility.split(id,'=',parts); 559 result = ULocale.getDisplayKeywordValue("und@"+id, parts[0], locale); 560 // TODO fix to tell when successful 561 if (result.equals(parts[1])) { 562 continue; 563 } 564 break; 565 case ID_CURRENCY_SYMBOL: 566 case ID_CURRENCY: 567 Currency temp = new Currency(id); 568 result =temp.getName(locale, type==ID_CURRENCY 569 ? Currency.LONG_NAME 570 : Currency.SYMBOL_NAME, null /* isChoiceFormat */); 571 // TODO: have method that doesn't take parameter. Add 572 // function to determine whether string is choice 573 // format. 574 // TODO: have method that doesn't require us 575 // to create a currency 576 break; 577 case ID_TIMEZONE: 578 SimpleDateFormat dtf = new SimpleDateFormat("vvvv",locale); 579 dtf.setTimeZone(TimeZone.getFrozenTimeZone(id)); 580 result = dtf.format(new Date()); 581 // TODO, have method that doesn't require us to create a timezone 582 // fix other hacks 583 // hack for couldn't match 584 585 boolean isBadStr = false; 586 // Matcher badTimeZone = Pattern.compile("[A-Z]{2}|.*\\s\\([A-Z]{2}\\)").matcher(""); 587 // badtzstr = badTimeZone.reset(result).matches(); 588 String teststr = result; 589 int sidx = result.indexOf('('); 590 int eidx = result.indexOf(')'); 591 if (sidx != -1 && eidx != -1 && (eidx - sidx) == 3) { 592 teststr = result.substring(sidx+1, eidx); 593 } 594 if (teststr.length() == 2) { 595 isBadStr = true; 596 for (int i = 0; i < 2; i++) { 597 char c = teststr.charAt(i); 598 if (c < 'A' || 'Z' < c) { 599 isBadStr = false; 600 break; 601 } 602 } 603 } 604 if (isBadStr) { 605 continue; 606 } 607 break; 608 default: 609 throw new IllegalArgumentException("Unknown type: " + type); 610 } 611 612 // TODO need better way of seeing if we fell back to root!! 613 // This will not work at all for lots of stuff 614 if (!id.equals(result)) { 615 return result; 616 } 617 } 618 return result; 619 } 620 621 /** 622 * Set an explicit date format. Overrides the locale priority list for 623 * a particular combination of dateStyle and timeStyle. DF_NONE should 624 * be used if for the style, where only the date or time format individually 625 * is being set. 626 * 627 * @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 628 * @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 629 * @param format The date format 630 * @return this, for chaining 631 * @draft ICU 3.6 632 */ setDateFormat(int dateStyle, int timeStyle, DateFormat format)633 public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) { 634 if (isFrozen()) { 635 throw new UnsupportedOperationException("Attempt to modify immutable object"); 636 } 637 if (dateFormats == null) { 638 dateFormats = new DateFormat[DF_LIMIT][DF_LIMIT]; 639 } 640 dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety 641 return this; 642 } 643 644 /** 645 * Gets a date format according to the current settings. If there 646 * is an explicit (non-null) date/time format set, a copy of that 647 * is returned. Otherwise, the language priority list is used. 648 * DF_NONE should be used for the style, where only the date or 649 * time format individually is being gotten. 650 * 651 * @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 652 * @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE 653 * @return a DateFormat, according to the above description 654 * @draft ICU 3.6 655 */ getDateFormat(int dateStyle, int timeStyle)656 public DateFormat getDateFormat(int dateStyle, int timeStyle) { 657 if (dateStyle == DF_NONE && timeStyle == DF_NONE 658 || dateStyle < 0 || dateStyle >= DF_LIMIT 659 || timeStyle < 0 || timeStyle >= DF_LIMIT) { 660 throw new IllegalArgumentException("Illegal date format style arguments"); 661 } 662 DateFormat result = null; 663 if (dateFormats != null) { 664 result = dateFormats[dateStyle][timeStyle]; 665 } 666 if (result != null) { 667 result = (DateFormat) result.clone(); // clone for safety 668 // Not sure overriding configuration is what we really want... 669 result.setTimeZone(getTimeZone()); 670 } else { 671 result = guessDateFormat(dateStyle, timeStyle); 672 } 673 return result; 674 } 675 676 /** 677 * Gets a number format according to the current settings. If 678 * there is an explicit (non-null) number format set, a copy of 679 * that is returned. Otherwise, the language priority list is 680 * used. 681 * 682 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER 683 * @draft ICU 3.6 684 */ getNumberFormat(int style)685 public NumberFormat getNumberFormat(int style) { 686 if (style < 0 || style >= NF_LIMIT) { 687 throw new IllegalArgumentException("Illegal number format type"); 688 } 689 NumberFormat result = null; 690 if (numberFormats != null) { 691 result = numberFormats[style]; 692 } 693 if (result != null) { 694 result = (NumberFormat) result.clone(); // clone for safety (later optimize) 695 } else { 696 result = guessNumberFormat(style); 697 } 698 return result; 699 } 700 701 /** 702 * Sets a number format explicitly. Overrides the general locale settings. 703 * 704 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER 705 * @param format The number format 706 * @return this, for chaining 707 * @draft ICU 3.6 708 */ setNumberFormat(int style, NumberFormat format)709 public GlobalizationPreferences setNumberFormat(int style, NumberFormat format) { 710 if (isFrozen()) { 711 throw new UnsupportedOperationException("Attempt to modify immutable object"); 712 } 713 if (numberFormats == null) { 714 numberFormats = new NumberFormat[NF_LIMIT]; 715 } 716 numberFormats[style] = (NumberFormat) format.clone(); // for safety 717 return this; 718 } 719 720 /** 721 * Restore the object to the initial state. 722 * 723 * @return this, for chaining 724 * @draft ICU 3.6 725 */ reset()726 public GlobalizationPreferences reset() { 727 if (isFrozen()) { 728 throw new UnsupportedOperationException("Attempt to modify immutable object"); 729 } 730 locales = null; 731 territory = null; 732 calendar = null; 733 collator = null; 734 breakIterators = null; 735 timezone = null; 736 currency = null; 737 dateFormats = null; 738 numberFormats = null; 739 implicitLocales = null; 740 return this; 741 } 742 743 /** 744 * Process a language/locale priority list specified via <code>setLocales</code>. 745 * The input locale list may be expanded or re-ordered to represent the prioritized 746 * language/locale order actually used by this object by the algorithm explained 747 * below. 748 * <br> 749 * <br> 750 * <b>Step 1</b>: Move later occurrence of more specific locale before earlier 751 * occurrence of less specific locale. 752 * <br> 753 * Before: en, fr_FR, en_US, en_GB 754 * <br> 755 * After: en_US, en_GB, en, fr_FR 756 * <br> 757 * <br> 758 * <b>Step 2</b>: Append a fallback locale to each locale. 759 * <br> 760 * Before: en_US, en_GB, en, fr_FR 761 * <br> 762 * After: en_US, en, en_GB, en, en, fr_FR, fr 763 * <br> 764 * <br> 765 * <b>Step 3</b>: Remove earlier occurrence of duplicated locale entries. 766 * <br> 767 * Before: en_US, en, en_GB, en, en, fr_FR, fr 768 * <br> 769 * After: en_US, en_GB, en, fr_FR, fr 770 * <br> 771 * <br> 772 * The final locale list is used to produce a default value for the appropriate territory, 773 * currency, timezone, etc. The list also represents the lookup order used in 774 * <code>getResourceBundle</code> for this object. A subclass may override this method 775 * to customize the algorithm used for populating the locale list. 776 * 777 * @param inputLocales The list of input locales 778 * @draft ICU 3.6 779 */ processLocales(List<ULocale> inputLocales)780 protected List<ULocale> processLocales(List<ULocale> inputLocales) { 781 // Note: Some of the callers, and non-ICU call sites, could be simpler/more efficient 782 // if this method took a Collection or even an Iterable. 783 // Maybe we can change it since this is still @draft and probably not widely overridden. 784 List<ULocale> result = new ArrayList<>(); 785 /* 786 * Step 1: Relocate later occurrence of more specific locale 787 * before earlier occurrence of less specific locale. 788 * 789 * Example: 790 * Before - en_US, fr_FR, zh, en_US_Boston, zh_TW, zh_Hant, fr_CA 791 * After - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA 792 */ 793 for (ULocale uloc : inputLocales) { 794 String language = uloc.getLanguage(); 795 String script = uloc.getScript(); 796 String country = uloc.getCountry(); 797 String variant = uloc.getVariant(); 798 799 boolean bInserted = false; 800 for (int j = 0; j < result.size(); j++) { 801 // Check if this locale is more specific 802 // than existing locale entries already inserted 803 // in the destination list 804 ULocale u = result.get(j); 805 if (!u.getLanguage().equals(language)) { 806 continue; 807 } 808 String s = u.getScript(); 809 String c = u.getCountry(); 810 String v = u.getVariant(); 811 if (!s.equals(script)) { 812 if (s.length() == 0 && c.length() == 0 && v.length() == 0) { 813 result.add(j, uloc); 814 bInserted = true; 815 break; 816 } else if (s.length() == 0 && c.equals(country)) { 817 // We want to see zh_Hant_HK before zh_HK 818 result.add(j, uloc); 819 bInserted = true; 820 break; 821 } else if (script.length() == 0 && country.length() > 0 && c.length() == 0) { 822 // We want to see zh_HK before zh_Hant 823 result.add(j, uloc); 824 bInserted = true; 825 break; 826 } 827 continue; 828 } 829 if (!c.equals(country)) { 830 if (c.length() == 0 && v.length() == 0) { 831 result.add(j, uloc); 832 bInserted = true; 833 break; 834 } 835 } 836 if (!v.equals(variant) && v.length() == 0) { 837 result.add(j, uloc); 838 bInserted = true; 839 break; 840 } 841 } 842 if (!bInserted) { 843 // Add this locale at the end of the list 844 result.add(uloc); 845 } 846 } 847 848 // TODO: Locale aliases might be resolved here 849 // For example, zh_Hant_TW = zh_TW 850 851 /* 852 * Step 2: Append fallback locales for each entry 853 * 854 * Example: 855 * Before - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA 856 * After - en_US_Boston, en_US, en, en_US, en, fr_FR, fr, 857 * zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr 858 */ 859 int index = 0; 860 while (index < result.size()) { 861 ULocale uloc = result.get(index); 862 while ((uloc = uloc.getFallback()) != null) { 863 if (uloc.getLanguage().length() == 0) { 864 break; 865 } 866 index++; 867 result.add(index, uloc); 868 } 869 index++; 870 } 871 872 /* 873 * Step 3: Remove earlier occurrence of duplicated locales 874 * 875 * Example: 876 * Before - en_US_Boston, en_US, en, en_US, en, fr_FR, fr, 877 * zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr 878 * After - en_US_Boston, en_US, en, fr_FR, zh_TW, zh_Hant, 879 * zh, fr_CA, fr 880 */ 881 index = 0; 882 while (index < result.size() - 1) { 883 ULocale uloc = result.get(index); 884 boolean bRemoved = false; 885 for (int i = index + 1; i < result.size(); i++) { 886 if (uloc.equals(result.get(i))) { 887 // Remove earlier one 888 result.remove(index); 889 bRemoved = true; 890 break; 891 } 892 } 893 if (!bRemoved) { 894 index++; 895 } 896 } 897 return result; 898 } 899 900 901 /** 902 * This function can be overridden by subclasses to use different heuristics. 903 * <b>It MUST return a 'safe' value, 904 * one whose modification will not affect this object.</b> 905 * 906 * @param dateStyle 907 * @param timeStyle 908 * @draft ICU 3.6 909 */ guessDateFormat(int dateStyle, int timeStyle)910 protected DateFormat guessDateFormat(int dateStyle, int timeStyle) { 911 DateFormat result; 912 ULocale dfLocale = getAvailableLocale(TYPE_DATEFORMAT); 913 if (dfLocale == null) { 914 dfLocale = ULocale.ROOT; 915 } 916 if (timeStyle == DF_NONE) { 917 result = DateFormat.getDateInstance(getCalendar(), dateStyle, dfLocale); 918 } else if (dateStyle == DF_NONE) { 919 result = DateFormat.getTimeInstance(getCalendar(), timeStyle, dfLocale); 920 } else { 921 result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, dfLocale); 922 } 923 return result; 924 } 925 926 /** 927 * This function can be overridden by subclasses to use different heuristics. 928 * <b>It MUST return a 'safe' value, 929 * one whose modification will not affect this object.</b> 930 * 931 * @param style 932 * @draft ICU 3.6 933 */ guessNumberFormat(int style)934 protected NumberFormat guessNumberFormat(int style) { 935 NumberFormat result; 936 ULocale nfLocale = getAvailableLocale(TYPE_NUMBERFORMAT); 937 if (nfLocale == null) { 938 nfLocale = ULocale.ROOT; 939 } 940 switch (style) { 941 case NF_NUMBER: 942 result = NumberFormat.getInstance(nfLocale); 943 break; 944 case NF_SCIENTIFIC: 945 result = NumberFormat.getScientificInstance(nfLocale); 946 break; 947 case NF_INTEGER: 948 result = NumberFormat.getIntegerInstance(nfLocale); 949 break; 950 case NF_PERCENT: 951 result = NumberFormat.getPercentInstance(nfLocale); 952 break; 953 case NF_CURRENCY: 954 result = NumberFormat.getCurrencyInstance(nfLocale); 955 result.setCurrency(getCurrency()); 956 break; 957 default: 958 throw new IllegalArgumentException("Unknown number format style"); 959 } 960 return result; 961 } 962 963 /** 964 * This function can be overridden by subclasses to use different heuristics. 965 * 966 * @draft ICU 3.6 967 */ guessTerritory()968 protected String guessTerritory() { 969 String result; 970 // pass through locales to see if there is a territory. 971 for (ULocale locale : getLocales()) { 972 result = locale.getCountry(); 973 if (result.length() != 0) { 974 return result; 975 } 976 } 977 // if not, guess from the first language tag, or maybe from 978 // intersection of languages, eg nl + fr => BE 979 // TODO: fix using real data 980 // for now, just use fixed values 981 ULocale firstLocale = getLocale(0); 982 String language = firstLocale.getLanguage(); 983 String script = firstLocale.getScript(); 984 result = null; 985 if (script.length() != 0) { 986 result = language_territory_hack_map.get(language + "_" + script); 987 } 988 if (result == null) { 989 result = language_territory_hack_map.get(language); 990 } 991 if (result == null) { 992 result = "US"; // need *some* default 993 } 994 return result; 995 } 996 997 /** 998 * This function can be overridden by subclasses to use different heuristics 999 * 1000 * @draft ICU 3.6 1001 */ guessCurrency()1002 protected Currency guessCurrency() { 1003 return Currency.getInstance(new ULocale("und-" + getTerritory())); 1004 } 1005 1006 /** 1007 * This function can be overridden by subclasses to use different heuristics 1008 * <b>It MUST return a 'safe' value, 1009 * one whose modification will not affect this object.</b> 1010 * 1011 * @draft ICU 3.6 1012 */ guessLocales()1013 protected List<ULocale> guessLocales() { 1014 if (implicitLocales == null) { 1015 List<ULocale> result = new ArrayList<>(1); 1016 result.add(ULocale.getDefault()); 1017 implicitLocales = processLocales(result); 1018 } 1019 return implicitLocales; 1020 } 1021 1022 /** 1023 * This function can be overridden by subclasses to use different heuristics. 1024 * <b>It MUST return a 'safe' value, 1025 * one whose modification will not affect this object.</b> 1026 * 1027 * @draft ICU 3.6 1028 */ guessCollator()1029 protected Collator guessCollator() { 1030 ULocale collLocale = getAvailableLocale(TYPE_COLLATOR); 1031 if (collLocale == null) { 1032 collLocale = ULocale.ROOT; 1033 } 1034 return Collator.getInstance(collLocale); 1035 } 1036 1037 /** 1038 * This function can be overridden by subclasses to use different heuristics. 1039 * <b>It MUST return a 'safe' value, 1040 * one whose modification will not affect this object.</b> 1041 * 1042 * @param type 1043 * @draft ICU 3.6 1044 */ guessBreakIterator(int type)1045 protected BreakIterator guessBreakIterator(int type) { 1046 BreakIterator bitr = null; 1047 ULocale brkLocale = getAvailableLocale(TYPE_BREAKITERATOR); 1048 if (brkLocale == null) { 1049 brkLocale = ULocale.ROOT; 1050 } 1051 switch (type) { 1052 case BI_CHARACTER: 1053 bitr = BreakIterator.getCharacterInstance(brkLocale); 1054 break; 1055 case BI_TITLE: 1056 bitr = BreakIterator.getTitleInstance(brkLocale); 1057 break; 1058 case BI_WORD: 1059 bitr = BreakIterator.getWordInstance(brkLocale); 1060 break; 1061 case BI_LINE: 1062 bitr = BreakIterator.getLineInstance(brkLocale); 1063 break; 1064 case BI_SENTENCE: 1065 bitr = BreakIterator.getSentenceInstance(brkLocale); 1066 break; 1067 default: 1068 throw new IllegalArgumentException("Unknown break iterator type"); 1069 } 1070 return bitr; 1071 } 1072 1073 /** 1074 * This function can be overridden by subclasses to use different heuristics. 1075 * <b>It MUST return a 'safe' value, 1076 * one whose modification will not affect this object.</b> 1077 * 1078 * @draft ICU 3.6 1079 */ guessTimeZone()1080 protected TimeZone guessTimeZone() { 1081 // TODO fix using real data 1082 // for single-zone countries, pick that zone 1083 // for others, pick the most populous zone 1084 // for now, just use fixed value 1085 // NOTE: in a few cases can do better by looking at language. 1086 // Eg haw+US should go to Pacific/Honolulu 1087 // fr+CA should go to America/Montreal 1088 String timezoneString = territory_tzid_hack_map.get(getTerritory()); 1089 if (timezoneString == null) { 1090 String[] attempt = TimeZone.getAvailableIDs(getTerritory()); 1091 if (attempt.length == 0) { 1092 timezoneString = "Etc/GMT"; // gotta do something 1093 } else { 1094 int i; 1095 // this all needs to be fixed to use real data. But for now, do slightly better by skipping cruft 1096 for (i = 0; i < attempt.length; ++i) { 1097 if (attempt[i].indexOf("/") >= 0) break; 1098 } 1099 if (i > attempt.length) i = 0; 1100 timezoneString = attempt[i]; 1101 } 1102 } 1103 return TimeZone.getTimeZone(timezoneString); 1104 } 1105 1106 /** 1107 * This function can be overridden by subclasses to use different heuristics. 1108 * <b>It MUST return a 'safe' value, 1109 * one whose modification will not affect this object.</b> 1110 * 1111 * @draft ICU 3.6 1112 */ guessCalendar()1113 protected Calendar guessCalendar() { 1114 ULocale calLocale = getAvailableLocale(TYPE_CALENDAR); 1115 if (calLocale == null) { 1116 calLocale = ULocale.US; 1117 } 1118 return Calendar.getInstance(getTimeZone(), calLocale); 1119 } 1120 1121 // PRIVATES 1122 1123 private List<ULocale> locales; 1124 private String territory; 1125 private Currency currency; 1126 private TimeZone timezone; 1127 private Calendar calendar; 1128 private Collator collator; 1129 private BreakIterator[] breakIterators; 1130 private DateFormat[][] dateFormats; 1131 private NumberFormat[] numberFormats; 1132 private List<ULocale> implicitLocales; 1133 1134 { reset()1135 reset(); 1136 } 1137 1138 getAvailableLocale(int type)1139 private ULocale getAvailableLocale(int type) { 1140 List<ULocale> locs = getLocales(); 1141 ULocale result = null; 1142 for (int i = 0; i < locs.size(); i++) { 1143 ULocale l = locs.get(i); 1144 if (isAvailableLocale(l, type)) { 1145 result = l; 1146 break; 1147 } 1148 } 1149 return result; 1150 } 1151 isAvailableLocale(ULocale loc, int type)1152 private boolean isAvailableLocale(ULocale loc, int type) { 1153 BitSet bits = available_locales.get(loc); 1154 if (bits != null && bits.get(type)) { 1155 return true; 1156 } 1157 return false; 1158 } 1159 1160 /* 1161 * Available locales for service types 1162 */ 1163 private static final HashMap<ULocale, BitSet> available_locales = new HashMap<>(); 1164 private static final int 1165 TYPE_GENERIC = 0, 1166 TYPE_CALENDAR = 1, 1167 TYPE_DATEFORMAT= 2, 1168 TYPE_NUMBERFORMAT = 3, 1169 TYPE_COLLATOR = 4, 1170 TYPE_BREAKITERATOR = 5, 1171 TYPE_LIMIT = TYPE_BREAKITERATOR + 1; 1172 1173 static { 1174 BitSet bits; 1175 ULocale[] allLocales = ULocale.getAvailableLocales(); 1176 for (int i = 0; i < allLocales.length; i++) { 1177 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1178 available_locales.put(allLocales[i], bits); 1179 bits.set(TYPE_GENERIC); 1180 } 1181 1182 ULocale[] calLocales = Calendar.getAvailableULocales(); 1183 for (int i = 0; i < calLocales.length; i++) { 1184 bits = available_locales.get(calLocales[i]); 1185 if (bits == null) { 1186 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1187 available_locales.put(allLocales[i], bits); 1188 } 1189 bits.set(TYPE_CALENDAR); 1190 } 1191 1192 ULocale[] dateLocales = DateFormat.getAvailableULocales(); 1193 for (int i = 0; i < dateLocales.length; i++) { 1194 bits = available_locales.get(dateLocales[i]); 1195 if (bits == null) { 1196 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1197 available_locales.put(allLocales[i], bits); 1198 } 1199 bits.set(TYPE_DATEFORMAT); 1200 } 1201 1202 ULocale[] numLocales = NumberFormat.getAvailableULocales(); 1203 for (int i = 0; i < numLocales.length; i++) { 1204 bits = available_locales.get(numLocales[i]); 1205 if (bits == null) { 1206 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1207 available_locales.put(allLocales[i], bits); 1208 } 1209 bits.set(TYPE_NUMBERFORMAT); 1210 } 1211 1212 ULocale[] collLocales = Collator.getAvailableULocales(); 1213 for (int i = 0; i < collLocales.length; i++) { 1214 bits = available_locales.get(collLocales[i]); 1215 if (bits == null) { 1216 bits = new BitSet(TYPE_LIMIT); available_locales.put(allLocales[i], bits)1217 available_locales.put(allLocales[i], bits); 1218 } 1219 bits.set(TYPE_COLLATOR); 1220 } 1221 1222 ULocale[] brkLocales = BreakIterator.getAvailableULocales(); 1223 for (int i = 0; i < brkLocales.length; i++) { 1224 bits = available_locales.get(brkLocales[i]); 1225 bits.set(TYPE_BREAKITERATOR); 1226 } 1227 } 1228 1229 /** WARNING: All of this data is temporary, until we start importing from CLDR!!! 1230 * 1231 */ 1232 private static final Map<String, String> language_territory_hack_map = new HashMap<>(); 1233 private static final String[][] language_territory_hack = { 1234 {"af", "ZA"}, 1235 {"am", "ET"}, 1236 {"ar", "SA"}, 1237 {"as", "IN"}, 1238 {"ay", "PE"}, 1239 {"az", "AZ"}, 1240 {"bal", "PK"}, 1241 {"be", "BY"}, 1242 {"bg", "BG"}, 1243 {"bn", "IN"}, 1244 {"bs", "BA"}, 1245 {"ca", "ES"}, 1246 {"ch", "MP"}, 1247 {"cpe", "SL"}, 1248 {"cs", "CZ"}, 1249 {"cy", "GB"}, 1250 {"da", "DK"}, 1251 {"de", "DE"}, 1252 {"dv", "MV"}, 1253 {"dz", "BT"}, 1254 {"el", "GR"}, 1255 {"en", "US"}, 1256 {"es", "ES"}, 1257 {"et", "EE"}, 1258 {"eu", "ES"}, 1259 {"fa", "IR"}, 1260 {"fi", "FI"}, 1261 {"fil", "PH"}, 1262 {"fj", "FJ"}, 1263 {"fo", "FO"}, 1264 {"fr", "FR"}, 1265 {"ga", "IE"}, 1266 {"gd", "GB"}, 1267 {"gl", "ES"}, 1268 {"gn", "PY"}, 1269 {"gu", "IN"}, 1270 {"gv", "GB"}, 1271 {"ha", "NG"}, 1272 {"he", "IL"}, 1273 {"hi", "IN"}, 1274 {"ho", "PG"}, 1275 {"hr", "HR"}, 1276 {"ht", "HT"}, 1277 {"hu", "HU"}, 1278 {"hy", "AM"}, 1279 {"id", "ID"}, 1280 {"is", "IS"}, 1281 {"it", "IT"}, 1282 {"ja", "JP"}, 1283 {"ka", "GE"}, 1284 {"kk", "KZ"}, 1285 {"kl", "GL"}, 1286 {"km", "KH"}, 1287 {"kn", "IN"}, 1288 {"ko", "KR"}, 1289 {"kok", "IN"}, 1290 {"ks", "IN"}, 1291 {"ku", "TR"}, 1292 {"ky", "KG"}, 1293 {"la", "VA"}, 1294 {"lb", "LU"}, 1295 {"ln", "CG"}, 1296 {"lo", "LA"}, 1297 {"lt", "LT"}, 1298 {"lv", "LV"}, 1299 {"mai", "IN"}, 1300 {"men", "GN"}, 1301 {"mg", "MG"}, 1302 {"mh", "MH"}, 1303 {"mk", "MK"}, 1304 {"ml", "IN"}, 1305 {"mn", "MN"}, 1306 {"mni", "IN"}, 1307 {"mo", "MD"}, 1308 {"mr", "IN"}, 1309 {"ms", "MY"}, 1310 {"mt", "MT"}, 1311 {"my", "MM"}, 1312 {"na", "NR"}, 1313 {"nb", "NO"}, 1314 {"nd", "ZA"}, 1315 {"ne", "NP"}, 1316 {"niu", "NU"}, 1317 {"nl", "NL"}, 1318 {"nn", "NO"}, 1319 {"no", "NO"}, 1320 {"nr", "ZA"}, 1321 {"nso", "ZA"}, 1322 {"ny", "MW"}, 1323 {"om", "KE"}, 1324 {"or", "IN"}, 1325 {"pa", "IN"}, 1326 {"pau", "PW"}, 1327 {"pl", "PL"}, 1328 {"ps", "PK"}, 1329 {"pt", "BR"}, 1330 {"qu", "PE"}, 1331 {"rn", "BI"}, 1332 {"ro", "RO"}, 1333 {"ru", "RU"}, 1334 {"rw", "RW"}, 1335 {"sd", "IN"}, 1336 {"sg", "CF"}, 1337 {"si", "LK"}, 1338 {"sk", "SK"}, 1339 {"sl", "SI"}, 1340 {"sm", "WS"}, 1341 {"so", "DJ"}, 1342 {"sq", "CS"}, 1343 {"sr", "CS"}, 1344 {"ss", "ZA"}, 1345 {"st", "ZA"}, 1346 {"sv", "SE"}, 1347 {"sw", "KE"}, 1348 {"ta", "IN"}, 1349 {"te", "IN"}, 1350 {"tem", "SL"}, 1351 {"tet", "TL"}, 1352 {"th", "TH"}, 1353 {"ti", "ET"}, 1354 {"tg", "TJ"}, 1355 {"tk", "TM"}, 1356 {"tkl", "TK"}, 1357 {"tvl", "TV"}, 1358 {"tl", "PH"}, 1359 {"tn", "ZA"}, 1360 {"to", "TO"}, 1361 {"tpi", "PG"}, 1362 {"tr", "TR"}, 1363 {"ts", "ZA"}, 1364 {"uk", "UA"}, 1365 {"ur", "IN"}, 1366 {"uz", "UZ"}, 1367 {"ve", "ZA"}, 1368 {"vi", "VN"}, 1369 {"wo", "SN"}, 1370 {"xh", "ZA"}, 1371 {"zh", "CN"}, 1372 {"zh_Hant", "TW"}, 1373 {"zu", "ZA"}, 1374 {"aa", "ET"}, 1375 {"byn", "ER"}, 1376 {"eo", "DE"}, 1377 {"gez", "ET"}, 1378 {"haw", "US"}, 1379 {"iu", "CA"}, 1380 {"kw", "GB"}, 1381 {"sa", "IN"}, 1382 {"sh", "HR"}, 1383 {"sid", "ET"}, 1384 {"syr", "SY"}, 1385 {"tig", "ER"}, 1386 {"tt", "RU"}, 1387 {"wal", "ET"}, }; 1388 static { 1389 for (int i = 0; i < language_territory_hack.length; ++i) { language_territory_hack_map.put(language_territory_hack[i][0],language_territory_hack[i][1])1390 language_territory_hack_map.put(language_territory_hack[i][0],language_territory_hack[i][1]); 1391 } 1392 } 1393 1394 static final Map<String, String> territory_tzid_hack_map = new HashMap<>(); 1395 static final String[][] territory_tzid_hack = { 1396 {"AQ", "Antarctica/McMurdo"}, 1397 {"AR", "America/Buenos_Aires"}, 1398 {"AU", "Australia/Sydney"}, 1399 {"BR", "America/Sao_Paulo"}, 1400 {"CA", "America/Toronto"}, 1401 {"CD", "Africa/Kinshasa"}, 1402 {"CL", "America/Santiago"}, 1403 {"CN", "Asia/Shanghai"}, 1404 {"EC", "America/Guayaquil"}, 1405 {"ES", "Europe/Madrid"}, 1406 {"GB", "Europe/London"}, 1407 {"GL", "America/Godthab"}, 1408 {"ID", "Asia/Jakarta"}, 1409 {"ML", "Africa/Bamako"}, 1410 {"MX", "America/Mexico_City"}, 1411 {"MY", "Asia/Kuala_Lumpur"}, 1412 {"NZ", "Pacific/Auckland"}, 1413 {"PT", "Europe/Lisbon"}, 1414 {"RU", "Europe/Moscow"}, 1415 {"UA", "Europe/Kiev"}, 1416 {"US", "America/New_York"}, 1417 {"UZ", "Asia/Tashkent"}, 1418 {"PF", "Pacific/Tahiti"}, 1419 {"FM", "Pacific/Kosrae"}, 1420 {"KI", "Pacific/Tarawa"}, 1421 {"KZ", "Asia/Almaty"}, 1422 {"MH", "Pacific/Majuro"}, 1423 {"MN", "Asia/Ulaanbaatar"}, 1424 {"SJ", "Arctic/Longyearbyen"}, 1425 {"UM", "Pacific/Midway"}, 1426 }; 1427 static { 1428 for (int i = 0; i < territory_tzid_hack.length; ++i) { territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1])1429 territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1]); 1430 } 1431 } 1432 1433 // Freezable implementation 1434 1435 private volatile boolean frozen; 1436 1437 /** 1438 * @draft ICU 3.6 1439 */ 1440 @Override isFrozen()1441 public boolean isFrozen() { 1442 return frozen; 1443 } 1444 1445 /** 1446 * @draft ICU 4.4 1447 */ 1448 @Override freeze()1449 public GlobalizationPreferences freeze() { 1450 frozen = true; 1451 return this; 1452 } 1453 1454 /** 1455 * @draft ICU 4.4 1456 */ 1457 @Override cloneAsThawed()1458 public GlobalizationPreferences cloneAsThawed() { 1459 try { 1460 GlobalizationPreferences result = (GlobalizationPreferences) clone(); 1461 result.frozen = false; 1462 return result; 1463 } catch (CloneNotSupportedException e) { 1464 // will always work 1465 return null; 1466 } 1467 } 1468 } 1469 1470