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