GNU Classpath (0.20) | |
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 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: * 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: /** 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 = VMStackWalker.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 = VMStackWalker.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 423: + " not found for locale " 424: + locale 425: + " by classloader " 426: + classLoader, 427: baseName, ""); 428: } 429: 430: /** 431: * Override this method to provide the resource for a keys. This gets 432: * called by <code>getObject</code>. If you don't have a resource 433: * for the given key, you should return null instead throwing a 434: * MissingResourceException. You don't have to ask the parent, getObject() 435: * already does this; nor should you throw a MissingResourceException. 436: * 437: * @param key the key of the resource 438: * @return the resource for the key, or null if not in bundle 439: * @throws NullPointerException if key is null 440: */ 441: protected abstract Object handleGetObject(String key); 442: 443: /** 444: * This method should return all keys for which a resource exists; you 445: * should include the enumeration of any parent's keys, after filtering out 446: * duplicates. 447: * 448: * @return an enumeration of the keys 449: */ 450: public abstract Enumeration getKeys(); 451: 452: /** 453: * Tries to load a class or a property file with the specified name. 454: * 455: * @param localizedName the name 456: * @param classloader the classloader 457: * @return the resource bundle if it was loaded, otherwise the backup 458: */ 459: private static ResourceBundle tryBundle(String localizedName, 460: ClassLoader classloader) 461: { 462: ResourceBundle bundle = null; 463: try 464: { 465: Class rbClass; 466: if (classloader == null) 467: rbClass = Class.forName(localizedName); 468: else 469: rbClass = classloader.loadClass(localizedName); 470: // Note that we do the check up front instead of catching 471: // ClassCastException. The reason for this is that some crazy 472: // programs (Eclipse) have classes that do not extend 473: // ResourceBundle but that have the same name as a property 474: // bundle; in fact Eclipse relies on ResourceBundle not 475: // instantiating these classes. 476: if (ResourceBundle.class.isAssignableFrom(rbClass)) 477: bundle = (ResourceBundle) rbClass.newInstance(); 478: } 479: catch (IllegalAccessException ex) {} 480: catch (InstantiationException ex) {} 481: catch (ClassNotFoundException ex) {} 482: 483: if (bundle == null) 484: { 485: try 486: { 487: InputStream is; 488: String resourceName 489: = localizedName.replace('.', '/') + ".properties"; 490: if (classloader == null) 491: is = ClassLoader.getSystemResourceAsStream(resourceName); 492: else 493: is = classloader.getResourceAsStream(resourceName); 494: if (is != null) 495: bundle = new PropertyResourceBundle(is); 496: } 497: catch (IOException ex) 498: { 499: MissingResourceException mre = new MissingResourceException 500: ("Failed to load bundle: " + localizedName, localizedName, ""); 501: mre.initCause(ex); 502: throw mre; 503: } 504: } 505: 506: return bundle; 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 baseName the raw bundle name, without locale qualifiers 514: * @param locale the locale 515: * @param classLoader the classloader 516: * @param wantBase whether a resource bundle made only from the base name 517: * (with no locale information attached) should be returned. 518: * @return the resource bundle if it was loaded, otherwise the backup 519: */ 520: private static ResourceBundle tryBundle(String baseName, Locale locale, 521: ClassLoader classLoader, 522: boolean wantBase) 523: { 524: String language = locale.getLanguage(); 525: String country = locale.getCountry(); 526: String variant = locale.getVariant(); 527: 528: int baseLen = baseName.length(); 529: 530: // Build up a StringBuffer containing the complete bundle name, fully 531: // qualified by locale. 532: StringBuffer sb = new StringBuffer(baseLen + variant.length() + 7); 533: 534: sb.append(baseName); 535: 536: if (language.length() > 0) 537: { 538: sb.append('_'); 539: sb.append(language); 540: 541: if (country.length() > 0) 542: { 543: sb.append('_'); 544: sb.append(country); 545: 546: if (variant.length() > 0) 547: { 548: sb.append('_'); 549: sb.append(variant); 550: } 551: } 552: } 553: 554: // Now try to load bundles, starting with the most specialized name. 555: // Build up the parent chain as we go. 556: String bundleName = sb.toString(); 557: ResourceBundle first = null; // The most specialized bundle. 558: ResourceBundle last = null; // The least specialized bundle. 559: 560: while (true) 561: { 562: ResourceBundle foundBundle = tryBundle(bundleName, classLoader); 563: if (foundBundle != null) 564: { 565: if (first == null) 566: first = foundBundle; 567: if (last != null) 568: last.parent = foundBundle; 569: foundBundle.locale = locale; 570: last = foundBundle; 571: } 572: int idx = bundleName.lastIndexOf('_'); 573: // Try the non-localized base name only if we already have a 574: // localized child bundle, or wantBase is true. 575: if (idx > baseLen || (idx == baseLen && (first != null || wantBase))) 576: bundleName = bundleName.substring(0, idx); 577: else 578: break; 579: } 580: 581: return first; 582: } 583: }
GNU Classpath (0.20) |