0001: /*
0002: *
0003: * @(#)ResourceBundle.java 1.74 06/10/10
0004: *
0005: * Portions Copyright 2000-2006 Sun Microsystems, Inc. All Rights
0006: * Reserved. Use is subject to license terms.
0007: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0008: *
0009: * This program is free software; you can redistribute it and/or
0010: * modify it under the terms of the GNU General Public License version
0011: * 2 only, as published by the Free Software Foundation.
0012: *
0013: * This program is distributed in the hope that it will be useful, but
0014: * WITHOUT ANY WARRANTY; without even the implied warranty of
0015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0016: * General Public License version 2 for more details (a copy is
0017: * included at /legal/license.txt).
0018: *
0019: * You should have received a copy of the GNU General Public License
0020: * version 2 along with this work; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0022: * 02110-1301 USA
0023: *
0024: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0025: * Clara, CA 95054 or visit www.sun.com if you need additional
0026: * information or have any questions.
0027: */
0028:
0029: /*
0030: * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
0031: * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
0032: *
0033: * The original version of this source code and documentation
0034: * is copyrighted and owned by Taligent, Inc., a wholly-owned
0035: * subsidiary of IBM. These materials are provided under terms
0036: * of a License Agreement between Taligent and Sun. This technology
0037: * is protected by multiple US and International patents.
0038: *
0039: * This notice and attribution to Taligent may not be removed.
0040: * Taligent is a registered trademark of Taligent, Inc.
0041: *
0042: */
0043:
0044: package java.util;
0045:
0046: import java.io.InputStream;
0047: import java.lang.ref.Reference;
0048: import java.lang.ref.ReferenceQueue;
0049: import java.lang.ref.WeakReference;
0050: import sun.misc.SoftCache;
0051:
0052: /**
0053: *
0054: * Resource bundles contain locale-specific objects.
0055: * When your program needs a locale-specific resource,
0056: * a <code>String</code> for example, your program can load it
0057: * from the resource bundle that is appropriate for the
0058: * current user's locale. In this way, you can write
0059: * program code that is largely independent of the user's
0060: * locale isolating most, if not all, of the locale-specific
0061: * information in resource bundles.
0062: *
0063: * <p>
0064: * This allows you to write programs that can:
0065: * <UL type=SQUARE>
0066: * <LI> be easily localized, or translated, into different languages
0067: * <LI> handle multiple locales at once
0068: * <LI> be easily modified later to support even more locales
0069: * </UL>
0070: *
0071: * <P>
0072: * Resource bundles belong to families whose members share a common base
0073: * name, but whose names also have additional components that identify
0074: * their locales. For example, the base name of a family of resource
0075: * bundles might be "MyResources". The family should have a default
0076: * resource bundle which simply has the same name as its family -
0077: * "MyResources" - and will be used as the bundle of last resort if a
0078: * specific locale is not supported. The family can then provide as
0079: * many locale-specific members as needed, for example a German one
0080: * named "MyResources_de".
0081: *
0082: * <P>
0083: * Each resource bundle in a family contains the same items, but the items have
0084: * been translated for the locale represented by that resource bundle.
0085: * For example, both "MyResources" and "MyResources_de" may have a
0086: * <code>String</code> that's used on a button for canceling operations.
0087: * In "MyResources" the <code>String</code> may contain "Cancel" and in
0088: * "MyResources_de" it may contain "Abbrechen".
0089: *
0090: * <P>
0091: * If there are different resources for different countries, you
0092: * can make specializations: for example, "MyResources_de_CH" contains objects for
0093: * the German language (de) in Switzerland (CH). If you want to only
0094: * modify some of the resources
0095: * in the specialization, you can do so.
0096: *
0097: * <P>
0098: * When your program needs a locale-specific object, it loads
0099: * the <code>ResourceBundle</code> class using the
0100: * {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
0101: * method:
0102: * <blockquote>
0103: * <pre>
0104: * ResourceBundle myResources =
0105: * ResourceBundle.getBundle("MyResources", currentLocale);
0106: * </pre>
0107: * </blockquote>
0108: *
0109: * <P>
0110: * Resource bundles contain key/value pairs. The keys uniquely
0111: * identify a locale-specific object in the bundle. Here's an
0112: * example of a <code>ListResourceBundle</code> that contains
0113: * two key/value pairs:
0114: * <blockquote>
0115: * <pre>
0116: * public class MyResources extends ListResourceBundle {
0117: * public Object[][] getContents() {
0118: * return contents;
0119: * }
0120: * static final Object[][] contents = {
0121: * // LOCALIZE THIS
0122: * {"OkKey", "OK"},
0123: * {"CancelKey", "Cancel"},
0124: * // END OF MATERIAL TO LOCALIZE
0125: * };
0126: * }
0127: * </pre>
0128: * </blockquote>
0129: * Keys are always <code>String</code>s.
0130: * In this example, the keys are "OkKey" and "CancelKey".
0131: * In the above example, the values
0132: * are also <code>String</code>s--"OK" and "Cancel"--but
0133: * they don't have to be. The values can be any type of object.
0134: *
0135: * <P>
0136: * You retrieve an object from resource bundle using the appropriate
0137: * getter method. Because "OkKey" and "CancelKey"
0138: * are both strings, you would use <code>getString</code> to retrieve them:
0139: * <blockquote>
0140: * <pre>
0141: * button1 = new Button(myResources.getString("OkKey"));
0142: * button2 = new Button(myResources.getString("CancelKey"));
0143: * </pre>
0144: * </blockquote>
0145: * The getter methods all require the key as an argument and return
0146: * the object if found. If the object is not found, the getter method
0147: * throws a <code>MissingResourceException</code>.
0148: *
0149: * <P>
0150: * Besides <code>getString</code>, ResourceBundle also provides
0151: * a method for getting string arrays, <code>getStringArray</code>,
0152: * as well as a generic <code>getObject</code> method for any other
0153: * type of object. When using <code>getObject</code>, you'll
0154: * have to cast the result to the appropriate type. For example:
0155: * <blockquote>
0156: * <pre>
0157: * int[] myIntegers = (int[]) myResources.getObject("intList");
0158: * </pre>
0159: * </blockquote>
0160: *
0161: * <P>
0162: * The Java 2 platform provides two subclasses of <code>ResourceBundle</code>,
0163: * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
0164: * that provide a fairly simple way to create resources.
0165: * As you saw briefly in a previous example, <code>ListResourceBundle</code>
0166: * manages its resource as a List of key/value pairs.
0167: * <code>PropertyResourceBundle</code> uses a properties file to manage
0168: * its resources.
0169: *
0170: * <p>
0171: * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
0172: * do not suit your needs, you can write your own <code>ResourceBundle</code>
0173: * subclass. Your subclasses must override two methods: <code>handleGetObject</code>
0174: * and <code>getKeys()</code>.
0175: *
0176: * <P>
0177: * The following is a very simple example of a <code>ResourceBundle</code>
0178: * subclass, MyResources, that manages two resources (for a larger number of
0179: * resources you would probably use a <code>Hashtable</code>).
0180: * Notice that you don't need to supply a value if
0181: * a "parent-level" <code>ResourceBundle</code> handles the same
0182: * key with the same value (as for the okKey below).
0183: * <p><strong>Example:</strong>
0184: * <blockquote>
0185: * <pre>
0186: * // default (English language, United States)
0187: * public class MyResources extends ResourceBundle {
0188: * public Object handleGetObject(String key) {
0189: * if (key.equals("okKey")) return "Ok";
0190: * if (key.equals("cancelKey")) return "Cancel";
0191: * return null;
0192: * }
0193: * }
0194: *
0195: * // German language
0196: * public class MyResources_de extends MyResources {
0197: * public Object handleGetObject(String key) {
0198: * // don't need okKey, since parent level handles it.
0199: * if (key.equals("cancelKey")) return "Abbrechen";
0200: * return null;
0201: * }
0202: * }
0203: * </pre>
0204: * </blockquote>
0205: * You do not have to restrict yourself to using a single family of
0206: * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
0207: * exception messages, <code>ExceptionResources</code>
0208: * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
0209: * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
0210: * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
0211: *
0212: * @see ListResourceBundle
0213: * @see PropertyResourceBundle
0214: * @see MissingResourceException
0215: * @since JDK1.1
0216: */
0217: abstract public class ResourceBundle {
0218: /**
0219: * Static key used for resource lookups. Concurrent
0220: * access to this object is controlled by synchronizing cacheList,
0221: * not cacheKey. A static object is used to do cache lookups
0222: * for performance reasons - the assumption being that synchronization
0223: * has a lower overhead than object allocation and subsequent
0224: * garbage collection.
0225: */
0226: private static final ResourceCacheKey cacheKey = new ResourceCacheKey();
0227:
0228: /** initial size of the bundle cache */
0229: private static final int INITIAL_CACHE_SIZE = 25;
0230:
0231: /** capacity of cache consumed before it should grow */
0232: private static final float CACHE_LOAD_FACTOR = (float) 1.0;
0233:
0234: /**
0235: * Maximum length of one branch of the resource search path tree.
0236: * Used in getBundle.
0237: */
0238: private static final int MAX_BUNDLES_SEARCHED = 3;
0239:
0240: /**
0241: * This Hashtable is used to keep multiple threads from loading the
0242: * same bundle concurrently. The table entries are (cacheKey, thread)
0243: * where cacheKey is the key for the bundle that is under construction
0244: * and thread is the thread that is constructing the bundle.
0245: * This list is manipulated in findBundle and putBundleInCache.
0246: * Synchronization of this object is done through cacheList, not on
0247: * this object.
0248: */
0249: private static final Hashtable underConstruction = new Hashtable(
0250: MAX_BUNDLES_SEARCHED, CACHE_LOAD_FACTOR);
0251:
0252: /** constant indicating that no resource bundle was found */
0253: private static final Object NOT_FOUND = new Object();
0254:
0255: /**
0256: * The cache is a map from cache keys (with bundle base name,
0257: * locale, and class loader) to either a resource bundle
0258: * (if one has been found) or NOT_FOUND (if no bundle has
0259: * been found).
0260: * The cache is a SoftCache, allowing bundles to be
0261: * removed from the cache if they are no longer
0262: * needed. This will also allow the cache keys
0263: * to be reclaimed along with the ClassLoaders
0264: * they reference.
0265: * This variable would be better named "cache", but we keep the old
0266: * name for compatibility with some workarounds for bug 4212439.
0267: */
0268: private static SoftCache cacheList = new SoftCache(
0269: INITIAL_CACHE_SIZE, CACHE_LOAD_FACTOR);
0270:
0271: /**
0272: * Queue for reference objects referring to class loaders.
0273: */
0274: private static ReferenceQueue referenceQueue = new ReferenceQueue();
0275:
0276: /**
0277: * The parent bundle of this bundle.
0278: * The parent bundle is searched by {@link #getObject getObject}
0279: * when this bundle does not contain a particular resource.
0280: */
0281: protected ResourceBundle parent = null;
0282:
0283: /**
0284: * The locale for this bundle.
0285: */
0286: private Locale locale = null;
0287:
0288: /**
0289: * Sole constructor. (For invocation by subclass constructors, typically
0290: * implicit.)
0291: */
0292: public ResourceBundle() {
0293: }
0294:
0295: /**
0296: * Gets a string for the given key from this resource bundle or one of its parents.
0297: * Calling this method is equivalent to calling
0298: * <blockquote>
0299: * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
0300: * </blockquote>
0301: *
0302: * @param key the key for the desired string
0303: * @exception NullPointerException if <code>key</code> is <code>null</code>
0304: * @exception MissingResourceException if no object for the given key can be found
0305: * @exception ClassCastException if the object found for the given key is not a string
0306: * @return the string for the given key
0307: */
0308: public final String getString(String key) {
0309: return (String) getObject(key);
0310: }
0311:
0312: /**
0313: * Gets a string array for the given key from this resource bundle or one of its parents.
0314: * Calling this method is equivalent to calling
0315: * <blockquote>
0316: * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
0317: * </blockquote>
0318: *
0319: * @param key the key for the desired string array
0320: * @exception NullPointerException if <code>key</code> is <code>null</code>
0321: * @exception MissingResourceException if no object for the given key can be found
0322: * @exception ClassCastException if the object found for the given key is not a string array
0323: * @return the string array for the given key
0324: */
0325: public final String[] getStringArray(String key) {
0326: return (String[]) getObject(key);
0327: }
0328:
0329: /**
0330: * Gets an object for the given key from this resource bundle or one of its parents.
0331: * This method first tries to obtain the object from this resource bundle using
0332: * {@link #handleGetObject(java.lang.String) handleGetObject}.
0333: * If not successful, and the parent resource bundle is not null,
0334: * it calls the parent's <code>getObject</code> method.
0335: * If still not successful, it throws a MissingResourceException.
0336: *
0337: * @param key the key for the desired object
0338: * @exception NullPointerException if <code>key</code> is <code>null</code>
0339: * @exception MissingResourceException if no object for the given key can be found
0340: * @return the object for the given key
0341: */
0342: public final Object getObject(String key) {
0343: Object obj = handleGetObject(key);
0344: if (obj == null) {
0345: if (parent != null) {
0346: obj = parent.getObject(key);
0347: }
0348: if (obj == null)
0349: throw new MissingResourceException(
0350: "Can't find resource for bundle "
0351: + this .getClass().getName() + ", key "
0352: + key, this .getClass().getName(), key);
0353: }
0354: return obj;
0355: }
0356:
0357: /**
0358: * Returns the locale of this resource bundle. This method can be used after a
0359: * call to getBundle() to determine whether the resource bundle returned really
0360: * corresponds to the requested locale or is a fallback.
0361: *
0362: * @return the locale of this resource bundle
0363: */
0364: public Locale getLocale() {
0365: return locale;
0366: }
0367:
0368: /**
0369: * Sets the locale for this bundle. This is the locale that this
0370: * bundle actually represents and does not depend on how the
0371: * bundle was found by getBundle. Ex. if the user was looking
0372: * for fr_FR and getBundle found en_US, the bundle's locale would
0373: * be en_US, NOT fr_FR
0374: * @param baseName the bundle's base name
0375: * @param bundleName the complete bundle name including locale
0376: * extension.
0377: */
0378: private void setLocale(String baseName, String bundleName) {
0379: if (baseName.length() == bundleName.length()) {
0380: locale = new Locale("", "");
0381: } else if (baseName.length() < bundleName.length()) {
0382: int pos = baseName.length();
0383: String temp = bundleName.substring(pos + 1);
0384: pos = temp.indexOf('_');
0385: if (pos == -1) {
0386: locale = new Locale(temp, "", "");
0387: return;
0388: }
0389:
0390: String language = temp.substring(0, pos);
0391: temp = temp.substring(pos + 1);
0392: pos = temp.indexOf('_');
0393: if (pos == -1) {
0394: locale = new Locale(language, temp, "");
0395: return;
0396: }
0397:
0398: String country = temp.substring(0, pos);
0399: temp = temp.substring(pos + 1);
0400:
0401: locale = new Locale(language, country, temp);
0402: } else {
0403: //The base name is longer than the bundle name. Something is very wrong
0404: //with the calling code.
0405: throw new IllegalArgumentException();
0406: }
0407: }
0408:
0409: /*
0410: * Automatic determination of the ClassLoader to be used to load
0411: * resources on behalf of the client. N.B. The client is getLoader's
0412: * caller's caller.
0413: */
0414: private static ClassLoader getLoader() {
0415: Class[] stack = getClassContext();
0416: /* Magic number 2 identifies our caller's caller */
0417: Class c = stack[2];
0418: ClassLoader cl = (c == null) ? null : c.getClassLoader();
0419: if (cl == null) {
0420: cl = ClassLoader.getSystemClassLoader();
0421: }
0422: return cl;
0423: }
0424:
0425: private static native Class[] getClassContext();
0426:
0427: /**
0428: * Sets the parent bundle of this bundle.
0429: * The parent bundle is searched by {@link #getObject getObject}
0430: * when this bundle does not contain a particular resource.
0431: *
0432: * @param parent this bundle's parent bundle.
0433: */
0434: protected void setParent(ResourceBundle parent) {
0435: this .parent = parent;
0436: }
0437:
0438: /**
0439: * Key used for cached resource bundles. The key checks
0440: * the resource name, the class loader, and the default
0441: * locale to determine if the resource is a match to the
0442: * requested one. The loader may be null, but the
0443: * searchName and the default locale must have a non-null value.
0444: * Note that the default locale may change over time, and
0445: * lookup should always be based on the current default
0446: * locale (if at all).
0447: */
0448: private static final class ResourceCacheKey implements Cloneable {
0449: private LoaderReference loaderRef;
0450: private String searchName;
0451: private Locale defaultLocale;
0452: private int hashCodeCache;
0453:
0454: public boolean equals(Object other) {
0455: if (this == other) {
0456: return true;
0457: }
0458: try {
0459: final ResourceCacheKey otherEntry = (ResourceCacheKey) other;
0460: //quick check to see if they are not equal
0461: if (hashCodeCache != otherEntry.hashCodeCache) {
0462: return false;
0463: }
0464: //are the names the same?
0465: if (!searchName.equals(otherEntry.searchName)) {
0466: return false;
0467: }
0468: // are the default locales the same?
0469: if (defaultLocale == null) {
0470: if (otherEntry.defaultLocale != null) {
0471: return false;
0472: }
0473: } else {
0474: if (!defaultLocale.equals(otherEntry.defaultLocale)) {
0475: return false;
0476: }
0477: }
0478: //are refs (both non-null) or (both null)?
0479: if (loaderRef == null) {
0480: return otherEntry.loaderRef == null;
0481: } else {
0482: Object loaderRefValue = loaderRef.get();
0483: return (otherEntry.loaderRef != null)
0484: // with a null reference we can no longer find
0485: // out which class loader was referenced; so
0486: // treat it as unequal
0487: && (loaderRefValue != null)
0488: && (loaderRefValue == otherEntry.loaderRef
0489: .get());
0490: }
0491: } catch (NullPointerException e) {
0492: return false;
0493: } catch (ClassCastException e) {
0494: return false;
0495: }
0496: }
0497:
0498: public int hashCode() {
0499: return hashCodeCache;
0500: }
0501:
0502: public Object clone() {
0503: try {
0504: ResourceCacheKey clone = (ResourceCacheKey) super
0505: .clone();
0506: if (loaderRef != null) {
0507: clone.loaderRef = new LoaderReference(loaderRef
0508: .get(), referenceQueue, clone);
0509: }
0510: return clone;
0511: } catch (CloneNotSupportedException e) {
0512: //this should never happen
0513: throw new InternalError();
0514: }
0515:
0516: }
0517:
0518: public void setKeyValues(ClassLoader loader, String searchName,
0519: Locale defaultLocale) {
0520: this .searchName = searchName;
0521: hashCodeCache = searchName.hashCode();
0522: this .defaultLocale = defaultLocale;
0523: if (defaultLocale != null) {
0524: hashCodeCache ^= defaultLocale.hashCode();
0525: }
0526: if (loader == null) {
0527: this .loaderRef = null;
0528: } else {
0529: loaderRef = new LoaderReference(loader, referenceQueue,
0530: this );
0531: hashCodeCache ^= loader.hashCode();
0532: }
0533: }
0534:
0535: public void clear() {
0536: setKeyValues(null, "", null);
0537: }
0538: }
0539:
0540: /**
0541: * References to class loaders are weak references, so that they can be
0542: * garbage collected when nobody else is using them. The ResourceBundle
0543: * class has no reason to keep class loaders alive.
0544: */
0545: private static final class LoaderReference extends WeakReference {
0546: private ResourceCacheKey cacheKey;
0547:
0548: LoaderReference(Object referent, ReferenceQueue q,
0549: ResourceCacheKey key) {
0550: super (referent, q);
0551: cacheKey = key;
0552: }
0553:
0554: ResourceCacheKey getCacheKey() {
0555: return cacheKey;
0556: }
0557: }
0558:
0559: /**
0560: * Gets a resource bundle using the specified base name, the default locale,
0561: * and the caller's class loader. Calling this method is equivalent to calling
0562: * <blockquote>
0563: * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
0564: * </blockquote>
0565: * except that <code>getClassLoader()</code> is run with the security
0566: * privileges of <code>ResourceBundle</code>.
0567: * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
0568: * for a complete description of the search and instantiation strategy.
0569: *
0570: * @param baseName the base name of the resource bundle, a fully qualified class name
0571: * @exception java.lang.NullPointerException
0572: * if <code>baseName</code> is <code>null</code>
0573: * @exception MissingResourceException
0574: * if no resource bundle for the specified base name can be found
0575: * @return a resource bundle for the given base name and the default locale
0576: */
0577: public static final ResourceBundle getBundle(String baseName) {
0578: return getBundleImpl(baseName, Locale.getDefault(),
0579: /* must determine loader here, else we break stack invariant */
0580: getLoader());
0581: }
0582:
0583: /**
0584: * Gets a resource bundle using the specified base name and locale,
0585: * and the caller's class loader. Calling this method is equivalent to calling
0586: * <blockquote>
0587: * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
0588: * </blockquote>
0589: * except that <code>getClassLoader()</code> is run with the security
0590: * privileges of <code>ResourceBundle</code>.
0591: * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
0592: * for a complete description of the search and instantiation strategy.
0593: *
0594: * @param baseName the base name of the resource bundle, a fully qualified class name
0595: * @param locale the locale for which a resource bundle is desired
0596: * @exception java.lang.NullPointerException
0597: * if <code>baseName</code> or <code>locale</code> is <code>null</code>
0598: * @exception MissingResourceException
0599: * if no resource bundle for the specified base name can be found
0600: * @return a resource bundle for the given base name and locale
0601: */
0602: public static final ResourceBundle getBundle(String baseName,
0603: Locale locale) {
0604: return getBundleImpl(baseName, locale, getLoader());
0605: }
0606:
0607: /**
0608: * Gets a resource bundle using the specified base name, locale, and class loader.
0609: *
0610: * <p>
0611: * Conceptually, <code>getBundle</code> uses the following strategy for locating and instantiating
0612: * resource bundles:
0613: * <p>
0614: * <code>getBundle</code> uses the base name, the specified locale, and the default
0615: * locale (obtained from {@link java.util.Locale#getDefault() Locale.getDefault})
0616: * to generate a sequence of <em>candidate bundle names</em>.
0617: * If the specified locale's language, country, and variant are all empty
0618: * strings, then the base name is the only candidate bundle name.
0619: * Otherwise, the following sequence is generated from the attribute
0620: * values of the specified locale (language1, country1, and variant1)
0621: * and of the default locale (language2, country2, and variant2):
0622: * <ul>
0623: * <li> baseName + "_" + language1 + "_" + country1 + "_" + variant1
0624: * <li> baseName + "_" + language1 + "_" + country1
0625: * <li> baseName + "_" + language1
0626: * <li> baseName + "_" + language2 + "_" + country2 + "_" + variant2
0627: * <li> baseName + "_" + language2 + "_" + country2
0628: * <li> baseName + "_" + language2
0629: * <li> baseName
0630: * </ul>
0631: * <p>
0632: * Candidate bundle names where the final component is an empty string are omitted.
0633: * For example, if country1 is an empty string, the second candidate bundle name is omitted.
0634: *
0635: * <p>
0636: * <code>getBundle</code> then iterates over the candidate bundle names to find the first
0637: * one for which it can <em>instantiate</em> an actual resource bundle. For each candidate
0638: * bundle name, it attempts to create a resource bundle:
0639: * <ul>
0640: * <li>
0641: * First, it attempts to load a class using the candidate bundle name.
0642: * If such a class can be found and loaded using the specified class loader, is assignment
0643: * compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated,
0644: * <code>getBundle</code> creates a new instance of this class and uses it as the <em>result
0645: * resource bundle</em>.
0646: * <li>
0647: * Otherwise, <code>getBundle</code> attempts to locate a property resource file.
0648: * It generates a path name from the candidate bundle name by replacing all "." characters
0649: * with "/" and appending the string ".properties".
0650: * It attempts to find a "resource" with this name using
0651: * {@link java.lang.ClassLoader#getResource(java.lang.String) ClassLoader.getResource}.
0652: * (Note that a "resource" in the sense of <code>getResource</code> has nothing to do with
0653: * the contents of a resource bundle, it is just a container of data, such as a file.)
0654: * If it finds a "resource", it attempts to create a new
0655: * {@link PropertyResourceBundle} instance from its contents.
0656: * If successful, this instance becomes the <em>result resource bundle</em>.
0657: * </ul>
0658: *
0659: * <p>
0660: * If no result resource bundle has been found, a <code>MissingResourceException</code>
0661: * is thrown.
0662: *
0663: * <p>
0664: * Once a result resource bundle has been found, its parent chain is instantiated.
0665: * <code>getBundle</code> iterates over the candidate bundle names that can be
0666: * obtained by successively removing variant, country, and language
0667: * (each time with the preceding "_") from the bundle name of the result resource bundle.
0668: * As above, candidate bundle names where the final component is an empty string are omitted.
0669: * With each of the candidate bundle names it attempts to instantiate a resource bundle, as
0670: * described above.
0671: * Whenever it succeeds, it calls the previously instantiated resource
0672: * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method
0673: * with the new resource bundle, unless the previously instantiated resource
0674: * bundle already has a non-null parent.
0675: *
0676: * <p>
0677: * Implementations of <code>getBundle</code> may cache instantiated resource bundles
0678: * and return the same resource bundle instance multiple times. They may also
0679: * vary the sequence in which resource bundles are instantiated as long as the
0680: * selection of the result resource bundle and its parent chain are compatible with
0681: * the description above.
0682: *
0683: * <p>
0684: * The <code>baseName</code> argument should be a fully qualified class name. However, for
0685: * compatibility with earlier versions, Sun's Java 2 runtime environments do not verify this,
0686: * and so it is possible to access <code>PropertyResourceBundle</code>s by specifying a
0687: * path name (using "/") instead of a fully qualified class name (using ".").
0688: *
0689: * <p>
0690: * <strong>Example:</strong> The following class and property files are provided:
0691: * MyResources.class, MyResources_fr_CH.properties, MyResources_fr_CH.class,
0692: * MyResources_fr.properties, MyResources_en.properties, MyResources_es_ES.class.
0693: * The contents of all files are valid (that is, public non-abstract subclasses of ResourceBundle for
0694: * the ".class" files, syntactically correct ".properties" files).
0695: * The default locale is <code>Locale("en", "GB")</code>.
0696: * <p>
0697: * Calling <code>getBundle</code> with the shown locale argument values instantiates
0698: * resource bundles from the following sources:
0699: * <ul>
0700: * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class
0701: * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent MyResources.class
0702: * <li>Locale("de", "DE"): result MyResources_en.properties, parent MyResources.class
0703: * <li>Locale("en", "US"): result MyResources_en.properties, parent MyResources.class
0704: * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent MyResources.class
0705: * </ul>
0706: * The file MyResources_fr_CH.properties is never used because it is hidden by
0707: * MyResources_fr_CH.class.
0708: *
0709: * <p>
0710: *
0711: * @param baseName the base name of the resource bundle, a fully qualified class name
0712: * @param locale the locale for which a resource bundle is desired
0713: * @param loader the class loader from which to load the resource bundle
0714: * @exception java.lang.NullPointerException
0715: * if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
0716: * @exception MissingResourceException
0717: * if no resource bundle for the specified base name can be found
0718: * @return a resource bundle for the given base name and locale
0719: * @since 1.2
0720: */
0721: public static ResourceBundle getBundle(String baseName,
0722: Locale locale, ClassLoader loader) {
0723: if (loader == null) {
0724: throw new NullPointerException();
0725: }
0726: return getBundleImpl(baseName, locale, loader);
0727: }
0728:
0729: private static ResourceBundle getBundleImpl(String baseName,
0730: Locale locale, ClassLoader loader) {
0731: if (baseName == null) {
0732: throw new NullPointerException();
0733: }
0734:
0735: //fast path the case where the bundle is cached
0736: String bundleName = baseName;
0737: String localeSuffix = locale.toString();
0738: if (localeSuffix.length() > 0) {
0739: bundleName += "_" + localeSuffix;
0740: } else if (locale.getVariant().length() > 0) {
0741: //This corrects some strange behavior in Locale where
0742: //new Locale("", "", "VARIANT").toString == ""
0743: bundleName += "___" + locale.getVariant();
0744: }
0745:
0746: // The default locale may influence the lookup result, and
0747: // it may change, so we get it here once.
0748: Locale defaultLocale = Locale.getDefault();
0749:
0750: Object lookup = findBundleInCache(loader, bundleName,
0751: defaultLocale);
0752: if (lookup == NOT_FOUND) {
0753: throwMissingResourceException(baseName, locale);
0754: } else if (lookup != null) {
0755: return (ResourceBundle) lookup;
0756: }
0757:
0758: //The bundle was not cached, so start doing lookup at the root
0759: //Resources are loaded starting at the root and working toward
0760: //the requested bundle.
0761:
0762: //If findBundle returns null, we become responsible for defining
0763: //the bundle, and must call putBundleInCache to complete this
0764: //task. This is critical because other threads may be waiting
0765: //for us to finish.
0766:
0767: Object parent = NOT_FOUND;
0768: try {
0769: //locate the root bundle and work toward the desired child
0770: Object root = findBundle(loader, baseName, defaultLocale,
0771: baseName, null);
0772: if (root == null) {
0773: putBundleInCache(loader, baseName, defaultLocale,
0774: NOT_FOUND);
0775: root = NOT_FOUND;
0776: }
0777:
0778: // Search the main branch of the search tree.
0779: // We need to keep references to the bundles we find on the main path
0780: // so they don't get garbage collected before we get to propagate().
0781: final Vector names = calculateBundleNames(baseName, locale);
0782: Vector bundlesFound = new Vector(MAX_BUNDLES_SEARCHED);
0783: // if we found the root bundle and no other bundle names are needed
0784: // we can stop here. We don't need to search or load anything further.
0785: boolean foundInMainBranch = (root != NOT_FOUND && names
0786: .size() == 0);
0787:
0788: if (!foundInMainBranch) {
0789: parent = root;
0790: for (int i = 0; i < names.size(); i++) {
0791: bundleName = (String) names.elementAt(i);
0792: lookup = findBundle(loader, bundleName,
0793: defaultLocale, baseName, parent);
0794: bundlesFound.addElement(lookup);
0795: if (lookup != null) {
0796: parent = lookup;
0797: foundInMainBranch = true;
0798: }
0799: }
0800: }
0801: parent = root;
0802: if (!foundInMainBranch) {
0803: //we didn't find anything on the main branch, so we do the fallback branch
0804: final Vector fallbackNames = calculateBundleNames(
0805: baseName, defaultLocale);
0806: for (int i = 0; i < fallbackNames.size(); i++) {
0807: bundleName = (String) fallbackNames.elementAt(i);
0808: if (names.contains(bundleName)) {
0809: //the fallback branch intersects the main branch so we can stop now.
0810: break;
0811: }
0812: lookup = findBundle(loader, bundleName,
0813: defaultLocale, baseName, parent);
0814: if (lookup != null) {
0815: parent = lookup;
0816: } else {
0817: //propagate the parent to the child. We can do this
0818: //here because we are in the default path.
0819: putBundleInCache(loader, bundleName,
0820: defaultLocale, parent);
0821: }
0822: }
0823: }
0824: //propagate the inheritance/fallback down through the main branch
0825: parent = propagate(loader, names, bundlesFound,
0826: defaultLocale, parent);
0827: } catch (Exception e) {
0828: //We should never get here unless there has been a change
0829: //to the code that doesn't catch it's own exceptions.
0830: cleanUpConstructionList();
0831: throwMissingResourceException(baseName, locale);
0832: } catch (Error e) {
0833: //The only Error that can currently hit this code is a ThreadDeathError
0834: //but errors might be added in the future, so we'll play it safe and
0835: //clean up.
0836: cleanUpConstructionList();
0837: throw e;
0838: }
0839: if (parent == NOT_FOUND) {
0840: throwMissingResourceException(baseName, locale);
0841: }
0842: return (ResourceBundle) parent;
0843: }
0844:
0845: /**
0846: * propagate bundles from the root down the specified branch of the search tree.
0847: * @param loader the class loader for the bundles
0848: * @param names the names of the bundles along search path
0849: * @param bundlesFound the bundles corresponding to the names (some may be null)
0850: * @param defaultLocale the default locale at the time getBundle was called
0851: * @param parent the parent of the first bundle in the path (the root bundle)
0852: * @return the value of the last bundle along the path
0853: */
0854: private static Object propagate(ClassLoader loader, Vector names,
0855: Vector bundlesFound, Locale defaultLocale, Object parent) {
0856: for (int i = 0; i < names.size(); i++) {
0857: final String bundleName = (String) names.elementAt(i);
0858: final Object lookup = bundlesFound.elementAt(i);
0859: if (lookup == null) {
0860: putBundleInCache(loader, bundleName, defaultLocale,
0861: parent);
0862: } else {
0863: parent = lookup;
0864: }
0865: }
0866: return parent;
0867: }
0868:
0869: /** Throw a MissingResourceException with proper message */
0870: private static void throwMissingResourceException(String baseName,
0871: Locale locale) throws MissingResourceException {
0872: throw new MissingResourceException(
0873: "Can't find bundle for base name " + baseName
0874: + ", locale " + locale,
0875: baseName + "_" + locale, "");
0876: }
0877:
0878: /**
0879: * Remove any entries this thread may have in the construction list.
0880: * This is done as cleanup in the case where a bundle can't be
0881: * constructed.
0882: */
0883: private static void cleanUpConstructionList() {
0884: synchronized (cacheList) {
0885: final Collection entries = underConstruction.values();
0886: final Thread this Thread = Thread.currentThread();
0887: while (entries.remove(this Thread)) {
0888: }
0889: }
0890: }
0891:
0892: /**
0893: * Find a bundle in the cache or load it via the loader or a property file.
0894: * If the bundle isn't found, an entry is put in the constructionCache
0895: * and null is returned. If null is returned, the caller must define the bundle
0896: * by calling putBundleInCache. This routine also propagates NOT_FOUND values
0897: * from parent to child bundles when the parent is NOT_FOUND.
0898: * @param loader the loader to use when loading a bundle
0899: * @param bundleName the complete bundle name including locale extension
0900: * @param defaultLocale the default locale at the time getBundle was called
0901: * @param parent the parent of the resource bundle being loaded. null if
0902: * the bundle is a root bundle
0903: * @return the bundle or null if the bundle could not be found in the cache
0904: * or loaded.
0905: */
0906: private static Object findBundle(ClassLoader loader,
0907: String bundleName, Locale defaultLocale, String baseName,
0908: Object parent) {
0909: Object result;
0910: synchronized (cacheList) {
0911: // Before we do the real work of this method, see
0912: // whether we need to do some housekeeping:
0913: // If references to class loaders have been nulled out,
0914: // remove all related information from the cache
0915: Reference ref = referenceQueue.poll();
0916: while (ref != null) {
0917: cacheList.remove(((LoaderReference) ref).getCacheKey());
0918: ref = referenceQueue.poll();
0919: }
0920:
0921: //check for bundle in cache
0922: cacheKey.setKeyValues(loader, bundleName, defaultLocale);
0923: result = cacheList.get(cacheKey);
0924: if (result != null) {
0925: cacheKey.clear();
0926: return result;
0927: }
0928: // check to see if some other thread is building this bundle.
0929: // Note that there is a rare chance that this thread is already
0930: // working on this bundle, and in the process getBundle was called
0931: // again, in which case we can't wait (4300693)
0932: Thread builder = (Thread) underConstruction.get(cacheKey);
0933: boolean beingBuilt = (builder != null && builder != Thread
0934: .currentThread());
0935: //if some other thread is building the bundle...
0936: if (beingBuilt) {
0937: //while some other thread is building the bundle...
0938: while (beingBuilt) {
0939: cacheKey.clear();
0940: try {
0941: //Wait until the bundle is complete
0942: cacheList.wait();
0943: } catch (InterruptedException e) {
0944: }
0945: cacheKey.setKeyValues(loader, bundleName,
0946: defaultLocale);
0947: beingBuilt = underConstruction
0948: .containsKey(cacheKey);
0949: }
0950: //if someone constructed the bundle for us, return it
0951: result = cacheList.get(cacheKey);
0952: if (result != null) {
0953: cacheKey.clear();
0954: return result;
0955: }
0956: }
0957: //The bundle isn't in the cache, so we are now responsible for
0958: //loading it and adding it to the cache.
0959: final Object key = cacheKey.clone();
0960: underConstruction.put(key, Thread.currentThread());
0961: //the bundle is removed from the cache by putBundleInCache
0962: cacheKey.clear();
0963: }
0964:
0965: //try loading the bundle via the class loader
0966: result = loadBundle(loader, bundleName, defaultLocale);
0967: if (result != null) {
0968: // check whether we're still responsible for construction -
0969: // a recursive call to getBundle might have handled it (4300693)
0970: boolean constructing;
0971: synchronized (cacheList) {
0972: cacheKey
0973: .setKeyValues(loader, bundleName, defaultLocale);
0974: constructing = underConstruction.get(cacheKey) == Thread
0975: .currentThread();
0976: cacheKey.clear();
0977: }
0978: if (constructing) {
0979: // set the bundle's parent and put it in the cache
0980: final ResourceBundle bundle = (ResourceBundle) result;
0981: if (parent != NOT_FOUND && bundle.parent == null) {
0982: bundle.setParent((ResourceBundle) parent);
0983: }
0984: bundle.setLocale(baseName, bundleName);
0985: putBundleInCache(loader, bundleName, defaultLocale,
0986: result);
0987: }
0988: }
0989: return result;
0990: }
0991:
0992: /**
0993: * Calculate the bundles along the search path from the base bundle to the
0994: * bundle specified by baseName and locale.
0995: * @param baseName the base bundle name
0996: * @param locale the locale
0997: * @param names the vector used to return the names of the bundles along
0998: * the search path.
0999: *
1000: */
1001: private static Vector calculateBundleNames(String baseName,
1002: Locale locale) {
1003: final Vector result = new Vector(MAX_BUNDLES_SEARCHED);
1004: final String language = locale.getLanguage();
1005: final int languageLength = language.length();
1006: final String country = locale.getCountry();
1007: final int countryLength = country.length();
1008: final String variant = locale.getVariant();
1009: final int variantLength = variant.length();
1010:
1011: if (languageLength + countryLength + variantLength == 0) {
1012: //The locale is "", "", "".
1013: return result;
1014: }
1015: final StringBuffer temp = new StringBuffer(baseName);
1016: temp.append('_');
1017: temp.append(language);
1018: if (languageLength > 0) {
1019: result.addElement(temp.toString());
1020: }
1021:
1022: if (countryLength + variantLength == 0) {
1023: return result;
1024: }
1025: temp.append('_');
1026: temp.append(country);
1027: if (countryLength > 0) {
1028: result.addElement(temp.toString());
1029: }
1030:
1031: if (variantLength == 0) {
1032: return result;
1033: }
1034: temp.append('_');
1035: temp.append(variant);
1036: result.addElement(temp.toString());
1037:
1038: return result;
1039: }
1040:
1041: /**
1042: * Find a bundle in the cache.
1043: * @param loader the class loader that is responsible for loading the bundle.
1044: * @param bundleName the complete name of the bundle including locale extension.
1045: * ex. sun.text.resources.LocaleElements_fr_BE
1046: * @param defaultLocale the default locale at the time getBundle was called
1047: * @return the cached bundle. null if the bundle is not in the cache.
1048: */
1049: private static Object findBundleInCache(ClassLoader loader,
1050: String bundleName, Locale defaultLocale) {
1051: //Synchronize access to cacheList, cacheKey, and underConstruction
1052: synchronized (cacheList) {
1053: cacheKey.setKeyValues(loader, bundleName, defaultLocale);
1054: Object result = cacheList.get(cacheKey);
1055: cacheKey.clear();
1056: return result;
1057: }
1058: }
1059:
1060: /**
1061: * Put a new bundle in the cache and notify waiting threads that a new
1062: * bundle has been put in the cache.
1063: * @param defaultLocale the default locale at the time getBundle was called
1064: */
1065: private static void putBundleInCache(ClassLoader loader,
1066: String bundleName, Locale defaultLocale, Object value) {
1067: //we use a static shared cacheKey but we use the lock in cacheList since
1068: //the key is only used to interact with cacheList.
1069: synchronized (cacheList) {
1070: cacheKey.setKeyValues(loader, bundleName, defaultLocale);
1071: cacheList.put(cacheKey.clone(), value);
1072: underConstruction.remove(cacheKey);
1073: cacheKey.clear();
1074: //notify waiters that we're done constructing the bundle
1075: cacheList.notifyAll();
1076: }
1077: }
1078:
1079: /**
1080: * Load a bundle through either the specified ClassLoader or from a ".properties" file
1081: * and return the loaded bundle.
1082: * @param loader the ClassLoader to use to load the bundle. If null, the system
1083: * ClassLoader is used.
1084: * @param bundleName the name of the resource to load. The name should be complete
1085: * including a qualified class name followed by the locale extension.
1086: * ex. sun.text.resources.LocaleElements_fr_BE
1087: * @param defaultLocale the default locale at the time getBundle was called
1088: * @return the bundle or null if none could be found.
1089: */
1090: private static Object loadBundle(final ClassLoader loader,
1091: String bundleName, Locale defaultLocale) {
1092:
1093: // replace any '/'s with '.'s for the bug-for-bug compatible
1094: // ClassLoader behavior. (5077272, see also 4872868)
1095: String correctedBundleName = bundleName.replace('/', '.');
1096:
1097: // Search for class file using class loader
1098: try {
1099: Class bundleClass;
1100: if (loader != null) {
1101: bundleClass = loader.loadClass(correctedBundleName);
1102: } else {
1103: bundleClass = Class.forName(correctedBundleName);
1104: }
1105: if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
1106: Object myBundle = bundleClass.newInstance();
1107: // Creating the instance may have triggered a recursive call to getBundle,
1108: // in which case the bundle created by the recursive call would be in the
1109: // cache now (4300693). For consistency, we'd then return the bundle from the cache.
1110: Object otherBundle = findBundleInCache(loader,
1111: bundleName, defaultLocale);
1112: if (otherBundle != null) {
1113: return otherBundle;
1114: } else {
1115: return myBundle;
1116: }
1117: }
1118: } catch (Exception e) {
1119: } catch (LinkageError e) {
1120: }
1121:
1122: // Next search for a Properties file.
1123: final String resName = bundleName.replace('.', '/')
1124: + ".properties";
1125: InputStream stream = (InputStream) java.security.AccessController
1126: .doPrivileged(new java.security.PrivilegedAction() {
1127: public Object run() {
1128: if (loader != null) {
1129: return loader.getResourceAsStream(resName);
1130: } else {
1131: return ClassLoader
1132: .getSystemResourceAsStream(resName);
1133: }
1134: }
1135: });
1136:
1137: if (stream != null) {
1138: // make sure it is buffered
1139: stream = new java.io.BufferedInputStream(stream);
1140: try {
1141: return new PropertyResourceBundle(stream);
1142: } catch (Exception e) {
1143: } finally {
1144: try {
1145: stream.close();
1146: } catch (Exception e) {
1147: // to avoid propagating an IOException back into the caller
1148: // (I'm assuming this is never going to happen, and if it does,
1149: // I'm obeying the precedent of swallowing exceptions set by the
1150: // existing code above)
1151: }
1152: }
1153: }
1154: return null;
1155: }
1156:
1157: /**
1158: * Gets an object for the given key from this resource bundle.
1159: * Returns null if this resource bundle does not contain an
1160: * object for the given key.
1161: *
1162: * @param key the key for the desired object
1163: * @exception NullPointerException if <code>key</code> is <code>null</code>
1164: * @return the object for the given key, or null
1165: */
1166: protected abstract Object handleGetObject(String key);
1167:
1168: /**
1169: * Returns an enumeration of the keys.
1170: *
1171: */
1172: public abstract Enumeration getKeys();
1173: }
|