--- /dev/null
+/*
+ * @(#)ResourceBundle.java 1.71 04/08/16
+ *
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+
+/*
+ * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
+ * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
+ *
+ * The original version of this source code and documentation
+ * is copyrighted and owned by Taligent, Inc., a wholly-owned
+ * subsidiary of IBM. These materials are provided under terms
+ * of a License Agreement between Taligent and Sun. This technology
+ * is protected by multiple US and International patents.
+ *
+ * This notice and attribution to Taligent may not be removed.
+ * Taligent is a registered trademark of Taligent, Inc.
+ *
+ */
+
+package instrumented.java.util;
+
+import sun.misc.SoftCache;
+
+import java.io.InputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Enumeration;
+import java.util.Locale;
+
+/**
+ *
+ * Resource bundles contain locale-specific objects.
+ * When your program needs a locale-specific resource,
+ * a <code>String</code> for example, your program can load it
+ * from the resource bundle that is appropriate for the
+ * current user's locale. In this way, you can write
+ * program code that is largely independent of the user's
+ * locale isolating most, if not all, of the locale-specific
+ * information in resource bundles.
+ *
+ * <p>
+ * This allows you to write programs that can:
+ * <UL type=SQUARE>
+ * <LI> be easily localized, or translated, into different languages
+ * <LI> handle multiple locales at once
+ * <LI> be easily modified later to support even more locales
+ * </UL>
+ *
+ * <P>
+ * Resource bundles belong to families whose members share a common base
+ * name, but whose names also have additional components that identify
+ * their locales. For example, the base name of a family of resource
+ * bundles might be "MyResources". The family should have a default
+ * resource bundle which simply has the same name as its family -
+ * "MyResources" - and will be used as the bundle of last resort if a
+ * specific locale is not supported. The family can then provide as
+ * many locale-specific members as needed, for example a German one
+ * named "MyResources_de".
+ *
+ * <P>
+ * Each resource bundle in a family contains the same items, but the items have
+ * been translated for the locale represented by that resource bundle.
+ * For example, both "MyResources" and "MyResources_de" may have a
+ * <code>String</code> that's used on a button for canceling operations.
+ * In "MyResources" the <code>String</code> may contain "Cancel" and in
+ * "MyResources_de" it may contain "Abbrechen".
+ *
+ * <P>
+ * If there are different resources for different countries, you
+ * can make specializations: for example, "MyResources_de_CH" contains objects for
+ * the German language (de) in Switzerland (CH). If you want to only
+ * modify some of the resources
+ * in the specialization, you can do so.
+ *
+ * <P>
+ * When your program needs a locale-specific object, it loads
+ * the <code>ResourceBundle</code> class using the
+ * {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
+ * method:
+ * <blockquote>
+ * <pre>
+ * ResourceBundle myResources =
+ * ResourceBundle.getBundle("MyResources", currentLocale);
+ * </pre>
+ * </blockquote>
+ *
+ * <P>
+ * Resource bundles contain key/value pairs. The keys uniquely
+ * identify a locale-specific object in the bundle. Here's an
+ * example of a <code>ListResourceBundle</code> that contains
+ * two key/value pairs:
+ * <blockquote>
+ * <pre>
+ * public class MyResources extends ListResourceBundle {
+ * public Object[][] getContents() {
+ * return contents;
+ * }
+ * static final Object[][] contents = {
+ * // LOCALIZE THIS
+ * {"OkKey", "OK"},
+ * {"CancelKey", "Cancel"},
+ * // END OF MATERIAL TO LOCALIZE
+ * };
+ * }
+ * </pre>
+ * </blockquote>
+ * Keys are always <code>String</code>s.
+ * In this example, the keys are "OkKey" and "CancelKey".
+ * In the above example, the values
+ * are also <code>String</code>s--"OK" and "Cancel"--but
+ * they don't have to be. The values can be any type of object.
+ *
+ * <P>
+ * You retrieve an object from resource bundle using the appropriate
+ * getter method. Because "OkKey" and "CancelKey"
+ * are both strings, you would use <code>getString</code> to retrieve them:
+ * <blockquote>
+ * <pre>
+ * button1 = new Button(myResources.getString("OkKey"));
+ * button2 = new Button(myResources.getString("CancelKey"));
+ * </pre>
+ * </blockquote>
+ * The getter methods all require the key as an argument and return
+ * the object if found. If the object is not found, the getter method
+ * throws a <code>MissingResourceException</code>.
+ *
+ * <P>
+ * Besides <code>getString</code>, ResourceBundle also provides
+ * a method for getting string arrays, <code>getStringArray</code>,
+ * as well as a generic <code>getObject</code> method for any other
+ * type of object. When using <code>getObject</code>, you'll
+ * have to cast the result to the appropriate type. For example:
+ * <blockquote>
+ * <pre>
+ * int[] myIntegers = (int[]) myResources.getObject("intList");
+ * </pre>
+ * </blockquote>
+ *
+ * <P>
+ * The Java 2 platform provides two subclasses of <code>ResourceBundle</code>,
+ * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
+ * that provide a fairly simple way to create resources.
+ * As you saw briefly in a previous example, <code>ListResourceBundle</code>
+ * manages its resource as a List of key/value pairs.
+ * <code>PropertyResourceBundle</code> uses a properties file to manage
+ * its resources.
+ *
+ * <p>
+ * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
+ * do not suit your needs, you can write your own <code>ResourceBundle</code>
+ * subclass. Your subclasses must override two methods: <code>handleGetObject</code>
+ * and <code>getKeys()</code>.
+ *
+ * <P>
+ * The following is a very simple example of a <code>ResourceBundle</code>
+ * subclass, MyResources, that manages two resources (for a larger number of
+ * resources you would probably use a <code>Hashtable</code>).
+ * Notice that you don't need to supply a value if
+ * a "parent-level" <code>ResourceBundle</code> handles the same
+ * key with the same value (as for the okKey below).
+ * <p><strong>Example:</strong>
+ * <blockquote>
+ * <pre>
+ * // default (English language, United States)
+ * public class MyResources extends ResourceBundle {
+ * public Object handleGetObject(String key) {
+ * if (key.equals("okKey")) return "Ok";
+ * if (key.equals("cancelKey")) return "Cancel";
+ * return null;
+ * }
+ * }
+ *
+ * // German language
+ * public class MyResources_de extends MyResources {
+ * public Object handleGetObject(String key) {
+ * // don't need okKey, since parent level handles it.
+ * if (key.equals("cancelKey")) return "Abbrechen";
+ * return null;
+ * }
+ * }
+ * </pre>
+ * </blockquote>
+ * You do not have to restrict yourself to using a single family of
+ * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
+ * exception messages, <code>ExceptionResources</code>
+ * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
+ * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
+ * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
+ *
+ * @see ListResourceBundle
+ * @see PropertyResourceBundle
+ * @see MissingResourceException
+ * @since JDK1.1
+ */
+abstract public class ResourceBundle {
+ /**
+ * Static key used for resource lookups. Concurrent
+ * access to this object is controlled by synchronizing cacheList,
+ * not cacheKey. A static object is used to do cache lookups
+ * for performance reasons - the assumption being that synchronization
+ * has a lower overhead than object allocation and subsequent
+ * garbage collection.
+ */
+ private static final ResourceCacheKey cacheKey = new ResourceCacheKey();
+
+ /** initial size of the bundle cache */
+ private static final int INITIAL_CACHE_SIZE = 25;
+
+ /** capacity of cache consumed before it should grow */
+ private static final float CACHE_LOAD_FACTOR = (float)1.0;
+
+ /**
+ * Maximum length of one branch of the resource search path tree.
+ * Used in getBundle.
+ */
+ private static final int MAX_BUNDLES_SEARCHED = 3;
+
+ /**
+ * This Hashtable is used to keep multiple threads from loading the
+ * same bundle concurrently. The table entries are (cacheKey, thread)
+ * where cacheKey is the key for the bundle that is under construction
+ * and thread is the thread that is constructing the bundle.
+ * This list is manipulated in findBundle and putBundleInCache.
+ * Synchronization of this object is done through cacheList, not on
+ * this object.
+ */
+ private static final Hashtable underConstruction = new Hashtable(MAX_BUNDLES_SEARCHED, CACHE_LOAD_FACTOR);
+
+ /** constant indicating that no resource bundle was found */
+ private static final Object NOT_FOUND = new Object();
+
+ /**
+ * The cache is a map from cache keys (with bundle base name,
+ * locale, and class loader) to either a resource bundle
+ * (if one has been found) or NOT_FOUND (if no bundle has
+ * been found).
+ * The cache is a SoftCache, allowing bundles to be
+ * removed from the cache if they are no longer
+ * needed. This will also allow the cache keys
+ * to be reclaimed along with the ClassLoaders
+ * they reference.
+ * This variable would be better named "cache", but we keep the old
+ * name for compatibility with some workarounds for bug 4212439.
+ */
+ private static SoftCache cacheList = new SoftCache(INITIAL_CACHE_SIZE, CACHE_LOAD_FACTOR);
+
+ /**
+ * Queue for reference objects referring to class loaders.
+ */
+ private static ReferenceQueue referenceQueue = new ReferenceQueue();
+
+ /**
+ * The parent bundle of this bundle.
+ * The parent bundle is searched by {@link #getObject getObject}
+ * when this bundle does not contain a particular resource.
+ */
+ protected ResourceBundle parent = null;
+
+ /**
+ * The locale for this bundle.
+ */
+ private Locale locale = null;
+
+ /**
+ * Sole constructor. (For invocation by subclass constructors, typically
+ * implicit.)
+ */
+ public ResourceBundle() {
+ }
+
+ /**
+ * Gets a string for the given key from this resource bundle or one of its parents.
+ * Calling this method is equivalent to calling
+ * <blockquote>
+ * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
+ * </blockquote>
+ *
+ * @param key the key for the desired string
+ * @exception NullPointerException if <code>key</code> is <code>null</code>
+ * @exception MissingResourceException if no object for the given key can be found
+ * @exception ClassCastException if the object found for the given key is not a string
+ * @return the string for the given key
+ */
+ public final String getString(String key) {
+ return (String) getObject(key);
+ }
+
+ /**
+ * Gets a string array for the given key from this resource bundle or one of its parents.
+ * Calling this method is equivalent to calling
+ * <blockquote>
+ * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
+ * </blockquote>
+ *
+ * @param key the key for the desired string array
+ * @exception NullPointerException if <code>key</code> is <code>null</code>
+ * @exception MissingResourceException if no object for the given key can be found
+ * @exception ClassCastException if the object found for the given key is not a string array
+ * @return the string array for the given key
+ */
+ public final String[] getStringArray(String key) {
+ return (String[]) getObject(key);
+ }
+
+ /**
+ * Gets an object for the given key from this resource bundle or one of its parents.
+ * This method first tries to obtain the object from this resource bundle using
+ * {@link #handleGetObject(java.lang.String) handleGetObject}.
+ * If not successful, and the parent resource bundle is not null,
+ * it calls the parent's <code>getObject</code> method.
+ * If still not successful, it throws a MissingResourceException.
+ *
+ * @param key the key for the desired object
+ * @exception NullPointerException if <code>key</code> is <code>null</code>
+ * @exception MissingResourceException if no object for the given key can be found
+ * @return the object for the given key
+ */
+ public final Object getObject(String key) {
+ Object obj = handleGetObject(key);
+ if (obj == null) {
+ if (parent != null) {
+ obj = parent.getObject(key);
+ }
+ if (obj == null)
+ throw new MissingResourceException("Can't find resource for bundle "
+ +this.getClass().getName()
+ +", key "+key,
+ this.getClass().getName(),
+ key);
+ }
+ return obj;
+ }
+
+ /**
+ * Returns the locale of this resource bundle. This method can be used after a
+ * call to getBundle() to determine whether the resource bundle returned really
+ * corresponds to the requested locale or is a fallback.
+ *
+ * @return the locale of this resource bundle
+ */
+ public Locale getLocale() {
+ return locale;
+ }
+
+ /**
+ * Sets the locale for this bundle. This is the locale that this
+ * bundle actually represents and does not depend on how the
+ * bundle was found by getBundle. Ex. if the user was looking
+ * for fr_FR and getBundle found en_US, the bundle's locale would
+ * be en_US, NOT fr_FR
+ * @param baseName the bundle's base name
+ * @param bundleName the complete bundle name including locale
+ * extension.
+ */
+ private void setLocale(String baseName, String bundleName) {
+ if (baseName.length() == bundleName.length()) {
+ locale = new Locale("", "");
+ } else if (baseName.length() < bundleName.length()) {
+ int pos = baseName.length();
+ String temp = bundleName.substring(pos + 1);
+ pos = temp.indexOf('_');
+ if (pos == -1) {
+ locale = new Locale(temp, "", "");
+ return;
+ }
+
+ String language = temp.substring(0, pos);
+ temp = temp.substring(pos + 1);
+ pos = temp.indexOf('_');
+ if (pos == -1) {
+ locale = new Locale(language, temp, "");
+ return;
+ }
+
+ String country = temp.substring(0, pos);
+ temp = temp.substring(pos + 1);
+
+ locale = new Locale(language, country, temp);
+ } else {
+ //The base name is longer than the bundle name. Something is very wrong
+ //with the calling code.
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /*
+ * Automatic determination of the ClassLoader to be used to load
+ * resources on behalf of the client. N.B. The client is getLoader's
+ * caller's caller.
+ */
+ private static ClassLoader getLoader() {
+ Class[] stack = getClassContext();
+ /* Magic number 2 identifies our caller's caller */
+ Class c = stack[2];
+ ClassLoader cl = (c == null) ? null : c.getClassLoader();
+ if (cl == null) {
+ cl = ClassLoader.getSystemClassLoader();
+ }
+ return cl;
+ }
+
+ private static native Class[] getClassContext();
+
+ /**
+ * Sets the parent bundle of this bundle.
+ * The parent bundle is searched by {@link #getObject getObject}
+ * when this bundle does not contain a particular resource.
+ *
+ * @param parent this bundle's parent bundle.
+ */
+ protected void setParent( ResourceBundle parent ) {
+ this.parent = parent;
+ }
+
+ /**
+ * Key used for cached resource bundles. The key checks
+ * the resource name, the class loader, and the default
+ * locale to determine if the resource is a match to the
+ * requested one. The loader may be null, but the
+ * searchName and the default locale must have a non-null value.
+ * Note that the default locale may change over time, and
+ * lookup should always be based on the current default
+ * locale (if at all).
+ */
+ private static final class ResourceCacheKey implements Cloneable {
+ private LoaderReference loaderRef;
+ private String searchName;
+ private Locale defaultLocale;
+ private int hashCodeCache;
+
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ try {
+ final ResourceCacheKey otherEntry = (ResourceCacheKey)other;
+ //quick check to see if they are not equal
+ if (hashCodeCache != otherEntry.hashCodeCache) {
+ return false;
+ }
+ //are the names the same?
+ if (!searchName.equals(otherEntry.searchName)) {
+ return false;
+ }
+ // are the default locales the same?
+ if (defaultLocale == null) {
+ if (otherEntry.defaultLocale != null) {
+ return false;
+ }
+ } else {
+ if (!defaultLocale.equals(otherEntry.defaultLocale)) {
+ return false;
+ }
+ }
+ //are refs (both non-null) or (both null)?
+ if (loaderRef == null) {
+ return otherEntry.loaderRef == null;
+ } else {
+ Object loaderRefValue = loaderRef.get();
+ return (otherEntry.loaderRef != null)
+ // with a null reference we can no longer find
+ // out which class loader was referenced; so
+ // treat it as unequal
+ && (loaderRefValue != null)
+ && (loaderRefValue == otherEntry.loaderRef.get());
+ }
+ } catch (NullPointerException e) {
+ return false;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ public int hashCode() {
+ return hashCodeCache;
+ }
+
+ public Object clone() {
+ try {
+ ResourceCacheKey clone = (ResourceCacheKey) super.clone();
+ if (loaderRef != null) {
+ clone.loaderRef = new LoaderReference(loaderRef.get(), referenceQueue, clone);
+ }
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ //this should never happen
+ throw new InternalError();
+ }
+
+ }
+
+ public void setKeyValues(ClassLoader loader, String searchName, Locale defaultLocale) {
+ this.searchName = searchName;
+ hashCodeCache = searchName.hashCode();
+ this.defaultLocale = defaultLocale;
+ if (defaultLocale != null) {
+ hashCodeCache ^= defaultLocale.hashCode();
+ }
+ if (loader == null) {
+ this.loaderRef = null;
+ } else {
+ loaderRef = new LoaderReference(loader, referenceQueue, this);
+ hashCodeCache ^= loader.hashCode();
+ }
+ }
+
+ public void clear() {
+ setKeyValues(null, "", null);
+ }
+ }
+
+ /**
+ * References to class loaders are weak references, so that they can be
+ * garbage collected when nobody else is using them. The ResourceBundle
+ * class has no reason to keep class loaders alive.
+ */
+ private static final class LoaderReference extends WeakReference {
+ private ResourceCacheKey cacheKey;
+
+ LoaderReference(Object referent, ReferenceQueue q, ResourceCacheKey key) {
+ super(referent, q);
+ cacheKey = key;
+ }
+
+ ResourceCacheKey getCacheKey() {
+ return cacheKey;
+ }
+ }
+
+
+ /**
+ * Gets a resource bundle using the specified base name, the default locale,
+ * and the caller's class loader. Calling this method is equivalent to calling
+ * <blockquote>
+ * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
+ * </blockquote>
+ * except that <code>getClassLoader()</code> is run with the security
+ * privileges of <code>ResourceBundle</code>.
+ * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
+ * for a complete description of the search and instantiation strategy.
+ *
+ * @param baseName the base name of the resource bundle, a fully qualified class name
+ * @exception java.lang.NullPointerException
+ * if <code>baseName</code> is <code>null</code>
+ * @exception MissingResourceException
+ * if no resource bundle for the specified base name can be found
+ * @return a resource bundle for the given base name and the default locale
+ */
+ public static final ResourceBundle getBundle(String baseName)
+ {
+ return getBundleImpl(baseName, Locale.getDefault(),
+ /* must determine loader here, else we break stack invariant */
+ getLoader());
+ }
+
+ /**
+ * Gets a resource bundle using the specified base name and locale,
+ * and the caller's class loader. Calling this method is equivalent to calling
+ * <blockquote>
+ * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
+ * </blockquote>
+ * except that <code>getClassLoader()</code> is run with the security
+ * privileges of <code>ResourceBundle</code>.
+ * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
+ * for a complete description of the search and instantiation strategy.
+ *
+ * @param baseName the base name of the resource bundle, a fully qualified class name
+ * @param locale the locale for which a resource bundle is desired
+ * @exception java.lang.NullPointerException
+ * if <code>baseName</code> or <code>locale</code> is <code>null</code>
+ * @exception MissingResourceException
+ * if no resource bundle for the specified base name can be found
+ * @return a resource bundle for the given base name and locale
+ */
+ public static final ResourceBundle getBundle(String baseName,
+ Locale locale)
+ {
+ return getBundleImpl(baseName, locale, getLoader());
+ }
+
+ /**
+ * Gets a resource bundle using the specified base name, locale, and class loader.
+ *
+ * <p>
+ * Conceptually, <code>getBundle</code> uses the following strategy for locating and instantiating
+ * resource bundles:
+ * <p>
+ * <code>getBundle</code> uses the base name, the specified locale, and the default
+ * locale (obtained from {@link java.util.Locale#getDefault() Locale.getDefault})
+ * to generate a sequence of <em>candidate bundle names</em>.
+ * If the specified locale's language, country, and variant are all empty
+ * strings, then the base name is the only candidate bundle name.
+ * Otherwise, the following sequence is generated from the attribute
+ * values of the specified locale (language1, country1, and variant1)
+ * and of the default locale (language2, country2, and variant2):
+ * <ul>
+ * <li> baseName + "_" + language1 + "_" + country1 + "_" + variant1
+ * <li> baseName + "_" + language1 + "_" + country1
+ * <li> baseName + "_" + language1
+ * <li> baseName + "_" + language2 + "_" + country2 + "_" + variant2
+ * <li> baseName + "_" + language2 + "_" + country2
+ * <li> baseName + "_" + language2
+ * <li> baseName
+ * </ul>
+ * <p>
+ * Candidate bundle names where the final component is an empty string are omitted.
+ * For example, if country1 is an empty string, the second candidate bundle name is omitted.
+ *
+ * <p>
+ * <code>getBundle</code> then iterates over the candidate bundle names to find the first
+ * one for which it can <em>instantiate</em> an actual resource bundle. For each candidate
+ * bundle name, it attempts to create a resource bundle:
+ * <ul>
+ * <li>
+ * First, it attempts to load a class using the candidate bundle name.
+ * If such a class can be found and loaded using the specified class loader, is assignment
+ * compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated,
+ * <code>getBundle</code> creates a new instance of this class and uses it as the <em>result
+ * resource bundle</em>.
+ * <li>
+ * Otherwise, <code>getBundle</code> attempts to locate a property resource file.
+ * It generates a path name from the candidate bundle name by replacing all "." characters
+ * with "/" and appending the string ".properties".
+ * It attempts to find a "resource" with this name using
+ * {@link java.lang.ClassLoader#getResource(java.lang.String) ClassLoader.getResource}.
+ * (Note that a "resource" in the sense of <code>getResource</code> has nothing to do with
+ * the contents of a resource bundle, it is just a container of data, such as a file.)
+ * If it finds a "resource", it attempts to create a new
+ * {@link PropertyResourceBundle} instance from its contents.
+ * If successful, this instance becomes the <em>result resource bundle</em>.
+ * </ul>
+ *
+ * <p>
+ * If no result resource bundle has been found, a <code>MissingResourceException</code>
+ * is thrown.
+ *
+ * <p>
+ * Once a result resource bundle has been found, its parent chain is instantiated.
+ * <code>getBundle</code> iterates over the candidate bundle names that can be
+ * obtained by successively removing variant, country, and language
+ * (each time with the preceding "_") from the bundle name of the result resource bundle.
+ * As above, candidate bundle names where the final component is an empty string are omitted.
+ * With each of the candidate bundle names it attempts to instantiate a resource bundle, as
+ * described above.
+ * Whenever it succeeds, it calls the previously instantiated resource
+ * bundle's {@link #setParent(benchmarks.instrumented.java.util.ResourceBundle) setParent} method
+ * with the new resource bundle, unless the previously instantiated resource
+ * bundle already has a non-null parent.
+ *
+ * <p>
+ * Implementations of <code>getBundle</code> may cache instantiated resource bundles
+ * and return the same resource bundle instance multiple times. They may also
+ * vary the sequence in which resource bundles are instantiated as long as the
+ * selection of the result resource bundle and its parent chain are compatible with
+ * the description above.
+ *
+ * <p>
+ * The <code>baseName</code> argument should be a fully qualified class name. However, for
+ * compatibility with earlier versions, Sun's Java 2 runtime environments do not verify this,
+ * and so it is possible to access <code>PropertyResourceBundle</code>s by specifying a
+ * path name (using "/") instead of a fully qualified class name (using ".").
+ *
+ * <p>
+ * <strong>Example:</strong> The following class and property files are provided:
+ * MyResources.class, MyResources_fr_CH.properties, MyResources_fr_CH.class,
+ * MyResources_fr.properties, MyResources_en.properties, MyResources_es_ES.class.
+ * The contents of all files are valid (that is, public non-abstract subclasses of ResourceBundle for
+ * the ".class" files, syntactically correct ".properties" files).
+ * The default locale is <code>Locale("en", "GB")</code>.
+ * <p>
+ * Calling <code>getBundle</code> 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>Locale("fr", "FR"): result MyResources_fr.properties, parent MyResources.class
+ * <li>Locale("de", "DE"): result MyResources_en.properties, parent MyResources.class
+ * <li>Locale("en", "US"): result MyResources_en.properties, parent MyResources.class
+ * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent MyResources.class
+ * </ul>
+ * The file MyResources_fr_CH.properties is never used because it is hidden by
+ * MyResources_fr_CH.class.
+ *
+ * <p>
+ *
+ * @param baseName the base name of the resource bundle, a fully qualified class name
+ * @param locale the locale for which a resource bundle is desired
+ * @param loader the class loader from which to load the resource bundle
+ * @exception java.lang.NullPointerException
+ * if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
+ * @exception MissingResourceException
+ * if no resource bundle for the specified base name can be found
+ * @return a resource bundle for the given base name and locale
+ * @since 1.2
+ */
+ public static ResourceBundle getBundle(String baseName, Locale locale,
+ ClassLoader loader)
+ {
+ if (loader == null) {
+ throw new NullPointerException();
+ }
+ return getBundleImpl(baseName, locale, loader);
+ }
+
+ private static ResourceBundle getBundleImpl(String baseName, Locale locale,
+ ClassLoader loader)
+ {
+ if (baseName == null) {
+ throw new NullPointerException();
+ }
+
+ //fast path the case where the bundle is cached
+ String bundleName = baseName;
+ String localeSuffix = locale.toString();
+ if (localeSuffix.length() > 0) {
+ bundleName += "_" + localeSuffix;
+ } else if (locale.getVariant().length() > 0) {
+ //This corrects some strange behavior in Locale where
+ //new Locale("", "", "VARIANT").toString == ""
+ bundleName += "___" + locale.getVariant();
+ }
+
+ // The default locale may influence the lookup result, and
+ // it may change, so we get it here once.
+ Locale defaultLocale = Locale.getDefault();
+
+ Object lookup = findBundleInCache(loader, bundleName, defaultLocale);
+ if (lookup == NOT_FOUND) {
+ throwMissingResourceException(baseName, locale);
+ } else if (lookup != null) {
+ return (ResourceBundle)lookup;
+ }
+
+ //The bundle was not cached, so start doing lookup at the root
+ //Resources are loaded starting at the root and working toward
+ //the requested bundle.
+
+ //If findBundle returns null, we become responsible for defining
+ //the bundle, and must call putBundleInCache to complete this
+ //task. This is critical because other threads may be waiting
+ //for us to finish.
+
+ Object parent = NOT_FOUND;
+ try {
+ //locate the root bundle and work toward the desired child
+ Object root = findBundle(loader, baseName, defaultLocale, baseName, null);
+ if (root == null) {
+ putBundleInCache(loader, baseName, defaultLocale, NOT_FOUND);
+ root = NOT_FOUND;
+ }
+
+ // Search the main branch of the search tree.
+ // We need to keep references to the bundles we find on the main path
+ // so they don't get garbage collected before we get to propagate().
+ final Vector names = calculateBundleNames(baseName, locale);
+ Vector bundlesFound = new Vector(MAX_BUNDLES_SEARCHED);
+ // if we found the root bundle and no other bundle names are needed
+ // we can stop here. We don't need to search or load anything further.
+ boolean foundInMainBranch = (root != NOT_FOUND && names.size() == 0);
+
+ if (!foundInMainBranch) {
+ parent = root;
+ for (int i = 0; i < names.size(); i++) {
+ bundleName = (String)names.elementAt(i);
+ lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent);
+ bundlesFound.addElement(lookup);
+ if (lookup != null) {
+ parent = lookup;
+ foundInMainBranch = true;
+ }
+ }
+ }
+ parent = root;
+ if (!foundInMainBranch) {
+ //we didn't find anything on the main branch, so we do the fallback branch
+ final Vector fallbackNames = calculateBundleNames(baseName, defaultLocale);
+ for (int i = 0; i < fallbackNames.size(); i++) {
+ bundleName = (String)fallbackNames.elementAt(i);
+ if (names.contains(bundleName)) {
+ //the fallback branch intersects the main branch so we can stop now.
+ break;
+ }
+ lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent);
+ if (lookup != null) {
+ parent = lookup;
+ } else {
+ //propagate the parent to the child. We can do this
+ //here because we are in the default path.
+ putBundleInCache(loader, bundleName, defaultLocale, parent);
+ }
+ }
+ }
+ //propagate the inheritance/fallback down through the main branch
+ parent = propagate(loader, names, bundlesFound, defaultLocale, parent);
+ } catch (Exception e) {
+ //We should never get here unless there has been a change
+ //to the code that doesn't catch it's own exceptions.
+ cleanUpConstructionList();
+ throwMissingResourceException(baseName, locale);
+ } catch (Error e) {
+ //The only Error that can currently hit this code is a ThreadDeathError
+ //but errors might be added in the future, so we'll play it safe and
+ //clean up.
+ cleanUpConstructionList();
+ throw e;
+ }
+ if (parent == NOT_FOUND) {
+ throwMissingResourceException(baseName, locale);
+ }
+ return (ResourceBundle)parent;
+ }
+
+ /**
+ * propagate bundles from the root down the specified branch of the search tree.
+ * @param loader the class loader for the bundles
+ * @param names the names of the bundles along search path
+ * @param bundlesFound the bundles corresponding to the names (some may be null)
+ * @param defaultLocale the default locale at the time getBundle was called
+ * @param parent the parent of the first bundle in the path (the root bundle)
+ * @return the value of the last bundle along the path
+ */
+ private static Object propagate(ClassLoader loader, Vector names,
+ Vector bundlesFound, Locale defaultLocale, Object parent) {
+ for (int i = 0; i < names.size(); i++) {
+ final String bundleName = (String)names.elementAt(i);
+ final Object lookup = bundlesFound.elementAt(i);
+ if (lookup == null) {
+ putBundleInCache(loader, bundleName, defaultLocale, parent);
+ } else {
+ parent = lookup;
+ }
+ }
+ return parent;
+ }
+
+ /** Throw a MissingResourceException with proper message */
+ private static void throwMissingResourceException(String baseName, Locale locale)
+ throws MissingResourceException{
+ throw new MissingResourceException("Can't find bundle for base name "
+ + baseName + ", locale " + locale,
+ baseName + "_" + locale,"");
+ }
+
+ /**
+ * Remove any entries this thread may have in the construction list.
+ * This is done as cleanup in the case where a bundle can't be
+ * constructed.
+ */
+ private static void cleanUpConstructionList() {
+ synchronized (cacheList) {
+ final Collection entries = underConstruction.values();
+ final Thread thisThread = Thread.currentThread();
+ while (entries.remove(thisThread)) {
+ }
+ }
+ }
+
+ /**
+ * Find a bundle in the cache or load it via the loader or a property file.
+ * If the bundle isn't found, an entry is put in the constructionCache
+ * and null is returned. If null is returned, the caller must define the bundle
+ * by calling putBundleInCache. This routine also propagates NOT_FOUND values
+ * from parent to child bundles when the parent is NOT_FOUND.
+ * @param loader the loader to use when loading a bundle
+ * @param bundleName the complete bundle name including locale extension
+ * @param defaultLocale the default locale at the time getBundle was called
+ * @param parent the parent of the resource bundle being loaded. null if
+ * the bundle is a root bundle
+ * @return the bundle or null if the bundle could not be found in the cache
+ * or loaded.
+ */
+ private static Object findBundle(ClassLoader loader, String bundleName, Locale defaultLocale,
+ String baseName, Object parent) {
+ Object result;
+ synchronized (cacheList) {
+ // Before we do the real work of this method, see
+ // whether we need to do some housekeeping:
+ // If references to class loaders have been nulled out,
+ // remove all related information from the cache
+ Reference ref = referenceQueue.poll();
+ while (ref != null) {
+ cacheList.remove(((LoaderReference) ref).getCacheKey());
+ ref = referenceQueue.poll();
+ }
+
+ //check for bundle in cache
+ cacheKey.setKeyValues(loader, bundleName, defaultLocale);
+ result = cacheList.get(cacheKey);
+ if (result != null) {
+ cacheKey.clear();
+ return result;
+ }
+ // check to see if some other thread is building this bundle.
+ // Note that there is a rare chance that this thread is already
+ // working on this bundle, and in the process getBundle was called
+ // again, in which case we can't wait (4300693)
+ Thread builder = (Thread) underConstruction.get(cacheKey);
+ boolean beingBuilt = (builder != null && builder != Thread.currentThread());
+ //if some other thread is building the bundle...
+ if (beingBuilt) {
+ //while some other thread is building the bundle...
+ while (beingBuilt) {
+ cacheKey.clear();
+ try {
+ //Wait until the bundle is complete
+ cacheList.wait();
+ } catch (InterruptedException e) {
+ }
+ cacheKey.setKeyValues(loader, bundleName, defaultLocale);
+ beingBuilt = underConstruction.containsKey(cacheKey);
+ }
+ //if someone constructed the bundle for us, return it
+ result = cacheList.get(cacheKey);
+ if (result != null) {
+ cacheKey.clear();
+ return result;
+ }
+ }
+ //The bundle isn't in the cache, so we are now responsible for
+ //loading it and adding it to the cache.
+ final Object key = cacheKey.clone();
+ underConstruction.put(key, Thread.currentThread());
+ //the bundle is removed from the cache by putBundleInCache
+ cacheKey.clear();
+ }
+
+ //try loading the bundle via the class loader
+ result = loadBundle(loader, bundleName, defaultLocale);
+ if (result != null) {
+ // check whether we're still responsible for construction -
+ // a recursive call to getBundle might have handled it (4300693)
+ boolean constructing;
+ synchronized (cacheList) {
+ cacheKey.setKeyValues(loader, bundleName, defaultLocale);
+ constructing = underConstruction.get(cacheKey) == Thread.currentThread();
+ cacheKey.clear();
+ }
+ if (constructing) {
+ // set the bundle's parent and put it in the cache
+ final ResourceBundle bundle = (ResourceBundle)result;
+ if (parent != NOT_FOUND && bundle.parent == null) {
+ bundle.setParent((ResourceBundle) parent);
+ }
+ bundle.setLocale(baseName, bundleName);
+ putBundleInCache(loader, bundleName, defaultLocale, result);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Calculate the bundles along the search path from the base bundle to the
+ * bundle specified by baseName and locale.
+ * @param baseName the base bundle name
+ * @param locale the locale
+ * the search path.
+ *
+ */
+ private static Vector calculateBundleNames(String baseName, Locale locale) {
+ final Vector result = new Vector(MAX_BUNDLES_SEARCHED);
+ final String language = locale.getLanguage();
+ final int languageLength = language.length();
+ final String country = locale.getCountry();
+ final int countryLength = country.length();
+ final String variant = locale.getVariant();
+ final int variantLength = variant.length();
+
+ if (languageLength + countryLength + variantLength == 0) {
+ //The locale is "", "", "".
+ return result;
+ }
+ final StringBuffer temp = new StringBuffer(baseName);
+ temp.append('_');
+ temp.append(language);
+ if (languageLength > 0) {
+ result.addElement(temp.toString());
+ }
+
+ if (countryLength + variantLength == 0) {
+ return result;
+ }
+ temp.append('_');
+ temp.append(country);
+ if (countryLength > 0) {
+ result.addElement(temp.toString());
+ }
+
+ if (variantLength == 0) {
+ return result;
+ }
+ temp.append('_');
+ temp.append(variant);
+ result.addElement(temp.toString());
+
+ return result;
+ }
+
+ /**
+ * Find a bundle in the cache.
+ * @param loader the class loader that is responsible for loading the bundle.
+ * @param bundleName the complete name of the bundle including locale extension.
+ * ex. sun.text.resources.LocaleElements_fr_BE
+ * @param defaultLocale the default locale at the time getBundle was called
+ * @return the cached bundle. null if the bundle is not in the cache.
+ */
+ private static Object findBundleInCache(ClassLoader loader, String bundleName,
+ Locale defaultLocale) {
+ //Synchronize access to cacheList, cacheKey, and underConstruction
+ synchronized (cacheList) {
+ cacheKey.setKeyValues(loader, bundleName, defaultLocale);
+ Object result = cacheList.get(cacheKey);
+ cacheKey.clear();
+ return result;
+ }
+ }
+
+ /**
+ * Put a new bundle in the cache and notify waiting threads that a new
+ * bundle has been put in the cache.
+ * @param defaultLocale the default locale at the time getBundle was called
+ */
+ private static void putBundleInCache(ClassLoader loader, String bundleName,
+ Locale defaultLocale, Object value) {
+ //we use a static shared cacheKey but we use the lock in cacheList since
+ //the key is only used to interact with cacheList.
+ synchronized (cacheList) {
+ cacheKey.setKeyValues(loader, bundleName, defaultLocale);
+ cacheList.put(cacheKey.clone(), value);
+ underConstruction.remove(cacheKey);
+ cacheKey.clear();
+ //notify waiters that we're done constructing the bundle
+ cacheList.notifyAll();
+ }
+ }
+
+ /**
+ * Load a bundle through either the specified ClassLoader or from a ".properties" file
+ * and return the loaded bundle.
+ * @param loader the ClassLoader to use to load the bundle. If null, the system
+ * ClassLoader is used.
+ * @param bundleName the name of the resource to load. The name should be complete
+ * including a qualified class name followed by the locale extension.
+ * ex. sun.text.resources.LocaleElements_fr_BE
+ * @param defaultLocale the default locale at the time getBundle was called
+ * @return the bundle or null if none could be found.
+ */
+ private static Object loadBundle(final ClassLoader loader, String bundleName, Locale defaultLocale) {
+
+ // replace any '/'s with '.'s for the bug-for-bug compatible
+ // ClassLoader behavior. (5077272, see also 4872868)
+ String correctedBundleName = bundleName.replace('/', '.');
+
+ // Search for class file using class loader
+ try {
+ Class bundleClass;
+ if (loader != null) {
+ bundleClass = loader.loadClass(correctedBundleName);
+ } else {
+ bundleClass = Class.forName(correctedBundleName);
+ }
+ if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
+ Object myBundle = bundleClass.newInstance();
+ // Creating the instance may have triggered a recursive call to getBundle,
+ // in which case the bundle created by the recursive call would be in the
+ // cache now (4300693). For consistency, we'd then return the bundle from the cache.
+ Object otherBundle = findBundleInCache(loader, bundleName, defaultLocale);
+ if (otherBundle != null) {
+ return otherBundle;
+ } else {
+ return myBundle;
+ }
+ }
+ } catch (Exception e) {
+ } catch (LinkageError e) {
+ }
+
+ // Next search for a Properties file.
+ final String resName = bundleName.replace('.', '/') + ".properties";
+ InputStream stream = (InputStream)java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ if (loader != null) {
+ return loader.getResourceAsStream(resName);
+ } else {
+ return ClassLoader.getSystemResourceAsStream(resName);
+ }
+ }
+ }
+ );
+
+ if (stream != null) {
+ // make sure it is buffered
+ stream = new java.io.BufferedInputStream(stream);
+ try {
+ return new PropertyResourceBundle(stream);
+ } catch (Exception e) {
+ } finally {
+ try {
+ stream.close();
+ } catch (Exception e) {
+ // to avoid propagating an IOException back into the caller
+ // (I'm assuming this is never going to happen, and if it does,
+ // I'm obeying the precedent of swallowing exceptions set by the
+ // existing code above)
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets an object for the given key from this resource bundle.
+ * Returns null if this resource bundle does not contain an
+ * object for the given key.
+ *
+ * @param key the key for the desired object
+ * @exception NullPointerException if <code>key</code> is <code>null</code>
+ * @return the object for the given key, or null
+ */
+ protected abstract Object handleGetObject(String key);
+
+ /**
+ * Returns an enumeration of the keys.
+ *
+ */
+ public abstract Enumeration getKeys();
+}