OSDN Git Service

2003-04-29 Michael Koch <konqueror@gmx.de>
[pf3gnuchains/gcc-fork.git] / libjava / java / util / ResourceBundle.java
1 /* ResourceBundle -- aids in loading resource bundles
2    Copyright (C) 1998, 1999, 2001, 2002, 2003 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package java.util;
40
41 import java.lang.ref.Reference;
42 import java.lang.ref.SoftReference;
43 import java.io.InputStream;
44 import java.io.IOException;
45 import gnu.classpath.Configuration;
46
47 /**
48  * A resource bundle contains locale-specific data. If you need localized
49  * data, you can load a resource bundle that matches the locale with
50  * <code>getBundle</code>. Now you can get your object by calling
51  * <code>getObject</code> or <code>getString</code> on that bundle.
52  *
53  * <p>When a bundle is demanded for a specific locale, the ResourceBundle
54  * is searched in following order (<i>def. language<i> stands for the
55  * two letter ISO language code of the default locale (see
56  * <code>Locale.getDefault()</code>).
57  *
58 <pre>baseName_<i>language code</i>_<i>country code</i>_<i>variant</i>
59 baseName_<i>language code</i>_<i>country code</i>
60 baseName_<i>language code</i>
61 baseName_<i>def. language</i>_<i>def. country</i>_<i>def. variant</i>
62 baseName_<i>def. language</i>_<i>def. country</i>
63 baseName_<i>def. language</i>
64 baseName</pre>
65  *
66  * <p>A bundle is backed up by less specific bundles (omitting variant, country
67  * or language). But it is not backed up by the default language locale.
68  *
69  * <p>If you provide a bundle for a given locale, say
70  * <code>Bundle_en_UK_POSIX</code>, you must also provide a bundle for
71  * all sub locales, ie. <code>Bundle_en_UK</code>, <code>Bundle_en</code>, and
72  * <code>Bundle</code>.
73  *
74  * <p>When a bundle is searched, we look first for a class with the given
75  * name, then for a file with <code>.properties</code> extension in the
76  * classpath. The name must be a fully qualified classname (with dots as
77  * path separators).
78  *
79  * <p>(Note: This implementation always backs up the class with a properties
80  * file if that is existing, but you shouldn't rely on this, if you want to
81  * be compatible to the standard JDK.)
82  *
83  * @author Jochen Hoenicke
84  * @author Eric Blake (ebb9@email.byu.edu)
85  * @see Locale
86  * @see ListResourceBundle
87  * @see PropertyResourceBundle
88  * @since 1.1
89  * @status updated to 1.4
90  */
91 public abstract class ResourceBundle
92 {
93   /**
94    * The parent bundle. This is consulted when you call getObject and there
95    * is no such resource in the current bundle. This field may be null.
96    */
97   protected ResourceBundle parent;
98
99   /**
100    * The locale of this resource bundle. You can read this with
101    * <code>getLocale</code> and it is automatically set in
102    * <code>getBundle</code>.
103    */
104   private Locale locale;
105
106   private static native ClassLoader getCallingClassLoader();
107
108   /**
109    * The resource bundle cache. This is a two-level hash map: The key
110    * is the class loader, the value is a new HashMap. The key of this
111    * second hash map is the localized name, the value is a soft
112    * references to the resource bundle.
113    */
114   private static Map resourceBundleCache;
115
116   /**
117    * The last default Locale we saw. If this ever changes then we have to
118    * reset our caches.
119    */
120   private static Locale lastDefaultLocale;
121
122   /**
123    * The `empty' locale is created once in order to optimize
124    * tryBundle().
125    */
126   private static final Locale emptyLocale = new Locale("");
127
128   /**
129    * The constructor. It does nothing special.
130    */
131   public ResourceBundle()
132   {
133   }
134
135   /**
136    * Get a String from this resource bundle. Since most localized Objects
137    * are Strings, this method provides a convenient way to get them without
138    * casting.
139    *
140    * @param key the name of the resource
141    * @throws MissingResourceException if the resource can't be found
142    * @throws NullPointerException if key is null
143    * @throws ClassCastException if resource is not a string
144    */
145   public final String getString(String key)
146   {
147     return (String) getObject(key);
148   }
149
150   /**
151    * Get an array of Strings from this resource bundle. This method
152    * provides a convenient way to get it without casting.
153    *
154    * @param key the name of the resource
155    * @throws MissingResourceException if the resource can't be found
156    * @throws NullPointerException if key is null
157    * @throws ClassCastException if resource is not a string
158    */
159   public final String[] getStringArray(String key)
160   {
161     return (String[]) getObject(key);
162   }
163
164   /**
165    * Get an object from this resource bundle. This will call
166    * <code>handleGetObject</code> for this resource and all of its parents,
167    * until it finds a non-null resource.
168    *
169    * @param key the name of the resource
170    * @throws MissingResourceException if the resource can't be found
171    * @throws NullPointerException if key is null
172    */
173   public final Object getObject(String key)
174   {
175     for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent)
176       try
177         {
178           Object o = bundle.handleGetObject(key);
179           if (o != null)
180             return o;
181         }
182       catch (MissingResourceException ex)
183         {
184         }
185  
186     throw new MissingResourceException("Key not found", getClass().getName(),
187                                        key);
188   }
189
190   /**
191    * Return the actual locale of this bundle. You can use it after calling
192    * getBundle, to know if the bundle for the desired locale was loaded or
193    * if the fall back was used.
194    *
195    * @return the bundle's locale
196    */
197   public Locale getLocale()
198   {
199     return locale;
200   }
201
202   /**
203    * Set the parent of this bundle. The parent is consulted when you call
204    * getObject and there is no such resource in the current bundle.
205    *
206    * @param parent the parent of this bundle
207    */
208   protected void setParent(ResourceBundle parent)
209   {
210     this.parent = parent;
211   }
212
213   /**
214    * Get the appropriate ResourceBundle for the default locale. This is like
215    * calling <code>getBundle(baseName, Locale.getDefault(),
216    * getClass().getClassLoader()</code>, except that any security check of
217    * getClassLoader won't fail.
218    *
219    * @param baseName the name of the ResourceBundle
220    * @return the desired resource bundle
221    * @throws MissingResourceException if the resource bundle can't be found
222    * @throws NullPointerException if baseName is null
223    */
224   public static final ResourceBundle getBundle(String baseName)
225   {
226     return getBundle(baseName, Locale.getDefault(),
227                      getCallingClassLoader());
228   }
229
230   /**
231    * Get the appropriate ResourceBundle for the given locale. This is like
232    * calling <code>getBundle(baseName, locale,
233    * getClass().getClassLoader()</code>, except that any security check of
234    * getClassLoader won't fail.
235    *
236    * @param baseName the name of the ResourceBundle
237    * @param locale A locale
238    * @return the desired resource bundle
239    * @throws MissingResourceException if the resource bundle can't be found
240    * @throws NullPointerException if baseName or locale is null
241    */
242   public static final ResourceBundle getBundle(String baseName,
243                                                Locale locale)
244   {
245     return getBundle(baseName, locale, getCallingClassLoader());
246   }
247
248   /**
249    * Get the appropriate ResourceBundle for the given locale. The following
250    * strategy is used:
251    *
252    * <p>A sequence of candidate bundle names are generated, and tested in
253    * this order, where the suffix 1 means the string from the specified
254    * locale, and the suffix 2 means the string from the default locale:<ul>
255    * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li>
256    * <li>baseName + "_" + language1 + "_" + country1</li>
257    * <li>baseName + "_" + language1</li>
258    * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li>
259    * <li>baseName + "_" + language2 + "_" + country2</li>
260    * <li>baseName + "_" + language2<li>
261    * <li>baseName</li>
262    * </ul>
263    *
264    * <p>In the sequence, entries with an empty string are ignored. Next,
265    * <code>getBundle</code> tries to instantiate the resource bundle:<ul>
266    * <li>First, an attempt is made to load a class in the specified classloader
267    * which is a subclass of ResourceBundle, and which has a public constructor
268    * with no arguments, via reflection.</li>
269    * <li>Next, a search is made for a property resource file, by replacing
270    * '.' with '/' and appending ".properties", and using
271    * ClassLoader.getResource(). If a file is found, then a
272    * PropertyResourceBundle is created from the file's contents.</li>
273    * </ul>
274    * If no resource bundle was found, a MissingResourceException is thrown.
275    *
276    * <p>Next, the parent chain is implemented. The remaining candidate names
277    * in the above sequence are tested in a similar manner, and if any results
278    * in a resource bundle, it is assigned as the parent of the first bundle
279    * using the <code>setParent</code> method (unless the first bundle already
280    * has a parent).
281    *
282    * <p>For example, suppose the following class and property files are
283    * provided: MyResources.class, MyResources_fr_CH.properties,
284    * MyResources_fr_CH.class, MyResources_fr.properties,
285    * MyResources_en.properties, and MyResources_es_ES.class. The contents of
286    * all files are valid (that is, public non-abstract subclasses of
287    * ResourceBundle with public nullary constructors for the ".class" files,
288    * syntactically correct ".properties" files). The default locale is
289    * Locale("en", "UK").
290    *
291    * <p>Calling getBundle with the shown locale argument values instantiates
292    * resource bundles from the following sources:<ul>
293    * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent
294    *   MyResources_fr.properties, parent MyResources.class</li>
295    * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent
296    *   MyResources.class</li>
297    * <li>Locale("de", "DE"): result MyResources_en.properties, parent
298    *   MyResources.class</li>
299    * <li>Locale("en", "US"): result MyResources_en.properties, parent
300    *   MyResources.class</li>
301    * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent
302    *   MyResources.class</li>
303    * </ul>
304    * The file MyResources_fr_CH.properties is never used because it is hidden
305    * by MyResources_fr_CH.class.
306    *
307    * @param baseName the name of the ResourceBundle
308    * @param locale A locale
309    * @param classloader a ClassLoader
310    * @return the desired resource bundle
311    * @throws MissingResourceException if the resource bundle can't be found
312    * @throws NullPointerException if any argument is null
313    * @since 1.2
314    */
315   // This method is synchronized so that the cache is properly
316   // handled.
317   public static final synchronized ResourceBundle getBundle
318     (String baseName, Locale locale, ClassLoader classLoader)
319   {
320     // This implementation searches the bundle in the reverse direction
321     // and builds the parent chain on the fly.
322     Locale defaultLocale = Locale.getDefault();
323     if (defaultLocale != lastDefaultLocale)
324       {
325         resourceBundleCache = new HashMap();
326         lastDefaultLocale = defaultLocale;
327       }
328     HashMap cache = (HashMap) resourceBundleCache.get(classLoader);
329     StringBuffer sb = new StringBuffer(60);
330     sb.append(baseName).append('_').append(locale);
331     String name = sb.toString();
332
333     if (cache == null)
334       {
335         cache = new HashMap();
336         resourceBundleCache.put(classLoader, cache);
337       }
338     else if (cache.containsKey(name))
339       {
340         Reference ref = (Reference) cache.get(name);
341         ResourceBundle result = null;
342         // If REF is null, that means that we added a `null' value to
343         // the hash map.  That means we failed to find the bundle
344         // previously, and we cached that fact.  The JDK does this, so
345         // it must be ok.
346         if (ref == null)
347           throw new MissingResourceException("Bundle " + baseName
348                                              + " not found",
349                                              baseName, "");
350         else
351           {
352             ResourceBundle rb = (ResourceBundle) ref.get();
353             if (rb != null)
354               {
355                 // RB should already have the right parent, except if
356                 // something very strange happened.
357                 return rb;
358               }
359             // If RB is null, then we previously found it but it was
360             // collected.  So we try again.
361           }
362       }
363
364     // It is ok if this returns null.  We aren't required to have the
365     // base bundle.
366     ResourceBundle baseBundle = tryBundle(baseName, emptyLocale,
367                                           classLoader, null, cache);
368
369     // Now use our locale, followed by the default locale.  We only
370     // need to try the default locale if our locale is different, and
371     // if our locale failed to yield a result other than the base
372     // bundle.
373     ResourceBundle bundle = tryLocalBundle(baseName, locale,
374                                            classLoader, baseBundle, cache);
375     if (bundle == baseBundle && !locale.equals(defaultLocale))
376       {
377         bundle = tryLocalBundle(baseName, defaultLocale,
378                                 classLoader, baseBundle, cache);
379         // We need to record that the argument locale maps to the
380         // bundle we just found.  If we didn't find a bundle, record
381         // that instead.
382         if (bundle == null)
383           cache.put(name, null);
384         else
385           cache.put(name, new SoftReference(bundle));
386       }
387
388     if (bundle == null)
389       throw new MissingResourceException("Bundle " + baseName + " not found",
390                                          baseName, "");
391
392     return bundle;
393   }
394
395   /**
396    * Override this method to provide the resource for a keys. This gets
397    * called by <code>getObject</code>. If you don't have a resource
398    * for the given key, you should return null instead throwing a
399    * MissingResourceException. You don't have to ask the parent, getObject()
400    * already does this; nor should you throw a MissingResourceException.
401    *
402    * @param key the key of the resource
403    * @return the resource for the key, or null if not in bundle
404    * @throws NullPointerException if key is null
405    */
406   protected abstract Object handleGetObject(String key);
407
408   /**
409    * This method should return all keys for which a resource exists; you
410    * should include the enumeration of any parent's keys, after filtering out
411    * duplicates.
412    *
413    * @return an enumeration of the keys
414    */
415   public abstract Enumeration getKeys();
416
417   /**
418    * Tries to load a class or a property file with the specified name.
419    *
420    * @param localizedName the name
421    * @param locale the locale, that must be used exactly
422    * @param classloader the classloader
423    * @param bundle the backup (parent) bundle
424    * @return the resource bundle if it was loaded, otherwise the backup
425    */
426   private static final ResourceBundle tryBundle(String localizedName,
427                                                 Locale locale,
428                                                 ClassLoader classloader,
429                                                 ResourceBundle bundle,
430                                                 HashMap cache)
431   {
432     // First look into the cache.
433     if (cache.containsKey(localizedName))
434       {
435         Reference ref = (Reference) cache.get(localizedName);
436         ResourceBundle result = null;
437         // If REF is null, that means that we added a `null' value to
438         // the hash map.  That means we failed to find the bundle
439         // previously, and we cached that fact.  The JDK does this, so
440         // it must be ok.
441         if (ref == null)
442           return null;
443         else
444           {
445             ResourceBundle rb = (ResourceBundle) ref.get();
446             if (rb != null)
447               {
448                 // RB should already have the right parent, except if
449                 // something very strange happened.
450                 return rb;
451               }
452             // If RB is null, then we previously found it but it was
453             // collected.  So we try again.
454           }
455       }
456
457     // foundBundle holds exact matches for the localizedName resource
458     // bundle, which may later be cached.
459     ResourceBundle foundBundle = null;
460     try
461       {
462         Class rbClass;
463         if (classloader == null)
464           rbClass = Class.forName(localizedName);
465         else
466           rbClass = classloader.loadClass(localizedName);
467         foundBundle = (ResourceBundle) rbClass.newInstance();
468         foundBundle.parent = bundle;
469         foundBundle.locale = locale;
470       }
471     catch (Exception ex)
472       {
473         // ignore them all
474         foundBundle = null;
475       }
476     if (foundBundle == null)
477       {
478         try
479           {
480             InputStream is;
481             final String resourceName
482               = localizedName.replace('.', '/') + ".properties";
483             if (classloader == null)
484               is = ClassLoader.getSystemResourceAsStream(resourceName);
485             else
486               is = classloader.getResourceAsStream(resourceName);
487             if (is != null)
488               {
489                 foundBundle = new PropertyResourceBundle(is);
490                 foundBundle.parent = bundle;
491                 foundBundle.locale = locale;
492               }
493           }
494         catch (IOException ex)
495           {
496           }
497       }
498
499     // Put the result into the hash table.  If we didn't find anything
500     // here, we record our parent bundle.  If we record `null' that means
501     // nothing, not even the base, was found.
502     if (foundBundle == null)
503       foundBundle = bundle;
504     if (foundBundle == null)
505       cache.put(localizedName, null);
506     else
507       cache.put(localizedName, new SoftReference(foundBundle));
508     return foundBundle;
509   }
510
511   /**
512    * Tries to load a the bundle for a given locale, also loads the backup
513    * locales with the same language.
514    *
515    * @param name the name
516    * @param locale the locale
517    * @param classloader the classloader
518    * @param bundle the backup (parent) bundle
519    * @return the resource bundle if it was loaded, otherwise the backup
520    */
521   private static final ResourceBundle tryLocalBundle(String baseName,
522                                                      Locale locale,
523                                                      ClassLoader classloader,
524                                                      ResourceBundle bundle,
525                                                      HashMap cache)
526   {
527     final String language = locale.getLanguage();
528     final String country = locale.getCountry();
529     final String variant = locale.getVariant();
530
531     StringBuffer sb = new StringBuffer(60);
532     sb.append(baseName);
533     sb.append('_');
534
535     if (language.length() > 0)
536       {
537         sb.append(language);
538         bundle = tryBundle(sb.toString(), new Locale(language),
539                            classloader, bundle, cache);
540       }
541     // If LANGUAGE was empty, we still need to try the other
542     // components, and the `_' is required.
543     sb.append('_');
544
545     if (country.length() > 0)
546       {
547         sb.append(country);
548         bundle = tryBundle(sb.toString(), new Locale(language, country),
549                            classloader, bundle, cache);
550       }
551     sb.append('_');
552
553     if (variant.length() > 0)
554       {
555         sb.append(variant);
556         bundle = tryBundle(sb.toString(), locale,
557                            classloader, bundle, cache);
558       }
559
560     return bundle;
561   }
562 }