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