home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / util / ResourceBundle.java < prev    next >
Encoding:
Java Source  |  1998-03-20  |  21.0 KB  |  562 lines

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