Source for java.util.ResourceBundle

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