diff options
author | Elliott Hughes <enh@google.com> | 2009-12-21 10:52:53 -0800 |
---|---|---|
committer | Elliott Hughes <enh@google.com> | 2009-12-21 15:02:47 -0800 |
commit | 2e3a41defb42a97b463194d859d2d4088a600fd8 (patch) | |
tree | 67a89bce7f2450e9125b96074e0848a71ab4f451 /icu/src/main/java/com | |
parent | 011496fc0d73dc55c452eb1d7b60fe3c73885921 (diff) | |
download | libcore-2e3a41defb42a97b463194d859d2d4088a600fd8.zip libcore-2e3a41defb42a97b463194d859d2d4088a600fd8.tar.gz libcore-2e3a41defb42a97b463194d859d2d4088a600fd8.tar.bz2 |
Speed up the way we access ICU's locale data.
This patch makes creating a new NumberFormat or new SimpleDateFormat 2x faster.
Basically, the ResourceBundle mechanism is really expensive in several ways:
1. The two-level caching is unnecessary for locale data, and expensive because
it burns through a lot of temporary objects.
2. The PrivilegedAction stuff is unnecessary and expensive because it too burns
quite a few temporary objects (including an ArrayList for each call; should
we consider removing support for SecurityManager so we can remove this cruft
from our code?).
3. The caching in most cases doesn't cache anything useful; the ResourceBundles
simply forward all questions straight to native code anyway, all we're
caching is an unnecessary forwarding object (in a cache where lookups cost
more than just creating a new unnecessary forwarding object would cost).
I've left CurrencyResourceBundle on the slow (ResourceBundle.getBundle) path
because I'm not yet sure how much of that path's semantics it relies on.
I still return LocaleResourceBundle instances (albeit via a much faster path)
but we should fix that. The native code returns an array which ResourceBundle
stuffs into a Hashtable and the calling code accesses via hash table lookups.
This despite the fact that the keys are a small fixed set known in advance.
We could make the native layer and the calling layer simpler and faster by
using a "struct", and doing so would make the middle layer go away completely.
Diffstat (limited to 'icu/src/main/java/com')
-rw-r--r-- | icu/src/main/java/com/ibm/icu4jni/text/DecimalFormatSymbols.java | 8 | ||||
-rw-r--r-- | icu/src/main/java/com/ibm/icu4jni/util/Resources.java | 341 |
2 files changed, 89 insertions, 260 deletions
diff --git a/icu/src/main/java/com/ibm/icu4jni/text/DecimalFormatSymbols.java b/icu/src/main/java/com/ibm/icu4jni/text/DecimalFormatSymbols.java index 533c674..0ceb287 100644 --- a/icu/src/main/java/com/ibm/icu4jni/text/DecimalFormatSymbols.java +++ b/icu/src/main/java/com/ibm/icu4jni/text/DecimalFormatSymbols.java @@ -37,13 +37,7 @@ public class DecimalFormatSymbols implements Cloneable { public DecimalFormatSymbols(Locale locale) { this.loc = locale; - ResourceBundle bundle = AccessController. - doPrivileged(new PrivilegedAction<ResourceBundle>() { - public ResourceBundle run() { - return ResourceBundle.getBundle( - "org.apache.harmony.luni.internal.locale.Locale", loc); //$NON-NLS-1$ - } - }); + ResourceBundle bundle = com.ibm.icu4jni.util.Resources.getLocaleInstance(locale); String pattern = bundle.getString("Number"); this.addr = NativeDecimalFormat.openDecimalFormatImpl( locale.toString(), pattern); diff --git a/icu/src/main/java/com/ibm/icu4jni/util/Resources.java b/icu/src/main/java/com/ibm/icu4jni/util/Resources.java index 7eeae7d..41bf3e3 100644 --- a/icu/src/main/java/com/ibm/icu4jni/util/Resources.java +++ b/icu/src/main/java/com/ibm/icu4jni/util/Resources.java @@ -16,34 +16,25 @@ 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; /** - * Helper class that delivers ResourceBundle instances expected by Harmony, but - * with the data taken from ICU's database. This approach has a couple of - * advantages: - * <ol> - * <li> We have less classes in the overall system, since we use different - * instances for different ResourceBundles. - * <li> We don't have these classes that consists of monstrous static arrays - * with anymore. - * <li> We have control over which values we load at which time or even cache - * for later use. - * <li> There is only one central place left in the system where I18N data needs - * to be configured, namely ICU. - * </ol> - * Since we're mimicking the original Harmony ResourceBundle structures, most of - * the Harmony code can stay the same. We basically just need to change the - * ResourceBundle instantiation. Only the special case of the Locale bundles - * needs some more tweaking, since we don't want to keep several hundred - * timezone names in memory. + * 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. @@ -66,41 +57,52 @@ public class Resources { private static String[] availableTimezones = null; /** - * Creates ResourceBundle instance and fills it with ICU data. - * - * @param bundleName The name of the requested Harmony resource bundle, - * excluding the package name. - * @param locale The locale to use for the resources. A null value denotes - * the default locale as configured in Java. - * @return The new ResourceBundle, or null, if no ResourceBundle was - * created. + * 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 getInstance(String bundleName, String locale) { + public static ResourceBundle getLocaleInstance(Locale locale) { if (locale == null) { - locale = java.util.Locale.getDefault().toString(); + 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); + } - if (bundleName.startsWith("Locale")) { - return new Locale(locale); - } else if (bundleName.startsWith("Country")) { - return new Country(locale); - } else if (bundleName.startsWith("Currency")) { - return new Currency(locale); - } else if (bundleName.startsWith("Language")) { - return new Language(locale); - } else if (bundleName.startsWith("Variant")) { - return new Variant(locale); - } else if (bundleName.equals("ISO3Countries")) { - return new ISO3Countries(); - } else if (bundleName.equals("ISO3Languages")) { - return new ISO3Languages(); - } else if (bundleName.equals("ISO4CurrenciesToDigits")) { - return new ISO4CurrenciesToDigits(); - } else if (bundleName.equals("ISO4Currencies")) { - return new ISO4Currencies(); + 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; + } - return null; + // 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); } /** @@ -174,13 +176,6 @@ public class Resources { } /** - * Gets the name of the default locale. - */ - private static String getDefaultLocaleName() { - return java.util.Locale.getDefault().toString(); - } - - /** * 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 @@ -190,7 +185,7 @@ public class Resources { /** * Name of default locale at the time this class was initialized. */ - private static final String locale = getDefaultLocaleName(); + private static final String locale = Locale.getDefault().toString(); /** * Names of time zones for the default locale. @@ -260,7 +255,7 @@ public class Resources { * the TomeZone class. */ public static String[][] getDisplayTimeZones(String locale) { - String defaultLocale = getDefaultLocaleName(); + String defaultLocale = Locale.getDefault().toString(); if (locale == null) { locale = defaultLocale; } @@ -286,135 +281,16 @@ public class Resources { // --- Specialized ResourceBundle subclasses ------------------------------ /** - * Internal ResourceBundle mimicking the Harmony "ISO3Countries" bundle. - * Keys are the two-letter ISO country codes. Values are the three-letter - * ISO country abbreviations. An example entry is "US"->"USA". - */ - private static final class ISO3Countries extends ResourceBundle { - - @Override - public Enumeration<String> getKeys() { - // Won't get used - throw new UnsupportedOperationException(); - } - - @Override - protected Object handleGetObject(String key) { - return getISO3CountryNative(key); - } - - } - - /** - * Internal ResourceBundle mimicking the Harmony "ISO3Languages" bundle. - * Keys are the two-letter ISO language codes. Values are the three-letter - * ISO language abbreviations. An example entry is "EN"->"ENG". - */ - private static final class ISO3Languages extends ResourceBundle { - - @Override - public Enumeration<String> getKeys() { - // Won't get used - throw new UnsupportedOperationException(); - } - - @Override - protected Object handleGetObject(String key) { - return getISO3LanguageNative(key); - } - - } - - /** - * Internal ResourceBundle mimicking the Harmony "ISO4Currencies" bundle. - * Keys are the two-letter ISO language codes. Values are the three-letter - * ISO currency abbreviations. An example entry is "US"->"USD". - */ - private static final class ISO4Currencies extends ResourceBundle { - - @Override - public Enumeration<String> getKeys() { - // Won't get used - throw new UnsupportedOperationException(); - } - - @Override - protected Object handleGetObject(String key) { - return getCurrencyCodeNative(key); - } - - } - - /** - * Internal ResourceBundle mimicking the Harmony "ISO4CurrenciesToDigits" - * bundle. Keys are the three-letter ISO currency codes. Values are strings - * containing the number of fraction digits to use for the currency. An - * example entry is "USD"->"2". - */ - private static final class ISO4CurrenciesToDigits extends ResourceBundle { - - @Override - public Enumeration<String> getKeys() { - // Won't get used - throw new UnsupportedOperationException(); - } - - @Override - protected Object handleGetObject(String key) { - // In some places the triple-x code is used as the fall back - // currency. The harmony package returned -1 for this requested - // currency. - if ("XXX".equals(key)) { - return "-1"; - } - int res = getFractionDigitsNative(key); - if (res < 0) { - throw new MissingResourceException("couldn't find resource.", - ISO4CurrenciesToDigits.class.getName(), key); - } - return Integer.toString(res); - } - - } - - /** - * Internal ResourceBundle mimicking the Harmony "Country_*" bundles. Keys - * are the two-letter ISO country codes. Values are the printable country - * names in terms of the specified locale. An example entry is "US"->"United - * States". - */ - private static final class Country extends ResourceBundle { - private String locale; - - public Country(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 getDisplayCountryNative(key, locale); - } - - } - - /** * 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 Currency extends ResourceBundle { + private static final class CurrencyResourceBundle extends ResourceBundle { private String locale; - public Currency(String locale) { + public CurrencyResourceBundle(String locale) { super(); this.locale = locale; } @@ -433,105 +309,64 @@ public class Resources { } /** - * Internal ResourceBundle mimicking the Harmony "Language_*" bundles. Keys - * are the two-letter ISO language codes. Values are the printable language - * names in terms of the specified locale. An example entry is - * "en"->"English". + * 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 Language extends ResourceBundle { - private String locale; - - public Language(String locale) { - super(); + private static final class LocaleResourceBundle extends ListResourceBundle { + private final Locale locale; + + public LocaleResourceBundle(Locale locale) { this.locale = locale; } - - @Override - public Enumeration<String> getKeys() { - // Won't get used - throw new UnsupportedOperationException(); - } - + + // We can't set the superclass' locale field, so we need our own, and our own accessor. @Override - protected Object handleGetObject(String key) { - return getDisplayLanguageNative(key, locale); - } - - } - - /** - * Internal ResourceBundle mimicking the Harmony "Variant_*" bundles. Keys - * are a fixed set of variants codes known to Harmony. Values are the - * printable variant names in terms of the specified locale. An example - * entry is "EURO"->"Euro". - */ - private static final class Variant extends ResourceBundle { - - private String locale; - - public Variant(String locale) { - super(); - this.locale = locale; + public Locale getLocale() { + return locale; } @Override - public Enumeration<String> getKeys() { - // Won't get used - throw new UnsupportedOperationException(); + protected Object[][] getContents() { + return getContentImpl(locale.toString()); } + // Increase accessibility of this method so we can call it. @Override - protected Object handleGetObject(String key) { - return getDisplayVariantNative(key, locale); - } - - } - - /** - * Internal ResourceBundle mimicking the Harmony "Locale_*" bundles. This is - * clearly the most complex case, because 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 Locale extends ListResourceBundle { - - private String locale; - - public Locale(String locale) { - super(); - this.locale = locale; + public void setParent(ResourceBundle bundle) { + this.parent = bundle; } - + @Override - protected Object[][] getContents() { - return getContentImpl(locale, false); + 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 ---------------------------- - private static native int getFractionDigitsNative(String currencyCode); - - private static native String getCurrencyCodeNative(String locale); + 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); - private static native String getCurrencySymbolNative(String locale, String currencyCode); + public static native String getISO3CountryNative(String locale); + public static native String getISO3LanguageNative(String locale); - private static native String getDisplayCountryNative(String countryCode, String locale); + public static native String getCurrencyCodeNative(String locale); + public static native String getCurrencySymbolNative(String locale, String currencyCode); - private static native String getDisplayLanguageNative(String languageCode, String locale); - - private static native String getDisplayVariantNative(String variantCode, String locale); - - private static native String getISO3CountryNative(String locale); - - private static native String getISO3LanguageNative(String locale); + 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); @@ -539,5 +374,5 @@ public class Resources { private static native String getDisplayTimeZoneNative(String id, boolean isDST, int style, String locale); - private static native Object[][] getContentImpl(String locale, boolean needsTimeZones); + private static native Object[][] getContentImpl(String locale); } |