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