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