Source for java.util.ResourceBundle

   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: }