104 |
* <code>getBundle</code>. |
* <code>getBundle</code>. |
105 |
*/ |
*/ |
106 |
private Locale locale; |
private Locale locale; |
107 |
|
|
108 |
/** |
/** |
109 |
* We override SecurityManager in order to access getClassContext(). |
* We override SecurityManager in order to access getClassContext(). |
110 |
*/ |
*/ |
125 |
{ |
{ |
126 |
Class[] stack = getClassContext(); |
Class[] stack = getClassContext(); |
127 |
for (int i = 0; i < stack.length; i++) |
for (int i = 0; i < stack.length; i++) |
128 |
{ |
{ |
129 |
if (stack[i] != Security.class && stack[i] != ResourceBundle.class) |
if (stack[i] != Security.class && stack[i] != ResourceBundle.class) |
130 |
return stack[i].getClassLoader(); |
return stack[i].getClassLoader(); |
131 |
} |
} |
132 |
|
|
133 |
return null; |
return null; |
134 |
} |
} |
138 |
private static final Security security |
private static final Security security |
139 |
= (Security) AccessController.doPrivileged(new PrivilegedAction() |
= (Security) AccessController.doPrivileged(new PrivilegedAction() |
140 |
{ |
{ |
141 |
// This will always work since java.util classes have (all) system |
// This will always work since java.util classes have (all) system |
142 |
// permissions. |
// permissions. |
143 |
public Object run() |
public Object run() |
144 |
{ |
{ |
145 |
return new Security(); |
return new Security(); |
146 |
} |
} |
147 |
} |
} |
148 |
); |
); |
149 |
|
|
150 |
/** |
/** |
151 |
* The resource bundle cache. This is a two-level hash map: The key |
* The resource bundle cache. |
|
* is the class loader, the value is a new HashMap. The key of this |
|
|
* second hash map is the localized name, the value is a soft |
|
|
* references to the resource bundle. |
|
152 |
*/ |
*/ |
153 |
private static Map resourceBundleCache; |
private static Map bundleCache; |
154 |
|
|
155 |
/** |
/** |
156 |
* The last default Locale we saw. If this ever changes then we have to |
* The last default Locale we saw. If this ever changes then we have to |
212 |
public final Object getObject(String key) |
public final Object getObject(String key) |
213 |
{ |
{ |
214 |
for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent) |
for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent) |
215 |
try |
{ |
216 |
{ |
Object o = bundle.handleGetObject(key); |
217 |
Object o = bundle.handleGetObject(key); |
if (o != null) |
218 |
if (o != null) |
return o; |
219 |
return o; |
} |
|
} |
|
|
catch (MissingResourceException ex) |
|
|
{ |
|
|
} |
|
220 |
|
|
221 |
throw new MissingResourceException("Key not found", getClass().getName(), |
throw new MissingResourceException("Key not found", getClass().getName(), |
222 |
key); |
key); |
256 |
* @throws MissingResourceException if the resource bundle can't be found |
* @throws MissingResourceException if the resource bundle can't be found |
257 |
* @throws NullPointerException if baseName is null |
* @throws NullPointerException if baseName is null |
258 |
*/ |
*/ |
259 |
public static final ResourceBundle getBundle(String baseName) |
public static ResourceBundle getBundle(String baseName) |
260 |
{ |
{ |
261 |
return getBundle(baseName, Locale.getDefault(), |
ClassLoader cl = security.getCallingClassLoader(); |
262 |
security.getCallingClassLoader()); |
if (cl == null) |
263 |
|
cl = ClassLoader.getSystemClassLoader(); |
264 |
|
return getBundle(baseName, Locale.getDefault(), cl); |
265 |
} |
} |
266 |
|
|
267 |
/** |
/** |
276 |
* @throws MissingResourceException if the resource bundle can't be found |
* @throws MissingResourceException if the resource bundle can't be found |
277 |
* @throws NullPointerException if baseName or locale is null |
* @throws NullPointerException if baseName or locale is null |
278 |
*/ |
*/ |
279 |
public static final ResourceBundle getBundle(String baseName, |
public static ResourceBundle getBundle(String baseName, Locale locale) |
|
Locale locale) |
|
280 |
{ |
{ |
281 |
return getBundle(baseName, locale, security.getCallingClassLoader()); |
ClassLoader cl = security.getCallingClassLoader(); |
282 |
|
if (cl == null) |
283 |
|
cl = ClassLoader.getSystemClassLoader(); |
284 |
|
return getBundle(baseName, locale, cl); |
285 |
} |
} |
286 |
|
|
287 |
|
/** Cache key for the ResourceBundle cache. Resource bundles are keyed |
288 |
|
by the combination of bundle name, locale, and class loader. */ |
289 |
|
private static class BundleKey |
290 |
|
{ |
291 |
|
String baseName; |
292 |
|
Locale locale; |
293 |
|
ClassLoader classLoader; |
294 |
|
int hashcode; |
295 |
|
|
296 |
|
BundleKey() {} |
297 |
|
|
298 |
|
BundleKey(String s, Locale l, ClassLoader cl) |
299 |
|
{ |
300 |
|
set(s, l, cl); |
301 |
|
} |
302 |
|
|
303 |
|
void set(String s, Locale l, ClassLoader cl) |
304 |
|
{ |
305 |
|
baseName = s; |
306 |
|
locale = l; |
307 |
|
classLoader = cl; |
308 |
|
hashcode = baseName.hashCode() ^ locale.hashCode() ^ |
309 |
|
classLoader.hashCode(); |
310 |
|
} |
311 |
|
|
312 |
|
public int hashCode() |
313 |
|
{ |
314 |
|
return hashcode; |
315 |
|
} |
316 |
|
|
317 |
|
public boolean equals(Object o) |
318 |
|
{ |
319 |
|
if (! (o instanceof BundleKey)) |
320 |
|
return false; |
321 |
|
BundleKey key = (BundleKey) o; |
322 |
|
return hashcode == key.hashcode && |
323 |
|
baseName.equals(key.baseName) && |
324 |
|
locale.equals(key.locale) && |
325 |
|
classLoader.equals(key.classLoader); |
326 |
|
} |
327 |
|
} |
328 |
|
|
329 |
|
/** A cache lookup key. This avoids having to a new one for every |
330 |
|
* getBundle() call. */ |
331 |
|
private static BundleKey lookupKey = new BundleKey(); |
332 |
|
|
333 |
|
/** Singleton cache entry to represent previous failed lookups. */ |
334 |
|
private static Object nullEntry = new Object(); |
335 |
|
|
336 |
/** |
/** |
337 |
* Get the appropriate ResourceBundle for the given locale. The following |
* Get the appropriate ResourceBundle for the given locale. The following |
338 |
* strategy is used: |
* strategy is used: |
409 |
*/ |
*/ |
410 |
// This method is synchronized so that the cache is properly |
// This method is synchronized so that the cache is properly |
411 |
// handled. |
// handled. |
412 |
public static final synchronized ResourceBundle getBundle |
public static synchronized ResourceBundle getBundle |
413 |
(String baseName, Locale locale, ClassLoader classLoader) |
(String baseName, Locale locale, ClassLoader classLoader) |
414 |
{ |
{ |
415 |
// This implementation searches the bundle in the reverse direction |
// If the default locale changed since the last time we were called, |
416 |
// and builds the parent chain on the fly. |
// all cache entries are invalidated. |
417 |
Locale defaultLocale = Locale.getDefault(); |
Locale defaultLocale = Locale.getDefault(); |
418 |
if (defaultLocale != lastDefaultLocale) |
if (defaultLocale != lastDefaultLocale) |
419 |
{ |
{ |
420 |
resourceBundleCache = new HashMap(); |
bundleCache = new HashMap(); |
421 |
lastDefaultLocale = defaultLocale; |
lastDefaultLocale = defaultLocale; |
422 |
} |
} |
|
HashMap cache = (HashMap) resourceBundleCache.get(classLoader); |
|
|
StringBuffer sb = new StringBuffer(60); |
|
|
sb.append(baseName).append('_').append(locale); |
|
|
String name = sb.toString(); |
|
423 |
|
|
424 |
if (cache == null) |
// This will throw NullPointerException if any arguments are null. |
425 |
|
lookupKey.set(baseName, locale, classLoader); |
426 |
|
|
427 |
|
Object obj = bundleCache.get(lookupKey); |
428 |
|
ResourceBundle rb = null; |
429 |
|
|
430 |
|
if (obj instanceof ResourceBundle) |
431 |
{ |
{ |
432 |
cache = new HashMap(); |
return (ResourceBundle) obj; |
|
resourceBundleCache.put(classLoader, cache); |
|
433 |
} |
} |
434 |
else if (cache.containsKey(name)) |
else if (obj == nullEntry) |
435 |
{ |
{ |
436 |
Reference ref = (Reference) cache.get(name); |
// Lookup has failed previously. Fall through. |
|
// If REF is null, that means that we added a `null' value to |
|
|
// the hash map. That means we failed to find the bundle |
|
|
// previously, and we cached that fact. The JDK does this, so |
|
|
// it must be ok. |
|
|
if (ref == null) |
|
|
throw new MissingResourceException("Bundle " + baseName |
|
|
+ " not found", |
|
|
baseName, ""); |
|
|
else |
|
|
{ |
|
|
ResourceBundle rb = (ResourceBundle) ref.get(); |
|
|
if (rb != null) |
|
|
{ |
|
|
// RB should already have the right parent, except if |
|
|
// something very strange happened. |
|
|
return rb; |
|
|
} |
|
|
// If RB is null, then we previously found it but it was |
|
|
// collected. So we try again. |
|
|
} |
|
437 |
} |
} |
438 |
|
else |
|
// It is ok if this returns null. We aren't required to have the |
|
|
// base bundle. |
|
|
ResourceBundle baseBundle = tryBundle(baseName, emptyLocale, |
|
|
classLoader, null, cache); |
|
|
|
|
|
// Now use our locale, followed by the default locale. We only |
|
|
// need to try the default locale if our locale is different, and |
|
|
// if our locale failed to yield a result other than the base |
|
|
// bundle. |
|
|
ResourceBundle bundle = tryLocalBundle(baseName, locale, |
|
|
classLoader, baseBundle, cache); |
|
|
if (bundle == baseBundle && !locale.equals(defaultLocale)) |
|
439 |
{ |
{ |
440 |
bundle = tryLocalBundle(baseName, defaultLocale, |
// First, look for a bundle for the specified locale. We don't want |
441 |
classLoader, baseBundle, cache); |
// the base bundle this time. |
442 |
// We need to record that the argument locale maps to the |
boolean wantBase = locale.equals(defaultLocale); |
443 |
// bundle we just found. If we didn't find a bundle, record |
ResourceBundle bundle = tryBundle(baseName, locale, classLoader, |
444 |
// that instead. |
wantBase); |
445 |
if (bundle == null) |
|
446 |
cache.put(name, null); |
// Try the default locale if neccessary. |
447 |
|
if (bundle == null && !locale.equals(defaultLocale)) |
448 |
|
bundle = tryBundle(baseName, defaultLocale, classLoader, true); |
449 |
|
|
450 |
|
BundleKey key = new BundleKey(baseName, locale, classLoader); |
451 |
|
if (bundle == null) |
452 |
|
{ |
453 |
|
// Cache the fact that this lookup has previously failed. |
454 |
|
bundleCache.put(key, nullEntry); |
455 |
|
} |
456 |
else |
else |
457 |
cache.put(name, new SoftReference(bundle)); |
{ |
458 |
|
// Cache the result and return it. |
459 |
|
bundleCache.put(key, bundle); |
460 |
|
return bundle; |
461 |
|
} |
462 |
} |
} |
463 |
|
|
464 |
if (bundle == null) |
throw new MissingResourceException("Bundle " + baseName + " not found", |
465 |
throw new MissingResourceException("Bundle " + baseName + " not found", |
baseName, ""); |
|
baseName, ""); |
|
|
|
|
|
return bundle; |
|
466 |
} |
} |
467 |
|
|
468 |
/** |
/** |
491 |
* Tries to load a class or a property file with the specified name. |
* Tries to load a class or a property file with the specified name. |
492 |
* |
* |
493 |
* @param localizedName the name |
* @param localizedName the name |
|
* @param locale the locale, that must be used exactly |
|
494 |
* @param classloader the classloader |
* @param classloader the classloader |
|
* @param bundle the backup (parent) bundle |
|
495 |
* @return the resource bundle if it was loaded, otherwise the backup |
* @return the resource bundle if it was loaded, otherwise the backup |
496 |
*/ |
*/ |
497 |
private static ResourceBundle tryBundle(String localizedName, Locale locale, |
private static ResourceBundle tryBundle(String localizedName, |
498 |
ClassLoader classloader, |
ClassLoader classloader) |
|
ResourceBundle bundle, HashMap cache) |
|
499 |
{ |
{ |
500 |
// First look into the cache. |
ResourceBundle bundle = null; |
|
if (cache.containsKey(localizedName)) |
|
|
{ |
|
|
Reference ref = (Reference) cache.get(localizedName); |
|
|
// If REF is null, that means that we added a `null' value to |
|
|
// the hash map. That means we failed to find the bundle |
|
|
// previously, and we cached that fact. The JDK does this, so |
|
|
// it must be ok. |
|
|
if (ref == null) |
|
|
return null; |
|
|
else |
|
|
{ |
|
|
ResourceBundle rb = (ResourceBundle) ref.get(); |
|
|
if (rb != null) |
|
|
{ |
|
|
// RB should already have the right parent, except if |
|
|
// something very strange happened. |
|
|
return rb; |
|
|
} |
|
|
// If RB is null, then we previously found it but it was |
|
|
// collected. So we try again. |
|
|
} |
|
|
} |
|
|
|
|
|
// foundBundle holds exact matches for the localizedName resource |
|
|
// bundle, which may later be cached. |
|
|
ResourceBundle foundBundle = null; |
|
501 |
try |
try |
502 |
{ |
{ |
503 |
Class rbClass; |
Class rbClass; |
505 |
rbClass = Class.forName(localizedName); |
rbClass = Class.forName(localizedName); |
506 |
else |
else |
507 |
rbClass = classloader.loadClass(localizedName); |
rbClass = classloader.loadClass(localizedName); |
508 |
foundBundle = (ResourceBundle) rbClass.newInstance(); |
// Note that we do the check up front instead of catching |
509 |
foundBundle.parent = bundle; |
// ClassCastException. The reason for this is that some crazy |
510 |
foundBundle.locale = locale; |
// programs (Eclipse) have classes that do not extend |
511 |
} |
// ResourceBundle but that have the same name as a property |
512 |
catch (Exception ex) |
// bundle; in fact Eclipse relies on ResourceBundle not |
513 |
{ |
// instantiating these classes. |
514 |
// ignore them all |
if (ResourceBundle.class.isAssignableFrom(rbClass)) |
515 |
foundBundle = null; |
bundle = (ResourceBundle) rbClass.newInstance(); |
516 |
} |
} |
517 |
if (foundBundle == null) |
catch (IllegalAccessException ex) {} |
518 |
|
catch (InstantiationException ex) {} |
519 |
|
catch (ClassNotFoundException ex) {} |
520 |
|
|
521 |
|
if (bundle == null) |
522 |
{ |
{ |
523 |
try |
try |
524 |
{ |
{ |
525 |
InputStream is; |
InputStream is; |
526 |
final String resourceName |
String resourceName |
527 |
= localizedName.replace('.', '/') + ".properties"; |
= localizedName.replace('.', '/') + ".properties"; |
528 |
if (classloader == null) |
if (classloader == null) |
529 |
is = ClassLoader.getSystemResourceAsStream(resourceName); |
is = ClassLoader.getSystemResourceAsStream(resourceName); |
530 |
else |
else |
531 |
is = classloader.getResourceAsStream(resourceName); |
is = classloader.getResourceAsStream(resourceName); |
532 |
if (is != null) |
if (is != null) |
533 |
{ |
bundle = new PropertyResourceBundle(is); |
|
foundBundle = new PropertyResourceBundle(is); |
|
|
foundBundle.parent = bundle; |
|
|
foundBundle.locale = locale; |
|
|
} |
|
534 |
} |
} |
535 |
catch (IOException ex) |
catch (IOException ex) |
536 |
{ |
{ |
537 |
|
MissingResourceException mre = new MissingResourceException |
538 |
|
("Failed to load bundle", localizedName, ""); |
539 |
|
mre.initCause(ex); |
540 |
|
throw mre; |
541 |
} |
} |
542 |
} |
} |
543 |
|
|
544 |
// Put the result into the hash table. If we didn't find anything |
return bundle; |
|
// here, we record our parent bundle. If we record `null' that means |
|
|
// nothing, not even the base, was found. |
|
|
if (foundBundle == null) |
|
|
foundBundle = bundle; |
|
|
if (foundBundle == null) |
|
|
cache.put(localizedName, null); |
|
|
else |
|
|
cache.put(localizedName, new SoftReference(foundBundle)); |
|
|
return foundBundle; |
|
545 |
} |
} |
546 |
|
|
547 |
/** |
/** |
548 |
* Tries to load a the bundle for a given locale, also loads the backup |
* Tries to load a the bundle for a given locale, also loads the backup |
549 |
* locales with the same language. |
* locales with the same language. |
550 |
* |
* |
551 |
* @param name the name |
* @param baseName the raw bundle name, without locale qualifiers |
552 |
* @param locale the locale |
* @param locale the locale |
553 |
* @param classloader the classloader |
* @param classloader the classloader |
554 |
* @param bundle the backup (parent) bundle |
* @param bundle the backup (parent) bundle |
555 |
|
* @param wantBase whether a resource bundle made only from the base name |
556 |
|
* (with no locale information attached) should be returned. |
557 |
* @return the resource bundle if it was loaded, otherwise the backup |
* @return the resource bundle if it was loaded, otherwise the backup |
558 |
*/ |
*/ |
559 |
private static ResourceBundle tryLocalBundle(String baseName, Locale locale, |
private static ResourceBundle tryBundle(String baseName, Locale locale, |
560 |
ClassLoader classloader, |
ClassLoader classLoader, |
561 |
ResourceBundle bundle, |
boolean wantBase) |
562 |
HashMap cache) |
{ |
563 |
{ |
String language = locale.getLanguage(); |
564 |
final String language = locale.getLanguage(); |
String country = locale.getCountry(); |
565 |
final String country = locale.getCountry(); |
String variant = locale.getVariant(); |
566 |
final String variant = locale.getVariant(); |
|
567 |
|
int baseLen = baseName.length(); |
568 |
|
|
569 |
|
// Build up a StringBuffer containing the complete bundle name, fully |
570 |
|
// qualified by locale. |
571 |
|
StringBuffer sb = new StringBuffer(baseLen + variant.length() + 7); |
572 |
|
|
|
StringBuffer sb = new StringBuffer(60); |
|
573 |
sb.append(baseName); |
sb.append(baseName); |
574 |
sb.append('_'); |
|
|
|
|
575 |
if (language.length() > 0) |
if (language.length() > 0) |
576 |
{ |
{ |
577 |
|
sb.append('_'); |
578 |
sb.append(language); |
sb.append(language); |
579 |
bundle = tryBundle(sb.toString(), new Locale(language), |
|
580 |
classloader, bundle, cache); |
if (country.length() > 0) |
581 |
} |
{ |
582 |
// If LANGUAGE was empty, we still need to try the other |
sb.append('_'); |
583 |
// components, and the `_' is required. |
sb.append(country); |
584 |
sb.append('_'); |
|
585 |
|
if (variant.length() > 0) |
586 |
if (country.length() > 0) |
{ |
587 |
{ |
sb.append('_'); |
588 |
sb.append(country); |
sb.append(variant); |
589 |
bundle = tryBundle(sb.toString(), new Locale(language, country), |
} |
590 |
classloader, bundle, cache); |
} |
591 |
} |
} |
|
sb.append('_'); |
|
592 |
|
|
593 |
if (variant.length() > 0) |
// Now try to load bundles, starting with the most specialized name. |
594 |
|
// Build up the parent chain as we go. |
595 |
|
String bundleName = sb.toString(); |
596 |
|
ResourceBundle first = null; // The most specialized bundle. |
597 |
|
ResourceBundle last = null; // The least specialized bundle. |
598 |
|
|
599 |
|
while (true) |
600 |
{ |
{ |
601 |
sb.append(variant); |
ResourceBundle foundBundle = tryBundle(bundleName, classLoader); |
602 |
bundle = tryBundle(sb.toString(), locale, |
if (foundBundle != null) |
603 |
classloader, bundle, cache); |
{ |
604 |
|
if (first == null) |
605 |
|
first = foundBundle; |
606 |
|
if (last != null) |
607 |
|
last.parent = foundBundle; |
608 |
|
foundBundle.locale = locale; |
609 |
|
last = foundBundle; |
610 |
|
} |
611 |
|
int idx = bundleName.lastIndexOf('_'); |
612 |
|
// Try the non-localized base name only if we already have a |
613 |
|
// localized child bundle, or wantBase is true. |
614 |
|
if (idx > baseLen || (idx == baseLen && (first != null || wantBase))) |
615 |
|
bundleName = bundleName.substring(0, idx); |
616 |
|
else |
617 |
|
break; |
618 |
} |
} |
619 |
|
|
620 |
return bundle; |
return first; |
621 |
} |
} |
622 |
} |
} |