You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1518 lines
52 KiB
1518 lines
52 KiB
6 years ago
|
// © 2016 and later: Unicode, Inc. and others.
|
||
|
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||
|
/*
|
||
|
*******************************************************************************
|
||
|
* Copyright (C) 2004-2014, International Business Machines Corporation and
|
||
|
* others. All Rights Reserved.
|
||
|
*******************************************************************************
|
||
|
*/
|
||
|
package com.fr.third.ibm.icu.util;
|
||
|
|
||
|
import java.text.ParseException;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.BitSet;
|
||
|
import java.util.Date;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.List;
|
||
|
import java.util.Map;
|
||
|
import java.util.MissingResourceException;
|
||
|
import java.util.ResourceBundle;
|
||
|
|
||
|
import com.fr.third.ibm.icu.impl.Utility;
|
||
|
import com.fr.third.ibm.icu.text.BreakIterator;
|
||
|
import com.fr.third.ibm.icu.text.Collator;
|
||
|
import com.fr.third.ibm.icu.text.DateFormat;
|
||
|
import com.fr.third.ibm.icu.text.NumberFormat;
|
||
|
import com.fr.third.ibm.icu.text.SimpleDateFormat;
|
||
|
|
||
|
/**
|
||
|
* This convenience class provides a mechanism for bundling together different
|
||
|
* globalization preferences. It includes:
|
||
|
* <ul>
|
||
|
* <li>A list of locales/languages in preference order</li>
|
||
|
* <li>A territory</li>
|
||
|
* <li>A currency</li>
|
||
|
* <li>A timezone</li>
|
||
|
* <li>A calendar</li>
|
||
|
* <li>A collator (for language-sensitive sorting, searching, and matching).</li>
|
||
|
* <li>Explicit overrides for date/time formats, etc.</li>
|
||
|
* </ul>
|
||
|
* The class will heuristically compute implicit, heuristic values for the above
|
||
|
* based on available data if explicit values are not supplied. These implicit
|
||
|
* values can be presented to users for confirmation, or replacement if the
|
||
|
* values are incorrect.
|
||
|
* <p>
|
||
|
* To reset any explicit field so that it will get heuristic values, pass in
|
||
|
* null. For example, myPreferences.setLocale(null);
|
||
|
* <p>
|
||
|
* All of the heuristics can be customized by subclasses, by overriding
|
||
|
* getTerritory(), guessCollator(), etc.
|
||
|
* <p>
|
||
|
* The class also supplies display names for languages, scripts, territories,
|
||
|
* currencies, timezones, etc. These are computed according to the
|
||
|
* locale/language preference list. Thus, if the preference is Breton; French;
|
||
|
* English, then the display name for a language will be returned in Breton if
|
||
|
* available, otherwise in French if available, otherwise in English.
|
||
|
* <p>
|
||
|
* The codes used to reference territory, currency, etc. are as defined elsewhere
|
||
|
* in ICU, and are taken from CLDR (which reflects RFC 3066bis usage, ISO 4217,
|
||
|
* and the TZ Timezone database identifiers).
|
||
|
* <p>
|
||
|
* <b>This is at a prototype stage, and has not incorporated all the design
|
||
|
* changes that we would like yet; further feedback is welcome.</b></p>
|
||
|
* Note:
|
||
|
* <ul>
|
||
|
* <li>to get the display name for the first day of the week, use the calendar +
|
||
|
* display names.</li>
|
||
|
* <li>to get the work days, ask the calendar (when that is available).</li>
|
||
|
* <li>to get papersize / measurement system/bidi-orientation, ask the locale
|
||
|
* (when that is available there)</li>
|
||
|
* <li>to get the field order in a date, and whether a time is 24hour or not,
|
||
|
* ask the DateFormat (when that is available there)</li>
|
||
|
* <li>it will support HOST locale when it becomes available (it is a special
|
||
|
* locale that will ask the services to use the host platform's values).</li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @draft ICU 3.6 (retainAll)
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
|
||
|
//TODO:
|
||
|
// - Add Holidays
|
||
|
// - Add convenience to get/take Locale as well as ULocale.
|
||
|
// - Add Lenient datetime formatting when that is available.
|
||
|
// - Should this be serializable?
|
||
|
// - Other utilities?
|
||
|
|
||
|
public class GlobalizationPreferences implements Freezable<GlobalizationPreferences> {
|
||
|
|
||
|
/**
|
||
|
* Default constructor
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences(){}
|
||
|
/**
|
||
|
* Number Format type
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public static final int
|
||
|
NF_NUMBER = 0, // NumberFormat.NUMBERSTYLE
|
||
|
NF_CURRENCY = 1, // NumberFormat.CURRENCYSTYLE
|
||
|
NF_PERCENT = 2, // NumberFormat.PERCENTSTYLE
|
||
|
NF_SCIENTIFIC = 3, // NumberFormat.SCIENTIFICSTYLE
|
||
|
NF_INTEGER = 4; // NumberFormat.INTEGERSTYLE
|
||
|
|
||
|
private static final int NF_LIMIT = NF_INTEGER + 1;
|
||
|
|
||
|
/**
|
||
|
* Date Format type
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public static final int
|
||
|
DF_FULL = DateFormat.FULL, // 0
|
||
|
DF_LONG = DateFormat.LONG, // 1
|
||
|
DF_MEDIUM = DateFormat.MEDIUM, // 2
|
||
|
DF_SHORT = DateFormat.SHORT, // 3
|
||
|
DF_NONE = 4;
|
||
|
|
||
|
private static final int DF_LIMIT = DF_NONE + 1;
|
||
|
|
||
|
/**
|
||
|
* For selecting a choice of display names
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public static final int
|
||
|
ID_LOCALE = 0,
|
||
|
ID_LANGUAGE = 1,
|
||
|
ID_SCRIPT = 2,
|
||
|
ID_TERRITORY = 3,
|
||
|
ID_VARIANT = 4,
|
||
|
ID_KEYWORD = 5,
|
||
|
ID_KEYWORD_VALUE = 6,
|
||
|
ID_CURRENCY = 7,
|
||
|
ID_CURRENCY_SYMBOL = 8,
|
||
|
ID_TIMEZONE = 9;
|
||
|
|
||
|
//private static final int ID_LIMIT = ID_TIMEZONE + 1;
|
||
|
|
||
|
/**
|
||
|
* Break iterator type
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public static final int
|
||
|
BI_CHARACTER = BreakIterator.KIND_CHARACTER, // 0
|
||
|
BI_WORD = BreakIterator.KIND_WORD, // 1
|
||
|
BI_LINE = BreakIterator.KIND_LINE, // 2
|
||
|
BI_SENTENCE = BreakIterator.KIND_SENTENCE, // 3
|
||
|
BI_TITLE = BreakIterator.KIND_TITLE; // 4
|
||
|
|
||
|
private static final int BI_LIMIT = BI_TITLE + 1;
|
||
|
|
||
|
/**
|
||
|
* Sets the language/locale priority list. If other information is
|
||
|
* not (yet) available, this is used to to produce a default value
|
||
|
* for the appropriate territory, currency, timezone, etc. The
|
||
|
* user should be given the opportunity to correct those defaults
|
||
|
* in case they are incorrect.
|
||
|
*
|
||
|
* @param inputLocales list of locales in priority order, eg {"be", "fr"}
|
||
|
* for Breton first, then French if that fails.
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setLocales(List<ULocale> inputLocales) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
locales = processLocales(inputLocales);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a copy of the language/locale priority list
|
||
|
*
|
||
|
* @return a copy of the language/locale priority list.
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public List<ULocale> getLocales() {
|
||
|
List<ULocale> result;
|
||
|
if (locales == null) {
|
||
|
result = guessLocales();
|
||
|
} else {
|
||
|
result = new ArrayList<ULocale>();
|
||
|
result.addAll(locales);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience function for getting the locales in priority order
|
||
|
* @param index The index (0..n) of the desired item.
|
||
|
* @return desired item. null if index is out of range
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public ULocale getLocale(int index) {
|
||
|
List<ULocale> lcls = locales;
|
||
|
if (lcls == null) {
|
||
|
lcls = guessLocales();
|
||
|
}
|
||
|
if (index >= 0 && index < lcls.size()) {
|
||
|
return lcls.get(index);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience routine for setting the language/locale priority
|
||
|
* list from an array.
|
||
|
*
|
||
|
* @see #setLocales(List locales)
|
||
|
* @param uLocales list of locales in an array
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setLocales(ULocale[] uLocales) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
return setLocales(Arrays.asList(uLocales));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience routine for setting the language/locale priority
|
||
|
* list from a single locale/language.
|
||
|
*
|
||
|
* @see #setLocales(List locales)
|
||
|
* @param uLocale single locale
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setLocale(ULocale uLocale) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
return setLocales(new ULocale[]{uLocale});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience routine for setting the locale priority list from
|
||
|
* an Accept-Language string.
|
||
|
* @see #setLocales(List locales)
|
||
|
* @param acceptLanguageString Accept-Language list, as defined by
|
||
|
* Section 14.4 of the RFC 2616 (HTTP 1.1)
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setLocales(String acceptLanguageString) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
ULocale[] acceptLocales = null;
|
||
|
try {
|
||
|
acceptLocales = ULocale.parseAcceptLanguage(acceptLanguageString, true);
|
||
|
} catch (ParseException pe) {
|
||
|
//TODO: revisit after 3.8
|
||
|
throw new IllegalArgumentException("Invalid Accept-Language string");
|
||
|
}
|
||
|
return setLocales(acceptLocales);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience function to get a ResourceBundle instance using
|
||
|
* the specified base name based on the language/locale priority list
|
||
|
* stored in this object.
|
||
|
*
|
||
|
* @param baseName the base name of the resource bundle, a fully qualified
|
||
|
* class name
|
||
|
* @return a resource bundle for the given base name and locale based on the
|
||
|
* language/locale priority list stored in this object
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public ResourceBundle getResourceBundle(String baseName) {
|
||
|
return getResourceBundle(baseName, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience function to get a ResourceBundle instance using
|
||
|
* the specified base name and class loader based on the language/locale
|
||
|
* priority list stored in this object.
|
||
|
*
|
||
|
* @param baseName the base name of the resource bundle, a fully qualified
|
||
|
* class name
|
||
|
* @param loader the class object from which to load the resource bundle
|
||
|
* @return a resource bundle for the given base name and locale based on the
|
||
|
* language/locale priority list stored in this object
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public ResourceBundle getResourceBundle(String baseName, ClassLoader loader) {
|
||
|
UResourceBundle urb = null;
|
||
|
UResourceBundle candidate = null;
|
||
|
String actualLocaleName = null;
|
||
|
List<ULocale> fallbacks = getLocales();
|
||
|
for (int i = 0; i < fallbacks.size(); i++) {
|
||
|
String localeName = (fallbacks.get(i)).toString();
|
||
|
if (actualLocaleName != null && localeName.equals(actualLocaleName)) {
|
||
|
// Actual locale name in the previous round may exactly matches
|
||
|
// with the next fallback locale
|
||
|
urb = candidate;
|
||
|
break;
|
||
|
}
|
||
|
try {
|
||
|
if (loader == null) {
|
||
|
candidate = UResourceBundle.getBundleInstance(baseName, localeName);
|
||
|
}
|
||
|
else {
|
||
|
candidate = UResourceBundle.getBundleInstance(baseName, localeName, loader);
|
||
|
}
|
||
|
if (candidate != null) {
|
||
|
actualLocaleName = candidate.getULocale().getName();
|
||
|
if (actualLocaleName.equals(localeName)) {
|
||
|
urb = candidate;
|
||
|
break;
|
||
|
}
|
||
|
if (urb == null) {
|
||
|
// Preserve the available bundle as the last resort
|
||
|
urb = candidate;
|
||
|
}
|
||
|
}
|
||
|
} catch (MissingResourceException mre) {
|
||
|
actualLocaleName = null;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
if (urb == null) {
|
||
|
throw new MissingResourceException("Can't find bundle for base name "
|
||
|
+ baseName, baseName, "");
|
||
|
}
|
||
|
return urb;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the territory, which is a valid territory according to for
|
||
|
* RFC 3066 (or successor). If not otherwise set, default
|
||
|
* currency and timezone values will be set from this. The user
|
||
|
* should be given the opportunity to correct those defaults in
|
||
|
* case they are incorrect.
|
||
|
*
|
||
|
* @param territory code
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setTerritory(String territory) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
this.territory = territory; // immutable, so don't need to clone
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the territory setting. If it wasn't explicitly set, it is
|
||
|
* computed from the general locale setting.
|
||
|
*
|
||
|
* @return territory code, explicit or implicit.
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public String getTerritory() {
|
||
|
if (territory == null) {
|
||
|
return guessTerritory();
|
||
|
}
|
||
|
return territory; // immutable, so don't need to clone
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the currency code. If this has not been set, uses default for territory.
|
||
|
*
|
||
|
* @param currency Valid ISO 4217 currency code.
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setCurrency(Currency currency) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
this.currency = currency; // immutable, so don't need to clone
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a copy of the currency computed according to the settings.
|
||
|
*
|
||
|
* @return currency code, explicit or implicit.
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public Currency getCurrency() {
|
||
|
if (currency == null) {
|
||
|
return guessCurrency();
|
||
|
}
|
||
|
return currency; // immutable, so don't have to clone
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the calendar. If this has not been set, uses default for territory.
|
||
|
*
|
||
|
* @param calendar arbitrary calendar
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setCalendar(Calendar calendar) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
this.calendar = (Calendar) calendar.clone(); // clone for safety
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a copy of the calendar according to the settings.
|
||
|
*
|
||
|
* @return calendar explicit or implicit.
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public Calendar getCalendar() {
|
||
|
if (calendar == null) {
|
||
|
return guessCalendar();
|
||
|
}
|
||
|
Calendar temp = (Calendar) calendar.clone(); // clone for safety
|
||
|
temp.setTimeZone(getTimeZone());
|
||
|
temp.setTimeInMillis(System.currentTimeMillis());
|
||
|
return temp;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the timezone ID. If this has not been set, uses default for territory.
|
||
|
*
|
||
|
* @param timezone a valid TZID (see UTS#35).
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setTimeZone(TimeZone timezone) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
this.timezone = (TimeZone) timezone.clone(); // clone for safety;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the timezone. It was either explicitly set, or is
|
||
|
* heuristically computed from other settings.
|
||
|
*
|
||
|
* @return timezone, either implicitly or explicitly set
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public TimeZone getTimeZone() {
|
||
|
if (timezone == null) {
|
||
|
return guessTimeZone();
|
||
|
}
|
||
|
return timezone.cloneAsThawed(); // clone for safety
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a copy of the collator according to the settings.
|
||
|
*
|
||
|
* @return collator explicit or implicit.
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public Collator getCollator() {
|
||
|
if (collator == null) {
|
||
|
return guessCollator();
|
||
|
}
|
||
|
try {
|
||
|
return (Collator) collator.clone(); // clone for safety
|
||
|
} catch (CloneNotSupportedException e) {
|
||
|
throw new ICUCloneNotSupportedException("Error in cloning collator", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Explicitly set the collator for this object.
|
||
|
* @param collator The collator object to be passed.
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setCollator(Collator collator) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
try {
|
||
|
this.collator = (Collator) collator.clone(); // clone for safety
|
||
|
} catch (CloneNotSupportedException e) {
|
||
|
throw new ICUCloneNotSupportedException("Error in cloning collator", e);
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a copy of the break iterator for the specified type according to the
|
||
|
* settings.
|
||
|
*
|
||
|
* @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE
|
||
|
* @return break iterator explicit or implicit
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public BreakIterator getBreakIterator(int type) {
|
||
|
if (type < BI_CHARACTER || type >= BI_LIMIT) {
|
||
|
throw new IllegalArgumentException("Illegal break iterator type");
|
||
|
}
|
||
|
if (breakIterators == null || breakIterators[type] == null) {
|
||
|
return guessBreakIterator(type);
|
||
|
}
|
||
|
return (BreakIterator) breakIterators[type].clone(); // clone for safety
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Explicitly set the break iterator for this object.
|
||
|
*
|
||
|
* @param type break type - BI_CHARACTER or BI_WORD, BI_LINE, BI_SENTENCE, BI_TITLE
|
||
|
* @param iterator a break iterator
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setBreakIterator(int type, BreakIterator iterator) {
|
||
|
if (type < BI_CHARACTER || type >= BI_LIMIT) {
|
||
|
throw new IllegalArgumentException("Illegal break iterator type");
|
||
|
}
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
if (breakIterators == null)
|
||
|
breakIterators = new BreakIterator[BI_LIMIT];
|
||
|
breakIterators[type] = (BreakIterator) iterator.clone(); // clone for safety
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the display name for an ID: language, script, territory, currency, timezone...
|
||
|
* Uses the language priority list to do so.
|
||
|
*
|
||
|
* @param id language code, script code, ...
|
||
|
* @param type specifies the type of the ID: ID_LANGUAGE, etc.
|
||
|
* @return the display name
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public String getDisplayName(String id, int type) {
|
||
|
String result = id;
|
||
|
for (ULocale locale : getLocales()) {
|
||
|
if (!isAvailableLocale(locale, TYPE_GENERIC)) {
|
||
|
continue;
|
||
|
}
|
||
|
switch (type) {
|
||
|
case ID_LOCALE:
|
||
|
result = ULocale.getDisplayName(id, locale);
|
||
|
break;
|
||
|
case ID_LANGUAGE:
|
||
|
result = ULocale.getDisplayLanguage(id, locale);
|
||
|
break;
|
||
|
case ID_SCRIPT:
|
||
|
result = ULocale.getDisplayScript("und-" + id, locale);
|
||
|
break;
|
||
|
case ID_TERRITORY:
|
||
|
result = ULocale.getDisplayCountry("und-" + id, locale);
|
||
|
break;
|
||
|
case ID_VARIANT:
|
||
|
// TODO fix variant parsing
|
||
|
result = ULocale.getDisplayVariant("und-QQ-" + id, locale);
|
||
|
break;
|
||
|
case ID_KEYWORD:
|
||
|
result = ULocale.getDisplayKeyword(id, locale);
|
||
|
break;
|
||
|
case ID_KEYWORD_VALUE:
|
||
|
String[] parts = new String[2];
|
||
|
Utility.split(id,'=',parts);
|
||
|
result = ULocale.getDisplayKeywordValue("und@"+id, parts[0], locale);
|
||
|
// TODO fix to tell when successful
|
||
|
if (result.equals(parts[1])) {
|
||
|
continue;
|
||
|
}
|
||
|
break;
|
||
|
case ID_CURRENCY_SYMBOL:
|
||
|
case ID_CURRENCY:
|
||
|
Currency temp = new Currency(id);
|
||
|
result =temp.getName(locale, type==ID_CURRENCY
|
||
|
? Currency.LONG_NAME
|
||
|
: Currency.SYMBOL_NAME, new boolean[1]);
|
||
|
// TODO: have method that doesn't take parameter. Add
|
||
|
// function to determine whether string is choice
|
||
|
// format.
|
||
|
// TODO: have method that doesn't require us
|
||
|
// to create a currency
|
||
|
break;
|
||
|
case ID_TIMEZONE:
|
||
|
SimpleDateFormat dtf = new SimpleDateFormat("vvvv",locale);
|
||
|
dtf.setTimeZone(TimeZone.getFrozenTimeZone(id));
|
||
|
result = dtf.format(new Date());
|
||
|
// TODO, have method that doesn't require us to create a timezone
|
||
|
// fix other hacks
|
||
|
// hack for couldn't match
|
||
|
|
||
|
boolean isBadStr = false;
|
||
|
// Matcher badTimeZone = Pattern.compile("[A-Z]{2}|.*\\s\\([A-Z]{2}\\)").matcher("");
|
||
|
// badtzstr = badTimeZone.reset(result).matches();
|
||
|
String teststr = result;
|
||
|
int sidx = result.indexOf('(');
|
||
|
int eidx = result.indexOf(')');
|
||
|
if (sidx != -1 && eidx != -1 && (eidx - sidx) == 3) {
|
||
|
teststr = result.substring(sidx+1, eidx);
|
||
|
}
|
||
|
if (teststr.length() == 2) {
|
||
|
isBadStr = true;
|
||
|
for (int i = 0; i < 2; i++) {
|
||
|
char c = teststr.charAt(i);
|
||
|
if (c < 'A' || 'Z' < c) {
|
||
|
isBadStr = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (isBadStr) {
|
||
|
continue;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
throw new IllegalArgumentException("Unknown type: " + type);
|
||
|
}
|
||
|
|
||
|
// TODO need better way of seeing if we fell back to root!!
|
||
|
// This will not work at all for lots of stuff
|
||
|
if (!id.equals(result)) {
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set an explicit date format. Overrides the locale priority list for
|
||
|
* a particular combination of dateStyle and timeStyle. DF_NONE should
|
||
|
* be used if for the style, where only the date or time format individually
|
||
|
* is being set.
|
||
|
*
|
||
|
* @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
|
||
|
* @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
|
||
|
* @param format The date format
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setDateFormat(int dateStyle, int timeStyle, DateFormat format) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
if (dateFormats == null) {
|
||
|
dateFormats = new DateFormat[DF_LIMIT][DF_LIMIT];
|
||
|
}
|
||
|
dateFormats[dateStyle][timeStyle] = (DateFormat) format.clone(); // for safety
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets a date format according to the current settings. If there
|
||
|
* is an explicit (non-null) date/time format set, a copy of that
|
||
|
* is returned. Otherwise, the language priority list is used.
|
||
|
* DF_NONE should be used for the style, where only the date or
|
||
|
* time format individually is being gotten.
|
||
|
*
|
||
|
* @param dateStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
|
||
|
* @param timeStyle DF_FULL, DF_LONG, DF_MEDIUM, DF_SHORT or DF_NONE
|
||
|
* @return a DateFormat, according to the above description
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public DateFormat getDateFormat(int dateStyle, int timeStyle) {
|
||
|
if (dateStyle == DF_NONE && timeStyle == DF_NONE
|
||
|
|| dateStyle < 0 || dateStyle >= DF_LIMIT
|
||
|
|| timeStyle < 0 || timeStyle >= DF_LIMIT) {
|
||
|
throw new IllegalArgumentException("Illegal date format style arguments");
|
||
|
}
|
||
|
DateFormat result = null;
|
||
|
if (dateFormats != null) {
|
||
|
result = dateFormats[dateStyle][timeStyle];
|
||
|
}
|
||
|
if (result != null) {
|
||
|
result = (DateFormat) result.clone(); // clone for safety
|
||
|
// Not sure overriding configuration is what we really want...
|
||
|
result.setTimeZone(getTimeZone());
|
||
|
} else {
|
||
|
result = guessDateFormat(dateStyle, timeStyle);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets a number format according to the current settings. If
|
||
|
* there is an explicit (non-null) number format set, a copy of
|
||
|
* that is returned. Otherwise, the language priority list is
|
||
|
* used.
|
||
|
*
|
||
|
* @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public NumberFormat getNumberFormat(int style) {
|
||
|
if (style < 0 || style >= NF_LIMIT) {
|
||
|
throw new IllegalArgumentException("Illegal number format type");
|
||
|
}
|
||
|
NumberFormat result = null;
|
||
|
if (numberFormats != null) {
|
||
|
result = numberFormats[style];
|
||
|
}
|
||
|
if (result != null) {
|
||
|
result = (NumberFormat) result.clone(); // clone for safety (later optimize)
|
||
|
} else {
|
||
|
result = guessNumberFormat(style);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets a number format explicitly. Overrides the general locale settings.
|
||
|
*
|
||
|
* @param style NF_NUMBER, NF_CURRENCY, NF_PERCENT, NF_SCIENTIFIC, NF_INTEGER
|
||
|
* @param format The number format
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences setNumberFormat(int style, NumberFormat format) {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
if (numberFormats == null) {
|
||
|
numberFormats = new NumberFormat[NF_LIMIT];
|
||
|
}
|
||
|
numberFormats[style] = (NumberFormat) format.clone(); // for safety
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Restore the object to the initial state.
|
||
|
*
|
||
|
* @return this, for chaining
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
public GlobalizationPreferences reset() {
|
||
|
if (isFrozen()) {
|
||
|
throw new UnsupportedOperationException("Attempt to modify immutable object");
|
||
|
}
|
||
|
locales = null;
|
||
|
territory = null;
|
||
|
calendar = null;
|
||
|
collator = null;
|
||
|
breakIterators = null;
|
||
|
timezone = null;
|
||
|
currency = null;
|
||
|
dateFormats = null;
|
||
|
numberFormats = null;
|
||
|
implicitLocales = null;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process a language/locale priority list specified via <code>setLocales</code>.
|
||
|
* The input locale list may be expanded or re-ordered to represent the prioritized
|
||
|
* language/locale order actually used by this object by the algorithm explained
|
||
|
* below.
|
||
|
* <br>
|
||
|
* <br>
|
||
|
* <b>Step 1</b>: Move later occurrence of more specific locale before earlier
|
||
|
* occurrence of less specific locale.
|
||
|
* <br>
|
||
|
* Before: en, fr_FR, en_US, en_GB
|
||
|
* <br>
|
||
|
* After: en_US, en_GB, en, fr_FR
|
||
|
* <br>
|
||
|
* <br>
|
||
|
* <b>Step 2</b>: Append a fallback locale to each locale.
|
||
|
* <br>
|
||
|
* Before: en_US, en_GB, en, fr_FR
|
||
|
* <br>
|
||
|
* After: en_US, en, en_GB, en, en, fr_FR, fr
|
||
|
* <br>
|
||
|
* <br>
|
||
|
* <b>Step 3</b>: Remove earlier occurrence of duplicated locale entries.
|
||
|
* <br>
|
||
|
* Before: en_US, en, en_GB, en, en, fr_FR, fr
|
||
|
* <br>
|
||
|
* After: en_US, en_GB, en, fr_FR, fr
|
||
|
* <br>
|
||
|
* <br>
|
||
|
* The final locale list is used to produce a default value for the appropriate territory,
|
||
|
* currency, timezone, etc. The list also represents the lookup order used in
|
||
|
* <code>getResourceBundle</code> for this object. A subclass may override this method
|
||
|
* to customize the algorithm used for populating the locale list.
|
||
|
*
|
||
|
* @param inputLocales The list of input locales
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected List<ULocale> processLocales(List<ULocale> inputLocales) {
|
||
|
List<ULocale> result = new ArrayList<ULocale>();
|
||
|
/*
|
||
|
* Step 1: Relocate later occurrence of more specific locale
|
||
|
* before earlier occurrence of less specific locale.
|
||
|
*
|
||
|
* Example:
|
||
|
* Before - en_US, fr_FR, zh, en_US_Boston, zh_TW, zh_Hant, fr_CA
|
||
|
* After - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA
|
||
|
*/
|
||
|
for (int i = 0; i < inputLocales.size(); i++) {
|
||
|
ULocale uloc = inputLocales.get(i);
|
||
|
|
||
|
String language = uloc.getLanguage();
|
||
|
String script = uloc.getScript();
|
||
|
String country = uloc.getCountry();
|
||
|
String variant = uloc.getVariant();
|
||
|
|
||
|
boolean bInserted = false;
|
||
|
for (int j = 0; j < result.size(); j++) {
|
||
|
// Check if this locale is more specific
|
||
|
// than existing locale entries already inserted
|
||
|
// in the destination list
|
||
|
ULocale u = result.get(j);
|
||
|
if (!u.getLanguage().equals(language)) {
|
||
|
continue;
|
||
|
}
|
||
|
String s = u.getScript();
|
||
|
String c = u.getCountry();
|
||
|
String v = u.getVariant();
|
||
|
if (!s.equals(script)) {
|
||
|
if (s.length() == 0 && c.length() == 0 && v.length() == 0) {
|
||
|
result.add(j, uloc);
|
||
|
bInserted = true;
|
||
|
break;
|
||
|
} else if (s.length() == 0 && c.equals(country)) {
|
||
|
// We want to see zh_Hant_HK before zh_HK
|
||
|
result.add(j, uloc);
|
||
|
bInserted = true;
|
||
|
break;
|
||
|
} else if (script.length() == 0 && country.length() > 0 && c.length() == 0) {
|
||
|
// We want to see zh_HK before zh_Hant
|
||
|
result.add(j, uloc);
|
||
|
bInserted = true;
|
||
|
break;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
if (!c.equals(country)) {
|
||
|
if (c.length() == 0 && v.length() == 0) {
|
||
|
result.add(j, uloc);
|
||
|
bInserted = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!v.equals(variant) && v.length() == 0) {
|
||
|
result.add(j, uloc);
|
||
|
bInserted = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!bInserted) {
|
||
|
// Add this locale at the end of the list
|
||
|
result.add(uloc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO: Locale aliases might be resolved here
|
||
|
// For example, zh_Hant_TW = zh_TW
|
||
|
|
||
|
/*
|
||
|
* Step 2: Append fallback locales for each entry
|
||
|
*
|
||
|
* Example:
|
||
|
* Before - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA
|
||
|
* After - en_US_Boston, en_US, en, en_US, en, fr_FR, fr,
|
||
|
* zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr
|
||
|
*/
|
||
|
int index = 0;
|
||
|
while (index < result.size()) {
|
||
|
ULocale uloc = result.get(index);
|
||
|
while ((uloc = uloc.getFallback()) != null) {
|
||
|
if (uloc.getLanguage().length() == 0) {
|
||
|
break;
|
||
|
}
|
||
|
index++;
|
||
|
result.add(index, uloc);
|
||
|
}
|
||
|
index++;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Step 3: Remove earlier occurrence of duplicated locales
|
||
|
*
|
||
|
* Example:
|
||
|
* Before - en_US_Boston, en_US, en, en_US, en, fr_FR, fr,
|
||
|
* zh_TW, zn, zh_Hant, zh, zh, fr_CA, fr
|
||
|
* After - en_US_Boston, en_US, en, fr_FR, zh_TW, zh_Hant,
|
||
|
* zh, fr_CA, fr
|
||
|
*/
|
||
|
index = 0;
|
||
|
while (index < result.size() - 1) {
|
||
|
ULocale uloc = result.get(index);
|
||
|
boolean bRemoved = false;
|
||
|
for (int i = index + 1; i < result.size(); i++) {
|
||
|
if (uloc.equals(result.get(i))) {
|
||
|
// Remove earlier one
|
||
|
result.remove(index);
|
||
|
bRemoved = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!bRemoved) {
|
||
|
index++;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics.
|
||
|
* <b>It MUST return a 'safe' value,
|
||
|
* one whose modification will not affect this object.</b>
|
||
|
*
|
||
|
* @param dateStyle
|
||
|
* @param timeStyle
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected DateFormat guessDateFormat(int dateStyle, int timeStyle) {
|
||
|
DateFormat result;
|
||
|
ULocale dfLocale = getAvailableLocale(TYPE_DATEFORMAT);
|
||
|
if (dfLocale == null) {
|
||
|
dfLocale = ULocale.ROOT;
|
||
|
}
|
||
|
if (timeStyle == DF_NONE) {
|
||
|
result = DateFormat.getDateInstance(getCalendar(), dateStyle, dfLocale);
|
||
|
} else if (dateStyle == DF_NONE) {
|
||
|
result = DateFormat.getTimeInstance(getCalendar(), timeStyle, dfLocale);
|
||
|
} else {
|
||
|
result = DateFormat.getDateTimeInstance(getCalendar(), dateStyle, timeStyle, dfLocale);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics.
|
||
|
* <b>It MUST return a 'safe' value,
|
||
|
* one whose modification will not affect this object.</b>
|
||
|
*
|
||
|
* @param style
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected NumberFormat guessNumberFormat(int style) {
|
||
|
NumberFormat result;
|
||
|
ULocale nfLocale = getAvailableLocale(TYPE_NUMBERFORMAT);
|
||
|
if (nfLocale == null) {
|
||
|
nfLocale = ULocale.ROOT;
|
||
|
}
|
||
|
switch (style) {
|
||
|
case NF_NUMBER:
|
||
|
result = NumberFormat.getInstance(nfLocale);
|
||
|
break;
|
||
|
case NF_SCIENTIFIC:
|
||
|
result = NumberFormat.getScientificInstance(nfLocale);
|
||
|
break;
|
||
|
case NF_INTEGER:
|
||
|
result = NumberFormat.getIntegerInstance(nfLocale);
|
||
|
break;
|
||
|
case NF_PERCENT:
|
||
|
result = NumberFormat.getPercentInstance(nfLocale);
|
||
|
break;
|
||
|
case NF_CURRENCY:
|
||
|
result = NumberFormat.getCurrencyInstance(nfLocale);
|
||
|
result.setCurrency(getCurrency());
|
||
|
break;
|
||
|
default:
|
||
|
throw new IllegalArgumentException("Unknown number format style");
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics.
|
||
|
*
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected String guessTerritory() {
|
||
|
String result;
|
||
|
// pass through locales to see if there is a territory.
|
||
|
for (ULocale locale : getLocales()) {
|
||
|
result = locale.getCountry();
|
||
|
if (result.length() != 0) {
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
// if not, guess from the first language tag, or maybe from
|
||
|
// intersection of languages, eg nl + fr => BE
|
||
|
// TODO: fix using real data
|
||
|
// for now, just use fixed values
|
||
|
ULocale firstLocale = getLocale(0);
|
||
|
String language = firstLocale.getLanguage();
|
||
|
String script = firstLocale.getScript();
|
||
|
result = null;
|
||
|
if (script.length() != 0) {
|
||
|
result = language_territory_hack_map.get(language + "_" + script);
|
||
|
}
|
||
|
if (result == null) {
|
||
|
result = language_territory_hack_map.get(language);
|
||
|
}
|
||
|
if (result == null) {
|
||
|
result = "US"; // need *some* default
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics
|
||
|
*
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected Currency guessCurrency() {
|
||
|
return Currency.getInstance(new ULocale("und-" + getTerritory()));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics
|
||
|
* <b>It MUST return a 'safe' value,
|
||
|
* one whose modification will not affect this object.</b>
|
||
|
*
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected List<ULocale> guessLocales() {
|
||
|
if (implicitLocales == null) {
|
||
|
List<ULocale> result = new ArrayList<ULocale>(1);
|
||
|
result.add(ULocale.getDefault());
|
||
|
implicitLocales = processLocales(result);
|
||
|
}
|
||
|
return implicitLocales;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics.
|
||
|
* <b>It MUST return a 'safe' value,
|
||
|
* one whose modification will not affect this object.</b>
|
||
|
*
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected Collator guessCollator() {
|
||
|
ULocale collLocale = getAvailableLocale(TYPE_COLLATOR);
|
||
|
if (collLocale == null) {
|
||
|
collLocale = ULocale.ROOT;
|
||
|
}
|
||
|
return Collator.getInstance(collLocale);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics.
|
||
|
* <b>It MUST return a 'safe' value,
|
||
|
* one whose modification will not affect this object.</b>
|
||
|
*
|
||
|
* @param type
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected BreakIterator guessBreakIterator(int type) {
|
||
|
BreakIterator bitr = null;
|
||
|
ULocale brkLocale = getAvailableLocale(TYPE_BREAKITERATOR);
|
||
|
if (brkLocale == null) {
|
||
|
brkLocale = ULocale.ROOT;
|
||
|
}
|
||
|
switch (type) {
|
||
|
case BI_CHARACTER:
|
||
|
bitr = BreakIterator.getCharacterInstance(brkLocale);
|
||
|
break;
|
||
|
case BI_TITLE:
|
||
|
bitr = BreakIterator.getTitleInstance(brkLocale);
|
||
|
break;
|
||
|
case BI_WORD:
|
||
|
bitr = BreakIterator.getWordInstance(brkLocale);
|
||
|
break;
|
||
|
case BI_LINE:
|
||
|
bitr = BreakIterator.getLineInstance(brkLocale);
|
||
|
break;
|
||
|
case BI_SENTENCE:
|
||
|
bitr = BreakIterator.getSentenceInstance(brkLocale);
|
||
|
break;
|
||
|
default:
|
||
|
throw new IllegalArgumentException("Unknown break iterator type");
|
||
|
}
|
||
|
return bitr;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics.
|
||
|
* <b>It MUST return a 'safe' value,
|
||
|
* one whose modification will not affect this object.</b>
|
||
|
*
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected TimeZone guessTimeZone() {
|
||
|
// TODO fix using real data
|
||
|
// for single-zone countries, pick that zone
|
||
|
// for others, pick the most populous zone
|
||
|
// for now, just use fixed value
|
||
|
// NOTE: in a few cases can do better by looking at language.
|
||
|
// Eg haw+US should go to Pacific/Honolulu
|
||
|
// fr+CA should go to America/Montreal
|
||
|
String timezoneString = territory_tzid_hack_map.get(getTerritory());
|
||
|
if (timezoneString == null) {
|
||
|
String[] attempt = TimeZone.getAvailableIDs(getTerritory());
|
||
|
if (attempt.length == 0) {
|
||
|
timezoneString = "Etc/GMT"; // gotta do something
|
||
|
} else {
|
||
|
int i;
|
||
|
// this all needs to be fixed to use real data. But for now, do slightly better by skipping cruft
|
||
|
for (i = 0; i < attempt.length; ++i) {
|
||
|
if (attempt[i].indexOf("/") >= 0) break;
|
||
|
}
|
||
|
if (i > attempt.length) i = 0;
|
||
|
timezoneString = attempt[i];
|
||
|
}
|
||
|
}
|
||
|
return TimeZone.getTimeZone(timezoneString);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This function can be overridden by subclasses to use different heuristics.
|
||
|
* <b>It MUST return a 'safe' value,
|
||
|
* one whose modification will not affect this object.</b>
|
||
|
*
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
protected Calendar guessCalendar() {
|
||
|
ULocale calLocale = getAvailableLocale(TYPE_CALENDAR);
|
||
|
if (calLocale == null) {
|
||
|
calLocale = ULocale.US;
|
||
|
}
|
||
|
return Calendar.getInstance(getTimeZone(), calLocale);
|
||
|
}
|
||
|
|
||
|
// PRIVATES
|
||
|
|
||
|
private List<ULocale> locales;
|
||
|
private String territory;
|
||
|
private Currency currency;
|
||
|
private TimeZone timezone;
|
||
|
private Calendar calendar;
|
||
|
private Collator collator;
|
||
|
private BreakIterator[] breakIterators;
|
||
|
private DateFormat[][] dateFormats;
|
||
|
private NumberFormat[] numberFormats;
|
||
|
private List<ULocale> implicitLocales;
|
||
|
|
||
|
{
|
||
|
reset();
|
||
|
}
|
||
|
|
||
|
|
||
|
private ULocale getAvailableLocale(int type) {
|
||
|
List<ULocale> locs = getLocales();
|
||
|
ULocale result = null;
|
||
|
for (int i = 0; i < locs.size(); i++) {
|
||
|
ULocale l = locs.get(i);
|
||
|
if (isAvailableLocale(l, type)) {
|
||
|
result = l;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private boolean isAvailableLocale(ULocale loc, int type) {
|
||
|
BitSet bits = available_locales.get(loc);
|
||
|
if (bits != null && bits.get(type)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Available locales for service types
|
||
|
*/
|
||
|
private static final HashMap<ULocale, BitSet> available_locales = new HashMap<ULocale, BitSet>();
|
||
|
private static final int
|
||
|
TYPE_GENERIC = 0,
|
||
|
TYPE_CALENDAR = 1,
|
||
|
TYPE_DATEFORMAT= 2,
|
||
|
TYPE_NUMBERFORMAT = 3,
|
||
|
TYPE_COLLATOR = 4,
|
||
|
TYPE_BREAKITERATOR = 5,
|
||
|
TYPE_LIMIT = TYPE_BREAKITERATOR + 1;
|
||
|
|
||
|
static {
|
||
|
BitSet bits;
|
||
|
ULocale[] allLocales = ULocale.getAvailableLocales();
|
||
|
for (int i = 0; i < allLocales.length; i++) {
|
||
|
bits = new BitSet(TYPE_LIMIT);
|
||
|
available_locales.put(allLocales[i], bits);
|
||
|
bits.set(TYPE_GENERIC);
|
||
|
}
|
||
|
|
||
|
ULocale[] calLocales = Calendar.getAvailableULocales();
|
||
|
for (int i = 0; i < calLocales.length; i++) {
|
||
|
bits = available_locales.get(calLocales[i]);
|
||
|
if (bits == null) {
|
||
|
bits = new BitSet(TYPE_LIMIT);
|
||
|
available_locales.put(allLocales[i], bits);
|
||
|
}
|
||
|
bits.set(TYPE_CALENDAR);
|
||
|
}
|
||
|
|
||
|
ULocale[] dateLocales = DateFormat.getAvailableULocales();
|
||
|
for (int i = 0; i < dateLocales.length; i++) {
|
||
|
bits = available_locales.get(dateLocales[i]);
|
||
|
if (bits == null) {
|
||
|
bits = new BitSet(TYPE_LIMIT);
|
||
|
available_locales.put(allLocales[i], bits);
|
||
|
}
|
||
|
bits.set(TYPE_DATEFORMAT);
|
||
|
}
|
||
|
|
||
|
ULocale[] numLocales = NumberFormat.getAvailableULocales();
|
||
|
for (int i = 0; i < numLocales.length; i++) {
|
||
|
bits = available_locales.get(numLocales[i]);
|
||
|
if (bits == null) {
|
||
|
bits = new BitSet(TYPE_LIMIT);
|
||
|
available_locales.put(allLocales[i], bits);
|
||
|
}
|
||
|
bits.set(TYPE_NUMBERFORMAT);
|
||
|
}
|
||
|
|
||
|
ULocale[] collLocales = Collator.getAvailableULocales();
|
||
|
for (int i = 0; i < collLocales.length; i++) {
|
||
|
bits = available_locales.get(collLocales[i]);
|
||
|
if (bits == null) {
|
||
|
bits = new BitSet(TYPE_LIMIT);
|
||
|
available_locales.put(allLocales[i], bits);
|
||
|
}
|
||
|
bits.set(TYPE_COLLATOR);
|
||
|
}
|
||
|
|
||
|
ULocale[] brkLocales = BreakIterator.getAvailableULocales();
|
||
|
for (int i = 0; i < brkLocales.length; i++) {
|
||
|
bits = available_locales.get(brkLocales[i]);
|
||
|
bits.set(TYPE_BREAKITERATOR);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** WARNING: All of this data is temporary, until we start importing from CLDR!!!
|
||
|
*
|
||
|
*/
|
||
|
private static final Map<String, String> language_territory_hack_map = new HashMap<String, String>();
|
||
|
private static final String[][] language_territory_hack = {
|
||
|
{"af", "ZA"},
|
||
|
{"am", "ET"},
|
||
|
{"ar", "SA"},
|
||
|
{"as", "IN"},
|
||
|
{"ay", "PE"},
|
||
|
{"az", "AZ"},
|
||
|
{"bal", "PK"},
|
||
|
{"be", "BY"},
|
||
|
{"bg", "BG"},
|
||
|
{"bn", "IN"},
|
||
|
{"bs", "BA"},
|
||
|
{"ca", "ES"},
|
||
|
{"ch", "MP"},
|
||
|
{"cpe", "SL"},
|
||
|
{"cs", "CZ"},
|
||
|
{"cy", "GB"},
|
||
|
{"da", "DK"},
|
||
|
{"de", "DE"},
|
||
|
{"dv", "MV"},
|
||
|
{"dz", "BT"},
|
||
|
{"el", "GR"},
|
||
|
{"en", "US"},
|
||
|
{"es", "ES"},
|
||
|
{"et", "EE"},
|
||
|
{"eu", "ES"},
|
||
|
{"fa", "IR"},
|
||
|
{"fi", "FI"},
|
||
|
{"fil", "PH"},
|
||
|
{"fj", "FJ"},
|
||
|
{"fo", "FO"},
|
||
|
{"fr", "FR"},
|
||
|
{"ga", "IE"},
|
||
|
{"gd", "GB"},
|
||
|
{"gl", "ES"},
|
||
|
{"gn", "PY"},
|
||
|
{"gu", "IN"},
|
||
|
{"gv", "GB"},
|
||
|
{"ha", "NG"},
|
||
|
{"he", "IL"},
|
||
|
{"hi", "IN"},
|
||
|
{"ho", "PG"},
|
||
|
{"hr", "HR"},
|
||
|
{"ht", "HT"},
|
||
|
{"hu", "HU"},
|
||
|
{"hy", "AM"},
|
||
|
{"id", "ID"},
|
||
|
{"is", "IS"},
|
||
|
{"it", "IT"},
|
||
|
{"ja", "JP"},
|
||
|
{"ka", "GE"},
|
||
|
{"kk", "KZ"},
|
||
|
{"kl", "GL"},
|
||
|
{"km", "KH"},
|
||
|
{"kn", "IN"},
|
||
|
{"ko", "KR"},
|
||
|
{"kok", "IN"},
|
||
|
{"ks", "IN"},
|
||
|
{"ku", "TR"},
|
||
|
{"ky", "KG"},
|
||
|
{"la", "VA"},
|
||
|
{"lb", "LU"},
|
||
|
{"ln", "CG"},
|
||
|
{"lo", "LA"},
|
||
|
{"lt", "LT"},
|
||
|
{"lv", "LV"},
|
||
|
{"mai", "IN"},
|
||
|
{"men", "GN"},
|
||
|
{"mg", "MG"},
|
||
|
{"mh", "MH"},
|
||
|
{"mk", "MK"},
|
||
|
{"ml", "IN"},
|
||
|
{"mn", "MN"},
|
||
|
{"mni", "IN"},
|
||
|
{"mo", "MD"},
|
||
|
{"mr", "IN"},
|
||
|
{"ms", "MY"},
|
||
|
{"mt", "MT"},
|
||
|
{"my", "MM"},
|
||
|
{"na", "NR"},
|
||
|
{"nb", "NO"},
|
||
|
{"nd", "ZA"},
|
||
|
{"ne", "NP"},
|
||
|
{"niu", "NU"},
|
||
|
{"nl", "NL"},
|
||
|
{"nn", "NO"},
|
||
|
{"no", "NO"},
|
||
|
{"nr", "ZA"},
|
||
|
{"nso", "ZA"},
|
||
|
{"ny", "MW"},
|
||
|
{"om", "KE"},
|
||
|
{"or", "IN"},
|
||
|
{"pa", "IN"},
|
||
|
{"pau", "PW"},
|
||
|
{"pl", "PL"},
|
||
|
{"ps", "PK"},
|
||
|
{"pt", "BR"},
|
||
|
{"qu", "PE"},
|
||
|
{"rn", "BI"},
|
||
|
{"ro", "RO"},
|
||
|
{"ru", "RU"},
|
||
|
{"rw", "RW"},
|
||
|
{"sd", "IN"},
|
||
|
{"sg", "CF"},
|
||
|
{"si", "LK"},
|
||
|
{"sk", "SK"},
|
||
|
{"sl", "SI"},
|
||
|
{"sm", "WS"},
|
||
|
{"so", "DJ"},
|
||
|
{"sq", "CS"},
|
||
|
{"sr", "CS"},
|
||
|
{"ss", "ZA"},
|
||
|
{"st", "ZA"},
|
||
|
{"sv", "SE"},
|
||
|
{"sw", "KE"},
|
||
|
{"ta", "IN"},
|
||
|
{"te", "IN"},
|
||
|
{"tem", "SL"},
|
||
|
{"tet", "TL"},
|
||
|
{"th", "TH"},
|
||
|
{"ti", "ET"},
|
||
|
{"tg", "TJ"},
|
||
|
{"tk", "TM"},
|
||
|
{"tkl", "TK"},
|
||
|
{"tvl", "TV"},
|
||
|
{"tl", "PH"},
|
||
|
{"tn", "ZA"},
|
||
|
{"to", "TO"},
|
||
|
{"tpi", "PG"},
|
||
|
{"tr", "TR"},
|
||
|
{"ts", "ZA"},
|
||
|
{"uk", "UA"},
|
||
|
{"ur", "IN"},
|
||
|
{"uz", "UZ"},
|
||
|
{"ve", "ZA"},
|
||
|
{"vi", "VN"},
|
||
|
{"wo", "SN"},
|
||
|
{"xh", "ZA"},
|
||
|
{"zh", "CN"},
|
||
|
{"zh_Hant", "TW"},
|
||
|
{"zu", "ZA"},
|
||
|
{"aa", "ET"},
|
||
|
{"byn", "ER"},
|
||
|
{"eo", "DE"},
|
||
|
{"gez", "ET"},
|
||
|
{"haw", "US"},
|
||
|
{"iu", "CA"},
|
||
|
{"kw", "GB"},
|
||
|
{"sa", "IN"},
|
||
|
{"sh", "HR"},
|
||
|
{"sid", "ET"},
|
||
|
{"syr", "SY"},
|
||
|
{"tig", "ER"},
|
||
|
{"tt", "RU"},
|
||
|
{"wal", "ET"}, };
|
||
|
static {
|
||
|
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]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static final Map<String, String> territory_tzid_hack_map = new HashMap<String, String>();
|
||
|
static final String[][] territory_tzid_hack = {
|
||
|
{"AQ", "Antarctica/McMurdo"},
|
||
|
{"AR", "America/Buenos_Aires"},
|
||
|
{"AU", "Australia/Sydney"},
|
||
|
{"BR", "America/Sao_Paulo"},
|
||
|
{"CA", "America/Toronto"},
|
||
|
{"CD", "Africa/Kinshasa"},
|
||
|
{"CL", "America/Santiago"},
|
||
|
{"CN", "Asia/Shanghai"},
|
||
|
{"EC", "America/Guayaquil"},
|
||
|
{"ES", "Europe/Madrid"},
|
||
|
{"GB", "Europe/London"},
|
||
|
{"GL", "America/Godthab"},
|
||
|
{"ID", "Asia/Jakarta"},
|
||
|
{"ML", "Africa/Bamako"},
|
||
|
{"MX", "America/Mexico_City"},
|
||
|
{"MY", "Asia/Kuala_Lumpur"},
|
||
|
{"NZ", "Pacific/Auckland"},
|
||
|
{"PT", "Europe/Lisbon"},
|
||
|
{"RU", "Europe/Moscow"},
|
||
|
{"UA", "Europe/Kiev"},
|
||
|
{"US", "America/New_York"},
|
||
|
{"UZ", "Asia/Tashkent"},
|
||
|
{"PF", "Pacific/Tahiti"},
|
||
|
{"FM", "Pacific/Kosrae"},
|
||
|
{"KI", "Pacific/Tarawa"},
|
||
|
{"KZ", "Asia/Almaty"},
|
||
|
{"MH", "Pacific/Majuro"},
|
||
|
{"MN", "Asia/Ulaanbaatar"},
|
||
|
{"SJ", "Arctic/Longyearbyen"},
|
||
|
{"UM", "Pacific/Midway"},
|
||
|
};
|
||
|
static {
|
||
|
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]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Freezable implementation
|
||
|
|
||
|
private volatile boolean frozen;
|
||
|
|
||
|
/**
|
||
|
* @draft ICU 3.6
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
@Override
|
||
|
public boolean isFrozen() {
|
||
|
return frozen;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @draft ICU 4.4
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
@Override
|
||
|
public GlobalizationPreferences freeze() {
|
||
|
frozen = true;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @draft ICU 4.4
|
||
|
* @provisional This API might change or be removed in a future release.
|
||
|
*/
|
||
|
@Override
|
||
|
public GlobalizationPreferences cloneAsThawed() {
|
||
|
try {
|
||
|
GlobalizationPreferences result = (GlobalizationPreferences) clone();
|
||
|
result.frozen = false;
|
||
|
return result;
|
||
|
} catch (CloneNotSupportedException e) {
|
||
|
// will always work
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|