diff options
Diffstat (limited to 'libjava/java/util/ResourceBundle.java')
-rw-r--r-- | libjava/java/util/ResourceBundle.java | 681 |
1 files changed, 376 insertions, 305 deletions
diff --git a/libjava/java/util/ResourceBundle.java b/libjava/java/util/ResourceBundle.java index 895c887..150a01b 100644 --- a/libjava/java/util/ResourceBundle.java +++ b/libjava/java/util/ResourceBundle.java @@ -1,5 +1,5 @@ -/* java.util.ResourceBundle - Copyright (C) 1998, 1999, 2001 Free Software Foundation, Inc. +/* ResourceBundle -- aids in loading resource bundles + Copyright (C) 1998, 1999, 2001, 2002 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -37,93 +37,106 @@ exception statement from your version. */ package java.util; + import java.lang.ref.Reference; import java.lang.ref.SoftReference; +import java.io.InputStream; +import java.io.IOException; import java.security.AccessController; import java.security.PrivilegedAction; import gnu.classpath.Configuration; /** - * A resource bundle contains locale-specific data. If you need - * localized data, you can load a resource bundle that matches the - * locale with <code>getBundle</code>. Now you can get your object by - * calling <code>getObject</code> or <code>getString</code> on that - * bundle. - * <br> - * When a bundle is demanded for a specific locale, the ResourceBundle - * is searched in following order (<i>def. language code<i> stands for - * the two letter ISO language code of the default locale (see + * A resource bundle contains locale-specific data. If you need localized + * data, you can load a resource bundle that matches the locale with + * <code>getBundle</code>. Now you can get your object by calling + * <code>getObject</code> or <code>getString</code> on that bundle. + * + * <p>When a bundle is demanded for a specific locale, the ResourceBundle + * is searched in following order (<i>def. language<i> stands for the + * two letter ISO language code of the default locale (see * <code>Locale.getDefault()</code>). - * <pre> - * baseName_<i>language code</i>_<i>country code</i>_<i>variant</i> - * baseName_<i>language code</i>_<i>country code</i> - * baseName_<i>language code</i> - * baseName_<i>def. language code</i>_<i>def. country code</i>_<i>def. variant</i> - * baseName_<i>def. language code</i>_<i>def. country code</i> - * baseName_<i>def. language code</i> - * baseName - * </pre> * - * A bundle is backed up, by less specific bundle (omiting variant, - * country or language). But it is not backed up by the default - * language locale. - * <br> - * If you provide a bundle for a given locale, say +<pre>baseName_<i>language code</i>_<i>country code</i>_<i>variant</i> +baseName_<i>language code</i>_<i>country code</i> +baseName_<i>language code</i> +baseName_<i>def. language</i>_<i>def. country</i>_<i>def. variant</i> +baseName_<i>def. language</i>_<i>def. country</i> +baseName_<i>def. language</i> +baseName</pre> + * + * <p>A bundle is backed up by less specific bundles (omiting variant, country + * or language). But it is not backed up by the default language locale. + * + * <p>If you provide a bundle for a given locale, say * <code>Bundle_en_UK_POSIX</code>, you must also provide a bundle for * all sub locales, ie. <code>Bundle_en_UK</code>, <code>Bundle_en</code>, and * <code>Bundle</code>. - * <br> - * When a bundle is searched, we look first for a class with - * the given name and if that is not found for a file with - * <code>.properties</code> extension in the classpath. The name - * must be a fully qualified classname (with dots as path separators). - * <br> - * (Note: This implementation always backs up the class with a - * properties file if that is existing, but you shouldn't rely on - * this, if you want to be compatible to the standard JDK.) * + * <p>When a bundle is searched, we look first for a class with the given + * name, then for a file with <code>.properties</code> extension in the + * classpath. The name must be a fully qualified classname (with dots as + * path separators). + * + * <p>(Note: This implementation always backs up the class with a properties + * file if that is existing, but you shouldn't rely on this, if you want to + * be compatible to the standard JDK.) + * + * @author Jochen Hoenicke + * @author Eric Blake (ebb9@email.byu.edu) * @see Locale + * @see ListResourceBundle * @see PropertyResourceBundle - * @author Jochen Hoenicke */ + * @since 1.1 + * @status updated to 1.4 + */ public abstract class ResourceBundle { /** - * The parent bundle. This is consulted when you call getObject - * and there is no such resource in the current bundle. This - * field may be null. + * The parent bundle. This is consulted when you call getObject and there + * is no such resource in the current bundle. This field may be null. */ protected ResourceBundle parent; /** - * The locale of this resource bundle. You can read this with + * The locale of this resource bundle. You can read this with * <code>getLocale</code> and it is automatically set in - * <code>getBundle</code>. + * <code>getBundle</code>. */ private Locale locale; /** - * We override SecurityManager in order to access getClassContext(). + * We override SecurityManager in order to access getClassContext(). */ - static class Security extends SecurityManager + private static final class Security extends SecurityManager { - /** Return the ClassLoader of the class which called into this - ResourceBundle, or null if it cannot be determined. */ + /** + * Avoid accessor method of private constructor. + */ + Security() + { + } + + /** + * Return the ClassLoader of the class which called into this + * ResourceBundle, or null if it cannot be determined. + */ ClassLoader getCallingClassLoader() { - Class[] stack = super.getClassContext(); + Class[] stack = getClassContext(); for (int i = 0; i < stack.length; i++) if (stack[i] != Security.class && stack[i] != ResourceBundle.class) - return stack[i].getClassLoader(); + return stack[i].getClassLoader(); return null; } } - - // This will always work since java.util classes have (all) system - // permissions. - static Security security = (Security) AccessController.doPrivileged - ( - new PrivilegedAction() + + /** A security context for grabbing the correct class loader. */ + private static final Security security + = (Security) AccessController.doPrivileged(new PrivilegedAction() { + // This will always work since java.util classes have (all) system + // permissions. public Object run() { return new Security(); @@ -132,343 +145,401 @@ public abstract class ResourceBundle ); /** - * The constructor. It does nothing special. + * The resource bundle cache. This is a two-level hash map: The key + * is the class loader, the value is a new HashMap. The key of this + * second hash map is the localized name, the value is a soft + * references to the resource bundle. + */ + private static final Map resourceBundleCache = new HashMap(); + + /** + * The `empty' locale is created once in order to optimize + * tryBundle(). + */ + private static final Locale emptyLocale = new Locale(""); + + /** + * The constructor. It does nothing special. */ public ResourceBundle() { } /** - * Get a String from this resource bundle. Since most localized - * Objects are Strings, this method provides a convenient way to get - * them without casting. - * @param key the name of the resource. - * @exception MissingResourceException - * if that particular object could not be found in this bundle nor - * the parent bundle. + * Get a String from this resource bundle. Since most localized Objects + * are Strings, this method provides a convenient way to get them without + * casting. + * + * @param key the name of the resource + * @throws MissingResourceException if the resource can't be found + * @throws NullPointerException if key is null + * @throws ClassCastException if resource is not a string */ - public final String getString(String key) throws MissingResourceException + public final String getString(String key) { return (String) getObject(key); } /** - * Get an array of Strings from this resource bundle. This method + * Get an array of Strings from this resource bundle. This method * provides a convenient way to get it without casting. - * @param key the name of the resource. - * @exception MissingResourceException - * if that particular object could not be found in this bundle nor - * the parent bundle. + * + * @param key the name of the resource + * @throws MissingResourceException if the resource can't be found + * @throws NullPointerException if key is null + * @throws ClassCastException if resource is not a string */ public final String[] getStringArray(String key) - throws MissingResourceException { return (String[]) getObject(key); } /** - * Get an object from this resource bundle. - * @param key the name of the resource. - * @exception MissingResourceException - * if that particular object could not be found in this bundle nor - * the parent bundle. + * Get an object from this resource bundle. This will call + * <code>handleGetObject</code> for this resource and all of its parents, + * until it finds a non-null resource. + * + * @param key the name of the resource + * @throws MissingResourceException if the resource can't be found + * @throws NullPointerException if key is null */ - public final Object getObject(String key) throws MissingResourceException + public final Object getObject(String key) { for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent) - { - try - { - Object o = bundle.handleGetObject(key); - if (o != null) - return o; - } - catch (MissingResourceException ex) - { - } - } - throw new MissingResourceException - ("Key not found", getClass().getName(), key); + try + { + Object o = bundle.handleGetObject(key); + if (o != null) + return o; + } + catch (MissingResourceException ex) + { + } + throw new MissingResourceException("Key not found", + getClass().getName(), key); } /** - * Get the appropriate ResourceBundle for the default locale. - * @param baseName the name of the ResourceBundle. This should be - * a name of a Class or a properties-File. See the class - * description for details. - * @return the desired resource bundle - * @exception MissingResourceException - * if the resource bundle couldn't be found. + * Return the actual locale of this bundle. You can use it after calling + * getBundle, to know if the bundle for the desired locale was loaded or + * if the fall back was used. + * + * @return the bundle's locale */ - public static final ResourceBundle getBundle(String baseName) - throws MissingResourceException + public Locale getLocale() { - return getBundle(baseName, Locale.getDefault(), - security.getCallingClassLoader()); + return locale; } /** - * Get the appropriate ResourceBundle for the given locale. - * @param baseName the name of the ResourceBundle. This should be - * a name of a Class or a properties-File. See the class - * description for details. - * @param locale A locale. - * @return the desired resource bundle - * @exception MissingResourceException - * if the resource bundle couldn't be found. + * Set the parent of this bundle. The parent is consulted when you call + * getObject and there is no such resource in the current bundle. + * + * @param parent the parent of this bundle */ - public static final ResourceBundle getBundle(String baseName, - Locale locale) - throws MissingResourceException + protected void setParent(ResourceBundle parent) { - return getBundle(baseName, locale, security.getCallingClassLoader()); + // Shall we ignore the old parent? + this.parent = parent; } /** - * The resource bundle cache. This is a two-level hash map: The key - * is the class loader, the value is a new HashMap. The key of this - * second hash map is the localized name, the value is a soft - * references to the resource bundle. */ - private static Map resourceBundleCache = new HashMap(); - - /** - * The `empty' locale is created once in order to optimize - * tryBundle(). - */ - private static final Locale emptyLocale = new Locale ("", ""); - - /** - * Tries to load a class or a property file with the specified name. - * @param localizedName the name. - * @param locale the locale, that must be used exactly. - * @param classloader the classloader. - * @param bundle the back up (parent) bundle - * @return the resource bundle if that could be loaded, otherwise - * <code>bundle</code>. + * Get the appropriate ResourceBundle for the default locale. This is like + * calling <code>getBundle(baseName, Locale.getDefault(), + * getClass().getClassLoader()</code>, except that any security check of + * getClassLoader won't fail. + * + * @param baseName the name of the ResourceBundle + * @return the desired resource bundle + * @throws MissingResourceException if the resource bundle can't be found + * @throws NullPointerException if baseName is null */ - private static final ResourceBundle tryBundle(String localizedName, - Locale locale, - ClassLoader classloader, - ResourceBundle bundle, - HashMap cache) + public static final ResourceBundle getBundle(String baseName) { - { - // First look into the cache. - // XXX We should remove cleared references from the cache. - Reference ref = (Reference) cache.get(localizedName); - if (ref != null) - { - ResourceBundle rb = (ResourceBundle) ref.get(); - if (rb != null) - // rb should already have the right parent, except if - // something very strange happened. - return rb; - } - } - - // foundBundle holds exact matches for the localizedName resource - // bundle, which may later be cached. - ResourceBundle foundBundle = null; - - try - { - java.io.InputStream is; - final String resourceName = - localizedName.replace('.', '/') + ".properties"; - if (classloader == null) - is = ClassLoader.getSystemResourceAsStream (resourceName); - else - is = classloader.getResourceAsStream (resourceName); - if (is != null) - { - foundBundle = new PropertyResourceBundle(is); - foundBundle.parent = bundle; - foundBundle.locale = locale; - } - } - catch (java.io.IOException ex) - { - } - - try - { - Class rbClass; - if (classloader == null) - rbClass = Class.forName(localizedName); - else - rbClass = classloader.loadClass(localizedName); - foundBundle = (ResourceBundle) rbClass.newInstance(); - foundBundle.parent = bundle; - foundBundle.locale = locale; - } - catch (ClassNotFoundException ex) - { - } - catch (IllegalAccessException ex) - { - } - catch (InstantiationException ex) - { - // ignore them all - // XXX should we also ignore ClassCastException? - } - - if (foundBundle != null) - cache.put(localizedName, new SoftReference(foundBundle)); - - return foundBundle != null ? foundBundle : bundle; + return getBundle(baseName, Locale.getDefault(), + security.getCallingClassLoader()); } /** - * Tries to load a the bundle for a given locale, also loads the backup - * locales with the same language. + * Get the appropriate ResourceBundle for the given locale. This is like + * calling <code>getBundle(baseName, locale, + * getClass().getClassLoader()</code>, except that any security check of + * getClassLoader won't fail. * - * @param name the name. - * @param locale the locale, that must be used exactly. - * @param classloader the classloader. - * @param bundle the back up (parent) bundle - * @return the resource bundle if that could be loaded, otherwise - * <code>bundle</code>. + * @param baseName the name of the ResourceBundle + * @param locale A locale + * @return the desired resource bundle + * @throws MissingResourceException if the resource bundle can't be found + * @throws NullPointerException if baseName or locale is null */ - private static final ResourceBundle tryLocalBundle(String baseName, - Locale locale, - ClassLoader classloader, - ResourceBundle bundle, - HashMap cache) + public static final ResourceBundle getBundle(String baseName, + Locale locale) { - final String language = locale.getLanguage(); - - if (language.length() > 0) - { - final String country = locale.getCountry(); - String name = baseName + "_" + language; - - if (country.length() != 0) - { - bundle = tryBundle(name, - new Locale(language, ""), - classloader, bundle, cache); - - name += "_" + country; - - final String variant = locale.getVariant(); - - if (variant.length() != 0) - { - bundle = tryBundle(name, - new Locale(language, - country), - classloader, bundle, cache); - - name += "_" + variant; - } - } - bundle = tryBundle(name, locale, classloader, bundle, cache); - } - return bundle; + return getBundle(baseName, locale, security.getCallingClassLoader()); } /** - * Get the appropriate ResourceBundle for the given locale. - * @param baseName the name of the ResourceBundle. This should be - * a name of a Class or a properties file. See the class - * description for details. - * @param locale A locale. - * @param classloader a ClassLoader. + * Get the appropriate ResourceBundle for the given locale. The following + * strategy is used: + * + * <p>A sequence of candidate bundle names are generated, and tested in + * this order, where the suffix 1 means the string from the specified + * locale, and the suffix 2 means the string from the default locale:<ul> + * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li> + * <li>baseName + "_" + language1 + "_" + country1</li> + * <li>baseName + "_" + language1</li> + * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li> + * <li>baseName + "_" + language2 + "_" + country2</li> + * <li>baseName + "_" + language2<li> + * <li>baseName</li> + * </ul> + * + * <p>In the sequence, entries with an empty string are ignored. Next, + * <code>getBundle</code> tries to instantiate the resource bundle:<ul> + * <li>First, an attempt is made to load a class in the specified classloader + * which is a subclass of ResourceBundle, and which has a public constructor + * with no arguments, via reflection.</li> + * <li>Next, a search is made for a property resource file, by replacing + * '.' with '/' and appending ".properties", and using + * ClassLoader.getResource(). If a file is found, then a + * PropertyResourceBundle is created from the file's contents.</li> + * </ul> + * If no resource bundle was found, a MissingResourceException is thrown. + * + * <p>Next, the parent chain is implemented. The remaining candidate names + * in the above sequence are tested in a similar manner, and if any results + * in a resource bundle, it is assigned as the parent of the first bundle + * using the <code>setParent</code> method (unless the first bundle already + * has a parent). + * + * <p>For example, suppose the following class and property files are + * provided: MyResources.class, MyResources_fr_CH.properties, + * MyResources_fr_CH.class, MyResources_fr.properties, + * MyResources_en.properties, and MyResources_es_ES.class. The contents of + * all files are valid (that is, public non-abstract subclasses of + * ResourceBundle with public nullary constructors for the ".class" files, + * syntactically correct ".properties" files). The default locale is + * Locale("en", "UK"). + * + * <p>Calling getBundle with the shown locale argument values instantiates + * resource bundles from the following sources:<ul> + * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent + * MyResources_fr.properties, parent MyResources.class</li> + * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent + * MyResources.class</li> + * <li>Locale("de", "DE"): result MyResources_en.properties, parent + * MyResources.class</li> + * <li>Locale("en", "US"): result MyResources_en.properties, parent + * MyResources.class</li> + * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent + * MyResources.class</li> + * </ul> + * The file MyResources_fr_CH.properties is never used because it is hidden + * by MyResources_fr_CH.class. + * + * @param baseName the name of the ResourceBundle + * @param locale A locale + * @param classloader a ClassLoader * @return the desired resource bundle - * @exception MissingResourceException - * if the resource bundle couldn't be found. + * @throws MissingResourceException if the resource bundle can't be found + * @throws NullPointerException if any argument is null + * @since 1.2 */ // This method is synchronized so that the cache is properly // handled. - public static final synchronized ResourceBundle getBundle(String baseName, - Locale locale, - ClassLoader classLoader) - throws MissingResourceException + public static final synchronized ResourceBundle getBundle + (String baseName, Locale locale, ClassLoader classLoader) { // This implementation searches the bundle in the reverse direction // and builds the parent chain on the fly. - HashMap cache = (HashMap) resourceBundleCache.get(classLoader); + StringBuffer sb = new StringBuffer(60); + sb.append(baseName).append('_').append(locale); + String name = sb.toString(); + if (cache == null) { - cache = new HashMap(); - resourceBundleCache.put(classLoader, cache); + cache = new HashMap(); + resourceBundleCache.put(classLoader, cache); } else { - // Fast path: If baseName + "_" + locale is in cache use it. - String name = baseName + "_" + locale.toString(); - Reference ref = (Reference) cache.get(name); - if (ref != null) - { - ResourceBundle rb = (ResourceBundle) ref.get(); - if (rb != null) - // rb should already have the right parent, except if - // something very strange happened. - return rb; - } + Reference ref = (Reference) cache.get(name); + if (ref != null) + { + ResourceBundle rb = (ResourceBundle) ref.get(); + if (rb != null) + // rb should already have the right parent, except if + // something very strange happened. + return rb; + } } ResourceBundle baseBundle = tryBundle(baseName, emptyLocale, - classLoader, null, cache); + classLoader, null, cache); if (baseBundle == null) // JDK says, that if one provides a bundle base_en_UK, one // must also provide the bundles base_en and base. // This implies that if there is no bundle for base, there // is no bundle at all. - throw new MissingResourceException("Bundle " + baseName + " not found", baseName, ""); + throw new MissingResourceException("Bundle " + baseName + " not found", + baseName, ""); // Now use the default locale. ResourceBundle bundle = tryLocalBundle(baseName, locale, - classLoader, baseBundle, cache); + classLoader, baseBundle, cache); if (bundle == baseBundle && !locale.equals(Locale.getDefault())) - { - bundle = tryLocalBundle(baseName, Locale.getDefault(), - classLoader, baseBundle, cache); - } + bundle = tryLocalBundle(baseName, Locale.getDefault(), + classLoader, baseBundle, cache); + + // Check whether baseName_locale has been loaded; if not, map the + // "baseName" bundle to "baseName_locale" to avoid retrying to load + // baseName_locale. + Reference ref = (Reference) cache.get(name); + if (ref == null) + cache.put(name, new SoftReference(bundle)); + return bundle; } /** - * Return the actual locale of this bundle. You can use it after - * calling getBundle, to know if the bundle for the desired locale - * was loaded or if the fall back was used. + * Override this method to provide the resource for a keys. This gets + * called by <code>getObject</code>. If you don't have a resource + * for the given key, you should return null instead throwing a + * MissingResourceException. You don't have to ask the parent, getObject() + * already does this; nor should you throw a MissingResourceException. + * + * @param key the key of the resource + * @return the resource for the key, or null if not in bundle + * @throws NullPointerException if key is null */ - public Locale getLocale() - { - return locale; - } + protected abstract Object handleGetObject(String key); /** - * Set the parent of this bundle. This is consulted when you call - * getObject and there is no such resource in the current bundle. - * @param parent the parent of this bundle. + * This method should return all keys for which a resource exists; you + * should include the enumeration of any parent's keys, after filtering out + * duplicates. + * + * @return an enumeration of the keys */ - protected void setParent(ResourceBundle parent) - { - // Shall we ignore the old parent? - this.parent = parent; - } + public abstract Enumeration getKeys(); /** - * Override this method to provide the resource for a keys. This gets - * called by <code>getObject</code>. If you don't have a resource - * for the given key, you should return null instead throwing a - * MissingResourceException. You don't have to ask the parent, - * getObject() already does this. + * Tries to load a class or a property file with the specified name. * - * @param key The key of the resource. - * @return The resource for the key, or null if not in bundle. - * @exception MissingResourceException - * you shouldn't throw this. + * @param localizedName the name + * @param locale the locale, that must be used exactly + * @param classloader the classloader + * @param bundle the backup (parent) bundle + * @return the resource bundle if it was loaded, otherwise the backup */ - protected abstract Object handleGetObject(String key) - throws MissingResourceException; + private static final ResourceBundle tryBundle(String localizedName, + Locale locale, + ClassLoader classloader, + ResourceBundle bundle, + HashMap cache) + { + // First look into the cache. + // XXX We should remove cleared references from the cache. + Reference ref = (Reference) cache.get(localizedName); + if (ref != null) + { + ResourceBundle rb = (ResourceBundle) ref.get(); + if (rb != null) + // rb should already have the right parent, except if + // something very strange happened. + return rb; + } + + // foundBundle holds exact matches for the localizedName resource + // bundle, which may later be cached. + ResourceBundle foundBundle = null; + try + { + Class rbClass; + if (classloader == null) + rbClass = Class.forName(localizedName); + else + rbClass = classloader.loadClass(localizedName); + foundBundle = (ResourceBundle) rbClass.newInstance(); + foundBundle.parent = bundle; + foundBundle.locale = locale; + } + catch (Exception ex) + { + // ignore them all + } + if (foundBundle == null) + try + { + InputStream is; + final String resourceName + = localizedName.replace('.', '/') + ".properties"; + if (classloader == null) + is = ClassLoader.getSystemResourceAsStream(resourceName); + else + is = classloader.getResourceAsStream(resourceName); + if (is != null) + { + foundBundle = new PropertyResourceBundle(is); + foundBundle.parent = bundle; + foundBundle.locale = locale; + } + } + catch (IOException ex) + { + } + + if (foundBundle != null) + cache.put(localizedName, new SoftReference(foundBundle)); + + return foundBundle != null ? foundBundle : bundle; + } /** - * This method should return all keys for which a resource exists. - * @return An enumeration of the keys. + * Tries to load a the bundle for a given locale, also loads the backup + * locales with the same language. + * + * @param name the name + * @param locale the locale, that must be used exactly + * @param classloader the classloader + * @param bundle the backup (parent) bundle + * @return the resource bundle if it was loaded, otherwise the backup */ - public abstract Enumeration getKeys(); -} + private static final ResourceBundle tryLocalBundle(String baseName, + Locale locale, + ClassLoader classloader, + ResourceBundle bundle, + HashMap cache) + { + final String language = locale.getLanguage(); + StringBuffer sb = new StringBuffer(60); + + if (language.length() > 0) + { + final String country = locale.getCountry(); + sb.append(baseName).append('_').append(language); + String name = sb.toString(); + + if (country.length() != 0) + { + bundle = tryBundle(name, new Locale(language), + classloader, bundle, cache); + sb.append('_').append(country); + name = sb.toString(); + + final String variant = locale.getVariant(); + + if (variant.length() != 0) + { + bundle = tryBundle(name, new Locale(language, country), + classloader, bundle, cache); + sb.append('_').append(variant); + name = sb.toString(); + } + } + bundle = tryBundle(name, locale, classloader, bundle, cache); + } + return bundle; + } +} // class ResourceBundle |