Frames | No Frames |
1: /* ResourceBundle -- aids in loading resource bundles 2: Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004, 2005, 2006 3: Free Software Foundation, Inc. 4: 5: This file is part of GNU Classpath. 6: 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. 11: 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. 16: 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 20: 02110-1301 USA. 21: 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. 26: 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. */ 38: 39: 40: package java.util; 41: 42: import gnu.classpath.VMStackWalker; 43: 44: import gnu.java.lang.CPStringBuilder; 45: 46: import java.io.IOException; 47: import java.io.InputStream; 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: * Maximum size of our cache of <code>ResourceBundle</code>s keyed by 97: * {@link BundleKey} instances. 98: * 99: * @see BundleKey 100: */ 101: private static final int CACHE_SIZE = 100; 102: 103: /** 104: * The parent bundle. This is consulted when you call getObject and there 105: * is no such resource in the current bundle. This field may be null. 106: */ 107: protected ResourceBundle parent; 108: 109: /** 110: * The locale of this resource bundle. You can read this with 111: * <code>getLocale</code> and it is automatically set in 112: * <code>getBundle</code>. 113: */ 114: private Locale locale; 115: 116: /** 117: * A VM-wide cache of resource bundles already fetched. 118: * <p> 119: * This {@link Map} is a Least Recently Used (LRU) cache, of the last 120: * {@link #CACHE_SIZE} accessed <code>ResourceBundle</code>s keyed by the 121: * tuple: default locale, resource-bundle name, resource-bundle locale, and 122: * classloader. 123: * 124: * @see BundleKey 125: */ 126: private static Map<BundleKey,Object> bundleCache = 127: new LinkedHashMap<BundleKey,Object>(CACHE_SIZE + 1, 0.75F, true) 128: { 129: public boolean removeEldestEntry(Map.Entry<BundleKey,Object> entry) 130: { 131: return size() > CACHE_SIZE; 132: } 133: }; 134: 135: /** 136: * The constructor. It does nothing special. 137: */ 138: public ResourceBundle() 139: { 140: } 141: 142: /** 143: * Get a String from this resource bundle. Since most localized Objects 144: * are Strings, this method provides a convenient way to get them without 145: * casting. 146: * 147: * @param key the name of the resource 148: * @throws MissingResourceException if the resource can't be found 149: * @throws NullPointerException if key is null 150: * @throws ClassCastException if resource is not a string 151: */ 152: public final String getString(String key) 153: { 154: return (String) getObject(key); 155: } 156: 157: /** 158: * Get an array of Strings from this resource bundle. This method 159: * provides a convenient way to get it without casting. 160: * 161: * @param key the name of the resource 162: * @throws MissingResourceException if the resource can't be found 163: * @throws NullPointerException if key is null 164: * @throws ClassCastException if resource is not a string 165: */ 166: public final String[] getStringArray(String key) 167: { 168: return (String[]) getObject(key); 169: } 170: 171: /** 172: * Get an object from this resource bundle. This will call 173: * <code>handleGetObject</code> for this resource and all of its parents, 174: * until it finds a non-null resource. 175: * 176: * @param key the name of the resource 177: * @throws MissingResourceException if the resource can't be found 178: * @throws NullPointerException if key is null 179: */ 180: public final Object getObject(String key) 181: { 182: for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent) 183: { 184: Object o = bundle.handleGetObject(key); 185: if (o != null) 186: return o; 187: } 188: 189: String className = getClass().getName(); 190: throw new MissingResourceException("Key '" + key 191: + "'not found in Bundle: " 192: + className, className, key); 193: } 194: 195: /** 196: * Return the actual locale of this bundle. You can use it after calling 197: * getBundle, to know if the bundle for the desired locale was loaded or 198: * if the fall back was used. 199: * 200: * @return the bundle's locale 201: */ 202: public Locale getLocale() 203: { 204: return locale; 205: } 206: 207: /** 208: * Set the parent of this bundle. The parent is consulted when you call 209: * getObject and there is no such resource in the current bundle. 210: * 211: * @param parent the parent of this bundle 212: */ 213: protected void setParent(ResourceBundle parent) 214: { 215: this.parent = parent; 216: } 217: 218: /** 219: * Get the appropriate ResourceBundle for the default locale. This is like 220: * calling <code>getBundle(baseName, Locale.getDefault(), 221: * getClass().getClassLoader()</code>, except that any security check of 222: * getClassLoader won't fail. 223: * 224: * @param baseName the name of the ResourceBundle 225: * @return the desired resource bundle 226: * @throws MissingResourceException if the resource bundle can't be found 227: * @throws NullPointerException if baseName is null 228: */ 229: public static ResourceBundle getBundle(String baseName) 230: { 231: ClassLoader cl = VMStackWalker.getCallingClassLoader(); 232: if (cl == null) 233: cl = ClassLoader.getSystemClassLoader(); 234: return getBundle(baseName, Locale.getDefault(), cl); 235: } 236: 237: /** 238: * Get the appropriate ResourceBundle for the given locale. This is like 239: * calling <code>getBundle(baseName, locale, 240: * getClass().getClassLoader()</code>, except that any security check of 241: * getClassLoader won't fail. 242: * 243: * @param baseName the name of the ResourceBundle 244: * @param locale A locale 245: * @return the desired resource bundle 246: * @throws MissingResourceException if the resource bundle can't be found 247: * @throws NullPointerException if baseName or locale is null 248: */ 249: public static ResourceBundle getBundle(String baseName, Locale locale) 250: { 251: ClassLoader cl = VMStackWalker.getCallingClassLoader(); 252: if (cl == null) 253: cl = ClassLoader.getSystemClassLoader(); 254: return getBundle(baseName, locale, cl); 255: } 256: 257: /** Cache key for the ResourceBundle cache. Resource bundles are keyed 258: by the combination of bundle name, locale, and class loader. */ 259: private static class BundleKey 260: { 261: Locale defaultLocale; 262: String baseName; 263: Locale locale; 264: ClassLoader classLoader; 265: int hashcode; 266: 267: BundleKey() {} 268: 269: BundleKey(Locale dl, String s, Locale l, ClassLoader cl) 270: { 271: set(dl, s, l, cl); 272: } 273: 274: void set(Locale dl, String s, Locale l, ClassLoader cl) 275: { 276: defaultLocale = dl; 277: baseName = s; 278: locale = l; 279: classLoader = cl; 280: hashcode = defaultLocale.hashCode() ^ baseName.hashCode() 281: ^ locale.hashCode() ^ classLoader.hashCode(); 282: } 283: 284: public int hashCode() 285: { 286: return hashcode; 287: } 288: 289: public boolean equals(Object o) 290: { 291: if (! (o instanceof BundleKey)) 292: return false; 293: BundleKey key = (BundleKey) o; 294: return hashcode == key.hashcode 295: && defaultLocale.equals(key.defaultLocale) 296: && baseName.equals(key.baseName) 297: && locale.equals(key.locale) 298: && classLoader.equals(key.classLoader); 299: } 300: 301: public String toString() 302: { 303: CPStringBuilder builder = new CPStringBuilder(getClass().getName()); 304: builder.append("[defaultLocale="); 305: builder.append(defaultLocale); 306: builder.append(",baseName="); 307: builder.append(baseName); 308: builder.append(",locale="); 309: builder.append(locale); 310: builder.append(",classLoader="); 311: builder.append(classLoader); 312: builder.append("]"); 313: return builder.toString(); 314: } 315: } 316: 317: /** A cache lookup key. This avoids having to a new one for every 318: * getBundle() call. */ 319: private static final BundleKey lookupKey = new BundleKey(); 320: 321: /** Singleton cache entry to represent previous failed lookups. */ 322: private static final Object nullEntry = new Object(); 323: 324: /** 325: * Get the appropriate ResourceBundle for the given locale. The following 326: * strategy is used: 327: * 328: * <p>A sequence of candidate bundle names are generated, and tested in 329: * this order, where the suffix 1 means the string from the specified 330: * locale, and the suffix 2 means the string from the default locale:</p> 331: * 332: * <ul> 333: * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li> 334: * <li>baseName + "_" + language1 + "_" + country1</li> 335: * <li>baseName + "_" + language1</li> 336: * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li> 337: * <li>baseName + "_" + language2 + "_" + country2</li> 338: * <li>baseName + "_" + language2</li> 339: * <li>baseName</li> 340: * </ul> 341: * 342: * <p>In the sequence, entries with an empty string are ignored. Next, 343: * <code>getBundle</code> tries to instantiate the resource bundle:</p> 344: * 345: * <ul> 346: * <li>First, an attempt is made to load a class in the specified classloader 347: * which is a subclass of ResourceBundle, and which has a public constructor 348: * with no arguments, via reflection.</li> 349: * <li>Next, a search is made for a property resource file, by replacing 350: * '.' with '/' and appending ".properties", and using 351: * ClassLoader.getResource(). If a file is found, then a 352: * PropertyResourceBundle is created from the file's contents.</li> 353: * </ul> 354: * If no resource bundle was found, a MissingResourceException is thrown. 355: * 356: * <p>Next, the parent chain is implemented. The remaining candidate names 357: * in the above sequence are tested in a similar manner, and if any results 358: * in a resource bundle, it is assigned as the parent of the first bundle 359: * using the <code>setParent</code> method (unless the first bundle already 360: * has a parent).</p> 361: * 362: * <p>For example, suppose the following class and property files are 363: * provided: MyResources.class, MyResources_fr_CH.properties, 364: * MyResources_fr_CH.class, MyResources_fr.properties, 365: * MyResources_en.properties, and MyResources_es_ES.class. The contents of 366: * all files are valid (that is, public non-abstract subclasses of 367: * ResourceBundle with public nullary constructors for the ".class" files, 368: * syntactically correct ".properties" files). The default locale is 369: * Locale("en", "UK").</p> 370: * 371: * <p>Calling getBundle with the shown locale argument values instantiates 372: * resource bundles from the following sources:</p> 373: * 374: * <ul> 375: * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent 376: * MyResources_fr.properties, parent MyResources.class</li> 377: * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent 378: * MyResources.class</li> 379: * <li>Locale("de", "DE"): result MyResources_en.properties, parent 380: * MyResources.class</li> 381: * <li>Locale("en", "US"): result MyResources_en.properties, parent 382: * MyResources.class</li> 383: * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent 384: * MyResources.class</li> 385: * </ul> 386: * 387: * <p>The file MyResources_fr_CH.properties is never used because it is hidden 388: * by MyResources_fr_CH.class.</p> 389: * 390: * @param baseName the name of the ResourceBundle 391: * @param locale A locale 392: * @param classLoader a ClassLoader 393: * @return the desired resource bundle 394: * @throws MissingResourceException if the resource bundle can't be found 395: * @throws NullPointerException if any argument is null 396: * @since 1.2 397: */ 398: // This method is synchronized so that the cache is properly 399: // handled. 400: public static synchronized ResourceBundle getBundle 401: (String baseName, Locale locale, ClassLoader classLoader) 402: { 403: Locale defaultLocale = Locale.getDefault(); 404: // This will throw NullPointerException if any arguments are null. 405: lookupKey.set(defaultLocale, baseName, locale, classLoader); 406: Object obj = bundleCache.get(lookupKey); 407: if (obj instanceof ResourceBundle) 408: return (ResourceBundle) obj; 409: 410: if (obj == nullEntry) 411: throw new MissingResourceException("Bundle " + baseName 412: + " not found for locale " + locale 413: + " by classloader " + classLoader, 414: baseName, ""); 415: // First, look for a bundle for the specified locale. We don't want 416: // the base bundle this time. 417: boolean wantBase = locale.equals(defaultLocale); 418: ResourceBundle bundle = tryBundle(baseName, locale, classLoader, wantBase); 419: // Try the default locale if neccessary. 420: if (bundle == null && ! wantBase) 421: bundle = tryBundle(baseName, defaultLocale, classLoader, true); 422: 423: BundleKey key = new BundleKey(defaultLocale, baseName, locale, classLoader); 424: if (bundle == null) 425: { 426: // Cache the fact that this lookup has previously failed. 427: bundleCache.put(key, nullEntry); 428: throw new MissingResourceException("Bundle " + baseName 429: + " not found for locale " + locale 430: + " by classloader " + classLoader, 431: baseName, ""); 432: } 433: // Cache the result and return it. 434: bundleCache.put(key, bundle); 435: return bundle; 436: } 437: 438: /** 439: * Override this method to provide the resource for a keys. This gets 440: * called by <code>getObject</code>. If you don't have a resource 441: * for the given key, you should return null instead throwing a 442: * MissingResourceException. You don't have to ask the parent, getObject() 443: * already does this; nor should you throw a MissingResourceException. 444: * 445: * @param key the key of the resource 446: * @return the resource for the key, or null if not in bundle 447: * @throws NullPointerException if key is null 448: */ 449: protected abstract Object handleGetObject(String key); 450: 451: /** 452: * This method should return all keys for which a resource exists; you 453: * should include the enumeration of any parent's keys, after filtering out 454: * duplicates. 455: * 456: * @return an enumeration of the keys 457: */ 458: public abstract Enumeration<String> getKeys(); 459: 460: /** 461: * Tries to load a class or a property file with the specified name. 462: * 463: * @param localizedName the name 464: * @param classloader the classloader 465: * @return the resource bundle if it was loaded, otherwise the backup 466: */ 467: private static ResourceBundle tryBundle(String localizedName, 468: ClassLoader classloader) 469: { 470: ResourceBundle bundle = null; 471: try 472: { 473: Class<?> rbClass; 474: if (classloader == null) 475: rbClass = Class.forName(localizedName); 476: else 477: rbClass = classloader.loadClass(localizedName); 478: // Note that we do the check up front instead of catching 479: // ClassCastException. The reason for this is that some crazy 480: // programs (Eclipse) have classes that do not extend 481: // ResourceBundle but that have the same name as a property 482: // bundle; in fact Eclipse relies on ResourceBundle not 483: // instantiating these classes. 484: if (ResourceBundle.class.isAssignableFrom(rbClass)) 485: bundle = (ResourceBundle) rbClass.newInstance(); 486: } 487: catch (Exception ex) {} 488: 489: if (bundle == null) 490: { 491: try 492: { 493: InputStream is; 494: String resourceName 495: = localizedName.replace('.', '/') + ".properties"; 496: if (classloader == null) 497: is = ClassLoader.getSystemResourceAsStream(resourceName); 498: else 499: is = classloader.getResourceAsStream(resourceName); 500: if (is != null) 501: bundle = new PropertyResourceBundle(is); 502: } 503: catch (IOException ex) 504: { 505: MissingResourceException mre = new MissingResourceException 506: ("Failed to load bundle: " + localizedName, localizedName, ""); 507: mre.initCause(ex); 508: throw mre; 509: } 510: } 511: 512: return bundle; 513: } 514: 515: /** 516: * Tries to load the bundle for a given locale, also loads the backup 517: * locales with the same language. 518: * 519: * @param baseName the raw bundle name, without locale qualifiers 520: * @param locale the locale 521: * @param classLoader the classloader 522: * @param wantBase whether a resource bundle made only from the base name 523: * (with no locale information attached) should be returned. 524: * @return the resource bundle if it was loaded, otherwise the backup 525: */ 526: private static ResourceBundle tryBundle(String baseName, Locale locale, 527: ClassLoader classLoader, 528: boolean wantBase) 529: { 530: String language = locale.getLanguage(); 531: String country = locale.getCountry(); 532: String variant = locale.getVariant(); 533: 534: int baseLen = baseName.length(); 535: 536: // Build up a CPStringBuilder containing the complete bundle name, fully 537: // qualified by locale. 538: CPStringBuilder sb = new CPStringBuilder(baseLen + variant.length() + 7); 539: 540: sb.append(baseName); 541: 542: if (language.length() > 0) 543: { 544: sb.append('_'); 545: sb.append(language); 546: 547: if (country.length() > 0) 548: { 549: sb.append('_'); 550: sb.append(country); 551: 552: if (variant.length() > 0) 553: { 554: sb.append('_'); 555: sb.append(variant); 556: } 557: } 558: } 559: 560: // Now try to load bundles, starting with the most specialized name. 561: // Build up the parent chain as we go. 562: String bundleName = sb.toString(); 563: ResourceBundle first = null; // The most specialized bundle. 564: ResourceBundle last = null; // The least specialized bundle. 565: 566: while (true) 567: { 568: ResourceBundle foundBundle = tryBundle(bundleName, classLoader); 569: if (foundBundle != null) 570: { 571: if (first == null) 572: first = foundBundle; 573: if (last != null) 574: last.parent = foundBundle; 575: foundBundle.locale = locale; 576: last = foundBundle; 577: } 578: int idx = bundleName.lastIndexOf('_'); 579: // Try the non-localized base name only if we already have a 580: // localized child bundle, or wantBase is true. 581: if (idx > baseLen || (idx == baseLen && (first != null || wantBase))) 582: bundleName = bundleName.substring(0, idx); 583: else 584: break; 585: } 586: 587: return first; 588: } 589: 590: /** 591: * Remove all resources from the cache that were loaded 592: * using the class loader of the calling class. 593: * 594: * @since 1.6 595: */ 596: public static final void clearCache() 597: { 598: clearCache(VMStackWalker.getCallingClassLoader()); 599: } 600: 601: /** 602: * Remove all resources from the cache that were loaded 603: * using the specified class loader. 604: * 605: * @param loader the loader used for the bundles that will be removed. 606: * @throws NullPointerException if {@code loader} is {@code null}. 607: * @since 1.6 608: */ 609: public static final void clearCache(ClassLoader loader) 610: { 611: if (loader == null) 612: throw new NullPointerException("The loader can not be null."); 613: synchronized (ResourceBundle.class) 614: { 615: Iterator<BundleKey> iter = bundleCache.keySet().iterator(); 616: while (iter.hasNext()) 617: { 618: BundleKey key = iter.next(); 619: if (key.classLoader == loader) 620: iter.remove(); 621: } 622: } 623: } 624: 625: }