FileDocCategorySizeDatePackage
Locale.javaAPI DocAndroid 1.5 API32111Wed May 06 22:41:04 BST 2009java.util

Locale.java

/*
 *  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;

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.security.PrivilegedAction;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

// BEGIN android-removed
//import org.apache.harmony.luni.internal.locale.Country;
//import org.apache.harmony.luni.internal.locale.Language;
// END android-removed
import org.apache.harmony.luni.util.PriviAction;
import org.apache.harmony.luni.util.Util;

//BEGIN android-added
import com.ibm.icu4jni.util.Resources;
// END android-added

/**
 * {@code Locale} represents a language/country/variant combination. It is an identifier
 * which dictates particular conventions for the presentation of information.
 * The language codes are two letter lowercase codes as defined by ISO-639. The
 * country codes are three letter uppercase codes as defined by ISO-3166. The
 * variant codes are unspecified.
 * 
 * @see ResourceBundle
 * @since Android 1.0
 */
public final class Locale implements Cloneable, Serializable {
    
    private static final long serialVersionUID = 9149081749638150636L;

    private static Locale[] availableLocales;

    // 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.
     * 
     * @since Android 1.0
     */
    public static final Locale CANADA = new Locale("en", "CA"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for fr_CA.
     * 
     * @since Android 1.0
     */
    public static final Locale CANADA_FRENCH = new Locale("fr", "CA"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for zh_CN.
     * 
     * @since Android 1.0
     */
    public static final Locale CHINA = new Locale("zh", "CN"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for zh.
     * 
     * @since Android 1.0
     */
    public static final Locale CHINESE = new Locale("zh", "");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for en.
     * 
     * @since Android 1.0
     */
    public static final Locale ENGLISH = new Locale("en", ""); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for fr_FR.
     * 
     * @since Android 1.0
     */
    public static final Locale FRANCE = new Locale("fr", "FR");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for fr.
     * 
     * @since Android 1.0
     */
    public static final Locale FRENCH = new Locale("fr", "");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for de.
     * 
     * @since Android 1.0
     */
    public static final Locale GERMAN = new Locale("de", ""); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for de_DE.
     * 
     * @since Android 1.0
     */
    public static final Locale GERMANY = new Locale("de", "DE"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for it.
     * 
     * @since Android 1.0
     */
    public static final Locale ITALIAN = new Locale("it", ""); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for it_IT.
     * 
     * @since Android 1.0
     */
    public static final Locale ITALY = new Locale("it", "IT"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for ja_JP.
     * 
     * @since Android 1.0
     */
    public static final Locale JAPAN = new Locale("ja", "JP");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for ja.
     * 
     * @since Android 1.0
     */
    public static final Locale JAPANESE = new Locale("ja", "");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for ko_KR.
     * 
     * @since Android 1.0
     */
    public static final Locale KOREA = new Locale("ko", "KR");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for ko.
     * 
     * @since Android 1.0
     */
    public static final Locale KOREAN = new Locale("ko", "");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for zh_CN.
     * 
     * @since Android 1.0
     */
    public static final Locale PRC = new Locale("zh", "CN");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for zh_CN.
     * 
     * @since Android 1.0
     */
    public static final Locale SIMPLIFIED_CHINESE = new Locale("zh", "CN");  //$NON-NLS-1$//$NON-NLS-2$

    /**
     * Locale constant for zh_TW.
     * 
     * @since Android 1.0
     */
    public static final Locale TAIWAN = new Locale("zh", "TW"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for zh_TW.
     * 
     * @since Android 1.0
     */
    public static final Locale TRADITIONAL_CHINESE = new Locale("zh", "TW"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for en_GB.
     * 
     * @since Android 1.0
     */
    public static final Locale UK = new Locale("en", "GB"); //$NON-NLS-1$ //$NON-NLS-2$

    /**
     * Locale constant for en_US.
     * 
     * @since Android 1.0
     */
    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;

    /**
     * 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.
     * 
     * @since Android 1.0
     */
    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.
     * @since Android 1.0
     */
    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}.
     * @since Android 1.0
     */
    public Locale(String language, String country, String variant) {
        if (language == null || country == null || variant == null) {
            throw new NullPointerException();
        }
        languageCode = Util.toASCIILowerCase(language);
        // 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
        countryCode = Util.toASCIIUpperCase(country);

        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
     * @since Android 1.0
     */
    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    /**
     * 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
     * @since Android 1.0
     */
    @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-removed
    // static Locale[] find(String prefix) {
    //     int last = prefix.lastIndexOf('/');
    //     final String thePackage = prefix.substring(0, last + 1);
    //     int length = prefix.length();
    //     final String classPrefix = prefix.substring(last + 1, length);
    //     Set<String> result = new HashSet<String>();
    //     StringTokenizer paths = new StringTokenizer(System.getProperty(
    //             // BEGIN android-removed
    //             // "org.apache.harmony.boot.class.path", ""), System.getProperty( //$NON-NLS-1$ //$NON-NLS-2$
    //             // END android-removed
    //             // BEGIN android-added
    //             "java.boot.class.path", ""), System.getProperty( //$NON-NLS-1$ //$NON-NLS-2$
    //             // END android-added
    //             "path.separator", ";"));  //$NON-NLS-1$//$NON-NLS-2$
    //     while (paths.hasMoreTokens()) {
    //         String nextToken = paths.nextToken();
    //         File directory = new File(nextToken);
    //         if (directory.exists()) {
    //             if (directory.isDirectory()) {
    //                 String path;
    //                 try {
    //                     path = directory.getCanonicalPath();
    //                 } catch (IOException e) {
    //                     continue;
    //                 }
    //                 File newDir;
    //                 if (path.charAt(path.length() - 1) == File.separatorChar) {
    //                     newDir = new File(path + thePackage);
    //                 } else {
    //                     newDir = new File(path + File.separatorChar
    //                             + thePackage);
    //                 }
    //                 if (newDir.isDirectory()) {
    //                     String[] list = newDir.list();
    //                     for (int i = 0; i < list.length; i++) {
    //                         String name = list[i];
    //                         if (name.startsWith(classPrefix)
    //                                 && name.endsWith(".class")) { //$NON-NLS-1$
    //                             result.add(name.substring(0,
    //                                             name.length() - 6));
    //                         }
    //                     }
    //                 }
    //                 
    //             } else {
    //                 // Handle ZIP/JAR files.
    //                 try {
    //                     ZipFile zip = new ZipFile(directory);
    //                     Enumeration<? extends ZipEntry> entries = zip.entries();
    //                     while (entries.hasMoreElements()) {
    //                         ZipEntry e = entries.nextElement();
    //                         String name = e.getName();
    //                         if (name.startsWith(prefix)
    //                                 && name.endsWith(".class")) {//$NON-NLS-1$
    //                             result.add(name.substring(last+1, name.length() - 6));
    //                         }
    //                     }
    //                     zip.close();
    //                 } catch (IOException e) {
    //                    // Empty
    //                 }
    //             }
    //         }
    //     }
    //     Locale[] locales = new Locale[result.size()];
    //     int i = 0;
    //     for (String name: result) {
    //         int index = name.indexOf('_');
    //         int nextIndex = name.indexOf('_', index + 1);
    //         if (nextIndex == -1) {
    //             locales[i++] = new Locale(name
    //                     .substring(index + 1, name.length()), ""); //$NON-NLS-1$
    //         }else{
    //             String language = name.substring(index + 1, nextIndex);
    //             String variant;
    //             if ((index = name.indexOf('_', nextIndex + 1)) == -1) {
    //                 variant = ""; //$NON-NLS-1$
    //                 index = name.length();
    //             } else {
    //                 variant = name.substring(index + 1, name.length());
    //             }
    //             String country = name.substring(nextIndex + 1, index);
    //             locales[i++] = new Locale(language, country, variant);
    //         }
    //     }
    //     return locales;
    // }
    // END android-removed

    // 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}. 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.
     * @since Android 1.0
     */
    public static Locale[] getAvailableLocales() {
        if (availableLocales == null) {
            // BEGIN android-removed
            // availableLocales = AccessController
            //         .doPrivileged(new PrivilegedAction<Locale[]>() {
            //             public Locale[] run() {
            //                 return find("org/apache/harmony/luni/internal/locale/Locale_"); //$NON-NLS-1$
            //             }
            //         });
            // END android-removed
            
            // BEGIN android-added
            availableLocales = find();
            // END android-added
        }
        return availableLocales.clone();
    }

    /**
     * Gets the country code for this {@code Locale} or an empty string of no country
     * was set.
     * 
     * @return a country code.
     * @since Android 1.0
     */
    public String getCountry() {
        return countryCode;
    }

    /**
     * Gets the default {@code Locale}.
     * 
     * @return the default {@code Locale}.
     * @since Android 1.0
     */
    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.
     * @since Android 1.0
     */
    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.
     * @since Android 1.0
     */
    public String getDisplayCountry(Locale locale) {
        if (countryCode.length() == 0) {
            return countryCode;
        }
        try {
            // First try the specified locale
            ResourceBundle bundle = getBundle("Country", locale); //$NON-NLS-1$
            // BEGIN android-changed
            String result = bundle.getString(this.toString());
            // END android-changed
            if (result != null) {
                return result;
            }
            // Now use the default locale
            if (locale != Locale.getDefault()) {
                bundle = getBundle("Country", Locale.getDefault()); //$NON-NLS-1$
            }
            return bundle.getString(countryCode);
        } catch (MissingResourceException e) {
            return countryCode;
        }
    }

    /**
     * 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.
     * @since Android 1.0
     */
    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.
     * @since Android 1.0
     */
    public String getDisplayLanguage(Locale locale) {
        if (languageCode.length() == 0) {
            return languageCode;
        }
        try {
            // First try the specified locale
            ResourceBundle bundle = getBundle("Language", locale); //$NON-NLS-1$
            // BEGIN android-changed
            String result = bundle.getString(this.toString());
            // END android-changed
            if (result != null) {
                return result;
            }
            // Now use the default locale
            if (locale != Locale.getDefault()) {
                bundle = getBundle("Language", Locale.getDefault()); //$NON-NLS-1$
            }
            return bundle.getString(languageCode);
        } catch (MissingResourceException e) {
            return languageCode;
        }
    }

    /**
     * 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.
     * @since Android 1.0
     */
    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.
     * @since Android 1.0
     */
    public String getDisplayName(Locale locale) {
        int count = 0;
        StringBuffer buffer = new StringBuffer();
        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.
     * @since Android 1.0
     */
    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.
     * @since Android 1.0
     */
    public String getDisplayVariant(Locale locale) {
        if (variantCode.length() == 0) {
            return variantCode;
        }
// BEGIN android-removed
//         ResourceBundle bundle;
//         try {
//             bundle = getBundle("Variant", locale); //$NON-NLS-1$
//         } catch (MissingResourceException e) {
//             return variantCode.replace('_', ',');
//         }
// 
//         StringBuffer result = new StringBuffer();
//         StringTokenizer tokens = new StringTokenizer(variantCode, "_"); //$NON-NLS-1$
//         while (tokens.hasMoreTokens()) {
//             String code, variant = tokens.nextToken();
//             try {
//                 code = bundle.getString(variant);
//             } catch (MissingResourceException e) {
//                 code = variant;
//             }
//             result.append(code);
//             if (tokens.hasMoreTokens()) {
//                 result.append(',');
//             }
//         }
//         return result.toString();
// END android-removed
// BEGIN android-added
        try {
            // First try the specified locale
            ResourceBundle bundle = getBundle("Variant", locale); //$NON-NLS-1$
            String result = bundle.getString(this.toString());
            if (result != null) {
                return result;
            }
            // Now use the default locale
            if (locale != Locale.getDefault()) {
                bundle = getBundle("Variant", Locale.getDefault()); //$NON-NLS-1$
            }
            return bundle.getString(variantCode);
        } catch (MissingResourceException e) {
            return variantCode;
        }
// END android-added
    }

    /**
     * Gets the three letter ISO country code which corresponds to the country
     * code for this {@code Locale}.
     * 
     * @return a three letter ISO language code.
     * @exception MissingResourceException
     *                when there is no matching three letter ISO country code.
     * @since Android 1.0
     */
    public String getISO3Country() throws MissingResourceException {
        if (countryCode.length() == 0) {
            return ""; //$NON-NLS-1$
        }
        ResourceBundle bundle = getBundle("ISO3Countries", this); //$NON-NLS-1$
        // BEGIN android-changed
        return bundle.getString(this.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.
     * @exception MissingResourceException
     *                when there is no matching three letter ISO language code.
     * @since Android 1.0
     */
    public String getISO3Language() throws MissingResourceException {
        if (languageCode.length() == 0) {
            return ""; //$NON-NLS-1$
        }
        ResourceBundle bundle = getBundle("ISO3Languages", this); //$NON-NLS-1$
        // BEGIN android-changed
        return bundle.getString(this.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.
     * @since Android 1.0
     */
    public static String[] getISOCountries() {
        // BEGIN android-removed
        // ListResourceBundle bundle = new Country();
        // 
        // // To initialize the table
        // Enumeration<String> keys = bundle.getKeys();
        // int size = bundle.table.size();
        // String[] result = new String[size];
        // int index = 0;
        // while (keys.hasMoreElements()) {
        //     String element = keys.nextElement();
        //     result[index++] = element;
        // }
        // return result;
        // END android-removed
        
        // BEGIN android-added
        return Resources.getISOCountries();
        // END android-added
    }

    /**
     * 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.
     * @since Android 1.0
     */
    public static String[] getISOLanguages() {
        // BEGIN android-removed
        // ListResourceBundle bundle = new Language();
        // Enumeration<String> keys = bundle.getKeys(); // to initialize the table
        // String[] result = new String[bundle.table.size()];
        // int index = 0;
        // while (keys.hasMoreElements()) {
        //     result[index++] = keys.nextElement();
        // }
        // return result;
        // END android-removed
        
        // BEGIN android-added
        return Resources.getISOLanguages();
        // END android-added
    }

    /**
     * Gets the language code for this {@code Locale} or the empty string of no language
     * was set.
     * 
     * @return a language code.
     * @since Android 1.0
     */
    public String getLanguage() {
        return languageCode;
    }

    /**
     * Gets the variant code for this {@code Locale} or an empty {@code String} of no variant
     * was set.
     * 
     * @return a variant code.
     * @since Android 1.0
     */
    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
     * @since Android 1.0
     */
    @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}.
     * @exception SecurityException
     *                if there is a {@code SecurityManager} in place which does not allow this
     *                operation.
     * @since Android 1.0
     */
    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 followed by the country and at the end the variant. They are
     * 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 alone canot be defined
     * without a language and/or a country (in this case this method would
     * return the empty string).
     * 
     * Examples: "en", "en_US", "_US", "en__POSIX", "en_US_POSIX"
     * 
     * @return the string representation of this {@code Locale}.
     * @since Android 1.0
     */
    @Override
    public final String toString() {
        StringBuilder result = new StringBuilder();
        result.append(languageCode);
        if (countryCode.length() > 0) {
            result.append('_');
            result.append(countryCode);
        }
        if (variantCode.length() > 0 && result.length() > 0 ) {
            if (0 == countryCode.length()) {
                result.append("__"); //$NON-NLS-1$
            } else {
                result.append('_');
            }
            result.append(variantCode);
        }
        return result.toString();
    }

    static ResourceBundle getBundle(final String clName, final Locale locale) {
        return AccessController.doPrivileged(new PrivilegedAction<ResourceBundle>() {
                    public ResourceBundle run() {
                        return ResourceBundle.getBundle("org.apache.harmony.luni.internal.locale." //$NON-NLS-1$
                                + clName, locale);
                    }
                });
    }

    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$
    }
}