home *** CD-ROM | disk | FTP | other *** search
/ Apple Developer Connection Student Program / ADC Tools Sampler CD Disk 3 1999.iso / Metrowerks CodeWarrior / Java Support / Java_Source / Java2 / src / java / util / ResourceBundle.java < prev    next >
Encoding:
Java Source  |  1999-05-28  |  22.8 KB  |  613 lines  |  [TEXT/CWIE]

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