source: trunk/gcc/libjava/java/util/ResourceBundle.java

Last change on this file was 1392, checked in by bird, 21 years ago

This commit was generated by cvs2svn to compensate for changes in r1391,
which included commits to RCS files with non-trunk default branches.

  • Property cvs2svn:cvs-rev set to 1.1.1.2
  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 19.6 KB
Line 
1/* ResourceBundle -- aids in loading resource bundles
2 Copyright (C) 1998, 1999, 2001, 2002 Free Software Foundation, Inc.
3
4This file is part of GNU Classpath.
5
6GNU Classpath is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 2, or (at your option)
9any later version.
10
11GNU Classpath is distributed in the hope that it will be useful, but
12WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with GNU Classpath; see the file COPYING. If not, write to the
18Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
1902111-1307 USA.
20
21Linking this library statically or dynamically with other modules is
22making a combined work based on this library. Thus, the terms and
23conditions of the GNU General Public License cover the whole
24combination.
25
26As a special exception, the copyright holders of this library give you
27permission to link this library with independent modules to produce an
28executable, regardless of the license terms of these independent
29modules, and to copy and distribute the resulting executable under
30terms of your choice, provided that you also meet, for each linked
31independent module, the terms and conditions of the license of that
32module. An independent module is a module which is not derived from
33or based on this library. If you modify this library, you may extend
34this exception to your version of the library, but you are not
35obligated to do so. If you do not wish to do so, delete this
36exception statement from your version. */
37
38
39package java.util;
40
41import java.lang.ref.Reference;
42import java.lang.ref.SoftReference;
43import java.io.InputStream;
44import java.io.IOException;
45import 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>
59baseName_<i>language code</i>_<i>country code</i>
60baseName_<i>language code</i>
61baseName_<i>def. language</i>_<i>def. country</i>_<i>def. variant</i>
62baseName_<i>def. language</i>_<i>def. country</i>
63baseName_<i>def. language</i>
64baseName</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 */
91public 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 throw new MissingResourceException("Key not found",
186 getClass().getName(), key);
187 }
188
189 /**
190 * Return the actual locale of this bundle. You can use it after calling
191 * getBundle, to know if the bundle for the desired locale was loaded or
192 * if the fall back was used.
193 *
194 * @return the bundle's locale
195 */
196 public Locale getLocale()
197 {
198 return locale;
199 }
200
201 /**
202 * Set the parent of this bundle. The parent is consulted when you call
203 * getObject and there is no such resource in the current bundle.
204 *
205 * @param parent the parent of this bundle
206 */
207 protected void setParent(ResourceBundle parent)
208 {
209 this.parent = parent;
210 }
211
212 /**
213 * Get the appropriate ResourceBundle for the default locale. This is like
214 * calling <code>getBundle(baseName, Locale.getDefault(),
215 * getClass().getClassLoader()</code>, except that any security check of
216 * getClassLoader won't fail.
217 *
218 * @param baseName the name of the ResourceBundle
219 * @return the desired resource bundle
220 * @throws MissingResourceException if the resource bundle can't be found
221 * @throws NullPointerException if baseName is null
222 */
223 public static final ResourceBundle getBundle(String baseName)
224 {
225 return getBundle(baseName, Locale.getDefault(),
226 getCallingClassLoader());
227 }
228
229 /**
230 * Get the appropriate ResourceBundle for the given locale. This is like
231 * calling <code>getBundle(baseName, locale,
232 * getClass().getClassLoader()</code>, except that any security check of
233 * getClassLoader won't fail.
234 *
235 * @param baseName the name of the ResourceBundle
236 * @param locale A locale
237 * @return the desired resource bundle
238 * @throws MissingResourceException if the resource bundle can't be found
239 * @throws NullPointerException if baseName or locale is null
240 */
241 public static final ResourceBundle getBundle(String baseName,
242 Locale locale)
243 {
244 return getBundle(baseName, locale, getCallingClassLoader());
245 }
246
247 /**
248 * Get the appropriate ResourceBundle for the given locale. The following
249 * strategy is used:
250 *
251 * <p>A sequence of candidate bundle names are generated, and tested in
252 * this order, where the suffix 1 means the string from the specified
253 * locale, and the suffix 2 means the string from the default locale:<ul>
254 * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li>
255 * <li>baseName + "_" + language1 + "_" + country1</li>
256 * <li>baseName + "_" + language1</li>
257 * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li>
258 * <li>baseName + "_" + language2 + "_" + country2</li>
259 * <li>baseName + "_" + language2<li>
260 * <li>baseName</li>
261 * </ul>
262 *
263 * <p>In the sequence, entries with an empty string are ignored. Next,
264 * <code>getBundle</code> tries to instantiate the resource bundle:<ul>
265 * <li>First, an attempt is made to load a class in the specified classloader
266 * which is a subclass of ResourceBundle, and which has a public constructor
267 * with no arguments, via reflection.</li>
268 * <li>Next, a search is made for a property resource file, by replacing
269 * '.' with '/' and appending ".properties", and using
270 * ClassLoader.getResource(). If a file is found, then a
271 * PropertyResourceBundle is created from the file's contents.</li>
272 * </ul>
273 * If no resource bundle was found, a MissingResourceException is thrown.
274 *
275 * <p>Next, the parent chain is implemented. The remaining candidate names
276 * in the above sequence are tested in a similar manner, and if any results
277 * in a resource bundle, it is assigned as the parent of the first bundle
278 * using the <code>setParent</code> method (unless the first bundle already
279 * has a parent).
280 *
281 * <p>For example, suppose the following class and property files are
282 * provided: MyResources.class, MyResources_fr_CH.properties,
283 * MyResources_fr_CH.class, MyResources_fr.properties,
284 * MyResources_en.properties, and MyResources_es_ES.class. The contents of
285 * all files are valid (that is, public non-abstract subclasses of
286 * ResourceBundle with public nullary constructors for the ".class" files,
287 * syntactically correct ".properties" files). The default locale is
288 * Locale("en", "UK").
289 *
290 * <p>Calling getBundle with the shown locale argument values instantiates
291 * resource bundles from the following sources:<ul>
292 * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent
293 * MyResources_fr.properties, parent MyResources.class</li>
294 * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent
295 * MyResources.class</li>
296 * <li>Locale("de", "DE"): result MyResources_en.properties, parent
297 * MyResources.class</li>
298 * <li>Locale("en", "US"): result MyResources_en.properties, parent
299 * MyResources.class</li>
300 * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent
301 * MyResources.class</li>
302 * </ul>
303 * The file MyResources_fr_CH.properties is never used because it is hidden
304 * by MyResources_fr_CH.class.
305 *
306 * @param baseName the name of the ResourceBundle
307 * @param locale A locale
308 * @param classloader a ClassLoader
309 * @return the desired resource bundle
310 * @throws MissingResourceException if the resource bundle can't be found
311 * @throws NullPointerException if any argument is null
312 * @since 1.2
313 */
314 // This method is synchronized so that the cache is properly
315 // handled.
316 public static final synchronized ResourceBundle getBundle
317 (String baseName, Locale locale, ClassLoader classLoader)
318 {
319 // This implementation searches the bundle in the reverse direction
320 // and builds the parent chain on the fly.
321 Locale defaultLocale = Locale.getDefault();
322 if (defaultLocale != lastDefaultLocale)
323 {
324 resourceBundleCache = new HashMap();
325 lastDefaultLocale = defaultLocale;
326 }
327 HashMap cache = (HashMap) resourceBundleCache.get(classLoader);
328 StringBuffer sb = new StringBuffer(60);
329 sb.append(baseName).append('_').append(locale);
330 String name = sb.toString();
331
332 if (cache == null)
333 {
334 cache = new HashMap();
335 resourceBundleCache.put(classLoader, cache);
336 }
337 else if (cache.containsKey(name))
338 {
339 Reference ref = (Reference) cache.get(name);
340 ResourceBundle result = null;
341 // If REF is null, that means that we added a `null' value to
342 // the hash map. That means we failed to find the bundle
343 // previously, and we cached that fact. The JDK does this, so
344 // it must be ok.
345 if (ref == null)
346 throw new MissingResourceException("Bundle " + baseName
347 + " not found",
348 baseName, "");
349 else
350 {
351 ResourceBundle rb = (ResourceBundle) ref.get();
352 if (rb != null)
353 {
354 // RB should already have the right parent, except if
355 // something very strange happened.
356 return rb;
357 }
358 // If RB is null, then we previously found it but it was
359 // collected. So we try again.
360 }
361 }
362
363 // It is ok if this returns null. We aren't required to have the
364 // base bundle.
365 ResourceBundle baseBundle = tryBundle(baseName, emptyLocale,
366 classLoader, null, cache);
367
368 // Now use our locale, followed by the default locale. We only
369 // need to try the default locale if our locale is different, and
370 // if our locale failed to yield a result other than the base
371 // bundle.
372 ResourceBundle bundle = tryLocalBundle(baseName, locale,
373 classLoader, baseBundle, cache);
374 if (bundle == baseBundle && !locale.equals(defaultLocale))
375 {
376 bundle = tryLocalBundle(baseName, defaultLocale,
377 classLoader, baseBundle, cache);
378 // We need to record that the argument locale maps to the
379 // bundle we just found. If we didn't find a bundle, record
380 // that instead.
381 if (bundle == null)
382 cache.put(name, null);
383 else
384 cache.put(name, new SoftReference(bundle));
385 }
386
387 if (bundle == null)
388 throw new MissingResourceException("Bundle " + baseName + " not found",
389 baseName, "");
390
391 return bundle;
392 }
393
394 /**
395 * Override this method to provide the resource for a keys. This gets
396 * called by <code>getObject</code>. If you don't have a resource
397 * for the given key, you should return null instead throwing a
398 * MissingResourceException. You don't have to ask the parent, getObject()
399 * already does this; nor should you throw a MissingResourceException.
400 *
401 * @param key the key of the resource
402 * @return the resource for the key, or null if not in bundle
403 * @throws NullPointerException if key is null
404 */
405 protected abstract Object handleGetObject(String key);
406
407 /**
408 * This method should return all keys for which a resource exists; you
409 * should include the enumeration of any parent's keys, after filtering out
410 * duplicates.
411 *
412 * @return an enumeration of the keys
413 */
414 public abstract Enumeration getKeys();
415
416 /**
417 * Tries to load a class or a property file with the specified name.
418 *
419 * @param localizedName the name
420 * @param locale the locale, that must be used exactly
421 * @param classloader the classloader
422 * @param bundle the backup (parent) bundle
423 * @return the resource bundle if it was loaded, otherwise the backup
424 */
425 private static final ResourceBundle tryBundle(String localizedName,
426 Locale locale,
427 ClassLoader classloader,
428 ResourceBundle bundle,
429 HashMap cache)
430 {
431 // First look into the cache.
432 if (cache.containsKey(localizedName))
433 {
434 Reference ref = (Reference) cache.get(localizedName);
435 ResourceBundle result = null;
436 // If REF is null, that means that we added a `null' value to
437 // the hash map. That means we failed to find the bundle
438 // previously, and we cached that fact. The JDK does this, so
439 // it must be ok.
440 if (ref == null)
441 return null;
442 else
443 {
444 ResourceBundle rb = (ResourceBundle) ref.get();
445 if (rb != null)
446 {
447 // RB should already have the right parent, except if
448 // something very strange happened.
449 return rb;
450 }
451 // If RB is null, then we previously found it but it was
452 // collected. So we try again.
453 }
454 }
455
456 // foundBundle holds exact matches for the localizedName resource
457 // bundle, which may later be cached.
458 ResourceBundle foundBundle = null;
459 try
460 {
461 Class rbClass;
462 if (classloader == null)
463 rbClass = Class.forName(localizedName);
464 else
465 rbClass = classloader.loadClass(localizedName);
466 foundBundle = (ResourceBundle) rbClass.newInstance();
467 foundBundle.parent = bundle;
468 foundBundle.locale = locale;
469 }
470 catch (Exception ex)
471 {
472 // ignore them all
473 }
474 if (foundBundle == null)
475 {
476 try
477 {
478 InputStream is;
479 final String resourceName
480 = localizedName.replace('.', '/') + ".properties";
481 if (classloader == null)
482 is = ClassLoader.getSystemResourceAsStream(resourceName);
483 else
484 is = classloader.getResourceAsStream(resourceName);
485 if (is != null)
486 {
487 foundBundle = new PropertyResourceBundle(is);
488 foundBundle.parent = bundle;
489 foundBundle.locale = locale;
490 }
491 }
492 catch (IOException ex)
493 {
494 }
495 }
496
497 // Put the result into the hash table. If we didn't find anything
498 // here, we record our parent bundle. If we record `null' that means
499 // nothing, not even the base, was found.
500 if (foundBundle == null)
501 foundBundle = bundle;
502 if (foundBundle == null)
503 cache.put(localizedName, null);
504 else
505 cache.put(localizedName, new SoftReference(foundBundle));
506 return foundBundle;
507 }
508
509 /**
510 * Tries to load a the bundle for a given locale, also loads the backup
511 * locales with the same language.
512 *
513 * @param name the name
514 * @param locale the locale
515 * @param classloader the classloader
516 * @param bundle the backup (parent) bundle
517 * @return the resource bundle if it was loaded, otherwise the backup
518 */
519 private static final ResourceBundle tryLocalBundle(String baseName,
520 Locale locale,
521 ClassLoader classloader,
522 ResourceBundle bundle,
523 HashMap cache)
524 {
525 final String language = locale.getLanguage();
526 final String country = locale.getCountry();
527 final String variant = locale.getVariant();
528
529 StringBuffer sb = new StringBuffer(60);
530 sb.append(baseName);
531 sb.append('_');
532
533 if (language.length() > 0)
534 {
535 sb.append(language);
536 bundle = tryBundle(sb.toString(), new Locale(language),
537 classloader, bundle, cache);
538 }
539 // If LANGUAGE was empty, we still need to try the other
540 // components, and the `_' is required.
541 sb.append('_');
542
543 if (country.length() > 0)
544 {
545 sb.append(country);
546 bundle = tryBundle(sb.toString(), new Locale(language, country),
547 classloader, bundle, cache);
548 }
549 sb.append('_');
550
551 if (variant.length() > 0)
552 {
553 sb.append(variant);
554 bundle = tryBundle(sb.toString(), locale,
555 classloader, bundle, cache);
556 }
557
558 return bundle;
559 }
560}
Note: See TracBrowser for help on using the repository browser.