Frames | No Frames |
1: /* ResourceBundle -- aids in loading resource bundles 2: Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004, 2005 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 java.io.IOException; 43: import java.io.InputStream; 44: 45: /** 46: * A resource bundle contains locale-specific data. If you need localized 47: * data, you can load a resource bundle that matches the locale with 48: * <code>getBundle</code>. Now you can get your object by calling 49: * <code>getObject</code> or <code>getString</code> on that bundle. 50: * 51: * <p>When a bundle is demanded for a specific locale, the ResourceBundle 52: * is searched in following order (<i>def. language</i> stands for the 53: * two letter ISO language code of the default locale (see 54: * <code>Locale.getDefault()</code>). 55: * 56: <pre>baseName_<i>language code</i>_<i>country code</i>_<i>variant</i> 57: baseName_<i>language code</i>_<i>country code</i> 58: baseName_<i>language code</i> 59: baseName_<i>def. language</i>_<i>def. country</i>_<i>def. variant</i> 60: baseName_<i>def. language</i>_<i>def. country</i> 61: baseName_<i>def. language</i> 62: baseName</pre> 63: * 64: * <p>A bundle is backed up by less specific bundles (omitting variant, country 65: * or language). But it is not backed up by the default language locale. 66: * 67: * <p>If you provide a bundle for a given locale, say 68: * <code>Bundle_en_UK_POSIX</code>, you must also provide a bundle for 69: * all sub locales, ie. <code>Bundle_en_UK</code>, <code>Bundle_en</code>, and 70: * <code>Bundle</code>. 71: * 72: * <p>When a bundle is searched, we look first for a class with the given 73: * name, then for a file with <code>.properties</code> extension in the 74: * classpath. The name must be a fully qualified classname (with dots as 75: * path separators). 76: * 77: * <p>(Note: This implementation always backs up the class with a properties 78: * file if that is existing, but you shouldn't rely on this, if you want to 79: * be compatible to the standard JDK.) 80: * 81: * @author Jochen Hoenicke 82: * @author Eric Blake (ebb9@email.byu.edu) 83: * @see Locale 84: * @see ListResourceBundle 85: * @see PropertyResourceBundle 86: * @since 1.1 87: * @status updated to 1.4 88: */ 89: public abstract class ResourceBundle 90: { 91: /** 92: * The parent bundle. This is consulted when you call getObject and there 93: * is no such resource in the current bundle. This field may be null. 94: */ 95: protected ResourceBundle parent; 96: 97: /** 98: * The locale of this resource bundle. You can read this with 99: * <code>getLocale</code> and it is automatically set in 100: * <code>getBundle</code>. 101: */ 102: private Locale locale; 103: 104: private static native ClassLoader getCallingClassLoader(); 105: 106: /** 107: * The resource bundle cache. 108: */ 109: private static Map bundleCache; 110: 111: /** 112: * The last default Locale we saw. If this ever changes then we have to 113: * reset our caches. 114: */ 115: private static Locale lastDefaultLocale; 116: 117: /** 118: * The `empty' locale is created once in order to optimize 119: * tryBundle(). 120: */ 121: private static final Locale emptyLocale = new Locale(""); 122: 123: /** 124: * The constructor. It does nothing special. 125: */ 126: public ResourceBundle() 127: { 128: } 129: 130: /** 131: * Get a String from this resource bundle. Since most localized Objects 132: * are Strings, this method provides a convenient way to get them without 133: * casting. 134: * 135: * @param key the name of the resource 136: * @throws MissingResourceException if the resource can't be found 137: * @throws NullPointerException if key is null 138: * @throws ClassCastException if resource is not a string 139: */ 140: public final String getString(String key) 141: { 142: return (String) getObject(key); 143: } 144: 145: /** 146: * Get an array of Strings from this resource bundle. This method 147: * provides a convenient way to get it without casting. 148: * 149: * @param key the name of the resource 150: * @throws MissingResourceException if the resource can't be found 151: * @throws NullPointerException if key is null 152: * @throws ClassCastException if resource is not a string 153: */ 154: public final String[] getStringArray(String key) 155: { 156: return (String[]) getObject(key); 157: } 158: 159: /** 160: * Get an object from this resource bundle. This will call 161: * <code>handleGetObject</code> for this resource and all of its parents, 162: * until it finds a non-null resource. 163: * 164: * @param key the name of the resource 165: * @throws MissingResourceException if the resource can't be found 166: * @throws NullPointerException if key is null 167: */ 168: public final Object getObject(String key) 169: { 170: for (ResourceBundle bundle = this; bundle != null; bundle = bundle.parent) 171: { 172: Object o = bundle.handleGetObject(key); 173: if (o != null) 174: return o; 175: } 176: 177: String className = getClass().getName(); 178: throw new MissingResourceException("Key '" + key 179: + "'not found in Bundle: " 180: + className, className, key); 181: } 182: 183: /** 184: * Return the actual locale of this bundle. You can use it after calling 185: * getBundle, to know if the bundle for the desired locale was loaded or 186: * if the fall back was used. 187: * 188: * @return the bundle's locale 189: */ 190: public Locale getLocale() 191: { 192: return locale; 193: } 194: 195: /** 196: * Set the parent of this bundle. The parent is consulted when you call 197: * getObject and there is no such resource in the current bundle. 198: * 199: * @param parent the parent of this bundle 200: */ 201: protected void setParent(ResourceBundle parent) 202: { 203: this.parent = parent; 204: } 205: 206: /** 207: * Get the appropriate ResourceBundle for the default locale. This is like 208: * calling <code>getBundle(baseName, Locale.getDefault(), 209: * getClass().getClassLoader()</code>, except that any security check of 210: * getClassLoader won't fail. 211: * 212: * @param baseName the name of the ResourceBundle 213: * @return the desired resource bundle 214: * @throws MissingResourceException if the resource bundle can't be found 215: * @throws NullPointerException if baseName is null 216: */ 217: public static ResourceBundle getBundle(String baseName) 218: { 219: ClassLoader cl = getCallingClassLoader(); 220: if (cl == null) 221: cl = ClassLoader.getSystemClassLoader(); 222: return getBundle(baseName, Locale.getDefault(), cl); 223: } 224: 225: /** 226: * Get the appropriate ResourceBundle for the given locale. This is like 227: * calling <code>getBundle(baseName, locale, 228: * getClass().getClassLoader()</code>, except that any security check of 229: * getClassLoader won't fail. 230: * 231: * @param baseName the name of the ResourceBundle 232: * @param locale A locale 233: * @return the desired resource bundle 234: * @throws MissingResourceException if the resource bundle can't be found 235: * @throws NullPointerException if baseName or locale is null 236: */ 237: public static ResourceBundle getBundle(String baseName, Locale locale) 238: { 239: ClassLoader cl = getCallingClassLoader(); 240: if (cl == null) 241: cl = ClassLoader.getSystemClassLoader(); 242: return getBundle(baseName, locale, cl); 243: } 244: 245: /** Cache key for the ResourceBundle cache. Resource bundles are keyed 246: by the combination of bundle name, locale, and class loader. */ 247: private static class BundleKey 248: { 249: String baseName; 250: Locale locale; 251: ClassLoader classLoader; 252: int hashcode; 253: 254: BundleKey() {} 255: 256: BundleKey(String s, Locale l, ClassLoader cl) 257: { 258: set(s, l, cl); 259: } 260: 261: void set(String s, Locale l, ClassLoader cl) 262: { 263: baseName = s; 264: locale = l; 265: classLoader = cl; 266: hashcode = baseName.hashCode() ^ locale.hashCode() ^ 267: classLoader.hashCode(); 268: } 269: 270: public int hashCode() 271: { 272: return hashcode; 273: } 274: 275: public boolean equals(Object o) 276: { 277: if (! (o instanceof BundleKey)) 278: return false; 279: BundleKey key = (BundleKey) o; 280: return hashcode == key.hashcode && 281: baseName.equals(key.baseName) && 282: locale.equals(key.locale) && 283: classLoader.equals(key.classLoader); 284: } 285: } 286: 287: /** A cache lookup key. This avoids having to a new one for every 288: * getBundle() call. */ 289: private static BundleKey lookupKey = new BundleKey(); 290: 291: /** Singleton cache entry to represent previous failed lookups. */ 292: private static Object nullEntry = new Object(); 293: 294: /** 295: * Get the appropriate ResourceBundle for the given locale. The following 296: * strategy is used: 297: * 298: * <p>A sequence of candidate bundle names are generated, and tested in 299: * this order, where the suffix 1 means the string from the specified 300: * locale, and the suffix 2 means the string from the default locale:</p> 301: * 302: * <ul> 303: * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li> 304: * <li>baseName + "_" + language1 + "_" + country1</li> 305: * <li>baseName + "_" + language1</li> 306: * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li> 307: * <li>baseName + "_" + language2 + "_" + country2</li> 308: * <li>baseName + "_" + language2</li> 309: * <li>baseName</li> 310: * </ul> 311: * 312: * <p>In the sequence, entries with an empty string are ignored. Next, 313: * <code>getBundle</code> tries to instantiate the resource bundle:</p> 314: * 315: * <ul> 316: * <li>First, an attempt is made to load a class in the specified classloader 317: * which is a subclass of ResourceBundle, and which has a public constructor 318: * with no arguments, via reflection.</li> 319: * <li>Next, a search is made for a property resource file, by replacing 320: * '.' with '/' and appending ".properties", and using 321: * ClassLoader.getResource(). If a file is found, then a 322: * PropertyResourceBundle is created from the file's contents.</li> 323: * </ul> 324: * If no resource bundle was found, a MissingResourceException is thrown. 325: * 326: * <p>Next, the parent chain is implemented. The remaining candidate names 327: * in the above sequence are tested in a similar manner, and if any results 328: * in a resource bundle, it is assigned as the parent of the first bundle 329: * using the <code>setParent</code> method (unless the first bundle already 330: * has a parent).</p> 331: * 332: * <p>For example, suppose the following class and property files are 333: * provided: MyResources.class, MyResources_fr_CH.properties, 334: * MyResources_fr_CH.class, MyResources_fr.properties, 335: * MyResources_en.properties, and MyResources_es_ES.class. The contents of 336: * all files are valid (that is, public non-abstract subclasses of 337: * ResourceBundle with public nullary constructors for the ".class" files, 338: * syntactically correct ".properties" files). The default locale is 339: * Locale("en", "UK").</p> 340: * 341: * <p>Calling getBundle with the shown locale argument values instantiates 342: * resource bundles from the following sources:</p> 343: * 344: * <ul> 345: * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent 346: * MyResources_fr.properties, parent MyResources.class</li> 347: * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent 348: * MyResources.class</li> 349: * <li>Locale("de", "DE"): result MyResources_en.properties, parent 350: * MyResources.class</li> 351: * <li>Locale("en", "US"): result MyResources_en.properties, parent 352: * MyResources.class</li> 353: * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent 354: * MyResources.class</li> 355: * </ul> 356: * 357: * <p>The file MyResources_fr_CH.properties is never used because it is hidden 358: * by MyResources_fr_CH.class.</p> 359: * 360: * @param baseName the name of the ResourceBundle 361: * @param locale A locale 362: * @param classLoader a ClassLoader 363: * @return the desired resource bundle 364: * @throws MissingResourceException if the resource bundle can't be found 365: * @throws NullPointerException if any argument is null 366: * @since 1.2 367: */ 368: // This method is synchronized so that the cache is properly 369: // handled. 370: public static synchronized ResourceBundle getBundle 371: (String baseName, Locale locale, ClassLoader classLoader) 372: { 373: // If the default locale changed since the last time we were called, 374: // all cache entries are invalidated. 375: Locale defaultLocale = Locale.getDefault(); 376: if (defaultLocale != lastDefaultLocale) 377: { 378: bundleCache = new HashMap(); 379: lastDefaultLocale = defaultLocale; 380: } 381: 382: // This will throw NullPointerException if any arguments are null. 383: lookupKey.set(baseName, locale, classLoader); 384: 385: Object obj = bundleCache.get(lookupKey); 386: ResourceBundle rb = null; 387: 388: if (obj instanceof ResourceBundle) 389: { 390: return (ResourceBundle) obj; 391: } 392: else if (obj == nullEntry) 393: { 394: // Lookup has failed previously. Fall through. 395: } 396: else 397: { 398: // First, look for a bundle for the specified locale. We don't want 399: // the base bundle this time. 400: boolean wantBase = locale.equals(defaultLocale); 401: ResourceBundle bundle = tryBundle(baseName, locale, classLoader, 402: wantBase); 403: 404: // Try the default locale if neccessary. 405: if (bundle == null && !locale.equals(defaultLocale)) 406: bundle = tryBundle(baseName, defaultLocale, classLoader, true); 407: 408: BundleKey key = new BundleKey(baseName, locale, classLoader); 409: if (bundle == null) 410: { 411: // Cache the fact that this lookup has previously failed. 412: bundleCache.put(key, nullEntry); 413: } 414: else 415: { 416: // Cache the result and return it. 417: bundleCache.put(key, bundle); 418: return bundle; 419: } 420: } 421: 422: throw new MissingResourceException("Bundle " + baseName + " not found", 423: baseName, ""); 424: } 425: 426: /** 427: * Override this method to provide the resource for a keys. This gets 428: * called by <code>getObject</code>. If you don't have a resource 429: * for the given key, you should return null instead throwing a 430: * MissingResourceException. You don't have to ask the parent, getObject() 431: * already does this; nor should you throw a MissingResourceException. 432: * 433: * @param key the key of the resource 434: * @return the resource for the key, or null if not in bundle 435: * @throws NullPointerException if key is null 436: */ 437: protected abstract Object handleGetObject(String key); 438: 439: /** 440: * This method should return all keys for which a resource exists; you 441: * should include the enumeration of any parent's keys, after filtering out 442: * duplicates. 443: * 444: * @return an enumeration of the keys 445: */ 446: public abstract Enumeration getKeys(); 447: 448: /** 449: * Tries to load a class or a property file with the specified name. 450: * 451: * @param localizedName the name 452: * @param classloader the classloader 453: * @return the resource bundle if it was loaded, otherwise the backup 454: */ 455: private static ResourceBundle tryBundle(String localizedName, 456: ClassLoader classloader) 457: { 458: ResourceBundle bundle = null; 459: try 460: { 461: Class rbClass; 462: if (classloader == null) 463: rbClass = Class.forName(localizedName); 464: else 465: rbClass = classloader.loadClass(localizedName); 466: // Note that we do the check up front instead of catching 467: // ClassCastException. The reason for this is that some crazy 468: // programs (Eclipse) have classes that do not extend 469: // ResourceBundle but that have the same name as a property 470: // bundle; in fact Eclipse relies on ResourceBundle not 471: // instantiating these classes. 472: if (ResourceBundle.class.isAssignableFrom(rbClass)) 473: bundle = (ResourceBundle) rbClass.newInstance(); 474: } 475: catch (IllegalAccessException ex) {} 476: catch (InstantiationException ex) {} 477: catch (ClassNotFoundException ex) {} 478: 479: if (bundle == null) 480: { 481: try 482: { 483: InputStream is; 484: String resourceName 485: = localizedName.replace('.', '/') + ".properties"; 486: if (classloader == null) 487: is = ClassLoader.getSystemResourceAsStream(resourceName); 488: else 489: is = classloader.getResourceAsStream(resourceName); 490: if (is != null) 491: bundle = new PropertyResourceBundle(is); 492: } 493: catch (IOException ex) 494: { 495: MissingResourceException mre = new MissingResourceException 496: ("Failed to load bundle: " + localizedName, localizedName, ""); 497: mre.initCause(ex); 498: throw mre; 499: } 500: } 501: 502: return bundle; 503: } 504: 505: /** 506: * Tries to load a the bundle for a given locale, also loads the backup 507: * locales with the same language. 508: * 509: * @param baseName the raw bundle name, without locale qualifiers 510: * @param locale the locale 511: * @param classloader the classloader 512: * @param bundle the backup (parent) bundle 513: * @param wantBase whether a resource bundle made only from the base name 514: * (with no locale information attached) should be returned. 515: * @return the resource bundle if it was loaded, otherwise the backup 516: */ 517: private static ResourceBundle tryBundle(String baseName, Locale locale, 518: ClassLoader classLoader, 519: boolean wantBase) 520: { 521: String language = locale.getLanguage(); 522: String country = locale.getCountry(); 523: String variant = locale.getVariant(); 524: 525: int baseLen = baseName.length(); 526: 527: // Build up a StringBuffer containing the complete bundle name, fully 528: // qualified by locale. 529: StringBuffer sb = new StringBuffer(baseLen + variant.length() + 7); 530: 531: sb.append(baseName); 532: 533: if (language.length() > 0) 534: { 535: sb.append('_'); 536: sb.append(language); 537: 538: if (country.length() > 0) 539: { 540: sb.append('_'); 541: sb.append(country); 542: 543: if (variant.length() > 0) 544: { 545: sb.append('_'); 546: sb.append(variant); 547: } 548: } 549: } 550: 551: // Now try to load bundles, starting with the most specialized name. 552: // Build up the parent chain as we go. 553: String bundleName = sb.toString(); 554: ResourceBundle first = null; // The most specialized bundle. 555: ResourceBundle last = null; // The least specialized bundle. 556: 557: while (true) 558: { 559: ResourceBundle foundBundle = tryBundle(bundleName, classLoader); 560: if (foundBundle != null) 561: { 562: if (first == null) 563: first = foundBundle; 564: if (last != null) 565: last.parent = foundBundle; 566: foundBundle.locale = locale; 567: last = foundBundle; 568: } 569: int idx = bundleName.lastIndexOf('_'); 570: // Try the non-localized base name only if we already have a 571: // localized child bundle, or wantBase is true. 572: if (idx > baseLen || (idx == baseLen && (first != null || wantBase))) 573: bundleName = bundleName.substring(0, idx); 574: else 575: break; 576: } 577: 578: return first; 579: } 580: }