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