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