blob: 41bf3e323616c183919b5b55fbba4b1ac1f4e794 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed 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 com.ibm.icu4jni.util;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/**
* Makes ICU data accessible to Java.
*
* TODO: finish removing the expensive ResourceBundle nonsense and rename this class.
*/
public class Resources {
// A cache for the locale-specific data.
private static final ConcurrentHashMap<String, LocaleResourceBundle> localeInstanceCache =
new ConcurrentHashMap<String, LocaleResourceBundle>();
/**
* Cache for ISO language names.
*/
private static String[] isoLanguages = null;
/**
* Cache for ISO country names.
*/
private static String[] isoCountries = null;
/**
* Available locales cache.
*/
private static String[] availableLocales = null;
/**
* Available timezones cache.
*/
private static String[] availableTimezones = null;
/**
* Returns a LocaleResourceBundle corresponding to the given locale.
* TODO: return something that allows cheap static field lookup rather than
* expensive chained hash table lookup.
*/
public static ResourceBundle getLocaleInstance(Locale locale) {
if (locale == null) {
locale = Locale.getDefault();
}
String localeName = locale.toString();
LocaleResourceBundle bundle = localeInstanceCache.get(localeName);
if (bundle != null) {
return bundle;
}
bundle = makeLocaleResourceBundle(locale);
localeInstanceCache.put(localeName, bundle);
boolean absent = (localeInstanceCache.putIfAbsent(localeName, bundle) == null);
return absent ? bundle : localeInstanceCache.get(localeName);
}
private static LocaleResourceBundle makeLocaleResourceBundle(Locale locale) {
LocaleResourceBundle result = new LocaleResourceBundle(locale);
// Anything not found in this ResourceBundle should be passed on to
// a parent ResourceBundle corresponding to the next-most-specific locale.
String country = locale.getCountry();
String language = locale.getLanguage();
if (locale.getVariant().length() > 0) {
result.setParent(getLocaleInstance(new Locale(language, country, "")));
} else if (country.length() > 0) {
result.setParent(getLocaleInstance(new Locale(language, "", "")));
} else if (language.length() > 0) {
result.setParent(getLocaleInstance(new Locale("", "", "")));
}
return result;
}
// TODO: fix remaining caller and remove this.
public static ResourceBundle getInstance(String bundleName, String locale) {
if (locale == null) {
locale = Locale.getDefault().toString();
}
if (bundleName.startsWith("Currency")) {
return new CurrencyResourceBundle(locale);
}
throw new AssertionError("bundle="+bundleName+" locale="+locale);
}
/**
* Returns an array of ISO language names (two-letter codes), fetched either
* from ICU's database or from our memory cache.
*
* @return The array.
*/
public static String[] getISOLanguages() {
if (isoLanguages == null) {
isoLanguages = getISOLanguagesNative();
}
return isoLanguages.clone();
}
/**
* Returns an array of ISO country names (two-letter codes), fetched either
* from ICU's database or from our memory cache.
*
* @return The array.
*/
public static String[] getISOCountries() {
if (isoCountries == null) {
isoCountries = getISOCountriesNative();
}
return isoCountries.clone();
}
/**
* Returns an array of names of locales that are available in the system,
* fetched either from ICU's database or from our memory cache.
*
* @return The array.
*/
public static String[] getAvailableLocales() {
if (availableLocales == null) {
availableLocales = getAvailableLocalesNative();
}
return availableLocales.clone();
}
/**
* Returns an array of names of timezones that are available in the system,
* fetched either from the TimeZone class or from our memory cache.
*
* @return The array.
*/
public static String[] getKnownTimezones() {
// TODO Drop the Linux ZoneInfo stuff in favor of ICU.
if (availableTimezones == null) {
availableTimezones = TimeZone.getAvailableIDs();
}
return availableTimezones.clone();
}
/**
* Returns the display name for the given time zone using the given locale.
*
* @param id The time zone ID, for example "Europe/Berlin"
* @param isDST Indicates whether daylight savings is in use
* @param style The style, 0 for long, 1 for short
* @param locale The locale name, for example "en_US".
* @return The desired display name
*/
public static String getDisplayTimeZone(String id, boolean isDST, int style, String locale) {
return getDisplayTimeZoneNative(id, isDST, style, locale);
}
/**
* Initialization holder for default time zone names. This class will
* be preloaded by the zygote to share the time and space costs of setting
* up the list of time zone names, so although it looks like the lazy
* initialization idiom, it's actually the opposite.
*/
private static class DefaultTimeZones {
/**
* Name of default locale at the time this class was initialized.
*/
private static final String locale = Locale.getDefault().toString();
/**
* Names of time zones for the default locale.
*/
private static final String[][] names = createTimeZoneNamesFor(locale);
}
/**
* Creates array of time zone names for the given locale. This method takes
* about 2s to run on a 400MHz ARM11.
*/
private static String[][] createTimeZoneNamesFor(String locale) {
long start = System.currentTimeMillis();
/*
* The following code is optimized for fast native response (the time a
* method call can be in native code is limited). It prepares an empty
* array to keep native code from having to create new Java objects. It
* also fill in the time zone IDs to speed things up a bit. There's one
* array for each time zone name type. (standard/long, standard/short,
* daylight/long, daylight/short) The native method that fetches these
* strings is faster if it can do all entries of one type, before having
* to change to the next type. That's why the array passed down to
* native has 5 entries, each providing space for all time zone names of
* one type. Likely this access to the fields is much faster in the
* native code because there's less array access overhead.
*/
String[][] arrayToFill = new String[5][];
arrayToFill[0] = getKnownTimezones();
arrayToFill[1] = new String[availableTimezones.length];
arrayToFill[2] = new String[availableTimezones.length];
arrayToFill[3] = new String[availableTimezones.length];
arrayToFill[4] = new String[availableTimezones.length];
/*
* Fill in the zone names in native.
*/
getTimeZonesNative(arrayToFill, locale);
/*
* Finally we need to reorder the entries so we get the expected result.
*/
String[][] result = new String[availableTimezones.length][5];
for (int i = 0; i < availableTimezones.length; i++) {
result[i][0] = arrayToFill[0][i];
result[i][1] = arrayToFill[1][i];
result[i][2] = arrayToFill[2][i];
result[i][3] = arrayToFill[3][i];
result[i][4] = arrayToFill[4][i];
}
Logger.getLogger(Resources.class.getSimpleName()).info(
"Loaded time zone names for " + locale + " in "
+ (System.currentTimeMillis() - start) + "ms.");
return result;
}
/**
* Returns the display names for all given timezones using the given locale.
*
* @return An array of time zone strings. Each row represents one time zone.
* The first columns holds the ID of the time zone, for example
* "Europe/Berlin". The other columns then hold for each row the
* four time zone names with and without daylight savings and in
* long and short format. It's exactly the array layout required by
* the TomeZone class.
*/
public static String[][] getDisplayTimeZones(String locale) {
String defaultLocale = Locale.getDefault().toString();
if (locale == null) {
locale = defaultLocale;
}
// If locale == default and the default locale hasn't changed since
// DefaultTimeZones loaded, return the cached names.
// TODO: We should force a reboot if the default locale changes.
if (defaultLocale.equals(locale) && DefaultTimeZones.locale.equals(defaultLocale)) {
return clone2dStringArray(DefaultTimeZones.names);
}
return createTimeZoneNamesFor(locale);
}
private static String[][] clone2dStringArray(String[][] array) {
String[][] result = new String[array.length][];
for (int i = 0; i < array.length; ++i) {
result[i] = array[i].clone();
}
return result;
}
// --- Specialized ResourceBundle subclasses ------------------------------
/**
* Internal ResourceBundle mimicking the Harmony "Currency_*" bundles. Keys
* are the three-letter ISO currency codes. Values are the printable
* currency names in terms of the specified locale. An example entry is
* "USD"->"$" (for inside the US) and "USD->"US$" (for outside the US).
*/
private static final class CurrencyResourceBundle extends ResourceBundle {
private String locale;
public CurrencyResourceBundle(String locale) {
super();
this.locale = locale;
}
@Override
public Enumeration<String> getKeys() {
// Won't get used
throw new UnsupportedOperationException();
}
@Override
protected Object handleGetObject(String key) {
return getCurrencySymbolNative(locale, key);
}
}
/**
* Internal ResourceBundle mimicking the Harmony "Locale_*" bundles.
* The content covers a wide range of
* data items, with values even being arrays in some cases. Note we are
* cheating with the "timezones" entry, since we normally don't want to
* waste our precious RAM on several thousand of these Strings.
*/
private static final class LocaleResourceBundle extends ListResourceBundle {
private final Locale locale;
public LocaleResourceBundle(Locale locale) {
this.locale = locale;
}
// We can't set the superclass' locale field, so we need our own, and our own accessor.
@Override
public Locale getLocale() {
return locale;
}
@Override
protected Object[][] getContents() {
return getContentImpl(locale.toString());
}
// Increase accessibility of this method so we can call it.
@Override
public void setParent(ResourceBundle bundle) {
this.parent = bundle;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("LocaleResourceBundle[locale=");
result.append(getLocale());
result.append(",contents=");
result.append(Arrays.deepToString(getContents()));
return result.toString();
}
}
// --- Native methods accessing ICU's database ----------------------------
public static native String getDisplayCountryNative(String countryCode, String locale);
public static native String getDisplayLanguageNative(String languageCode, String locale);
public static native String getDisplayVariantNative(String variantCode, String locale);
public static native String getISO3CountryNative(String locale);
public static native String getISO3LanguageNative(String locale);
public static native String getCurrencyCodeNative(String locale);
public static native String getCurrencySymbolNative(String locale, String currencyCode);
public static native int getCurrencyFractionDigitsNative(String currencyCode);
private static native String[] getAvailableLocalesNative();
private static native String[] getISOLanguagesNative();
private static native String[] getISOCountriesNative();
private static native void getTimeZonesNative(String[][] arrayToFill, String locale);
private static native String getDisplayTimeZoneNative(String id, boolean isDST, int style,
String locale);
private static native Object[][] getContentImpl(String locale);
}