Merge from mainline (gomp-merge-2005-02-26).
[official-gcc.git] / libjava / java / util / ResourceBundle.java
blob57a9bfc7dc7c91886422b57aef39bc2650e2fc8b
1 /* ResourceBundle -- aids in loading resource bundles
2 Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004
3 Free Software Foundation, Inc.
5 This file is part of GNU Classpath.
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20 02111-1307 USA.
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library. Thus, the terms and
24 conditions of the GNU General Public License cover the whole
25 combination.
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module. An independent module is a module which is not derived from
34 or based on this library. If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so. If you do not wish to do so, delete this
37 exception statement from your version. */
40 package java.util;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.lang.ref.Reference;
45 import java.lang.ref.SoftReference;
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.
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>).
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>
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.
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>.
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).
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.)
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
91 public abstract class ResourceBundle
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.
97 protected ResourceBundle parent;
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>.
104 private Locale locale;
106 private static native ClassLoader getCallingClassLoader();
109 * The resource bundle cache.
111 private static Map bundleCache;
114 * The last default Locale we saw. If this ever changes then we have to
115 * reset our caches.
117 private static Locale lastDefaultLocale;
120 * The `empty' locale is created once in order to optimize
121 * tryBundle().
123 private static final Locale emptyLocale = new Locale("");
126 * The constructor. It does nothing special.
128 public ResourceBundle()
133 * Get a String from this resource bundle. Since most localized Objects
134 * are Strings, this method provides a convenient way to get them without
135 * casting.
137 * @param key the name of the resource
138 * @throws MissingResourceException if the resource can't be found
139 * @throws NullPointerException if key is null
140 * @throws ClassCastException if resource is not a string
142 public final String getString(String key)
144 return (String) getObject(key);
148 * Get an array of Strings from this resource bundle. This method
149 * provides a convenient way to get it without casting.
151 * @param key the name of the resource
152 * @throws MissingResourceException if the resource can't be found
153 * @throws NullPointerException if key is null
154 * @throws ClassCastException if resource is not a string
156 public final String[] getStringArray(String key)
158 return (String[]) getObject(key);
162 * Get an object from this resource bundle. This will call
163 * <code>handleGetObject</code> for this resource and all of its parents,
164 * until it finds a non-null resource.
166 * @param key the name of the resource
167 * @throws MissingResourceException if the resource can't be found
168 * @throws NullPointerException if key is null
170 public final Object getObject(String key)
172 for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent)
174 Object o = bundle.handleGetObject(key);
175 if (o != null)
176 return o;
179 throw new MissingResourceException("Key not found", getClass().getName(),
180 key);
184 * Return the actual locale of this bundle. You can use it after calling
185 * getBundle, to know if the bundle for the desired locale was loaded or
186 * if the fall back was used.
188 * @return the bundle's locale
190 public Locale getLocale()
192 return locale;
196 * Set the parent of this bundle. The parent is consulted when you call
197 * getObject and there is no such resource in the current bundle.
199 * @param parent the parent of this bundle
201 protected void setParent(ResourceBundle parent)
203 this.parent = parent;
207 * Get the appropriate ResourceBundle for the default locale. This is like
208 * calling <code>getBundle(baseName, Locale.getDefault(),
209 * getClass().getClassLoader()</code>, except that any security check of
210 * getClassLoader won't fail.
212 * @param baseName the name of the ResourceBundle
213 * @return the desired resource bundle
214 * @throws MissingResourceException if the resource bundle can't be found
215 * @throws NullPointerException if baseName is null
217 public static ResourceBundle getBundle(String baseName)
219 ClassLoader cl = getCallingClassLoader();
220 if (cl == null)
221 cl = ClassLoader.getSystemClassLoader();
222 return getBundle(baseName, Locale.getDefault(), cl);
226 * Get the appropriate ResourceBundle for the given locale. This is like
227 * calling <code>getBundle(baseName, locale,
228 * getClass().getClassLoader()</code>, except that any security check of
229 * getClassLoader won't fail.
231 * @param baseName the name of the ResourceBundle
232 * @param locale A locale
233 * @return the desired resource bundle
234 * @throws MissingResourceException if the resource bundle can't be found
235 * @throws NullPointerException if baseName or locale is null
237 public static ResourceBundle getBundle(String baseName, Locale locale)
239 ClassLoader cl = getCallingClassLoader();
240 if (cl == null)
241 cl = ClassLoader.getSystemClassLoader();
242 return getBundle(baseName, locale, cl);
245 /** Cache key for the ResourceBundle cache. Resource bundles are keyed
246 by the combination of bundle name, locale, and class loader. */
247 private static class BundleKey
249 String baseName;
250 Locale locale;
251 ClassLoader classLoader;
252 int hashcode;
254 BundleKey() {}
256 BundleKey(String s, Locale l, ClassLoader cl)
258 set(s, l, cl);
261 void set(String s, Locale l, ClassLoader cl)
263 baseName = s;
264 locale = l;
265 classLoader = cl;
266 hashcode = baseName.hashCode() ^ locale.hashCode() ^
267 classLoader.hashCode();
270 public int hashCode()
272 return hashcode;
275 public boolean equals(Object o)
277 if (! (o instanceof BundleKey))
278 return false;
279 BundleKey key = (BundleKey) o;
280 return hashcode == key.hashcode &&
281 baseName.equals(key.baseName) &&
282 locale.equals(key.locale) &&
283 classLoader.equals(key.classLoader);
287 /** A cache lookup key. This avoids having to a new one for every
288 * getBundle() call. */
289 private static BundleKey lookupKey = new BundleKey();
291 /** Singleton cache entry to represent previous failed lookups. */
292 private static Object nullEntry = new Object();
295 * Get the appropriate ResourceBundle for the given locale. The following
296 * strategy is used:
298 * <p>A sequence of candidate bundle names are generated, and tested in
299 * this order, where the suffix 1 means the string from the specified
300 * locale, and the suffix 2 means the string from the default locale:</p>
302 * <ul>
303 * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li>
304 * <li>baseName + "_" + language1 + "_" + country1</li>
305 * <li>baseName + "_" + language1</li>
306 * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li>
307 * <li>baseName + "_" + language2 + "_" + country2</li>
308 * <li>baseName + "_" + language2</li>
309 * <li>baseName</li>
310 * </ul>
312 * <p>In the sequence, entries with an empty string are ignored. Next,
313 * <code>getBundle</code> tries to instantiate the resource bundle:</p>
315 * <ul>
316 * <li>First, an attempt is made to load a class in the specified classloader
317 * which is a subclass of ResourceBundle, and which has a public constructor
318 * with no arguments, via reflection.</li>
319 * <li>Next, a search is made for a property resource file, by replacing
320 * '.' with '/' and appending ".properties", and using
321 * ClassLoader.getResource(). If a file is found, then a
322 * PropertyResourceBundle is created from the file's contents.</li>
323 * </ul>
324 * If no resource bundle was found, a MissingResourceException is thrown.
326 * <p>Next, the parent chain is implemented. The remaining candidate names
327 * in the above sequence are tested in a similar manner, and if any results
328 * in a resource bundle, it is assigned as the parent of the first bundle
329 * using the <code>setParent</code> method (unless the first bundle already
330 * has a parent).</p>
332 * <p>For example, suppose the following class and property files are
333 * provided: MyResources.class, MyResources_fr_CH.properties,
334 * MyResources_fr_CH.class, MyResources_fr.properties,
335 * MyResources_en.properties, and MyResources_es_ES.class. The contents of
336 * all files are valid (that is, public non-abstract subclasses of
337 * ResourceBundle with public nullary constructors for the ".class" files,
338 * syntactically correct ".properties" files). The default locale is
339 * Locale("en", "UK").</p>
341 * <p>Calling getBundle with the shown locale argument values instantiates
342 * resource bundles from the following sources:</p>
344 * <ul>
345 * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent
346 * MyResources_fr.properties, parent MyResources.class</li>
347 * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent
348 * MyResources.class</li>
349 * <li>Locale("de", "DE"): result MyResources_en.properties, parent
350 * MyResources.class</li>
351 * <li>Locale("en", "US"): result MyResources_en.properties, parent
352 * MyResources.class</li>
353 * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent
354 * MyResources.class</li>
355 * </ul>
357 * <p>The file MyResources_fr_CH.properties is never used because it is hidden
358 * by MyResources_fr_CH.class.</p>
360 * @param baseName the name of the ResourceBundle
361 * @param locale A locale
362 * @param classloader a ClassLoader
363 * @return the desired resource bundle
364 * @throws MissingResourceException if the resource bundle can't be found
365 * @throws NullPointerException if any argument is null
366 * @since 1.2
368 // This method is synchronized so that the cache is properly
369 // handled.
370 public static synchronized ResourceBundle getBundle
371 (String baseName, Locale locale, ClassLoader classLoader)
373 // If the default locale changed since the last time we were called,
374 // all cache entries are invalidated.
375 Locale defaultLocale = Locale.getDefault();
376 if (defaultLocale != lastDefaultLocale)
378 bundleCache = new HashMap();
379 lastDefaultLocale = defaultLocale;
382 // This will throw NullPointerException if any arguments are null.
383 lookupKey.set(baseName, locale, classLoader);
385 Object obj = bundleCache.get(lookupKey);
386 ResourceBundle rb = null;
388 if (obj instanceof ResourceBundle)
390 return (ResourceBundle) obj;
392 else if (obj == nullEntry)
394 // Lookup has failed previously. Fall through.
396 else
398 // First, look for a bundle for the specified locale. We don't want
399 // the base bundle this time.
400 boolean wantBase = locale.equals(defaultLocale);
401 ResourceBundle bundle = tryBundle(baseName, locale, classLoader,
402 wantBase);
404 // Try the default locale if neccessary.
405 if (bundle == null && !locale.equals(defaultLocale))
406 bundle = tryBundle(baseName, defaultLocale, classLoader, true);
408 BundleKey key = new BundleKey(baseName, locale, classLoader);
409 if (bundle == null)
411 // Cache the fact that this lookup has previously failed.
412 bundleCache.put(key, nullEntry);
414 else
416 // Cache the result and return it.
417 bundleCache.put(key, bundle);
418 return bundle;
422 throw new MissingResourceException("Bundle " + baseName + " not found",
423 baseName, "");
427 * Override this method to provide the resource for a keys. This gets
428 * called by <code>getObject</code>. If you don't have a resource
429 * for the given key, you should return null instead throwing a
430 * MissingResourceException. You don't have to ask the parent, getObject()
431 * already does this; nor should you throw a MissingResourceException.
433 * @param key the key of the resource
434 * @return the resource for the key, or null if not in bundle
435 * @throws NullPointerException if key is null
437 protected abstract Object handleGetObject(String key);
440 * This method should return all keys for which a resource exists; you
441 * should include the enumeration of any parent's keys, after filtering out
442 * duplicates.
444 * @return an enumeration of the keys
446 public abstract Enumeration getKeys();
449 * Tries to load a class or a property file with the specified name.
451 * @param localizedName the name
452 * @param classloader the classloader
453 * @return the resource bundle if it was loaded, otherwise the backup
455 private static ResourceBundle tryBundle(String localizedName,
456 ClassLoader classloader)
458 ResourceBundle bundle = null;
461 Class rbClass;
462 if (classloader == null)
463 rbClass = Class.forName(localizedName);
464 else
465 rbClass = classloader.loadClass(localizedName);
466 // Note that we do the check up front instead of catching
467 // ClassCastException. The reason for this is that some crazy
468 // programs (Eclipse) have classes that do not extend
469 // ResourceBundle but that have the same name as a property
470 // bundle; in fact Eclipse relies on ResourceBundle not
471 // instantiating these classes.
472 if (ResourceBundle.class.isAssignableFrom(rbClass))
473 bundle = (ResourceBundle) rbClass.newInstance();
475 catch (IllegalAccessException ex) {}
476 catch (InstantiationException ex) {}
477 catch (ClassNotFoundException ex) {}
479 if (bundle == null)
483 InputStream is;
484 String resourceName
485 = localizedName.replace('.', '/') + ".properties";
486 if (classloader == null)
487 is = ClassLoader.getSystemResourceAsStream(resourceName);
488 else
489 is = classloader.getResourceAsStream(resourceName);
490 if (is != null)
491 bundle = new PropertyResourceBundle(is);
493 catch (IOException ex)
495 MissingResourceException mre = new MissingResourceException
496 ("Failed to load bundle", localizedName, "");
497 mre.initCause(ex);
498 throw mre;
502 return bundle;
506 * Tries to load a the bundle for a given locale, also loads the backup
507 * locales with the same language.
509 * @param baseName the raw bundle name, without locale qualifiers
510 * @param locale the locale
511 * @param classloader the classloader
512 * @param bundle the backup (parent) bundle
513 * @param wantBase whether a resource bundle made only from the base name
514 * (with no locale information attached) should be returned.
515 * @return the resource bundle if it was loaded, otherwise the backup
517 private static ResourceBundle tryBundle(String baseName, Locale locale,
518 ClassLoader classLoader,
519 boolean wantBase)
521 String language = locale.getLanguage();
522 String country = locale.getCountry();
523 String variant = locale.getVariant();
525 int baseLen = baseName.length();
527 // Build up a StringBuffer containing the complete bundle name, fully
528 // qualified by locale.
529 StringBuffer sb = new StringBuffer(baseLen + variant.length() + 7);
531 sb.append(baseName);
533 if (language.length() > 0)
535 sb.append('_');
536 sb.append(language);
538 if (country.length() > 0)
540 sb.append('_');
541 sb.append(country);
543 if (variant.length() > 0)
545 sb.append('_');
546 sb.append(variant);
551 // Now try to load bundles, starting with the most specialized name.
552 // Build up the parent chain as we go.
553 String bundleName = sb.toString();
554 ResourceBundle first = null; // The most specialized bundle.
555 ResourceBundle last = null; // The least specialized bundle.
557 while (true)
559 ResourceBundle foundBundle = tryBundle(bundleName, classLoader);
560 if (foundBundle != null)
562 if (first == null)
563 first = foundBundle;
564 if (last != null)
565 last.parent = foundBundle;
566 foundBundle.locale = locale;
567 last = foundBundle;
569 int idx = bundleName.lastIndexOf('_');
570 // Try the non-localized base name only if we already have a
571 // localized child bundle, or wantBase is true.
572 if (idx > baseLen || (idx == baseLen && (first != null || wantBase)))
573 bundleName = bundleName.substring(0, idx);
574 else
575 break;
578 return first;