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