GNU Classpath (0.95) | |
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 java.io.IOException; 45: import java.io.InputStream; 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> 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> 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: */ 91: public abstract class ResourceBundle 92: { 93: /** 94: * Maximum size of our cache of <code>ResourceBundle</code>s keyed by 95: * {@link BundleKey} instances. 96: * 97: * @see BundleKey 98: */ 99: private static final int CACHE_SIZE = 100; 100: 101: /** 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. 104: */ 105: protected ResourceBundle parent; 106: 107: /** 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>. 111: */ 112: private Locale locale; 113: 114: /** 115: * A VM-wide cache of resource bundles already fetched. 116: * <p> 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 120: * classloader. 121: * 122: * @see BundleKey 123: */ 124: private static Map bundleCache = new LinkedHashMap(CACHE_SIZE + 1, 0.75F, true) 125: { 126: public boolean removeEldestEntry(Map.Entry entry) 127: { 128: return size() > CACHE_SIZE; 129: } 130: }; 131: 132: /** 133: * The constructor. It does nothing special. 134: */ 135: public ResourceBundle() 136: { 137: } 138: 139: /** 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 142: * casting. 143: * 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 148: */ 149: public final String getString(String key) 150: { 151: return (String) getObject(key); 152: } 153: 154: /** 155: * Get an array of Strings from this resource bundle. This method 156: * provides a convenient way to get it without casting. 157: * 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 162: */ 163: public final String[] getStringArray(String key) 164: { 165: return (String[]) getObject(key); 166: } 167: 168: /** 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. 172: * 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 176: */ 177: public final Object getObject(String key) 178: { 179: for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent) 180: { 181: Object o = bundle.handleGetObject(key); 182: if (o != null) 183: return o; 184: } 185: 186: String className = getClass().getName(); 187: throw new MissingResourceException("Key '" + key 188: + "'not found in Bundle: " 189: + className, className, key); 190: } 191: 192: /** 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. 196: * 197: * @return the bundle's locale 198: */ 199: public Locale getLocale() 200: { 201: return locale; 202: } 203: 204: /** 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. 207: * 208: * @param parent the parent of this bundle 209: */ 210: protected void setParent(ResourceBundle parent) 211: { 212: this.parent = parent; 213: } 214: 215: /** 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. 220: * 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 225: */ 226: public static ResourceBundle getBundle(String baseName) 227: { 228: ClassLoader cl = VMStackWalker.getCallingClassLoader(); 229: if (cl == null) 230: cl = ClassLoader.getSystemClassLoader(); 231: return getBundle(baseName, Locale.getDefault(), cl); 232: } 233: 234: /** 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. 239: * 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 245: */ 246: public static ResourceBundle getBundle(String baseName, Locale locale) 247: { 248: ClassLoader cl = VMStackWalker.getCallingClassLoader(); 249: if (cl == null) 250: cl = ClassLoader.getSystemClassLoader(); 251: return getBundle(baseName, locale, cl); 252: } 253: 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 257: { 258: Locale defaultLocale; 259: String baseName; 260: Locale locale; 261: ClassLoader classLoader; 262: int hashcode; 263: 264: BundleKey() {} 265: 266: BundleKey(Locale dl, String s, Locale l, ClassLoader cl) 267: { 268: set(dl, s, l, cl); 269: } 270: 271: void set(Locale dl, String s, Locale l, ClassLoader cl) 272: { 273: defaultLocale = dl; 274: baseName = s; 275: locale = l; 276: classLoader = cl; 277: hashcode = defaultLocale.hashCode() ^ baseName.hashCode() 278: ^ locale.hashCode() ^ classLoader.hashCode(); 279: } 280: 281: public int hashCode() 282: { 283: return hashcode; 284: } 285: 286: public boolean equals(Object o) 287: { 288: if (! (o instanceof BundleKey)) 289: return false; 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); 296: } 297: } 298: 299: /** A cache lookup key. This avoids having to a new one for every 300: * getBundle() call. */ 301: private static BundleKey lookupKey = new BundleKey(); 302: 303: /** Singleton cache entry to represent previous failed lookups. */ 304: private static Object nullEntry = new Object(); 305: 306: /** 307: * Get the appropriate ResourceBundle for the given locale. The following 308: * strategy is used: 309: * 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> 313: * 314: * <ul> 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> 321: * <li>baseName</li> 322: * </ul> 323: * 324: * <p>In the sequence, entries with an empty string are ignored. Next, 325: * <code>getBundle</code> tries to instantiate the resource bundle:</p> 326: * 327: * <ul> 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> 335: * </ul> 336: * If no resource bundle was found, a MissingResourceException is thrown. 337: * 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 342: * has a parent).</p> 343: * 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> 352: * 353: * <p>Calling getBundle with the shown locale argument values instantiates 354: * resource bundles from the following sources:</p> 355: * 356: * <ul> 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> 367: * </ul> 368: * 369: * <p>The file MyResources_fr_CH.properties is never used because it is hidden 370: * by MyResources_fr_CH.class.</p> 371: * 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 378: * @since 1.2 379: */ 380: // This method is synchronized so that the cache is properly 381: // handled. 382: public static synchronized ResourceBundle getBundle 383: (String baseName, Locale locale, ClassLoader classLoader) 384: { 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; 391: 392: if (obj == nullEntry) 393: throw new MissingResourceException("Bundle " + baseName 394: + " not found for locale " + locale 395: + " by classloader " + classLoader, 396: baseName, ""); 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); 404: 405: BundleKey key = new BundleKey(defaultLocale, baseName, locale, classLoader); 406: if (bundle == null) 407: { 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, 413: baseName, ""); 414: } 415: // Cache the result and return it. 416: bundleCache.put(key, bundle); 417: return bundle; 418: } 419: 420: /** 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. 426: * 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 430: */ 431: protected abstract Object handleGetObject(String key); 432: 433: /** 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 436: * duplicates. 437: * 438: * @return an enumeration of the keys 439: */ 440: public abstract Enumeration<String> getKeys(); 441: 442: /** 443: * Tries to load a class or a property file with the specified name. 444: * 445: * @param localizedName the name 446: * @param classloader the classloader 447: * @return the resource bundle if it was loaded, otherwise the backup 448: */ 449: private static ResourceBundle tryBundle(String localizedName, 450: ClassLoader classloader) 451: { 452: ResourceBundle bundle = null; 453: try 454: { 455: Class rbClass; 456: if (classloader == null) 457: rbClass = Class.forName(localizedName); 458: else 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(); 468: } 469: catch (Exception ex) {} 470: 471: if (bundle == null) 472: { 473: try 474: { 475: InputStream is; 476: String resourceName 477: = localizedName.replace('.', '/') + ".properties"; 478: if (classloader == null) 479: is = ClassLoader.getSystemResourceAsStream(resourceName); 480: else 481: is = classloader.getResourceAsStream(resourceName); 482: if (is != null) 483: bundle = new PropertyResourceBundle(is); 484: } 485: catch (IOException ex) 486: { 487: MissingResourceException mre = new MissingResourceException 488: ("Failed to load bundle: " + localizedName, localizedName, ""); 489: mre.initCause(ex); 490: throw mre; 491: } 492: } 493: 494: return bundle; 495: } 496: 497: /** 498: * Tries to load a the bundle for a given locale, also loads the backup 499: * locales with the same language. 500: * 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 507: */ 508: private static ResourceBundle tryBundle(String baseName, Locale locale, 509: ClassLoader classLoader, 510: boolean wantBase) 511: { 512: String language = locale.getLanguage(); 513: String country = locale.getCountry(); 514: String variant = locale.getVariant(); 515: 516: int baseLen = baseName.length(); 517: 518: // Build up a StringBuffer containing the complete bundle name, fully 519: // qualified by locale. 520: StringBuffer sb = new StringBuffer(baseLen + variant.length() + 7); 521: 522: sb.append(baseName); 523: 524: if (language.length() > 0) 525: { 526: sb.append('_'); 527: sb.append(language); 528: 529: if (country.length() > 0) 530: { 531: sb.append('_'); 532: sb.append(country); 533: 534: if (variant.length() > 0) 535: { 536: sb.append('_'); 537: sb.append(variant); 538: } 539: } 540: } 541: 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. 547: 548: while (true) 549: { 550: ResourceBundle foundBundle = tryBundle(bundleName, classLoader); 551: if (foundBundle != null) 552: { 553: if (first == null) 554: first = foundBundle; 555: if (last != null) 556: last.parent = foundBundle; 557: foundBundle.locale = locale; 558: last = foundBundle; 559: } 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); 565: else 566: break; 567: } 568: 569: return first; 570: } 571: }
GNU Classpath (0.95) |