2 *******************************************************************************
3 * Copyright (C) 2004-2011, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.util;
9 import java.text.ParseException;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.BitSet;
13 import java.util.Date;
14 import java.util.HashMap;
15 import java.util.List;
17 import java.util.MissingResourceException;
18 import java.util.ResourceBundle;
20 import com.ibm.icu.impl.Utility;
21 import com.ibm.icu.text.BreakIterator;
22 import com.ibm.icu.text.Collator;
23 import com.ibm.icu.text.DateFormat;
24 import com.ibm.icu.text.NumberFormat;
25 import com.ibm.icu.text.SimpleDateFormat;
28 * This convenience class provides a mechanism for bundling together different
29 * globalization preferences. It includes:
31 * <li>A list of locales/languages in preference order</li>
32 * <li>A territory</li>
36 * <li>A collator (for language-sensitive sorting, searching, and matching).</li>
37 * <li>Explicit overrides for date/time formats, etc.</li>
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.
44 * To reset any explicit field so that it will get heuristic values, pass in
45 * null. For example, myPreferences.setLocale(null);
47 * All of the heuristics can be customized by subclasses, by overriding
48 * getTerritory(), guessCollator(), etc.
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.
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).
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>
64 * <li>to get the display name for the first day of the week, use the calendar +
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>
75 * @draft ICU 3.6 (retainAll)
76 * @provisional This API might change or be removed in a future release.
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?
86 public class GlobalizationPreferences implements Freezable<GlobalizationPreferences> {
91 * @provisional This API might change or be removed in a future release.
93 public GlobalizationPreferences(){}
97 * @provisional This API might change or be removed in a future release.
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
106 private static final int NF_LIMIT = NF_INTEGER + 1;
111 * @provisional This API might change or be removed in a future release.
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
120 private static final int DF_LIMIT = DF_NONE + 1;
123 * For selecting a choice of display names
125 * @provisional This API might change or be removed in a future release.
127 public static final int
134 ID_KEYWORD_VALUE = 6,
136 ID_CURRENCY_SYMBOL = 8,
139 //private static final int ID_LIMIT = ID_TIMEZONE + 1;
142 * Break iterator type
144 * @provisional This API might change or be removed in a future release.
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
153 private static final int BI_LIMIT = BI_TITLE + 1;
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.
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
166 * @provisional This API might change or be removed in a future release.
168 public GlobalizationPreferences setLocales(List<ULocale> inputLocales) {
170 throw new UnsupportedOperationException("Attempt to modify immutable object");
172 locales = processLocales(inputLocales);
177 * Get a copy of the language/locale priority list
179 * @return a copy of the language/locale priority list.
181 * @provisional This API might change or be removed in a future release.
183 public List<ULocale> getLocales() {
184 List<ULocale> result;
185 if (locales == null) {
186 result = guessLocales();
188 result = new ArrayList<ULocale>();
189 result.addAll(locales);
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
199 * @provisional This API might change or be removed in a future release.
201 public ULocale getLocale(int index) {
202 List<ULocale> lcls = locales;
204 lcls = guessLocales();
206 if (index >= 0 && index < lcls.size()) {
207 return lcls.get(index);
213 * Convenience routine for setting the language/locale priority
214 * list from an array.
216 * @see #setLocales(List locales)
217 * @param uLocales list of locales in an array
218 * @return this, for chaining
220 * @provisional This API might change or be removed in a future release.
222 public GlobalizationPreferences setLocales(ULocale[] uLocales) {
224 throw new UnsupportedOperationException("Attempt to modify immutable object");
226 return setLocales(Arrays.asList(uLocales));
230 * Convenience routine for setting the language/locale priority
231 * list from a single locale/language.
233 * @see #setLocales(List locales)
234 * @param uLocale single locale
235 * @return this, for chaining
237 * @provisional This API might change or be removed in a future release.
239 public GlobalizationPreferences setLocale(ULocale uLocale) {
241 throw new UnsupportedOperationException("Attempt to modify immutable object");
243 return setLocales(new ULocale[]{uLocale});
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
254 * @provisional This API might change or be removed in a future release.
256 public GlobalizationPreferences setLocales(String acceptLanguageString) {
258 throw new UnsupportedOperationException("Attempt to modify immutable object");
260 ULocale[] acceptLocales = null;
262 acceptLocales = ULocale.parseAcceptLanguage(acceptLanguageString, true);
263 } catch (ParseException pe) {
264 //TODO: revisit after 3.8
265 throw new IllegalArgumentException("Invalid Accept-Language string");
267 return setLocales(acceptLocales);
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.
275 * @param baseName the base name of the resource bundle, a fully qualified
277 * @return a resource bundle for the given base name and locale based on the
278 * language/locale priority list stored in this object
280 * @provisional This API might change or be removed in a future release.
282 public ResourceBundle getResourceBundle(String baseName) {
283 return getResourceBundle(baseName, null);
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.
291 * @param baseName the base name of the resource bundle, a fully qualified
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
297 * @provisional This API might change or be removed in a future release.
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
313 if (loader == null) {
314 candidate = UResourceBundle.getBundleInstance(baseName, localeName);
317 candidate = UResourceBundle.getBundleInstance(baseName, localeName, loader);
319 if (candidate != null) {
320 actualLocaleName = candidate.getULocale().getName();
321 if (actualLocaleName.equals(localeName)) {
326 // Preserve the available bundle as the last resort
330 } catch (MissingResourceException mre) {
331 actualLocaleName = null;
336 throw new MissingResourceException("Can't find bundle for base name "
337 + baseName, baseName, "");
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.
349 * @param territory code
350 * @return this, for chaining
352 * @provisional This API might change or be removed in a future release.
354 public GlobalizationPreferences setTerritory(String territory) {
356 throw new UnsupportedOperationException("Attempt to modify immutable object");
358 this.territory = territory; // immutable, so don't need to clone
363 * Gets the territory setting. If it wasn't explicitly set, it is
364 * computed from the general locale setting.
366 * @return territory code, explicit or implicit.
368 * @provisional This API might change or be removed in a future release.
370 public String getTerritory() {
371 if (territory == null) {
372 return guessTerritory();
374 return territory; // immutable, so don't need to clone
378 * Sets the currency code. If this has not been set, uses default for territory.
380 * @param currency Valid ISO 4217 currency code.
381 * @return this, for chaining
383 * @provisional This API might change or be removed in a future release.
385 public GlobalizationPreferences setCurrency(Currency currency) {
387 throw new UnsupportedOperationException("Attempt to modify immutable object");
389 this.currency = currency; // immutable, so don't need to clone
394 * Get a copy of the currency computed according to the settings.
396 * @return currency code, explicit or implicit.
398 * @provisional This API might change or be removed in a future release.
400 public Currency getCurrency() {
401 if (currency == null) {
402 return guessCurrency();
404 return currency; // immutable, so don't have to clone
408 * Sets the calendar. If this has not been set, uses default for territory.
410 * @param calendar arbitrary calendar
411 * @return this, for chaining
413 * @provisional This API might change or be removed in a future release.
415 public GlobalizationPreferences setCalendar(Calendar calendar) {
417 throw new UnsupportedOperationException("Attempt to modify immutable object");
419 this.calendar = (Calendar) calendar.clone(); // clone for safety
424 * Get a copy of the calendar according to the settings.
426 * @return calendar explicit or implicit.
428 * @provisional This API might change or be removed in a future release.
430 public Calendar getCalendar() {
431 if (calendar == null) {
432 return guessCalendar();
434 Calendar temp = (Calendar) calendar.clone(); // clone for safety
435 temp.setTimeZone(getTimeZone());
436 temp.setTimeInMillis(System.currentTimeMillis());
441 * Sets the timezone ID. If this has not been set, uses default for territory.
443 * @param timezone a valid TZID (see UTS#35).
444 * @return this, for chaining
446 * @provisional This API might change or be removed in a future release.
448 public GlobalizationPreferences setTimeZone(TimeZone timezone) {
450 throw new UnsupportedOperationException("Attempt to modify immutable object");
452 this.timezone = (TimeZone) timezone.clone(); // clone for safety;
457 * Get the timezone. It was either explicitly set, or is
458 * heuristically computed from other settings.
460 * @return timezone, either implicitly or explicitly set
462 * @provisional This API might change or be removed in a future release.
464 public TimeZone getTimeZone() {
465 if (timezone == null) {
466 return guessTimeZone();
468 return timezone.cloneAsThawed(); // clone for safety
472 * Get a copy of the collator according to the settings.
474 * @return collator explicit or implicit.
476 * @provisional This API might change or be removed in a future release.
478 public Collator getCollator() {
479 if (collator == null) {
480 return guessCollator();
483 return (Collator) collator.clone(); // clone for safety
484 } catch (CloneNotSupportedException e) {
485 throw new IllegalStateException("Error in cloning collator");
490 * Explicitly set the collator for this object.
491 * @param collator The collator object to be passed.
492 * @return this, for chaining
494 * @provisional This API might change or be removed in a future release.
496 public GlobalizationPreferences setCollator(Collator collator) {
498 throw new UnsupportedOperationException("Attempt to modify immutable object");
501 this.collator = (Collator) collator.clone(); // clone for safety
502 } catch (CloneNotSupportedException e) {
503 throw new IllegalStateException("Error in cloning collator");
509 * Get a copy of the break iterator for the specified type according to the
512 * @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE
513 * @return break iterator explicit or implicit
515 * @provisional This API might change or be removed in a future release.
517 public BreakIterator getBreakIterator(int type) {
518 if (type < BI_CHARACTER || type >= BI_LIMIT) {
519 throw new IllegalArgumentException("Illegal break iterator type");
521 if (breakIterators == null || breakIterators[type] == null) {
522 return guessBreakIterator(type);
524 return (BreakIterator) breakIterators[type].clone(); // clone for safety
528 * Explicitly set the break iterator for this object.
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
534 * @provisional This API might change or be removed in a future release.
536 public GlobalizationPreferences setBreakIterator(int type, BreakIterator iterator) {
537 if (type < BI_CHARACTER || type >= BI_LIMIT) {
538 throw new IllegalArgumentException("Illegal break iterator type");
541 throw new UnsupportedOperationException("Attempt to modify immutable object");
543 if (breakIterators == null)
544 breakIterators = new BreakIterator[BI_LIMIT];
545 breakIterators[type] = (BreakIterator) iterator.clone(); // clone for safety
550 * Get the display name for an ID: language, script, territory, currency, timezone...
551 * Uses the language priority list to do so.
553 * @param id language code, script code, ...
554 * @param type specifies the type of the ID: ID_LANGUAGE, etc.
555 * @return the display name
557 * @provisional This API might change or be removed in a future release.
559 public String getDisplayName(String id, int type) {
561 for (ULocale locale : getLocales()) {
562 if (!isAvailableLocale(locale, TYPE_GENERIC)) {
567 result = ULocale.getDisplayName(id, locale);
570 result = ULocale.getDisplayLanguage(id, locale);
573 result = ULocale.getDisplayScript("und-" + id, locale);
576 result = ULocale.getDisplayCountry("und-" + id, locale);
579 // TODO fix variant parsing
580 result = ULocale.getDisplayVariant("und-QQ-" + id, locale);
583 result = ULocale.getDisplayKeyword(id, locale);
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])) {
594 case ID_CURRENCY_SYMBOL:
596 Currency temp = new Currency(id);
597 result =temp.getName(locale, type==ID_CURRENCY
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
603 // TODO: have method that doesn't require us
604 // to create a currency
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
612 // hack for couldn't match
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);
623 if (teststr.length() == 2) {
625 for (int i = 0; i < 2; i++) {
626 char c = teststr.charAt(i);
627 if (c < 'A' || 'Z' < c) {
638 throw new IllegalArgumentException("Unknown type: " + type);
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)) {
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
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
661 * @provisional This API might change or be removed in a future release.
663 public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) {
665 throw new UnsupportedOperationException("Attempt to modify immutable object");
667 if (dateFormats == null) {
668 dateFormats = new DateFormat[DF_LIMIT][DF_LIMIT];
670 dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety
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.
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
685 * @provisional This API might change or be removed in a future release.
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");
693 DateFormat result = null;
694 if (dateFormats != null) {
695 result = dateFormats[dateStyle][timeStyle];
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());
702 result = guessDateFormat(dateStyle, timeStyle);
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
713 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER
715 * @provisional This API might change or be removed in a future release.
717 public NumberFormat getNumberFormat(int style) {
718 if (style < 0 || style >= NF_LIMIT) {
719 throw new IllegalArgumentException("Illegal number format type");
721 NumberFormat result = null;
722 if (numberFormats != null) {
723 result = numberFormats[style];
725 if (result != null) {
726 result = (NumberFormat) result.clone(); // clone for safety (later optimize)
728 result = guessNumberFormat(style);
734 * Sets a number format explicitly. Overrides the general locale settings.
736 * @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER
737 * @param format The number format
738 * @return this, for chaining
740 * @provisional This API might change or be removed in a future release.
742 public GlobalizationPreferences setNumberFormat(int style, NumberFormat format) {
744 throw new UnsupportedOperationException("Attempt to modify immutable object");
746 if (numberFormats == null) {
747 numberFormats = new NumberFormat[NF_LIMIT];
749 numberFormats[style] = (NumberFormat) format.clone(); // for safety
754 * Restore the object to the initial state.
756 * @return this, for chaining
758 * @provisional This API might change or be removed in a future release.
760 public GlobalizationPreferences reset() {
762 throw new UnsupportedOperationException("Attempt to modify immutable object");
768 breakIterators = null;
772 numberFormats = null;
773 implicitLocales = null;
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
784 * <b>Step 1</b>: Move later occurrence of more specific locale before earlier
785 * occurrence of less specific locale.
787 * Before: en, fr_FR, en_US, en_GB
789 * After: en_US, en_GB, en, fr_FR
792 * <b>Step 2</b>: Append a fallback locale to each locale.
794 * Before: en_US, en_GB, en, fr_FR
796 * After: en_US, en, en_GB, en, en, fr_FR, fr
799 * <b>Step 3</b>: Remove earlier occurrence of duplicated locale entries.
801 * Before: en_US, en, en_GB, en, en, fr_FR, fr
803 * After: en_US, en_GB, en, fr_FR, fr
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.
811 * @param inputLocales The list of input locales
813 * @provisional This API might change or be removed in a future release.
815 protected List<ULocale> processLocales(List<ULocale> inputLocales) {
816 List<ULocale> result = new ArrayList<ULocale>();
818 * Step 1: Relocate later occurrence of more specific locale
819 * before earlier occurrence of less specific locale.
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
825 for (int i = 0; i < inputLocales.size(); i++) {
826 ULocale uloc = inputLocales.get(i);
828 String language = uloc.getLanguage();
829 String script = uloc.getScript();
830 String country = uloc.getCountry();
831 String variant = uloc.getVariant();
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)) {
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) {
850 } else if (s.length() == 0 && c.equals(country)) {
851 // We want to see zh_Hant_HK before zh_HK
855 } else if (script.length() == 0 && country.length() > 0 && c.length() == 0) {
856 // We want to see zh_HK before zh_Hant
863 if (!c.equals(country)) {
864 if (c.length() == 0 && v.length() == 0) {
870 if (!v.equals(variant) && v.length() == 0) {
877 // Add this locale at the end of the list
882 // TODO: Locale aliases might be resolved here
883 // For example, zh_Hant_TW = zh_TW
886 * Step 2: Append fallback locales for each entry
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
894 while (index < result.size()) {
895 ULocale uloc = result.get(index);
897 uloc = uloc.getFallback();
898 if (uloc.getLanguage().length() == 0) {
902 result.add(index, uloc);
908 * Step 3: Remove earlier occurrence of duplicated locales
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,
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);
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>
944 * @provisional This API might change or be removed in a future release.
946 protected DateFormat guessDateFormat(int dateStyle, int timeStyle) {
948 ULocale dfLocale = getAvailableLocale(TYPE_DATEFORMAT);
949 if (dfLocale == null) {
950 dfLocale = ULocale.ROOT;
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);
957 result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, dfLocale);
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>
969 * @provisional This API might change or be removed in a future release.
971 protected NumberFormat guessNumberFormat(int style) {
973 ULocale nfLocale = getAvailableLocale(TYPE_NUMBERFORMAT);
974 if (nfLocale == null) {
975 nfLocale = ULocale.ROOT;
979 result = NumberFormat.getInstance(nfLocale);
982 result = NumberFormat.getScientificInstance(nfLocale);
985 result = NumberFormat.getIntegerInstance(nfLocale);
988 result = NumberFormat.getPercentInstance(nfLocale);
991 result = NumberFormat.getCurrencyInstance(nfLocale);
992 result.setCurrency(getCurrency());
995 throw new IllegalArgumentException("Unknown number format style");
1001 * This function can be overridden by subclasses to use different heuristics.
1004 * @provisional This API might change or be removed in a future release.
1006 protected String guessTerritory() {
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) {
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();
1023 if (script.length() != 0) {
1024 result = language_territory_hack_map.get(language + "_" + script);
1026 if (result == null) {
1027 result = language_territory_hack_map.get(language);
1029 if (result == null) {
1030 result = "US"; // need *some* default
1036 * This function can be overridden by subclasses to use different heuristics
1039 * @provisional This API might change or be removed in a future release.
1041 protected Currency guessCurrency() {
1042 return Currency.getInstance(new ULocale("und-" + getTerritory()));
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>
1051 * @provisional This API might change or be removed in a future release.
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);
1059 return implicitLocales;
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>
1068 * @provisional This API might change or be removed in a future release.
1070 protected Collator guessCollator() {
1071 ULocale collLocale = getAvailableLocale(TYPE_COLLATOR);
1072 if (collLocale == null) {
1073 collLocale = ULocale.ROOT;
1075 return Collator.getInstance(collLocale);
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>
1085 * @provisional This API might change or be removed in a future release.
1087 protected BreakIterator guessBreakIterator(int type) {
1088 BreakIterator bitr = null;
1089 ULocale brkLocale = getAvailableLocale(TYPE_BREAKITERATOR);
1090 if (brkLocale == null) {
1091 brkLocale = ULocale.ROOT;
1095 bitr = BreakIterator.getCharacterInstance(brkLocale);
1098 bitr = BreakIterator.getTitleInstance(brkLocale);
1101 bitr = BreakIterator.getWordInstance(brkLocale);
1104 bitr = BreakIterator.getLineInstance(brkLocale);
1107 bitr = BreakIterator.getSentenceInstance(brkLocale);
1110 throw new IllegalArgumentException("Unknown break iterator type");
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>
1121 * @provisional This API might change or be removed in a future release.
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
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;
1142 if (i > attempt.length) i = 0;
1143 timezoneString = attempt[i];
1146 return TimeZone.getTimeZone(timezoneString);
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>
1155 * @provisional This API might change or be removed in a future release.
1157 protected Calendar guessCalendar() {
1158 ULocale calLocale = getAvailableLocale(TYPE_CALENDAR);
1159 if (calLocale == null) {
1160 calLocale = ULocale.US;
1162 return Calendar.getInstance(getTimeZone(), calLocale);
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;
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)) {
1196 private boolean isAvailableLocale(ULocale loc, int type) {
1197 BitSet bits = available_locales.get(loc);
1198 if (bits != null && bits.get(type)) {
1205 * Available locales for service types
1207 private static final HashMap<ULocale, BitSet> available_locales = new HashMap<ULocale, BitSet>();
1208 private static final int
1212 TYPE_NUMBERFORMAT = 3,
1214 TYPE_BREAKITERATOR = 5,
1215 TYPE_LIMIT = TYPE_BREAKITERATOR + 1;
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);
1226 ULocale[] calLocales = Calendar.getAvailableULocales();
1227 for (int i = 0; i < calLocales.length; i++) {
1228 bits = available_locales.get(calLocales[i]);
1230 bits = new BitSet(TYPE_LIMIT);
1231 available_locales.put(allLocales[i], bits);
1233 bits.set(TYPE_CALENDAR);
1236 ULocale[] dateLocales = DateFormat.getAvailableULocales();
1237 for (int i = 0; i < dateLocales.length; i++) {
1238 bits = available_locales.get(dateLocales[i]);
1240 bits = new BitSet(TYPE_LIMIT);
1241 available_locales.put(allLocales[i], bits);
1243 bits.set(TYPE_DATEFORMAT);
1246 ULocale[] numLocales = NumberFormat.getAvailableULocales();
1247 for (int i = 0; i < numLocales.length; i++) {
1248 bits = available_locales.get(numLocales[i]);
1250 bits = new BitSet(TYPE_LIMIT);
1251 available_locales.put(allLocales[i], bits);
1253 bits.set(TYPE_NUMBERFORMAT);
1256 ULocale[] collLocales = Collator.getAvailableULocales();
1257 for (int i = 0; i < collLocales.length; i++) {
1258 bits = available_locales.get(collLocales[i]);
1260 bits = new BitSet(TYPE_LIMIT);
1261 available_locales.put(allLocales[i], bits);
1263 bits.set(TYPE_COLLATOR);
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);
1273 /** WARNING: All of this data is temporary, until we start importing from CLDR!!!
1276 private static final Map<String, String> language_territory_hack_map = new HashMap<String, String>();
1277 private static final String[][] language_territory_hack = {
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]);
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"},
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]);
1477 // Freezable implementation
1479 private boolean frozen;
1483 * @provisional This API might change or be removed in a future release.
1485 public boolean isFrozen() {
1491 * @provisional This API might change or be removed in a future release.
1493 public GlobalizationPreferences freeze() {
1500 * @provisional This API might change or be removed in a future release.
1502 public GlobalizationPreferences cloneAsThawed() {
1504 GlobalizationPreferences result = (GlobalizationPreferences) clone();
1505 result.frozen = false;
1507 } catch (CloneNotSupportedException e) {