summaryrefslogtreecommitdiffstats
path: root/icu/src/main/java/com
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2009-12-21 10:52:53 -0800
committerElliott Hughes <enh@google.com>2009-12-21 15:02:47 -0800
commit2e3a41defb42a97b463194d859d2d4088a600fd8 (patch)
tree67a89bce7f2450e9125b96074e0848a71ab4f451 /icu/src/main/java/com
parent011496fc0d73dc55c452eb1d7b60fe3c73885921 (diff)
downloadlibcore-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.java8
-rw-r--r--icu/src/main/java/com/ibm/icu4jni/util/Resources.java341
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);
}