Save This Page
Home » openjdk-7 » java » util » [javadoc | source]
    1   /*
    2    * Copyright 1996-2006 Sun Microsystems, Inc.  All Rights Reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any questions.
   24    */
   25   
   26   /*
   27    * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
   28    * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
   29    *
   30    * The original version of this source code and documentation
   31    * is copyrighted and owned by Taligent, Inc., a wholly-owned
   32    * subsidiary of IBM. These materials are provided under terms
   33    * of a License Agreement between Taligent and Sun. This technology
   34    * is protected by multiple US and International patents.
   35    *
   36    * This notice and attribution to Taligent may not be removed.
   37    * Taligent is a registered trademark of Taligent, Inc.
   38    *
   39    */
   40   
   41   package java.util;
   42   
   43   import java.io.IOException;
   44   import java.io.InputStream;
   45   import java.lang.ref.ReferenceQueue;
   46   import java.lang.ref.SoftReference;
   47   import java.lang.ref.WeakReference;
   48   import java.net.JarURLConnection;
   49   import java.net.URL;
   50   import java.net.URLConnection;
   51   import java.security.AccessController;
   52   import java.security.PrivilegedAction;
   53   import java.security.PrivilegedActionException;
   54   import java.security.PrivilegedExceptionAction;
   55   import java.util.concurrent.ConcurrentHashMap;
   56   import java.util.concurrent.ConcurrentMap;
   57   import java.util.jar.JarEntry;
   58   
   59   
   60   /**
   61    *
   62    * Resource bundles contain locale-specific objects.
   63    * When your program needs a locale-specific resource,
   64    * a <code>String</code> for example, your program can load it
   65    * from the resource bundle that is appropriate for the
   66    * current user's locale. In this way, you can write
   67    * program code that is largely independent of the user's
   68    * locale isolating most, if not all, of the locale-specific
   69    * information in resource bundles.
   70    *
   71    * <p>
   72    * This allows you to write programs that can:
   73    * <UL type=SQUARE>
   74    * <LI> be easily localized, or translated, into different languages
   75    * <LI> handle multiple locales at once
   76    * <LI> be easily modified later to support even more locales
   77    * </UL>
   78    *
   79    * <P>
   80    * Resource bundles belong to families whose members share a common base
   81    * name, but whose names also have additional components that identify
   82    * their locales. For example, the base name of a family of resource
   83    * bundles might be "MyResources". The family should have a default
   84    * resource bundle which simply has the same name as its family -
   85    * "MyResources" - and will be used as the bundle of last resort if a
   86    * specific locale is not supported. The family can then provide as
   87    * many locale-specific members as needed, for example a German one
   88    * named "MyResources_de".
   89    *
   90    * <P>
   91    * Each resource bundle in a family contains the same items, but the items have
   92    * been translated for the locale represented by that resource bundle.
   93    * For example, both "MyResources" and "MyResources_de" may have a
   94    * <code>String</code> that's used on a button for canceling operations.
   95    * In "MyResources" the <code>String</code> may contain "Cancel" and in
   96    * "MyResources_de" it may contain "Abbrechen".
   97    *
   98    * <P>
   99    * If there are different resources for different countries, you
  100    * can make specializations: for example, "MyResources_de_CH" contains objects for
  101    * the German language (de) in Switzerland (CH). If you want to only
  102    * modify some of the resources
  103    * in the specialization, you can do so.
  104    *
  105    * <P>
  106    * When your program needs a locale-specific object, it loads
  107    * the <code>ResourceBundle</code> class using the
  108    * {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
  109    * method:
  110    * <blockquote>
  111    * <pre>
  112    * ResourceBundle myResources =
  113    *      ResourceBundle.getBundle("MyResources", currentLocale);
  114    * </pre>
  115    * </blockquote>
  116    *
  117    * <P>
  118    * Resource bundles contain key/value pairs. The keys uniquely
  119    * identify a locale-specific object in the bundle. Here's an
  120    * example of a <code>ListResourceBundle</code> that contains
  121    * two key/value pairs:
  122    * <blockquote>
  123    * <pre>
  124    * public class MyResources extends ListResourceBundle {
  125    *     protected Object[][] getContents() {
  126    *         return new Object[][] {
  127    *             // LOCALIZE THE SECOND STRING OF EACH ARRAY (e.g., "OK")
  128    *             {"OkKey", "OK"},
  129    *             {"CancelKey", "Cancel"},
  130    *             // END OF MATERIAL TO LOCALIZE
  131    *        };
  132    *     }
  133    * }
  134    * </pre>
  135    * </blockquote>
  136    * Keys are always <code>String</code>s.
  137    * In this example, the keys are "OkKey" and "CancelKey".
  138    * In the above example, the values
  139    * are also <code>String</code>s--"OK" and "Cancel"--but
  140    * they don't have to be. The values can be any type of object.
  141    *
  142    * <P>
  143    * You retrieve an object from resource bundle using the appropriate
  144    * getter method. Because "OkKey" and "CancelKey"
  145    * are both strings, you would use <code>getString</code> to retrieve them:
  146    * <blockquote>
  147    * <pre>
  148    * button1 = new Button(myResources.getString("OkKey"));
  149    * button2 = new Button(myResources.getString("CancelKey"));
  150    * </pre>
  151    * </blockquote>
  152    * The getter methods all require the key as an argument and return
  153    * the object if found. If the object is not found, the getter method
  154    * throws a <code>MissingResourceException</code>.
  155    *
  156    * <P>
  157    * Besides <code>getString</code>, <code>ResourceBundle</code> also provides
  158    * a method for getting string arrays, <code>getStringArray</code>,
  159    * as well as a generic <code>getObject</code> method for any other
  160    * type of object. When using <code>getObject</code>, you'll
  161    * have to cast the result to the appropriate type. For example:
  162    * <blockquote>
  163    * <pre>
  164    * int[] myIntegers = (int[]) myResources.getObject("intList");
  165    * </pre>
  166    * </blockquote>
  167    *
  168    * <P>
  169    * The Java Platform provides two subclasses of <code>ResourceBundle</code>,
  170    * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
  171    * that provide a fairly simple way to create resources.
  172    * As you saw briefly in a previous example, <code>ListResourceBundle</code>
  173    * manages its resource as a list of key/value pairs.
  174    * <code>PropertyResourceBundle</code> uses a properties file to manage
  175    * its resources.
  176    *
  177    * <p>
  178    * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
  179    * do not suit your needs, you can write your own <code>ResourceBundle</code>
  180    * subclass.  Your subclasses must override two methods: <code>handleGetObject</code>
  181    * and <code>getKeys()</code>.
  182    *
  183    * <h4>ResourceBundle.Control</h4>
  184    *
  185    * The {@link ResourceBundle.Control} class provides information necessary
  186    * to perform the bundle loading process by the <code>getBundle</code>
  187    * factory methods that take a <code>ResourceBundle.Control</code>
  188    * instance. You can implement your own subclass in order to enable
  189    * non-standard resource bundle formats, change the search strategy, or
  190    * define caching parameters. Refer to the descriptions of the class and the
  191    * {@link #getBundle(String, Locale, ClassLoader, Control) getBundle}
  192    * factory method for details.
  193    *
  194    * <h4>Cache Management</h4>
  195    *
  196    * Resource bundle instances created by the <code>getBundle</code> factory
  197    * methods are cached by default, and the factory methods return the same
  198    * resource bundle instance multiple times if it has been
  199    * cached. <code>getBundle</code> clients may clear the cache, manage the
  200    * lifetime of cached resource bundle instances using time-to-live values,
  201    * or specify not to cache resource bundle instances. Refer to the
  202    * descriptions of the {@linkplain #getBundle(String, Locale, ClassLoader,
  203    * Control) <code>getBundle</code> factory method}, {@link
  204    * #clearCache(ClassLoader) clearCache}, {@link
  205    * Control#getTimeToLive(String, Locale)
  206    * ResourceBundle.Control.getTimeToLive}, and {@link
  207    * Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle,
  208    * long) ResourceBundle.Control.needsReload} for details.
  209    *
  210    * <h4>Example</h4>
  211    *
  212    * The following is a very simple example of a <code>ResourceBundle</code>
  213    * subclass, <code>MyResources</code>, that manages two resources (for a larger number of
  214    * resources you would probably use a <code>Map</code>).
  215    * Notice that you don't need to supply a value if
  216    * a "parent-level" <code>ResourceBundle</code> handles the same
  217    * key with the same value (as for the okKey below).
  218    * <blockquote>
  219    * <pre>
  220    * // default (English language, United States)
  221    * public class MyResources extends ResourceBundle {
  222    *     public Object handleGetObject(String key) {
  223    *         if (key.equals("okKey")) return "Ok";
  224    *         if (key.equals("cancelKey")) return "Cancel";
  225    *         return null;
  226    *     }
  227    *
  228    *     public Enumeration&lt;String&gt; getKeys() {
  229    *         return Collections.enumeration(keySet());
  230    *     }
  231    *
  232    *     // Overrides handleKeySet() so that the getKeys() implementation
  233    *     // can rely on the keySet() value.
  234    *     protected Set&lt;String&gt; handleKeySet() {
  235    *         return new HashSet&lt;String&gt;(Arrays.asList("okKey", "cancelKey"));
  236    *     }
  237    * }
  238    *
  239    * // German language
  240    * public class MyResources_de extends MyResources {
  241    *     public Object handleGetObject(String key) {
  242    *         // don't need okKey, since parent level handles it.
  243    *         if (key.equals("cancelKey")) return "Abbrechen";
  244    *         return null;
  245    *     }
  246    *
  247    *     protected Set&lt;String&gt; handleKeySet() {
  248    *         return new HashSet&lt;String&gt;(Arrays.asList("cancelKey"));
  249    *     }
  250    * }
  251    * </pre>
  252    * </blockquote>
  253    * You do not have to restrict yourself to using a single family of
  254    * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
  255    * exception messages, <code>ExceptionResources</code>
  256    * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
  257    * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
  258    * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
  259    *
  260    * @see ListResourceBundle
  261    * @see PropertyResourceBundle
  262    * @see MissingResourceException
  263    * @since JDK1.1
  264    */
  265   public abstract class ResourceBundle {
  266   
  267       /** initial size of the bundle cache */
  268       private static final int INITIAL_CACHE_SIZE = 32;
  269   
  270       /** constant indicating that no resource bundle exists */
  271       private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
  272               public Enumeration<String> getKeys() { return null; }
  273               protected Object handleGetObject(String key) { return null; }
  274               public String toString() { return "NONEXISTENT_BUNDLE"; }
  275           };
  276   
  277   
  278       /**
  279        * The cache is a map from cache keys (with bundle base name, locale, and
  280        * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a
  281        * BundleReference.
  282        *
  283        * The cache is a ConcurrentMap, allowing the cache to be searched
  284        * concurrently by multiple threads.  This will also allow the cache keys
  285        * to be reclaimed along with the ClassLoaders they reference.
  286        *
  287        * This variable would be better named "cache", but we keep the old
  288        * name for compatibility with some workarounds for bug 4212439.
  289        */
  290       private static final ConcurrentMap<CacheKey, BundleReference> cacheList
  291           = new ConcurrentHashMap<CacheKey, BundleReference>(INITIAL_CACHE_SIZE);
  292   
  293       /**
  294        * This ConcurrentMap is used to keep multiple threads from loading the
  295        * same bundle concurrently.  The table entries are <CacheKey, Thread>
  296        * where CacheKey is the key for the bundle that is under construction
  297        * and Thread is the thread that is constructing the bundle.
  298        * This list is manipulated in findBundleInCache and putBundleInCache.
  299        */
  300       private static final ConcurrentMap<CacheKey, Thread> underConstruction
  301           = new ConcurrentHashMap<CacheKey, Thread>();
  302   
  303       /**
  304        * Queue for reference objects referring to class loaders or bundles.
  305        */
  306       private static final ReferenceQueue referenceQueue = new ReferenceQueue();
  307   
  308       /**
  309        * The parent bundle of this bundle.
  310        * The parent bundle is searched by {@link #getObject getObject}
  311        * when this bundle does not contain a particular resource.
  312        */
  313       protected ResourceBundle parent = null;
  314   
  315       /**
  316        * The locale for this bundle.
  317        */
  318       private Locale locale = null;
  319   
  320       /**
  321        * The base bundle name for this bundle.
  322        */
  323       private String name;
  324   
  325       /**
  326        * The flag indicating this bundle has expired in the cache.
  327        */
  328       private volatile boolean expired;
  329   
  330       /**
  331        * The back link to the cache key. null if this bundle isn't in
  332        * the cache (yet) or has expired.
  333        */
  334       private volatile CacheKey cacheKey;
  335   
  336       /**
  337        * A Set of the keys contained only in this ResourceBundle.
  338        */
  339       private volatile Set<String> keySet;
  340   
  341       /**
  342        * Sole constructor.  (For invocation by subclass constructors, typically
  343        * implicit.)
  344        */
  345       public ResourceBundle() {
  346       }
  347   
  348       /**
  349        * Gets a string for the given key from this resource bundle or one of its parents.
  350        * Calling this method is equivalent to calling
  351        * <blockquote>
  352        * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
  353        * </blockquote>
  354        *
  355        * @param key the key for the desired string
  356        * @exception NullPointerException if <code>key</code> is <code>null</code>
  357        * @exception MissingResourceException if no object for the given key can be found
  358        * @exception ClassCastException if the object found for the given key is not a string
  359        * @return the string for the given key
  360        */
  361       public final String getString(String key) {
  362           return (String) getObject(key);
  363       }
  364   
  365       /**
  366        * Gets a string array for the given key from this resource bundle or one of its parents.
  367        * Calling this method is equivalent to calling
  368        * <blockquote>
  369        * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
  370        * </blockquote>
  371        *
  372        * @param key the key for the desired string array
  373        * @exception NullPointerException if <code>key</code> is <code>null</code>
  374        * @exception MissingResourceException if no object for the given key can be found
  375        * @exception ClassCastException if the object found for the given key is not a string array
  376        * @return the string array for the given key
  377        */
  378       public final String[] getStringArray(String key) {
  379           return (String[]) getObject(key);
  380       }
  381   
  382       /**
  383        * Gets an object for the given key from this resource bundle or one of its parents.
  384        * This method first tries to obtain the object from this resource bundle using
  385        * {@link #handleGetObject(java.lang.String) handleGetObject}.
  386        * If not successful, and the parent resource bundle is not null,
  387        * it calls the parent's <code>getObject</code> method.
  388        * If still not successful, it throws a MissingResourceException.
  389        *
  390        * @param key the key for the desired object
  391        * @exception NullPointerException if <code>key</code> is <code>null</code>
  392        * @exception MissingResourceException if no object for the given key can be found
  393        * @return the object for the given key
  394        */
  395       public final Object getObject(String key) {
  396           Object obj = handleGetObject(key);
  397           if (obj == null) {
  398               if (parent != null) {
  399                   obj = parent.getObject(key);
  400               }
  401               if (obj == null)
  402                   throw new MissingResourceException("Can't find resource for bundle "
  403                                                      +this.getClass().getName()
  404                                                      +", key "+key,
  405                                                      this.getClass().getName(),
  406                                                      key);
  407           }
  408           return obj;
  409       }
  410   
  411       /**
  412        * Returns the locale of this resource bundle. This method can be used after a
  413        * call to getBundle() to determine whether the resource bundle returned really
  414        * corresponds to the requested locale or is a fallback.
  415        *
  416        * @return the locale of this resource bundle
  417        */
  418       public Locale getLocale() {
  419           return locale;
  420       }
  421   
  422       /*
  423        * Automatic determination of the ClassLoader to be used to load
  424        * resources on behalf of the client.  N.B. The client is getLoader's
  425        * caller's caller.
  426        */
  427       private static ClassLoader getLoader() {
  428           Class[] stack = getClassContext();
  429           /* Magic number 2 identifies our caller's caller */
  430           Class c = stack[2];
  431           ClassLoader cl = (c == null) ? null : c.getClassLoader();
  432           if (cl == null) {
  433               // When the caller's loader is the boot class loader, cl is null
  434               // here. In that case, ClassLoader.getSystemClassLoader() may
  435               // return the same class loader that the application is
  436               // using. We therefore use a wrapper ClassLoader to create a
  437               // separate scope for bundles loaded on behalf of the Java
  438               // runtime so that these bundles cannot be returned from the
  439               // cache to the application (5048280).
  440               cl = RBClassLoader.INSTANCE;
  441           }
  442           return cl;
  443       }
  444   
  445       private static native Class[] getClassContext();
  446   
  447       /**
  448        * A wrapper of ClassLoader.getSystemClassLoader().
  449        */
  450       private static class RBClassLoader extends ClassLoader {
  451           private static final RBClassLoader INSTANCE = AccessController.doPrivileged(
  452                       new PrivilegedAction<RBClassLoader>() {
  453                           public RBClassLoader run() {
  454                               return new RBClassLoader();
  455                           }
  456                       });
  457           private static final ClassLoader loader = ClassLoader.getSystemClassLoader();
  458   
  459           private RBClassLoader() {
  460           }
  461           public Class<?> loadClass(String name) throws ClassNotFoundException {
  462               if (loader != null) {
  463                   return loader.loadClass(name);
  464               }
  465               return Class.forName(name);
  466           }
  467           public URL getResource(String name) {
  468               if (loader != null) {
  469                   return loader.getResource(name);
  470               }
  471               return ClassLoader.getSystemResource(name);
  472           }
  473           public InputStream getResourceAsStream(String name) {
  474               if (loader != null) {
  475                   return loader.getResourceAsStream(name);
  476               }
  477               return ClassLoader.getSystemResourceAsStream(name);
  478           }
  479       }
  480   
  481       /**
  482        * Sets the parent bundle of this bundle.
  483        * The parent bundle is searched by {@link #getObject getObject}
  484        * when this bundle does not contain a particular resource.
  485        *
  486        * @param parent this bundle's parent bundle.
  487        */
  488       protected void setParent(ResourceBundle parent) {
  489           assert parent != NONEXISTENT_BUNDLE;
  490           this.parent = parent;
  491       }
  492   
  493       /**
  494        * Key used for cached resource bundles.  The key checks the base
  495        * name, the locale, and the class loader to determine if the
  496        * resource is a match to the requested one. The loader may be
  497        * null, but the base name and the locale must have a non-null
  498        * value.
  499        */
  500       private static final class CacheKey implements Cloneable {
  501           // These three are the actual keys for lookup in Map.
  502           private String name;
  503           private Locale locale;
  504           private LoaderReference loaderRef;
  505   
  506           // bundle format which is necessary for calling
  507           // Control.needsReload().
  508           private String format;
  509   
  510           // These time values are in CacheKey so that NONEXISTENT_BUNDLE
  511           // doesn't need to be cloned for caching.
  512   
  513           // The time when the bundle has been loaded
  514           private volatile long loadTime;
  515   
  516           // The time when the bundle expires in the cache, or either
  517           // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
  518           private volatile long expirationTime;
  519   
  520           // Placeholder for an error report by a Throwable
  521           private Throwable cause;
  522   
  523           // Hash code value cache to avoid recalculating the hash code
  524           // of this instance.
  525           private int hashCodeCache;
  526   
  527           CacheKey(String baseName, Locale locale, ClassLoader loader) {
  528               this.name = baseName;
  529               this.locale = locale;
  530               if (loader == null) {
  531                   this.loaderRef = null;
  532               } else {
  533                   loaderRef = new LoaderReference(loader, referenceQueue, this);
  534               }
  535               calculateHashCode();
  536           }
  537   
  538           String getName() {
  539               return name;
  540           }
  541   
  542           CacheKey setName(String baseName) {
  543               if (!this.name.equals(baseName)) {
  544                   this.name = baseName;
  545                   calculateHashCode();
  546               }
  547               return this;
  548           }
  549   
  550           Locale getLocale() {
  551               return locale;
  552           }
  553   
  554           CacheKey setLocale(Locale locale) {
  555               if (!this.locale.equals(locale)) {
  556                   this.locale = locale;
  557                   calculateHashCode();
  558               }
  559               return this;
  560           }
  561   
  562           ClassLoader getLoader() {
  563               return (loaderRef != null) ? loaderRef.get() : null;
  564           }
  565   
  566           public boolean equals(Object other) {
  567               if (this == other) {
  568                   return true;
  569               }
  570               try {
  571                   final CacheKey otherEntry = (CacheKey)other;
  572                   //quick check to see if they are not equal
  573                   if (hashCodeCache != otherEntry.hashCodeCache) {
  574                       return false;
  575                   }
  576                   //are the names the same?
  577                   if (!name.equals(otherEntry.name)) {
  578                       return false;
  579                   }
  580                   // are the locales the same?
  581                   if (!locale.equals(otherEntry.locale)) {
  582                       return false;
  583                   }
  584                   //are refs (both non-null) or (both null)?
  585                   if (loaderRef == null) {
  586                       return otherEntry.loaderRef == null;
  587                   }
  588                   ClassLoader loader = loaderRef.get();
  589                   return (otherEntry.loaderRef != null)
  590                           // with a null reference we can no longer find
  591                           // out which class loader was referenced; so
  592                           // treat it as unequal
  593                           && (loader != null)
  594                           && (loader == otherEntry.loaderRef.get());
  595               } catch (NullPointerException e) {
  596               } catch (ClassCastException e) {
  597               }
  598               return false;
  599           }
  600   
  601           public int hashCode() {
  602               return hashCodeCache;
  603           }
  604   
  605           private void calculateHashCode() {
  606               hashCodeCache = name.hashCode() << 3;
  607               hashCodeCache ^= locale.hashCode();
  608               ClassLoader loader = getLoader();
  609               if (loader != null) {
  610                   hashCodeCache ^= loader.hashCode();
  611               }
  612           }
  613   
  614           public Object clone() {
  615               try {
  616                   CacheKey clone = (CacheKey) super.clone();
  617                   if (loaderRef != null) {
  618                       clone.loaderRef = new LoaderReference(loaderRef.get(),
  619                                                             referenceQueue, clone);
  620                   }
  621                   // Clear the reference to a Throwable
  622                   clone.cause = null;
  623                   return clone;
  624               } catch (CloneNotSupportedException e) {
  625                   //this should never happen
  626                   throw new InternalError();
  627               }
  628           }
  629   
  630           String getFormat() {
  631               return format;
  632           }
  633   
  634           void setFormat(String format) {
  635               this.format = format;
  636           }
  637   
  638           private void setCause(Throwable cause) {
  639               if (this.cause == null) {
  640                   this.cause = cause;
  641               } else {
  642                   // Override the cause if the previous one is
  643                   // ClassNotFoundException.
  644                   if (this.cause instanceof ClassNotFoundException) {
  645                       this.cause = cause;
  646                   }
  647               }
  648           }
  649   
  650           private Throwable getCause() {
  651               return cause;
  652           }
  653   
  654           public String toString() {
  655               String l = locale.toString();
  656               if (l.length() == 0) {
  657                   if (locale.getVariant().length() != 0) {
  658                       l = "__" + locale.getVariant();
  659                   } else {
  660                       l = "\"\"";
  661                   }
  662               }
  663               return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
  664                   + "(format=" + format + ")]";
  665           }
  666       }
  667   
  668       /**
  669        * The common interface to get a CacheKey in LoaderReference and
  670        * BundleReference.
  671        */
  672       private static interface CacheKeyReference {
  673           public CacheKey getCacheKey();
  674       }
  675   
  676       /**
  677        * References to class loaders are weak references, so that they can be
  678        * garbage collected when nobody else is using them. The ResourceBundle
  679        * class has no reason to keep class loaders alive.
  680        */
  681       private static final class LoaderReference extends WeakReference<ClassLoader>
  682                                                  implements CacheKeyReference {
  683           private CacheKey cacheKey;
  684   
  685           LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) {
  686               super(referent, q);
  687               cacheKey = key;
  688           }
  689   
  690           public CacheKey getCacheKey() {
  691               return cacheKey;
  692           }
  693       }
  694   
  695       /**
  696        * References to bundles are soft references so that they can be garbage
  697        * collected when they have no hard references.
  698        */
  699       private static final class BundleReference extends SoftReference<ResourceBundle>
  700                                                  implements CacheKeyReference {
  701           private CacheKey cacheKey;
  702   
  703           BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) {
  704               super(referent, q);
  705               cacheKey = key;
  706           }
  707   
  708           public CacheKey getCacheKey() {
  709               return cacheKey;
  710           }
  711       }
  712   
  713       /**
  714        * Gets a resource bundle using the specified base name, the default locale,
  715        * and the caller's class loader. Calling this method is equivalent to calling
  716        * <blockquote>
  717        * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
  718        * </blockquote>
  719        * except that <code>getClassLoader()</code> is run with the security
  720        * privileges of <code>ResourceBundle</code>.
  721        * See {@link #getBundle(String, Locale, ClassLoader) getBundle}
  722        * for a complete description of the search and instantiation strategy.
  723        *
  724        * @param baseName the base name of the resource bundle, a fully qualified class name
  725        * @exception java.lang.NullPointerException
  726        *     if <code>baseName</code> is <code>null</code>
  727        * @exception MissingResourceException
  728        *     if no resource bundle for the specified base name can be found
  729        * @return a resource bundle for the given base name and the default locale
  730        */
  731       public static final ResourceBundle getBundle(String baseName)
  732       {
  733           return getBundleImpl(baseName, Locale.getDefault(),
  734                                /* must determine loader here, else we break stack invariant */
  735                                getLoader(),
  736                                Control.INSTANCE);
  737       }
  738   
  739       /**
  740        * Returns a resource bundle using the specified base name, the
  741        * default locale and the specified control. Calling this method
  742        * is equivalent to calling
  743        * <pre>
  744        * getBundle(baseName, Locale.getDefault(),
  745        *           this.getClass().getClassLoader(), control),
  746        * </pre>
  747        * except that <code>getClassLoader()</code> is run with the security
  748        * privileges of <code>ResourceBundle</code>.  See {@link
  749        * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
  750        * complete description of the resource bundle loading process with a
  751        * <code>ResourceBundle.Control</code>.
  752        *
  753        * @param baseName
  754        *        the base name of the resource bundle, a fully qualified class
  755        *        name
  756        * @param control
  757        *        the control which gives information for the resource bundle
  758        *        loading process
  759        * @return a resource bundle for the given base name and the default
  760        *        locale
  761        * @exception NullPointerException
  762        *        if <code>baseName</code> or <code>control</code> is
  763        *        <code>null</code>
  764        * @exception MissingResourceException
  765        *        if no resource bundle for the specified base name can be found
  766        * @exception IllegalArgumentException
  767        *        if the given <code>control</code> doesn't perform properly
  768        *        (e.g., <code>control.getCandidateLocales</code> returns null.)
  769        *        Note that validation of <code>control</code> is performed as
  770        *        needed.
  771        * @since 1.6
  772        */
  773       public static final ResourceBundle getBundle(String baseName,
  774                                                    Control control) {
  775           return getBundleImpl(baseName, Locale.getDefault(),
  776                                /* must determine loader here, else we break stack invariant */
  777                                getLoader(),
  778                                control);
  779       }
  780   
  781       /**
  782        * Gets a resource bundle using the specified base name and locale,
  783        * and the caller's class loader. Calling this method is equivalent to calling
  784        * <blockquote>
  785        * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
  786        * </blockquote>
  787        * except that <code>getClassLoader()</code> is run with the security
  788        * privileges of <code>ResourceBundle</code>.
  789        * See {@link #getBundle(String, Locale, ClassLoader) getBundle}
  790        * for a complete description of the search and instantiation strategy.
  791        *
  792        * @param baseName
  793        *        the base name of the resource bundle, a fully qualified class name
  794        * @param locale
  795        *        the locale for which a resource bundle is desired
  796        * @exception NullPointerException
  797        *        if <code>baseName</code> or <code>locale</code> is <code>null</code>
  798        * @exception MissingResourceException
  799        *        if no resource bundle for the specified base name can be found
  800        * @return a resource bundle for the given base name and locale
  801        */
  802       public static final ResourceBundle getBundle(String baseName,
  803                                                    Locale locale)
  804       {
  805           return getBundleImpl(baseName, locale,
  806                                /* must determine loader here, else we break stack invariant */
  807                                getLoader(),
  808                                Control.INSTANCE);
  809       }
  810   
  811       /**
  812        * Returns a resource bundle using the specified base name, target
  813        * locale and control, and the caller's class loader. Calling this
  814        * method is equivalent to calling
  815        * <pre>
  816        * getBundle(baseName, targetLocale, this.getClass().getClassLoader(),
  817        *           control),
  818        * </pre>
  819        * except that <code>getClassLoader()</code> is run with the security
  820        * privileges of <code>ResourceBundle</code>.  See {@link
  821        * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
  822        * complete description of the resource bundle loading process with a
  823        * <code>ResourceBundle.Control</code>.
  824        *
  825        * @param baseName
  826        *        the base name of the resource bundle, a fully qualified
  827        *        class name
  828        * @param targetLocale
  829        *        the locale for which a resource bundle is desired
  830        * @param control
  831        *        the control which gives information for the resource
  832        *        bundle loading process
  833        * @return a resource bundle for the given base name and a
  834        *        <code>Locale</code> in <code>locales</code>
  835        * @exception NullPointerException
  836        *        if <code>baseName</code>, <code>locales</code> or
  837        *        <code>control</code> is <code>null</code>
  838        * @exception MissingResourceException
  839        *        if no resource bundle for the specified base name in any
  840        *        of the <code>locales</code> can be found.
  841        * @exception IllegalArgumentException
  842        *        if the given <code>control</code> doesn't perform properly
  843        *        (e.g., <code>control.getCandidateLocales</code> returns null.)
  844        *        Note that validation of <code>control</code> is performed as
  845        *        needed.
  846        * @since 1.6
  847        */
  848       public static final ResourceBundle getBundle(String baseName, Locale targetLocale,
  849                                                    Control control) {
  850           return getBundleImpl(baseName, targetLocale,
  851                                /* must determine loader here, else we break stack invariant */
  852                                getLoader(),
  853                                control);
  854       }
  855   
  856       /**
  857        * Gets a resource bundle using the specified base name, locale, and class loader.
  858        *
  859        * <p><a name="default_behavior"/>
  860        * Conceptually, <code>getBundle</code> uses the following strategy for locating and instantiating
  861        * resource bundles:
  862        * <p>
  863        * <code>getBundle</code> uses the base name, the specified locale, and the default
  864        * locale (obtained from {@link java.util.Locale#getDefault() Locale.getDefault})
  865        * to generate a sequence of <a name="candidates"><em>candidate bundle names</em></a>.
  866        * If the specified locale's language, country, and variant are all empty
  867        * strings, then the base name is the only candidate bundle name.
  868        * Otherwise, the following sequence is generated from the attribute
  869        * values of the specified locale (language1, country1, and variant1)
  870        * and of the default locale (language2, country2, and variant2):
  871        * <ul>
  872        * <li> baseName + "_" + language1 + "_" + country1 + "_" + variant1
  873        * <li> baseName + "_" + language1 + "_" + country1
  874        * <li> baseName + "_" + language1
  875        * <li> baseName + "_" + language2 + "_" + country2 + "_" + variant2
  876        * <li> baseName + "_" + language2 + "_" + country2
  877        * <li> baseName + "_" + language2
  878        * <li> baseName
  879        * </ul>
  880        * <p>
  881        * Candidate bundle names where the final component is an empty string are omitted.
  882        * For example, if country1 is an empty string, the second candidate bundle name is omitted.
  883        *
  884        * <p>
  885        * <code>getBundle</code> then iterates over the candidate bundle names to find the first
  886        * one for which it can <em>instantiate</em> an actual resource bundle. For each candidate
  887        * bundle name, it attempts to create a resource bundle:
  888        * <ul>
  889        * <li>
  890        * First, it attempts to load a class using the candidate bundle name.
  891        * If such a class can be found and loaded using the specified class loader, is assignment
  892        * compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated,
  893        * <code>getBundle</code> creates a new instance of this class and uses it as the <em>result
  894        * resource bundle</em>.
  895        * <li>
  896        * Otherwise, <code>getBundle</code> attempts to locate a property resource file.
  897        * It generates a path name from the candidate bundle name by replacing all "." characters
  898        * with "/" and appending the string ".properties".
  899        * It attempts to find a "resource" with this name using
  900        * {@link java.lang.ClassLoader#getResource(java.lang.String) ClassLoader.getResource}.
  901        * (Note that a "resource" in the sense of <code>getResource</code> has nothing to do with
  902        * the contents of a resource bundle, it is just a container of data, such as a file.)
  903        * If it finds a "resource", it attempts to create a new
  904        * {@link PropertyResourceBundle} instance from its contents.
  905        * If successful, this instance becomes the <em>result resource bundle</em>.
  906        * </ul>
  907        *
  908        * <p>
  909        * If no result resource bundle has been found, a <code>MissingResourceException</code>
  910        * is thrown.
  911        *
  912        * <p><a name="parent_chain"/>
  913        * Once a result resource bundle has been found, its <em>parent chain</em> is instantiated.
  914        * <code>getBundle</code> iterates over the candidate bundle names that can be
  915        * obtained by successively removing variant, country, and language
  916        * (each time with the preceding "_") from the bundle name of the result resource bundle.
  917        * As above, candidate bundle names where the final component is an empty string are omitted.
  918        * With each of the candidate bundle names it attempts to instantiate a resource bundle, as
  919        * described above.
  920        * Whenever it succeeds, it calls the previously instantiated resource
  921        * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method
  922        * with the new resource bundle, unless the previously instantiated resource
  923        * bundle already has a non-null parent.
  924        *
  925        * <p>
  926        * <code>getBundle</code> caches instantiated resource bundles and
  927        * may return the same resource bundle instance multiple
  928        * times.
  929        *
  930        * <p>
  931        * The <code>baseName</code> argument should be a fully qualified class name. However, for
  932        * compatibility with earlier versions, Sun's Java SE Runtime Environments do not verify this,
  933        * and so it is possible to access <code>PropertyResourceBundle</code>s by specifying a
  934        * path name (using "/") instead of a fully qualified class name (using ".").
  935        *
  936        * <p><a name="default_behavior_example"/>
  937        * <strong>Example:</strong><br>The following class and property files are provided:
  938        * <pre>
  939        *     MyResources.class
  940        *     MyResources.properties
  941        *     MyResources_fr.properties
  942        *     MyResources_fr_CH.class
  943        *     MyResources_fr_CH.properties
  944        *     MyResources_en.properties
  945        *     MyResources_es_ES.class
  946        * </pre>
  947        * The contents of all files are valid (that is, public non-abstract subclasses of <code>ResourceBundle</code> for
  948        * the ".class" files, syntactically correct ".properties" files).
  949        * The default locale is <code>Locale("en", "GB")</code>.
  950        * <p>
  951        * Calling <code>getBundle</code> with the shown locale argument values instantiates
  952        * resource bundles from the following sources:
  953        * <ul>
  954        * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class
  955        * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent MyResources.class
  956        * <li>Locale("de", "DE"): result MyResources_en.properties, parent MyResources.class
  957        * <li>Locale("en", "US"): result MyResources_en.properties, parent MyResources.class
  958        * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent MyResources.class
  959        * </ul>
  960        * <p>The file MyResources_fr_CH.properties is never used because it is hidden by
  961        * MyResources_fr_CH.class. Likewise, MyResources.properties is also hidden by
  962        * MyResources.class.
  963        *
  964        * @param baseName the base name of the resource bundle, a fully qualified class name
  965        * @param locale the locale for which a resource bundle is desired
  966        * @param loader the class loader from which to load the resource bundle
  967        * @return a resource bundle for the given base name and locale
  968        * @exception java.lang.NullPointerException
  969        *        if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
  970        * @exception MissingResourceException
  971        *        if no resource bundle for the specified base name can be found
  972        * @since 1.2
  973        */
  974       public static ResourceBundle getBundle(String baseName, Locale locale,
  975                                              ClassLoader loader)
  976       {
  977           if (loader == null) {
  978               throw new NullPointerException();
  979           }
  980           return getBundleImpl(baseName, locale, loader, Control.INSTANCE);
  981       }
  982   
  983       /**
  984        * Returns a resource bundle using the specified base name, target
  985        * locale, class loader and control. Unlike the {@linkplain
  986        * #getBundle(String, Locale, ClassLoader) <code>getBundle</code>
  987        * factory methods with no <code>control</code> argument}, the given
  988        * <code>control</code> specifies how to locate and instantiate resource
  989        * bundles. Conceptually, the bundle loading process with the given
  990        * <code>control</code> is performed in the following steps.
  991        *
  992        * <p>
  993        * <ol>
  994        * <li>This factory method looks up the resource bundle in the cache for
  995        * the specified <code>baseName</code>, <code>targetLocale</code> and
  996        * <code>loader</code>.  If the requested resource bundle instance is
  997        * found in the cache and the time-to-live periods of the instance and
  998        * all of its parent instances have not expired, the instance is returned
  999        * to the caller. Otherwise, this factory method proceeds with the
 1000        * loading process below.</li>
 1001        *
 1002        * <li>The {@link ResourceBundle.Control#getFormats(String)
 1003        * control.getFormats} method is called to get resource bundle formats
 1004        * to produce bundle or resource names. The strings
 1005        * <code>"java.class"</code> and <code>"java.properties"</code>
 1006        * designate class-based and {@linkplain PropertyResourceBundle
 1007        * property}-based resource bundles, respectively. Other strings
 1008        * starting with <code>"java."</code> are reserved for future extensions
 1009        * and must not be used for application-defined formats. Other strings
 1010        * designate application-defined formats.</li>
 1011        *
 1012        * <li>The {@link ResourceBundle.Control#getCandidateLocales(String,
 1013        * Locale) control.getCandidateLocales} method is called with the target
 1014        * locale to get a list of <em>candidate <code>Locale</code>s</em> for
 1015        * which resource bundles are searched.</li>
 1016        *
 1017        * <li>The {@link ResourceBundle.Control#newBundle(String, Locale,
 1018        * String, ClassLoader, boolean) control.newBundle} method is called to
 1019        * instantiate a <code>ResourceBundle</code> for the base bundle name, a
 1020        * candidate locale, and a format. (Refer to the note on the cache
 1021        * lookup below.) This step is iterated over all combinations of the
 1022        * candidate locales and formats until the <code>newBundle</code> method
 1023        * returns a <code>ResourceBundle</code> instance or the iteration has
 1024        * used up all the combinations. For example, if the candidate locales
 1025        * are <code>Locale("de", "DE")</code>, <code>Locale("de")</code> and
 1026        * <code>Locale("")</code> and the formats are <code>"java.class"</code>
 1027        * and <code>"java.properties"</code>, then the following is the
 1028        * sequence of locale-format combinations to be used to call
 1029        * <code>control.newBundle</code>.
 1030        *
 1031        * <table style="width: 50%; text-align: left; margin-left: 40px;"
 1032        *  border="0" cellpadding="2" cellspacing="2">
 1033        * <tbody><code>
 1034        * <tr>
 1035        * <td
 1036        * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">Locale<br>
 1037        * </td>
 1038        * <td
 1039        * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">format<br>
 1040        * </td>
 1041        * </tr>
 1042        * <tr>
 1043        * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")<br>
 1044        * </td>
 1045        * <td style="vertical-align: top; width: 50%;">java.class<br>
 1046        * </td>
 1047        * </tr>
 1048        * <tr>
 1049        * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")</td>
 1050        * <td style="vertical-align: top; width: 50%;">java.properties<br>
 1051        * </td>
 1052        * </tr>
 1053        * <tr>
 1054        * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
 1055        * <td style="vertical-align: top; width: 50%;">java.class</td>
 1056        * </tr>
 1057        * <tr>
 1058        * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
 1059        * <td style="vertical-align: top; width: 50%;">java.properties</td>
 1060        * </tr>
 1061        * <tr>
 1062        * <td style="vertical-align: top; width: 50%;">Locale("")<br>
 1063        * </td>
 1064        * <td style="vertical-align: top; width: 50%;">java.class</td>
 1065        * </tr>
 1066        * <tr>
 1067        * <td style="vertical-align: top; width: 50%;">Locale("")</td>
 1068        * <td style="vertical-align: top; width: 50%;">java.properties</td>
 1069        * </tr>
 1070        * </code></tbody>
 1071        * </table>
 1072        * </li>
 1073        *
 1074        * <li>If the previous step has found no resource bundle, proceed to
 1075        * Step 6. If a bundle has been found that is a base bundle (a bundle
 1076        * for <code>Locale("")</code>), and the candidate locale list only contained
 1077        * <code>Locale("")</code>, return the bundle to the caller. If a bundle
 1078        * has been found that is a base bundle, but the candidate locale list
 1079        * contained locales other than Locale(""), put the bundle on hold and
 1080        * proceed to Step 6. If a bundle has been found that is not a base
 1081        * bundle, proceed to Step 7.</li>
 1082        *
 1083        * <li>The {@link ResourceBundle.Control#getFallbackLocale(String,
 1084        * Locale) control.getFallbackLocale} method is called to get a fallback
 1085        * locale (alternative to the current target locale) to try further
 1086        * finding a resource bundle. If the method returns a non-null locale,
 1087        * it becomes the next target locale and the loading process starts over
 1088        * from Step 3. Otherwise, if a base bundle was found and put on hold in
 1089        * a previous Step 5, it is returned to the caller now. Otherwise, a
 1090        * MissingResourceException is thrown.</li>
 1091        *
 1092        * <li>At this point, we have found a resource bundle that's not the
 1093        * base bundle. If this bundle set its parent during its instantiation,
 1094        * it is returned to the caller. Otherwise, its <a
 1095        * href="./ResourceBundle.html#parent_chain">parent chain</a> is
 1096        * instantiated based on the list of candidate locales from which it was
 1097        * found. Finally, the bundle is returned to the caller.</li>
 1098        *
 1099        *
 1100        * </ol>
 1101        *
 1102        * <p>During the resource bundle loading process above, this factory
 1103        * method looks up the cache before calling the {@link
 1104        * Control#newBundle(String, Locale, String, ClassLoader, boolean)
 1105        * control.newBundle} method.  If the time-to-live period of the
 1106        * resource bundle found in the cache has expired, the factory method
 1107        * calls the {@link ResourceBundle.Control#needsReload(String, Locale,
 1108        * String, ClassLoader, ResourceBundle, long) control.needsReload}
 1109        * method to determine whether the resource bundle needs to be reloaded.
 1110        * If reloading is required, the factory method calls
 1111        * <code>control.newBundle</code> to reload the resource bundle.  If
 1112        * <code>control.newBundle</code> returns <code>null</code>, the factory
 1113        * method puts a dummy resource bundle in the cache as a mark of
 1114        * nonexistent resource bundles in order to avoid lookup overhead for
 1115        * subsequent requests. Such dummy resource bundles are under the same
 1116        * expiration control as specified by <code>control</code>.
 1117        *
 1118        * <p>All resource bundles loaded are cached by default. Refer to
 1119        * {@link Control#getTimeToLive(String,Locale)
 1120        * control.getTimeToLive} for details.
 1121        *
 1122        *
 1123        * <p>The following is an example of the bundle loading process with the
 1124        * default <code>ResourceBundle.Control</code> implementation.
 1125        *
 1126        * <p>Conditions:
 1127        * <ul>
 1128        * <li>Base bundle name: <code>foo.bar.Messages</code>
 1129        * <li>Requested <code>Locale</code>: {@link Locale#ITALY}</li>
 1130        * <li>Default <code>Locale</code>: {@link Locale#FRENCH}</li>
 1131        * <li>Available resource bundles:
 1132        * <code>foo/bar/Messages_fr.properties</code> and
 1133        * <code>foo/bar/Messages.properties</code></li>
 1134        *
 1135        * </ul>
 1136        *
 1137        * <p>First, <code>getBundle</code> tries loading a resource bundle in
 1138        * the following sequence.
 1139        *
 1140        * <ul>
 1141        * <li>class <code>foo.bar.Messages_it_IT</code>
 1142        * <li>file <code>foo/bar/Messages_it_IT.properties</code>
 1143        * <li>class <code>foo.bar.Messages_it</code></li>
 1144        * <li>file <code>foo/bar/Messages_it.properties</code></li>
 1145        * <li>class <code>foo.bar.Messages</code></li>
 1146        * <li>file <code>foo/bar/Messages.properties</code></li>
 1147        * </ul>
 1148        *
 1149        * <p>At this point, <code>getBundle</code> finds
 1150        * <code>foo/bar/Messages.properties</code>, which is put on hold
 1151        * because it's the base bundle.  <code>getBundle</code> calls {@link
 1152        * Control#getFallbackLocale(String, Locale)
 1153        * control.getFallbackLocale("foo.bar.Messages", Locale.ITALY)} which
 1154        * returns <code>Locale.FRENCH</code>. Next, <code>getBundle</code>
 1155        * tries loading a bundle in the following sequence.
 1156        *
 1157        * <ul>
 1158        * <li>class <code>foo.bar.Messages_fr</code></li>
 1159        * <li>file <code>foo/bar/Messages_fr.properties</code></li>
 1160        * <li>class <code>foo.bar.Messages</code></li>
 1161        * <li>file <code>foo/bar/Messages.properties</code></li>
 1162        * </ul>
 1163        *
 1164        * <p><code>getBundle</code> finds
 1165        * <code>foo/bar/Messages_fr.properties</code> and creates a
 1166        * <code>ResourceBundle</code> instance. Then, <code>getBundle</code>
 1167        * sets up its parent chain from the list of the candiate locales.  Only
 1168        * <code>foo/bar/Messages.properties</code> is found in the list and
 1169        * <code>getBundle</code> creates a <code>ResourceBundle</code> instance
 1170        * that becomes the parent of the instance for
 1171        * <code>foo/bar/Messages_fr.properties</code>.
 1172        *
 1173        * @param baseName
 1174        *        the base name of the resource bundle, a fully qualified
 1175        *        class name
 1176        * @param targetLocale
 1177        *        the locale for which a resource bundle is desired
 1178        * @param loader
 1179        *        the class loader from which to load the resource bundle
 1180        * @param control
 1181        *        the control which gives information for the resource
 1182        *        bundle loading process
 1183        * @return a resource bundle for the given base name and locale
 1184        * @exception NullPointerException
 1185        *        if <code>baseName</code>, <code>targetLocale</code>,
 1186        *        <code>loader</code>, or <code>control</code> is
 1187        *        <code>null</code>
 1188        * @exception MissingResourceException
 1189        *        if no resource bundle for the specified base name can be found
 1190        * @exception IllegalArgumentException
 1191        *        if the given <code>control</code> doesn't perform properly
 1192        *        (e.g., <code>control.getCandidateLocales</code> returns null.)
 1193        *        Note that validation of <code>control</code> is performed as
 1194        *        needed.
 1195        * @since 1.6
 1196        */
 1197       public static ResourceBundle getBundle(String baseName, Locale targetLocale,
 1198                                              ClassLoader loader, Control control) {
 1199           if (loader == null || control == null) {
 1200               throw new NullPointerException();
 1201           }
 1202           return getBundleImpl(baseName, targetLocale, loader, control);
 1203       }
 1204   
 1205       private static ResourceBundle getBundleImpl(String baseName, Locale locale,
 1206                                                   ClassLoader loader, Control control) {
 1207           if (locale == null || control == null) {
 1208               throw new NullPointerException();
 1209           }
 1210   
 1211           // We create a CacheKey here for use by this call. The base
 1212           // name and loader will never change during the bundle loading
 1213           // process. We have to make sure that the locale is set before
 1214           // using it as a cache key.
 1215           CacheKey cacheKey = new CacheKey(baseName, locale, loader);
 1216           ResourceBundle bundle = null;
 1217   
 1218           // Quick lookup of the cache.
 1219           BundleReference bundleRef = cacheList.get(cacheKey);
 1220           if (bundleRef != null) {
 1221               bundle = bundleRef.get();
 1222               bundleRef = null;
 1223           }
 1224   
 1225           // If this bundle and all of its parents are valid (not expired),
 1226           // then return this bundle. If any of the bundles is expired, we
 1227           // don't call control.needsReload here but instead drop into the
 1228           // complete loading process below.
 1229           if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
 1230               return bundle;
 1231           }
 1232   
 1233           // No valid bundle was found in the cache, so we need to load the
 1234           // resource bundle and its parents.
 1235   
 1236           boolean isKnownControl = (control == Control.INSTANCE) ||
 1237                                      (control instanceof SingleFormatControl);
 1238           List<String> formats = control.getFormats(baseName);
 1239           if (!isKnownControl && !checkList(formats)) {
 1240               throw new IllegalArgumentException("Invalid Control: getFormats");
 1241           }
 1242   
 1243           ResourceBundle baseBundle = null;
 1244           for (Locale targetLocale = locale;
 1245                targetLocale != null;
 1246                targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
 1247               List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
 1248               if (!isKnownControl && !checkList(candidateLocales)) {
 1249                   throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
 1250               }
 1251   
 1252               bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);
 1253   
 1254               // If the loaded bundle is the base bundle and exactly for the
 1255               // requested locale or the only candidate locale, then take the
 1256               // bundle as the resulting one. If the loaded bundle is the base
 1257               // bundle, it's put on hold until we finish processing all
 1258               // fallback locales.
 1259               if (isValidBundle(bundle)) {
 1260                   boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
 1261                   if (!isBaseBundle || bundle.locale.equals(locale)
 1262                       || (candidateLocales.size() == 1
 1263                           && bundle.locale.equals(candidateLocales.get(0)))) {
 1264                       break;
 1265                   }
 1266   
 1267                   // If the base bundle has been loaded, keep the reference in
 1268                   // baseBundle so that we can avoid any redundant loading in case
 1269                   // the control specify not to cache bundles.
 1270                   if (isBaseBundle && baseBundle == null) {
 1271                       baseBundle = bundle;
 1272                   }
 1273               }
 1274           }
 1275   
 1276           if (bundle == null) {
 1277               if (baseBundle == null) {
 1278                   throwMissingResourceException(baseName, locale, cacheKey.getCause());
 1279               }
 1280               bundle = baseBundle;
 1281           }
 1282   
 1283           return bundle;
 1284       }
 1285   
 1286       /**
 1287        * Checks if the given <code>List</code> is not null, not empty,
 1288        * not having null in its elements.
 1289        */
 1290       private static final boolean checkList(List a) {
 1291           boolean valid = (a != null && a.size() != 0);
 1292           if (valid) {
 1293               int size = a.size();
 1294               for (int i = 0; valid && i < size; i++) {
 1295                   valid = (a.get(i) != null);
 1296               }
 1297           }
 1298           return valid;
 1299       }
 1300   
 1301       private static final ResourceBundle findBundle(CacheKey cacheKey,
 1302                                                      List<Locale> candidateLocales,
 1303                                                      List<String> formats,
 1304                                                      int index,
 1305                                                      Control control,
 1306                                                      ResourceBundle baseBundle) {
 1307           Locale targetLocale = candidateLocales.get(index);
 1308           ResourceBundle parent = null;
 1309           if (index != candidateLocales.size() - 1) {
 1310               parent = findBundle(cacheKey, candidateLocales, formats, index + 1,
 1311                                   control, baseBundle);
 1312           } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
 1313               return baseBundle;
 1314           }
 1315   
 1316           // Before we do the real loading work, see whether we need to
 1317           // do some housekeeping: If references to class loaders or
 1318           // resource bundles have been nulled out, remove all related
 1319           // information from the cache.
 1320           Object ref;
 1321           while ((ref = referenceQueue.poll()) != null) {
 1322               cacheList.remove(((CacheKeyReference)ref).getCacheKey());
 1323           }
 1324   
 1325           // flag indicating the resource bundle has expired in the cache
 1326           boolean expiredBundle = false;
 1327   
 1328           // First, look up the cache to see if it's in the cache, without
 1329           // declaring beginLoading.
 1330           cacheKey.setLocale(targetLocale);
 1331           ResourceBundle bundle = findBundleInCache(cacheKey, control);
 1332           if (isValidBundle(bundle)) {
 1333               expiredBundle = bundle.expired;
 1334               if (!expiredBundle) {
 1335                   // If its parent is the one asked for by the candidate
 1336                   // locales (the runtime lookup path), we can take the cached
 1337                   // one. (If it's not identical, then we'd have to check the
 1338                   // parent's parents to be consistent with what's been
 1339                   // requested.)
 1340                   if (bundle.parent == parent) {
 1341                       return bundle;
 1342                   }
 1343                   // Otherwise, remove the cached one since we can't keep
 1344                   // the same bundles having different parents.
 1345                   BundleReference bundleRef = cacheList.get(cacheKey);
 1346                   if (bundleRef != null && bundleRef.get() == bundle) {
 1347                       cacheList.remove(cacheKey, bundleRef);
 1348                   }
 1349               }
 1350           }
 1351   
 1352           if (bundle != NONEXISTENT_BUNDLE) {
 1353               CacheKey constKey = (CacheKey) cacheKey.clone();
 1354   
 1355               try {
 1356                   // Try declaring loading. If beginLoading() returns true,
 1357                   // then we can proceed. Otherwise, we need to take a look
 1358                   // at the cache again to see if someone else has loaded
 1359                   // the bundle and put it in the cache while we've been
 1360                   // waiting for other loading work to complete.
 1361                   while (!beginLoading(constKey)) {
 1362                       bundle = findBundleInCache(cacheKey, control);
 1363                       if (bundle == null) {
 1364                           continue;
 1365                       }
 1366                       if (bundle == NONEXISTENT_BUNDLE) {
 1367                           // If the bundle is NONEXISTENT_BUNDLE, the bundle doesn't exist.
 1368                           return parent;
 1369                       }
 1370                       expiredBundle = bundle.expired;
 1371                       if (!expiredBundle) {
 1372                           if (bundle.parent == parent) {
 1373                               return bundle;
 1374                           }
 1375                           BundleReference bundleRef = cacheList.get(cacheKey);
 1376                           if (bundleRef != null && bundleRef.get() == bundle) {
 1377                               cacheList.remove(cacheKey, bundleRef);
 1378                           }
 1379                       }
 1380                   }
 1381   
 1382                   try {
 1383                       bundle = loadBundle(cacheKey, formats, control, expiredBundle);
 1384                       if (bundle != null) {
 1385                           if (bundle.parent == null) {
 1386                               bundle.setParent(parent);
 1387                           }
 1388                           bundle.locale = targetLocale;
 1389                           bundle = putBundleInCache(cacheKey, bundle, control);
 1390                           return bundle;
 1391                       }
 1392   
 1393                       // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
 1394                       // instance for the locale.
 1395                       putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control);
 1396                   } finally {
 1397                       endLoading(constKey);
 1398                   }
 1399               } finally {
 1400                   if (constKey.getCause() instanceof InterruptedException) {
 1401                       Thread.currentThread().interrupt();
 1402                   }
 1403               }
 1404           }
 1405           assert underConstruction.get(cacheKey) != Thread.currentThread();
 1406           return parent;
 1407       }
 1408   
 1409       private static final ResourceBundle loadBundle(CacheKey cacheKey,
 1410                                                      List<String> formats,
 1411                                                      Control control,
 1412                                                      boolean reload) {
 1413           assert underConstruction.get(cacheKey) == Thread.currentThread();
 1414   
 1415           // Here we actually load the bundle in the order of formats
 1416           // specified by the getFormats() value.
 1417           Locale targetLocale = cacheKey.getLocale();
 1418   
 1419           ResourceBundle bundle = null;
 1420           int size = formats.size();
 1421           for (int i = 0; i < size; i++) {
 1422               String format = formats.get(i);
 1423               try {
 1424                   bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
 1425                                              cacheKey.getLoader(), reload);
 1426               } catch (LinkageError error) {
 1427                   // We need to handle the LinkageError case due to
 1428                   // inconsistent case-sensitivity in ClassLoader.
 1429                   // See 6572242 for details.
 1430                   cacheKey.setCause(error);
 1431               } catch (Exception cause) {
 1432                   cacheKey.setCause(cause);
 1433               }
 1434               if (bundle != null) {
 1435                   // Set the format in the cache key so that it can be
 1436                   // used when calling needsReload later.
 1437                   cacheKey.setFormat(format);
 1438                   bundle.name = cacheKey.getName();
 1439                   bundle.locale = targetLocale;
 1440                   // Bundle provider might reuse instances. So we should make
 1441                   // sure to clear the expired flag here.
 1442                   bundle.expired = false;
 1443                   break;
 1444               }
 1445           }
 1446           assert underConstruction.get(cacheKey) == Thread.currentThread();
 1447   
 1448           return bundle;
 1449       }
 1450   
 1451       private static final boolean isValidBundle(ResourceBundle bundle) {
 1452           return bundle != null && bundle != NONEXISTENT_BUNDLE;
 1453       }
 1454   
 1455       /**
 1456        * Determines whether any of resource bundles in the parent chain,
 1457        * including the leaf, have expired.
 1458        */
 1459       private static final boolean hasValidParentChain(ResourceBundle bundle) {
 1460           long now = System.currentTimeMillis();
 1461           while (bundle != null) {
 1462               if (bundle.expired) {
 1463                   return false;
 1464               }
 1465               CacheKey key = bundle.cacheKey;
 1466               if (key != null) {
 1467                   long expirationTime = key.expirationTime;
 1468                   if (expirationTime >= 0 && expirationTime <= now) {
 1469                       return false;
 1470                   }
 1471               }
 1472               bundle = bundle.parent;
 1473           }
 1474           return true;
 1475       }
 1476   
 1477       /**
 1478        * Declares the beginning of actual resource bundle loading. This method
 1479        * returns true if the declaration is successful and the current thread has
 1480        * been put in underConstruction. If someone else has already begun
 1481        * loading, this method waits until that loading work is complete and
 1482        * returns false.
 1483        */
 1484       private static final boolean beginLoading(CacheKey constKey) {
 1485           Thread me = Thread.currentThread();
 1486           Thread worker;
 1487           // We need to declare by putting the current Thread (me) to
 1488           // underConstruction that we are working on loading the specified
 1489           // resource bundle. If we are already working the loading, it means
 1490           // that the resource loading requires a recursive call. In that case,
 1491           // we have to proceed. (4300693)
 1492           if (((worker = underConstruction.putIfAbsent(constKey, me)) == null)
 1493               || worker == me) {
 1494               return true;
 1495           }
 1496   
 1497           // If someone else is working on the loading, wait until
 1498           // the Thread finishes the bundle loading.
 1499           synchronized (worker) {
 1500               while (underConstruction.get(constKey) == worker) {
 1501                   try {
 1502                       worker.wait();
 1503                   } catch (InterruptedException e) {
 1504                       // record the interruption
 1505                       constKey.setCause(e);
 1506                   }
 1507               }
 1508           }
 1509           return false;
 1510       }
 1511   
 1512       /**
 1513        * Declares the end of the bundle loading. This method calls notifyAll
 1514        * for those who are waiting for this completion.
 1515        */
 1516       private static final void endLoading(CacheKey constKey) {
 1517           // Remove this Thread from the underConstruction map and wake up
 1518           // those who have been waiting for me to complete this bundle
 1519           // loading.
 1520           Thread me = Thread.currentThread();
 1521           assert (underConstruction.get(constKey) == me);
 1522           underConstruction.remove(constKey);
 1523           synchronized (me) {
 1524               me.notifyAll();
 1525           }
 1526       }
 1527   
 1528       /**
 1529        * Throw a MissingResourceException with proper message
 1530        */
 1531       private static final void throwMissingResourceException(String baseName,
 1532                                                               Locale locale,
 1533                                                               Throwable cause) {
 1534           // If the cause is a MissingResourceException, avoid creating
 1535           // a long chain. (6355009)
 1536           if (cause instanceof MissingResourceException) {
 1537               cause = null;
 1538           }
 1539           throw new MissingResourceException("Can't find bundle for base name "
 1540                                              + baseName + ", locale " + locale,
 1541                                              baseName + "_" + locale, // className
 1542                                              "",                      // key
 1543                                              cause);
 1544       }
 1545   
 1546       /**
 1547        * Finds a bundle in the cache. Any expired bundles are marked as
 1548        * `expired' and removed from the cache upon return.
 1549        *
 1550        * @param cacheKey the key to look up the cache
 1551        * @param control the Control to be used for the expiration control
 1552        * @return the cached bundle, or null if the bundle is not found in the
 1553        * cache or its parent has expired. <code>bundle.expire</code> is true
 1554        * upon return if the bundle in the cache has expired.
 1555        */
 1556       private static final ResourceBundle findBundleInCache(CacheKey cacheKey,
 1557                                                             Control control) {
 1558           BundleReference bundleRef = cacheList.get(cacheKey);
 1559           if (bundleRef == null) {
 1560               return null;
 1561           }
 1562           ResourceBundle bundle = bundleRef.get();
 1563           if (bundle == null) {
 1564               return null;
 1565           }
 1566           ResourceBundle p = bundle.parent;
 1567           assert p != NONEXISTENT_BUNDLE;
 1568           // If the parent has expired, then this one must also expire. We
 1569           // check only the immediate parent because the actual loading is
 1570           // done from the root (base) to leaf (child) and the purpose of
 1571           // checking is to propagate expiration towards the leaf. For
 1572           // example, if the requested locale is ja_JP_JP and there are
 1573           // bundles for all of the candidates in the cache, we have a list,
 1574           //
 1575           // base <- ja <- ja_JP <- ja_JP_JP
 1576           //
 1577           // If ja has expired, then it will reload ja and the list becomes a
 1578           // tree.
 1579           //
 1580           // base <- ja (new)
 1581           //  "   <- ja (expired) <- ja_JP <- ja_JP_JP
 1582           //
 1583           // When looking up ja_JP in the cache, it finds ja_JP in the cache
 1584           // which references to the expired ja. Then, ja_JP is marked as
 1585           // expired and removed from the cache. This will be propagated to
 1586           // ja_JP_JP.
 1587           //
 1588           // Now, it's possible, for example, that while loading new ja_JP,
 1589           // someone else has started loading the same bundle and finds the
 1590           // base bundle has expired. Then, what we get from the first
 1591           // getBundle call includes the expired base bundle. However, if
 1592           // someone else didn't start its loading, we wouldn't know if the
 1593           // base bundle has expired at the end of the loading process. The
 1594           // expiration control doesn't guarantee that the returned bundle and
 1595           // its parents haven't expired.
 1596           //
 1597           // We could check the entire parent chain to see if there's any in
 1598           // the chain that has expired. But this process may never end. An
 1599           // extreme case would be that getTimeToLive returns 0 and
 1600           // needsReload always returns true.
 1601           if (p != null && p.expired) {
 1602               assert bundle != NONEXISTENT_BUNDLE;
 1603               bundle.expired = true;
 1604               bundle.cacheKey = null;
 1605               cacheList.remove(cacheKey, bundleRef);
 1606               bundle = null;
 1607           } else {
 1608               CacheKey key = bundleRef.getCacheKey();
 1609               long expirationTime = key.expirationTime;
 1610               if (!bundle.expired && expirationTime >= 0 &&
 1611                   expirationTime <= System.currentTimeMillis()) {
 1612                   // its TTL period has expired.
 1613                   if (bundle != NONEXISTENT_BUNDLE) {
 1614                       // Synchronize here to call needsReload to avoid
 1615                       // redundant concurrent calls for the same bundle.
 1616                       synchronized (bundle) {
 1617                           expirationTime = key.expirationTime;
 1618                           if (!bundle.expired && expirationTime >= 0 &&
 1619                               expirationTime <= System.currentTimeMillis()) {
 1620                               try {
 1621                                   bundle.expired = control.needsReload(key.getName(),
 1622                                                                        key.getLocale(),
 1623                                                                        key.getFormat(),
 1624                                                                        key.getLoader(),
 1625                                                                        bundle,
 1626                                                                        key.loadTime);
 1627                               } catch (Exception e) {
 1628                                   cacheKey.setCause(e);
 1629                               }
 1630                               if (bundle.expired) {
 1631                                   // If the bundle needs to be reloaded, then
 1632                                   // remove the bundle from the cache, but
 1633                                   // return the bundle with the expired flag
 1634                                   // on.
 1635                                   bundle.cacheKey = null;
 1636                                   cacheList.remove(cacheKey, bundleRef);
 1637                               } else {
 1638                                   // Update the expiration control info. and reuse
 1639                                   // the same bundle instance
 1640                                   setExpirationTime(key, control);
 1641                               }
 1642                           }
 1643                       }
 1644                   } else {
 1645                       // We just remove NONEXISTENT_BUNDLE from the cache.
 1646                       cacheList.remove(cacheKey, bundleRef);
 1647                       bundle = null;
 1648                   }
 1649               }
 1650           }
 1651           return bundle;
 1652       }
 1653   
 1654       /**
 1655        * Put a new bundle in the cache.
 1656        *
 1657        * @param cacheKey the key for the resource bundle
 1658        * @param bundle the resource bundle to be put in the cache
 1659        * @return the ResourceBundle for the cacheKey; if someone has put
 1660        * the bundle before this call, the one found in the cache is
 1661        * returned.
 1662        */
 1663       private static final ResourceBundle putBundleInCache(CacheKey cacheKey,
 1664                                                            ResourceBundle bundle,
 1665                                                            Control control) {
 1666           setExpirationTime(cacheKey, control);
 1667           if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) {
 1668               CacheKey key = (CacheKey) cacheKey.clone();
 1669               BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key);
 1670               bundle.cacheKey = key;
 1671   
 1672               // Put the bundle in the cache if it's not been in the cache.
 1673               BundleReference result = cacheList.putIfAbsent(key, bundleRef);
 1674   
 1675               // If someone else has put the same bundle in the cache before
 1676               // us and it has not expired, we should use the one in the cache.
 1677               if (result != null) {
 1678                   ResourceBundle rb = result.get();
 1679                   if (rb != null && !rb.expired) {
 1680                       // Clear the back link to the cache key
 1681                       bundle.cacheKey = null;
 1682                       bundle = rb;
 1683                       // Clear the reference in the BundleReference so that
 1684                       // it won't be enqueued.
 1685                       bundleRef.clear();
 1686                   } else {
 1687                       // Replace the invalid (garbage collected or expired)
 1688                       // instance with the valid one.
 1689                       cacheList.put(key, bundleRef);
 1690                   }
 1691               }
 1692           }
 1693           return bundle;
 1694       }
 1695   
 1696       private static final void setExpirationTime(CacheKey cacheKey, Control control) {
 1697           long ttl = control.getTimeToLive(cacheKey.getName(),
 1698                                            cacheKey.getLocale());
 1699           if (ttl >= 0) {
 1700               // If any expiration time is specified, set the time to be
 1701               // expired in the cache.
 1702               long now = System.currentTimeMillis();
 1703               cacheKey.loadTime = now;
 1704               cacheKey.expirationTime = now + ttl;
 1705           } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
 1706               cacheKey.expirationTime = ttl;
 1707           } else {
 1708               throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
 1709           }
 1710       }
 1711   
 1712       /**
 1713        * Removes all resource bundles from the cache that have been loaded
 1714        * using the caller's class loader.
 1715        *
 1716        * @since 1.6
 1717        * @see ResourceBundle.Control#getTimeToLive(String,Locale)
 1718        */
 1719       public static final void clearCache() {
 1720           clearCache(getLoader());
 1721       }
 1722   
 1723       /**
 1724        * Removes all resource bundles from the cache that have been loaded
 1725        * using the given class loader.
 1726        *
 1727        * @param loader the class loader
 1728        * @exception NullPointerException if <code>loader</code> is null
 1729        * @since 1.6
 1730        * @see ResourceBundle.Control#getTimeToLive(String,Locale)
 1731        */
 1732       public static final void clearCache(ClassLoader loader) {
 1733           if (loader == null) {
 1734               throw new NullPointerException();
 1735           }
 1736           Set<CacheKey> set = cacheList.keySet();
 1737           for (CacheKey key : set) {
 1738               if (key.getLoader() == loader) {
 1739                   set.remove(key);
 1740               }
 1741           }
 1742       }
 1743   
 1744       /**
 1745        * Gets an object for the given key from this resource bundle.
 1746        * Returns null if this resource bundle does not contain an
 1747        * object for the given key.
 1748        *
 1749        * @param key the key for the desired object
 1750        * @exception NullPointerException if <code>key</code> is <code>null</code>
 1751        * @return the object for the given key, or null
 1752        */
 1753       protected abstract Object handleGetObject(String key);
 1754   
 1755       /**
 1756        * Returns an enumeration of the keys.
 1757        *
 1758        * @return an <code>Enumeration</code> of the keys contained in
 1759        *         this <code>ResourceBundle</code> and its parent bundles.
 1760        */
 1761       public abstract Enumeration<String> getKeys();
 1762   
 1763       /**
 1764        * Determines whether the given <code>key</code> is contained in
 1765        * this <code>ResourceBundle</code> or its parent bundles.
 1766        *
 1767        * @param key
 1768        *        the resource <code>key</code>
 1769        * @return <code>true</code> if the given <code>key</code> is
 1770        *        contained in this <code>ResourceBundle</code> or its
 1771        *        parent bundles; <code>false</code> otherwise.
 1772        * @exception NullPointerException
 1773        *         if <code>key</code> is <code>null</code>
 1774        * @since 1.6
 1775        */
 1776       public boolean containsKey(String key) {
 1777           if (key == null) {
 1778               throw new NullPointerException();
 1779           }
 1780           for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
 1781               if (rb.handleKeySet().contains(key)) {
 1782                   return true;
 1783               }
 1784           }
 1785           return false;
 1786       }
 1787   
 1788       /**
 1789        * Returns a <code>Set</code> of all keys contained in this
 1790        * <code>ResourceBundle</code> and its parent bundles.
 1791        *
 1792        * @return a <code>Set</code> of all keys contained in this
 1793        *         <code>ResourceBundle</code> and its parent bundles.
 1794        * @since 1.6
 1795        */
 1796       public Set<String> keySet() {
 1797           Set<String> keys = new HashSet<String>();
 1798           for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
 1799               keys.addAll(rb.handleKeySet());
 1800           }
 1801           return keys;
 1802       }
 1803   
 1804       /**
 1805        * Returns a <code>Set</code> of the keys contained <em>only</em>
 1806        * in this <code>ResourceBundle</code>.
 1807        *
 1808        * <p>The default implementation returns a <code>Set</code> of the
 1809        * keys returned by the {@link #getKeys() getKeys} method except
 1810        * for the ones for which the {@link #handleGetObject(String)
 1811        * handleGetObject} method returns <code>null</code>. Once the
 1812        * <code>Set</code> has been created, the value is kept in this
 1813        * <code>ResourceBundle</code> in order to avoid producing the
 1814        * same <code>Set</code> in the next calls.  Override this method
 1815        * in subclass implementations for faster handling.
 1816        *
 1817        * @return a <code>Set</code> of the keys contained only in this
 1818        *        <code>ResourceBundle</code>
 1819        * @since 1.6
 1820        */
 1821       protected Set<String> handleKeySet() {
 1822           if (keySet == null) {
 1823               synchronized (this) {
 1824                   if (keySet == null) {
 1825                       Set<String> keys = new HashSet<String>();
 1826                       Enumeration<String> enumKeys = getKeys();
 1827                       while (enumKeys.hasMoreElements()) {
 1828                           String key = enumKeys.nextElement();
 1829                           if (handleGetObject(key) != null) {
 1830                               keys.add(key);
 1831                           }
 1832                       }
 1833                       keySet = keys;
 1834                   }
 1835               }
 1836           }
 1837           return keySet;
 1838       }
 1839   
 1840   
 1841   
 1842       /**
 1843        * <code>ResourceBundle.Control</code> defines a set of callback methods
 1844        * that are invoked by the {@link ResourceBundle#getBundle(String,
 1845        * Locale, ClassLoader, Control) ResourceBundle.getBundle} factory
 1846        * methods during the bundle loading process. In other words, a
 1847        * <code>ResourceBundle.Control</code> collaborates with the factory
 1848        * methods for loading resource bundles. The default implementation of
 1849        * the callback methods provides the information necessary for the
 1850        * factory methods to perform the <a
 1851        * href="./ResourceBundle.html#default_behavior">default behavior</a>.
 1852        *
 1853        * <p>In addition to the callback methods, the {@link
 1854        * #toBundleName(String, Locale) toBundleName} and {@link
 1855        * #toResourceName(String, String) toResourceName} methods are defined
 1856        * primarily for convenience in implementing the callback
 1857        * methods. However, the <code>toBundleName</code> method could be
 1858        * overridden to provide different conventions in the organization and
 1859        * packaging of localized resources.  The <code>toResourceName</code>
 1860        * method is <code>final</code> to avoid use of wrong resource and class
 1861        * name separators.
 1862        *
 1863        * <p>Two factory methods, {@link #getControl(List)} and {@link
 1864        * #getNoFallbackControl(List)}, provide
 1865        * <code>ResourceBundle.Control</code> instances that implement common
 1866        * variations of the default bundle loading process.
 1867        *
 1868        * <p>The formats returned by the {@link Control#getFormats(String)
 1869        * getFormats} method and candidate locales returned by the {@link
 1870        * ResourceBundle.Control#getCandidateLocales(String, Locale)
 1871        * getCandidateLocales} method must be consistent in all
 1872        * <code>ResourceBundle.getBundle</code> invocations for the same base
 1873        * bundle. Otherwise, the <code>ResourceBundle.getBundle</code> methods
 1874        * may return unintended bundles. For example, if only
 1875        * <code>"java.class"</code> is returned by the <code>getFormats</code>
 1876        * method for the first call to <code>ResourceBundle.getBundle</code>
 1877        * and only <code>"java.properties"</code> for the second call, then the
 1878        * second call will return the class-based one that has been cached
 1879        * during the first call.
 1880        *
 1881        * <p>A <code>ResourceBundle.Control</code> instance must be thread-safe
 1882        * if it's simultaneously used by multiple threads.
 1883        * <code>ResourceBundle.getBundle</code> does not synchronize to call
 1884        * the <code>ResourceBundle.Control</code> methods. The default
 1885        * implementations of the methods are thread-safe.
 1886        *
 1887        * <p>Applications can specify <code>ResourceBundle.Control</code>
 1888        * instances returned by the <code>getControl</code> factory methods or
 1889        * created from a subclass of <code>ResourceBundle.Control</code> to
 1890        * customize the bundle loading process. The following are examples of
 1891        * changing the default bundle loading process.
 1892        *
 1893        * <p><b>Example 1</b>
 1894        *
 1895        * <p>The following code lets <code>ResourceBundle.getBundle</code> look
 1896        * up only properties-based resources.
 1897        *
 1898        * <pre>
 1899        * import java.util.*;
 1900        * import static java.util.ResourceBundle.Control.*;
 1901        * ...
 1902        * ResourceBundle bundle =
 1903        *   ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"),
 1904        *                            ResourceBundle.Control.getControl(FORMAT_PROPERTIES));
 1905        * </pre>
 1906        *
 1907        * Given the resource bundles in the <a
 1908        * href="./ResourceBundle.html#default_behavior_example">example</a> in
 1909        * the <code>ResourceBundle.getBundle</code> description, this
 1910        * <code>ResourceBundle.getBundle</code> call loads
 1911        * <code>MyResources_fr_CH.properties</code> whose parent is
 1912        * <code>MyResources_fr.properties</code> whose parent is
 1913        * <code>MyResources.properties</code>. (<code>MyResources_fr_CH.properties</code>
 1914        * is not hidden, but <code>MyResources_fr_CH.class</code> is.)
 1915        *
 1916        * <p><b>Example 2</b>
 1917        *
 1918        * <p>The following is an example of loading XML-based bundles
 1919        * using {@link Properties#loadFromXML(java.io.InputStream)
 1920        * Properties.loadFromXML}.
 1921        *
 1922        * <pre>
 1923        * ResourceBundle rb = ResourceBundle.getBundle("Messages",
 1924        *     new ResourceBundle.Control() {
 1925        *         public List&lt;String&gt; getFormats(String baseName) {
 1926        *             if (baseName == null)
 1927        *                 throw new NullPointerException();
 1928        *             return Arrays.asList("xml");
 1929        *         }
 1930        *         public ResourceBundle newBundle(String baseName,
 1931        *                                         Locale locale,
 1932        *                                         String format,
 1933        *                                         ClassLoader loader,
 1934        *                                         boolean reload)
 1935        *                          throws IllegalAccessException,
 1936        *                                 InstantiationException,
 1937        *                                 IOException {
 1938        *             if (baseName == null || locale == null
 1939        *                   || format == null || loader == null)
 1940        *                 throw new NullPointerException();
 1941        *             ResourceBundle bundle = null;
 1942        *             if (format.equals("xml")) {
 1943        *                 String bundleName = toBundleName(baseName, locale);
 1944        *                 String resourceName = toResourceName(bundleName, format);
 1945        *                 InputStream stream = null;
 1946        *                 if (reload) {
 1947        *                     URL url = loader.getResource(resourceName);
 1948        *                     if (url != null) {
 1949        *                         URLConnection connection = url.openConnection();
 1950        *                         if (connection != null) {
 1951        *                             // Disable caches to get fresh data for
 1952        *                             // reloading.
 1953        *                             connection.setUseCaches(false);
 1954        *                             stream = connection.getInputStream();
 1955        *                         }
 1956        *                     }
 1957        *                 } else {
 1958        *                     stream = loader.getResourceAsStream(resourceName);
 1959        *                 }
 1960        *                 if (stream != null) {
 1961        *                     BufferedInputStream bis = new BufferedInputStream(stream);
 1962        *                     bundle = new XMLResourceBundle(bis);
 1963        *                     bis.close();
 1964        *                 }
 1965        *             }
 1966        *             return bundle;
 1967        *         }
 1968        *     });
 1969        *
 1970        * ...
 1971        *
 1972        * private static class XMLResourceBundle extends ResourceBundle {
 1973        *     private Properties props;
 1974        *     XMLResourceBundle(InputStream stream) throws IOException {
 1975        *         props = new Properties();
 1976        *         props.loadFromXML(stream);
 1977        *     }
 1978        *     protected Object handleGetObject(String key) {
 1979        *         return props.getProperty(key);
 1980        *     }
 1981        *     public Enumeration&lt;String&gt; getKeys() {
 1982        *         ...
 1983        *     }
 1984        * }
 1985        * </pre>
 1986        *
 1987        * @since 1.6
 1988        */
 1989       public static class Control {
 1990           /**
 1991            * The default format <code>List</code>, which contains the strings
 1992            * <code>"java.class"</code> and <code>"java.properties"</code>, in
 1993            * this order. This <code>List</code> is {@linkplain
 1994            * Collections#unmodifiableList(List) unmodifiable}.
 1995            *
 1996            * @see #getFormats(String)
 1997            */
 1998           public static final List<String> FORMAT_DEFAULT
 1999               = Collections.unmodifiableList(Arrays.asList("java.class",
 2000                                                            "java.properties"));
 2001   
 2002           /**
 2003            * The class-only format <code>List</code> containing
 2004            * <code>"java.class"</code>. This <code>List</code> is {@linkplain
 2005            * Collections#unmodifiableList(List) unmodifiable}.
 2006            *
 2007            * @see #getFormats(String)
 2008            */
 2009           public static final List<String> FORMAT_CLASS
 2010               = Collections.unmodifiableList(Arrays.asList("java.class"));
 2011   
 2012           /**
 2013            * The properties-only format <code>List</code> containing
 2014            * <code>"java.properties"</code>. This <code>List</code> is
 2015            * {@linkplain Collections#unmodifiableList(List) unmodifiable}.
 2016            *
 2017            * @see #getFormats(String)
 2018            */
 2019           public static final List<String> FORMAT_PROPERTIES
 2020               = Collections.unmodifiableList(Arrays.asList("java.properties"));
 2021   
 2022           /**
 2023            * The time-to-live constant for not caching loaded resource bundle
 2024            * instances.
 2025            *
 2026            * @see #getTimeToLive(String, Locale)
 2027            */
 2028           public static final long TTL_DONT_CACHE = -1;
 2029   
 2030           /**
 2031            * The time-to-live constant for disabling the expiration control
 2032            * for loaded resource bundle instances in the cache.
 2033            *
 2034            * @see #getTimeToLive(String, Locale)
 2035            */
 2036           public static final long TTL_NO_EXPIRATION_CONTROL = -2;
 2037   
 2038           private static final Control INSTANCE = new Control();
 2039   
 2040           /**
 2041            * Sole constructor. (For invocation by subclass constructors,
 2042            * typically implicit.)
 2043            */
 2044           protected Control() {
 2045           }
 2046   
 2047           /**
 2048            * Returns a <code>ResourceBundle.Control</code> in which the {@link
 2049            * #getFormats(String) getFormats} method returns the specified
 2050            * <code>formats</code>. The <code>formats</code> must be equal to
 2051            * one of {@link Control#FORMAT_PROPERTIES}, {@link
 2052            * Control#FORMAT_CLASS} or {@link
 2053            * Control#FORMAT_DEFAULT}. <code>ResourceBundle.Control</code>
 2054            * instances returned by this method are singletons and thread-safe.
 2055            *
 2056            * <p>Specifying {@link Control#FORMAT_DEFAULT} is equivalent to
 2057            * instantiating the <code>ResourceBundle.Control</code> class,
 2058            * except that this method returns a singleton.
 2059            *
 2060            * @param formats
 2061            *        the formats to be returned by the
 2062            *        <code>ResourceBundle.Control.getFormats</code> method
 2063            * @return a <code>ResourceBundle.Control</code> supporting the
 2064            *        specified <code>formats</code>
 2065            * @exception NullPointerException
 2066            *        if <code>formats</code> is <code>null</code>
 2067            * @exception IllegalArgumentException
 2068            *        if <code>formats</code> is unknown
 2069            */
 2070           public static final Control getControl(List<String> formats) {
 2071               if (formats.equals(Control.FORMAT_PROPERTIES)) {
 2072                   return SingleFormatControl.PROPERTIES_ONLY;
 2073               }
 2074               if (formats.equals(Control.FORMAT_CLASS)) {
 2075                   return SingleFormatControl.CLASS_ONLY;
 2076               }
 2077               if (formats.equals(Control.FORMAT_DEFAULT)) {
 2078                   return Control.INSTANCE;
 2079               }
 2080               throw new IllegalArgumentException();
 2081           }
 2082   
 2083           /**
 2084            * Returns a <code>ResourceBundle.Control</code> in which the {@link
 2085            * #getFormats(String) getFormats} method returns the specified
 2086            * <code>formats</code> and the {@link
 2087            * Control#getFallbackLocale(String, Locale) getFallbackLocale}
 2088            * method returns <code>null</code>. The <code>formats</code> must
 2089            * be equal to one of {@link Control#FORMAT_PROPERTIES}, {@link
 2090            * Control#FORMAT_CLASS} or {@link Control#FORMAT_DEFAULT}.
 2091            * <code>ResourceBundle.Control</code> instances returned by this
 2092            * method are singletons and thread-safe.
 2093            *
 2094            * @param formats
 2095            *        the formats to be returned by the
 2096            *        <code>ResourceBundle.Control.getFormats</code> method
 2097            * @return a <code>ResourceBundle.Control</code> supporting the
 2098            *        specified <code>formats</code> with no fallback
 2099            *        <code>Locale</code> support
 2100            * @exception NullPointerException
 2101            *        if <code>formats</code> is <code>null</code>
 2102            * @exception IllegalArgumentException
 2103            *        if <code>formats</code> is unknown
 2104            */
 2105           public static final Control getNoFallbackContro