123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- /* ResourceBundle -- aids in loading resource bundles
- Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004, 2005, 2006
- Free Software Foundation, Inc.
- This file is part of GNU Classpath.
- GNU Classpath is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- GNU Classpath is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with GNU Classpath; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA.
- Linking this library statically or dynamically with other modules is
- making a combined work based on this library. Thus, the terms and
- conditions of the GNU General Public License cover the whole
- combination.
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent
- modules, and to copy and distribute the resulting executable under
- terms of your choice, provided that you also meet, for each linked
- independent module, the terms and conditions of the license of that
- module. An independent module is a module which is not derived from
- or based on this library. If you modify this library, you may extend
- this exception to your version of the library, but you are not
- obligated to do so. If you do not wish to do so, delete this
- exception statement from your version. */
- package java.util;
- import gnu.classpath.VMStackWalker;
- import gnu.java.lang.CPStringBuilder;
- import java.io.IOException;
- import java.io.InputStream;
- /**
- * 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</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 (omitting 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>.
- *
- * <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
- * @since 1.1
- * @status updated to 1.4
- */
- public abstract class ResourceBundle
- {
- /**
- * Maximum size of our cache of <code>ResourceBundle</code>s keyed by
- * {@link BundleKey} instances.
- *
- * @see BundleKey
- */
- private static final int CACHE_SIZE = 100;
- /**
- * 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
- * <code>getLocale</code> and it is automatically set in
- * <code>getBundle</code>.
- */
- private Locale locale;
- /**
- * A VM-wide cache of resource bundles already fetched.
- * <p>
- * This {@link Map} is a Least Recently Used (LRU) cache, of the last
- * {@link #CACHE_SIZE} accessed <code>ResourceBundle</code>s keyed by the
- * tuple: default locale, resource-bundle name, resource-bundle locale, and
- * classloader.
- *
- * @see BundleKey
- */
- private static Map<BundleKey,Object> bundleCache =
- new LinkedHashMap<BundleKey,Object>(CACHE_SIZE + 1, 0.75F, true)
- {
- public boolean removeEldestEntry(Map.Entry<BundleKey,Object> entry)
- {
- return size() > CACHE_SIZE;
- }
- };
- /**
- * 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
- * @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)
- {
- return (String) getObject(key);
- }
- /**
- * 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
- * @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)
- {
- return (String[]) getObject(key);
- }
- /**
- * 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)
- {
- for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent)
- {
- Object o = bundle.handleGetObject(key);
- if (o != null)
- return o;
- }
- String className = getClass().getName();
- throw new MissingResourceException("Key '" + key
- + "'not found in Bundle: "
- + className, className, key);
- }
- /**
- * 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 Locale getLocale()
- {
- return locale;
- }
- /**
- * 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
- */
- protected void setParent(ResourceBundle parent)
- {
- this.parent = parent;
- }
- /**
- * 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
- */
- public static ResourceBundle getBundle(String baseName)
- {
- ClassLoader cl = VMStackWalker.getCallingClassLoader();
- if (cl == null)
- cl = ClassLoader.getSystemClassLoader();
- return getBundle(baseName, Locale.getDefault(), cl);
- }
- /**
- * 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 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
- */
- public static ResourceBundle getBundle(String baseName, Locale locale)
- {
- ClassLoader cl = VMStackWalker.getCallingClassLoader();
- if (cl == null)
- cl = ClassLoader.getSystemClassLoader();
- return getBundle(baseName, locale, cl);
- }
- /** Cache key for the ResourceBundle cache. Resource bundles are keyed
- by the combination of bundle name, locale, and class loader. */
- private static class BundleKey
- {
- Locale defaultLocale;
- String baseName;
- Locale locale;
- ClassLoader classLoader;
- int hashcode;
- BundleKey() {}
- BundleKey(Locale dl, String s, Locale l, ClassLoader cl)
- {
- set(dl, s, l, cl);
- }
- void set(Locale dl, String s, Locale l, ClassLoader cl)
- {
- defaultLocale = dl;
- baseName = s;
- locale = l;
- classLoader = cl;
- hashcode = defaultLocale.hashCode() ^ baseName.hashCode()
- ^ locale.hashCode() ^ classLoader.hashCode();
- }
- public int hashCode()
- {
- return hashcode;
- }
- public boolean equals(Object o)
- {
- if (! (o instanceof BundleKey))
- return false;
- BundleKey key = (BundleKey) o;
- return hashcode == key.hashcode
- && defaultLocale.equals(key.defaultLocale)
- && baseName.equals(key.baseName)
- && locale.equals(key.locale)
- && classLoader.equals(key.classLoader);
- }
- public String toString()
- {
- CPStringBuilder builder = new CPStringBuilder(getClass().getName());
- builder.append("[defaultLocale=");
- builder.append(defaultLocale);
- builder.append(",baseName=");
- builder.append(baseName);
- builder.append(",locale=");
- builder.append(locale);
- builder.append(",classLoader=");
- builder.append(classLoader);
- builder.append("]");
- return builder.toString();
- }
- }
- /** A cache lookup key. This avoids having to a new one for every
- * getBundle() call. */
- private static final BundleKey lookupKey = new BundleKey();
- /** Singleton cache entry to represent previous failed lookups. */
- private static final Object nullEntry = new Object();
- /**
- * 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:</p>
- *
- * <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:</p>
- *
- * <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>
- *
- * <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>
- *
- * <p>Calling getBundle with the shown locale argument values instantiates
- * resource bundles from the following sources:</p>
- *
- * <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>
- *
- * <p>The file MyResources_fr_CH.properties is never used because it is hidden
- * by MyResources_fr_CH.class.</p>
- *
- * @param baseName the name of the ResourceBundle
- * @param locale A locale
- * @param classLoader a ClassLoader
- * @return the desired resource bundle
- * @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 synchronized ResourceBundle getBundle
- (String baseName, Locale locale, ClassLoader classLoader)
- {
- Locale defaultLocale = Locale.getDefault();
- // This will throw NullPointerException if any arguments are null.
- lookupKey.set(defaultLocale, baseName, locale, classLoader);
- Object obj = bundleCache.get(lookupKey);
- if (obj instanceof ResourceBundle)
- return (ResourceBundle) obj;
- if (obj == nullEntry)
- throw new MissingResourceException("Bundle " + baseName
- + " not found for locale " + locale
- + " by classloader " + classLoader,
- baseName, "");
- // First, look for a bundle for the specified locale. We don't want
- // the base bundle this time.
- boolean wantBase = locale.equals(defaultLocale);
- ResourceBundle bundle = tryBundle(baseName, locale, classLoader, wantBase);
- // Try the default locale if neccessary.
- if (bundle == null && ! wantBase)
- bundle = tryBundle(baseName, defaultLocale, classLoader, true);
- BundleKey key = new BundleKey(defaultLocale, baseName, locale, classLoader);
- if (bundle == null)
- {
- // Cache the fact that this lookup has previously failed.
- bundleCache.put(key, nullEntry);
- throw new MissingResourceException("Bundle " + baseName
- + " not found for locale " + locale
- + " by classloader " + classLoader,
- baseName, "");
- }
- // Cache the result and return it.
- bundleCache.put(key, bundle);
- return bundle;
- }
- /**
- * 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
- */
- protected abstract Object handleGetObject(String key);
- /**
- * 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
- */
- public abstract Enumeration<String> getKeys();
- /**
- * Tries to load a class or a property file with the specified name.
- *
- * @param localizedName the name
- * @param classloader the classloader
- * @return the resource bundle if it was loaded, otherwise the backup
- */
- private static ResourceBundle tryBundle(String localizedName,
- ClassLoader classloader)
- {
- ResourceBundle bundle = null;
- try
- {
- Class<?> rbClass;
- if (classloader == null)
- rbClass = Class.forName(localizedName);
- else
- rbClass = classloader.loadClass(localizedName);
- // Note that we do the check up front instead of catching
- // ClassCastException. The reason for this is that some crazy
- // programs (Eclipse) have classes that do not extend
- // ResourceBundle but that have the same name as a property
- // bundle; in fact Eclipse relies on ResourceBundle not
- // instantiating these classes.
- if (ResourceBundle.class.isAssignableFrom(rbClass))
- bundle = (ResourceBundle) rbClass.newInstance();
- }
- catch (Exception ex) {}
- if (bundle == null)
- {
- try
- {
- InputStream is;
- String resourceName
- = localizedName.replace('.', '/') + ".properties";
- if (classloader == null)
- is = ClassLoader.getSystemResourceAsStream(resourceName);
- else
- is = classloader.getResourceAsStream(resourceName);
- if (is != null)
- bundle = new PropertyResourceBundle(is);
- }
- catch (IOException ex)
- {
- MissingResourceException mre = new MissingResourceException
- ("Failed to load bundle: " + localizedName, localizedName, "");
- mre.initCause(ex);
- throw mre;
- }
- }
- return bundle;
- }
- /**
- * Tries to load the bundle for a given locale, also loads the backup
- * locales with the same language.
- *
- * @param baseName the raw bundle name, without locale qualifiers
- * @param locale the locale
- * @param classLoader the classloader
- * @param wantBase whether a resource bundle made only from the base name
- * (with no locale information attached) should be returned.
- * @return the resource bundle if it was loaded, otherwise the backup
- */
- private static ResourceBundle tryBundle(String baseName, Locale locale,
- ClassLoader classLoader,
- boolean wantBase)
- {
- String language = locale.getLanguage();
- String country = locale.getCountry();
- String variant = locale.getVariant();
- int baseLen = baseName.length();
- // Build up a CPStringBuilder containing the complete bundle name, fully
- // qualified by locale.
- CPStringBuilder sb = new CPStringBuilder(baseLen + variant.length() + 7);
- sb.append(baseName);
- if (language.length() > 0)
- {
- sb.append('_');
- sb.append(language);
- if (country.length() > 0)
- {
- sb.append('_');
- sb.append(country);
- if (variant.length() > 0)
- {
- sb.append('_');
- sb.append(variant);
- }
- }
- }
- // Now try to load bundles, starting with the most specialized name.
- // Build up the parent chain as we go.
- String bundleName = sb.toString();
- ResourceBundle first = null; // The most specialized bundle.
- ResourceBundle last = null; // The least specialized bundle.
- while (true)
- {
- ResourceBundle foundBundle = tryBundle(bundleName, classLoader);
- if (foundBundle != null)
- {
- if (first == null)
- first = foundBundle;
- if (last != null)
- last.parent = foundBundle;
- foundBundle.locale = locale;
- last = foundBundle;
- }
- int idx = bundleName.lastIndexOf('_');
- // Try the non-localized base name only if we already have a
- // localized child bundle, or wantBase is true.
- if (idx > baseLen || (idx == baseLen && (first != null || wantBase)))
- bundleName = bundleName.substring(0, idx);
- else
- break;
- }
- return first;
- }
- /**
- * Remove all resources from the cache that were loaded
- * using the class loader of the calling class.
- *
- * @since 1.6
- */
- public static final void clearCache()
- {
- clearCache(VMStackWalker.getCallingClassLoader());
- }
- /**
- * Remove all resources from the cache that were loaded
- * using the specified class loader.
- *
- * @param loader the loader used for the bundles that will be removed.
- * @throws NullPointerException if {@code loader} is {@code null}.
- * @since 1.6
- */
- public static final void clearCache(ClassLoader loader)
- {
- if (loader == null)
- throw new NullPointerException("The loader can not be null.");
- synchronized (ResourceBundle.class)
- {
- Iterator<BundleKey> iter = bundleCache.keySet().iterator();
- while (iter.hasNext())
- {
- BundleKey key = iter.next();
- if (key.classLoader == loader)
- iter.remove();
- }
- }
- }
- }
|