OSDN Git Service

2003-04-29 Michael Koch <konqueror@gmx.de>
[pf3gnuchains/gcc-fork.git] / libjava / java / util / ResourceBundle.java
index 30f28a6..7a4a282 100644 (file)
@@ -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, 2003 Free Software Foundation, Inc.
 
 This file is part of GNU Classpath.
 
@@ -18,426 +18,545 @@ along with GNU Classpath; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 02111-1307 USA.
 
-As a special exception, if you link this library with other files to
-produce an executable, this library does not by itself cause the
-resulting executable to be covered by the GNU General Public License.
-This exception does not however invalidate any other reasons why the
-executable file might be covered by the GNU General Public License. */
+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 java.lang.ref.Reference;
 import java.lang.ref.SoftReference;
+import java.io.InputStream;
+import java.io.IOException;
+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 (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>.
- * <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;
 
+  private static native ClassLoader getCallingClassLoader();
+
+  /**
+   * 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;
+
   /**
-   * The constructor.  It does nothing special.
+   * The last default Locale we saw. If this ever changes then we have to
+   * reset our caches.
+   */
+  private static Locale lastDefaultLocale;
+
+  /**
+   * 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);
   }
 
   /**
-   * This method returns an array with the classes of the calling
-   * methods.  The zeroth entry is the class that called this method
-   * (should always be ResourceBundle), the first contains the class
-   * that called the caller (i.e. the class that called getBundle).
+   * 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.
    *
-   * Implementation note: This depends on the fact, that getBundle
-   * doesn't get inlined, but since it calls a private method, it
-   * isn't inlineable.
+   * @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.
    *
-   * @return an array containing the classes for the callers.  
+   * @param parent the parent of this bundle
    */
-  private static native Class[] getClassContext();
+  protected void setParent(ResourceBundle parent)
+  {
+    this.parent = parent;
+  }
 
   /**
-   * 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.  
+   * 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
-   * @exception MissingResourceException 
-   *    if the resource bundle couldn't be found.  */
+   * @throws MissingResourceException if the resource bundle can't be found
+   * @throws NullPointerException if baseName is null
+   */
   public static final ResourceBundle getBundle(String baseName)
-    throws MissingResourceException
   {
     return getBundle(baseName, Locale.getDefault(),
-                    getClassContext()[1].getClassLoader());
+                     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.
+   * 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
-   * @exception MissingResourceException 
-   *    if the resource bundle couldn't be found.
+   * @throws MissingResourceException if the resource bundle can't be found
+   * @throws NullPointerException if baseName or locale is null
    */
   public static final ResourceBundle getBundle(String baseName,
-                                              Locale locale)
-    throws MissingResourceException
+                                               Locale locale)
   {
-    return getBundle(baseName, locale, getClassContext()[1].getClassLoader());
+    return getBundle(baseName, locale, getCallingClassLoader());
   }
 
   /**
-   * 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();
+   * 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
+   * @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)
+  {
+    // This implementation searches the bundle in the reverse direction
+    // and builds the parent chain on the fly.
+    Locale defaultLocale = Locale.getDefault();
+    if (defaultLocale != lastDefaultLocale)
+      {
+       resourceBundleCache = new HashMap();
+       lastDefaultLocale = defaultLocale;
+      }
+    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);
+      }
+    else if (cache.containsKey(name))
+      {
+       Reference ref = (Reference) cache.get(name);
+       ResourceBundle result = null;
+       // If REF is null, that means that we added a `null' value to
+       // the hash map.  That means we failed to find the bundle
+       // previously, and we cached that fact.  The JDK does this, so
+       // it must be ok.
+       if (ref == null)
+         throw new MissingResourceException("Bundle " + baseName
+                                            + " not found",
+                                            baseName, "");
+       else
+         {
+           ResourceBundle rb = (ResourceBundle) ref.get();
+           if (rb != null)
+             {
+               // RB should already have the right parent, except if
+               // something very strange happened.
+               return rb;
+             }
+           // If RB is null, then we previously found it but it was
+           // collected.  So we try again.
+         }
+      }
+
+    // It is ok if this returns null.  We aren't required to have the
+    // base bundle.
+    ResourceBundle baseBundle = tryBundle(baseName, emptyLocale,
+                                          classLoader, null, cache);
+
+    // Now use our locale, followed by the default locale.  We only
+    // need to try the default locale if our locale is different, and
+    // if our locale failed to yield a result other than the base
+    // bundle.
+    ResourceBundle bundle = tryLocalBundle(baseName, locale,
+                                           classLoader, baseBundle, cache);
+    if (bundle == baseBundle && !locale.equals(defaultLocale))
+      {
+       bundle = tryLocalBundle(baseName, defaultLocale,
+                               classLoader, baseBundle, cache);
+       // We need to record that the argument locale maps to the
+       // bundle we just found.  If we didn't find a bundle, record
+       // that instead.
+       if (bundle == null)
+         cache.put(name, null);
+       else
+         cache.put(name, new SoftReference(bundle));
+      }
+
+    if (bundle == null)
+      throw new MissingResourceException("Bundle " + baseName + " not found",
+                                        baseName, "");
+
+    return bundle;
+  }
 
   /**
-   * The `empty' locale is created once in order to optimize
-   * tryBundle().  
+   * 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
    */
-  private static final Locale emptyLocale = new Locale ("", "");
+  public abstract Enumeration getKeys();
 
   /**
    * 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>.
+   *
+   * @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
    */
   private static final ResourceBundle tryBundle(String localizedName,
-                                               Locale locale,
-                                               ClassLoader classloader,
-                                               ResourceBundle bundle,
-                                               HashMap cache)
+                                                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
+    // First look into the cache.
+    if (cache.containsKey(localizedName))
       {
-       java.io.InputStream is;
-       final String resourceName =
-         localizedName.replace('.', '/') + ".properties";
-       if (classloader == null)
-         is = ClassLoader.getSystemResourceAsStream (resourceName);
+       Reference ref = (Reference) cache.get(localizedName);
+       ResourceBundle result = null;
+       // If REF is null, that means that we added a `null' value to
+       // the hash map.  That means we failed to find the bundle
+       // previously, and we cached that fact.  The JDK does this, so
+       // it must be ok.
+       if (ref == null)
+         return null;
        else
-         is = classloader.getResourceAsStream (resourceName);
-       if (is != null)
          {
-           foundBundle = new PropertyResourceBundle(is);
-           foundBundle.parent = bundle;
-           foundBundle.locale = locale;
+           ResourceBundle rb = (ResourceBundle) ref.get();
+           if (rb != null)
+             {
+               // RB should already have the right parent, except if
+               // something very strange happened.
+               return rb;
+             }
+           // If RB is null, then we previously found it but it was
+           // collected.  So we try again.
          }
       }
-    catch (java.io.IOException ex)
-      {
-      }
 
+    // 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 (ClassNotFoundException ex)
-      {
+        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 (IllegalAccessException ex)
+    catch (Exception ex)
       {
+        // ignore them all
+       foundBundle = null;
       }
-    catch (InstantiationException ex)
+    if (foundBundle == null)
       {
-       // ignore them all
-       // XXX should we also ignore ClassCastException?
+       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)
+    // Put the result into the hash table.  If we didn't find anything
+    // here, we record our parent bundle.  If we record `null' that means
+    // nothing, not even the base, was found.
+    if (foundBundle == null)
+      foundBundle = bundle;
+    if (foundBundle == null)
+      cache.put(localizedName, null);
+    else
       cache.put(localizedName, new SoftReference(foundBundle));
-
-    return foundBundle != null ? foundBundle : bundle;
+    return foundBundle;
   }
 
   /**
    * 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 back up (parent) bundle
-   * @return the resource bundle if that could be loaded, otherwise 
-   * <code>bundle</code>.
+   * @param name the name
+   * @param locale the locale
+   * @param classloader the classloader
+   * @param bundle the backup (parent) bundle
+   * @return the resource bundle if it was loaded, otherwise the backup
    */
   private static final ResourceBundle tryLocalBundle(String baseName,
                                                     Locale locale,
-                                                    ClassLoader classloader,
-                                                    ResourceBundle bundle,
-                                                    HashMap cache)
+                                                     ClassLoader classloader,
+                                                     ResourceBundle bundle,
+                                                     HashMap cache)
   {
     final String language = locale.getLanguage();
+    final String country = locale.getCountry();
+    final String variant = locale.getVariant();
+
+    StringBuffer sb = new StringBuffer(60);
+    sb.append(baseName);
+    sb.append('_');
 
     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);
+       sb.append(language);
+       bundle = tryBundle(sb.toString(), new Locale(language),
+                          classloader, bundle, cache);
       }
-    return bundle;
-  }
+    // If LANGUAGE was empty, we still need to try the other
+    // components, and the `_' is required.
+    sb.append('_');
 
-  /**
-   * 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.
-   * @return the desired resource bundle
-   * @exception MissingResourceException 
-   *    if the resource bundle couldn't be found.
-   */
-  // 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
-  {
-    // This implementation searches the bundle in the reverse direction
-    // and builds the parent chain on the fly.
-
-    HashMap cache = (HashMap) resourceBundleCache.get(classLoader);
-    if (cache == null)
+    if (country.length() > 0)
       {
-       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;
-         }
+       sb.append(country);
+       bundle = tryBundle(sb.toString(), new Locale(language, country),
+                          classloader, bundle, cache);
       }
+    sb.append('_');
 
-    ResourceBundle baseBundle = tryBundle(baseName, emptyLocale,
-                                         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, "");
-
-    // Now use the default locale.
-    ResourceBundle bundle = tryLocalBundle(baseName, locale,
-                                          classLoader, baseBundle, cache);
-    if (bundle == baseBundle && !locale.equals(Locale.getDefault()))
+    if (variant.length() > 0)
       {
-       bundle = tryLocalBundle(baseName, Locale.getDefault(),
-                               classLoader, baseBundle, cache);
+       sb.append(variant);
+       bundle = tryBundle(sb.toString(), locale,
+                          classloader, bundle, cache);
       }
-    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.
-   */
-  public Locale getLocale()
-  {
-    return locale;
-  }
-
-  /**
-   * 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.
-   */
-  protected void setParent(ResourceBundle parent)
-  {
-    // Shall we ignore the old parent?
-    this.parent = parent;
+    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.
-   *
-   * @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.
-   */
-  protected abstract Object handleGetObject(String key)
-    throws MissingResourceException;
-
-  /**
-   * This method should return all keys for which a resource exists.
-   * @return An enumeration of the keys.
-   */
-  public abstract Enumeration getKeys();
 }