/*
*
* @(#)Locale.java 1.64 06/10/10
*
* Portions Copyright 2000-2008 Sun Microsystems, Inc. All Rights
* Reserved. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
/*
* (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 <code>Locale</code> object represents a specific geographical, political,
* or cultural region. An operation that requires a <code>Locale</code> to perform
* its task is called <em>locale-sensitive</em> and uses the <code>Locale</code>
* 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.
*
* <P>
* Create a <code>Locale</code> object using the constructors in this class:
* <blockquote>
* <pre>
* Locale(String language)
* Locale(String language, String country)
* Locale(String language, String country, String variant)
* </pre>
* </blockquote>
* The language argument is a valid <STRONG>ISO Language Code.</STRONG>
* 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:
* <BR><a href ="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt">
* <code>http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt</code></a>
*
* <P>
* The country argument is a valid <STRONG>ISO Country Code.</STRONG> 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:
* <BR><a href="http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html">
* <code>http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html</code></a>
*
* <P>
* 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".
*
* <P>
* Because a <code>Locale</code> object is just an identifier for a region,
* no validity check is performed when you construct a <code>Locale</code>.
* If you want to see whether particular resources are available for the
* <code>Locale</code> you construct, you must query those resources. For
* example, ask the <code>NumberFormat</code> for the locales it supports
* using its <code>getAvailableLocales</code> method.
* <BR><STRONG>Note:</STRONG> 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}.
*
* <P>
* The <code>Locale</code> class provides a number of convenient constants
* that you can use to create <code>Locale</code> objects for commonly used
* locales. For example, the following creates a <code>Locale</code> object
* for the United States:
* <blockquote>
* <pre>
* Locale.US
* </pre>
* </blockquote>
*
* <P>
* Once you've created a <code>Locale</code> you can query it for information about
* itself. Use <code>getCountry</code> to get the ISO Country Code and
* <code>getLanguage</code> to get the ISO Language Code. You can
* use <code>getDisplayCountry</code> to get the
* name of the country suitable for displaying to the user. Similarly,
* you can use <code>getDisplayLanguage</code> to get the name of
* the language suitable for displaying to the user. Interestingly,
* the <code>getDisplayXXX</code> 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.
*
* <P>
* The Java 2 platform provides a number of classes that perform locale-sensitive
* operations. For example, the <code>NumberFormat</code> class formats
* numbers, currency, or percentages in a locale-sensitive manner. Classes
* such as <code>NumberFormat</code> have a number of convenience methods
* for creating a default object of that type. For example, the
* <code>NumberFormat</code> class provides these three convenience methods
* for creating a default <code>NumberFormat</code> object:
* <blockquote>
* <pre>
* NumberFormat.getInstance()
* NumberFormat.getCurrencyInstance()
* NumberFormat.getPercentInstance()
* </pre>
* </blockquote>
* These methods have two variants; one with an explicit locale
* and one without; the latter using the default locale.
* <blockquote>
* <pre>
* NumberFormat.getInstance(myLocale)
* NumberFormat.getCurrencyInstance(myLocale)
* NumberFormat.getPercentInstance(myLocale)
* </pre>
* </blockquote>
* A <code>Locale</code> is the mechanism for identifying the kind of object
* (<code>NumberFormat</code>) that you would like to get. The locale is
* <STRONG>just</STRONG> a mechanism for identifying objects,
* <STRONG>not</STRONG> a container for the objects themselves.
*
* <P>
* Each class that performs locale-sensitive operations allows you
* to get all the available objects of that type. You can sift
* through these objects by language, country, or variant,
* and use the display names to present a menu to the user.
* For example, you can create a menu of all the collation objects
* suitable for a given language. Such classes must implement these
* three class methods:
* <blockquote>
* <pre>
* public static Locale[] getAvailableLocales()
* public static String getDisplayName(Locale objectLocale,
* Locale displayLocale)
* public static final String getDisplayName(Locale objectLocale)
* // getDisplayName will throw MissingResourceException if the locale
* // is not one of the available locales.
* </pre>
* </blockquote>
*
* @see ResourceBundle
* @see java.text.Format
* @see java.text.NumberFormat
* @see java.text.Collator
* @version 1.55, 01/19/00
* @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.
* <p>
* 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.
* <p>
* If there is a security manager, its <code>checkPermission</code>
* method is called with a <code>PropertyPermission("user.language", "write")</code>
* permission before the default locale is changed.
* <p>
* 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.
* <p>
* 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
* <code>checkPermission</code> method doesn't allow the operation.
* @throws NullPointerException if <code>newLocale</code> 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 a list of all 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.
* <p>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 <pre>
* if (locale.getLanguage().equals("he")
* ...
* </pre>Instead, do<pre>
* if (locale.getLanguage().equals(new Locale("he", "", "").getLanguage())
* ...</pre>
* @see #getDisplayLanguage
*/
public String getLanguage() {
return language;
}
/**
* Returns the country/region code for this locale, which will either be the empty string
* or an upercase 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
* <a href="ftp://dkuug.dk/i18n/iso-639-2.txt"><code>ftp://dkuug.dk/i18n/iso-639-2.txt</code></a>
* @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 tbe the empty string. Otherwise, this will
* be an uppercase ISO 3166 3-letter country code.
* @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.variant = "";
break;
case 1:
workingLocale.country = "";
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.variant = "";
break;
case 1:
workingLocale.country = "";
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:<p><blockquote>
* language (country, variant)<p>
* language (country)<p>
* language (variant)<p>
* country (variant)<p>
* language<p>
* country<p>
* variant<p></blockquote>
* 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:<p><blockquote>
* language (country, variant)<p>
* language (country)<p>
* language (variant)<p>
* country (variant)<p>
* language<p>
* country<p>
* variant<p></blockquote>
* 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.
*/
// Depending on performance of synchronized, may want to
// just compute in constructor.
public synchronized int hashCode() {
if (hashcode == -1) {
hashcode =
language.hashCode() ^
country.hashCode() ^
variant.hashCode();
}
return hashcode;
}
// 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)) // (1) same object?
return false;
Locale other = (Locale) obj;
if (hashCode() != other.hashCode()) return false; // quick check
if (language != other.language) return false;
if (country != other.country) return false;
if (variant != other.variant) return false;
return true; // we made it through the guantlet.
// (1) We don't check super.equals since it is Object.
// Since Locale is final, we don't have to check both directions.
}
// ================= privates =====================================
// instance and class variables. For now keep these separate, since it is
// faster to match. Later, make into single string.
/**
* @serial
* @see #getLanguage
*/
private String language = "";
/**
* @serial
* @see #getCountry
*/
private String country = "";
/**
* @serial
* @see #getVariant
*/
private String variant = "";
/**
* Placeholder for the object's hash code. Always -1.
* @serial
*/
private int hashcode = -1;
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; i<names.length; ++i) {
String token = tokenizer.nextToken();
try {
names[i] = (String)bundle.getObject("%%" + token);
}
catch (MissingResourceException e) {
names[i] = token;
}
}
return names;
}
/**
* Format a list with an array of patterns.
* @param patterns an array of three patterns. The first pattern is not
* used. The second pattern should create a MessageFormat taking 0-3 arguments
* and formatting them into a list. The third pattern should take 2 arguments
* and is used by composeList. If patterns is null, then a the list is
* formatted by concatenation with the delimiter ','.
* @param stringList the list of strings to be formatted.
* @return a string representing the list.
*/
private static String formatList(String[] patterns, String[] stringList) {
// If we have no list patterns, compose the list in a simple,
// non-localized way.
if (patterns == null) {
StringBuffer result = new StringBuffer();
for (int i=0; i<stringList.length; ++i) {
if (i>0) 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);
}
/**
* @serialData The first three fields are three <code>String</code> objects:
* the first is a 2-letter ISO 639 code representing the locale's language,
* the second is a 2-letter ISO 3166 code representing the locale's region or
* country, and the third is an optional chain of variant codes defined by this
* library. Any of the fields may be the empty string. The fourth field is an
* <code>int</code> whose value is always -1. This is a sentinel value indicating
* the <code>Locale</code>'s hash code must be recomputed.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
// hashcode is semantically transient. We couldn't define it as transient
// because versions of this class that DIDN'T declare it as transient have
// already shipped. What we're doing here is making sure that the written-out
// version of hashcode is always -1, regardless of what's really stored there
// (we hold onto the original value just in case someone might want it again).
// Writing -1 ensures that version 1.1 Locales will always recalculate their
// hash codes after being streamed back in. This is necessary because
// String.hashCode() calculates its hash code differently in 1.2 than it did
// in 1.1.
int temp = hashcode;
hashcode = -1;
out.defaultWriteObject();
hashcode = temp;
}
/**
* @serialData The first three fields are three <code>String</code> objects:
* the first is a 2-letter ISO 639 code representing the locale's language,
* the second is a 2-letter ISO 3166 code representing the locale's region or
* country, and the third is an optional chain of variant codes defined by this
* library. Any of the fields may be the empty string. The fourth field is an
* <code>int</code>representing the locale's hash code, but is ignored by
* <code>readObject()</code>. Whatever this field's value, the hash code is
* initialized to -1, a sentinel value that indicates the hash code must be
* recomputed.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// hashcode is semantically transient. We couldn't define it as transient
// because versions of this class that DIDN'T declare is as transient have
// already shipped. This code makes sure that whatever value for hashcode
// was written on the stream, we ignore it and recalculate it on demand. This
// is necessary because String.hashCode() calculates is hash code differently
// in version 1.2 than it did in 1.1.
in.defaultReadObject();
hashcode = -1;
language = convertOldISOCodes(language);
country = country.intern();
variant = variant.intern();
}
/**
* 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,afafr,amamh,arara,asasm,ayaym,azaze,babak,bebel,bgbul,bhbih,bibis,bnben,"
+ "bobod,brbre,cacat,cocos,csces,cycym,dadan,dedeu,dzdzo,elell,eneng,eoepo,esspa,"
+ "etest,eueus,fafas,fifin,fjfij,fofao,frfra,fyfry,gagai,gdgdh,glglg,gngrn,guguj,"
+ "hahau,heheb,hihin,hrhrv,huhun,hyhye,iaina,idind,ieile,ikipk,inind,isisl,itita,"
+ "iuiku,iwheb,jajpn,jiyid,jwjaw,kakat,kkkaz,klkal,kmkhm,knkan,kokor,kskas,kukur,"
+ "kykir,lalat,lnlin,lolao,ltlit,lvlav,mgmlg,mimri,mkmkd,mlmal,mnmon,momol,mrmar,"
+ "msmsa,mtmlt,mymya,nanau,nenep,nlnld,nonor,ococi,omorm,orori,papan,plpol,pspus,"
+ "ptpor,quque,rmroh,rnrun,roron,rurus,rwkin,sasan,sdsnd,sgsag,shsrp,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,uguig,ukukr,"
+ "ururd,uzuzb,vivie,vovol,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,AZAZE,BABIH,BBBRB,BDBGD,BEBEL,BFBFA,BGBGR,BHBHR,BIBDI,BJBEN,BMBMU,"
+ "BNBRN,BOBOL,BRBRA,BSBHS,BTBTN,BVBVT,BWBWA,BYBLR,BZBLZ,CACAN,CCCCK,CFCAF,CGCOG,"
+ "CHCHE,CICIV,CKCOK,CLCHL,CMCMR,CNCHN,COCOL,CRCRI,CUCUB,CVCPV,CXCXR,CYCYP,CZCZE,"
+ "DEDEU,DJDJI,DKDNK,DMDMA,DODOM,DZDZA,ECECU,EEEST,EGEGY,EHESH,ERERI,ESESP,ETETH,"
+ "FIFIN,FJFJI,FKFLK,FMFSM,FOFRO,FRFRA,FXFXX,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,PTPRT,PWPLW,PYPRY,QAQAT,REREU,ROROM,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,"
+ "TMTKM,TNTUN,TOTON,TPTMP,TRTUR,TTTTO,TVTUV,TWTWN,TZTZA,UAUKR,UGUGA,UMUMI,USUSA,"
+ "UYURY,UZUZB,VAVAT,VCVCT,VEVEN,VGVGB,VIVIR,VNVNM,VUVUT,WFWLF,WSWSM,YEYEM,YTMYT,"
+ "YUYUG,ZAZAF,ZMZMB,ZRZAR,ZWZWE";
/*
* Locale needs its own, locale insenitive 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 = str.toCharArray();
for (int i = 0; i < buf.length; i++) {
buf[i] = Character.toLowerCase( buf[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 = str.toCharArray();
for (int i = 0; i < buf.length; i++) {
buf[i] = Character.toUpperCase( buf[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;
}
}
}