1 /* ResourceBundle -- aids in loading resource bundles
2 Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004, 2005, 2006
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)
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., 51 Franklin Street, Fifth Floor, Boston, MA
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
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. */
42 import gnu.classpath.VMStackWalker;
44 import java.io.IOException;
45 import java.io.InputStream;
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>
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
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)
86 * @see ListResourceBundle
87 * @see PropertyResourceBundle
89 * @status updated to 1.4
91 public abstract class ResourceBundle
94 * Maximum size of our cache of <code>ResourceBundle</code>s keyed by
95 * {@link BundleKey} instances.
99 private static final int CACHE_SIZE = 100;
102 * The parent bundle. This is consulted when you call getObject and there
103 * is no such resource in the current bundle. This field may be null.
105 protected ResourceBundle parent;
108 * The locale of this resource bundle. You can read this with
109 * <code>getLocale</code> and it is automatically set in
110 * <code>getBundle</code>.
112 private Locale locale;
115 * A VM-wide cache of resource bundles already fetched.
117 * This {@link Map} is a Least Recently Used (LRU) cache, of the last
118 * {@link #CACHE_SIZE} accessed <code>ResourceBundle</code>s keyed by the
119 * tuple: default locale, resource-bundle name, resource-bundle locale, and
124 private static Map bundleCache = new LinkedHashMap(CACHE_SIZE + 1, 0.75F, true)
126 public boolean removeEldestEntry(Map.Entry entry)
128 return size() > CACHE_SIZE;
133 * The constructor. It does nothing special.
135 public ResourceBundle()
140 * Get a String from this resource bundle. Since most localized Objects
141 * are Strings, this method provides a convenient way to get them without
144 * @param key the name of the resource
145 * @throws MissingResourceException if the resource can't be found
146 * @throws NullPointerException if key is null
147 * @throws ClassCastException if resource is not a string
149 public final String getString(String key)
151 return (String) getObject(key);
155 * Get an array of Strings from this resource bundle. This method
156 * provides a convenient way to get it without casting.
158 * @param key the name of the resource
159 * @throws MissingResourceException if the resource can't be found
160 * @throws NullPointerException if key is null
161 * @throws ClassCastException if resource is not a string
163 public final String[] getStringArray(String key)
165 return (String[]) getObject(key);
169 * Get an object from this resource bundle. This will call
170 * <code>handleGetObject</code> for this resource and all of its parents,
171 * until it finds a non-null resource.
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
177 public final Object getObject(String key)
179 for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent)
181 Object o = bundle.handleGetObject(key);
186 String className = getClass().getName();
187 throw new MissingResourceException("Key '" + key
188 + "'not found in Bundle: "
189 + className, className, key);
193 * Return the actual locale of this bundle. You can use it after calling
194 * getBundle, to know if the bundle for the desired locale was loaded or
195 * if the fall back was used.
197 * @return the bundle's locale
199 public Locale getLocale()
205 * Set the parent of this bundle. The parent is consulted when you call
206 * getObject and there is no such resource in the current bundle.
208 * @param parent the parent of this bundle
210 protected void setParent(ResourceBundle parent)
212 this.parent = parent;
216 * Get the appropriate ResourceBundle for the default locale. This is like
217 * calling <code>getBundle(baseName, Locale.getDefault(),
218 * getClass().getClassLoader()</code>, except that any security check of
219 * getClassLoader won't fail.
221 * @param baseName the name of the ResourceBundle
222 * @return the desired resource bundle
223 * @throws MissingResourceException if the resource bundle can't be found
224 * @throws NullPointerException if baseName is null
226 public static ResourceBundle getBundle(String baseName)
228 ClassLoader cl = VMStackWalker.getCallingClassLoader();
230 cl = ClassLoader.getSystemClassLoader();
231 return getBundle(baseName, Locale.getDefault(), cl);
235 * Get the appropriate ResourceBundle for the given locale. This is like
236 * calling <code>getBundle(baseName, locale,
237 * getClass().getClassLoader()</code>, except that any security check of
238 * getClassLoader won't fail.
240 * @param baseName the name of the ResourceBundle
241 * @param locale A locale
242 * @return the desired resource bundle
243 * @throws MissingResourceException if the resource bundle can't be found
244 * @throws NullPointerException if baseName or locale is null
246 public static ResourceBundle getBundle(String baseName, Locale locale)
248 ClassLoader cl = VMStackWalker.getCallingClassLoader();
250 cl = ClassLoader.getSystemClassLoader();
251 return getBundle(baseName, locale, cl);
254 /** Cache key for the ResourceBundle cache. Resource bundles are keyed
255 by the combination of bundle name, locale, and class loader. */
256 private static class BundleKey
258 Locale defaultLocale;
261 ClassLoader classLoader;
266 BundleKey(Locale dl, String s, Locale l, ClassLoader cl)
271 void set(Locale dl, String s, Locale l, ClassLoader cl)
277 hashcode = defaultLocale.hashCode() ^ baseName.hashCode()
278 ^ locale.hashCode() ^ classLoader.hashCode();
281 public int hashCode()
286 public boolean equals(Object o)
288 if (! (o instanceof BundleKey))
290 BundleKey key = (BundleKey) o;
291 return hashcode == key.hashcode
292 && defaultLocale.equals(key.defaultLocale)
293 && baseName.equals(key.baseName)
294 && locale.equals(key.locale)
295 && classLoader.equals(key.classLoader);
299 /** A cache lookup key. This avoids having to a new one for every
300 * getBundle() call. */
301 private static BundleKey lookupKey = new BundleKey();
303 /** Singleton cache entry to represent previous failed lookups. */
304 private static Object nullEntry = new Object();
307 * Get the appropriate ResourceBundle for the given locale. The following
310 * <p>A sequence of candidate bundle names are generated, and tested in
311 * this order, where the suffix 1 means the string from the specified
312 * locale, and the suffix 2 means the string from the default locale:</p>
315 * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li>
316 * <li>baseName + "_" + language1 + "_" + country1</li>
317 * <li>baseName + "_" + language1</li>
318 * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li>
319 * <li>baseName + "_" + language2 + "_" + country2</li>
320 * <li>baseName + "_" + language2</li>
324 * <p>In the sequence, entries with an empty string are ignored. Next,
325 * <code>getBundle</code> tries to instantiate the resource bundle:</p>
328 * <li>First, an attempt is made to load a class in the specified classloader
329 * which is a subclass of ResourceBundle, and which has a public constructor
330 * with no arguments, via reflection.</li>
331 * <li>Next, a search is made for a property resource file, by replacing
332 * '.' with '/' and appending ".properties", and using
333 * ClassLoader.getResource(). If a file is found, then a
334 * PropertyResourceBundle is created from the file's contents.</li>
336 * If no resource bundle was found, a MissingResourceException is thrown.
338 * <p>Next, the parent chain is implemented. The remaining candidate names
339 * in the above sequence are tested in a similar manner, and if any results
340 * in a resource bundle, it is assigned as the parent of the first bundle
341 * using the <code>setParent</code> method (unless the first bundle already
344 * <p>For example, suppose the following class and property files are
345 * provided: MyResources.class, MyResources_fr_CH.properties,
346 * MyResources_fr_CH.class, MyResources_fr.properties,
347 * MyResources_en.properties, and MyResources_es_ES.class. The contents of
348 * all files are valid (that is, public non-abstract subclasses of
349 * ResourceBundle with public nullary constructors for the ".class" files,
350 * syntactically correct ".properties" files). The default locale is
351 * Locale("en", "UK").</p>
353 * <p>Calling getBundle with the shown locale argument values instantiates
354 * resource bundles from the following sources:</p>
357 * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent
358 * MyResources_fr.properties, parent MyResources.class</li>
359 * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent
360 * MyResources.class</li>
361 * <li>Locale("de", "DE"): result MyResources_en.properties, parent
362 * MyResources.class</li>
363 * <li>Locale("en", "US"): result MyResources_en.properties, parent
364 * MyResources.class</li>
365 * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent
366 * MyResources.class</li>
369 * <p>The file MyResources_fr_CH.properties is never used because it is hidden
370 * by MyResources_fr_CH.class.</p>
372 * @param baseName the name of the ResourceBundle
373 * @param locale A locale
374 * @param classLoader a ClassLoader
375 * @return the desired resource bundle
376 * @throws MissingResourceException if the resource bundle can't be found
377 * @throws NullPointerException if any argument is null
380 // This method is synchronized so that the cache is properly
382 public static synchronized ResourceBundle getBundle
383 (String baseName, Locale locale, ClassLoader classLoader)
385 Locale defaultLocale = Locale.getDefault();
386 // This will throw NullPointerException if any arguments are null.
387 lookupKey.set(defaultLocale, baseName, locale, classLoader);
388 Object obj = bundleCache.get(lookupKey);
389 if (obj instanceof ResourceBundle)
390 return (ResourceBundle) obj;
392 if (obj == nullEntry)
393 throw new MissingResourceException("Bundle " + baseName
394 + " not found for locale " + locale
395 + " by classloader " + classLoader,
397 // First, look for a bundle for the specified locale. We don't want
398 // the base bundle this time.
399 boolean wantBase = locale.equals(defaultLocale);
400 ResourceBundle bundle = tryBundle(baseName, locale, classLoader, wantBase);
401 // Try the default locale if neccessary.
402 if (bundle == null && ! wantBase)
403 bundle = tryBundle(baseName, defaultLocale, classLoader, true);
405 BundleKey key = new BundleKey(defaultLocale, baseName, locale, classLoader);
408 // Cache the fact that this lookup has previously failed.
409 bundleCache.put(key, nullEntry);
410 throw new MissingResourceException("Bundle " + baseName
411 + " not found for locale " + locale
412 + " by classloader " + classLoader,
415 // Cache the result and return it.
416 bundleCache.put(key, bundle);
421 * Override this method to provide the resource for a keys. This gets
422 * called by <code>getObject</code>. If you don't have a resource
423 * for the given key, you should return null instead throwing a
424 * MissingResourceException. You don't have to ask the parent, getObject()
425 * already does this; nor should you throw a MissingResourceException.
427 * @param key the key of the resource
428 * @return the resource for the key, or null if not in bundle
429 * @throws NullPointerException if key is null
431 protected abstract Object handleGetObject(String key);
434 * This method should return all keys for which a resource exists; you
435 * should include the enumeration of any parent's keys, after filtering out
438 * @return an enumeration of the keys
440 public abstract Enumeration<String> getKeys();
443 * Tries to load a class or a property file with the specified name.
445 * @param localizedName the name
446 * @param classloader the classloader
447 * @return the resource bundle if it was loaded, otherwise the backup
449 private static ResourceBundle tryBundle(String localizedName,
450 ClassLoader classloader)
452 ResourceBundle bundle = null;
456 if (classloader == null)
457 rbClass = Class.forName(localizedName);
459 rbClass = classloader.loadClass(localizedName);
460 // Note that we do the check up front instead of catching
461 // ClassCastException. The reason for this is that some crazy
462 // programs (Eclipse) have classes that do not extend
463 // ResourceBundle but that have the same name as a property
464 // bundle; in fact Eclipse relies on ResourceBundle not
465 // instantiating these classes.
466 if (ResourceBundle.class.isAssignableFrom(rbClass))
467 bundle = (ResourceBundle) rbClass.newInstance();
469 catch (Exception ex) {}
477 = localizedName.replace('.', '/') + ".properties";
478 if (classloader == null)
479 is = ClassLoader.getSystemResourceAsStream(resourceName);
481 is = classloader.getResourceAsStream(resourceName);
483 bundle = new PropertyResourceBundle(is);
485 catch (IOException ex)
487 MissingResourceException mre = new MissingResourceException
488 ("Failed to load bundle: " + localizedName, localizedName, "");
498 * Tries to load a the bundle for a given locale, also loads the backup
499 * locales with the same language.
501 * @param baseName the raw bundle name, without locale qualifiers
502 * @param locale the locale
503 * @param classLoader the classloader
504 * @param wantBase whether a resource bundle made only from the base name
505 * (with no locale information attached) should be returned.
506 * @return the resource bundle if it was loaded, otherwise the backup
508 private static ResourceBundle tryBundle(String baseName, Locale locale,
509 ClassLoader classLoader,
512 String language = locale.getLanguage();
513 String country = locale.getCountry();
514 String variant = locale.getVariant();
516 int baseLen = baseName.length();
518 // Build up a StringBuffer containing the complete bundle name, fully
519 // qualified by locale.
520 StringBuffer sb = new StringBuffer(baseLen + variant.length() + 7);
524 if (language.length() > 0)
529 if (country.length() > 0)
534 if (variant.length() > 0)
542 // Now try to load bundles, starting with the most specialized name.
543 // Build up the parent chain as we go.
544 String bundleName = sb.toString();
545 ResourceBundle first = null; // The most specialized bundle.
546 ResourceBundle last = null; // The least specialized bundle.
550 ResourceBundle foundBundle = tryBundle(bundleName, classLoader);
551 if (foundBundle != null)
556 last.parent = foundBundle;
557 foundBundle.locale = locale;
560 int idx = bundleName.lastIndexOf('_');
561 // Try the non-localized base name only if we already have a
562 // localized child bundle, or wantBase is true.
563 if (idx > baseLen || (idx == baseLen && (first != null || wantBase)))
564 bundleName = bundleName.substring(0, idx);