blob: 5337e48e24f1768bb5f795c488be07fe4df2e5a0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.util;
// BEGIN android-changed
// import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.security.AccessController;
// import java.util.zip.ZipEntry;
// import java.util.zip.ZipFile;
import org.apache.harmony.luni.util.PriviAction;
import org.apache.harmony.luni.util.Util;
import com.ibm.icu4jni.util.Resources;
// END android-changed
/**
* {@code Locale} represents a language/country/variant combination. Locales are used to
* alter the presentation of information such as numbers or dates to suit the conventions
* in the region they describe.
*
* <p>The language codes are two-letter lowercase ISO language codes (such as "en") as defined by
* <a href="http://en.wikipedia.org/wiki/ISO_639-1">ISO 639-1</a>.
* The country codes are two-letter uppercase ISO country codes (such as "US") as defined by
* <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3">ISO 3166-1</a>.
* The variant codes are unspecified.
*
* <p>Note that Java uses several deprecated two-letter codes. The Hebrew ("he") language
* code is rewritten as "iw", Indonesian ("id") as "in", and Yiddish ("yi") as "ji". This
* is true even if you construct your own {@code Locale} object, not just of instances returned by
* the various lookup methods.
*
* <p>Just because you can create a {@code Locale} doesn't mean that it makes much sense.
* Imagine "de_US" for German as spoken in the US, for example. It is also a mistake to
* assume that all devices have the same locales available. A device sold in the US will
* almost certainly support en_US and sp_US (English and Spanish, as spoken in the US),
* but not necessarily en_GB or sp_SP (English as spoken in Great Britain or Spanish as
* spoken in Spain), for example. The opposite may well be true for a device sold in Europe.
* (This limitation even affects those locales pre-defined as constants in this class.)
*
* <p>You can use {@code getDefault} to get an appropriate locale for the device you're
* running on, or {@code getAvailableLocales} to get a list of all the locales available
* on the device you're running on.
*
* @see ResourceBundle
*/
public final class Locale implements Cloneable, Serializable {
private static final long serialVersionUID = 9149081749638150636L;
// BEGIN android-added
private static volatile Locale[] availableLocales;
// END android-added
// Initialize a default which is used during static
// initialization of the default for the platform.
private static Locale defaultLocale = new Locale();
/**
* Locale constant for en_CA.
*/
public static final Locale CANADA = new Locale("en", "CA"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for fr_CA.
*/
public static final Locale CANADA_FRENCH = new Locale("fr", "CA"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for zh_CN.
*/
public static final Locale CHINA = new Locale("zh", "CN"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for zh.
*/
public static final Locale CHINESE = new Locale("zh", ""); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for en.
*/
public static final Locale ENGLISH = new Locale("en", ""); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for fr_FR.
*/
public static final Locale FRANCE = new Locale("fr", "FR"); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for fr.
*/
public static final Locale FRENCH = new Locale("fr", ""); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for de.
*/
public static final Locale GERMAN = new Locale("de", ""); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for de_DE.
*/
public static final Locale GERMANY = new Locale("de", "DE"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for it.
*/
public static final Locale ITALIAN = new Locale("it", ""); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for it_IT.
*/
public static final Locale ITALY = new Locale("it", "IT"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for ja_JP.
*/
public static final Locale JAPAN = new Locale("ja", "JP"); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for ja.
*/
public static final Locale JAPANESE = new Locale("ja", ""); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for ko_KR.
*/
public static final Locale KOREA = new Locale("ko", "KR"); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for ko.
*/
public static final Locale KOREAN = new Locale("ko", ""); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for zh_CN.
*/
public static final Locale PRC = new Locale("zh", "CN"); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for zh_CN.
*/
public static final Locale SIMPLIFIED_CHINESE = new Locale("zh", "CN"); //$NON-NLS-1$//$NON-NLS-2$
/**
* Locale constant for zh_TW.
*/
public static final Locale TAIWAN = new Locale("zh", "TW"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for zh_TW.
*/
public static final Locale TRADITIONAL_CHINESE = new Locale("zh", "TW"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for en_GB.
*/
public static final Locale UK = new Locale("en", "GB"); //$NON-NLS-1$ //$NON-NLS-2$
/**
* Locale constant for en_US.
*/
public static final Locale US = new Locale("en", "US"); //$NON-NLS-1$//$NON-NLS-2$
private static final PropertyPermission setLocalePermission = new PropertyPermission(
"user.language", "write"); //$NON-NLS-1$//$NON-NLS-2$
static {
String language = AccessController
.doPrivileged(new PriviAction<String>("user.language", "en")); //$NON-NLS-1$ //$NON-NLS-2$
// BEGIN android-changed
String region = AccessController.doPrivileged(new PriviAction<String>(
"user.region", "US")); //$NON-NLS-1$ //$NON-NLS-2$
// END android-changed
String variant = AccessController.doPrivileged(new PriviAction<String>(
"user.variant", "")); //$NON-NLS-1$ //$NON-NLS-2$
defaultLocale = new Locale(language, region, variant);
}
private transient String countryCode;
private transient String languageCode;
private transient String variantCode;
private transient String cachedToStringResult;
// BEGIN android-removed
// private transient ULocale uLocale;
// END android-removed
/**
* Constructs a default which is used during static initialization of the
* default for the platform.
*/
private Locale() {
languageCode = "en"; //$NON-NLS-1$
countryCode = "US"; //$NON-NLS-1$
variantCode = ""; //$NON-NLS-1$
}
/**
* Constructs a new {@code Locale} using the specified language.
*
* @param language
* the language this {@code Locale} represents.
*/
public Locale(String language) {
this(language, "", ""); //$NON-NLS-1$//$NON-NLS-2$
}
/**
* Constructs a new {@code Locale} using the specified language and country codes.
*
* @param language
* the language this {@code Locale} represents.
* @param country
* the country this {@code Locale} represents.
*/
public Locale(String language, String country) {
this(language, country, ""); //$NON-NLS-1$
}
/**
* Constructs a new {@code Locale} using the specified language, country, and
* variant codes.
*
* @param language
* the language this {@code Locale} represents.
* @param country
* the country this {@code Locale} represents.
* @param variant
* the variant this {@code Locale} represents.
* @throws NullPointerException
* if {@code language}, {@code country}, or
* {@code variant} is {@code null}.
*/
public Locale(String language, String country, String variant) {
if (language == null || country == null || variant == null) {
throw new NullPointerException();
}
if(language.length() == 0 && country.length() == 0){
languageCode = "";
countryCode = "";
variantCode = variant;
return;
}
// BEGIN android-changed
// this.uLocale = new ULocale(language, country, variant);
// languageCode = uLocale.getLanguage();
languageCode = Util.toASCIILowerCase(language);
// END android-changed
// Map new language codes to the obsolete language
// codes so the correct resource bundles will be used.
if (languageCode.equals("he")) {//$NON-NLS-1$
languageCode = "iw"; //$NON-NLS-1$
} else if (languageCode.equals("id")) {//$NON-NLS-1$
languageCode = "in"; //$NON-NLS-1$
} else if (languageCode.equals("yi")) {//$NON-NLS-1$
languageCode = "ji"; //$NON-NLS-1$
}
// countryCode is defined in ASCII character set
// BEGIN android-changed
// countryCode = country.length()!=0?uLocale.getCountry():"";
countryCode = Util.toASCIIUpperCase(country);
// END android-changed
// Work around for be compatible with RI
variantCode = variant;
}
/**
* Returns a new {@code Locale} with the same language, country and variant codes as
* this {@code Locale}.
*
* @return a shallow copy of this {@code Locale}.
* @see java.lang.Cloneable
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // android-changed
}
}
/**
* Compares the specified object to this {@code Locale} and returns whether they are
* equal. The object must be an instance of {@code Locale} and have the same
* language, country and variant.
*
* @param object
* the object to compare with this object.
* @return {@code true} if the specified object is equal to this {@code Locale},
* {@code false} otherwise.
* @see #hashCode
*/
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof Locale) {
Locale o = (Locale) object;
return languageCode.equals(o.languageCode)
&& countryCode.equals(o.countryCode)
&& variantCode.equals(o.variantCode);
}
return false;
}
// BEGIN android-added
static Locale[] find() {
String[] locales = Resources.getAvailableLocales();
ArrayList<Locale> temp = new ArrayList<Locale>();
for (int i = 0; i < locales.length; i++) {
String s = locales[i];
int first = s.indexOf('_');
int second = s.indexOf('_', first + 1);
if (first == -1) {
// Language only
temp.add(new Locale(s));
} else if (second == -1) {
// Language and country
temp.add(new Locale(s.substring(0, first), s.substring(first + 1)));
} else {
// Language and country and variant
temp.add(new Locale(s.substring(0, first), s.substring(first + 1, second), s.substring(second + 1)));
}
}
Locale[] result = new Locale[temp.size()];
return temp.toArray(result);
}
// END android-added
/**
* Gets the list of installed {@code Locale}s. At least a {@code Locale} that is equal to
* {@code Locale.US} must be contained in this array.
*
* @return an array of {@code Locale}s.
*/
public static Locale[] getAvailableLocales() {
// BEGIN android-changed
// ULocale[] ulocales = ULocale.getAvailableLocales();
// Locale[] locales = new Locale[ulocales.length];
// for (int i = 0; i < locales.length; i++) {
// locales[i] = ulocales[i].toLocale();
// }
// return locales;
if (availableLocales == null) {
availableLocales = find();
}
return availableLocales.clone();
// END android-changed
}
/**
* Gets the country code for this {@code Locale} or an empty string of no country
* was set.
*
* @return a country code.
*/
public String getCountry() {
return countryCode;
}
/**
* Gets the default {@code Locale}.
*
* @return the default {@code Locale}.
*/
public static Locale getDefault() {
return defaultLocale;
}
/**
* Gets the full country name in the default {@code Locale} for the country code of
* this {@code Locale}. If there is no matching country name, the country code is
* returned.
*
* @return a country name.
*/
public final String getDisplayCountry() {
return getDisplayCountry(getDefault());
}
/**
* Gets the full country name in the specified {@code Locale} for the country code
* of this {@code Locale}. If there is no matching country name, the country code is
* returned.
*
* @param locale
* the {@code Locale} for which the display name is retrieved.
* @return a country name.
*/
public String getDisplayCountry(Locale locale) {
// BEGIN android-changed
if (countryCode.length() == 0) {
return countryCode;
}
String result = Resources.getDisplayCountryNative(toString(), locale.toString());
if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
result = Resources.getDisplayCountryNative(toString(), Locale.getDefault().toString());
}
return result;
// END android-changed
}
/**
* Gets the full language name in the default {@code Locale} for the language code
* of this {@code Locale}. If there is no matching language name, the language code
* is returned.
*
* @return a language name.
*/
public final String getDisplayLanguage() {
return getDisplayLanguage(getDefault());
}
/**
* Gets the full language name in the specified {@code Locale} for the language code
* of this {@code Locale}. If there is no matching language name, the language code
* is returned.
*
* @param locale
* the {@code Locale} for which the display name is retrieved.
* @return a language name.
*/
public String getDisplayLanguage(Locale locale) {
// BEGIN android-changed
if (languageCode.length() == 0) {
return languageCode;
}
String result = Resources.getDisplayLanguageNative(toString(), locale.toString());
if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
result = Resources.getDisplayLanguageNative(toString(), Locale.getDefault().toString());
}
return result;
// END android-changed
}
/**
* Gets the full language, country, and variant names in the default {@code Locale}
* for the codes of this {@code Locale}.
*
* @return a {@code Locale} name.
*/
public final String getDisplayName() {
return getDisplayName(getDefault());
}
/**
* Gets the full language, country, and variant names in the specified
* Locale for the codes of this {@code Locale}.
*
* @param locale
* the {@code Locale} for which the display name is retrieved.
* @return a {@code Locale} name.
*/
public String getDisplayName(Locale locale) {
int count = 0;
StringBuilder buffer = new StringBuilder();
if (languageCode.length() > 0) {
buffer.append(getDisplayLanguage(locale));
count++;
}
if (countryCode.length() > 0) {
if (count == 1) {
buffer.append(" ("); //$NON-NLS-1$
}
buffer.append(getDisplayCountry(locale));
count++;
}
if (variantCode.length() > 0) {
if (count == 1) {
buffer.append(" ("); //$NON-NLS-1$
} else if (count == 2) {
buffer.append(","); //$NON-NLS-1$
}
buffer.append(getDisplayVariant(locale));
count++;
}
if (count > 1) {
buffer.append(")"); //$NON-NLS-1$
}
return buffer.toString();
}
/**
* Gets the full variant name in the default {@code Locale} for the variant code of
* this {@code Locale}. If there is no matching variant name, the variant code is
* returned.
*
* @return a variant name.
*/
public final String getDisplayVariant() {
return getDisplayVariant(getDefault());
}
/**
* Gets the full variant name in the specified {@code Locale} for the variant code
* of this {@code Locale}. If there is no matching variant name, the variant code is
* returned.
*
* @param locale
* the {@code Locale} for which the display name is retrieved.
* @return a variant name.
*/
public String getDisplayVariant(Locale locale) {
// BEGIN android-changed
if (variantCode.length() == 0) {
return variantCode;
}
String result = Resources.getDisplayVariantNative(toString(), locale.toString());
if (result == null) { // TODO: do we need to do this, or does ICU do it for us?
result = Resources.getDisplayVariantNative(toString(), Locale.getDefault().toString());
}
return result;
// END android-changed
}
/**
* Gets the three letter ISO country code which corresponds to the country
* code for this {@code Locale}.
*
* @return a three letter ISO language code.
* @throws MissingResourceException
* if there is no matching three letter ISO country code.
*/
public String getISO3Country() throws MissingResourceException {
// BEGIN android-changed
if (countryCode.length() == 0) {
return countryCode;
}
return Resources.getISO3CountryNative(toString());
// END android-changed
}
/**
* Gets the three letter ISO language code which corresponds to the language
* code for this {@code Locale}.
*
* @return a three letter ISO language code.
* @throws MissingResourceException
* if there is no matching three letter ISO language code.
*/
public String getISO3Language() throws MissingResourceException {
// BEGIN android-changed
if (languageCode.length() == 0) {
return languageCode;
}
return Resources.getISO3LanguageNative(toString());
// END android-changed
}
/**
* Gets the list of two letter ISO country codes which can be used as the
* country code for a {@code Locale}.
*
* @return an array of strings.
*/
public static String[] getISOCountries() {
// BEGIN android-changed
return Resources.getISOCountries();
// END android-changed
}
/**
* Gets the list of two letter ISO language codes which can be used as the
* language code for a {@code Locale}.
*
* @return an array of strings.
*/
public static String[] getISOLanguages() {
// BEGIN android-changed
return Resources.getISOLanguages();
// END android-changed
}
/**
* Gets the language code for this {@code Locale} or the empty string of no language
* was set.
*
* @return a language code.
*/
public String getLanguage() {
return languageCode;
}
/**
* Gets the variant code for this {@code Locale} or an empty {@code String} if no variant
* was set.
*
* @return a variant code.
*/
public String getVariant() {
return variantCode;
}
/**
* Returns an integer hash code for the receiver. Objects which are equal
* return the same value for this method.
*
* @return the receiver's hash.
* @see #equals
*/
@Override
public synchronized int hashCode() {
return countryCode.hashCode() + languageCode.hashCode()
+ variantCode.hashCode();
}
/**
* Sets the default {@code Locale} to the specified {@code Locale}.
*
* @param locale
* the new default {@code Locale}.
* @throws SecurityException
* if there is a {@code SecurityManager} in place which does not allow this
* operation.
*/
public synchronized static void setDefault(Locale locale) {
if (locale != null) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(setLocalePermission);
}
defaultLocale = locale;
} else {
throw new NullPointerException();
}
}
/**
* Returns the string representation of this {@code Locale}. It consists of the
* language code, country code and variant separated by underscores.
* If the language is missing the string begins
* with an underscore. If the country is missing there are 2 underscores
* between the language and the variant. The variant cannot stand alone
* without a language and/or country code: in this case this method would
* return the empty string.
*
* <p>Examples: "en", "en_US", "_US", "en__POSIX", "en_US_POSIX"
*
* @return the string representation of this {@code Locale}.
*/
@Override
public final String toString() {
String result = cachedToStringResult;
return (result == null) ? (cachedToStringResult = toNewString()) : result;
}
private String toNewString() {
// The string form of a locale that only has a variant is the empty string.
if (languageCode.length() == 0 && countryCode.length() == 0) {
return "";
}
// Otherwise, the output format is "ll_cc_variant", where language and country are always
// two letters, but the variant is an arbitrary length. A size of 11 characters has room
// for "en_US_POSIX", the largest "common" value. (In practice, the string form is almost
// always 5 characters: "ll_cc".)
StringBuilder result = new StringBuilder(11);
result.append(languageCode);
if (countryCode.length() > 0 || variantCode.length() > 0) {
result.append('_');
}
result.append(countryCode);
if (variantCode.length() > 0) {
result.append('_');
}
result.append(variantCode);
return result.toString();
}
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("country", String.class), //$NON-NLS-1$
new ObjectStreamField("hashcode", Integer.TYPE), //$NON-NLS-1$
new ObjectStreamField("language", String.class), //$NON-NLS-1$
new ObjectStreamField("variant", String.class) }; //$NON-NLS-1$
private void writeObject(ObjectOutputStream stream) throws IOException {
ObjectOutputStream.PutField fields = stream.putFields();
fields.put("country", countryCode); //$NON-NLS-1$
fields.put("hashcode", -1); //$NON-NLS-1$
fields.put("language", languageCode); //$NON-NLS-1$
fields.put("variant", variantCode); //$NON-NLS-1$
stream.writeFields();
}
private void readObject(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
ObjectInputStream.GetField fields = stream.readFields();
countryCode = (String) fields.get("country", ""); //$NON-NLS-1$//$NON-NLS-2$
languageCode = (String) fields.get("language", ""); //$NON-NLS-1$//$NON-NLS-2$
variantCode = (String) fields.get("variant", ""); //$NON-NLS-1$//$NON-NLS-2$
}
}