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