/* * @(#)Locale.java 1.79 04/05/10 * * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ /* * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved * * The original version of this source code and documentation * is copyrighted and owned by Taligent, Inc., a wholly-owned * subsidiary of IBM. These materials are provided under terms * of a License Agreement between Taligent and Sun. This technology * is protected by multiple US and International patents. * * This notice and attribution to Taligent may not be removed. * Taligent is a registered trademark of Taligent, Inc. * */ package java.util; import java.io.*; import java.security.AccessController; import java.text.MessageFormat; import sun.security.action.GetPropertyAction; import sun.text.resources.LocaleData; /** * * A Locale object represents a specific geographical, political, * or cultural region. An operation that requires a Locale to perform * its task is called locale-sensitive and uses the Locale * to tailor information for the user. For example, displaying a number * is a locale-sensitive operation--the number should be formatted * according to the customs/conventions of the user's native country, * region, or culture. * *

* Create a Locale object using the constructors in this class: *

*
 * Locale(String language)
 * Locale(String language, String country)
 * Locale(String language, String country, String variant)
 * 
*
* The language argument is a valid ISO Language Code. * These codes are the lower-case, two-letter codes as defined by ISO-639. * You can find a full list of these codes at a number of sites, such as: *
* http://www.loc.gov/standards/iso639-2/englangn.html * *

* The country argument is a valid ISO Country Code. These * codes are the upper-case, two-letter codes as defined by ISO-3166. * You can find a full list of these codes at a number of sites, such as: *
* http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html * *

* The variant argument is a vendor or browser-specific code. * For example, use WIN for Windows, MAC for Macintosh, and POSIX for POSIX. * Where there are two variants, separate them with an underscore, and * put the most important one first. For example, a Traditional Spanish collation * might construct a locale with parameters for language, country and variant as: * "es", "ES", "Traditional_WIN". * *

* Because a Locale object is just an identifier for a region, * no validity check is performed when you construct a Locale. * If you want to see whether particular resources are available for the * Locale you construct, you must query those resources. For * example, ask the NumberFormat for the locales it supports * using its getAvailableLocales method. *
Note: When you ask for a resource for a particular * locale, you get back the best available match, not necessarily * precisely what you asked for. For more information, look at * {@link ResourceBundle}. * *

* The Locale class provides a number of convenient constants * that you can use to create Locale objects for commonly used * locales. For example, the following creates a Locale object * for the United States: *

*
 * Locale.US
 * 
*
* *

* Once you've created a Locale you can query it for information about * itself. Use getCountry to get the ISO Country Code and * getLanguage to get the ISO Language Code. You can * use getDisplayCountry to get the * name of the country suitable for displaying to the user. Similarly, * you can use getDisplayLanguage to get the name of * the language suitable for displaying to the user. Interestingly, * the getDisplayXXX methods are themselves locale-sensitive * and have two versions: one that uses the default locale and one * that uses the locale specified as an argument. * *

* The Java 2 platform provides a number of classes that perform locale-sensitive * operations. For example, the NumberFormat class formats * numbers, currency, or percentages in a locale-sensitive manner. Classes * such as NumberFormat have a number of convenience methods * for creating a default object of that type. For example, the * NumberFormat class provides these three convenience methods * for creating a default NumberFormat object: *

*
 * NumberFormat.getInstance()
 * NumberFormat.getCurrencyInstance()
 * NumberFormat.getPercentInstance()
 * 
*
* These methods have two variants; one with an explicit locale * and one without; the latter using the default locale. *
*
 * NumberFormat.getInstance(myLocale)
 * NumberFormat.getCurrencyInstance(myLocale)
 * NumberFormat.getPercentInstance(myLocale)
 * 
*
* A Locale is the mechanism for identifying the kind of object * (NumberFormat) that you would like to get. The locale is * just a mechanism for identifying objects, * not a container for the objects themselves. * * @see ResourceBundle * @see java.text.Format * @see java.text.NumberFormat * @see java.text.Collator * @author Mark Davis * @since 1.1 */ public final class Locale implements Cloneable, Serializable { /** Useful constant for language. */ static public final Locale ENGLISH = new Locale("en","",""); /** Useful constant for language. */ static public final Locale FRENCH = new Locale("fr","",""); /** Useful constant for language. */ static public final Locale GERMAN = new Locale("de","",""); /** Useful constant for language. */ static public final Locale ITALIAN = new Locale("it","",""); /** Useful constant for language. */ static public final Locale JAPANESE = new Locale("ja","",""); /** Useful constant for language. */ static public final Locale KOREAN = new Locale("ko","",""); /** Useful constant for language. */ static public final Locale CHINESE = new Locale("zh","",""); /** Useful constant for language. */ static public final Locale SIMPLIFIED_CHINESE = new Locale("zh","CN",""); /** Useful constant for language. */ static public final Locale TRADITIONAL_CHINESE = new Locale("zh","TW",""); /** Useful constant for country. */ static public final Locale FRANCE = new Locale("fr","FR",""); /** Useful constant for country. */ static public final Locale GERMANY = new Locale("de","DE",""); /** Useful constant for country. */ static public final Locale ITALY = new Locale("it","IT",""); /** Useful constant for country. */ static public final Locale JAPAN = new Locale("ja","JP",""); /** Useful constant for country. */ static public final Locale KOREA = new Locale("ko","KR",""); /** Useful constant for country. */ static public final Locale CHINA = new Locale("zh","CN",""); /** Useful constant for country. */ static public final Locale PRC = new Locale("zh","CN",""); /** Useful constant for country. */ static public final Locale TAIWAN = new Locale("zh","TW",""); /** Useful constant for country. */ static public final Locale UK = new Locale("en","GB",""); /** Useful constant for country. */ static public final Locale US = new Locale("en","US",""); /** Useful constant for country. */ static public final Locale CANADA = new Locale("en","CA",""); /** Useful constant for country. */ static public final Locale CANADA_FRENCH = new Locale("fr","CA",""); /** serialization ID */ static final long serialVersionUID = 9149081749638150636L; /** * Construct a locale from language, country, variant. * NOTE: ISO 639 is not a stable standard; some of the language codes it defines * (specifically iw, ji, and in) have changed. This constructor accepts both the * old codes (iw, ji, and in) and the new codes (he, yi, and id), but all other * API on Locale will return only the OLD codes. * @param language lowercase two-letter ISO-639 code. * @param country uppercase two-letter ISO-3166 code. * @param variant vendor and browser specific code. See class description. * @exception NullPointerException thrown if any argument is null. */ public Locale(String language, String country, String variant) { this.language = convertOldISOCodes(language); this.country = toUpperCase(country).intern(); this.variant = variant.intern(); } /** * Construct a locale from language, country. * NOTE: ISO 639 is not a stable standard; some of the language codes it defines * (specifically iw, ji, and in) have changed. This constructor accepts both the * old codes (iw, ji, and in) and the new codes (he, yi, and id), but all other * API on Locale will return only the OLD codes. * @param language lowercase two-letter ISO-639 code. * @param country uppercase two-letter ISO-3166 code. * @exception NullPointerException thrown if either argument is null. */ public Locale(String language, String country) { this(language, country, ""); } /** * Construct a locale from a language code. * NOTE: ISO 639 is not a stable standard; some of the language codes it defines * (specifically iw, ji, and in) have changed. This constructor accepts both the * old codes (iw, ji, and in) and the new codes (he, yi, and id), but all other * API on Locale will return only the OLD codes. * @param language lowercase two-letter ISO-639 code. * @exception NullPointerException thrown if argument is null. * @since 1.4 */ public Locale(String language) { this(language, "", ""); } /** * Gets the current value of the default locale for this instance * of the Java Virtual Machine. *

* The Java Virtual Machine sets the default locale during startup * based on the host environment. It is used by many locale-sensitive * methods if no locale is explicitly specified. * It can be changed using the * {@link #setDefault(java.util.Locale) setDefault} method. * * @return the default locale for this instance of the Java Virtual Machine */ public static Locale getDefault() { // do not synchronize this method - see 4071298 // it's OK if more than one default locale happens to be created if (defaultLocale == null) { String language, region, country, variant; language = (String) AccessController.doPrivileged( new GetPropertyAction("user.language", "en")); // for compatibility, check for old user.region property region = (String) AccessController.doPrivileged( new GetPropertyAction("user.region")); if (region != null) { // region can be of form country, country_variant, or _variant int i = region.indexOf('_'); if (i >= 0) { country = region.substring(0, i); variant = region.substring(i + 1); } else { country = region; variant = ""; } } else { country = (String) AccessController.doPrivileged( new GetPropertyAction("user.country", "")); variant = (String) AccessController.doPrivileged( new GetPropertyAction("user.variant", "")); } defaultLocale = new Locale(language, country, variant); } return defaultLocale; } /** * Sets the default locale for this instance of the Java Virtual Machine. * This does not affect the host locale. *

* If there is a security manager, its checkPermission * method is called with a PropertyPermission("user.language", "write") * permission before the default locale is changed. *

* The Java Virtual Machine sets the default locale during startup * based on the host environment. It is used by many locale-sensitive * methods if no locale is explicitly specified. *

* Since changing the default locale may affect many different areas * of functionality, this method should only be used if the caller * is prepared to reinitialize locale-sensitive code running * within the same Java Virtual Machine, such as the user interface. * * @throws SecurityException * if a security manager exists and its * checkPermission method doesn't allow the operation. * @throws NullPointerException if newLocale is null * @param newLocale the new default locale * @see SecurityManager#checkPermission * @see java.util.PropertyPermission */ public static synchronized void setDefault(Locale newLocale) { if (newLocale == null) throw new NullPointerException("Can't set default locale to NULL"); SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(new PropertyPermission ("user.language", "write")); defaultLocale = newLocale; } /** * Returns an array of all installed locales. * The array returned must contain at least a Locale * instance equal to {@link java.util.Locale#US Locale.US}. * * @return An array of installed locales. */ public static Locale[] getAvailableLocales() { return LocaleData.getAvailableLocales("LocaleString"); } /** * Returns a list of all 2-letter country codes defined in ISO 3166. * Can be used to create Locales. */ public static String[] getISOCountries() { if (isoCountries == null) { isoCountries = new String[compressedIsoCountries.length() / 6]; for (int i = 0; i < isoCountries.length; i++) isoCountries[i] = compressedIsoCountries.substring((i * 6) + 1, (i * 6) + 3); } String[] result = new String[isoCountries.length]; System.arraycopy(isoCountries, 0, result, 0, isoCountries.length); return result; } /** * Returns a list of all 2-letter language codes defined in ISO 639. * Can be used to create Locales. * [NOTE: ISO 639 is not a stable standard-- some languages' codes have changed. * The list this function returns includes both the new and the old codes for the * languages whose codes have changed.] */ public static String[] getISOLanguages() { if (isoLanguages == null) { isoLanguages = new String[compressedIsoLanguages.length() / 6]; for (int i = 0; i < isoLanguages.length; i++) isoLanguages[i] = compressedIsoLanguages.substring((i * 6) + 1, (i * 6) + 3); } String[] result = new String[isoLanguages.length]; System.arraycopy(isoLanguages, 0, result, 0, isoLanguages.length); return result; } /** * Returns the language code for this locale, which will either be the empty string * or a lowercase ISO 639 code. *

NOTE: ISO 639 is not a stable standard-- some languages' codes have changed. * Locale's constructor recognizes both the new and the old codes for the languages * whose codes have changed, but this function always returns the old code. If you * want to check for a specific language whose code has changed, don't do

     * if (locale.getLanguage().equals("he")
     *    ...
     * 
Instead, do
     * if (locale.getLanguage().equals(new Locale("he", "", "").getLanguage())
     *    ...
* @see #getDisplayLanguage */ public String getLanguage() { return language; } /** * Returns the country/region code for this locale, which will * either be the empty string or an uppercase ISO 3166 2-letter code. * @see #getDisplayCountry */ public String getCountry() { return country; } /** * Returns the variant code for this locale. * @see #getDisplayVariant */ public String getVariant() { return variant; } /** * Getter for the programmatic name of the entire locale, * with the language, country and variant separated by underbars. * Language is always lower case, and country is always upper case. * If the language is missing, the string will begin with an underbar. * If both the language and country fields are missing, this function * will return the empty string, even if the variant field is filled in * (you can't have a locale with just a variant-- the variant must accompany * a valid language or country code). * Examples: "en", "de_DE", "_GB", "en_US_WIN", "de__POSIX", "fr__MAC" * @see #getDisplayName */ public final String toString() { boolean l = language.length() != 0; boolean c = country.length() != 0; boolean v = variant.length() != 0; StringBuffer result = new StringBuffer(language); if (c||(l&&v)) { result.append('_').append(country); // This may just append '_' } if (v&&(l||c)) { result.append('_').append(variant); } return result.toString(); } /** * Returns a three-letter abbreviation for this locale's language. If the locale * doesn't specify a language, this will be the empty string. Otherwise, this will * be a lowercase ISO 639-2/T language code. * The ISO 639-2 language codes can be found on-line at * http://www.loc.gov/standards/iso639-2/englangn.html * @exception MissingResourceException Throws MissingResourceException if the * three-letter language abbreviation is not available for this locale. */ public String getISO3Language() throws MissingResourceException { int length = language.length(); if (length == 0) { return ""; } int index = compressedIsoLanguages.indexOf("," + language); if (index == -1 || length != 2) { throw new MissingResourceException("Couldn't find 3-letter language code for " + language, "LocaleElements_" + toString(), "ShortLanguage"); } return compressedIsoLanguages.substring(index + 3, index + 6); } /** * Returns a three-letter abbreviation for this locale's country. If the locale * doesn't specify a country, this will be the empty string. Otherwise, this will * be an uppercase ISO 3166 3-letter country code. * The ISO 3166-2 country codes can be found on-line at * http://www.davros.org/misc/iso3166.txt * @exception MissingResourceException Throws MissingResourceException if the * three-letter country abbreviation is not available for this locale. */ public String getISO3Country() throws MissingResourceException { int length = country.length(); if (length == 0) { return ""; } int index = compressedIsoCountries.indexOf("," + country); if (index == -1 || length != 2) { throw new MissingResourceException("Couldn't find 3-letter country code for " + country, "LocaleElements_" + toString(), "ShortCountry"); } return compressedIsoCountries.substring(index + 3, index + 6); } /** * Returns a name for the locale's language that is appropriate for display to the * user. * If possible, the name returned will be localized for the default locale. * For example, if the locale is fr_FR and the default locale * is en_US, getDisplayLanguage() will return "French"; if the locale is en_US and * the default locale is fr_FR, getDisplayLanguage() will return "anglais". * If the name returned cannot be localized for the default locale, * (say, we don't have a Japanese name for Croatian), * this function falls back on the English name, and uses the ISO code as a last-resort * value. If the locale doesn't specify a language, this function returns the empty string. */ public final String getDisplayLanguage() { return getDisplayLanguage(getDefault()); } /** * Returns a name for the locale's language that is appropriate for display to the * user. * If possible, the name returned will be localized according to inLocale. * For example, if the locale is fr_FR and inLocale * is en_US, getDisplayLanguage() will return "French"; if the locale is en_US and * inLocale is fr_FR, getDisplayLanguage() will return "anglais". * If the name returned cannot be localized according to inLocale, * (say, we don't have a Japanese name for Croatian), * this function falls back on the default locale, on the English name, and finally * on the ISO code as a last-resort value. If the locale doesn't specify a language, * this function returns the empty string. */ public String getDisplayLanguage(Locale inLocale) { String langCode = language; if (langCode.length() == 0) return ""; Locale workingLocale = (Locale)inLocale.clone(); String result = null; int phase = 0; boolean done = false; if (workingLocale.variant.length() == 0) phase = 1; if (workingLocale.country.length() == 0) phase = 2; while (!done) { try { ResourceBundle bundle = LocaleData.getLocaleElements(workingLocale); result = findStringMatch((String[][])bundle.getObject("Languages"), langCode, langCode); if (result.length() != 0) done = true; } catch (Exception e) { // just fall through } if (!done) { switch (phase) { case 0: workingLocale = new Locale(workingLocale.language, workingLocale.country, ""); break; case 1: workingLocale = new Locale(workingLocale.language, "", workingLocale.variant); break; case 2: workingLocale = getDefault(); break; case 3: workingLocale = new Locale("", "", ""); break; default: return langCode; } phase++; } } return result; } /** * Returns a name for the locale's country that is appropriate for display to the * user. * If possible, the name returned will be localized for the default locale. * For example, if the locale is fr_FR and the default locale * is en_US, getDisplayCountry() will return "France"; if the locale is en_US and * the default locale is fr_FR, getDisplayLanguage() will return "Etats-Unis". * If the name returned cannot be localized for the default locale, * (say, we don't have a Japanese name for Croatia), * this function falls back on the English name, and uses the ISO code as a last-resort * value. If the locale doesn't specify a country, this function returns the empty string. */ public final String getDisplayCountry() { return getDisplayCountry(getDefault()); } /** * Returns a name for the locale's country that is appropriate for display to the * user. * If possible, the name returned will be localized according to inLocale. * For example, if the locale is fr_FR and inLocale * is en_US, getDisplayCountry() will return "France"; if the locale is en_US and * inLocale is fr_FR, getDisplayLanguage() will return "Etats-Unis". * If the name returned cannot be localized according to inLocale. * (say, we don't have a Japanese name for Croatia), * this function falls back on the default locale, on the English name, and finally * on the ISO code as a last-resort value. If the locale doesn't specify a country, * this function returns the empty string. */ public String getDisplayCountry(Locale inLocale) { String ctryCode = country; if (ctryCode.length() == 0) return ""; Locale workingLocale = (Locale)inLocale.clone(); String result = null; int phase = 0; boolean done = false; if (workingLocale.variant.length() == 0) phase = 1; if (workingLocale.country.length() == 0) phase = 2; while (!done) { try { ResourceBundle bundle = LocaleData.getLocaleElements(workingLocale); result = findStringMatch((String[][])bundle.getObject("Countries"), ctryCode, ctryCode); if (result.length() != 0) done = true; } catch (Exception e) { // just fall through } if (!done) { switch (phase) { case 0: workingLocale = new Locale(workingLocale.language, workingLocale.country, ""); break; case 1: workingLocale = new Locale(workingLocale.language, "", workingLocale.variant); break; case 2: workingLocale = getDefault(); break; case 3: workingLocale = new Locale("", "", ""); break; default: return ctryCode; } phase++; } } return result; } /** * Returns a name for the locale's variant code that is appropriate for display to the * user. If possible, the name will be localized for the default locale. If the locale * doesn't specify a variant code, this function returns the empty string. */ public final String getDisplayVariant() { return getDisplayVariant(getDefault()); } /** * Returns a name for the locale's variant code that is appropriate for display to the * user. If possible, the name will be localized for inLocale. If the locale * doesn't specify a variant code, this function returns the empty string. */ public String getDisplayVariant(Locale inLocale) { if (variant.length() == 0) return ""; ResourceBundle bundle = LocaleData.getLocaleElements(inLocale); String names[] = getDisplayVariantArray(bundle); // Get the localized patterns for formatting a list, and use // them to format the list. String[] patterns; try { patterns = (String[])bundle.getObject("LocaleNamePatterns"); } catch (MissingResourceException e) { patterns = null; } return formatList(patterns, names); } /** * Returns a name for the locale that is appropriate for display to the * user. This will be the values returned by getDisplayLanguage(), getDisplayCountry(), * and getDisplayVariant() assembled into a single string. The display name will have * one of the following forms:

* language (country, variant)

* language (country)

* language (variant)

* country (variant)

* language

* country

* variant

* depending on which fields are specified in the locale. If the language, country, * and variant fields are all empty, this function returns the empty string. */ public final String getDisplayName() { return getDisplayName(getDefault()); } /** * Returns a name for the locale that is appropriate for display to the * user. This will be the values returned by getDisplayLanguage(), getDisplayCountry(), * and getDisplayVariant() assembled into a single string. The display name will have * one of the following forms:

* language (country, variant)

* language (country)

* language (variant)

* country (variant)

* language

* country

* variant

* depending on which fields are specified in the locale. If the language, country, * and variant fields are all empty, this function returns the empty string. */ public String getDisplayName(Locale inLocale) { ResourceBundle bundle = LocaleData.getLocaleElements(inLocale); String languageName = getDisplayLanguage(inLocale); String countryName = getDisplayCountry(inLocale); String[] variantNames = getDisplayVariantArray(bundle); // Get the localized patterns for formatting a display name. String[] patterns; try { patterns = (String[])bundle.getObject("LocaleNamePatterns"); } catch (MissingResourceException e) { patterns = null; } // The display name consists of a main name, followed by qualifiers. // Typically, the format is "MainName (Qualifier, Qualifier)" but this // depends on what pattern is stored in the display locale. String mainName = null; String[] qualifierNames = null; // The main name is the language, or if there is no language, the country. // If there is neither language nor country (an anomalous situation) then // the display name is simply the variant's display name. if (languageName.length() != 0) { mainName = languageName; if (countryName.length() != 0) { qualifierNames = new String[variantNames.length + 1]; System.arraycopy(variantNames, 0, qualifierNames, 1, variantNames.length); qualifierNames[0] = countryName; } else qualifierNames = variantNames; } else if (countryName.length() != 0) { mainName = countryName; qualifierNames = variantNames; } else { return formatList(patterns, variantNames); } // Create an array whose first element is the number of remaining // elements. This serves as a selector into a ChoiceFormat pattern from // the resource. The second and third elements are the main name and // the qualifier; if there are no qualifiers, the third element is // unused by the format pattern. Object[] displayNames = { new Integer(qualifierNames.length != 0 ? 2 : 1), mainName, // We could also just call formatList() and have it handle the empty // list case, but this is more efficient, and we want it to be // efficient since all the language-only locales will not have any // qualifiers. qualifierNames.length != 0 ? formatList(patterns, qualifierNames) : null }; if (patterns != null) { return new MessageFormat(patterns[0]).format(displayNames); } else { // If we cannot get the message format pattern, then we use a simple // hard-coded pattern. This should not occur in practice unless the // installation is missing some core files (LocaleElements etc.). StringBuffer result = new StringBuffer(); result.append((String)displayNames[1]); if (displayNames.length > 2) { result.append(" ("); result.append((String)displayNames[2]); result.append(")"); } return result.toString(); } } /** * Overrides Cloneable */ public Object clone() { try { Locale that = (Locale)super.clone(); return that; } catch (CloneNotSupportedException e) { throw new InternalError(); } } /** * Override hashCode. * Since Locales are often used in hashtables, caches the value * for speed. */ public int hashCode() { int hc = hashCodeValue; if (hc == 0) { hc = (language.hashCode() << 8) ^ country.hashCode() ^ (variant.hashCode() << 4); hashCodeValue = hc; } return hc; } // Overrides /** * Returns true if this Locale is equal to another object. A Locale is * deemed equal to another Locale with identical language, country, * and variant, and unequal to all other objects. * * @return true if this Locale is equal to the specified object. */ public boolean equals(Object obj) { if (this == obj) // quick check return true; if (!(obj instanceof Locale)) return false; Locale other = (Locale) obj; return language == other.language && country == other.country && variant == other.variant; } // ================= privates ===================================== // XXX instance and class variables. For now keep these separate, since it is // faster to match. Later, make into single string. /** * @serial * @see #getLanguage */ private final String language; /** * @serial * @see #getCountry */ private final String country; /** * @serial * @see #getVariant */ private final String variant; /** * Placeholder for the object's hash code. Always -1. * @serial */ private volatile int hashcode = -1; // lazy evaluate /** * Calculated hashcode to fix 4518797. */ private transient volatile int hashCodeValue = 0; private static Locale defaultLocale = null; /** * Return an array of the display names of the variant. * @param bundle the ResourceBundle to use to get the display names * @return an array of display names, possible of zero length. */ private String[] getDisplayVariantArray(ResourceBundle bundle) { // Split the variant name into tokens separated by '_'. StringTokenizer tokenizer = new StringTokenizer(variant, "_"); String[] names = new String[tokenizer.countTokens()]; // For each variant token, lookup the display name. If // not found, use the variant name itself. for (int i=0; i0) result.append(','); result.append(stringList[i]); } return result.toString(); } // Compose the list down to three elements if necessary if (stringList.length > 3) { MessageFormat format = new MessageFormat(patterns[2]); stringList = composeList(format, stringList); } // Rebuild the argument list with the list length as the first element Object[] args = new Object[stringList.length + 1]; System.arraycopy(stringList, 0, args, 1, stringList.length); args[0] = new Integer(stringList.length); // Format it using the pattern in the resource MessageFormat format = new MessageFormat(patterns[1]); return format.format(args); } /** * Given a list of strings, return a list shortened to three elements. * Shorten it by applying the given format to the first two elements * recursively. * @param format a format which takes two arguments * @param list a list of strings * @return if the list is three elements or shorter, the same list; * otherwise, a new list of three elements. */ private static String[] composeList(MessageFormat format, String[] list) { if (list.length <= 3) return list; // Use the given format to compose the first two elements into one String[] listItems = { list[0], list[1] }; String newItem = format.format(listItems); // Form a new list one element shorter String[] newList = new String[list.length-1]; System.arraycopy(list, 2, newList, 1, newList.length-1); newList[0] = newItem; // Recurse return composeList(format, newList); } /** * Replace the deserialized Locale object with a newly * created object. Older language codes are replaced with newer ISO * codes. The country and variant codes are replaced with internalized * String copies. */ private Object readResolve() throws java.io.ObjectStreamException { return new Locale(language, country, variant); } /** * List of all 2-letter language codes currently defined in ISO 639. * (Because the Java VM specification turns an array constant into executable code * that generates the array element by element, we keep the array in compressed * form in a single string and build the array from it at run time when requested.) * [We're now also using this table to store a mapping from 2-letter ISO language codes * to 3-letter ISO language codes. Each group of characters consists of a comma, a * 2-letter code, and a 3-letter code. We look up a 3-letter code by searching for * a comma followed by a 2-letter code and then getting the three letters following * the 2-letter code.] */ private static String[] isoLanguages = null; private static final String compressedIsoLanguages = ",aaaar,ababk,aeave,afafr,akaka,amamh,anarg,arara,asasm,avava" + ",ayaym,azaze,babak,bebel,bgbul,bhbih,bibis,bmbam,bnben,bobod" + ",brbre,bsbos,cacat,ceche,chcha,cocos,crcre,csces,cuchu,cvchv" + ",cycym,dadan,dedeu,dvdiv,dzdzo,eeewe,elell,eneng,eoepo,esspa" + ",etest,eueus,fafas,ffful,fifin,fjfij,fofao,frfra,fyfry,gagle" + ",gdgla,glglg,gngrn,guguj,gvglv,hahau,heheb,hihin,hohmo,hrhrv" + ",hthat,huhun,hyhye,hzher,iaina,idind,ieile,igibo,iiiii,ikipk" + ",inind,ioido,isisl,itita,iuiku,iwheb,jajpn,jiyid,jvjav,kakat" + ",kgkon,kikik,kjkua,kkkaz,klkal,kmkhm,knkan,kokor,krkau,kskas" + ",kukur,kvkom,kwcor,kykir,lalat,lbltz,lglug,lilim,lnlin,lolao" + ",ltlit,lulub,lvlav,mgmlg,mhmah,mimri,mkmkd,mlmal,mnmon,momol" + ",mrmar,msmsa,mtmlt,mymya,nanau,nbnob,ndnde,nenep,ngndo,nlnld" + ",nnnno,nonor,nrnbl,nvnav,nynya,ococi,ojoji,omorm,orori,ososs" + ",papan,pipli,plpol,pspus,ptpor,quque,rmroh,rnrun,roron,rurus" + ",rwkin,sasan,scsrd,sdsnd,sesme,sgsag,sisin,skslk,slslv,smsmo" + ",snsna,sosom,sqsqi,srsrp,ssssw,stsot,susun,svswe,swswa,tatam" + ",tetel,tgtgk,ththa,titir,tktuk,tltgl,tntsn,toton,trtur,tstso" + ",tttat,twtwi,tytah,uguig,ukukr,ururd,uzuzb,veven,vivie,vovol" + ",wawln,wowol,xhxho,yiyid,yoyor,zazha,zhzho,zuzul"; /** * List of all 2-letter country codes currently defined in ISO 3166. * (Because the Java VM specification turns an array constant into executable code * that generates the array element by element, we keep the array in compressed * form in a single string and build the array from it at run time when requested.) * [We're now also using this table to store a mapping from 2-letter ISO country codes * to 3-letter ISO country codes. Each group of characters consists of a comma, a * 2-letter code, and a 3-letter code. We look up a 3-letter code by searching for * a comma followed by a 2-letter code and then getting the three letters following * the 2-letter code.] */ private static String[] isoCountries = null; private static final String compressedIsoCountries = ",ADAND,AEARE,AFAFG,AGATG,AIAIA,ALALB,AMARM,ANANT,AOAGO,AQATA" + ",ARARG,ASASM,ATAUT,AUAUS,AWABW,AXALA,AZAZE,BABIH,BBBRB,BDBGD,BEBEL" + ",BFBFA,BGBGR,BHBHR,BIBDI,BJBEN,BMBMU,BNBRN,BOBOL,BRBRA,BSBHS" + ",BTBTN,BVBVT,BWBWA,BYBLR,BZBLZ,CACAN,CCCCK,CDCOD,CFCAF,CGCOG" + ",CHCHE,CICIV,CKCOK,CLCHL,CMCMR,CNCHN,COCOL,CRCRI,CSSCG,CUCUB" + ",CVCPV,CXCXR,CYCYP,CZCZE,DEDEU,DJDJI,DKDNK,DMDMA,DODOM,DZDZA" + ",ECECU,EEEST,EGEGY,EHESH,ERERI,ESESP,ETETH,FIFIN,FJFJI,FKFLK" + ",FMFSM,FOFRO,FRFRA,GAGAB,GBGBR,GDGRD,GEGEO,GFGUF,GHGHA,GIGIB" + ",GLGRL,GMGMB,GNGIN,GPGLP,GQGNQ,GRGRC,GSSGS,GTGTM,GUGUM,GWGNB" + ",GYGUY,HKHKG,HMHMD,HNHND,HRHRV,HTHTI,HUHUN,IDIDN,IEIRL,ILISR" + ",ININD,IOIOT,IQIRQ,IRIRN,ISISL,ITITA,JMJAM,JOJOR,JPJPN,KEKEN" + ",KGKGZ,KHKHM,KIKIR,KMCOM,KNKNA,KPPRK,KRKOR,KWKWT,KYCYM,KZKAZ" + ",LALAO,LBLBN,LCLCA,LILIE,LKLKA,LRLBR,LSLSO,LTLTU,LULUX,LVLVA" + ",LYLBY,MAMAR,MCMCO,MDMDA,MGMDG,MHMHL,MKMKD,MLMLI,MMMMR,MNMNG" + ",MOMAC,MPMNP,MQMTQ,MRMRT,MSMSR,MTMLT,MUMUS,MVMDV,MWMWI,MXMEX" + ",MYMYS,MZMOZ,NANAM,NCNCL,NENER,NFNFK,NGNGA,NINIC,NLNLD,NONOR" + ",NPNPL,NRNRU,NUNIU,NZNZL,OMOMN,PAPAN,PEPER,PFPYF,PGPNG,PHPHL" + ",PKPAK,PLPOL,PMSPM,PNPCN,PRPRI,PSPSE,PTPRT,PWPLW,PYPRY,QAQAT" + ",REREU,ROROU,RURUS,RWRWA,SASAU,SBSLB,SCSYC,SDSDN,SESWE,SGSGP" + ",SHSHN,SISVN,SJSJM,SKSVK,SLSLE,SMSMR,SNSEN,SOSOM,SRSUR,STSTP" + ",SVSLV,SYSYR,SZSWZ,TCTCA,TDTCD,TFATF,TGTGO,THTHA,TJTJK,TKTKL" + ",TLTLS,TMTKM,TNTUN,TOTON,TRTUR,TTTTO,TVTUV,TWTWN,TZTZA,UAUKR" + ",UGUGA,UMUMI,USUSA,UYURY,UZUZB,VAVAT,VCVCT,VEVEN,VGVGB,VIVIR" + ",VNVNM,VUVUT,WFWLF,WSWSM,YEYEM,YTMYT,ZAZAF,ZMZMB,ZWZWE"; /* * Locale needs its own, locale insensitive version of toLowerCase to * avoid circularity problems between Locale and String. * The most straightforward algorithm is used. Look at optimizations later. */ private String toLowerCase(String str) { char[] buf = new char[str.length()]; for (int i = 0; i < buf.length; i++) { buf[i] = Character.toLowerCase(str.charAt(i)); } return new String( buf ); } /* * Locale needs its own, locale insensitive version of toUpperCase to * avoid circularity problems between Locale and String. * The most straightforward algorithm is used. Look at optimizations later. */ private String toUpperCase(String str) { char[] buf = new char[str.length()]; for (int i = 0; i < buf.length; i++) { buf[i] = Character.toUpperCase(str.charAt(i)); } return new String( buf ); } private String findStringMatch(String[][] languages, String desiredLanguage, String fallbackLanguage) { for (int i = 0; i < languages.length; ++i) if (desiredLanguage.equals(languages[i][0])) return languages[i][1]; if (!fallbackLanguage.equals(desiredLanguage)) for (int i = 0; i < languages.length; ++i) if (fallbackLanguage.equals(languages[i][0])) return languages[i][1]; if (!"EN".equals(desiredLanguage) && "EN".equals(fallbackLanguage)) for (int i = 0; i < languages.length; ++i) if ("EN".equals(languages[i][0])) return languages[i][1]; return ""; } private String convertOldISOCodes(String language) { // we accept both the old and the new ISO codes for the languages whose ISO // codes have changed, but we always store the OLD code, for backward compatibility language = toLowerCase(language).intern(); if (language == "he") { return "iw"; } else if (language == "yi") { return "ji"; } else if (language == "id") { return "in"; } else { return language; } } }