1. /*
  2. * @(#)ResourceBundle.java 1.71 04/05/05
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
  9. * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
  10. *
  11. * The original version of this source code and documentation
  12. * is copyrighted and owned by Taligent, Inc., a wholly-owned
  13. * subsidiary of IBM. These materials are provided under terms
  14. * of a License Agreement between Taligent and Sun. This technology
  15. * is protected by multiple US and International patents.
  16. *
  17. * This notice and attribution to Taligent may not be removed.
  18. * Taligent is a registered trademark of Taligent, Inc.
  19. *
  20. */
  21. package java.util;
  22. import java.io.InputStream;
  23. import java.lang.ref.Reference;
  24. import java.lang.ref.ReferenceQueue;
  25. import java.lang.ref.WeakReference;
  26. import sun.misc.SoftCache;
  27. /**
  28. *
  29. * Resource bundles contain locale-specific objects.
  30. * When your program needs a locale-specific resource,
  31. * a <code>String</code> for example, your program can load it
  32. * from the resource bundle that is appropriate for the
  33. * current user's locale. In this way, you can write
  34. * program code that is largely independent of the user's
  35. * locale isolating most, if not all, of the locale-specific
  36. * information in resource bundles.
  37. *
  38. * <p>
  39. * This allows you to write programs that can:
  40. * <UL type=SQUARE>
  41. * <LI> be easily localized, or translated, into different languages
  42. * <LI> handle multiple locales at once
  43. * <LI> be easily modified later to support even more locales
  44. * </UL>
  45. *
  46. * <P>
  47. * Resource bundles belong to families whose members share a common base
  48. * name, but whose names also have additional components that identify
  49. * their locales. For example, the base name of a family of resource
  50. * bundles might be "MyResources". The family should have a default
  51. * resource bundle which simply has the same name as its family -
  52. * "MyResources" - and will be used as the bundle of last resort if a
  53. * specific locale is not supported. The family can then provide as
  54. * many locale-specific members as needed, for example a German one
  55. * named "MyResources_de".
  56. *
  57. * <P>
  58. * Each resource bundle in a family contains the same items, but the items have
  59. * been translated for the locale represented by that resource bundle.
  60. * For example, both "MyResources" and "MyResources_de" may have a
  61. * <code>String</code> that's used on a button for canceling operations.
  62. * In "MyResources" the <code>String</code> may contain "Cancel" and in
  63. * "MyResources_de" it may contain "Abbrechen".
  64. *
  65. * <P>
  66. * If there are different resources for different countries, you
  67. * can make specializations: for example, "MyResources_de_CH" contains objects for
  68. * the German language (de) in Switzerland (CH). If you want to only
  69. * modify some of the resources
  70. * in the specialization, you can do so.
  71. *
  72. * <P>
  73. * When your program needs a locale-specific object, it loads
  74. * the <code>ResourceBundle</code> class using the
  75. * {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
  76. * method:
  77. * <blockquote>
  78. * <pre>
  79. * ResourceBundle myResources =
  80. * ResourceBundle.getBundle("MyResources", currentLocale);
  81. * </pre>
  82. * </blockquote>
  83. *
  84. * <P>
  85. * Resource bundles contain key/value pairs. The keys uniquely
  86. * identify a locale-specific object in the bundle. Here's an
  87. * example of a <code>ListResourceBundle</code> that contains
  88. * two key/value pairs:
  89. * <blockquote>
  90. * <pre>
  91. * public class MyResources extends ListResourceBundle {
  92. * public Object[][] getContents() {
  93. * return contents;
  94. * }
  95. * static final Object[][] contents = {
  96. * // LOCALIZE THIS
  97. * {"OkKey", "OK"},
  98. * {"CancelKey", "Cancel"},
  99. * // END OF MATERIAL TO LOCALIZE
  100. * };
  101. * }
  102. * </pre>
  103. * </blockquote>
  104. * Keys are always <code>String</code>s.
  105. * In this example, the keys are "OkKey" and "CancelKey".
  106. * In the above example, the values
  107. * are also <code>String</code>s--"OK" and "Cancel"--but
  108. * they don't have to be. The values can be any type of object.
  109. *
  110. * <P>
  111. * You retrieve an object from resource bundle using the appropriate
  112. * getter method. Because "OkKey" and "CancelKey"
  113. * are both strings, you would use <code>getString</code> to retrieve them:
  114. * <blockquote>
  115. * <pre>
  116. * button1 = new Button(myResources.getString("OkKey"));
  117. * button2 = new Button(myResources.getString("CancelKey"));
  118. * </pre>
  119. * </blockquote>
  120. * The getter methods all require the key as an argument and return
  121. * the object if found. If the object is not found, the getter method
  122. * throws a <code>MissingResourceException</code>.
  123. *
  124. * <P>
  125. * Besides <code>getString</code>, ResourceBundle also provides
  126. * a method for getting string arrays, <code>getStringArray</code>,
  127. * as well as a generic <code>getObject</code> method for any other
  128. * type of object. When using <code>getObject</code>, you'll
  129. * have to cast the result to the appropriate type. For example:
  130. * <blockquote>
  131. * <pre>
  132. * int[] myIntegers = (int[]) myResources.getObject("intList");
  133. * </pre>
  134. * </blockquote>
  135. *
  136. * <P>
  137. * The Java 2 platform provides two subclasses of <code>ResourceBundle</code>,
  138. * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
  139. * that provide a fairly simple way to create resources.
  140. * As you saw briefly in a previous example, <code>ListResourceBundle</code>
  141. * manages its resource as a List of key/value pairs.
  142. * <code>PropertyResourceBundle</code> uses a properties file to manage
  143. * its resources.
  144. *
  145. * <p>
  146. * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
  147. * do not suit your needs, you can write your own <code>ResourceBundle</code>
  148. * subclass. Your subclasses must override two methods: <code>handleGetObject</code>
  149. * and <code>getKeys()</code>.
  150. *
  151. * <P>
  152. * The following is a very simple example of a <code>ResourceBundle</code>
  153. * subclass, MyResources, that manages two resources (for a larger number of
  154. * resources you would probably use a <code>Hashtable</code>).
  155. * Notice that you don't need to supply a value if
  156. * a "parent-level" <code>ResourceBundle</code> handles the same
  157. * key with the same value (as for the okKey below).
  158. * <p><strong>Example:</strong>
  159. * <blockquote>
  160. * <pre>
  161. * // default (English language, United States)
  162. * public class MyResources extends ResourceBundle {
  163. * public Object handleGetObject(String key) {
  164. * if (key.equals("okKey")) return "Ok";
  165. * if (key.equals("cancelKey")) return "Cancel";
  166. * return null;
  167. * }
  168. * }
  169. *
  170. * // German language
  171. * public class MyResources_de extends MyResources {
  172. * public Object handleGetObject(String key) {
  173. * // don't need okKey, since parent level handles it.
  174. * if (key.equals("cancelKey")) return "Abbrechen";
  175. * return null;
  176. * }
  177. * }
  178. * </pre>
  179. * </blockquote>
  180. * You do not have to restrict yourself to using a single family of
  181. * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
  182. * exception messages, <code>ExceptionResources</code>
  183. * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
  184. * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
  185. * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
  186. *
  187. * @see ListResourceBundle
  188. * @see PropertyResourceBundle
  189. * @see MissingResourceException
  190. * @since JDK1.1
  191. */
  192. abstract public class ResourceBundle {
  193. /**
  194. * Static key used for resource lookups. Concurrent
  195. * access to this object is controlled by synchronizing cacheList,
  196. * not cacheKey. A static object is used to do cache lookups
  197. * for performance reasons - the assumption being that synchronization
  198. * has a lower overhead than object allocation and subsequent
  199. * garbage collection.
  200. */
  201. private static final ResourceCacheKey cacheKey = new ResourceCacheKey();
  202. /** initial size of the bundle cache */
  203. private static final int INITIAL_CACHE_SIZE = 25;
  204. /** capacity of cache consumed before it should grow */
  205. private static final float CACHE_LOAD_FACTOR = (float)1.0;
  206. /**
  207. * Maximum length of one branch of the resource search path tree.
  208. * Used in getBundle.
  209. */
  210. private static final int MAX_BUNDLES_SEARCHED = 3;
  211. /**
  212. * This Hashtable is used to keep multiple threads from loading the
  213. * same bundle concurrently. The table entries are (cacheKey, thread)
  214. * where cacheKey is the key for the bundle that is under construction
  215. * and thread is the thread that is constructing the bundle.
  216. * This list is manipulated in findBundle and putBundleInCache.
  217. * Synchronization of this object is done through cacheList, not on
  218. * this object.
  219. */
  220. private static final Hashtable underConstruction = new Hashtable(MAX_BUNDLES_SEARCHED, CACHE_LOAD_FACTOR);
  221. /** constant indicating that no resource bundle was found */
  222. private static final Object NOT_FOUND = new Object();
  223. /**
  224. * The cache is a map from cache keys (with bundle base name,
  225. * locale, and class loader) to either a resource bundle
  226. * (if one has been found) or NOT_FOUND (if no bundle has
  227. * been found).
  228. * The cache is a SoftCache, allowing bundles to be
  229. * removed from the cache if they are no longer
  230. * needed. This will also allow the cache keys
  231. * to be reclaimed along with the ClassLoaders
  232. * they reference.
  233. * This variable would be better named "cache", but we keep the old
  234. * name for compatibility with some workarounds for bug 4212439.
  235. */
  236. private static SoftCache cacheList = new SoftCache(INITIAL_CACHE_SIZE, CACHE_LOAD_FACTOR);
  237. /**
  238. * Queue for reference objects referring to class loaders.
  239. */
  240. private static ReferenceQueue referenceQueue = new ReferenceQueue();
  241. /**
  242. * The parent bundle of this bundle.
  243. * The parent bundle is searched by {@link #getObject getObject}
  244. * when this bundle does not contain a particular resource.
  245. */
  246. protected ResourceBundle parent = null;
  247. /**
  248. * The locale for this bundle.
  249. */
  250. private Locale locale = null;
  251. /**
  252. * Sole constructor. (For invocation by subclass constructors, typically
  253. * implicit.)
  254. */
  255. public ResourceBundle() {
  256. }
  257. /**
  258. * Gets a string for the given key from this resource bundle or one of its parents.
  259. * Calling this method is equivalent to calling
  260. * <blockquote>
  261. * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
  262. * </blockquote>
  263. *
  264. * @param key the key for the desired string
  265. * @exception NullPointerException if <code>key</code> is <code>null</code>
  266. * @exception MissingResourceException if no object for the given key can be found
  267. * @exception ClassCastException if the object found for the given key is not a string
  268. * @return the string for the given key
  269. */
  270. public final String getString(String key) {
  271. return (String) getObject(key);
  272. }
  273. /**
  274. * Gets a string array for the given key from this resource bundle or one of its parents.
  275. * Calling this method is equivalent to calling
  276. * <blockquote>
  277. * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
  278. * </blockquote>
  279. *
  280. * @param key the key for the desired string array
  281. * @exception NullPointerException if <code>key</code> is <code>null</code>
  282. * @exception MissingResourceException if no object for the given key can be found
  283. * @exception ClassCastException if the object found for the given key is not a string array
  284. * @return the string array for the given key
  285. */
  286. public final String[] getStringArray(String key) {
  287. return (String[]) getObject(key);
  288. }
  289. /**
  290. * Gets an object for the given key from this resource bundle or one of its parents.
  291. * This method first tries to obtain the object from this resource bundle using
  292. * {@link #handleGetObject(java.lang.String) handleGetObject}.
  293. * If not successful, and the parent resource bundle is not null,
  294. * it calls the parent's <code>getObject</code> method.
  295. * If still not successful, it throws a MissingResourceException.
  296. *
  297. * @param key the key for the desired object
  298. * @exception NullPointerException if <code>key</code> is <code>null</code>
  299. * @exception MissingResourceException if no object for the given key can be found
  300. * @return the object for the given key
  301. */
  302. public final Object getObject(String key) {
  303. Object obj = handleGetObject(key);
  304. if (obj == null) {
  305. if (parent != null) {
  306. obj = parent.getObject(key);
  307. }
  308. if (obj == null)
  309. throw new MissingResourceException("Can't find resource for bundle "
  310. +this.getClass().getName()
  311. +", key "+key,
  312. this.getClass().getName(),
  313. key);
  314. }
  315. return obj;
  316. }
  317. /**
  318. * Returns the locale of this resource bundle. This method can be used after a
  319. * call to getBundle() to determine whether the resource bundle returned really
  320. * corresponds to the requested locale or is a fallback.
  321. *
  322. * @return the locale of this resource bundle
  323. */
  324. public Locale getLocale() {
  325. return locale;
  326. }
  327. /**
  328. * Sets the locale for this bundle. This is the locale that this
  329. * bundle actually represents and does not depend on how the
  330. * bundle was found by getBundle. Ex. if the user was looking
  331. * for fr_FR and getBundle found en_US, the bundle's locale would
  332. * be en_US, NOT fr_FR
  333. * @param baseName the bundle's base name
  334. * @param bundleName the complete bundle name including locale
  335. * extension.
  336. */
  337. private void setLocale(String baseName, String bundleName) {
  338. if (baseName.length() == bundleName.length()) {
  339. locale = new Locale("", "");
  340. } else if (baseName.length() < bundleName.length()) {
  341. int pos = baseName.length();
  342. String temp = bundleName.substring(pos + 1);
  343. pos = temp.indexOf('_');
  344. if (pos == -1) {
  345. locale = new Locale(temp, "", "");
  346. return;
  347. }
  348. String language = temp.substring(0, pos);
  349. temp = temp.substring(pos + 1);
  350. pos = temp.indexOf('_');
  351. if (pos == -1) {
  352. locale = new Locale(language, temp, "");
  353. return;
  354. }
  355. String country = temp.substring(0, pos);
  356. temp = temp.substring(pos + 1);
  357. locale = new Locale(language, country, temp);
  358. } else {
  359. //The base name is longer than the bundle name. Something is very wrong
  360. //with the calling code.
  361. throw new IllegalArgumentException();
  362. }
  363. }
  364. /*
  365. * Automatic determination of the ClassLoader to be used to load
  366. * resources on behalf of the client. N.B. The client is getLoader's
  367. * caller's caller.
  368. */
  369. private static ClassLoader getLoader() {
  370. Class[] stack = getClassContext();
  371. /* Magic number 2 identifies our caller's caller */
  372. Class c = stack[2];
  373. ClassLoader cl = (c == null) ? null : c.getClassLoader();
  374. if (cl == null) {
  375. cl = ClassLoader.getSystemClassLoader();
  376. }
  377. return cl;
  378. }
  379. private static native Class[] getClassContext();
  380. /**
  381. * Sets the parent bundle of this bundle.
  382. * The parent bundle is searched by {@link #getObject getObject}
  383. * when this bundle does not contain a particular resource.
  384. *
  385. * @param parent this bundle's parent bundle.
  386. */
  387. protected void setParent( ResourceBundle parent ) {
  388. this.parent = parent;
  389. }
  390. /**
  391. * Key used for cached resource bundles. The key checks
  392. * the resource name, the class loader, and the default
  393. * locale to determine if the resource is a match to the
  394. * requested one. The loader may be null, but the
  395. * searchName and the default locale must have a non-null value.
  396. * Note that the default locale may change over time, and
  397. * lookup should always be based on the current default
  398. * locale (if at all).
  399. */
  400. private static final class ResourceCacheKey implements Cloneable {
  401. private LoaderReference loaderRef;
  402. private String searchName;
  403. private Locale defaultLocale;
  404. private int hashCodeCache;
  405. public boolean equals(Object other) {
  406. if (this == other) {
  407. return true;
  408. }
  409. try {
  410. final ResourceCacheKey otherEntry = (ResourceCacheKey)other;
  411. //quick check to see if they are not equal
  412. if (hashCodeCache != otherEntry.hashCodeCache) {
  413. return false;
  414. }
  415. //are the names the same?
  416. if (!searchName.equals(otherEntry.searchName)) {
  417. return false;
  418. }
  419. // are the default locales the same?
  420. if (defaultLocale == null) {
  421. if (otherEntry.defaultLocale != null) {
  422. return false;
  423. }
  424. } else {
  425. if (!defaultLocale.equals(otherEntry.defaultLocale)) {
  426. return false;
  427. }
  428. }
  429. //are refs (both non-null) or (both null)?
  430. if (loaderRef == null) {
  431. return otherEntry.loaderRef == null;
  432. } else {
  433. Object loaderRefValue = loaderRef.get();
  434. return (otherEntry.loaderRef != null)
  435. // with a null reference we can no longer find
  436. // out which class loader was referenced; so
  437. // treat it as unequal
  438. && (loaderRefValue != null)
  439. && (loaderRefValue == otherEntry.loaderRef.get());
  440. }
  441. } catch (NullPointerException e) {
  442. return false;
  443. } catch (ClassCastException e) {
  444. return false;
  445. }
  446. }
  447. public int hashCode() {
  448. return hashCodeCache;
  449. }
  450. public Object clone() {
  451. try {
  452. ResourceCacheKey clone = (ResourceCacheKey) super.clone();
  453. if (loaderRef != null) {
  454. clone.loaderRef = new LoaderReference(loaderRef.get(), referenceQueue, clone);
  455. }
  456. return clone;
  457. } catch (CloneNotSupportedException e) {
  458. //this should never happen
  459. throw new InternalError();
  460. }
  461. }
  462. public void setKeyValues(ClassLoader loader, String searchName, Locale defaultLocale) {
  463. this.searchName = searchName;
  464. hashCodeCache = searchName.hashCode();
  465. this.defaultLocale = defaultLocale;
  466. if (defaultLocale != null) {
  467. hashCodeCache ^= defaultLocale.hashCode();
  468. }
  469. if (loader == null) {
  470. this.loaderRef = null;
  471. } else {
  472. loaderRef = new LoaderReference(loader, referenceQueue, this);
  473. hashCodeCache ^= loader.hashCode();
  474. }
  475. }
  476. public void clear() {
  477. setKeyValues(null, "", null);
  478. }
  479. }
  480. /**
  481. * References to class loaders are weak references, so that they can be
  482. * garbage collected when nobody else is using them. The ResourceBundle
  483. * class has no reason to keep class loaders alive.
  484. */
  485. private static final class LoaderReference extends WeakReference {
  486. private ResourceCacheKey cacheKey;
  487. LoaderReference(Object referent, ReferenceQueue q, ResourceCacheKey key) {
  488. super(referent, q);
  489. cacheKey = key;
  490. }
  491. ResourceCacheKey getCacheKey() {
  492. return cacheKey;
  493. }
  494. }
  495. /**
  496. * Gets a resource bundle using the specified base name, the default locale,
  497. * and the caller's class loader. Calling this method is equivalent to calling
  498. * <blockquote>
  499. * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
  500. * </blockquote>
  501. * except that <code>getClassLoader()</code> is run with the security
  502. * privileges of <code>ResourceBundle</code>.
  503. * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
  504. * for a complete description of the search and instantiation strategy.
  505. *
  506. * @param baseName the base name of the resource bundle, a fully qualified class name
  507. * @exception java.lang.NullPointerException
  508. * if <code>baseName</code> is <code>null</code>
  509. * @exception MissingResourceException
  510. * if no resource bundle for the specified base name can be found
  511. * @return a resource bundle for the given base name and the default locale
  512. */
  513. public static final ResourceBundle getBundle(String baseName)
  514. {
  515. return getBundleImpl(baseName, Locale.getDefault(),
  516. /* must determine loader here, else we break stack invariant */
  517. getLoader());
  518. }
  519. /**
  520. * Gets a resource bundle using the specified base name and locale,
  521. * and the caller's class loader. Calling this method is equivalent to calling
  522. * <blockquote>
  523. * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
  524. * </blockquote>
  525. * except that <code>getClassLoader()</code> is run with the security
  526. * privileges of <code>ResourceBundle</code>.
  527. * See {@link #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) getBundle}
  528. * for a complete description of the search and instantiation strategy.
  529. *
  530. * @param baseName the base name of the resource bundle, a fully qualified class name
  531. * @param locale the locale for which a resource bundle is desired
  532. * @exception java.lang.NullPointerException
  533. * if <code>baseName</code> or <code>locale</code> is <code>null</code>
  534. * @exception MissingResourceException
  535. * if no resource bundle for the specified base name can be found
  536. * @return a resource bundle for the given base name and locale
  537. */
  538. public static final ResourceBundle getBundle(String baseName,
  539. Locale locale)
  540. {
  541. return getBundleImpl(baseName, locale, getLoader());
  542. }
  543. /**
  544. * Gets a resource bundle using the specified base name, locale, and class loader.
  545. *
  546. * <p>
  547. * Conceptually, <code>getBundle</code> uses the following strategy for locating and instantiating
  548. * resource bundles:
  549. * <p>
  550. * <code>getBundle</code> uses the base name, the specified locale, and the default
  551. * locale (obtained from {@link java.util.Locale#getDefault() Locale.getDefault})
  552. * to generate a sequence of <em>candidate bundle names</em>.
  553. * If the specified locale's language, country, and variant are all empty
  554. * strings, then the base name is the only candidate bundle name.
  555. * Otherwise, the following sequence is generated from the attribute
  556. * values of the specified locale (language1, country1, and variant1)
  557. * and of the default locale (language2, country2, and variant2):
  558. * <ul>
  559. * <li> baseName + "_" + language1 + "_" + country1 + "_" + variant1
  560. * <li> baseName + "_" + language1 + "_" + country1
  561. * <li> baseName + "_" + language1
  562. * <li> baseName + "_" + language2 + "_" + country2 + "_" + variant2
  563. * <li> baseName + "_" + language2 + "_" + country2
  564. * <li> baseName + "_" + language2
  565. * <li> baseName
  566. * </ul>
  567. * <p>
  568. * Candidate bundle names where the final component is an empty string are omitted.
  569. * For example, if country1 is an empty string, the second candidate bundle name is omitted.
  570. *
  571. * <p>
  572. * <code>getBundle</code> then iterates over the candidate bundle names to find the first
  573. * one for which it can <em>instantiate</em> an actual resource bundle. For each candidate
  574. * bundle name, it attempts to create a resource bundle:
  575. * <ul>
  576. * <li>
  577. * First, it attempts to load a class using the candidate bundle name.
  578. * If such a class can be found and loaded using the specified class loader, is assignment
  579. * compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated,
  580. * <code>getBundle</code> creates a new instance of this class and uses it as the <em>result
  581. * resource bundle</em>.
  582. * <li>
  583. * Otherwise, <code>getBundle</code> attempts to locate a property resource file.
  584. * It generates a path name from the candidate bundle name by replacing all "." characters
  585. * with "/" and appending the string ".properties".
  586. * It attempts to find a "resource" with this name using
  587. * {@link java.lang.ClassLoader#getResource(java.lang.String) ClassLoader.getResource}.
  588. * (Note that a "resource" in the sense of <code>getResource</code> has nothing to do with
  589. * the contents of a resource bundle, it is just a container of data, such as a file.)
  590. * If it finds a "resource", it attempts to create a new
  591. * {@link PropertyResourceBundle} instance from its contents.
  592. * If successful, this instance becomes the <em>result resource bundle</em>.
  593. * </ul>
  594. *
  595. * <p>
  596. * If no result resource bundle has been found, a <code>MissingResourceException</code>
  597. * is thrown.
  598. *
  599. * <p>
  600. * Once a result resource bundle has been found, its parent chain is instantiated.
  601. * <code>getBundle</code> iterates over the candidate bundle names that can be
  602. * obtained by successively removing variant, country, and language
  603. * (each time with the preceding "_") from the bundle name of the result resource bundle.
  604. * As above, candidate bundle names where the final component is an empty string are omitted.
  605. * With each of the candidate bundle names it attempts to instantiate a resource bundle, as
  606. * described above.
  607. * Whenever it succeeds, it calls the previously instantiated resource
  608. * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method
  609. * with the new resource bundle, unless the previously instantiated resource
  610. * bundle already has a non-null parent.
  611. *
  612. * <p>
  613. * Implementations of <code>getBundle</code> may cache instantiated resource bundles
  614. * and return the same resource bundle instance multiple times. They may also
  615. * vary the sequence in which resource bundles are instantiated as long as the
  616. * selection of the result resource bundle and its parent chain are compatible with
  617. * the description above.
  618. *
  619. * <p>
  620. * The <code>baseName</code> argument should be a fully qualified class name. However, for
  621. * compatibility with earlier versions, Sun's Java 2 runtime environments do not verify this,
  622. * and so it is possible to access <code>PropertyResourceBundle</code>s by specifying a
  623. * path name (using "/") instead of a fully qualified class name (using ".").
  624. *
  625. * <p>
  626. * <strong>Example:</strong> The following class and property files are provided:
  627. * MyResources.class, MyResources_fr_CH.properties, MyResources_fr_CH.class,
  628. * MyResources_fr.properties, MyResources_en.properties, MyResources_es_ES.class.
  629. * The contents of all files are valid (that is, public non-abstract subclasses of ResourceBundle for
  630. * the ".class" files, syntactically correct ".properties" files).
  631. * The default locale is <code>Locale("en", "GB")</code>.
  632. * <p>
  633. * Calling <code>getBundle</code> with the shown locale argument values instantiates
  634. * resource bundles from the following sources:
  635. * <ul>
  636. * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class
  637. * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent MyResources.class
  638. * <li>Locale("de", "DE"): result MyResources_en.properties, parent MyResources.class
  639. * <li>Locale("en", "US"): result MyResources_en.properties, parent MyResources.class
  640. * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent MyResources.class
  641. * </ul>
  642. * The file MyResources_fr_CH.properties is never used because it is hidden by
  643. * MyResources_fr_CH.class.
  644. *
  645. * <p>
  646. *
  647. * @param baseName the base name of the resource bundle, a fully qualified class name
  648. * @param locale the locale for which a resource bundle is desired
  649. * @param loader the class loader from which to load the resource bundle
  650. * @exception java.lang.NullPointerException
  651. * if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
  652. * @exception MissingResourceException
  653. * if no resource bundle for the specified base name can be found
  654. * @return a resource bundle for the given base name and locale
  655. * @since 1.2
  656. */
  657. public static ResourceBundle getBundle(String baseName, Locale locale,
  658. ClassLoader loader)
  659. {
  660. if (loader == null) {
  661. throw new NullPointerException();
  662. }
  663. return getBundleImpl(baseName, locale, loader);
  664. }
  665. private static ResourceBundle getBundleImpl(String baseName, Locale locale,
  666. ClassLoader loader)
  667. {
  668. if (baseName == null) {
  669. throw new NullPointerException();
  670. }
  671. //fast path the case where the bundle is cached
  672. String bundleName = baseName;
  673. String localeSuffix = locale.toString();
  674. if (localeSuffix.length() > 0) {
  675. bundleName += "_" + localeSuffix;
  676. } else if (locale.getVariant().length() > 0) {
  677. //This corrects some strange behavior in Locale where
  678. //new Locale("", "", "VARIANT").toString == ""
  679. bundleName += "___" + locale.getVariant();
  680. }
  681. // The default locale may influence the lookup result, and
  682. // it may change, so we get it here once.
  683. Locale defaultLocale = Locale.getDefault();
  684. Object lookup = findBundleInCache(loader, bundleName, defaultLocale);
  685. if (lookup == NOT_FOUND) {
  686. throwMissingResourceException(baseName, locale);
  687. } else if (lookup != null) {
  688. return (ResourceBundle)lookup;
  689. }
  690. //The bundle was not cached, so start doing lookup at the root
  691. //Resources are loaded starting at the root and working toward
  692. //the requested bundle.
  693. //If findBundle returns null, we become responsible for defining
  694. //the bundle, and must call putBundleInCache to complete this
  695. //task. This is critical because other threads may be waiting
  696. //for us to finish.
  697. Object parent = NOT_FOUND;
  698. try {
  699. //locate the root bundle and work toward the desired child
  700. Object root = findBundle(loader, baseName, defaultLocale, baseName, null);
  701. if (root == null) {
  702. putBundleInCache(loader, baseName, defaultLocale, NOT_FOUND);
  703. root = NOT_FOUND;
  704. }
  705. // Search the main branch of the search tree.
  706. // We need to keep references to the bundles we find on the main path
  707. // so they don't get garbage collected before we get to propagate().
  708. final Vector names = calculateBundleNames(baseName, locale);
  709. Vector bundlesFound = new Vector(MAX_BUNDLES_SEARCHED);
  710. // if we found the root bundle and no other bundle names are needed
  711. // we can stop here. We don't need to search or load anything further.
  712. boolean foundInMainBranch = (root != NOT_FOUND && names.size() == 0);
  713. if (!foundInMainBranch) {
  714. parent = root;
  715. for (int i = 0; i < names.size(); i++) {
  716. bundleName = (String)names.elementAt(i);
  717. lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent);
  718. bundlesFound.addElement(lookup);
  719. if (lookup != null) {
  720. parent = lookup;
  721. foundInMainBranch = true;
  722. }
  723. }
  724. }
  725. parent = root;
  726. if (!foundInMainBranch) {
  727. //we didn't find anything on the main branch, so we do the fallback branch
  728. final Vector fallbackNames = calculateBundleNames(baseName, defaultLocale);
  729. for (int i = 0; i < fallbackNames.size(); i++) {
  730. bundleName = (String)fallbackNames.elementAt(i);
  731. if (names.contains(bundleName)) {
  732. //the fallback branch intersects the main branch so we can stop now.
  733. break;
  734. }
  735. lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent);
  736. if (lookup != null) {
  737. parent = lookup;
  738. } else {
  739. //propagate the parent to the child. We can do this
  740. //here because we are in the default path.
  741. putBundleInCache(loader, bundleName, defaultLocale, parent);
  742. }
  743. }
  744. }
  745. //propagate the inheritance/fallback down through the main branch
  746. parent = propagate(loader, names, bundlesFound, defaultLocale, parent);
  747. } catch (Exception e) {
  748. //We should never get here unless there has been a change
  749. //to the code that doesn't catch it's own exceptions.
  750. cleanUpConstructionList();
  751. throwMissingResourceException(baseName, locale);
  752. } catch (Error e) {
  753. //The only Error that can currently hit this code is a ThreadDeathError
  754. //but errors might be added in the future, so we'll play it safe and
  755. //clean up.
  756. cleanUpConstructionList();
  757. throw e;
  758. }
  759. if (parent == NOT_FOUND) {
  760. throwMissingResourceException(baseName, locale);
  761. }
  762. return (ResourceBundle)parent;
  763. }
  764. /**
  765. * propagate bundles from the root down the specified branch of the search tree.
  766. * @param loader the class loader for the bundles
  767. * @param names the names of the bundles along search path
  768. * @param bundlesFound the bundles corresponding to the names (some may be null)
  769. * @param defaultLocale the default locale at the time getBundle was called
  770. * @param parent the parent of the first bundle in the path (the root bundle)
  771. * @return the value of the last bundle along the path
  772. */
  773. private static Object propagate(ClassLoader loader, Vector names,
  774. Vector bundlesFound, Locale defaultLocale, Object parent) {
  775. for (int i = 0; i < names.size(); i++) {
  776. final String bundleName = (String)names.elementAt(i);
  777. final Object lookup = bundlesFound.elementAt(i);
  778. if (lookup == null) {
  779. putBundleInCache(loader, bundleName, defaultLocale, parent);
  780. } else {
  781. parent = lookup;
  782. }
  783. }
  784. return parent;
  785. }
  786. /** Throw a MissingResourceException with proper message */
  787. private static void throwMissingResourceException(String baseName, Locale locale)
  788. throws MissingResourceException{
  789. throw new MissingResourceException("Can't find bundle for base name "
  790. + baseName + ", locale " + locale,
  791. baseName + "_" + locale,"");
  792. }
  793. /**
  794. * Remove any entries this thread may have in the construction list.
  795. * This is done as cleanup in the case where a bundle can't be
  796. * constructed.
  797. */
  798. private static void cleanUpConstructionList() {
  799. synchronized (cacheList) {
  800. final Collection entries = underConstruction.values();
  801. final Thread thisThread = Thread.currentThread();
  802. while (entries.remove(thisThread)) {
  803. }
  804. }
  805. }
  806. /**
  807. * Find a bundle in the cache or load it via the loader or a property file.
  808. * If the bundle isn't found, an entry is put in the constructionCache
  809. * and null is returned. If null is returned, the caller must define the bundle
  810. * by calling putBundleInCache. This routine also propagates NOT_FOUND values
  811. * from parent to child bundles when the parent is NOT_FOUND.
  812. * @param loader the loader to use when loading a bundle
  813. * @param bundleName the complete bundle name including locale extension
  814. * @param defaultLocale the default locale at the time getBundle was called
  815. * @param parent the parent of the resource bundle being loaded. null if
  816. * the bundle is a root bundle
  817. * @return the bundle or null if the bundle could not be found in the cache
  818. * or loaded.
  819. */
  820. private static Object findBundle(ClassLoader loader, String bundleName, Locale defaultLocale,
  821. String baseName, Object parent) {
  822. Object result;
  823. synchronized (cacheList) {
  824. // Before we do the real work of this method, see
  825. // whether we need to do some housekeeping:
  826. // If references to class loaders have been nulled out,
  827. // remove all related information from the cache
  828. Reference ref = referenceQueue.poll();
  829. while (ref != null) {
  830. cacheList.remove(((LoaderReference) ref).getCacheKey());
  831. ref = referenceQueue.poll();
  832. }
  833. //check for bundle in cache
  834. cacheKey.setKeyValues(loader, bundleName, defaultLocale);
  835. result = cacheList.get(cacheKey);
  836. if (result != null) {
  837. cacheKey.clear();
  838. return result;
  839. }
  840. // check to see if some other thread is building this bundle.
  841. // Note that there is a rare chance that this thread is already
  842. // working on this bundle, and in the process getBundle was called
  843. // again, in which case we can't wait (4300693)
  844. Thread builder = (Thread) underConstruction.get(cacheKey);
  845. boolean beingBuilt = (builder != null && builder != Thread.currentThread());
  846. //if some other thread is building the bundle...
  847. if (beingBuilt) {
  848. //while some other thread is building the bundle...
  849. while (beingBuilt) {
  850. cacheKey.clear();
  851. try {
  852. //Wait until the bundle is complete
  853. cacheList.wait();
  854. } catch (InterruptedException e) {
  855. }
  856. cacheKey.setKeyValues(loader, bundleName, defaultLocale);
  857. beingBuilt = underConstruction.containsKey(cacheKey);
  858. }
  859. //if someone constructed the bundle for us, return it
  860. result = cacheList.get(cacheKey);
  861. if (result != null) {
  862. cacheKey.clear();
  863. return result;
  864. }
  865. }
  866. //The bundle isn't in the cache, so we are now responsible for
  867. //loading it and adding it to the cache.
  868. final Object key = cacheKey.clone();
  869. underConstruction.put(key, Thread.currentThread());
  870. //the bundle is removed from the cache by putBundleInCache
  871. cacheKey.clear();
  872. }
  873. //try loading the bundle via the class loader
  874. result = loadBundle(loader, bundleName, defaultLocale);
  875. if (result != null) {
  876. // check whether we're still responsible for construction -
  877. // a recursive call to getBundle might have handled it (4300693)
  878. boolean constructing;
  879. synchronized (cacheList) {
  880. cacheKey.setKeyValues(loader, bundleName, defaultLocale);
  881. constructing = underConstruction.get(cacheKey) == Thread.currentThread();
  882. cacheKey.clear();
  883. }
  884. if (constructing) {
  885. // set the bundle's parent and put it in the cache
  886. final ResourceBundle bundle = (ResourceBundle)result;
  887. if (parent != NOT_FOUND && bundle.parent == null) {
  888. bundle.setParent((ResourceBundle) parent);
  889. }
  890. bundle.setLocale(baseName, bundleName);
  891. putBundleInCache(loader, bundleName, defaultLocale, result);
  892. }
  893. }
  894. return result;
  895. }
  896. /**
  897. * Calculate the bundles along the search path from the base bundle to the
  898. * bundle specified by baseName and locale.
  899. * @param baseName the base bundle name
  900. * @param locale the locale
  901. * @param names the vector used to return the names of the bundles along
  902. * the search path.
  903. *
  904. */
  905. private static Vector calculateBundleNames(String baseName, Locale locale) {
  906. final Vector result = new Vector(MAX_BUNDLES_SEARCHED);
  907. final String language = locale.getLanguage();
  908. final int languageLength = language.length();
  909. final String country = locale.getCountry();
  910. final int countryLength = country.length();
  911. final String variant = locale.getVariant();
  912. final int variantLength = variant.length();
  913. if (languageLength + countryLength + variantLength == 0) {
  914. //The locale is "", "", "".
  915. return result;
  916. }
  917. final StringBuffer temp = new StringBuffer(baseName);
  918. temp.append('_');
  919. temp.append(language);
  920. if (languageLength > 0) {
  921. result.addElement(temp.toString());
  922. }
  923. if (countryLength + variantLength == 0) {
  924. return result;
  925. }
  926. temp.append('_');
  927. temp.append(country);
  928. if (countryLength > 0) {
  929. result.addElement(temp.toString());
  930. }
  931. if (variantLength == 0) {
  932. return result;
  933. }
  934. temp.append('_');
  935. temp.append(variant);
  936. result.addElement(temp.toString());
  937. return result;
  938. }
  939. /**
  940. * Find a bundle in the cache.
  941. * @param loader the class loader that is responsible for loading the bundle.
  942. * @param bundleName the complete name of the bundle including locale extension.
  943. * ex. sun.text.resources.LocaleElements_fr_BE
  944. * @param defaultLocale the default locale at the time getBundle was called
  945. * @return the cached bundle. null if the bundle is not in the cache.
  946. */
  947. private static Object findBundleInCache(ClassLoader loader, String bundleName,
  948. Locale defaultLocale) {
  949. //Synchronize access to cacheList, cacheKey, and underConstruction
  950. synchronized (cacheList) {
  951. cacheKey.setKeyValues(loader, bundleName, defaultLocale);
  952. Object result = cacheList.get(cacheKey);
  953. cacheKey.clear();
  954. return result;
  955. }
  956. }
  957. /**
  958. * Put a new bundle in the cache and notify waiting threads that a new
  959. * bundle has been put in the cache.
  960. * @param defaultLocale the default locale at the time getBundle was called
  961. */
  962. private static void putBundleInCache(ClassLoader loader, String bundleName,
  963. Locale defaultLocale, Object value) {
  964. //we use a static shared cacheKey but we use the lock in cacheList since
  965. //the key is only used to interact with cacheList.
  966. synchronized (cacheList) {
  967. cacheKey.setKeyValues(loader, bundleName, defaultLocale);
  968. cacheList.put(cacheKey.clone(), value);
  969. underConstruction.remove(cacheKey);
  970. cacheKey.clear();
  971. //notify waiters that we're done constructing the bundle
  972. cacheList.notifyAll();
  973. }
  974. }
  975. /**
  976. * Load a bundle through either the specified ClassLoader or from a ".properties" file
  977. * and return the loaded bundle.
  978. * @param loader the ClassLoader to use to load the bundle. If null, the system
  979. * ClassLoader is used.
  980. * @param bundleName the name of the resource to load. The name should be complete
  981. * including a qualified class name followed by the locale extension.
  982. * ex. sun.text.resources.LocaleElements_fr_BE
  983. * @param defaultLocale the default locale at the time getBundle was called
  984. * @return the bundle or null if none could be found.
  985. */
  986. private static Object loadBundle(final ClassLoader loader, String bundleName, Locale defaultLocale) {
  987. // Search for class file using class loader
  988. try {
  989. Class bundleClass;
  990. if (loader != null) {
  991. bundleClass = loader.loadClass(bundleName);
  992. } else {
  993. bundleClass = Class.forName(bundleName);
  994. }
  995. if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
  996. Object myBundle = bundleClass.newInstance();
  997. // Creating the instance may have triggered a recursive call to getBundle,
  998. // in which case the bundle created by the recursive call would be in the
  999. // cache now (4300693). For consistency, we'd then return the bundle from the cache.
  1000. Object otherBundle = findBundleInCache(loader, bundleName, defaultLocale);
  1001. if (otherBundle != null) {
  1002. return otherBundle;
  1003. } else {
  1004. return myBundle;
  1005. }
  1006. }
  1007. } catch (Exception e) {
  1008. } catch (LinkageError e) {
  1009. }
  1010. // Next search for a Properties file.
  1011. final String resName = bundleName.replace('.', '/') + ".properties";
  1012. InputStream stream = (InputStream)java.security.AccessController.doPrivileged(
  1013. new java.security.PrivilegedAction() {
  1014. public Object run() {
  1015. if (loader != null) {
  1016. return loader.getResourceAsStream(resName);
  1017. } else {
  1018. return ClassLoader.getSystemResourceAsStream(resName);
  1019. }
  1020. }
  1021. }
  1022. );
  1023. if (stream != null) {
  1024. // make sure it is buffered
  1025. stream = new java.io.BufferedInputStream(stream);
  1026. try {
  1027. return new PropertyResourceBundle(stream);
  1028. } catch (Exception e) {
  1029. } finally {
  1030. try {
  1031. stream.close();
  1032. } catch (Exception e) {
  1033. // to avoid propagating an IOException back into the caller
  1034. // (I'm assuming this is never going to happen, and if it does,
  1035. // I'm obeying the precedent of swallowing exceptions set by the
  1036. // existing code above)
  1037. }
  1038. }
  1039. }
  1040. return null;
  1041. }
  1042. /**
  1043. * Gets an object for the given key from this resource bundle.
  1044. * Returns null if this resource bundle does not contain an
  1045. * object for the given key.
  1046. *
  1047. * @param key the key for the desired object
  1048. * @exception NullPointerException if <code>key</code> is <code>null</code>
  1049. * @return the object for the given key, or null
  1050. */
  1051. protected abstract Object handleGetObject(String key);
  1052. /**
  1053. * Returns an enumeration of the keys.
  1054. *
  1055. */
  1056. public abstract Enumeration<String> getKeys();
  1057. }