OSDN Git Service

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