KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > util > ResourceBundle


1 /*
2  * @(#)ResourceBundle.java 1.73 05/11/04
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 /*
9  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
10  * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
11  *
12  * The original version of this source code and documentation
13  * is copyrighted and owned by Taligent, Inc., a wholly-owned
14  * subsidiary of IBM. These materials are provided under terms
15  * of a License Agreement between Taligent and Sun. This technology
16  * is protected by multiple US and International patents.
17  *
18  * This notice and attribution to Taligent may not be removed.
19  * Taligent is a registered trademark of Taligent, Inc.
20  *
21  */

22
23 package java.util;
24
25 import java.io.InputStream JavaDoc;
26 import java.lang.ref.Reference JavaDoc;
27 import java.lang.ref.ReferenceQueue JavaDoc;
28 import java.lang.ref.WeakReference JavaDoc;
29 import sun.misc.SoftCache;
30
31 /**
32  *
33  * Resource bundles contain locale-specific objects.
34  * When your program needs a locale-specific resource,
35  * a <code>String</code> for example, your program can load it
36  * from the resource bundle that is appropriate for the
37  * current user's locale. In this way, you can write
38  * program code that is largely independent of the user's
39  * locale isolating most, if not all, of the locale-specific
40  * information in resource bundles.
41  *
42  * <p>
43  * This allows you to write programs that can:
44  * <UL type=SQUARE>
45  * <LI> be easily localized, or translated, into different languages
46  * <LI> handle multiple locales at once
47  * <LI> be easily modified later to support even more locales
48  * </UL>
49  *
50  * <P>
51  * Resource bundles belong to families whose members share a common base
52  * name, but whose names also have additional components that identify
53  * their locales. For example, the base name of a family of resource
54  * bundles might be "MyResources". The family should have a default
55  * resource bundle which simply has the same name as its family -
56  * "MyResources" - and will be used as the bundle of last resort if a
57  * specific locale is not supported. The family can then provide as
58  * many locale-specific members as needed, for example a German one
59  * named "MyResources_de".
60  *
61  * <P>
62  * Each resource bundle in a family contains the same items, but the items have
63  * been translated for the locale represented by that resource bundle.
64  * For example, both "MyResources" and "MyResources_de" may have a
65  * <code>String</code> that's used on a button for canceling operations.
66  * In "MyResources" the <code>String</code> may contain "Cancel" and in
67  * "MyResources_de" it may contain "Abbrechen".
68  *
69  * <P>
70  * If there are different resources for different countries, you
71  * can make specializations: for example, "MyResources_de_CH" contains objects for
72  * the German language (de) in Switzerland (CH). If you want to only
73  * modify some of the resources
74  * in the specialization, you can do so.
75  *
76  * <P>
77  * When your program needs a locale-specific object, it loads
78  * the <code>ResourceBundle</code> class using the
79  * {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
80  * method:
81  * <blockquote>
82  * <pre>
83  * ResourceBundle myResources =
84  * ResourceBundle.getBundle("MyResources", currentLocale);
85  * </pre>
86  * </blockquote>
87  *
88  * <P>
89  * Resource bundles contain key/value pairs. The keys uniquely
90  * identify a locale-specific object in the bundle. Here's an
91  * example of a <code>ListResourceBundle</code> that contains
92  * two key/value pairs:
93  * <blockquote>
94  * <pre>
95  * public class MyResources extends ListResourceBundle {
96  * protected Object[][] getContents() {
97  * return new Object[][] = {
98  * // LOCALIZE THIS
99  * {"OkKey", "OK"},
100  * {"CancelKey", "Cancel"},
101  * // END OF MATERIAL TO LOCALIZE
102  * };
103  * }
104  * }
105  * </pre>
106  * </blockquote>
107  * Keys are always <code>String</code>s.
108  * In this example, the keys are "OkKey" and "CancelKey".
109  * In the above example, the values
110  * are also <code>String</code>s--"OK" and "Cancel"--but
111  * they don't have to be. The values can be any type of object.
112  *
113  * <P>
114  * You retrieve an object from resource bundle using the appropriate
115  * getter method. Because "OkKey" and "CancelKey"
116  * are both strings, you would use <code>getString</code> to retrieve them:
117  * <blockquote>
118  * <pre>
119  * button1 = new Button(myResources.getString("OkKey"));
120  * button2 = new Button(myResources.getString("CancelKey"));
121  * </pre>
122  * </blockquote>
123  * The getter methods all require the key as an argument and return
124  * the object if found. If the object is not found, the getter method
125  * throws a <code>MissingResourceException</code>.
126  *
127  * <P>
128  * Besides <code>getString</code>, ResourceBundle also provides
129  * a method for getting string arrays, <code>getStringArray</code>,
130  * as well as a generic <code>getObject</code> method for any other
131  * type of object. When using <code>getObject</code>, you'll
132  * have to cast the result to the appropriate type. For example:
133  * <blockquote>
134  * <pre>
135  * int[] myIntegers = (int[]) myResources.getObject("intList");
136  * </pre>
137  * </blockquote>
138  *
139  * <P>
140  * The Java 2 platform provides two subclasses of <code>ResourceBundle</code>,
141  * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
142  * that provide a fairly simple way to create resources.
143  * As you saw briefly in a previous example, <code>ListResourceBundle</code>
144  * manages its resource as a List of key/value pairs.
145  * <code>PropertyResourceBundle</code> uses a properties file to manage
146  * its resources.
147  *
148  * <p>
149  * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
150  * do not suit your needs, you can write your own <code>ResourceBundle</code>
151  * subclass. Your subclasses must override two methods: <code>handleGetObject</code>
152  * and <code>getKeys()</code>.
153  *
154  * <P>
155  * The following is a very simple example of a <code>ResourceBundle</code>
156  * subclass, MyResources, that manages two resources (for a larger number of
157  * resources you would probably use a <code>Hashtable</code>).
158  * Notice that you don't need to supply a value if
159  * a "parent-level" <code>ResourceBundle</code> handles the same
160  * key with the same value (as for the okKey below).
161  * <p><strong>Example:</strong>
162  * <blockquote>
163  * <pre>
164  * // default (English language, United States)
165  * public class MyResources extends ResourceBundle {
166  * public Object handleGetObject(String key) {
167  * if (key.equals("okKey")) return "Ok";
168  * if (key.equals("cancelKey")) return "Cancel";
169  * return null;
170  * }
171  * }
172  *
173  * // German language
174  * public class MyResources_de extends MyResources {
175  * public Object handleGetObject(String key) {
176  * // don't need okKey, since parent level handles it.
177  * if (key.equals("cancelKey")) return "Abbrechen";
178  * return null;
179  * }
180  * }
181  * </pre>
182  * </blockquote>
183  * You do not have to restrict yourself to using a single family of
184  * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
185  * exception messages, <code>ExceptionResources</code>
186  * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
187  * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
188  * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
189  *
190  * @see ListResourceBundle
191  * @see PropertyResourceBundle
192  * @see MissingResourceException
193  * @since JDK1.1
194  */

195 abstract public class ResourceBundle {
196     /**
197      * Static key used for resource lookups. Concurrent
198      * access to this object is controlled by synchronizing cacheList,
199      * not cacheKey. A static object is used to do cache lookups
200      * for performance reasons - the assumption being that synchronization
201      * has a lower overhead than object allocation and subsequent
202      * garbage collection.
203      */

204     private static final ResourceCacheKey cacheKey = new ResourceCacheKey();
205
206     /** initial size of the bundle cache */
207     private static final int INITIAL_CACHE_SIZE = 25;
208
209     /** capacity of cache consumed before it should grow */
210     private static final float CACHE_LOAD_FACTOR = (float)1.0;
211
212     /**
213      * Maximum length of one branch of the resource search path tree.
214      * Used in getBundle.
215      */

216     private static final int MAX_BUNDLES_SEARCHED = 3;
217
218     /**
219      * This Hashtable is used to keep multiple threads from loading the
220      * same bundle concurrently. The table entries are (cacheKey, thread)
221      * where cacheKey is the key for the bundle that is under construction
222      * and thread is the thread that is constructing the bundle.
223      * This list is manipulated in findBundle and putBundleInCache.
224      * Synchronization of this object is done through cacheList, not on
225      * this object.
226      */

227     private static final Hashtable JavaDoc underConstruction = new Hashtable JavaDoc(MAX_BUNDLES_SEARCHED, CACHE_LOAD_FACTOR);
228
229     /** constant indicating that no resource bundle was found */
230     private static final Object JavaDoc NOT_FOUND = new Object JavaDoc();
231
232     /**
233      * The cache is a map from cache keys (with bundle base name,
234      * locale, and class loader) to either a resource bundle
235      * (if one has been found) or NOT_FOUND (if no bundle has
236      * been found).
237      * The cache is a SoftCache, allowing bundles to be
238      * removed from the cache if they are no longer
239      * needed. This will also allow the cache keys
240      * to be reclaimed along with the ClassLoaders
241      * they reference.
242      * This variable would be better named "cache", but we keep the old
243      * name for compatibility with some workarounds for bug 4212439.
244      */

245     private static SoftCache cacheList = new SoftCache(INITIAL_CACHE_SIZE, CACHE_LOAD_FACTOR);
246
247     /**
248      * Queue for reference objects referring to class loaders.
249      */

250     private static ReferenceQueue JavaDoc referenceQueue = new ReferenceQueue JavaDoc();
251
252     /**
253      * The parent bundle of this bundle.
254      * The parent bundle is searched by {@link #getObject getObject}
255      * when this bundle does not contain a particular resource.
256      */

257     protected ResourceBundle JavaDoc parent = null;
258
259     /**
260      * The locale for this bundle.
261      */

262     private Locale JavaDoc locale = null;
263
264     /**
265      * Sole constructor. (For invocation by subclass constructors, typically
266      * implicit.)
267      */

268     public ResourceBundle() {
269     }
270
271     /**
272      * Gets a string for the given key from this resource bundle or one of its parents.
273      * Calling this method is equivalent to calling
274      * <blockquote>
275      * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
276      * </blockquote>
277      *
278      * @param key the key for the desired string
279      * @exception NullPointerException if <code>key</code> is <code>null</code>
280      * @exception MissingResourceException if no object for the given key can be found
281      * @exception ClassCastException if the object found for the given key is not a string
282      * @return the string for the given key
283      */

284     public final String JavaDoc getString(String JavaDoc key) {
285         return (String JavaDoc) getObject(key);
286     }
287
288     /**
289      * Gets a string array for the given key from this resource bundle or one of its parents.
290      * Calling this method is equivalent to calling
291      * <blockquote>
292      * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
293      * </blockquote>
294      *
295      * @param key the key for the desired string array
296      * @exception NullPointerException if <code>key</code> is <code>null</code>
297      * @exception MissingResourceException if no object for the given key can be found
298      * @exception ClassCastException if the object found for the given key is not a string array
299      * @return the string array for the given key
300      */

301     public final String JavaDoc[] getStringArray(String JavaDoc key) {
302         return (String JavaDoc[]) getObject(key);
303     }
304
305     /**
306      * Gets an object for the given key from this resource bundle or one of its parents.
307      * This method first tries to obtain the object from this resource bundle using
308      * {@link #handleGetObject(java.lang.String) handleGetObject}.
309      * If not successful, and the parent resource bundle is not null,
310      * it calls the parent's <code>getObject</code> method.
311      * If still not successful, it throws a MissingResourceException.
312      *
313      * @param key the key for the desired object
314      * @exception NullPointerException if <code>key</code> is <code>null</code>
315      * @exception MissingResourceException if no object for the given key can be found
316      * @return the object for the given key
317      */

318     public final Object JavaDoc getObject(String JavaDoc key) {
319         Object JavaDoc obj = handleGetObject(key);
320         if (obj == null) {
321             if (parent != null) {
322                 obj = parent.getObject(key);
323             }
324             if (obj == null)
325                 throw new MissingResourceException JavaDoc("Can't find resource for bundle "
326                                                    +this.getClass().getName()
327                                                    +", key "+key,
328                                                    this.getClass().getName(),
329                                                    key);
330         }
331         return obj;
332     }
333
334     /**
335      * Returns the locale of this resource bundle. This method can be used after a
336      * call to getBundle() to determine whether the resource bundle returned really
337      * corresponds to the requested locale or is a fallback.
338      *
339      * @return the locale of this resource bundle
340      */

341     public Locale JavaDoc getLocale() {
342         return locale;
343     }
344
345     /**
346      * Sets the locale for this bundle. This is the locale that this
347      * bundle actually represents and does not depend on how the
348      * bundle was found by getBundle. Ex. if the user was looking
349      * for fr_FR and getBundle found en_US, the bundle's locale would
350      * be en_US, NOT fr_FR
351      * @param baseName the bundle's base name
352      * @param bundleName the complete bundle name including locale
353      * extension.
354      */

355     private void setLocale(String JavaDoc baseName, String JavaDoc bundleName) {
356         if (baseName.length() == bundleName.length()) {
357             locale = new Locale JavaDoc("", "");
358         } else if (baseName.length() < bundleName.length()) {
359             int pos = baseName.length();
360             String JavaDoc temp = bundleName.substring(pos + 1);
361             pos = temp.indexOf('_');
362             if (pos == -1) {
363                 locale = new Locale JavaDoc(temp, "", "");
364                 return;
365             }
366
367             String JavaDoc language = temp.substring(0, pos);
368             temp = temp.substring(pos + 1);
369             pos = temp.indexOf('_');
370             if (pos == -1) {
371                 locale = new Locale JavaDoc(language, temp, "");
372                 return;
373             }
374
375             String JavaDoc country = temp.substring(0, pos);
376             temp = temp.substring(pos + 1);
377
378             locale = new Locale JavaDoc(language, country, temp);
379         } else {
380             //The base name is longer than the bundle name. Something is very wrong
381
//with the calling code.
382
throw new IllegalArgumentException JavaDoc();
383         }
384     }
385
386     /*
387      * Automatic determination of the ClassLoader to be used to load
388      * resources on behalf of the client. N.B. The client is getLoader's
389      * caller's caller.
390      */

391     private static ClassLoader JavaDoc getLoader() {
392         Class JavaDoc[] stack = getClassContext();
393         /* Magic number 2 identifies our caller's caller */
394         Class JavaDoc c = stack[2];
395         ClassLoader JavaDoc cl = (c == null) ? null : c.getClassLoader();
396         if (cl == null) {
397             cl = ClassLoader.getSystemClassLoader();
398         }
399         return cl;
400     }
401
402     private static native Class JavaDoc[] getClassContext();
403
404     /**
405      * Sets the parent bundle of this bundle.
406      * The parent bundle is searched by {@link #getObject getObject}
407      * when this bundle does not contain a particular resource.
408      *
409      * @param parent this bundle's parent bundle.
410      */

411     protected void setParent( ResourceBundle JavaDoc parent ) {
412         this.parent = parent;
413     }
414
415      /**
416       * Key used for cached resource bundles. The key checks
417       * the resource name, the class loader, and the default
418       * locale to determine if the resource is a match to the
419       * requested one. The loader may be null, but the
420       * searchName and the default locale must have a non-null value.
421       * Note that the default locale may change over time, and
422       * lookup should always be based on the current default
423       * locale (if at all).
424       */

425     private static final class ResourceCacheKey implements Cloneable JavaDoc {
426         private LoaderReference loaderRef;
427         private String JavaDoc searchName;
428         private Locale JavaDoc defaultLocale;
429         private int hashCodeCache;
430
431         public boolean equals(Object JavaDoc other) {
432             if (this == other) {
433                 return true;
434             }
435             try {
436                 final ResourceCacheKey otherEntry = (ResourceCacheKey)other;
437                 //quick check to see if they are not equal
438
if (hashCodeCache != otherEntry.hashCodeCache) {
439                     return false;
440                 }
441                 //are the names the same?
442
if (!searchName.equals(otherEntry.searchName)) {
443                     return false;
444                 }
445                 // are the default locales the same?
446
if (defaultLocale == null) {
447                     if (otherEntry.defaultLocale != null) {
448                         return false;
449                     }
450                 } else {
451                     if (!defaultLocale.equals(otherEntry.defaultLocale)) {
452                         return false;
453                     }
454                 }
455                 //are refs (both non-null) or (both null)?
456
if (loaderRef == null) {
457                     return otherEntry.loaderRef == null;
458                 } else {
459                     Object JavaDoc loaderRefValue = loaderRef.get();
460                     return (otherEntry.loaderRef != null)
461                             // with a null reference we can no longer find
462
// out which class loader was referenced; so
463
// treat it as unequal
464
&& (loaderRefValue != null)
465                             && (loaderRefValue == otherEntry.loaderRef.get());
466                 }
467             } catch (NullPointerException JavaDoc e) {
468                 return false;
469             } catch (ClassCastException JavaDoc e) {
470                 return false;
471             }
472         }
473
474         public int hashCode() {
475             return hashCodeCache;
476         }
477
478         public Object JavaDoc clone() {
479             try {
480                 ResourceCacheKey clone = (ResourceCacheKey) super.clone();
481                 if (loaderRef != null) {
482                     clone.loaderRef = new LoaderReference(loaderRef.get(), referenceQueue, clone);
483                 }
484                 return clone;
485             } catch (CloneNotSupportedException JavaDoc e) {
486                 //this should never happen
487
throw new InternalError JavaDoc();
488             }
489         }
490
491         public void setKeyValues(ClassLoader JavaDoc loader, String JavaDoc searchName, Locale JavaDoc defaultLocale) {
492             this.searchName = searchName;
493             hashCodeCache = searchName.hashCode();
494             this.defaultLocale = defaultLocale;
495             if (defaultLocale != null) {
496                 hashCodeCache ^= defaultLocale.hashCode();
497             }
498             if (loader == null) {
499                 this.loaderRef = null;
500             } else {
501                 loaderRef = new LoaderReference(loader, referenceQueue, this);
502                 hashCodeCache ^= loader.hashCode();
503             }
504         }
505
506         public void clear() {
507             setKeyValues(null, "", null);
508         }
509     }
510
511     /**
512      * References to class loaders are weak references, so that they can be
513      * garbage collected when nobody else is using them. The ResourceBundle
514      * class has no reason to keep class loaders alive.
515      */

516     private static final class LoaderReference extends WeakReference JavaDoc {
517         private ResourceCacheKey cacheKey;
518         
519         LoaderReference(Object JavaDoc referent, ReferenceQueue JavaDoc q, ResourceCacheKey key) {
520             super(referent, q);
521             cacheKey = key;
522         }
523         
524         ResourceCacheKey getCacheKey() {
525             return cacheKey;
526         }
527     }
528     
529     /**
530      * Gets a resource bundle using the specified base name, the default locale,
531      * and the caller's class loader. Calling this method is equivalent to calling
532      * <blockquote>
533      * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
534      * </blockquote>
535      * except that <code>getClassLoader()</code> is run with the security
536      * privileges of <code>ResourceBundle</code>.
537      * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
538      * for a complete description of the search and instantiation strategy.
539      *
540      * @param baseName the base name of the resource bundle, a fully qualified class name
541      * @exception java.lang.NullPointerException
542      * if <code>baseName</code> is <code>null</code>
543      * @exception MissingResourceException
544      * if no resource bundle for the specified base name can be found
545      * @return a resource bundle for the given base name and the default locale
546      */

547     public static final ResourceBundle JavaDoc getBundle(String JavaDoc baseName)
548     {
549         return getBundleImpl(baseName, Locale.getDefault(),
550         /* must determine loader here, else we break stack invariant */
551         getLoader());
552     }
553
554     /**
555      * Gets a resource bundle using the specified base name and locale,
556      * and the caller's class loader. Calling this method is equivalent to calling
557      * <blockquote>
558      * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
559      * </blockquote>
560      * except that <code>getClassLoader()</code> is run with the security
561      * privileges of <code>ResourceBundle</code>.
562      * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
563      * for a complete description of the search and instantiation strategy.
564      *
565      * @param baseName the base name of the resource bundle, a fully qualified class name
566      * @param locale the locale for which a resource bundle is desired
567      * @exception java.lang.NullPointerException
568      * if <code>baseName</code> or <code>locale</code> is <code>null</code>
569      * @exception MissingResourceException
570      * if no resource bundle for the specified base name can be found
571      * @return a resource bundle for the given base name and locale
572      */

573     public static final ResourceBundle JavaDoc getBundle(String JavaDoc baseName,
574                                                          Locale JavaDoc locale)
575     {
576         return getBundleImpl(baseName, locale, getLoader());
577     }
578
579     /**
580      * Gets a resource bundle using the specified base name, locale, and class loader.
581      *
582      * <p>
583      * Conceptually, <code>getBundle</code> uses the following strategy for locating and instantiating
584      * resource bundles:
585      * <p>
586      * <code>getBundle</code> uses the base name, the specified locale, and the default
587      * locale (obtained from {@link java.util.Locale#getDefault() Locale.getDefault})
588      * to generate a sequence of <em>candidate bundle names</em>.
589      * If the specified locale's language, country, and variant are all empty
590      * strings, then the base name is the only candidate bundle name.
591      * Otherwise, the following sequence is generated from the attribute
592      * values of the specified locale (language1, country1, and variant1)
593      * and of the default locale (language2, country2, and variant2):
594      * <ul>
595      * <li> baseName + "_" + language1 + "_" + country1 + "_" + variant1
596      * <li> baseName + "_" + language1 + "_" + country1
597      * <li> baseName + "_" + language1
598      * <li> baseName + "_" + language2 + "_" + country2 + "_" + variant2
599      * <li> baseName + "_" + language2 + "_" + country2
600      * <li> baseName + "_" + language2
601      * <li> baseName
602      * </ul>
603      * <p>
604      * Candidate bundle names where the final component is an empty string are omitted.
605      * For example, if country1 is an empty string, the second candidate bundle name is omitted.
606      *
607      * <p>
608      * <code>getBundle</code> then iterates over the candidate bundle names to find the first
609      * one for which it can <em>instantiate</em> an actual resource bundle. For each candidate
610      * bundle name, it attempts to create a resource bundle:
611      * <ul>
612      * <li>
613      * First, it attempts to load a class using the candidate bundle name.
614      * If such a class can be found and loaded using the specified class loader, is assignment
615      * compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated,
616      * <code>getBundle</code> creates a new instance of this class and uses it as the <em>result
617      * resource bundle</em>.
618      * <li>
619      * Otherwise, <code>getBundle</code> attempts to locate a property resource file.
620      * It generates a path name from the candidate bundle name by replacing all "." characters
621      * with "/" and appending the string ".properties".
622      * It attempts to find a "resource" with this name using
623      * {@link java.lang.ClassLoader#getResource(java.lang.String) ClassLoader.getResource}.
624      * (Note that a "resource" in the sense of <code>getResource</code> has nothing to do with
625      * the contents of a resource bundle, it is just a container of data, such as a file.)
626      * If it finds a "resource", it attempts to create a new
627      * {@link PropertyResourceBundle} instance from its contents.
628      * If successful, this instance becomes the <em>result resource bundle</em>.
629      * </ul>
630      *
631      * <p>
632      * If no result resource bundle has been found, a <code>MissingResourceException</code>
633      * is thrown.
634      *
635      * <p>
636      * Once a result resource bundle has been found, its parent chain is instantiated.
637      * <code>getBundle</code> iterates over the candidate bundle names that can be
638      * obtained by successively removing variant, country, and language
639      * (each time with the preceding "_") from the bundle name of the result resource bundle.
640      * As above, candidate bundle names where the final component is an empty string are omitted.
641      * With each of the candidate bundle names it attempts to instantiate a resource bundle, as
642      * described above.
643      * Whenever it succeeds, it calls the previously instantiated resource
644      * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method
645      * with the new resource bundle, unless the previously instantiated resource
646      * bundle already has a non-null parent.
647      *
648      * <p>
649      * Implementations of <code>getBundle</code> may cache instantiated resource bundles
650      * and return the same resource bundle instance multiple times. They may also
651      * vary the sequence in which resource bundles are instantiated as long as the
652      * selection of the result resource bundle and its parent chain are compatible with
653      * the description above.
654      *
655      * <p>
656      * The <code>baseName</code> argument should be a fully qualified class name. However, for
657      * compatibility with earlier versions, Sun's Java 2 runtime environments do not verify this,
658      * and so it is possible to access <code>PropertyResourceBundle</code>s by specifying a
659      * path name (using "/") instead of a fully qualified class name (using ".").
660      *
661      * <p>
662      * <strong>Example:</strong> The following class and property files are provided:
663      * MyResources.class, MyResources_fr_CH.properties, MyResources_fr_CH.class,
664      * MyResources_fr.properties, MyResources_en.properties, MyResources_es_ES.class.
665      * The contents of all files are valid (that is, public non-abstract subclasses of ResourceBundle for
666      * the ".class" files, syntactically correct ".properties" files).
667      * The default locale is <code>Locale("en", "GB")</code>.
668      * <p>
669      * Calling <code>getBundle</code> with the shown locale argument values instantiates
670      * resource bundles from the following sources:
671      * <ul>
672      * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class
673      * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent MyResources.class
674      * <li>Locale("de", "DE"): result MyResources_en.properties, parent MyResources.class
675      * <li>Locale("en", "US"): result MyResources_en.properties, parent MyResources.class
676      * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent MyResources.class
677      * </ul>
678      * The file MyResources_fr_CH.properties is never used because it is hidden by
679      * MyResources_fr_CH.class.
680      *
681      * <p>
682      *
683      * @param baseName the base name of the resource bundle, a fully qualified class name
684      * @param locale the locale for which a resource bundle is desired
685      * @param loader the class loader from which to load the resource bundle
686      * @exception java.lang.NullPointerException
687      * if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
688      * @exception MissingResourceException
689      * if no resource bundle for the specified base name can be found
690      * @return a resource bundle for the given base name and locale
691      * @since 1.2
692      */

693     public static ResourceBundle JavaDoc getBundle(String JavaDoc baseName, Locale JavaDoc locale,
694                                            ClassLoader JavaDoc loader)
695     {
696         if (loader == null) {
697             throw new NullPointerException JavaDoc();
698         }
699         return getBundleImpl(baseName, locale, loader);
700     }
701
702     private static ResourceBundle JavaDoc getBundleImpl(String JavaDoc baseName, Locale JavaDoc locale,
703                                            ClassLoader JavaDoc loader)
704     {
705         if (baseName == null) {
706             throw new NullPointerException JavaDoc();
707         }
708
709         //fast path the case where the bundle is cached
710
String JavaDoc bundleName = baseName;
711         String JavaDoc localeSuffix = locale.toString();
712         if (localeSuffix.length() > 0) {
713             bundleName += "_" + localeSuffix;
714         } else if (locale.getVariant().length() > 0) {
715             //This corrects some strange behavior in Locale where
716
//new Locale("", "", "VARIANT").toString == ""
717
bundleName += "___" + locale.getVariant();
718         }
719         
720         // The default locale may influence the lookup result, and
721
// it may change, so we get it here once.
722
Locale JavaDoc defaultLocale = Locale.getDefault();
723
724         Object JavaDoc lookup = findBundleInCache(loader, bundleName, defaultLocale);
725         if (lookup == NOT_FOUND) {
726             throwMissingResourceException(baseName, locale);
727         } else if (lookup != null) {
728             return (ResourceBundle JavaDoc)lookup;
729         }
730
731         //The bundle was not cached, so start doing lookup at the root
732
//Resources are loaded starting at the root and working toward
733
//the requested bundle.
734

735         //If findBundle returns null, we become responsible for defining
736
//the bundle, and must call putBundleInCache to complete this
737
//task. This is critical because other threads may be waiting
738
//for us to finish.
739

740         Object JavaDoc parent = NOT_FOUND;
741         try {
742             //locate the root bundle and work toward the desired child
743
Object JavaDoc root = findBundle(loader, baseName, defaultLocale, baseName, null);
744             if (root == null) {
745                 putBundleInCache(loader, baseName, defaultLocale, NOT_FOUND);
746                 root = NOT_FOUND;
747             }
748
749             // Search the main branch of the search tree.
750
// We need to keep references to the bundles we find on the main path
751
// so they don't get garbage collected before we get to propagate().
752
final Vector JavaDoc names = calculateBundleNames(baseName, locale);
753             Vector JavaDoc bundlesFound = new Vector JavaDoc(MAX_BUNDLES_SEARCHED);
754         // if we found the root bundle and no other bundle names are needed
755
// we can stop here. We don't need to search or load anything further.
756
boolean foundInMainBranch = (root != NOT_FOUND && names.size() == 0);
757         
758         if (!foundInMainBranch) {
759           parent = root;
760           for (int i = 0; i < names.size(); i++) {
761                 bundleName = (String JavaDoc)names.elementAt(i);
762                 lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent);
763                 bundlesFound.addElement(lookup);
764                 if (lookup != null) {
765                     parent = lookup;
766                     foundInMainBranch = true;
767                 }
768           }
769             }
770             parent = root;
771             if (!foundInMainBranch) {
772                 //we didn't find anything on the main branch, so we do the fallback branch
773
final Vector JavaDoc fallbackNames = calculateBundleNames(baseName, defaultLocale);
774                 for (int i = 0; i < fallbackNames.size(); i++) {
775                     bundleName = (String JavaDoc)fallbackNames.elementAt(i);
776                     if (names.contains(bundleName)) {
777                         //the fallback branch intersects the main branch so we can stop now.
778
break;
779                     }
780                     lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent);
781                     if (lookup != null) {
782                         parent = lookup;
783                     } else {
784                         //propagate the parent to the child. We can do this
785
//here because we are in the default path.
786
putBundleInCache(loader, bundleName, defaultLocale, parent);
787                     }
788                 }
789             }
790             //propagate the inheritance/fallback down through the main branch
791
parent = propagate(loader, names, bundlesFound, defaultLocale, parent);
792         } catch (Exception JavaDoc e) {
793             //We should never get here unless there has been a change
794
//to the code that doesn't catch it's own exceptions.
795
cleanUpConstructionList();
796             throwMissingResourceException(baseName, locale);
797         } catch (Error JavaDoc e) {
798             //The only Error that can currently hit this code is a ThreadDeathError
799
//but errors might be added in the future, so we'll play it safe and
800
//clean up.
801
cleanUpConstructionList();
802             throw e;
803         }
804         if (parent == NOT_FOUND) {
805             throwMissingResourceException(baseName, locale);
806         }
807         return (ResourceBundle JavaDoc)parent;
808     }
809
810     /**
811      * propagate bundles from the root down the specified branch of the search tree.
812      * @param loader the class loader for the bundles
813      * @param names the names of the bundles along search path
814      * @param bundlesFound the bundles corresponding to the names (some may be null)
815      * @param defaultLocale the default locale at the time getBundle was called
816      * @param parent the parent of the first bundle in the path (the root bundle)
817      * @return the value of the last bundle along the path
818      */

819     private static Object JavaDoc propagate(ClassLoader JavaDoc loader, Vector JavaDoc names,
820             Vector JavaDoc bundlesFound, Locale JavaDoc defaultLocale, Object JavaDoc parent) {
821         for (int i = 0; i < names.size(); i++) {
822             final String JavaDoc bundleName = (String JavaDoc)names.elementAt(i);
823             final Object JavaDoc lookup = bundlesFound.elementAt(i);
824             if (lookup == null) {
825                 putBundleInCache(loader, bundleName, defaultLocale, parent);
826             } else {
827                 parent = lookup;
828             }
829         }
830         return parent;
831     }
832
833     /** Throw a MissingResourceException with proper message */
834     private static void throwMissingResourceException(String JavaDoc baseName, Locale JavaDoc locale)
835             throws MissingResourceException JavaDoc{
836         throw new MissingResourceException JavaDoc("Can't find bundle for base name "
837                                            + baseName + ", locale " + locale,
838                                            baseName + "_" + locale,"");
839     }
840
841     /**
842      * Remove any entries this thread may have in the construction list.
843      * This is done as cleanup in the case where a bundle can't be
844      * constructed.
845      */

846     private static void cleanUpConstructionList() {
847         synchronized (cacheList) {
848             final Collection JavaDoc entries = underConstruction.values();
849             final Thread JavaDoc thisThread = Thread.currentThread();
850             while (entries.remove(thisThread)) {
851             }
852         // Wake up threads that have been waiting for construction
853
// completion. (6329105)
854
cacheList.notifyAll();
855         }
856     }
857
858     /**
859      * Find a bundle in the cache or load it via the loader or a property file.
860      * If the bundle isn't found, an entry is put in the constructionCache
861      * and null is returned. If null is returned, the caller must define the bundle
862      * by calling putBundleInCache. This routine also propagates NOT_FOUND values
863      * from parent to child bundles when the parent is NOT_FOUND.
864      * @param loader the loader to use when loading a bundle
865      * @param bundleName the complete bundle name including locale extension
866      * @param defaultLocale the default locale at the time getBundle was called
867      * @param parent the parent of the resource bundle being loaded. null if
868      * the bundle is a root bundle
869      * @return the bundle or null if the bundle could not be found in the cache
870      * or loaded.
871      */

872     private static Object JavaDoc findBundle(ClassLoader JavaDoc loader, String JavaDoc bundleName, Locale JavaDoc defaultLocale,
873             String JavaDoc baseName, Object JavaDoc parent) {
874         Object JavaDoc result;
875         synchronized (cacheList) {
876             // Before we do the real work of this method, see
877
// whether we need to do some housekeeping:
878
// If references to class loaders have been nulled out,
879
// remove all related information from the cache
880
Reference JavaDoc ref = referenceQueue.poll();
881             while (ref != null) {
882                 cacheList.remove(((LoaderReference) ref).getCacheKey());
883                 ref = referenceQueue.poll();
884             }
885             
886             //check for bundle in cache
887
cacheKey.setKeyValues(loader, bundleName, defaultLocale);
888             result = cacheList.get(cacheKey);
889             if (result != null) {
890                 cacheKey.clear();
891                 return result;
892             }
893             // check to see if some other thread is building this bundle.
894
// Note that there is a rare chance that this thread is already
895
// working on this bundle, and in the process getBundle was called
896
// again, in which case we can't wait (4300693)
897
Thread JavaDoc builder = (Thread JavaDoc) underConstruction.get(cacheKey);
898             boolean beingBuilt = (builder != null && builder != Thread.currentThread());
899             //if some other thread is building the bundle...
900
if (beingBuilt) {
901                 //while some other thread is building the bundle...
902
while (beingBuilt) {
903                     cacheKey.clear();
904                     try {
905                         //Wait until the bundle is complete
906
cacheList.wait();
907                     } catch (InterruptedException JavaDoc e) {
908                     }
909                     cacheKey.setKeyValues(loader, bundleName, defaultLocale);
910                     beingBuilt = underConstruction.containsKey(cacheKey);
911                 }
912                 //if someone constructed the bundle for us, return it
913
result = cacheList.get(cacheKey);
914                 if (result != null) {
915                     cacheKey.clear();
916                     return result;
917                 }
918             }
919             //The bundle isn't in the cache, so we are now responsible for
920
//loading it and adding it to the cache.
921
final Object JavaDoc key = cacheKey.clone();
922             underConstruction.put(key, Thread.currentThread());
923             //the bundle is removed from the cache by putBundleInCache
924
cacheKey.clear();
925         }
926
927         //try loading the bundle via the class loader
928
result = loadBundle(loader, bundleName, defaultLocale);
929         if (result != null) {
930             // check whether we're still responsible for construction -
931
// a recursive call to getBundle might have handled it (4300693)
932
boolean constructing;
933             synchronized (cacheList) {
934                 cacheKey.setKeyValues(loader, bundleName, defaultLocale);
935                 constructing = underConstruction.get(cacheKey) == Thread.currentThread();
936                 cacheKey.clear();
937             }
938             if (constructing) {
939                 // set the bundle's parent and put it in the cache
940
final ResourceBundle JavaDoc bundle = (ResourceBundle JavaDoc)result;
941                 if (parent != NOT_FOUND && bundle.parent == null) {
942                     bundle.setParent((ResourceBundle JavaDoc) parent);
943                 }
944                 bundle.setLocale(baseName, bundleName);
945                 putBundleInCache(loader, bundleName, defaultLocale, result);
946             }
947         }
948         return result;
949     }
950
951     /**
952      * Calculate the bundles along the search path from the base bundle to the
953      * bundle specified by baseName and locale.
954      * @param baseName the base bundle name
955      * @param locale the locale
956      * @param names the vector used to return the names of the bundles along
957      * the search path.
958      *
959      */

960     private static Vector JavaDoc calculateBundleNames(String JavaDoc baseName, Locale JavaDoc locale) {
961         final Vector JavaDoc result = new Vector JavaDoc(MAX_BUNDLES_SEARCHED);
962         final String JavaDoc language = locale.getLanguage();
963         final int languageLength = language.length();
964         final String JavaDoc country = locale.getCountry();
965         final int countryLength = country.length();
966         final String JavaDoc variant = locale.getVariant();
967         final int variantLength = variant.length();
968
969         if (languageLength + countryLength + variantLength == 0) {
970             //The locale is "", "", "".
971
return result;
972         }
973         final StringBuffer JavaDoc temp = new StringBuffer JavaDoc(baseName);
974         temp.append('_');
975         temp.append(language);
976         if (languageLength > 0) {
977             result.addElement(temp.toString());
978         }
979
980         if (countryLength + variantLength == 0) {
981             return result;
982         }
983         temp.append('_');
984         temp.append(country);
985         if (countryLength > 0) {
986             result.addElement(temp.toString());
987         }
988
989         if (variantLength == 0) {
990             return result;
991         }
992         temp.append('_');
993         temp.append(variant);
994         result.addElement(temp.toString());
995
996         return result;
997     }
998
999     /**
1000     * Find a bundle in the cache.
1001     * @param loader the class loader that is responsible for loading the bundle.
1002     * @param bundleName the complete name of the bundle including locale extension.
1003     * ex. sun.text.resources.LocaleElements_fr_BE
1004     * @param defaultLocale the default locale at the time getBundle was called
1005     * @return the cached bundle. null if the bundle is not in the cache.
1006     */

1007    private static Object JavaDoc findBundleInCache(ClassLoader JavaDoc loader, String JavaDoc bundleName,
1008            Locale JavaDoc defaultLocale) {
1009        //Synchronize access to cacheList, cacheKey, and underConstruction
1010
synchronized (cacheList) {
1011            cacheKey.setKeyValues(loader, bundleName, defaultLocale);
1012            Object JavaDoc result = cacheList.get(cacheKey);
1013            cacheKey.clear();
1014            return result;
1015        }
1016    }
1017
1018    /**
1019     * Put a new bundle in the cache and notify waiting threads that a new
1020     * bundle has been put in the cache.
1021     * @param defaultLocale the default locale at the time getBundle was called
1022     */

1023    private static void putBundleInCache(ClassLoader JavaDoc loader, String JavaDoc bundleName,
1024            Locale JavaDoc defaultLocale, Object JavaDoc value) {
1025        //we use a static shared cacheKey but we use the lock in cacheList since
1026
//the key is only used to interact with cacheList.
1027
synchronized (cacheList) {
1028            cacheKey.setKeyValues(loader, bundleName, defaultLocale);
1029            cacheList.put(cacheKey.clone(), value);
1030            underConstruction.remove(cacheKey);
1031            cacheKey.clear();
1032            //notify waiters that we're done constructing the bundle
1033
cacheList.notifyAll();
1034        }
1035    }
1036
1037    /**
1038     * Load a bundle through either the specified ClassLoader or from a ".properties" file
1039     * and return the loaded bundle.
1040     * @param loader the ClassLoader to use to load the bundle. If null, the system
1041     * ClassLoader is used.
1042     * @param bundleName the name of the resource to load. The name should be complete
1043     * including a qualified class name followed by the locale extension.
1044     * ex. sun.text.resources.LocaleElements_fr_BE
1045     * @param defaultLocale the default locale at the time getBundle was called
1046     * @return the bundle or null if none could be found.
1047     */

1048    private static Object JavaDoc loadBundle(final ClassLoader JavaDoc loader, String JavaDoc bundleName, Locale JavaDoc defaultLocale) {
1049        // Search for class file using class loader
1050
try {
1051            Class JavaDoc bundleClass;
1052            if (loader != null) {
1053                bundleClass = loader.loadClass(bundleName);
1054            } else {
1055                bundleClass = Class.forName(bundleName);
1056            }
1057            if (ResourceBundle JavaDoc.class.isAssignableFrom(bundleClass)) {
1058                Object JavaDoc myBundle = bundleClass.newInstance();
1059                // Creating the instance may have triggered a recursive call to getBundle,
1060
// in which case the bundle created by the recursive call would be in the
1061
// cache now (4300693). For consistency, we'd then return the bundle from the cache.
1062
Object JavaDoc otherBundle = findBundleInCache(loader, bundleName, defaultLocale);
1063                if (otherBundle != null) {
1064                    return otherBundle;
1065                } else {
1066                    return myBundle;
1067                }
1068            }
1069        } catch (Exception JavaDoc e) {
1070        } catch (LinkageError JavaDoc e) {
1071        }
1072
1073        // Next search for a Properties file.
1074
final String JavaDoc resName = bundleName.replace('.', '/') + ".properties";
1075        InputStream JavaDoc stream = (InputStream JavaDoc)java.security.AccessController.doPrivileged(
1076            new java.security.PrivilegedAction JavaDoc() {
1077                public Object JavaDoc run() {
1078                    if (loader != null) {
1079                        return loader.getResourceAsStream(resName);
1080                    } else {
1081                        return ClassLoader.getSystemResourceAsStream(resName);
1082                    }
1083                }
1084            }
1085        );
1086
1087        if (stream != null) {
1088            // make sure it is buffered
1089
stream = new java.io.BufferedInputStream JavaDoc(stream);
1090            try {
1091                return new PropertyResourceBundle JavaDoc(stream);
1092            } catch (Exception JavaDoc e) {
1093            } finally {
1094                try {
1095                    stream.close();
1096                } catch (Exception JavaDoc e) {
1097                    // to avoid propagating an IOException back into the caller
1098
// (I'm assuming this is never going to happen, and if it does,
1099
// I'm obeying the precedent of swallowing exceptions set by the
1100
// existing code above)
1101
}
1102            }
1103        }
1104        return null;
1105    }
1106
1107    /**
1108     * Gets an object for the given key from this resource bundle.
1109     * Returns null if this resource bundle does not contain an
1110     * object for the given key.
1111     *
1112     * @param key the key for the desired object
1113     * @exception NullPointerException if <code>key</code> is <code>null</code>
1114     * @return the object for the given key, or null
1115     */

1116    protected abstract Object JavaDoc handleGetObject(String JavaDoc key);
1117
1118    /**
1119     * Returns an enumeration of the keys.
1120     *
1121     */

1122    public abstract Enumeration JavaDoc<String JavaDoc> getKeys();
1123}
1124
Popular Tags