001 /* ResourceBundle -- aids in loading resource bundles 002 Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004, 2005, 2006 003 Free Software Foundation, Inc. 004 005 This file is part of GNU Classpath. 006 007 GNU Classpath is free software; you can redistribute it and/or modify 008 it under the terms of the GNU General Public License as published by 009 the Free Software Foundation; either version 2, or (at your option) 010 any later version. 011 012 GNU Classpath is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of 014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 General Public License for more details. 016 017 You should have received a copy of the GNU General Public License 018 along with GNU Classpath; see the file COPYING. If not, write to the 019 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 020 02110-1301 USA. 021 022 Linking this library statically or dynamically with other modules is 023 making a combined work based on this library. Thus, the terms and 024 conditions of the GNU General Public License cover the whole 025 combination. 026 027 As a special exception, the copyright holders of this library give you 028 permission to link this library with independent modules to produce an 029 executable, regardless of the license terms of these independent 030 modules, and to copy and distribute the resulting executable under 031 terms of your choice, provided that you also meet, for each linked 032 independent module, the terms and conditions of the license of that 033 module. An independent module is a module which is not derived from 034 or based on this library. If you modify this library, you may extend 035 this exception to your version of the library, but you are not 036 obligated to do so. If you do not wish to do so, delete this 037 exception statement from your version. */ 038 039 040 package java.util; 041 042 import gnu.classpath.VMStackWalker; 043 044 import java.io.IOException; 045 import java.io.InputStream; 046 047 /** 048 * A resource bundle contains locale-specific data. If you need localized 049 * data, you can load a resource bundle that matches the locale with 050 * <code>getBundle</code>. Now you can get your object by calling 051 * <code>getObject</code> or <code>getString</code> on that bundle. 052 * 053 * <p>When a bundle is demanded for a specific locale, the ResourceBundle 054 * is searched in following order (<i>def. language</i> stands for the 055 * two letter ISO language code of the default locale (see 056 * <code>Locale.getDefault()</code>). 057 * 058 <pre>baseName_<i>language code</i>_<i>country code</i>_<i>variant</i> 059 baseName_<i>language code</i>_<i>country code</i> 060 baseName_<i>language code</i> 061 baseName_<i>def. language</i>_<i>def. country</i>_<i>def. variant</i> 062 baseName_<i>def. language</i>_<i>def. country</i> 063 baseName_<i>def. language</i> 064 baseName</pre> 065 * 066 * <p>A bundle is backed up by less specific bundles (omitting variant, country 067 * or language). But it is not backed up by the default language locale. 068 * 069 * <p>If you provide a bundle for a given locale, say 070 * <code>Bundle_en_UK_POSIX</code>, you must also provide a bundle for 071 * all sub locales, ie. <code>Bundle_en_UK</code>, <code>Bundle_en</code>, and 072 * <code>Bundle</code>. 073 * 074 * <p>When a bundle is searched, we look first for a class with the given 075 * name, then for a file with <code>.properties</code> extension in the 076 * classpath. The name must be a fully qualified classname (with dots as 077 * path separators). 078 * 079 * <p>(Note: This implementation always backs up the class with a properties 080 * file if that is existing, but you shouldn't rely on this, if you want to 081 * be compatible to the standard JDK.) 082 * 083 * @author Jochen Hoenicke 084 * @author Eric Blake (ebb9@email.byu.edu) 085 * @see Locale 086 * @see ListResourceBundle 087 * @see PropertyResourceBundle 088 * @since 1.1 089 * @status updated to 1.4 090 */ 091 public abstract class ResourceBundle 092 { 093 /** 094 * Maximum size of our cache of <code>ResourceBundle</code>s keyed by 095 * {@link BundleKey} instances. 096 * 097 * @see BundleKey 098 */ 099 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 }