1. /*
  2. * @(#)ResourceBundle.java 1.48 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * @(#)ResourceBundle.java 1.24 98/01/15
  9. *
  10. * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
  11. * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
  12. *
  13. * Portions copyright (c) 1996-1998 Sun Microsystems, Inc.
  14. * All Rights Reserved.
  15. *
  16. * The original version of this source code and documentation
  17. * is copyrighted and owned by Taligent, Inc., a wholly-owned
  18. * subsidiary of IBM. These materials are provided under terms
  19. * of a License Agreement between Taligent and Sun. This technology
  20. * is protected by multiple US and International patents.
  21. *
  22. * This notice and attribution to Taligent may not be removed.
  23. * Taligent is a registered trademark of Taligent, Inc.
  24. *
  25. * Permission to use, copy, modify, and distribute this software
  26. * and its documentation for NON-COMMERCIAL purposes and without
  27. * fee is hereby granted provided that this copyright notice
  28. * appears in all copies. Please refer to the file "copyright.html"
  29. * for further important copyright and licensing information.
  30. *
  31. * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  32. * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  33. * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  34. * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  35. * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  36. * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  37. *
  38. */
  39. package java.util;
  40. import java.io.InputStream;
  41. import java.io.FileInputStream;
  42. import java.util.Hashtable;
  43. import sun.misc.SoftCache;
  44. import java.lang.ref.SoftReference;
  45. /**
  46. *
  47. * Resource bundles contain locale-specific objects.
  48. * When your program needs a locale-specific resource,
  49. * a <code>String</code> for example, your program can load it
  50. * from the resource bundle that is appropriate for the
  51. * current user's locale. In this way, you can write
  52. * program code that is largely independent of the user's
  53. * locale isolating most, if not all, of the locale-specific
  54. * information in resource bundles.
  55. *
  56. * <p>
  57. * This allows you to write programs that can:
  58. * <UL type=SQUARE>
  59. * <LI> be easily localized, or translated, into different languages
  60. * <LI> handle multiple locales at once
  61. * <LI> be easily modified later to support even more locales
  62. * </UL>
  63. *
  64. * <P>
  65. * One resource bundle is, conceptually, a set of related classes that
  66. * inherit from <code>ResourceBundle</code>. Each related subclass of
  67. * <code>ResourceBundle</code> has the same base name plus an additional
  68. * component that identifies its locale. For example, suppose your resource
  69. * bundle is named <code>MyResources</code>. The first class you are likely
  70. * to write is the default resource bundle which simply has the same name as
  71. * its family--<code>MyResources</code>. You can also provide as
  72. * many related locale-specific classes as you need: for example, perhaps
  73. * you would provide a German one named <code>MyResources_de</code>.
  74. *
  75. * <P>
  76. * Each related subclass of <code>ResourceBundle</code> contains the same
  77. * items, but the items have been translated for the locale represented by that
  78. * <code>ResourceBundle</code> subclass. For example, both <code>MyResources</code>
  79. * and <code>MyResources_de</code> may have a <code>String</code> that's used
  80. * on a button for confirming operations. In <code>MyResources</code> the
  81. * <code>String</code> may contain <code>OK</code> and in
  82. * <code>MyResources_de</code> it may contain <code>Gut</code>.
  83. *
  84. * <P>
  85. * If there are different resources for different countries, you
  86. * can make specializations: for example, <code>MyResources_de_CH</code>
  87. * is the German language (de) in Switzerland (CH). If you want to only
  88. * modify some of the resources
  89. * in the specialization, you can do so.
  90. *
  91. * <P>
  92. * When your program needs a locale-specific object, it loads
  93. * the <code>ResourceBundle</code> class using the <code>getBundle</code>
  94. * method:
  95. * <blockquote>
  96. * <pre>
  97. * ResourceBundle myResources =
  98. * ResourceBundle.getBundle("MyResources", currentLocale);
  99. * </pre>
  100. * </blockquote>
  101. * The first argument specifies the family name of the resource
  102. * bundle that contains the object in question. The second argument
  103. * indicates the desired locale. <code>getBundle</code>
  104. * uses these two arguments to construct the name of the
  105. * <code>ResourceBundle</code> subclass it should load as follows.
  106. *
  107. * <P>
  108. * The resource bundle lookup searches for classes with various suffixes
  109. * on the basis of (1) the desired locale and (2) the current default locale
  110. * as returned by Locale.getDefault(), and (3) the root resource bundle (baseclass),
  111. * in the following order from lower-level (more specific) to parent-level
  112. * (less specific):
  113. * <p> baseclass + "_" + language1 + "_" + country1 + "_" + variant1
  114. * <BR> baseclass + "_" + language1 + "_" + country1
  115. * <BR> baseclass + "_" + language1
  116. * <BR> baseclass + "_" + language2 + "_" + country2 + "_" + variant2
  117. * <BR> baseclass + "_" + language2 + "_" + country2
  118. * <BR> baseclass + "_" + language2
  119. * <BR> baseclass
  120. *
  121. * <P>
  122. * For example, if the current default locale is <TT>en_US</TT>, the locale the caller
  123. * is interested in is <TT>fr_CH</TT>, and the resource bundle name is <TT>MyResources</TT>,
  124. * resource bundle lookup will search for the following classes, in order:
  125. * <BR> <TT>MyResources_fr_CH
  126. * <BR> MyResources_fr
  127. * <BR> MyResources_en_US
  128. * <BR> MyResources_en
  129. * <BR> MyResources</TT>
  130. *
  131. * <P>
  132. * The result of the lookup is a class, but that class may be
  133. * backed by a property file on disk. If a lookup fails,
  134. * <code>getBundle()</code> throws a <code>MissingResourceException</code>.
  135. *
  136. * <P>
  137. * The baseclass <strong>must</strong> be fully
  138. * qualified (for example, <code>myPackage.MyResources</code>, not just
  139. * <code>MyResources</code>). It must
  140. * also be accessable by your code; it cannot be a class that is private
  141. * to the package where <code>ResourceBundle.getBundle</code> is called.
  142. *
  143. * <P>
  144. * Note: <code>ResourceBundle</code> are used internally in accessing
  145. * <code>NumberFormat</code>s, <code>Collation</code>s, and so on.
  146. * The lookup strategy is the same.
  147. *
  148. * <P>
  149. * Resource bundles contain key/value pairs. The keys uniquely
  150. * identify a locale-specific object in the bundle. Here's an
  151. * example of a <code>ListResourceBundle</code> that contains
  152. * two key/value pairs:
  153. * <blockquote>
  154. * <pre>
  155. * class MyResource extends ListResourceBundle {
  156. * public Object[][] getContents() {
  157. * return contents;
  158. * }
  159. * static final Object[][] contents = {
  160. * // LOCALIZE THIS
  161. * {"OkKey", "OK"},
  162. * {"CancelKey", "Cancel"},
  163. * // END OF MATERIAL TO LOCALIZE
  164. * };
  165. * }
  166. * </pre>
  167. * </blockquote>
  168. * Keys are always <code>String</code>s.
  169. * In this example, the keys are <code>OkKey</code> and <code>CancelKey</code>.
  170. * In the above example, the values
  171. * are also <code>String</code>s--<code>OK</code> and <code>Cancel</code>--but
  172. * they don't have to be. The values can be any type of object.
  173. *
  174. * <P>
  175. * You retrieve an object from resource bundle using the appropriate
  176. * getter method. Because <code>OkKey</code> and <code>CancelKey</code>
  177. * are both strings, you would use <code>getString</code> to retrieve them:
  178. * <blockquote>
  179. * <pre>
  180. * button1 = new Button(myResourceBundle.getString("OkKey"));
  181. * button2 = new Button(myResourceBundle.getString("CancelKey"));
  182. * </pre>
  183. * </blockquote>
  184. * The getter methods all require the key as an argument and return
  185. * the object if found. If the object is not found, the getter method
  186. * throws a <code>MissingResourceException</code>.
  187. *
  188. * <P>
  189. * Besides <code>getString</code> ResourceBundle supports a number
  190. * of other methods for getting different types of objects such as
  191. * <code>getStringArray</code>. If you don't have an object that
  192. * matches one of these methods, you can use <code>getObject</code>
  193. * and cast the result to the appropriate type. For example:
  194. * <blockquote>
  195. * <pre>
  196. * int[] myIntegers = (int[]) myResources.getObject("intList");
  197. * </pre>
  198. * </blockquote>
  199. *
  200. * <P>
  201. * <STRONG>NOTE:</STRONG> You should always supply a baseclass with
  202. * no suffixes. This will be the class of "last resort", if a locale
  203. * is requested that does not exist. In fact, you must provide <I>all</I>
  204. * of the classes in any given inheritance chain that you provide a resource
  205. * for. For example, if you provide <TT>MyResources_fr_BE</TT>, you must provide
  206. * <I>both</I> <TT>MyResources</TT> <I>and</I> <TT>MyResources_fr</TT> or
  207. * the resource bundle lookup won't work right.
  208. *
  209. * <P>
  210. * The JDK provides two subclasses of <code>ResourceBundle</code>,
  211. * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
  212. * that provide a fairly simple way to create resources. (Once serialization
  213. * is fully integrated, we will provide another
  214. * way.) As you saw briefly in a previous example, <code>ListResourceBundle</code>
  215. * manages its resource as a List of key/value pairs.
  216. * <code>PropertyResourceBundle</code> uses a properties file to manage
  217. * its resources.
  218. *
  219. * <p>
  220. * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
  221. * do not suit your needs, you can write your own <code>ResourceBundle</code>
  222. * subclass. Your subclasses must override two methods: <code>handleGetObject</code>
  223. * and <code>getKeys()</code>.
  224. *
  225. * <P>
  226. * The following is a very simple example of a <code>ResourceBundle</code>
  227. * subclass, MyResources, that manages two resources (for a larger number of
  228. * resources you would probably use a <code>Hashtable</code>). Notice that if
  229. * the key is not found, <code>handleGetObject</code> must return null. Notice
  230. * also that you don't need to supply a value if a "parent-level"
  231. * <code>ResourceBundle</code> handles the same
  232. * key with the same value (as in United Kingdom below). Also notice that because
  233. * you specify an <TT>en_GB</TT> resource bundle, you also have to provide a default <TT>en</TT>
  234. * resource bundle even though it inherits all its data from the root resource bundle.
  235. * <p><strong>Example:</strong>
  236. * <blockquote>
  237. * <pre>
  238. * // default (English language, United States)
  239. * abstract class MyResources extends ResourceBundle {
  240. * public Object handleGetObject(String key) {
  241. * if (key.equals("okKey")) return "Ok";
  242. * if (key.equals("cancelKey")) return "Cancel";
  243. * return null;
  244. * }
  245. * }
  246. *
  247. * // German language
  248. * public class MyResources_de extends MyResources {
  249. * public Object handleGetObject(String key) {
  250. * if (key.equals("okKey")) return "Gut";
  251. * if (key.equals("cancelKey")) return "Vernichten";
  252. * return null;
  253. * }
  254. * }
  255. *
  256. * // English language, default (must provide even though all the data is
  257. * // in the root locale)
  258. * public class MyResources_en extends MyResources {
  259. * public Object handleGetObject(String key) {
  260. * return null;
  261. * }
  262. * }
  263. *
  264. * // English language, United Kingdom (Great Britain)
  265. * public class MyResources_en_GB extends MyResources {
  266. * public Object handleGetObject(String key) {
  267. * // don't need okKey, since parent level handles it.
  268. * if (key.equals("cancelKey")) return "Dispose";
  269. * return null;
  270. * }
  271. * }
  272. * </pre>
  273. * </blockquote>
  274. * You do not have to restrict yourself to using a single family of
  275. * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
  276. * exception messages, <code>ExceptionResources</code>
  277. * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
  278. * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
  279. * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
  280. *
  281. * @see ListResourceBundle
  282. * @see PropertyResourceBundle
  283. * @see MissingResourceException
  284. */
  285. abstract public class ResourceBundle {
  286. /** Static key used by findBundle for resource lookups. */
  287. private static final ResourceCacheKey cacheKey = new ResourceCacheKey();
  288. /**
  289. * Sole constructor. (For invocation by subclass constructors, typically
  290. * implicit.)
  291. */
  292. public ResourceBundle() {
  293. }
  294. /**
  295. * Get an object from a ResourceBundle.
  296. * <BR>Convenience method to save casting.
  297. * @param key see class description.
  298. */
  299. public final String getString(String key) throws MissingResourceException {
  300. return (String) getObject(key);
  301. }
  302. /**
  303. * Get an object from a ResourceBundle.
  304. * <BR>Convenience method to save casting.
  305. * @param key see class description.
  306. */
  307. public final String[] getStringArray(String key)
  308. throws MissingResourceException {
  309. return (String[]) getObject(key);
  310. }
  311. /**
  312. * Get an object from a ResourceBundle.
  313. * @param key see class description.
  314. */
  315. public final Object getObject(String key) throws MissingResourceException {
  316. Object obj = handleGetObject(key);
  317. if (obj == null) {
  318. if (parent != null) {
  319. obj = parent.getObject(key);
  320. }
  321. if (obj == null)
  322. throw new MissingResourceException("Can't find resource for bundle "
  323. +this.getClass().getName()
  324. +", key "+key,
  325. this.getClass().getName(),
  326. key);
  327. }
  328. return obj;
  329. }
  330. /**
  331. * Get the appropriate ResourceBundle subclass.
  332. * @param baseName see class description.
  333. */
  334. public static final ResourceBundle getBundle(String baseName)
  335. throws MissingResourceException
  336. {
  337. return getBundle(baseName, Locale.getDefault(),
  338. /* must determine loader here, else we break stack invariant */
  339. getLoader());
  340. }
  341. /**
  342. * Get the appropriate ResourceBundle subclass.
  343. * @param baseName see class description.
  344. * @param locale see class description.
  345. */
  346. public static final ResourceBundle getBundle(String baseName,
  347. Locale locale)
  348. {
  349. return getBundle(baseName, locale, getLoader());
  350. }
  351. /**
  352. * Return the Locale for this ResourceBundle. (This function can be used after a
  353. * call to getBundle() to determine whether the ResourceBundle returned really
  354. * corresponds to the requested locale or is a fallback.)
  355. */
  356. public Locale getLocale() {
  357. String className = getClass().getName();
  358. int pos = className.indexOf('_');
  359. if (pos == -1)
  360. return new Locale("", "", "");
  361. className = className.substring(pos + 1);
  362. pos = className.indexOf('_');
  363. if (pos == -1)
  364. return new Locale(className, "", "");
  365. String language = className.substring(0, pos);
  366. className = className.substring(pos + 1);
  367. pos = className.indexOf('_');
  368. if (pos == -1)
  369. return new Locale(language, className, "");
  370. String country = className.substring(0, pos);
  371. className = className.substring(pos + 1);
  372. return new Locale(language, country, className);
  373. }
  374. /*
  375. * Automatic determination of the ClassLoader to be used to load
  376. * resources on behalf of the client. N.B. The client is getLoader's
  377. * caller's caller.
  378. */
  379. private static ClassLoader getLoader() {
  380. Class[] stack = getClassContext();
  381. /* Magic number 2 identifies our caller's caller */
  382. Class c = stack[2];
  383. ClassLoader cl = (c == null) ? null : c.getClassLoader();
  384. if (cl == null) {
  385. cl = ClassLoader.getSystemClassLoader();
  386. }
  387. return cl;
  388. }
  389. private static native Class[] getClassContext();
  390. /**
  391. * Get the appropriate ResourceBundle subclass.
  392. * @param baseName see class description.
  393. * @param locale see class description.
  394. * @param loader the ClassLoader to load the resource from
  395. */
  396. public static ResourceBundle getBundle(String baseName, Locale locale,
  397. ClassLoader loader)
  398. throws MissingResourceException
  399. {
  400. StringBuffer localeName
  401. = new StringBuffer("_").append(locale.toString());
  402. if (locale.toString().equals(""))
  403. localeName.setLength(0);
  404. ResourceBundle lookup = findBundle(baseName,localeName,loader,false);
  405. if(lookup == null) {
  406. localeName.setLength(0);
  407. localeName.append("_").append( Locale.getDefault().toString() );
  408. lookup = findBundle(baseName, localeName, loader, true);
  409. if( lookup == null ) {
  410. throw new MissingResourceException("Can't find resource for base name "
  411. + baseName + ", locale " + locale,
  412. baseName + "_" + locale,"");
  413. }
  414. }
  415. // Setup lookup's ancestry. If we find an ancestor whose parent is null,
  416. // we set up the ancestor's parent as well.
  417. ResourceBundle child = lookup;
  418. while( (child != null) && (child.parent == null) ) {
  419. // Chop off the last component of the locale name and search for that
  420. // as the parent locale. Use it to set the parent of current child.
  421. int lastUnderbar = localeName.toString().lastIndexOf('_');
  422. if( lastUnderbar != -1 ) {
  423. localeName.setLength(lastUnderbar);
  424. child.setParent( findBundle(baseName,localeName,loader,true) );
  425. }
  426. child = child.parent;
  427. }
  428. return lookup;
  429. }
  430. /**
  431. * Set the parent bundle of this bundle. The parent bundle is
  432. * searched by getObject when this bundle does not contain a
  433. * particular resource.
  434. * @param parent this bundle's parent bundle.
  435. */
  436. protected void setParent( ResourceBundle parent ) {
  437. this.parent = parent;
  438. }
  439. /**
  440. * Key used for cached resource bundles. The key checks
  441. * both the resource name and its class loader to determine
  442. * if the resource is a match to the requested one. The
  443. * loader may be null, but the searchName must have a
  444. * non-null value.
  445. */
  446. private static class ResourceCacheKey implements Cloneable {
  447. private SoftReference loaderRef;
  448. private String searchName;
  449. private int hashCodeCache;
  450. public boolean equals(Object other) {
  451. if (this == other) {
  452. return true;
  453. }
  454. if (null == other) {
  455. return false;
  456. }
  457. if (!(other instanceof ResourceCacheKey)) {
  458. return false;
  459. }
  460. ResourceCacheKey otherEntry = (ResourceCacheKey)other;
  461. boolean result = hashCodeCache == otherEntry.hashCodeCache;
  462. if (result) {
  463. //are the names the same
  464. result = searchName.equals(otherEntry.searchName);
  465. if (result) {
  466. final boolean hasLoaderRef = loaderRef != null;
  467. //are refs (both non-null) or (both null)
  468. result = (hasLoaderRef && (otherEntry.loaderRef != null)) || (loaderRef == otherEntry.loaderRef);
  469. //if both non-null, check that they reference the same loader
  470. if (result && hasLoaderRef) {
  471. result = loaderRef.get() == otherEntry.loaderRef.get();
  472. }
  473. }
  474. }
  475. return result;
  476. }
  477. public int hashCode() {
  478. return hashCodeCache;
  479. }
  480. public Object clone() {
  481. try {
  482. return super.clone();
  483. } catch (CloneNotSupportedException e) {
  484. throw new InternalError();
  485. }
  486. }
  487. public void setKeyValues(ClassLoader loader, String searchName) {
  488. this.searchName = searchName;
  489. hashCodeCache = searchName.hashCode();
  490. if (loader == null) {
  491. this.loaderRef = null;
  492. } else {
  493. loaderRef = new SoftReference(loader);
  494. hashCodeCache ^= loader.hashCode();
  495. }
  496. }
  497. }
  498. /**
  499. * The internal routine that does the real work of finding and loading
  500. * the right ResourceBundle for a given name and locale.
  501. */
  502. private static ResourceBundle findBundle(String baseName,
  503. StringBuffer localeName,
  504. final ClassLoader loader,
  505. boolean includeBase)
  506. {
  507. String localeStr = localeName.toString();
  508. String baseFileName = baseName.replace('.', '/');
  509. Object lookup = null;
  510. String searchName;
  511. Vector cacheCandidates = new Vector();
  512. int lastUnderbar;
  513. InputStream stream;
  514. //Use the loader itself as a NOTFOUND value. The key only maintains a SoftReference
  515. //to the loader, so when the loader gets GCed, the NOTFOUND table entries will
  516. //be GCed with it. A ClassLoader is never a valid resource so we don't have to
  517. //worry about it ever showing up in the cache as an actual ResourceValue.
  518. Object NOTFOUND;
  519. if (loader == null) {
  520. NOTFOUND = NOLOADER_NOTFOUND;
  521. } else {
  522. NOTFOUND = loader; //use the loader itself as the NOTFOUND value
  523. }
  524. searchLoop:
  525. while (true) {
  526. searchName = baseName + localeStr;
  527. // First, look in the cache. We may either find the bundle we're
  528. // looking for or we may find that the bundle was not found by a
  529. // previous search.
  530. synchronized (cacheList) {
  531. //we use a static shared cacheKey to avoid the object
  532. //unless the lookup fails.
  533. cacheKey.setKeyValues(loader, searchName);
  534. lookup = cacheList.get(cacheKey);
  535. //If the value == the class loader, this
  536. //signifies that a prior search failed
  537. if( lookup == NOTFOUND ) {
  538. localeName.setLength(0);
  539. break searchLoop;
  540. }
  541. if( lookup != null ) {
  542. localeName.setLength(0);
  543. break searchLoop;
  544. }
  545. cacheCandidates.addElement( cacheKey.clone() );
  546. cacheKey.setKeyValues(null, "");
  547. }
  548. // Next search for a class
  549. try {
  550. if (loader != null) {
  551. lookup = (ResourceBundle)(loader.loadClass(searchName).newInstance());
  552. } else {
  553. lookup = (ResourceBundle)(Class.forName(searchName).newInstance());
  554. }
  555. break searchLoop;
  556. } catch( Exception e ){}
  557. // Next search for a Properties file.
  558. searchName = baseFileName + localeStr + ".properties";
  559. final String resName = searchName;
  560. stream = (InputStream)java.security.AccessController.doPrivileged
  561. (new java.security.PrivilegedAction() {
  562. public Object run() {
  563. if (loader != null) {
  564. return loader.getResourceAsStream
  565. (resName);
  566. } else {
  567. return ClassLoader.getSystemResourceAsStream
  568. (resName);
  569. }
  570. }
  571. });
  572. if( stream != null ) {
  573. // make sure it is buffered
  574. stream = new java.io.BufferedInputStream(stream);
  575. try {
  576. lookup = (Object)new PropertyResourceBundle( stream );
  577. break searchLoop;
  578. }
  579. catch (Exception e) {
  580. }
  581. finally {
  582. try {
  583. stream.close();
  584. }
  585. catch (Exception e) {
  586. // to avoid propagating an IOException back into the caller
  587. // (I'm assuming this is never going to happen, and if it does,
  588. // I'm obeying the precedent of swallowing exceptions set by the
  589. // existing code above)
  590. }
  591. }
  592. }
  593. //Chop off the last part of the locale name string and try again.
  594. lastUnderbar = localeStr.lastIndexOf('_');
  595. if( ((lastUnderbar==0)&&(!includeBase)) || (lastUnderbar == -1) ) {
  596. break;
  597. }
  598. localeStr = localeStr.substring(0,lastUnderbar);
  599. localeName.setLength(lastUnderbar);
  600. }
  601. // If we searched all the way to the base, then we can add
  602. // the NOTFOUND result to the cache. Otherwise we can say
  603. // nothing.
  604. if (lookup == null && includeBase == true)
  605. lookup = NOTFOUND;
  606. if( lookup != null )
  607. synchronized (cacheList) {
  608. // Add a positive result to the cache. The result may include
  609. // NOTFOUND
  610. for( int i=0; i<cacheCandidates.size(); i++ ) {
  611. cacheList.put(cacheCandidates.elementAt(i), lookup);
  612. }
  613. }
  614. if( (lookup == NOTFOUND) || (lookup == null) )
  615. return null;
  616. else
  617. return (ResourceBundle)lookup;
  618. }
  619. /** Get an object from a ResourceBundle.
  620. * <STRONG>NOTE: </STRONG>Subclasses must override.
  621. * @param key see class description.
  622. */
  623. protected abstract Object handleGetObject(String key)
  624. throws MissingResourceException;
  625. /**
  626. * Return an enumeration of the keys.
  627. * <STRONG>NOTE: </STRONG>Subclasses must override.
  628. */
  629. public abstract Enumeration getKeys();
  630. /**
  631. * For printf debugging.
  632. */
  633. private static final boolean debugFlag = false;
  634. private static void debug(String str) {
  635. if( debugFlag ) {
  636. System.out.println("ResourceBundle: " + str);
  637. }
  638. }
  639. /**
  640. * The parent bundle is consulted by getObject when this bundle
  641. * does not contain a particular resource.
  642. */
  643. protected ResourceBundle parent = null;
  644. //This is a SoftCache, allowing bundles to be
  645. //removed from the cache if they are no longer
  646. //needed. This will also allow the cache keys
  647. //to be reclaimed along with the ClassLoaders
  648. //they reference.
  649. private static SoftCache cacheList = new SoftCache();
  650. //The NOTFOUND value used when the class loader is null
  651. private static final Object NOLOADER_NOTFOUND = new Integer(-1);
  652. }