2 *******************************************************************************
\r
3 * Copyright (C) 2004-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.util;
\r
9 import java.text.ParseException;
\r
10 import java.util.ArrayList;
\r
11 import java.util.Arrays;
\r
12 import java.util.BitSet;
\r
13 import java.util.Date;
\r
14 import java.util.HashMap;
\r
15 import java.util.List;
\r
16 import java.util.Map;
\r
17 import java.util.MissingResourceException;
\r
18 import java.util.ResourceBundle;
\r
20 import com.ibm.icu.impl.Utility;
\r
21 import com.ibm.icu.impl.ZoneMeta;
\r
22 import com.ibm.icu.text.BreakIterator;
\r
23 import com.ibm.icu.text.Collator;
\r
24 import com.ibm.icu.text.DateFormat;
\r
25 import com.ibm.icu.text.NumberFormat;
\r
26 import com.ibm.icu.text.SimpleDateFormat;
\r
29 * This convenience class provides a mechanism for bundling together different
\r
30 * globalization preferences. It includes:
\r
32 * <li>A list of locales/languages in preference order</li>
\r
33 * <li>A territory</li>
\r
34 * <li>A currency</li>
\r
35 * <li>A timezone</li>
\r
36 * <li>A calendar</li>
\r
37 * <li>A collator (for language-sensitive sorting, searching, and matching).</li>
\r
38 * <li>Explicit overrides for date/time formats, etc.</li>
\r
40 * The class will heuristically compute implicit, heuristic values for the above
\r
41 * based on available data if explicit values are not supplied. These implicit
\r
42 * values can be presented to users for confirmation, or replacement if the
\r
43 * values are incorrect.
\r
45 * To reset any explicit field so that it will get heuristic values, pass in
\r
46 * null. For example, myPreferences.setLocale(null);
\r
48 * All of the heuristics can be customized by subclasses, by overriding
\r
49 * getTerritory(), guessCollator(), etc.
\r
51 * The class also supplies display names for languages, scripts, territories,
\r
52 * currencies, timezones, etc. These are computed according to the
\r
53 * locale/language preference list. Thus, if the preference is Breton; French;
\r
54 * English, then the display name for a language will be returned in Breton if
\r
55 * available, otherwise in French if available, otherwise in English.
\r
57 * The codes used to reference territory, currency, etc. are as defined elsewhere
\r
58 * in ICU, and are taken from CLDR (which reflects RFC 3066bis usage, ISO 4217,
\r
59 * and the TZ Timezone database identifiers).
\r
61 * <b>This is at a prototype stage, and has not incorporated all the design
\r
62 * changes that we would like yet; further feedback is welcome.</b></p>
\r
65 * <li>to get the display name for the first day of the week, use the calendar +
\r
66 * display names.</li>
\r
67 * <li>to get the work days, ask the calendar (when that is available).</li>
\r
68 * <li>to get papersize / measurement system/bidi-orientation, ask the locale
\r
69 * (when that is available there)</li>
\r
70 * <li>to get the field order in a date, and whether a time is 24hour or not,
\r
71 * ask the DateFormat (when that is available there)</li>
\r
72 * <li>it will support HOST locale when it becomes available (it is a special
\r
73 * locale that will ask the services to use the host platform's values).</li>
\r
76 * @draft ICU 3.6 (retainAll)
\r
77 * @provisional This API might change or be removed in a future release.
\r
82 // - Add convenience to get/take Locale as well as ULocale.
\r
83 // - Add Lenient datetime formatting when that is available.
\r
84 // - Should this be serializable?
\r
85 // - Other utilities?
\r
87 public class GlobalizationPreferences implements Freezable<GlobalizationPreferences> {
\r
90 * Default constructor
\r
92 * @provisional This API might change or be removed in a future release.
\r
94 public GlobalizationPreferences(){}
\r
96 * Number Format type
\r
98 * @provisional This API might change or be removed in a future release.
\r
100 public static final int
\r
101 NF_NUMBER = 0, // NumberFormat.NUMBERSTYLE
\r
102 NF_CURRENCY = 1, // NumberFormat.CURRENCYSTYLE
\r
103 NF_PERCENT = 2, // NumberFormat.PERCENTSTYLE
\r
104 NF_SCIENTIFIC = 3, // NumberFormat.SCIENTIFICSTYLE
\r
105 NF_INTEGER = 4; // NumberFormat.INTEGERSTYLE
\r
107 private static final int NF_LIMIT = NF_INTEGER + 1;
\r
112 * @provisional This API might change or be removed in a future release.
\r
114 public static final int
\r
115 DF_FULL = DateFormat.FULL, // 0
\r
116 DF_LONG = DateFormat.LONG, // 1
\r
117 DF_MEDIUM = DateFormat.MEDIUM, // 2
\r
118 DF_SHORT = DateFormat.SHORT, // 3
\r
121 private static final int DF_LIMIT = DF_NONE + 1;
\r
124 * For selecting a choice of display names
\r
126 * @provisional This API might change or be removed in a future release.
\r
128 public static final int
\r
135 ID_KEYWORD_VALUE = 6,
\r
137 ID_CURRENCY_SYMBOL = 8,
\r
140 //private static final int ID_LIMIT = ID_TIMEZONE + 1;
\r
143 * Break iterator type
\r
145 * @provisional This API might change or be removed in a future release.
\r
147 public static final int
\r
148 BI_CHARACTER = BreakIterator.KIND_CHARACTER, // 0
\r
149 BI_WORD = BreakIterator.KIND_WORD, // 1
\r
150 BI_LINE = BreakIterator.KIND_LINE, // 2
\r
151 BI_SENTENCE = BreakIterator.KIND_SENTENCE, // 3
\r
152 BI_TITLE = BreakIterator.KIND_TITLE; // 4
\r
154 private static final int BI_LIMIT = BI_TITLE + 1;
\r
157 * Sets the language/locale priority list. If other information is
\r
158 * not (yet) available, this is used to to produce a default value
\r
159 * for the appropriate territory, currency, timezone, etc. The
\r
160 * user should be given the opportunity to correct those defaults
\r
161 * in case they are incorrect.
\r
163 * @param inputLocales list of locales in priority order, eg {"be", "fr"}
\r
164 * for Breton first, then French if that fails.
\r
165 * @return this, for chaining
\r
167 * @provisional This API might change or be removed in a future release.
\r
169 public GlobalizationPreferences setLocales(List<ULocale> inputLocales) {
\r
171 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
173 locales = processLocales(inputLocales);
\r
178 * Get a copy of the language/locale priority list
\r
180 * @return a copy of the language/locale priority list.
\r
182 * @provisional This API might change or be removed in a future release.
\r
184 public List<ULocale> getLocales() {
\r
185 List<ULocale> result;
\r
186 if (locales == null) {
\r
187 result = guessLocales();
\r
189 result = new ArrayList<ULocale>();
\r
190 result.addAll(locales);
\r
196 * Convenience function for getting the locales in priority order
\r
197 * @param index The index (0..n) of the desired item.
\r
198 * @return desired item. null if index is out of range
\r
200 * @provisional This API might change or be removed in a future release.
\r
202 public ULocale getLocale(int index) {
\r
203 List<ULocale> lcls = locales;
\r
204 if (lcls == null) {
\r
205 lcls = guessLocales();
\r
207 if (index >= 0 && index < lcls.size()) {
\r
208 return lcls.get(index);
\r
214 * Convenience routine for setting the language/locale priority
\r
215 * list from an array.
\r
217 * @see #setLocales(List locales)
\r
218 * @param uLocales list of locales in an array
\r
219 * @return this, for chaining
\r
221 * @provisional This API might change or be removed in a future release.
\r
223 public GlobalizationPreferences setLocales(ULocale[] uLocales) {
\r
225 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
227 return setLocales(Arrays.asList(uLocales));
\r
231 * Convenience routine for setting the language/locale priority
\r
232 * list from a single locale/language.
\r
234 * @see #setLocales(List locales)
\r
235 * @param uLocale single locale
\r
236 * @return this, for chaining
\r
238 * @provisional This API might change or be removed in a future release.
\r
240 public GlobalizationPreferences setLocale(ULocale uLocale) {
\r
242 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
244 return setLocales(new ULocale[]{uLocale});
\r
248 * Convenience routine for setting the locale priority list from
\r
249 * an Accept-Language string.
\r
250 * @see #setLocales(List locales)
\r
251 * @param acceptLanguageString Accept-Language list, as defined by
\r
252 * Section 14.4 of the RFC 2616 (HTTP 1.1)
\r
253 * @return this, for chaining
\r
255 * @provisional This API might change or be removed in a future release.
\r
257 public GlobalizationPreferences setLocales(String acceptLanguageString) {
\r
259 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
261 ULocale[] acceptLocales = null;
\r
263 acceptLocales = ULocale.parseAcceptLanguage(acceptLanguageString, true);
\r
264 } catch (ParseException pe) {
\r
265 //TODO: revisit after 3.8
\r
266 throw new IllegalArgumentException("Invalid Accept-Language string");
\r
268 return setLocales(acceptLocales);
\r
272 * Convenience function to get a ResourceBundle instance using
\r
273 * the specified base name based on the language/locale priority list
\r
274 * stored in this object.
\r
276 * @param baseName the base name of the resource bundle, a fully qualified
\r
278 * @return a resource bundle for the given base name and locale based on the
\r
279 * language/locale priority list stored in this object
\r
281 * @provisional This API might change or be removed in a future release.
\r
283 public ResourceBundle getResourceBundle(String baseName) {
\r
284 return getResourceBundle(baseName, null);
\r
288 * Convenience function to get a ResourceBundle instance using
\r
289 * the specified base name and class loader based on the language/locale
\r
290 * priority list stored in this object.
\r
292 * @param baseName the base name of the resource bundle, a fully qualified
\r
294 * @param loader the class object from which to load the resource bundle
\r
295 * @return a resource bundle for the given base name and locale based on the
\r
296 * language/locale priority list stored in this object
\r
298 * @provisional This API might change or be removed in a future release.
\r
300 public ResourceBundle getResourceBundle(String baseName, ClassLoader loader) {
\r
301 UResourceBundle urb = null;
\r
302 UResourceBundle candidate = null;
\r
303 String actualLocaleName = null;
\r
304 List<ULocale> fallbacks = getLocales();
\r
305 for (int i = 0; i < fallbacks.size(); i++) {
\r
306 String localeName = (fallbacks.get(i)).toString();
\r
307 if (actualLocaleName != null && localeName.equals(actualLocaleName)) {
\r
308 // Actual locale name in the previous round may exactly matches
\r
309 // with the next fallback locale
\r
314 if (loader == null) {
\r
315 candidate = UResourceBundle.getBundleInstance(baseName, localeName);
\r
318 candidate = UResourceBundle.getBundleInstance(baseName, localeName, loader);
\r
320 if (candidate != null) {
\r
321 actualLocaleName = candidate.getULocale().getName();
\r
322 if (actualLocaleName.equals(localeName)) {
\r
327 // Preserve the available bundle as the last resort
\r
331 } catch (MissingResourceException mre) {
\r
332 actualLocaleName = null;
\r
337 throw new MissingResourceException("Can't find bundle for base name "
\r
338 + baseName, baseName, "");
\r
344 * Sets the territory, which is a valid territory according to for
\r
345 * RFC 3066 (or successor). If not otherwise set, default
\r
346 * currency and timezone values will be set from this. The user
\r
347 * should be given the opportunity to correct those defaults in
\r
348 * case they are incorrect.
\r
350 * @param territory code
\r
351 * @return this, for chaining
\r
353 * @provisional This API might change or be removed in a future release.
\r
355 public GlobalizationPreferences setTerritory(String territory) {
\r
357 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
359 this.territory = territory; // immutable, so don't need to clone
\r
364 * Gets the territory setting. If it wasn't explicitly set, it is
\r
365 * computed from the general locale setting.
\r
367 * @return territory code, explicit or implicit.
\r
369 * @provisional This API might change or be removed in a future release.
\r
371 public String getTerritory() {
\r
372 if (territory == null) {
\r
373 return guessTerritory();
\r
375 return territory; // immutable, so don't need to clone
\r
379 * Sets the currency code. If this has not been set, uses default for territory.
\r
381 * @param currency Valid ISO 4217 currency code.
\r
382 * @return this, for chaining
\r
384 * @provisional This API might change or be removed in a future release.
\r
386 public GlobalizationPreferences setCurrency(Currency currency) {
\r
388 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
390 this.currency = currency; // immutable, so don't need to clone
\r
395 * Get a copy of the currency computed according to the settings.
\r
397 * @return currency code, explicit or implicit.
\r
399 * @provisional This API might change or be removed in a future release.
\r
401 public Currency getCurrency() {
\r
402 if (currency == null) {
\r
403 return guessCurrency();
\r
405 return currency; // immutable, so don't have to clone
\r
409 * Sets the calendar. If this has not been set, uses default for territory.
\r
411 * @param calendar arbitrary calendar
\r
412 * @return this, for chaining
\r
414 * @provisional This API might change or be removed in a future release.
\r
416 public GlobalizationPreferences setCalendar(Calendar calendar) {
\r
418 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
420 this.calendar = (Calendar) calendar.clone(); // clone for safety
\r
425 * Get a copy of the calendar according to the settings.
\r
427 * @return calendar explicit or implicit.
\r
429 * @provisional This API might change or be removed in a future release.
\r
431 public Calendar getCalendar() {
\r
432 if (calendar == null) {
\r
433 return guessCalendar();
\r
435 Calendar temp = (Calendar) calendar.clone(); // clone for safety
\r
436 temp.setTimeZone(getTimeZone());
\r
437 temp.setTimeInMillis(System.currentTimeMillis());
\r
442 * Sets the timezone ID. If this has not been set, uses default for territory.
\r
444 * @param timezone a valid TZID (see UTS#35).
\r
445 * @return this, for chaining
\r
447 * @provisional This API might change or be removed in a future release.
\r
449 public GlobalizationPreferences setTimeZone(TimeZone timezone) {
\r
451 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
453 this.timezone = (TimeZone) timezone.clone(); // clone for safety;
\r
458 * Get the timezone. It was either explicitly set, or is
\r
459 * heuristically computed from other settings.
\r
461 * @return timezone, either implicitly or explicitly set
\r
463 * @provisional This API might change or be removed in a future release.
\r
465 public TimeZone getTimeZone() {
\r
466 if (timezone == null) {
\r
467 return guessTimeZone();
\r
469 return (TimeZone) timezone.clone(); // clone for safety
\r
473 * Get a copy of the collator according to the settings.
\r
475 * @return collator explicit or implicit.
\r
477 * @provisional This API might change or be removed in a future release.
\r
479 public Collator getCollator() {
\r
480 if (collator == null) {
\r
481 return guessCollator();
\r
484 return (Collator) collator.clone(); // clone for safety
\r
485 } catch (CloneNotSupportedException e) {
\r
486 throw new IllegalStateException("Error in cloning collator");
\r
491 * Explicitly set the collator for this object.
\r
492 * @param collator The collator object to be passed.
\r
493 * @return this, for chaining
\r
495 * @provisional This API might change or be removed in a future release.
\r
497 public GlobalizationPreferences setCollator(Collator collator) {
\r
499 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
502 this.collator = (Collator) collator.clone(); // clone for safety
\r
503 } catch (CloneNotSupportedException e) {
\r
504 throw new IllegalStateException("Error in cloning collator");
\r
510 * Get a copy of the break iterator for the specified type according to the
\r
513 * @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE
\r
514 * @return break iterator explicit or implicit
\r
516 * @provisional This API might change or be removed in a future release.
\r
518 public BreakIterator getBreakIterator(int type) {
\r
519 if (type < BI_CHARACTER || type >= BI_LIMIT) {
\r
520 throw new IllegalArgumentException("Illegal break iterator type");
\r
522 if (breakIterators == null || breakIterators[type] == null) {
\r
523 return guessBreakIterator(type);
\r
525 return (BreakIterator) breakIterators[type].clone(); // clone for safety
\r
529 * Explicitly set the break iterator for this object.
\r
531 * @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE
\r
532 * @param iterator a break iterator
\r
533 * @return this, for chaining
\r
535 * @provisional This API might change or be removed in a future release.
\r
537 public GlobalizationPreferences setBreakIterator(int type, BreakIterator iterator) {
\r
538 if (type < BI_CHARACTER || type >= BI_LIMIT) {
\r
539 throw new IllegalArgumentException("Illegal break iterator type");
\r
542 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
544 if (breakIterators == null)
\r
545 breakIterators = new BreakIterator[BI_LIMIT];
\r
546 breakIterators[type] = (BreakIterator) iterator.clone(); // clone for safety
\r
551 * Get the display name for an ID: language, script, territory, currency, timezone...
\r
552 * Uses the language priority list to do so.
\r
554 * @param id language code, script code, ...
\r
555 * @param type specifies the type of the ID: ID_LANGUAGE, etc.
\r
556 * @return the display name
\r
558 * @provisional This API might change or be removed in a future release.
\r
560 public String getDisplayName(String id, int type) {
\r
561 String result = id;
\r
562 for (ULocale locale : getLocales()) {
\r
563 if (!isAvailableLocale(locale, TYPE_GENERIC)) {
\r
568 result = ULocale.getDisplayName(id, locale);
\r
571 result = ULocale.getDisplayLanguage(id, locale);
\r
574 result = ULocale.getDisplayScript("und-" + id, locale);
\r
577 result = ULocale.getDisplayCountry("und-" + id, locale);
\r
580 // TODO fix variant parsing
\r
581 result = ULocale.getDisplayVariant("und-QQ-" + id, locale);
\r
584 result = ULocale.getDisplayKeyword(id, locale);
\r
586 case ID_KEYWORD_VALUE:
\r
587 String[] parts = new String[2];
\r
588 Utility.split(id,'=',parts);
\r
589 result = ULocale.getDisplayKeywordValue("und@"+id, parts[0], locale);
\r
590 // TODO fix to tell when successful
\r
591 if (result.equals(parts[1])) {
\r
595 case ID_CURRENCY_SYMBOL:
\r
597 Currency temp = new Currency(id);
\r
598 result =temp.getName(locale, type==ID_CURRENCY
\r
599 ? Currency.LONG_NAME
\r
600 : Currency.SYMBOL_NAME, new boolean[1]);
\r
601 // TODO: have method that doesn't take parameter. Add
\r
602 // function to determine whether string is choice
\r
604 // TODO: have method that doesn't require us
\r
605 // to create a currency
\r
608 SimpleDateFormat dtf = new SimpleDateFormat("vvvv",locale);
\r
609 dtf.setTimeZone(TimeZone.getTimeZone(id));
\r
610 result = dtf.format(new Date());
\r
611 // TODO, have method that doesn't require us to create a timezone
\r
613 // hack for couldn't match
\r
615 boolean isBadStr = false;
\r
616 // Matcher badTimeZone = Pattern.compile("[A-Z]{2}|.*\\s\\([A-Z]{2}\\)").matcher("");
\r
617 // badtzstr = badTimeZone.reset(result).matches();
\r
618 String teststr = result;
\r
619 int sidx = result.indexOf('(');
\r
620 int eidx = result.indexOf(')');
\r
621 if (sidx != -1 && eidx != -1 && (eidx - sidx) == 3) {
\r
622 teststr = result.substring(sidx+1, eidx);
\r
624 if (teststr.length() == 2) {
\r
626 for (int i = 0; i < 2; i++) {
\r
627 char c = teststr.charAt(i);
\r
628 if (c < 'A' || 'Z' < c) {
\r
639 throw new IllegalArgumentException("Unknown type: " + type);
\r
642 // TODO need better way of seeing if we fell back to root!!
\r
643 // This will not work at all for lots of stuff
\r
644 if (!id.equals(result)) {
\r
652 * Set an explicit date format. Overrides the locale priority list for
\r
653 * a particular combination of dateStyle and timeStyle. DF_NONE should
\r
654 * be used if for the style, where only the date or time format individually
\r
657 * @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
\r
658 * @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
\r
659 * @param format The date format
\r
660 * @return this, for chaining
\r
662 * @provisional This API might change or be removed in a future release.
\r
664 public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) {
\r
666 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
668 if (dateFormats == null) {
\r
669 dateFormats = new DateFormat[DF_LIMIT][DF_LIMIT];
\r
671 dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety
\r
676 * Gets a date format according to the current settings. If there
\r
677 * is an explicit (non-null) date/time format set, a copy of that
\r
678 * is returned. Otherwise, the language priority list is used.
\r
679 * DF_NONE should be used for the style, where only the date or
\r
680 * time format individually is being gotten.
\r
682 * @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
\r
683 * @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
\r
684 * @return a DateFormat, according to the above description
\r
686 * @provisional This API might change or be removed in a future release.
\r
688 public DateFormat getDateFormat(int dateStyle, int timeStyle) {
\r
689 if (dateStyle == DF_NONE && timeStyle == DF_NONE
\r
690 || dateStyle < 0 || dateStyle >= DF_LIMIT
\r
691 || timeStyle < 0 || timeStyle >= DF_LIMIT) {
\r
692 throw new IllegalArgumentException("Illegal date format style arguments");
\r
694 DateFormat result = null;
\r
695 if (dateFormats != null) {
\r
696 result = dateFormats[dateStyle][timeStyle];
\r
698 if (result != null) {
\r
699 result = (DateFormat) result.clone(); // clone for safety
\r
700 // Not sure overriding configuration is what we really want...
\r
701 result.setTimeZone(getTimeZone());
\r
703 result = guessDateFormat(dateStyle, timeStyle);
\r
709 * Gets a number format according to the current settings. If
\r
710 * there is an explicit (non-null) number format set, a copy of
\r
711 * that is returned. Otherwise, the language priority list is
\r
714 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER
\r
716 * @provisional This API might change or be removed in a future release.
\r
718 public NumberFormat getNumberFormat(int style) {
\r
719 if (style < 0 || style >= NF_LIMIT) {
\r
720 throw new IllegalArgumentException("Illegal number format type");
\r
722 NumberFormat result = null;
\r
723 if (numberFormats != null) {
\r
724 result = numberFormats[style];
\r
726 if (result != null) {
\r
727 result = (NumberFormat) result.clone(); // clone for safety (later optimize)
\r
729 result = guessNumberFormat(style);
\r
735 * Sets a number format explicitly. Overrides the general locale settings.
\r
737 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER
\r
738 * @param format The number format
\r
739 * @return this, for chaining
\r
741 * @provisional This API might change or be removed in a future release.
\r
743 public GlobalizationPreferences setNumberFormat(int style, NumberFormat format) {
\r
745 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
747 if (numberFormats == null) {
\r
748 numberFormats = new NumberFormat[NF_LIMIT];
\r
750 numberFormats[style] = (NumberFormat) format.clone(); // for safety
\r
755 * Restore the object to the initial state.
\r
757 * @return this, for chaining
\r
759 * @provisional This API might change or be removed in a future release.
\r
761 public GlobalizationPreferences reset() {
\r
763 throw new UnsupportedOperationException("Attempt to modify immutable object");
\r
769 breakIterators = null;
\r
772 dateFormats = null;
\r
773 numberFormats = null;
\r
774 implicitLocales = null;
\r
779 * Process a language/locale priority list specified via <code>setLocales</code>.
\r
780 * The input locale list may be expanded or re-ordered to represent the prioritized
\r
781 * language/locale order actually used by this object by the algorithm explained
\r
785 * <b>Step 1</b>: Move later occurrence of more specific locale before earlier
\r
786 * occurrence of less specific locale.
\r
788 * Before: en, fr_FR, en_US, en_GB
\r
790 * After: en_US, en_GB, en, fr_FR
\r
793 * <b>Step 2</b>: Append a fallback locale to each locale.
\r
795 * Before: en_US, en_GB, en, fr_FR
\r
797 * After: en_US, en, en_GB, en, en, fr_FR, fr
\r
800 * <b>Step 3</b>: Remove earlier occurrence of duplicated locale entries.
\r
802 * Before: en_US, en, en_GB, en, en, fr_FR, fr
\r
804 * After: en_US, en_GB, en, fr_FR, fr
\r
807 * The final locale list is used to produce a default value for the appropriate territory,
\r
808 * currency, timezone, etc. The list also represents the lookup order used in
\r
809 * <code>getResourceBundle</code> for this object. A subclass may override this method
\r
810 * to customize the algorithm used for populating the locale list.
\r
812 * @param inputLocales The list of input locales
\r
814 * @provisional This API might change or be removed in a future release.
\r
816 protected List<ULocale> processLocales(List<ULocale> inputLocales) {
\r
817 List<ULocale> result = new ArrayList<ULocale>();
\r
819 * Step 1: Relocate later occurrence of more specific locale
\r
820 * before earlier occurrence of less specific locale.
\r
823 * Before - en_US, fr_FR, zh, en_US_Boston, zh_TW, zh_Hant, fr_CA
\r
824 * After - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA
\r
826 for (int i = 0; i < inputLocales.size(); i++) {
\r
827 ULocale uloc = inputLocales.get(i);
\r
829 String language = uloc.getLanguage();
\r
830 String script = uloc.getScript();
\r
831 String country = uloc.getCountry();
\r
832 String variant = uloc.getVariant();
\r
834 boolean bInserted = false;
\r
835 for (int j = 0; j < result.size(); j++) {
\r
836 // Check if this locale is more specific
\r
837 // than existing locale entries already inserted
\r
838 // in the destination list
\r
839 ULocale u = result.get(j);
\r
840 if (!u.getLanguage().equals(language)) {
\r
843 String s = u.getScript();
\r
844 String c = u.getCountry();
\r
845 String v = u.getVariant();
\r
846 if (!s.equals(script)) {
\r
847 if (s.length() == 0 && c.length() == 0 && v.length() == 0) {
\r
848 result.add(j, uloc);
\r
851 } else if (s.length() == 0 && c.equals(country)) {
\r
852 // We want to see zh_Hant_HK before zh_HK
\r
853 result.add(j, uloc);
\r
856 } else if (script.length() == 0 && country.length() > 0 && c.length() == 0) {
\r
857 // We want to see zh_HK before zh_Hant
\r
858 result.add(j, uloc);
\r
864 if (!c.equals(country)) {
\r
865 if (c.length() == 0 && v.length() == 0) {
\r
866 result.add(j, uloc);
\r
871 if (!v.equals(variant) && v.length() == 0) {
\r
872 result.add(j, uloc);
\r
878 // Add this locale at the end of the list
\r
883 // TODO: Locale aliases might be resolved here
\r
884 // For example, zh_Hant_TW = zh_TW
\r
887 * Step 2: Append fallback locales for each entry
\r
890 * Before - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA
\r
891 * After - en_US_Boston, en_US, en, en_US, en, fr_FR, fr,
\r
892 * zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr
\r
895 while (index < result.size()) {
\r
896 ULocale uloc = result.get(index);
\r
898 uloc = uloc.getFallback();
\r
899 if (uloc.getLanguage().length() == 0) {
\r
903 result.add(index, uloc);
\r
909 * Step 3: Remove earlier occurrence of duplicated locales
\r
912 * Before - en_US_Boston, en_US, en, en_US, en, fr_FR, fr,
\r
913 * zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr
\r
914 * After - en_US_Boston, en_US, en, fr_FR, zh_TW, zh_Hant,
\r
918 while (index < result.size() - 1) {
\r
919 ULocale uloc = result.get(index);
\r
920 boolean bRemoved = false;
\r
921 for (int i = index + 1; i < result.size(); i++) {
\r
922 if (uloc.equals(result.get(i))) {
\r
923 // Remove earlier one
\r
924 result.remove(index);
\r
938 * This function can be overridden by subclasses to use different heuristics.
\r
939 * <b>It MUST return a 'safe' value,
\r
940 * one whose modification will not affect this object.</b>
\r
945 * @provisional This API might change or be removed in a future release.
\r
947 protected DateFormat guessDateFormat(int dateStyle, int timeStyle) {
\r
949 ULocale dfLocale = getAvailableLocale(TYPE_DATEFORMAT);
\r
950 if (dfLocale == null) {
\r
951 dfLocale = ULocale.ROOT;
\r
953 if (timeStyle == DF_NONE) {
\r
954 result = DateFormat.getDateInstance(getCalendar(), dateStyle, dfLocale);
\r
955 } else if (dateStyle == DF_NONE) {
\r
956 result = DateFormat.getTimeInstance(getCalendar(), timeStyle, dfLocale);
\r
958 result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, dfLocale);
\r
964 * This function can be overridden by subclasses to use different heuristics.
\r
965 * <b>It MUST return a 'safe' value,
\r
966 * one whose modification will not affect this object.</b>
\r
970 * @provisional This API might change or be removed in a future release.
\r
972 protected NumberFormat guessNumberFormat(int style) {
\r
973 NumberFormat result;
\r
974 ULocale nfLocale = getAvailableLocale(TYPE_NUMBERFORMAT);
\r
975 if (nfLocale == null) {
\r
976 nfLocale = ULocale.ROOT;
\r
980 result = NumberFormat.getInstance(nfLocale);
\r
982 case NF_SCIENTIFIC:
\r
983 result = NumberFormat.getScientificInstance(nfLocale);
\r
986 result = NumberFormat.getIntegerInstance(nfLocale);
\r
989 result = NumberFormat.getPercentInstance(nfLocale);
\r
992 result = NumberFormat.getCurrencyInstance(nfLocale);
\r
993 result.setCurrency(getCurrency());
\r
996 throw new IllegalArgumentException("Unknown number format style");
\r
1002 * This function can be overridden by subclasses to use different heuristics.
\r
1005 * @provisional This API might change or be removed in a future release.
\r
1007 protected String guessTerritory() {
\r
1009 // pass through locales to see if there is a territory.
\r
1010 for (ULocale locale : getLocales()) {
\r
1011 result = locale.getCountry();
\r
1012 if (result.length() != 0) {
\r
1016 // if not, guess from the first language tag, or maybe from
\r
1017 // intersection of languages, eg nl + fr => BE
\r
1018 // TODO: fix using real data
\r
1019 // for now, just use fixed values
\r
1020 ULocale firstLocale = getLocale(0);
\r
1021 String language = firstLocale.getLanguage();
\r
1022 String script = firstLocale.getScript();
\r
1024 if (script.length() != 0) {
\r
1025 result = language_territory_hack_map.get(language + "_" + script);
\r
1027 if (result == null) {
\r
1028 result = language_territory_hack_map.get(language);
\r
1030 if (result == null) {
\r
1031 result = "US"; // need *some* default
\r
1037 * This function can be overridden by subclasses to use different heuristics
\r
1040 * @provisional This API might change or be removed in a future release.
\r
1042 protected Currency guessCurrency() {
\r
1043 return Currency.getInstance(new ULocale("und-" + getTerritory()));
\r
1047 * This function can be overridden by subclasses to use different heuristics
\r
1048 * <b>It MUST return a 'safe' value,
\r
1049 * one whose modification will not affect this object.</b>
\r
1052 * @provisional This API might change or be removed in a future release.
\r
1054 protected List<ULocale> guessLocales() {
\r
1055 if (implicitLocales == null) {
\r
1056 List<ULocale> result = new ArrayList<ULocale>(1);
\r
1057 result.add(ULocale.getDefault());
\r
1058 implicitLocales = processLocales(result);
\r
1060 return implicitLocales;
\r
1064 * This function can be overridden by subclasses to use different heuristics.
\r
1065 * <b>It MUST return a 'safe' value,
\r
1066 * one whose modification will not affect this object.</b>
\r
1069 * @provisional This API might change or be removed in a future release.
\r
1071 protected Collator guessCollator() {
\r
1072 ULocale collLocale = getAvailableLocale(TYPE_COLLATOR);
\r
1073 if (collLocale == null) {
\r
1074 collLocale = ULocale.ROOT;
\r
1076 return Collator.getInstance(collLocale);
\r
1080 * This function can be overridden by subclasses to use different heuristics.
\r
1081 * <b>It MUST return a 'safe' value,
\r
1082 * one whose modification will not affect this object.</b>
\r
1086 * @provisional This API might change or be removed in a future release.
\r
1088 protected BreakIterator guessBreakIterator(int type) {
\r
1089 BreakIterator bitr = null;
\r
1090 ULocale brkLocale = getAvailableLocale(TYPE_BREAKITERATOR);
\r
1091 if (brkLocale == null) {
\r
1092 brkLocale = ULocale.ROOT;
\r
1095 case BI_CHARACTER:
\r
1096 bitr = BreakIterator.getCharacterInstance(brkLocale);
\r
1099 bitr = BreakIterator.getTitleInstance(brkLocale);
\r
1102 bitr = BreakIterator.getWordInstance(brkLocale);
\r
1105 bitr = BreakIterator.getLineInstance(brkLocale);
\r
1108 bitr = BreakIterator.getSentenceInstance(brkLocale);
\r
1111 throw new IllegalArgumentException("Unknown break iterator type");
\r
1117 * This function can be overridden by subclasses to use different heuristics.
\r
1118 * <b>It MUST return a 'safe' value,
\r
1119 * one whose modification will not affect this object.</b>
\r
1122 * @provisional This API might change or be removed in a future release.
\r
1124 protected TimeZone guessTimeZone() {
\r
1125 // TODO fix using real data
\r
1126 // for single-zone countries, pick that zone
\r
1127 // for others, pick the most populous zone
\r
1128 // for now, just use fixed value
\r
1129 // NOTE: in a few cases can do better by looking at language.
\r
1130 // Eg haw+US should go to Pacific/Honolulu
\r
1131 // fr+CA should go to America/Montreal
\r
1132 String timezoneString = territory_tzid_hack_map.get(getTerritory());
\r
1133 if (timezoneString == null) {
\r
1134 String[] attempt = ZoneMeta.getAvailableIDs(getTerritory());
\r
1135 if (attempt.length == 0) {
\r
1136 timezoneString = "Etc/GMT"; // gotta do something
\r
1139 // this all needs to be fixed to use real data. But for now, do slightly better by skipping cruft
\r
1140 for (i = 0; i < attempt.length; ++i) {
\r
1141 if (attempt[i].indexOf("/") >= 0) break;
\r
1143 if (i > attempt.length) i = 0;
\r
1144 timezoneString = attempt[i];
\r
1147 return TimeZone.getTimeZone(timezoneString);
\r
1151 * This function can be overridden by subclasses to use different heuristics.
\r
1152 * <b>It MUST return a 'safe' value,
\r
1153 * one whose modification will not affect this object.</b>
\r
1156 * @provisional This API might change or be removed in a future release.
\r
1158 protected Calendar guessCalendar() {
\r
1159 ULocale calLocale = getAvailableLocale(TYPE_CALENDAR);
\r
1160 if (calLocale == null) {
\r
1161 calLocale = ULocale.US;
\r
1163 return Calendar.getInstance(getTimeZone(), calLocale);
\r
1168 private List<ULocale> locales;
\r
1169 private String territory;
\r
1170 private Currency currency;
\r
1171 private TimeZone timezone;
\r
1172 private Calendar calendar;
\r
1173 private Collator collator;
\r
1174 private BreakIterator[] breakIterators;
\r
1175 private DateFormat[][] dateFormats;
\r
1176 private NumberFormat[] numberFormats;
\r
1177 private List<ULocale> implicitLocales;
\r
1184 private ULocale getAvailableLocale(int type) {
\r
1185 List<ULocale> locs = getLocales();
\r
1186 ULocale result = null;
\r
1187 for (int i = 0; i < locs.size(); i++) {
\r
1188 ULocale l = locs.get(i);
\r
1189 if (isAvailableLocale(l, type)) {
\r
1197 private boolean isAvailableLocale(ULocale loc, int type) {
\r
1198 BitSet bits = available_locales.get(loc);
\r
1199 if (bits != null && bits.get(type)) {
\r
1206 * Available locales for service types
\r
1208 private static final HashMap<ULocale, BitSet> available_locales = new HashMap<ULocale, BitSet>();
\r
1209 private static final int
\r
1211 TYPE_CALENDAR = 1,
\r
1212 TYPE_DATEFORMAT= 2,
\r
1213 TYPE_NUMBERFORMAT = 3,
\r
1214 TYPE_COLLATOR = 4,
\r
1215 TYPE_BREAKITERATOR = 5,
\r
1216 TYPE_LIMIT = TYPE_BREAKITERATOR + 1;
\r
1220 ULocale[] allLocales = ULocale.getAvailableLocales();
\r
1221 for (int i = 0; i < allLocales.length; i++) {
\r
1222 bits = new BitSet(TYPE_LIMIT);
\r
1223 available_locales.put(allLocales[i], bits);
\r
1224 bits.set(TYPE_GENERIC);
\r
1227 ULocale[] calLocales = Calendar.getAvailableULocales();
\r
1228 for (int i = 0; i < calLocales.length; i++) {
\r
1229 bits = available_locales.get(calLocales[i]);
\r
1230 if (bits == null) {
\r
1231 bits = new BitSet(TYPE_LIMIT);
\r
1232 available_locales.put(allLocales[i], bits);
\r
1234 bits.set(TYPE_CALENDAR);
\r
1237 ULocale[] dateLocales = DateFormat.getAvailableULocales();
\r
1238 for (int i = 0; i < dateLocales.length; i++) {
\r
1239 bits = available_locales.get(dateLocales[i]);
\r
1240 if (bits == null) {
\r
1241 bits = new BitSet(TYPE_LIMIT);
\r
1242 available_locales.put(allLocales[i], bits);
\r
1244 bits.set(TYPE_DATEFORMAT);
\r
1247 ULocale[] numLocales = NumberFormat.getAvailableULocales();
\r
1248 for (int i = 0; i < numLocales.length; i++) {
\r
1249 bits = available_locales.get(numLocales[i]);
\r
1250 if (bits == null) {
\r
1251 bits = new BitSet(TYPE_LIMIT);
\r
1252 available_locales.put(allLocales[i], bits);
\r
1254 bits.set(TYPE_NUMBERFORMAT);
\r
1257 ULocale[] collLocales = Collator.getAvailableULocales();
\r
1258 for (int i = 0; i < collLocales.length; i++) {
\r
1259 bits = available_locales.get(collLocales[i]);
\r
1260 if (bits == null) {
\r
1261 bits = new BitSet(TYPE_LIMIT);
\r
1262 available_locales.put(allLocales[i], bits);
\r
1264 bits.set(TYPE_COLLATOR);
\r
1267 ULocale[] brkLocales = BreakIterator.getAvailableULocales();
\r
1268 for (int i = 0; i < brkLocales.length; i++) {
\r
1269 bits = available_locales.get(brkLocales[i]);
\r
1270 bits.set(TYPE_BREAKITERATOR);
\r
1274 /** WARNING: All of this data is temporary, until we start importing from CLDR!!!
\r
1277 private static final Map<String, String> language_territory_hack_map = new HashMap<String, String>();
\r
1278 private static final String[][] language_territory_hack = {
\r
1417 {"zh_Hant", "TW"},
\r
1434 for (int i = 0; i < language_territory_hack.length; ++i) {
\r
1435 language_territory_hack_map.put(language_territory_hack[i][0],language_territory_hack[i][1]);
\r
1439 static final Map<String, String> territory_tzid_hack_map = new HashMap<String, String>();
\r
1440 static final String[][] territory_tzid_hack = {
\r
1441 {"AQ", "Antarctica/McMurdo"},
\r
1442 {"AR", "America/Buenos_Aires"},
\r
1443 {"AU", "Australia/Sydney"},
\r
1444 {"BR", "America/Sao_Paulo"},
\r
1445 {"CA", "America/Toronto"},
\r
1446 {"CD", "Africa/Kinshasa"},
\r
1447 {"CL", "America/Santiago"},
\r
1448 {"CN", "Asia/Shanghai"},
\r
1449 {"EC", "America/Guayaquil"},
\r
1450 {"ES", "Europe/Madrid"},
\r
1451 {"GB", "Europe/London"},
\r
1452 {"GL", "America/Godthab"},
\r
1453 {"ID", "Asia/Jakarta"},
\r
1454 {"ML", "Africa/Bamako"},
\r
1455 {"MX", "America/Mexico_City"},
\r
1456 {"MY", "Asia/Kuala_Lumpur"},
\r
1457 {"NZ", "Pacific/Auckland"},
\r
1458 {"PT", "Europe/Lisbon"},
\r
1459 {"RU", "Europe/Moscow"},
\r
1460 {"UA", "Europe/Kiev"},
\r
1461 {"US", "America/New_York"},
\r
1462 {"UZ", "Asia/Tashkent"},
\r
1463 {"PF", "Pacific/Tahiti"},
\r
1464 {"FM", "Pacific/Kosrae"},
\r
1465 {"KI", "Pacific/Tarawa"},
\r
1466 {"KZ", "Asia/Almaty"},
\r
1467 {"MH", "Pacific/Majuro"},
\r
1468 {"MN", "Asia/Ulaanbaatar"},
\r
1469 {"SJ", "Arctic/Longyearbyen"},
\r
1470 {"UM", "Pacific/Midway"},
\r
1473 for (int i = 0; i < territory_tzid_hack.length; ++i) {
\r
1474 territory_tzid_hack_map.put(territory_tzid_hack[i][0],territory_tzid_hack[i][1]);
\r
1478 // Freezable implementation
\r
1480 private boolean frozen;
\r
1484 * @provisional This API might change or be removed in a future release.
\r
1486 public boolean isFrozen() {
\r
1492 * @provisional This API might change or be removed in a future release.
\r
1494 public GlobalizationPreferences freeze() {
\r
1501 * @provisional This API might change or be removed in a future release.
\r
1503 public GlobalizationPreferences cloneAsThawed() {
\r
1505 GlobalizationPreferences result = (GlobalizationPreferences) clone();
\r
1506 result.frozen = false;
\r
1508 } catch (CloneNotSupportedException e) {
\r
1509 // will always work
\r