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