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