Home » openjdk-7 » java » util » [javadoc | source]

    1   /*
    2    * Copyright (c) 1996, 2011, Oracle and/or its affiliates. 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.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * 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   import sun.util.locale.BaseLocale;
   60   import sun.util.locale.LocaleObjectCache;
   61   
   62   
   63   /**
   64    *
   65    * Resource bundles contain locale-specific objects.  When your program needs a
   66    * locale-specific resource, a <code>String</code> for example, your program can
   67    * load it from the resource bundle that is appropriate for the current user's
   68    * locale. In this way, you can write program code that is largely independent
   69    * of the user's locale isolating most, if not all, of the locale-specific
   70    * information in resource bundles.
   71    *
   72    * <p>
   73    * This allows you to write programs that can:
   74    * <UL type=SQUARE>
   75    * <LI> be easily localized, or translated, into different languages
   76    * <LI> handle multiple locales at once
   77    * <LI> be easily modified later to support even more locales
   78    * </UL>
   79    *
   80    * <P>
   81    * Resource bundles belong to families whose members share a common base
   82    * name, but whose names also have additional components that identify
   83    * their locales. For example, the base name of a family of resource
   84    * bundles might be "MyResources". The family should have a default
   85    * resource bundle which simply has the same name as its family -
   86    * "MyResources" - and will be used as the bundle of last resort if a
   87    * specific locale is not supported. The family can then provide as
   88    * many locale-specific members as needed, for example a German one
   89    * named "MyResources_de".
   90    *
   91    * <P>
   92    * Each resource bundle in a family contains the same items, but the items have
   93    * been translated for the locale represented by that resource bundle.
   94    * For example, both "MyResources" and "MyResources_de" may have a
   95    * <code>String</code> that's used on a button for canceling operations.
   96    * In "MyResources" the <code>String</code> may contain "Cancel" and in
   97    * "MyResources_de" it may contain "Abbrechen".
   98    *
   99    * <P>
  100    * If there are different resources for different countries, you
  101    * can make specializations: for example, "MyResources_de_CH" contains objects for
  102    * the German language (de) in Switzerland (CH). If you want to only
  103    * modify some of the resources
  104    * in the specialization, you can do so.
  105    *
  106    * <P>
  107    * When your program needs a locale-specific object, it loads
  108    * the <code>ResourceBundle</code> class using the
  109    * {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
  110    * method:
  111    * <blockquote>
  112    * <pre>
  113    * ResourceBundle myResources =
  114    *      ResourceBundle.getBundle("MyResources", currentLocale);
  115    * </pre>
  116    * </blockquote>
  117    *
  118    * <P>
  119    * Resource bundles contain key/value pairs. The keys uniquely
  120    * identify a locale-specific object in the bundle. Here's an
  121    * example of a <code>ListResourceBundle</code> that contains
  122    * two key/value pairs:
  123    * <blockquote>
  124    * <pre>
  125    * public class MyResources extends ListResourceBundle {
  126    *     protected Object[][] getContents() {
  127    *         return new Object[][] {
  128    *             // LOCALIZE THE SECOND STRING OF EACH ARRAY (e.g., "OK")
  129    *             {"OkKey", "OK"},
  130    *             {"CancelKey", "Cancel"},
  131    *             // END OF MATERIAL TO LOCALIZE
  132    *        };
  133    *     }
  134    * }
  135    * </pre>
  136    * </blockquote>
  137    * Keys are always <code>String</code>s.
  138    * In this example, the keys are "OkKey" and "CancelKey".
  139    * In the above example, the values
  140    * are also <code>String</code>s--"OK" and "Cancel"--but
  141    * they don't have to be. The values can be any type of object.
  142    *
  143    * <P>
  144    * You retrieve an object from resource bundle using the appropriate
  145    * getter method. Because "OkKey" and "CancelKey"
  146    * are both strings, you would use <code>getString</code> to retrieve them:
  147    * <blockquote>
  148    * <pre>
  149    * button1 = new Button(myResources.getString("OkKey"));
  150    * button2 = new Button(myResources.getString("CancelKey"));
  151    * </pre>
  152    * </blockquote>
  153    * The getter methods all require the key as an argument and return
  154    * the object if found. If the object is not found, the getter method
  155    * throws a <code>MissingResourceException</code>.
  156    *
  157    * <P>
  158    * Besides <code>getString</code>, <code>ResourceBundle</code> also provides
  159    * a method for getting string arrays, <code>getStringArray</code>,
  160    * as well as a generic <code>getObject</code> method for any other
  161    * type of object. When using <code>getObject</code>, you'll
  162    * have to cast the result to the appropriate type. For example:
  163    * <blockquote>
  164    * <pre>
  165    * int[] myIntegers = (int[]) myResources.getObject("intList");
  166    * </pre>
  167    * </blockquote>
  168    *
  169    * <P>
  170    * The Java Platform provides two subclasses of <code>ResourceBundle</code>,
  171    * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
  172    * that provide a fairly simple way to create resources.
  173    * As you saw briefly in a previous example, <code>ListResourceBundle</code>
  174    * manages its resource as a list of key/value pairs.
  175    * <code>PropertyResourceBundle</code> uses a properties file to manage
  176    * its resources.
  177    *
  178    * <p>
  179    * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
  180    * do not suit your needs, you can write your own <code>ResourceBundle</code>
  181    * subclass.  Your subclasses must override two methods: <code>handleGetObject</code>
  182    * and <code>getKeys()</code>.
  183    *
  184    * <h4>ResourceBundle.Control</h4>
  185    *
  186    * The {@link ResourceBundle.Control} class provides information necessary
  187    * to perform the bundle loading process by the <code>getBundle</code>
  188    * factory methods that take a <code>ResourceBundle.Control</code>
  189    * instance. You can implement your own subclass in order to enable
  190    * non-standard resource bundle formats, change the search strategy, or
  191    * define caching parameters. Refer to the descriptions of the class and the
  192    * {@link #getBundle(String, Locale, ClassLoader, Control) getBundle}
  193    * factory method for details.
  194    *
  195    * <h4>Cache Management</h4>
  196    *
  197    * Resource bundle instances created by the <code>getBundle</code> factory
  198    * methods are cached by default, and the factory methods return the same
  199    * resource bundle instance multiple times if it has been
  200    * cached. <code>getBundle</code> clients may clear the cache, manage the
  201    * lifetime of cached resource bundle instances using time-to-live values,
  202    * or specify not to cache resource bundle instances. Refer to the
  203    * descriptions of the {@linkplain #getBundle(String, Locale, ClassLoader,
  204    * Control) <code>getBundle</code> factory method}, {@link
  205    * #clearCache(ClassLoader) clearCache}, {@link
  206    * Control#getTimeToLive(String, Locale)
  207    * ResourceBundle.Control.getTimeToLive}, and {@link
  208    * Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle,
  209    * long) ResourceBundle.Control.needsReload} for details.
  210    *
  211    * <h4>Example</h4>
  212    *
  213    * The following is a very simple example of a <code>ResourceBundle</code>
  214    * subclass, <code>MyResources</code>, that manages two resources (for a larger number of
  215    * resources you would probably use a <code>Map</code>).
  216    * Notice that you don't need to supply a value if
  217    * a "parent-level" <code>ResourceBundle</code> handles the same
  218    * key with the same value (as for the okKey below).
  219    * <blockquote>
  220    * <pre>
  221    * // default (English language, United States)
  222    * public class MyResources extends ResourceBundle {
  223    *     public Object handleGetObject(String key) {
  224    *         if (key.equals("okKey")) return "Ok";
  225    *         if (key.equals("cancelKey")) return "Cancel";
  226    *         return null;
  227    *     }
  228    *
  229    *     public Enumeration&lt;String&gt; getKeys() {
  230    *         return Collections.enumeration(keySet());
  231    *     }
  232    *
  233    *     // Overrides handleKeySet() so that the getKeys() implementation
  234    *     // can rely on the keySet() value.
  235    *     protected Set&lt;String&gt; handleKeySet() {
  236    *         return new HashSet&lt;String&gt;(Arrays.asList("okKey", "cancelKey"));
  237    *     }
  238    * }
  239    *
  240    * // German language
  241    * public class MyResources_de extends MyResources {
  242    *     public Object handleGetObject(String key) {
  243    *         // don't need okKey, since parent level handles it.
  244    *         if (key.equals("cancelKey")) return "Abbrechen";
  245    *         return null;
  246    *     }
  247    *
  248    *     protected Set&lt;String&gt; handleKeySet() {
  249    *         return new HashSet&lt;String&gt;(Arrays.asList("cancelKey"));
  250    *     }
  251    * }
  252    * </pre>
  253    * </blockquote>
  254    * You do not have to restrict yourself to using a single family of
  255    * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
  256    * exception messages, <code>ExceptionResources</code>
  257    * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
  258    * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
  259    * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
  260    *
  261    * @see ListResourceBundle
  262    * @see PropertyResourceBundle
  263    * @see MissingResourceException
  264    * @since JDK1.1
  265    */
  266   public abstract class ResourceBundle {
  267   
  268       /** initial size of the bundle cache */
  269       private static final int INITIAL_CACHE_SIZE = 32;
  270   
  271       /** constant indicating that no resource bundle exists */
  272       private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
  273               public Enumeration<String> getKeys() { return null; }
  274               protected Object handleGetObject(String key) { return null; }
  275               public String toString() { return "NONEXISTENT_BUNDLE"; }
  276           };
  277   
  278   
  279       /**
  280        * The cache is a map from cache keys (with bundle base name, locale, and
  281        * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a
  282        * BundleReference.
  283        *
  284        * The cache is a ConcurrentMap, allowing the cache to be searched
  285        * concurrently by multiple threads.  This will also allow the cache keys
  286        * to be reclaimed along with the ClassLoaders they reference.
  287        *
  288        * This variable would be better named "cache", but we keep the old
  289        * name for compatibility with some workarounds for bug 4212439.
  290        */
  291       private static final ConcurrentMap<CacheKey, BundleReference> cacheList
  292           = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
  293   
  294       /**
  295        * Queue for reference objects referring to class loaders or bundles.
  296        */
  297       private static final ReferenceQueue referenceQueue = new ReferenceQueue();
  298   
  299       /**
  300        * The parent bundle of this bundle.
  301        * The parent bundle is searched by {@link #getObject getObject}
  302        * when this bundle does not contain a particular resource.
  303        */
  304       protected ResourceBundle parent = null;
  305   
  306       /**
  307        * The locale for this bundle.
  308        */
  309       private Locale locale = null;
  310   
  311       /**
  312        * The base bundle name for this bundle.
  313        */
  314       private String name;
  315   
  316       /**
  317        * The flag indicating this bundle has expired in the cache.
  318        */
  319       private volatile boolean expired;
  320   
  321       /**
  322        * The back link to the cache key. null if this bundle isn't in
  323        * the cache (yet) or has expired.
  324        */
  325       private volatile CacheKey cacheKey;
  326   
  327       /**
  328        * A Set of the keys contained only in this ResourceBundle.
  329        */
  330       private volatile Set<String> keySet;
  331   
  332       /**
  333        * Sole constructor.  (For invocation by subclass constructors, typically
  334        * implicit.)
  335        */
  336       public ResourceBundle() {
  337       }
  338   
  339       /**
  340        * Gets a string for the given key from this resource bundle or one of its parents.
  341        * Calling this method is equivalent to calling
  342        * <blockquote>
  343        * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
  344        * </blockquote>
  345        *
  346        * @param key the key for the desired string
  347        * @exception NullPointerException if <code>key</code> is <code>null</code>
  348        * @exception MissingResourceException if no object for the given key can be found
  349        * @exception ClassCastException if the object found for the given key is not a string
  350        * @return the string for the given key
  351        */
  352       public final String getString(String key) {
  353           return (String) getObject(key);
  354       }
  355   
  356       /**
  357        * Gets a string array for the given key from this resource bundle or one of its parents.
  358        * Calling this method is equivalent to calling
  359        * <blockquote>
  360        * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
  361        * </blockquote>
  362        *
  363        * @param key the key for the desired string array
  364        * @exception NullPointerException if <code>key</code> is <code>null</code>
  365        * @exception MissingResourceException if no object for the given key can be found
  366        * @exception ClassCastException if the object found for the given key is not a string array
  367        * @return the string array for the given key
  368        */
  369       public final String[] getStringArray(String key) {
  370           return (String[]) getObject(key);
  371       }
  372   
  373       /**
  374        * Gets an object for the given key from this resource bundle or one of its parents.
  375        * This method first tries to obtain the object from this resource bundle using
  376        * {@link #handleGetObject(java.lang.String) handleGetObject}.
  377        * If not successful, and the parent resource bundle is not null,
  378        * it calls the parent's <code>getObject</code> method.
  379        * If still not successful, it throws a MissingResourceException.
  380        *
  381        * @param key the key for the desired object
  382        * @exception NullPointerException if <code>key</code> is <code>null</code>
  383        * @exception MissingResourceException if no object for the given key can be found
  384        * @return the object for the given key
  385        */
  386       public final Object getObject(String key) {
  387           Object obj = handleGetObject(key);
  388           if (obj == null) {
  389               if (parent != null) {
  390                   obj = parent.getObject(key);
  391               }
  392               if (obj == null)
  393                   throw new MissingResourceException("Can't find resource for bundle "
  394                                                      +this.getClass().getName()
  395                                                      +", key "+key,
  396                                                      this.getClass().getName(),
  397                                                      key);
  398           }
  399           return obj;
  400       }
  401   
  402       /**
  403        * Returns the locale of this resource bundle. This method can be used after a
  404        * call to getBundle() to determine whether the resource bundle returned really
  405        * corresponds to the requested locale or is a fallback.
  406        *
  407        * @return the locale of this resource bundle
  408        */
  409       public Locale getLocale() {
  410           return locale;
  411       }
  412   
  413       /*
  414        * Automatic determination of the ClassLoader to be used to load
  415        * resources on behalf of the client.  N.B. The client is getLoader's
  416        * caller's caller.
  417        */
  418       private static ClassLoader getLoader() {
  419           Class[] stack = getClassContext();
  420           /* Magic number 2 identifies our caller's caller */
  421           Class c = stack[2];
  422           ClassLoader cl = (c == null) ? null : c.getClassLoader();
  423           if (cl == null) {
  424               // When the caller's loader is the boot class loader, cl is null
  425               // here. In that case, ClassLoader.getSystemClassLoader() may
  426               // return the same class loader that the application is
  427               // using. We therefore use a wrapper ClassLoader to create a
  428               // separate scope for bundles loaded on behalf of the Java
  429               // runtime so that these bundles cannot be returned from the
  430               // cache to the application (5048280).
  431               cl = RBClassLoader.INSTANCE;
  432           }
  433           return cl;
  434       }
  435   
  436       private static native Class[] getClassContext();
  437   
  438       /**
  439        * A wrapper of ClassLoader.getSystemClassLoader().
  440        */
  441       private static class RBClassLoader extends ClassLoader {
  442           private static final RBClassLoader INSTANCE = AccessController.doPrivileged(
  443                       new PrivilegedAction<RBClassLoader>() {
  444                           public RBClassLoader run() {
  445                               return new RBClassLoader();
  446                           }
  447                       });
  448           private static final ClassLoader loader = ClassLoader.getSystemClassLoader();
  449   
  450           private RBClassLoader() {
  451           }
  452           public Class<?> loadClass(String name) throws ClassNotFoundException {
  453               if (loader != null) {
  454                   return loader.loadClass(name);
  455               }
  456               return Class.forName(name);
  457           }
  458           public URL getResource(String name) {
  459               if (loader != null) {
  460                   return loader.getResource(name);
  461               }
  462               return ClassLoader.getSystemResource(name);
  463           }
  464           public InputStream getResourceAsStream(String name) {
  465               if (loader != null) {
  466                   return loader.getResourceAsStream(name);
  467               }
  468               return ClassLoader.getSystemResourceAsStream(name);
  469           }
  470       }
  471   
  472       /**
  473        * Sets the parent bundle of this bundle.
  474        * The parent bundle is searched by {@link #getObject getObject}
  475        * when this bundle does not contain a particular resource.
  476        *
  477        * @param parent this bundle's parent bundle.
  478        */
  479       protected void setParent(ResourceBundle parent) {
  480           assert parent != NONEXISTENT_BUNDLE;
  481           this.parent = parent;
  482       }
  483   
  484       /**
  485        * Key used for cached resource bundles.  The key checks the base
  486        * name, the locale, and the class loader to determine if the
  487        * resource is a match to the requested one. The loader may be
  488        * null, but the base name and the locale must have a non-null
  489        * value.
  490        */
  491       private static final class CacheKey implements Cloneable {
  492           // These three are the actual keys for lookup in Map.
  493           private String name;
  494           private Locale locale;
  495           private LoaderReference loaderRef;
  496   
  497           // bundle format which is necessary for calling
  498           // Control.needsReload().
  499           private String format;
  500   
  501           // These time values are in CacheKey so that NONEXISTENT_BUNDLE
  502           // doesn't need to be cloned for caching.
  503   
  504           // The time when the bundle has been loaded
  505           private volatile long loadTime;
  506   
  507           // The time when the bundle expires in the cache, or either
  508           // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
  509           private volatile long expirationTime;
  510   
  511           // Placeholder for an error report by a Throwable
  512           private Throwable cause;
  513   
  514           // Hash code value cache to avoid recalculating the hash code
  515           // of this instance.
  516           private int hashCodeCache;
  517   
  518           CacheKey(String baseName, Locale locale, ClassLoader loader) {
  519               this.name = baseName;
  520               this.locale = locale;
  521               if (loader == null) {
  522                   this.loaderRef = null;
  523               } else {
  524                   loaderRef = new LoaderReference(loader, referenceQueue, this);
  525               }
  526               calculateHashCode();
  527           }
  528   
  529           String getName() {
  530               return name;
  531           }
  532   
  533           CacheKey setName(String baseName) {
  534               if (!this.name.equals(baseName)) {
  535                   this.name = baseName;
  536                   calculateHashCode();
  537               }
  538               return this;
  539           }
  540   
  541           Locale getLocale() {
  542               return locale;
  543           }
  544   
  545           CacheKey setLocale(Locale locale) {
  546               if (!this.locale.equals(locale)) {
  547                   this.locale = locale;
  548                   calculateHashCode();
  549               }
  550               return this;
  551           }
  552   
  553           ClassLoader getLoader() {
  554               return (loaderRef != null) ? loaderRef.get() : null;
  555           }
  556   
  557           public boolean equals(Object other) {
  558               if (this == other) {
  559                   return true;
  560               }
  561               try {
  562                   final CacheKey otherEntry = (CacheKey)other;
  563                   //quick check to see if they are not equal
  564                   if (hashCodeCache != otherEntry.hashCodeCache) {
  565                       return false;
  566                   }
  567                   //are the names the same?
  568                   if (!name.equals(otherEntry.name)) {
  569                       return false;
  570                   }
  571                   // are the locales the same?
  572                   if (!locale.equals(otherEntry.locale)) {
  573                       return false;
  574                   }
  575                   //are refs (both non-null) or (both null)?
  576                   if (loaderRef == null) {
  577                       return otherEntry.loaderRef == null;
  578                   }
  579                   ClassLoader loader = loaderRef.get();
  580                   return (otherEntry.loaderRef != null)
  581                           // with a null reference we can no longer find
  582                           // out which class loader was referenced; so
  583                           // treat it as unequal
  584                           && (loader != null)
  585                           && (loader == otherEntry.loaderRef.get());
  586               } catch (NullPointerException e) {
  587               } catch (ClassCastException e) {
  588               }
  589               return false;
  590           }
  591   
  592           public int hashCode() {
  593               return hashCodeCache;
  594           }
  595   
  596           private void calculateHashCode() {
  597               hashCodeCache = name.hashCode() << 3;
  598               hashCodeCache ^= locale.hashCode();
  599               ClassLoader loader = getLoader();
  600               if (loader != null) {
  601                   hashCodeCache ^= loader.hashCode();
  602               }
  603           }
  604   
  605           public Object clone() {
  606               try {
  607                   CacheKey clone = (CacheKey) super.clone();
  608                   if (loaderRef != null) {
  609                       clone.loaderRef = new LoaderReference(loaderRef.get(),
  610                                                             referenceQueue, clone);
  611                   }
  612                   // Clear the reference to a Throwable
  613                   clone.cause = null;
  614                   return clone;
  615               } catch (CloneNotSupportedException e) {
  616                   //this should never happen
  617                   throw new InternalError();
  618               }
  619           }
  620   
  621           String getFormat() {
  622               return format;
  623           }
  624   
  625           void setFormat(String format) {
  626               this.format = format;
  627           }
  628   
  629           private void setCause(Throwable cause) {
  630               if (this.cause == null) {
  631                   this.cause = cause;
  632               } else {
  633                   // Override the cause if the previous one is
  634                   // ClassNotFoundException.
  635                   if (this.cause instanceof ClassNotFoundException) {
  636                       this.cause = cause;
  637                   }
  638               }
  639           }
  640   
  641           private Throwable getCause() {
  642               return cause;
  643           }
  644   
  645           public String toString() {
  646               String l = locale.toString();
  647               if (l.length() == 0) {
  648                   if (locale.getVariant().length() != 0) {
  649                       l = "__" + locale.getVariant();
  650                   } else {
  651                       l = "\"\"";
  652                   }
  653               }
  654               return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
  655                   + "(format=" + format + ")]";
  656           }
  657       }
  658   
  659       /**
  660        * The common interface to get a CacheKey in LoaderReference and
  661        * BundleReference.
  662        */
  663       private static interface CacheKeyReference {
  664           public CacheKey getCacheKey();
  665       }
  666   
  667       /**
  668        * References to class loaders are weak references, so that they can be
  669        * garbage collected when nobody else is using them. The ResourceBundle
  670        * class has no reason to keep class loaders alive.
  671        */
  672       private static final class LoaderReference extends WeakReference<ClassLoader>
  673                                                  implements CacheKeyReference {
  674           private CacheKey cacheKey;
  675   
  676           LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) {
  677               super(referent, q);
  678               cacheKey = key;
  679           }
  680   
  681           public CacheKey getCacheKey() {
  682               return cacheKey;
  683           }
  684       }
  685   
  686       /**
  687        * References to bundles are soft references so that they can be garbage
  688        * collected when they have no hard references.
  689        */
  690       private static final class BundleReference extends SoftReference<ResourceBundle>
  691                                                  implements CacheKeyReference {
  692           private CacheKey cacheKey;
  693   
  694           BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) {
  695               super(referent, q);
  696               cacheKey = key;
  697           }
  698   
  699           public CacheKey getCacheKey() {
  700               return cacheKey;
  701           }
  702       }
  703   
  704       /**
  705        * Gets a resource bundle using the specified base name, the default locale,
  706        * and the caller's class loader. Calling this method is equivalent to calling
  707        * <blockquote>
  708        * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
  709        * </blockquote>
  710        * except that <code>getClassLoader()</code> is run with the security
  711        * privileges of <code>ResourceBundle</code>.
  712        * See {@link #getBundle(String, Locale, ClassLoader) getBundle}
  713        * for a complete description of the search and instantiation strategy.
  714        *
  715        * @param baseName the base name of the resource bundle, a fully qualified class name
  716        * @exception java.lang.NullPointerException
  717        *     if <code>baseName</code> is <code>null</code>
  718        * @exception MissingResourceException
  719        *     if no resource bundle for the specified base name can be found
  720        * @return a resource bundle for the given base name and the default locale
  721        */
  722       public static final ResourceBundle getBundle(String baseName)
  723       {
  724           return getBundleImpl(baseName, Locale.getDefault(),
  725                                /* must determine loader here, else we break stack invariant */
  726                                getLoader(),
  727                                Control.INSTANCE);
  728       }
  729   
  730       /**
  731        * Returns a resource bundle using the specified base name, the
  732        * default locale and the specified control. Calling this method
  733        * is equivalent to calling
  734        * <pre>
  735        * getBundle(baseName, Locale.getDefault(),
  736        *           this.getClass().getClassLoader(), control),
  737        * </pre>
  738        * except that <code>getClassLoader()</code> is run with the security
  739        * privileges of <code>ResourceBundle</code>.  See {@link
  740        * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
  741        * complete description of the resource bundle loading process with a
  742        * <code>ResourceBundle.Control</code>.
  743        *
  744        * @param baseName
  745        *        the base name of the resource bundle, a fully qualified class
  746        *        name
  747        * @param control
  748        *        the control which gives information for the resource bundle
  749        *        loading process
  750        * @return a resource bundle for the given base name and the default
  751        *        locale
  752        * @exception NullPointerException
  753        *        if <code>baseName</code> or <code>control</code> is
  754        *        <code>null</code>
  755        * @exception MissingResourceException
  756        *        if no resource bundle for the specified base name can be found
  757        * @exception IllegalArgumentException
  758        *        if the given <code>control</code> doesn't perform properly
  759        *        (e.g., <code>control.getCandidateLocales</code> returns null.)
  760        *        Note that validation of <code>control</code> is performed as
  761        *        needed.
  762        * @since 1.6
  763        */
  764       public static final ResourceBundle getBundle(String baseName,
  765                                                    Control control) {
  766           return getBundleImpl(baseName, Locale.getDefault(),
  767                                /* must determine loader here, else we break stack invariant */
  768                                getLoader(),
  769                                control);
  770       }
  771   
  772       /**
  773        * Gets a resource bundle using the specified base name and locale,
  774        * and the caller's class loader. Calling this method is equivalent to calling
  775        * <blockquote>
  776        * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
  777        * </blockquote>
  778        * except that <code>getClassLoader()</code> is run with the security
  779        * privileges of <code>ResourceBundle</code>.
  780        * See {@link #getBundle(String, Locale, ClassLoader) getBundle}
  781        * for a complete description of the search and instantiation strategy.
  782        *
  783        * @param baseName
  784        *        the base name of the resource bundle, a fully qualified class name
  785        * @param locale
  786        *        the locale for which a resource bundle is desired
  787        * @exception NullPointerException
  788        *        if <code>baseName</code> or <code>locale</code> is <code>null</code>
  789        * @exception MissingResourceException
  790        *        if no resource bundle for the specified base name can be found
  791        * @return a resource bundle for the given base name and locale
  792        */
  793       public static final ResourceBundle getBundle(String baseName,
  794                                                    Locale locale)
  795       {
  796           return getBundleImpl(baseName, locale,
  797                                /* must determine loader here, else we break stack invariant */
  798                                getLoader(),
  799                                Control.INSTANCE);
  800       }
  801   
  802       /**
  803        * Returns a resource bundle using the specified base name, target
  804        * locale and control, and the caller's class loader. Calling this
  805        * method is equivalent to calling
  806        * <pre>
  807        * getBundle(baseName, targetLocale, this.getClass().getClassLoader(),
  808        *           control),
  809        * </pre>
  810        * except that <code>getClassLoader()</code> is run with the security
  811        * privileges of <code>ResourceBundle</code>.  See {@link
  812        * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
  813        * complete description of the resource bundle loading process with a
  814        * <code>ResourceBundle.Control</code>.
  815        *
  816        * @param baseName
  817        *        the base name of the resource bundle, a fully qualified
  818        *        class name
  819        * @param targetLocale
  820        *        the locale for which a resource bundle is desired
  821        * @param control
  822        *        the control which gives information for the resource
  823        *        bundle loading process
  824        * @return a resource bundle for the given base name and a
  825        *        <code>Locale</code> in <code>locales</code>
  826        * @exception NullPointerException
  827        *        if <code>baseName</code>, <code>locales</code> or
  828        *        <code>control</code> is <code>null</code>
  829        * @exception MissingResourceException
  830        *        if no resource bundle for the specified base name in any
  831        *        of the <code>locales</code> can be found.
  832        * @exception IllegalArgumentException
  833        *        if the given <code>control</code> doesn't perform properly
  834        *        (e.g., <code>control.getCandidateLocales</code> returns null.)
  835        *        Note that validation of <code>control</code> is performed as
  836        *        needed.
  837        * @since 1.6
  838        */
  839       public static final ResourceBundle getBundle(String baseName, Locale targetLocale,
  840                                                    Control control) {
  841           return getBundleImpl(baseName, targetLocale,
  842                                /* must determine loader here, else we break stack invariant */
  843                                getLoader(),
  844                                control);
  845       }
  846   
  847       /**
  848        * Gets a resource bundle using the specified base name, locale, and class
  849        * loader.
  850        *
  851        * <p><a name="default_behavior"/>This method behaves the same as calling
  852        * {@link #getBundle(String, Locale, ClassLoader, Control)} passing a
  853        * default instance of {@link Control}. The following describes this behavior.
  854        *
  855        * <p><code>getBundle</code> uses the base name, the specified locale, and
  856        * the default locale (obtained from {@link java.util.Locale#getDefault()
  857        * Locale.getDefault}) to generate a sequence of <a
  858        * name="candidates"><em>candidate bundle names</em></a>.  If the specified
  859        * locale's language, script, country, and variant are all empty strings,
  860        * then the base name is the only candidate bundle name.  Otherwise, a list
  861        * of candidate locales is generated from the attribute values of the
  862        * specified locale (language, script, country and variant) and appended to
  863        * the base name.  Typically, this will look like the following:
  864        *
  865        * <pre>
  866        *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
  867        *     baseName + "_" + language + "_" + script + "_" + country
  868        *     baseName + "_" + language + "_" + script
  869        *     baseName + "_" + language + "_" + country + "_" + variant
  870        *     baseName + "_" + language + "_" + country
  871        *     baseName + "_" + language
  872        * </pre>
  873        *
  874        * <p>Candidate bundle names where the final component is an empty string
  875        * are omitted, along with the underscore.  For example, if country is an
  876        * empty string, the second and the fifth candidate bundle names above
  877        * would be omitted.  Also, if script is an empty string, the candidate names
  878        * including script are omitted.  For example, a locale with language "de"
  879        * and variant "JAVA" will produce candidate names with base name
  880        * "MyResource" below.
  881        *
  882        * <pre>
  883        *     MyResource_de__JAVA
  884        *     MyResource_de
  885        * </pre>
  886        *
  887        * In the case that the variant contains one or more underscores ('_'), a
  888        * sequence of bundle names generated by truncating the last underscore and
  889        * the part following it is inserted after a candidate bundle name with the
  890        * original variant.  For example, for a locale with language "en", script
  891        * "Latn, country "US" and variant "WINDOWS_VISTA", and bundle base name
  892        * "MyResource", the list of candidate bundle names below is generated:
  893        *
  894        * <pre>
  895        * MyResource_en_Latn_US_WINDOWS_VISTA
  896        * MyResource_en_Latn_US_WINDOWS
  897        * MyResource_en_Latn_US
  898        * MyResource_en_Latn
  899        * MyResource_en_US_WINDOWS_VISTA
  900        * MyResource_en_US_WINDOWS
  901        * MyResource_en_US
  902        * MyResource_en
  903        * </pre>
  904        *
  905        * <blockquote><b>Note:</b> For some <code>Locale</code>s, the list of
  906        * candidate bundle names contains extra names, or the order of bundle names
  907        * is slightly modified.  See the description of the default implementation
  908        * of {@link Control#getCandidateLocales(String, Locale)
  909        * getCandidateLocales} for details.</blockquote>
  910        *
  911        * <p><code>getBundle</code> then iterates over the candidate bundle names
  912        * to find the first one for which it can <em>instantiate</em> an actual
  913        * resource bundle. It uses the default controls' {@link Control#getFormats
  914        * getFormats} method, which generates two bundle names for each generated
  915        * name, the first a class name and the second a properties file name. For
  916        * each candidate bundle name, it attempts to create a resource bundle:
  917        *
  918        * <ul><li>First, it attempts to load a class using the generated class name.
  919        * If such a class can be found and loaded using the specified class
  920        * loader, is assignment compatible with ResourceBundle, is accessible from
  921        * ResourceBundle, and can be instantiated, <code>getBundle</code> creates a
  922        * new instance of this class and uses it as the <em>result resource
  923        * bundle</em>.
  924        *
  925        * <li>Otherwise, <code>getBundle</code> attempts to locate a property
  926        * resource file using the generated properties file name.  It generates a
  927        * path name from the candidate bundle name by replacing all "." characters
  928        * with "/" and appending the string ".properties".  It attempts to find a
  929        * "resource" with this name using {@link
  930        * java.lang.ClassLoader#getResource(java.lang.String)
  931        * ClassLoader.getResource}.  (Note that a "resource" in the sense of
  932        * <code>getResource</code> has nothing to do with the contents of a
  933        * resource bundle, it is just a container of data, such as a file.)  If it
  934        * finds a "resource", it attempts to create a new {@link
  935        * PropertyResourceBundle} instance from its contents.  If successful, this
  936        * instance becomes the <em>result resource bundle</em>.  </ul>
  937        *
  938        * <p>This continues until a result resource bundle is instantiated or the
  939        * list of candidate bundle names is exhausted.  If no matching resource
  940        * bundle is found, the default control's {@link Control#getFallbackLocale
  941        * getFallbackLocale} method is called, which returns the current default
  942        * locale.  A new sequence of candidate locale names is generated using this
  943        * locale and and searched again, as above.
  944        *
  945        * <p>If still no result bundle is found, the base name alone is looked up. If
  946        * this still fails, a <code>MissingResourceException</code> is thrown.
  947        *
  948        * <p><a name="parent_chain"/> Once a result resource bundle has been found,
  949        * its <em>parent chain</em> is instantiated.  If the result bundle already
  950        * has a parent (perhaps because it was returned from a cache) the chain is
  951        * complete.
  952        *
  953        * <p>Otherwise, <code>getBundle</code> examines the remainder of the
  954        * candidate locale list that was used during the pass that generated the
  955        * result resource bundle.  (As before, candidate bundle names where the
  956        * final component is an empty string are omitted.)  When it comes to the
  957        * end of the candidate list, it tries the plain bundle name.  With each of the
  958        * candidate bundle names it attempts to instantiate a resource bundle (first
  959        * looking for a class and then a properties file, as described above).
  960        *
  961        * <p>Whenever it succeeds, it calls the previously instantiated resource
  962        * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method
  963        * with the new resource bundle.  This continues until the list of names
  964        * is exhausted or the current bundle already has a non-null parent.
  965        *
  966        * <p>Once the parent chain is complete, the bundle is returned.
  967        *
  968        * <p><b>Note:</b> <code>getBundle</code> caches instantiated resource
  969        * bundles and might return the same resource bundle instance multiple times.
  970        *
  971        * <p><b>Note:</b>The <code>baseName</code> argument should be a fully
  972        * qualified class name. However, for compatibility with earlier versions,
  973        * Sun's Java SE Runtime Environments do not verify this, and so it is
  974        * possible to access <code>PropertyResourceBundle</code>s by specifying a
  975        * path name (using "/") instead of a fully qualified class name (using
  976        * ".").
  977        *
  978        * <p><a name="default_behavior_example"/>
  979        * <strong>Example:</strong>
  980        * <p>
  981        * The following class and property files are provided:
  982        * <pre>
  983        *     MyResources.class
  984        *     MyResources.properties
  985        *     MyResources_fr.properties
  986        *     MyResources_fr_CH.class
  987        *     MyResources_fr_CH.properties
  988        *     MyResources_en.properties
  989        *     MyResources_es_ES.class
  990        * </pre>
  991        *
  992        * The contents of all files are valid (that is, public non-abstract
  993        * subclasses of <code>ResourceBundle</code> for the ".class" files,
  994        * syntactically correct ".properties" files).  The default locale is
  995        * <code>Locale("en", "GB")</code>.
  996        *
  997        * <p>Calling <code>getBundle</code> with the locale arguments below will
  998        * instantiate resource bundles as follows:
  999        *
 1000        * <table>
 1001        * <tr><td>Locale("fr", "CH")</td><td>MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class</td></tr>
 1002        * <tr><td>Locale("fr", "FR")</td><td>MyResources_fr.properties, parent MyResources.class</td></tr>
 1003        * <tr><td>Locale("de", "DE")</td><td>MyResources_en.properties, parent MyResources.class</td></tr>
 1004        * <tr><td>Locale("en", "US")</td><td>MyResources_en.properties, parent MyResources.class</td></tr>
 1005        * <tr><td>Locale("es", "ES")</td><td>MyResources_es_ES.class, parent MyResources.class</td></tr>
 1006        * </table>
 1007        *
 1008        * <p>The file MyResources_fr_CH.properties is never used because it is
 1009        * hidden by the MyResources_fr_CH.class. Likewise, MyResources.properties
 1010        * is also hidden by MyResources.class.
 1011        *
 1012        * @param baseName the base name of the resource bundle, a fully qualified class name
 1013        * @param locale the locale for which a resource bundle is desired
 1014        * @param loader the class loader from which to load the resource bundle
 1015        * @return a resource bundle for the given base name and locale
 1016        * @exception java.lang.NullPointerException
 1017        *        if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
 1018        * @exception MissingResourceException
 1019        *        if no resource bundle for the specified base name can be found
 1020        * @since 1.2
 1021        */
 1022       public static ResourceBundle getBundle(String baseName, Locale locale,
 1023                                              ClassLoader loader)
 1024       {
 1025           if (loader == null) {
 1026               throw new NullPointerException();
 1027           }
 1028           return getBundleImpl(baseName, locale, loader, Control.INSTANCE);
 1029       }
 1030   
 1031       /**
 1032        * Returns a resource bundle using the specified base name, target
 1033        * locale, class loader and control. Unlike the {@linkplain
 1034        * #getBundle(String, Locale, ClassLoader) <code>getBundle</code>
 1035        * factory methods with no <code>control</code> argument}, the given
 1036        * <code>control</code> specifies how to locate and instantiate resource
 1037        * bundles. Conceptually, the bundle loading process with the given
 1038        * <code>control</code> is performed in the following steps.
 1039        *
 1040        * <p>
 1041        * <ol>
 1042        * <li>This factory method looks up the resource bundle in the cache for
 1043        * the specified <code>baseName</code>, <code>targetLocale</code> and
 1044        * <code>loader</code>.  If the requested resource bundle instance is
 1045        * found in the cache and the time-to-live periods of the instance and
 1046        * all of its parent instances have not expired, the instance is returned
 1047        * to the caller. Otherwise, this factory method proceeds with the
 1048        * loading process below.</li>
 1049        *
 1050        * <li>The {@link ResourceBundle.Control#getFormats(String)
 1051        * control.getFormats} method is called to get resource bundle formats
 1052        * to produce bundle or resource names. The strings
 1053        * <code>"java.class"</code> and <code>"java.properties"</code>
 1054        * designate class-based and {@linkplain PropertyResourceBundle
 1055        * property}-based resource bundles, respectively. Other strings
 1056        * starting with <code>"java."</code> are reserved for future extensions
 1057        * and must not be used for application-defined formats. Other strings
 1058        * designate application-defined formats.</li>
 1059        *
 1060        * <li>The {@link ResourceBundle.Control#getCandidateLocales(String,
 1061        * Locale) control.getCandidateLocales} method is called with the target
 1062        * locale to get a list of <em>candidate <code>Locale</code>s</em> for
 1063        * which resource bundles are searched.</li>
 1064        *
 1065        * <li>The {@link ResourceBundle.Control#newBundle(String, Locale,
 1066        * String, ClassLoader, boolean) control.newBundle} method is called to
 1067        * instantiate a <code>ResourceBundle</code> for the base bundle name, a
 1068        * candidate locale, and a format. (Refer to the note on the cache
 1069        * lookup below.) This step is iterated over all combinations of the
 1070        * candidate locales and formats until the <code>newBundle</code> method
 1071        * returns a <code>ResourceBundle</code> instance or the iteration has
 1072        * used up all the combinations. For example, if the candidate locales
 1073        * are <code>Locale("de", "DE")</code>, <code>Locale("de")</code> and
 1074        * <code>Locale("")</code> and the formats are <code>"java.class"</code>
 1075        * and <code>"java.properties"</code>, then the following is the
 1076        * sequence of locale-format combinations to be used to call
 1077        * <code>control.newBundle</code>.
 1078        *
 1079        * <table style="width: 50%; text-align: left; margin-left: 40px;"
 1080        *  border="0" cellpadding="2" cellspacing="2">
 1081        * <tbody><code>
 1082        * <tr>
 1083        * <td
 1084        * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">Locale<br>
 1085        * </td>
 1086        * <td
 1087        * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">format<br>
 1088        * </td>
 1089        * </tr>
 1090        * <tr>
 1091        * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")<br>
 1092        * </td>
 1093        * <td style="vertical-align: top; width: 50%;">java.class<br>
 1094        * </td>
 1095        * </tr>
 1096        * <tr>
 1097        * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")</td>
 1098        * <td style="vertical-align: top; width: 50%;">java.properties<br>
 1099        * </td>
 1100        * </tr>
 1101        * <tr>
 1102        * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
 1103        * <td style="vertical-align: top; width: 50%;">java.class</td>
 1104        * </tr>
 1105        * <tr>
 1106        * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
 1107        * <td style="vertical-align: top; width: 50%;">java.properties</td>
 1108        * </tr>
 1109        * <tr>
 1110        * <td style="vertical-align: top; width: 50%;">Locale("")<br>
 1111        * </td>
 1112        * <td style="vertical-align: top; width: 50%;">java.class</td>
 1113        * </tr>
 1114        * <tr>
 1115        * <td style="vertical-align: top; width: 50%;">Locale("")</td>
 1116        * <td style="vertical-align: top; width: 50%;">java.properties</td>
 1117        * </tr>
 1118        * </code></tbody>
 1119        * </table>
 1120        * </li>
 1121        *
 1122        * <li>If the previous step has found no resource bundle, proceed to
 1123        * Step 6. If a bundle has been found that is a base bundle (a bundle
 1124        * for <code>Locale("")</code>), and the candidate locale list only contained
 1125        * <code>Locale("")</code>, return the bundle to the caller. If a bundle
 1126        * has been found that is a base bundle, but the candidate locale list
 1127        * contained locales other than Locale(""), put the bundle on hold and
 1128        * proceed to Step 6. If a bundle has been found that is not a base
 1129        * bundle, proceed to Step 7.</li>
 1130        *
 1131        * <li>The {@link ResourceBundle.Control#getFallbackLocale(String,
 1132        * Locale) control.getFallbackLocale} method is called to get a fallback
 1133        * locale (alternative to the current target locale) to try further
 1134        * finding a resource bundle. If the method returns a non-null locale,
 1135        * it becomes the next target locale and the loading process starts over
 1136        * from Step 3. Otherwise, if a base bundle was found and put on hold in
 1137        * a previous Step 5, it is returned to the caller now. Otherwise, a
 1138        * MissingResourceException is thrown.</li>
 1139        *
 1140        * <li>At this point, we have found a resource bundle that's not the
 1141        * base bundle. If this bundle set its parent during its instantiation,
 1142        * it is returned to the caller. Otherwise, its <a
 1143        * href="./ResourceBundle.html#parent_chain">parent chain</a> is
 1144        * instantiated based on the list of candidate locales from which it was
 1145        * found. Finally, the bundle is returned to the caller.</li>
 1146        * </ol>
 1147        *
 1148        * <p>During the resource bundle loading process above, this factory
 1149        * method looks up the cache before calling the {@link
 1150        * Control#newBundle(String, Locale, String, ClassLoader, boolean)
 1151        * control.newBundle} method.  If the time-to-live period of the
 1152        * resource bundle found in the cache has expired, the factory method
 1153        * calls the {@link ResourceBundle.Control#needsReload(String, Locale,
 1154        * String, ClassLoader, ResourceBundle, long) control.needsReload}
 1155        * method to determine whether the resource bundle needs to be reloaded.
 1156        * If reloading is required, the factory method calls
 1157        * <code>control.newBundle</code> to reload the resource bundle.  If
 1158        * <code>control.newBundle</code> returns <code>null</code>, the factory
 1159        * method puts a dummy resource bundle in the cache as a mark of
 1160        * nonexistent resource bundles in order to avoid lookup overhead for
 1161        * subsequent requests. Such dummy resource bundles are under the same
 1162        * expiration control as specified by <code>control</code>.
 1163        *
 1164        * <p>All resource bundles loaded are cached by default. Refer to
 1165        * {@link Control#getTimeToLive(String,Locale)
 1166        * control.getTimeToLive} for details.
 1167        *
 1168        * <p>The following is an example of the bundle loading process with the
 1169        * default <code>ResourceBundle.Control</code> implementation.
 1170        *
 1171        * <p>Conditions:
 1172        * <ul>
 1173        * <li>Base bundle name: <code>foo.bar.Messages</code>
 1174        * <li>Requested <code>Locale</code>: {@link Locale#ITALY}</li>
 1175        * <li>Default <code>Locale</code>: {@link Locale#FRENCH}</li>
 1176        * <li>Available resource bundles:
 1177        * <code>foo/bar/Messages_fr.properties</code> and
 1178        * <code>foo/bar/Messages.properties</code></li>
 1179        * </ul>
 1180        *
 1181        * <p>First, <code>getBundle</code> tries loading a resource bundle in
 1182        * the following sequence.
 1183        *
 1184        * <ul>
 1185        * <li>class <code>foo.bar.Messages_it_IT</code>
 1186        * <li>file <code>foo/bar/Messages_it_IT.properties</code>
 1187        * <li>class <code>foo.bar.Messages_it</code></li>
 1188        * <li>file <code>foo/bar/Messages_it.properties</code></li>
 1189        * <li>class <code>foo.bar.Messages</code></li>
 1190        * <li>file <code>foo/bar/Messages.properties</code></li>
 1191        * </ul>
 1192        *
 1193        * <p>At this point, <code>getBundle</code> finds
 1194        * <code>foo/bar/Messages.properties</code>, which is put on hold
 1195        * because it's the base bundle.  <code>getBundle</code> calls {@link
 1196        * Control#getFallbackLocale(String, Locale)
 1197        * control.getFallbackLocale("foo.bar.Messages", Locale.ITALY)} which
 1198        * returns <code>Locale.FRENCH</code>. Next, <code>getBundle</code>
 1199        * tries loading a bundle in the following sequence.
 1200        *
 1201        * <ul>
 1202        * <li>class <code>foo.bar.Messages_fr</code></li>
 1203        * <li>file <code>foo/bar/Messages_fr.properties</code></li>
 1204        * <li>class <code>foo.bar.Messages</code></li>
 1205        * <li>file <code>foo/bar/Messages.properties</code></li>
 1206        * </ul>
 1207        *
 1208        * <p><code>getBundle</code> finds
 1209        * <code>foo/bar/Messages_fr.properties</code> and creates a
 1210        * <code>ResourceBundle</code> instance. Then, <code>getBundle</code>
 1211        * sets up its parent chain from the list of the candiate locales.  Only
 1212        * <code>foo/bar/Messages.properties</code> is found in the list and
 1213        * <code>getBundle</code> creates a <code>ResourceBundle</code> instance
 1214        * that becomes the parent of the instance for
 1215        * <code>foo/bar/Messages_fr.properties</code>.
 1216        *
 1217        * @param baseName
 1218        *        the base name of the resource bundle, a fully qualified
 1219        *        class name
 1220        * @param targetLocale
 1221        *        the locale for which a resource bundle is desired
 1222        * @param loader
 1223        *        the class loader from which to load the resource bundle
 1224        * @param control
 1225        *        the control which gives information for the resource
 1226        *        bundle loading process
 1227        * @return a resource bundle for the given base name and locale
 1228        * @exception NullPointerException
 1229        *        if <code>baseName</code>, <code>targetLocale</code>,
 1230        *        <code>loader</code>, or <code>control</code> is
 1231        *        <code>null</code>
 1232        * @exception MissingResourceException
 1233        *        if no resource bundle for the specified base name can be found
 1234        * @exception IllegalArgumentException
 1235        *        if the given <code>control</code> doesn't perform properly
 1236        *        (e.g., <code>control.getCandidateLocales</code> returns null.)
 1237        *        Note that validation of <code>control</code> is performed as
 1238        *        needed.
 1239        * @since 1.6
 1240        */
 1241       public static ResourceBundle getBundle(String baseName, Locale targetLocale,
 1242                                              ClassLoader loader, Control control) {
 1243           if (loader == null || control == null) {
 1244               throw new NullPointerException();
 1245           }
 1246           return getBundleImpl(baseName, targetLocale, loader, control);
 1247       }
 1248   
 1249       private static ResourceBundle getBundleImpl(String baseName, Locale locale,
 1250                                                   ClassLoader loader, Control control) {
 1251           if (locale == null || control == null) {
 1252               throw new NullPointerException();
 1253           }
 1254   
 1255           // We create a CacheKey here for use by this call. The base
 1256           // name and loader will never change during the bundle loading
 1257           // process. We have to make sure that the locale is set before
 1258           // using it as a cache key.
 1259           CacheKey cacheKey = new CacheKey(baseName, locale, loader);
 1260           ResourceBundle bundle = null;
 1261   
 1262           // Quick lookup of the cache.
 1263           BundleReference bundleRef = cacheList.get(cacheKey);
 1264           if (bundleRef != null) {
 1265               bundle = bundleRef.get();
 1266               bundleRef = null;
 1267           }
 1268   
 1269           // If this bundle and all of its parents are valid (not expired),
 1270           // then return this bundle. If any of the bundles is expired, we
 1271           // don't call control.needsReload here but instead drop into the
 1272           // complete loading process below.
 1273           if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
 1274               return bundle;
 1275           }
 1276   
 1277           // No valid bundle was found in the cache, so we need to load the
 1278           // resource bundle and its parents.
 1279   
 1280           boolean isKnownControl = (control == Control.INSTANCE) ||
 1281                                      (control instanceof SingleFormatControl);
 1282           List<String> formats = control.getFormats(baseName);
 1283           if (!isKnownControl && !checkList(formats)) {
 1284               throw new IllegalArgumentException("Invalid Control: getFormats");
 1285           }
 1286   
 1287           ResourceBundle baseBundle = null;
 1288           for (Locale targetLocale = locale;
 1289                targetLocale != null;
 1290                targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
 1291               List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
 1292               if (!isKnownControl && !checkList(candidateLocales)) {
 1293                   throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
 1294               }
 1295   
 1296               bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);
 1297   
 1298               // If the loaded bundle is the base bundle and exactly for the
 1299               // requested locale or the only candidate locale, then take the
 1300               // bundle as the resulting one. If the loaded bundle is the base
 1301               // bundle, it's put on hold until we finish processing all
 1302               // fallback locales.
 1303               if (isValidBundle(bundle)) {
 1304                   boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
 1305                   if (!isBaseBundle || bundle.locale.equals(locale)
 1306                       || (candidateLocales.size() == 1
 1307                           && bundle.locale.equals(candidateLocales.get(0)))) {
 1308                       break;
 1309                   }
 1310   
 1311                   // If the base bundle has been loaded, keep the reference in
 1312                   // baseBundle so that we can avoid any redundant loading in case
 1313                   // the control specify not to cache bundles.
 1314                   if (isBaseBundle && baseBundle == null) {
 1315                       baseBundle = bundle;
 1316                   }
 1317               }
 1318           }
 1319   
 1320           if (bundle == null) {
 1321               if (baseBundle == null) {
 1322                   throwMissingResourceException(baseName, locale, cacheKey.getCause());
 1323               }
 1324               bundle = baseBundle;
 1325           }
 1326   
 1327           return bundle;
 1328       }
 1329   
 1330       /**
 1331        * Checks if the given <code>List</code> is not null, not empty,
 1332        * not having null in its elements.
 1333        */
 1334       private static final boolean checkList(List a) {
 1335           boolean valid = (a != null && a.size() != 0);
 1336           if (valid) {
 1337               int size = a.size();
 1338               for (int i = 0; valid && i < size; i++) {
 1339                   valid = (a.get(i) != null);
 1340               }
 1341           }
 1342           return valid;
 1343       }
 1344   
 1345       private static final ResourceBundle findBundle(CacheKey cacheKey,
 1346                                                      List<Locale> candidateLocales,
 1347                                                      List<String> formats,
 1348                                                      int index,
 1349                                                      Control control,
 1350                                                      ResourceBundle baseBundle) {
 1351           Locale targetLocale = candidateLocales.get(index);
 1352           ResourceBundle parent = null;
 1353           if (index != candidateLocales.size() - 1) {
 1354               parent = findBundle(cacheKey, candidateLocales, formats, index + 1,
 1355                                   control, baseBundle);
 1356           } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
 1357               return baseBundle;
 1358           }
 1359   
 1360           // Before we do the real loading work, see whether we need to
 1361           // do some housekeeping: If references to class loaders or
 1362           // resource bundles have been nulled out, remove all related
 1363           // information from the cache.
 1364           Object ref;
 1365           while ((ref = referenceQueue.poll()) != null) {
 1366               cacheList.remove(((CacheKeyReference)ref).getCacheKey());
 1367           }
 1368   
 1369           // flag indicating the resource bundle has expired in the cache
 1370           boolean expiredBundle = false;
 1371   
 1372           // First, look up the cache to see if it's in the cache, without
 1373           // attempting to load bundle.
 1374           cacheKey.setLocale(targetLocale);
 1375           ResourceBundle bundle = findBundleInCache(cacheKey, control);
 1376           if (isValidBundle(bundle)) {
 1377               expiredBundle = bundle.expired;
 1378               if (!expiredBundle) {
 1379                   // If its parent is the one asked for by the candidate
 1380                   // locales (the runtime lookup path), we can take the cached
 1381                   // one. (If it's not identical, then we'd have to check the
 1382                   // parent's parents to be consistent with what's been
 1383                   // requested.)
 1384                   if (bundle.parent == parent) {
 1385                       return bundle;
 1386                   }
 1387                   // Otherwise, remove the cached one since we can't keep
 1388                   // the same bundles having different parents.
 1389                   BundleReference bundleRef = cacheList.get(cacheKey);
 1390                   if (bundleRef != null && bundleRef.get() == bundle) {
 1391                       cacheList.remove(cacheKey, bundleRef);
 1392                   }
 1393               }
 1394           }
 1395   
 1396           if (bundle != NONEXISTENT_BUNDLE) {
 1397               CacheKey constKey = (CacheKey) cacheKey.clone();
 1398   
 1399               try {
 1400                   bundle = loadBundle(cacheKey, formats, control, expiredBundle);
 1401                   if (bundle != null) {
 1402                       if (bundle.parent == null) {
 1403                           bundle.setParent(parent);
 1404                       }
 1405                       bundle.locale = targetLocale;
 1406                       bundle = putBundleInCache(cacheKey, bundle, control);
 1407                       return bundle;
 1408                   }
 1409   
 1410                   // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
 1411                   // instance for the locale.
 1412                   putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control);
 1413               } finally {
 1414                   if (constKey.getCause() instanceof InterruptedException) {
 1415                       Thread.currentThread().interrupt();
 1416                   }
 1417               }
 1418           }
 1419           return parent;
 1420       }
 1421   
 1422       private static final ResourceBundle loadBundle(CacheKey cacheKey,
 1423                                                      List<String> formats,
 1424                                                      Control control,
 1425                                                      boolean reload) {
 1426   
 1427           // Here we actually load the bundle in the order of formats
 1428           // specified by the getFormats() value.
 1429           Locale targetLocale = cacheKey.getLocale();
 1430   
 1431           ResourceBundle bundle = null;
 1432           int size = formats.size();
 1433           for (int i = 0; i < size; i++) {
 1434               String format = formats.get(i);
 1435               try {
 1436                   bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
 1437                                              cacheKey.getLoader(), reload);
 1438               } catch (LinkageError error) {
 1439                   // We need to handle the LinkageError case due to
 1440                   // inconsistent case-sensitivity in ClassLoader.
 1441                   // See 6572242 for details.
 1442                   cacheKey.setCause(error);
 1443               } catch (Exception cause) {
 1444                   cacheKey.setCause(cause);
 1445               }
 1446               if (bundle != null) {
 1447                   // Set the format in the cache key so that it can be
 1448                   // used when calling needsReload later.
 1449                   cacheKey.setFormat(format);
 1450                   bundle.name = cacheKey.getName();
 1451                   bundle.locale = targetLocale;
 1452                   // Bundle provider might reuse instances. So we should make
 1453                   // sure to clear the expired flag here.
 1454                   bundle.expired = false;
 1455                   break;
 1456               }
 1457           }
 1458   
 1459           return bundle;
 1460       }
 1461   
 1462       private static final boolean isValidBundle(ResourceBundle bundle) {
 1463           return bundle != null && bundle != NONEXISTENT_BUNDLE;
 1464       }
 1465   
 1466       /**
 1467        * Determines whether any of resource bundles in the parent chain,
 1468        * including the leaf, have expired.
 1469        */
 1470       private static final boolean hasValidParentChain(ResourceBundle bundle) {
 1471           long now = System.currentTimeMillis();
 1472           while (bundle != null) {
 1473               if (bundle.expired) {
 1474                   return false;
 1475               }
 1476               CacheKey key = bundle.cacheKey;
 1477               if (key != null) {
 1478                   long expirationTime = key.expirationTime;
 1479                   if (expirationTime >= 0 && expirationTime <= now) {
 1480                       return false;
 1481                   }
 1482               }
 1483               bundle = bundle.parent;
 1484           }
 1485           return true;
 1486       }
 1487   
 1488       /**
 1489        * Throw a MissingResourceException with proper message
 1490        */
 1491       private static final void throwMissingResourceException(String baseName,
 1492                                                               Locale locale,
 1493                                                               Throwable cause) {
 1494           // If the cause is a MissingResourceException, avoid creating
 1495           // a long chain. (6355009)
 1496           if (cause instanceof MissingResourceException) {
 1497               cause = null;
 1498           }
 1499           throw new MissingResourceException("Can't find bundle for base name "
 1500                                              + baseName + ", locale " + locale,
 1501                                              baseName + "_" + locale, // className
 1502                                              "",                      // key
 1503                                              cause);
 1504       }
 1505   
 1506       /**
 1507        * Finds a bundle in the cache. Any expired bundles are marked as
 1508        * `expired' and removed from the cache upon return.
 1509        *
 1510        * @param cacheKey the key to look up the cache
 1511        * @param control the Control to be used for the expiration control
 1512        * @return the cached bundle, or null if the bundle is not found in the
 1513        * cache or its parent has expired. <code>bundle.expire</code> is true
 1514        * upon return if the bundle in the cache has expired.
 1515        */
 1516       private static final ResourceBundle findBundleInCache(CacheKey cacheKey,
 1517                                                             Control control) {
 1518           BundleReference bundleRef = cacheList.get(cacheKey);
 1519           if (bundleRef == null) {
 1520               return null;
 1521           }
 1522           ResourceBundle bundle = bundleRef.get();
 1523           if (bundle == null) {
 1524               return null;
 1525           }
 1526           ResourceBundle p = bundle.parent;
 1527           assert p != NONEXISTENT_BUNDLE;
 1528           // If the parent has expired, then this one must also expire. We
 1529           // check only the immediate parent because the actual loading is
 1530           // done from the root (base) to leaf (child) and the purpose of
 1531           // checking is to propagate expiration towards the leaf. For
 1532           // example, if the requested locale is ja_JP_JP and there are
 1533           // bundles for all of the candidates in the cache, we have a list,
 1534           //
 1535           // base <- ja <- ja_JP <- ja_JP_JP
 1536           //
 1537           // If ja has expired, then it will reload ja and the list becomes a
 1538           // tree.
 1539           //
 1540           // base <- ja (new)
 1541           //  "   <- ja (expired) <- ja_JP <- ja_JP_JP
 1542           //
 1543           // When looking up ja_JP in the cache, it finds ja_JP in the cache
 1544           // which references to the expired ja. Then, ja_JP is marked as
 1545           // expired and removed from the cache. This will be propagated to
 1546           // ja_JP_JP.
 1547           //
 1548           // Now, it's possible, for example, that while loading new ja_JP,
 1549           // someone else has started loading the same bundle and finds the
 1550           // base bundle has expired. Then, what we get from the first
 1551           // getBundle call includes the expired base bundle. However, if
 1552           // someone else didn't start its loading, we wouldn't know if the
 1553           // base bundle has expired at the end of the loading process. The
 1554           // expiration control doesn't guarantee that the returned bundle and
 1555           // its parents haven't expired.
 1556           //
 1557           // We could check the entire parent chain to see if there's any in
 1558           // the chain that has expired. But this process may never end. An
 1559           // extreme case would be that getTimeToLive returns 0 and
 1560           // needsReload always returns true.
 1561           if (p != null && p.expired) {
 1562               assert bundle != NONEXISTENT_BUNDLE;
 1563               bundle.expired = true;
 1564               bundle.cacheKey = null;
 1565               cacheList.remove(cacheKey, bundleRef);
 1566               bundle = null;
 1567           } else {
 1568               CacheKey key = bundleRef.getCacheKey();
 1569               long expirationTime = key.expirationTime;
 1570               if (!bundle.expired && expirationTime >= 0 &&
 1571                   expirationTime <= System.currentTimeMillis()) {
 1572                   // its TTL period has expired.
 1573                   if (bundle != NONEXISTENT_BUNDLE) {
 1574                       // Synchronize here to call needsReload to avoid
 1575                       // redundant concurrent calls for the same bundle.
 1576                       synchronized (bundle) {
 1577                           expirationTime = key.expirationTime;
 1578                           if (!bundle.expired && expirationTime >= 0 &&
 1579                               expirationTime <= System.currentTimeMillis()) {
 1580                               try {
 1581                                   bundle.expired = control.needsReload(key.getName(),
 1582                                                                        key.getLocale(),
 1583                                                                        key.getFormat(),
 1584                                                                        key.getLoader(),
 1585                                                                        bundle,
 1586                                                                        key.loadTime);
 1587                               } catch (Exception e) {
 1588                                   cacheKey.setCause(e);
 1589                               }
 1590                               if (bundle.expired) {
 1591                                   // If the bundle needs to be reloaded, then
 1592                                   // remove the bundle from the cache, but
 1593                                   // return the bundle with the expired flag
 1594                                   // on.
 1595                                   bundle.cacheKey = null;
 1596                                   cacheList.remove(cacheKey, bundleRef);
 1597                               } else {
 1598                                   // Update the expiration control info. and reuse
 1599                                   // the same bundle instance
 1600                                   setExpirationTime(key, control);
 1601                               }
 1602                           }
 1603                       }
 1604                   } else {
 1605                       // We just remove NONEXISTENT_BUNDLE from the cache.
 1606                       cacheList.remove(cacheKey, bundleRef);
 1607                       bundle = null;
 1608                   }
 1609               }
 1610           }
 1611           return bundle;
 1612       }
 1613   
 1614       /**
 1615        * Put a new bundle in the cache.
 1616        *
 1617        * @param cacheKey the key for the resource bundle
 1618        * @param bundle the resource bundle to be put in the cache
 1619        * @return the ResourceBundle for the cacheKey; if someone has put
 1620        * the bundle before this call, the one found in the cache is
 1621        * returned.
 1622        */
 1623       private static final ResourceBundle putBundleInCache(CacheKey cacheKey,
 1624                                                            ResourceBundle bundle,
 1625                                                            Control control) {
 1626           setExpirationTime(cacheKey, control);
 1627           if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) {
 1628               CacheKey key = (CacheKey) cacheKey.clone();
 1629               BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key);
 1630               bundle.cacheKey = key;
 1631   
 1632               // Put the bundle in the cache if it's not been in the cache.
 1633               BundleReference result = cacheList.putIfAbsent(key, bundleRef);
 1634   
 1635               // If someone else has put the same bundle in the cache before
 1636               // us and it has not expired, we should use the one in the cache.
 1637               if (result != null) {
 1638                   ResourceBundle rb = result.get();
 1639                   if (rb != null && !rb.expired) {
 1640                       // Clear the back link to the cache key
 1641                       bundle.cacheKey = null;
 1642                       bundle = rb;
 1643                       // Clear the reference in the BundleReference so that
 1644                       // it won't be enqueued.
 1645                       bundleRef.clear();
 1646                   } else {
 1647                       // Replace the invalid (garbage collected or expired)
 1648                       // instance with the valid one.
 1649                       cacheList.put(key, bundleRef);
 1650                   }
 1651               }
 1652           }
 1653           return bundle;
 1654       }
 1655   
 1656       private static final void setExpirationTime(CacheKey cacheKey, Control control) {
 1657           long ttl = control.getTimeToLive(cacheKey.getName(),
 1658                                            cacheKey.getLocale());
 1659           if (ttl >= 0) {
 1660               // If any expiration time is specified, set the time to be
 1661               // expired in the cache.
 1662               long now = System.currentTimeMillis();
 1663               cacheKey.loadTime = now;
 1664               cacheKey.expirationTime = now + ttl;
 1665           } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
 1666               cacheKey.expirationTime = ttl;
 1667           } else {
 1668               throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
 1669           }
 1670       }
 1671   
 1672       /**
 1673        * Removes all resource bundles from the cache that have been loaded
 1674        * using the caller's class loader.
 1675        *
 1676        * @since 1.6
 1677        * @see ResourceBundle.Control#getTimeToLive(String,Locale)
 1678        */
 1679       public static final void clearCache() {
 1680           clearCache(getLoader());
 1681       }
 1682   
 1683       /**
 1684        * Removes all resource bundles from the cache that have been loaded
 1685        * using the given class loader.
 1686        *
 1687        * @param loader the class loader
 1688        * @exception NullPointerException if <code>loader</code> is null
 1689        * @since 1.6
 1690        * @see ResourceBundle.Control#getTimeToLive(String,Locale)
 1691        */
 1692       public static final void clearCache(ClassLoader loader) {
 1693           if (loader == null) {
 1694               throw new NullPointerException();
 1695           }
 1696           Set<CacheKey> set = cacheList.keySet();
 1697           for (CacheKey key : set) {
 1698               if (key.getLoader() == loader) {
 1699                   set.remove(key);
 1700               }
 1701           }
 1702       }
 1703   
 1704       /**
 1705        * Gets an object for the given key from this resource bundle.
 1706        * Returns null if this resource bundle does not contain an
 1707        * object for the given key.
 1708        *
 1709        * @param key the key for the desired object
 1710        * @exception NullPointerException if <code>key</code> is <code>null</code>
 1711        * @return the object for the given key, or null
 1712        */
 1713       protected abstract Object handleGetObject(String key);
 1714   
 1715       /**
 1716        * Returns an enumeration of the keys.
 1717        *
 1718        * @return an <code>Enumeration</code> of the keys contained in
 1719        *         this <code>ResourceBundle</code> and its parent bundles.
 1720        */
 1721       public abstract Enumeration<String> getKeys();
 1722   
 1723       /**
 1724        * Determines whether the given <code>key</code> is contained in
 1725        * this <code>ResourceBundle</code> or its parent bundles.
 1726        *
 1727        * @param key
 1728        *        the resource <code>key</code>
 1729        * @return <code>true</code> if the given <code>key</code> is
 1730        *        contained in this <code>ResourceBundle</code> or its
 1731        *        parent bundles; <code>false</code> otherwise.
 1732        * @exception NullPointerException
 1733        *         if <code>key</code> is <code>null</code>
 1734        * @since 1.6
 1735        */
 1736       public boolean containsKey(String key) {
 1737           if (key == null) {
 1738               throw new NullPointerException();
 1739           }
 1740           for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
 1741               if (rb.handleKeySet().contains(key)) {
 1742                   return true;
 1743               }
 1744           }
 1745           return false;
 1746       }
 1747   
 1748       /**
 1749        * Returns a <code>Set</code> of all keys contained in this
 1750        * <code>ResourceBundle</code> and its parent bundles.
 1751        *
 1752        * @return a <code>Set</code> of all keys contained in this
 1753        *         <code>ResourceBundle</code> and its parent bundles.
 1754        * @since 1.6
 1755        */
 1756       public Set<String> keySet() {
 1757           Set<String> keys = new HashSet<>();
 1758           for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
 1759               keys.addAll(rb.handleKeySet());
 1760           }
 1761           return keys;
 1762       }
 1763   
 1764       /**
 1765        * Returns a <code>Set</code> of the keys contained <em>only</em>
 1766        * in this <code>ResourceBundle</code>.
 1767        *
 1768        * <p>The default implementation returns a <code>Set</code> of the
 1769        * keys returned by the {@link #getKeys() getKeys} method except
 1770        * for the ones for which the {@link #handleGetObject(String)
 1771        * handleGetObject} method returns <code>null</code>. Once the
 1772        * <code>Set</code> has been created, the value is kept in this
 1773        * <code>ResourceBundle</code> in order to avoid producing the
 1774        * same <code>Set</code> in subsequent calls. Subclasses can
 1775        * override this method for faster handling.
 1776        *
 1777        * @return a <code>Set</code> of the keys contained only in this
 1778        *        <code>ResourceBundle</code>
 1779        * @since 1.6
 1780        */
 1781       protected Set<String> handleKeySet() {
 1782           if (keySet == null) {
 1783               synchronized (this) {
 1784                   if (keySet == null) {
 1785                       Set<String> keys = new HashSet<>();
 1786                       Enumeration<String> enumKeys = getKeys();
 1787                       while (enumKeys.hasMoreElements()) {
 1788                           String key = enumKeys.nextElement();
 1789                           if (handleGetObject(key) != null) {
 1790                               keys.add(key);
 1791                           }
 1792                       }
 1793                       keySet = keys;
 1794                   }
 1795               }
 1796           }
 1797           return keySet;
 1798       }
 1799   
 1800   
 1801   
 1802       /**
 1803        * <code>ResourceBundle.Control</code> defines a set of callback methods
 1804        * that are invoked by the {@link ResourceBundle#getBundle(String,
 1805        * Locale, ClassLoader, Control) ResourceBundle.getBundle} factory
 1806        * methods during the bundle loading process. In other words, a
 1807        * <code>ResourceBundle.Control</code> collaborates with the factory
 1808        * methods for loading resource bundles. The default implementation of
 1809        * the callback methods provides the information necessary for the
 1810        * factory methods to perform the <a
 1811        * href="./ResourceBundle.html#default_behavior">default behavior</a>.
 1812        *
 1813        * <p>In addition to the callback methods, the {@link
 1814        * #toBundleName(String, Locale) toBundleName} and {@link
 1815        * #toResourceName(String, String) toResourceName} methods are defined
 1816        * primarily for convenience in implementing the callback
 1817        * methods. However, the <code>toBundleName</code> method could be
 1818        * overridden to provide different conventions in the organization and
 1819        * packaging of localized resources.  The <code>toResourceName</code>
 1820        * method is <code>final</code> to avoid use of wrong resource and class
 1821        * name separators.
 1822        *
 1823        * <p>Two factory methods, {@link #getControl(List)} and {@link
 1824        * #getNoFallbackControl(List)}, provide
 1825        * <code>ResourceBundle.Control</code> instances that implement common
 1826        * variations of the default bundle loading process.
 1827        *
 1828        * <p>The formats returned by the {@link Control#getFormats(String)
 1829        * getFormats} method and candidate locales returned by the {@link
 1830        * ResourceBundle.Control#getCandidateLocales(String, Locale)
 1831        * getCandidateLocales} method must be consistent in all
 1832        * <code>ResourceBundle.getBundle</code> invocations for the same base
 1833        * bundle. Otherwise, the <code>ResourceBundle.getBundle</code> methods
 1834        * may return unintended bundles. For example, if only
 1835        * <code>"java.class"</code> is returned by the <code>getFormats</code>
 1836        * method for the first call to <code>ResourceBundle.getBundle</code>
 1837        * and only <code>"java.properties"</code> for the second call, then the
 1838        * second call will return the class-based one that has been cached
 1839        * during the first call.
 1840        *
 1841        * <p>A <code>ResourceBundle.Control</code> instance must be thread-safe
 1842        * if it's simultaneously used by multiple threads.
 1843        * <code>ResourceBundle.getBundle</code> does not synchronize to call
 1844        * the <code>ResourceBundle.Control</code> methods. The default
 1845        * implementations of the methods are thread-safe.
 1846        *
 1847        * <p>Applications can specify <code>ResourceBundle.Control</code>
 1848        * instances returned by the <code>getControl</code> factory methods or
 1849        * created from a subclass of <code>ResourceBundle.Control</code> to
 1850        * customize the bundle loading process. The following are examples of
 1851        * changing the default bundle loading process.
 1852        *
 1853        * <p><b>Example 1</b>
 1854        *
 1855        * <p>The following code lets <code>ResourceBundle.getBundle</code> look
 1856        * up only properties-based resources.
 1857        *
 1858        * <pre>
 1859        * import java.util.*;
 1860        * import static java.util.ResourceBundle.Control.*;
 1861        * ...
 1862        * ResourceBundle bundle =
 1863        *   ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"),
 1864        *                            ResourceBundle.Control.getControl(FORMAT_PROPERTIES));
 1865        * </pre>
 1866        *
 1867        * Given the resource bundles in the <a
 1868        * href="./ResourceBundle.html#default_behavior_example">example</a> in
 1869        * the <code>ResourceBundle.getBundle</code> description, this
 1870        * <code>ResourceBundle.getBundle</code> call loads
 1871        * <code>MyResources_fr_CH.properties</code> whose parent is
 1872        * <code>MyResources_fr.properties</code> whose parent is
 1873        * <code>MyResources.properties</code>. (<code>MyResources_fr_CH.properties</code>
 1874        * is not hidden, but <code>MyResources_fr_CH.class</code> is.)
 1875        *
 1876        * <p><b>Example 2</b>
 1877        *
 1878        * <p>The following is an example of loading XML-based bundles
 1879        * using {@link Properties#loadFromXML(java.io.InputStream)
 1880        * Properties.loadFromXML}.
 1881        *
 1882        * <pre>
 1883        * ResourceBundle rb = ResourceBundle.getBundle("Messages",
 1884        *     new ResourceBundle.Control() {
 1885        *         public List&lt;String&gt; getFormats(String baseName) {
 1886        *             if (baseName == null)
 1887        *                 throw new NullPointerException();
 1888        *             return Arrays.asList("xml");
 1889        *         }
 1890        *         public ResourceBundle newBundle(String baseName,
 1891        *                                         Locale locale,
 1892        *                                         String format,
 1893        *                                         ClassLoader loader,
 1894        *                                         boolean reload)
 1895        *                          throws IllegalAccessException,
 1896        *                                 InstantiationException,
 1897        *                                 IOException {
 1898        *             if (baseName == null || locale == null
 1899        *                   || format == null || loader == null)
 1900        *                 throw new NullPointerException();
 1901        *             ResourceBundle bundle = null;
 1902        *             if (format.equals("xml")) {
 1903        *                 String bundleName = toBundleName(baseName, locale);
 1904        *                 String resourceName = toResourceName(bundleName, format);
 1905        *                 InputStream stream = null;
 1906        *                 if (reload) {
 1907        *                     URL url = loader.getResource(resourceName);
 1908        *                     if (url != null) {
 1909        *                         URLConnection connection = url.openConnection();
 1910        *                         if (connection != null) {
 1911        *                             // Disable caches to get fresh data for
 1912        *                             // reloading.
 1913        *                             connection.setUseCaches(false);
 1914        *                             stream = connection.getInputStream();
 1915        *                         }
 1916        *                     }
 1917        *                 } else {
 1918        *                     stream = loader.getResourceAsStream(resourceName);
 1919        *                 }
 1920        *                 if (stream != null) {
 1921        *                     BufferedInputStream bis = new BufferedInputStream(stream);
 1922        *                     bundle = new XMLResourceBundle(bis);
 1923        *                     bis.close();
 1924        *                 }
 1925        *             }
 1926        *             return bundle;
 1927        *         }
 1928        *     });
 1929        *
 1930        * ...
 1931        *
 1932        * private static class XMLResourceBundle extends ResourceBundle {
 1933        *     private Properties props;
 1934        *     XMLResourceBundle(InputStream stream) throws IOException {
 1935        *         props = new Properties();
 1936        *         props.loadFromXML(stream);
 1937        *     }
 1938        *     protected Object handleGetObject(String key) {
 1939        *         return props.getProperty(key);
 1940        *     }
 1941        *     public Enumeration&lt;String&gt; getKeys() {
 1942        *         ...
 1943        *     }
 1944        * }
 1945        * </pre>
 1946        *
 1947        * @since 1.6
 1948        */
 1949       public static class Control {
 1950           /**
 1951            * The default format <code>List</code>, which contains the strings
 1952            * <code>"java.class"</code> and <code>"java.properties"</code>, in
 1953            * this order. This <code>List</code> is {@linkplain
 1954            * Collections#unmodifiableList(List) unmodifiable}.
 1955            *
 1956            * @see #getFormats(String)
 1957            */
 1958           public static final List<String> FORMAT_DEFAULT
 1959               = Collections.unmodifiableList(Arrays.asList("java.class",
 1960                                                            "java.properties"));
 1961   
 1962           /**
 1963            * The class-only format <code>List</code> containing
 1964            * <code>"java.class"</code>. This <code>List</code> is {@linkplain
 1965            * Collections#unmodifiableList(List) unmodifiable}.
 1966            *
 1967            * @see #getFormats(String)
 1968            */
 1969           public static final List<String> FORMAT_CLASS
 1970               = Collections.unmodifiableList(Arrays.asList("java.class"));
 1971   
 1972           /**
 1973            * The properties-only format <code>List</code> containing
 1974            * <code>"java.properties"</code>. This <code>List</code> is
 1975            * {@linkplain Collections#unmodifiableList(List) unmodifiable}.
 1976            *
 1977            * @see #getFormats(String)
 1978            */
 1979           public static final List<String> FORMAT_PROPERTIES
 1980               = Collections.unmodifiableList(Arrays.asList("java.properties"));
 1981   
 1982           /**
 1983            * The time-to-live constant for not caching loaded resource bundle
 1984            * instances.
 1985            *
 1986            * @see #getTimeToLive(String, Locale)
 1987            */
 1988           public static final long TTL_DONT_CACHE = -1;
 1989   
 1990           /**
 1991            * The time-to-live constant for disabling the expiration control
 1992            * for loaded resource bundle instances in the cache.
 1993            *
 1994            * @see #getTimeToLive(String, Locale)
 1995            */
 1996           public static final long TTL_NO_EXPIRATION_CONTROL = -2;
 1997   
 1998           private static final Control INSTANCE = new Control();
 1999   
 2000           /**
 2001            * Sole constructor. (For invocation by subclass constructors,
 2002            * typically implicit.)
 2003            */
 2004           protected Control() {
 2005           }
 2006   
 2007           /**
 2008            * Returns a <code>ResourceBundle.Control</code> in which the {@link
 2009            * #getFormats(String) getFormats} method returns the specified
 2010            * <code>formats</code>. The <code>formats</code> must be equal to
 2011            * one of {@link Control#FORMAT_PROPERTIES}, {@link
 2012            * Control#FORMAT_CLASS} or {@link
 2013            * Control#FORMAT_DEFAULT}. <code>ResourceBundle.Control</code>
 2014            * instances returned by this method are singletons and thread-safe.
 2015            *
 2016            * <p>Specifying {@link Control#FORMAT_DEFAULT} is equivalent to
 2017            * instantiating the <code>ResourceBundle.Control</code> class,
 2018            * except that this method returns a singleton.
 2019            *
 2020            * @param formats
 2021            *        the formats to be returned by the
 2022            *        <code>ResourceBundle.Control.getFormats</code> method
 2023            * @return a <code>ResourceBundle.Control</code> supporting the
 2024            *        specified <code>formats</code>
 2025            * @exception NullPointerException
 2026            *        if <code>formats</code> is <code>null</code>
 2027            * @exception IllegalArgumentException
 2028            *        if <code>formats</code> is unknown
 2029            */
 2030           public static final Control getControl(List<String> formats) {
 2031               if (formats.equals(Control.FORMAT_PROPERTIES)) {
 2032                   return SingleFormatControl.PROPERTIES_ONLY;
 2033               }
 2034               if (formats.equals(Control.FORMAT_CLASS)) {
 2035                   return SingleFormatControl.CLASS_ONLY;
 2036               }
 2037               if (formats.equals(Control.FORMAT_DEFAULT)) {
 2038                   return Control.INSTANCE;
 2039               }
 2040               throw new IllegalArgumentException();
 2041           }
 2042   
 2043           /**
 2044            * Returns a <code>ResourceBundle.Control</code> in which the {@link
 2045            * #getFormats(String) getFormats} method returns the specified
 2046            * <code>formats</code> and the {@link
 2047            * Control#getFallbackLocale(String, Locale) getFallbackLocale}
 2048            * method returns <code>null</code>. The <code>formats</code> must
 2049            * be equal to one of {@link Control#FORMAT_PROPERTIES}, {@link
 2050            * Control#FORMAT_CLASS} or {@link Control#FORMAT_DEFAULT}.
 2051            * <code>ResourceBundle.Control</code> instances returned by this
 2052            * method are singletons and thread-safe.
 2053            *
 2054            * @param formats
 2055            *        the formats to be returned by the
 2056            *        <code>ResourceBundle.Control.getFormats</code> method
 2057            * @return a <code>ResourceBundle.Control</code> supporting the
 2058            *        specified <code>formats</code> with no fallback
 2059            *        <code>Locale</code> support
 2060            * @exception NullPointerException
 2061            *        if <code>formats</code> is <code>null</code>
 2062            * @exception IllegalArgumentException
 2063            *        if <code>formats</code> is unknown
 2064            */
 2065           public static final Control getNoFallbackControl(List<String> formats) {
 2066               if (formats.equals(Control.FORMAT_DEFAULT)) {
 2067                   return NoFallbackControl.NO_FALLBACK;
 2068               }
 2069               if (formats.equals(Control.FORMAT_PROPERTIES)) {
 2070                   return NoFallbackControl.PROPERTIES_ONLY_NO_FALLBACK;
 2071               }
 2072               if (formats.equals(Control.FORMAT_CLASS)) {
 2073                   return NoFallbackControl.CLASS_ONLY_NO_FALLBACK;
 2074               }
 2075               throw new IllegalArgumentException();
 2076           }
 2077   
 2078           /**
 2079            * Returns a <code>List</code> of <code>String</code>s containing
 2080            * formats to be used to load resource bundles for the given
 2081            * <code>baseName</code>. The <code>ResourceBundle.getBundle</code>
 2082            * factory method tries to load resource bundles with formats in the
 2083            * order specified by the list. The list returned by this method
 2084            * must have at least one <code>String</code>. The predefined
 2085            * formats are <code>"java.class"</code> for class-based resource
 2086            * bundles and <code>"java.properties"</code> for {@linkplain
 2087            * PropertyResourceBundle properties-based} ones. Strings starting
 2088            * with <code>"java."</code> are reserved for future extensions and
 2089            * must not be used by application-defined formats.
 2090            *
 2091            * <p>It is not a requirement to return an immutable (unmodifiable)
 2092            * <code>List</code>.  However, the returned <code>List</code> must
 2093            * not be mutated after it has been returned by
 2094            * <code>getFormats</code>.
 2095            *
 2096            * <p>The default implementation returns {@link #FORMAT_DEFAULT} so
 2097            * that the <code>ResourceBundle.getBundle</code> factory method
 2098            * looks up first class-based resource bundles, then
 2099            * properties-based ones.
 2100            *
 2101            * @param baseName
 2102            *        the base name of the resource bundle, a fully qualified class
 2103            *        name
 2104            * @return a <code>List</code> of <code>String</code>s containing
 2105            *        formats for loading resource bundles.
 2106            * @exception NullPointerException
 2107            *        if <code>baseName</code> is null
 2108            * @see #FORMAT_DEFAULT
 2109            * @see #FORMAT_CLASS
 2110            * @see #FORMAT_PROPERTIES
 2111            */
 2112           public List<String> getFormats(String baseName) {
 2113               if (baseName == null) {
 2114                   throw new NullPointerException();
 2115               }
 2116               return FORMAT_DEFAULT;
 2117           }
 2118   
 2119           /**
 2120            * Returns a <code>List</code> of <code>Locale</code>s as candidate
 2121            * locales for <code>baseName</code> and <code>locale</code>. This
 2122            * method is called by the <code>ResourceBundle.getBundle</code>
 2123            * factory method each time the factory method tries finding a
 2124            * resource bundle for a target <code>Locale</code>.
 2125            *
 2126            * <p>The sequence of the candidate locales also corresponds to the
 2127            * runtime resource lookup path (also known as the <I>parent
 2128            * chain</I>), if the corresponding resource bundles for the
 2129            * candidate locales exist and their parents are not defined by
 2130            * loaded resource bundles themselves.  The last element of the list
 2131            * must be a {@linkplain Locale#ROOT root locale} if it is desired to
 2132            * have the base bundle as the terminal of the parent chain.
 2133            *
 2134            * <p>If the given locale is equal to <code>Locale.ROOT</code> (the
 2135            * root locale), a <code>List</code> containing only the root
 2136            * <code>Locale</code> must be returned. In this case, the
 2137            * <code>ResourceBundle.getBundle</code> factory method loads only
 2138            * the base bundle as the resulting resource bundle.
 2139            *
 2140            * <p>It is not a requirement to return an immutable (unmodifiable)
 2141            * <code>List</code>. However, the returned <code>List</code> must not
 2142            * be mutated after it has been returned by
 2143            * <code>getCandidateLocales</code>.
 2144            *
 2145            * <p>The default implementation returns a <code>List</code> containing
 2146            * <code>Locale</code>s using the rules described below.  In the
 2147            * description below, <em>L</em>, <em>S</em>, <em>C</em> and <em>V</em>
 2148            * respectively represent non-empty language, script, country, and
 2149            * variant.  For example, [<em>L</em>, <em>C</em>] represents a
 2150            * <code>Locale</code> that has non-empty values only for language and
 2151            * country.  The form <em>L</em>("xx") represents the (non-empty)
 2152            * language value is "xx".  For all cases, <code>Locale</code>s whose
 2153            * final component values are empty strings are omitted.
 2154            *
 2155            * <ol><li>For an input <code>Locale</code> with an empty script value,
 2156            * append candidate <code>Locale</code>s by omitting the final component
 2157            * one by one as below:
 2158            *
 2159            * <ul>
 2160            * <li> [<em>L</em>, <em>C</em>, <em>V</em>]
 2161            * <li> [<em>L</em>, <em>C</em>]
 2162            * <li> [<em>L</em>]
 2163            * <li> <code>Locale.ROOT</code>
 2164            * </ul>
 2165            *
 2166            * <li>For an input <code>Locale</code> with a non-empty script value,
 2167            * append candidate <code>Locale</code>s by omitting the final component
 2168            * up to language, then append candidates generated from the
 2169            * <code>Locale</code> with country and variant restored:
 2170            *
 2171            * <ul>
 2172            * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V</em>]
 2173            * <li> [<em>L</em>, <em>S</em>, <em>C</em>]
 2174            * <li> [<em>L</em>, <em>S</em>]
 2175            * <li> [<em>L</em>, <em>C</em>, <em>V</em>]
 2176            * <li> [<em>L</em>, <em>C</em>]
 2177            * <li> [<em>L</em>]
 2178            * <li> <code>Locale.ROOT</code>
 2179            * </ul>
 2180            *
 2181            * <li>For an input <code>Locale</code> with a variant value consisting
 2182            * of multiple subtags separated by underscore, generate candidate
 2183            * <code>Locale</code>s by omitting the variant subtags one by one, then
 2184            * insert them after every occurence of <code> Locale</code>s with the
 2185            * full variant value in the original list.  For example, if the
 2186            * the variant consists of two subtags <em>V1</em> and <em>V2</em>:
 2187            *
 2188            * <ul>
 2189            * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]
 2190            * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>]
 2191            * <li> [<em>L</em>, <em>S</em>, <em>C</em>]
 2192            * <li> [<em>L</em>, <em>S</em>]
 2193            * <li> [<em>L</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]
 2194            * <li> [<em>L</em>, <em>C</em>, <em>V1</em>]
 2195            * <li> [<em>L</em>, <em>C</em>]
 2196            * <li> [<em>L</em>]
 2197            * <li> <code>Locale.ROOT</code>
 2198            * </ul>
 2199            *
 2200            * <li>Special cases for Chinese.  When an input <code>Locale</code> has the
 2201            * language "zh" (Chinese) and an empty script value, either "Hans" (Simplified) or
 2202            * "Hant" (Traditional) might be supplied, depending on the country.
 2203            * When the country is "CN" (China) or "SG" (Singapore), "Hans" is supplied.
 2204            * When the country is "HK" (Hong Kong SAR China), "MO" (Macau SAR China),
 2205            * or "TW" (Taiwan), "Hant" is supplied.  For all other countries or when the country
 2206            * is empty, no script is supplied.  For example, for <code>Locale("zh", "CN")
 2207            * </code>, the candidate list will be:
 2208            * <ul>
 2209            * <li> [<em>L</em>("zh"), <em>S</em>("Hans"), <em>C</em>("CN")]
 2210            * <li> [<em>L</em>("zh"), <em>S</em>("Hans")]
 2211            * <li> [<em>L</em>("zh"), <em>C</em>("CN")]
 2212            * <li> [<em>L</em>("zh")]
 2213            * <li> <code>Locale.ROOT</code>
 2214            * </ul>
 2215            *
 2216            * For <code>Locale("zh", "TW")</code>, the candidate list will be:
 2217            * <ul>
 2218            * <li> [<em>L</em>("zh"), <em>S</em>("Hant"), <em>C</em>("TW")]
 2219            * <li> [<em>L</em>("zh"), <em>S</em>("Hant")]
 2220            * <li> [<em>L</em>("zh"), <em>C</em>("TW")]
 2221            * <li> [<em>L</em>("zh")]
 2222            * <li> <code>Locale.ROOT</code>
 2223            * </ul>
 2224            *
 2225            * <li>Special cases for Norwegian.  Both <code>Locale("no", "NO",
 2226            * "NY")</code> and <code>Locale("nn", "NO")</code> represent Norwegian
 2227            * Nynorsk.  When a locale's language is "nn", the standard candidate
 2228            * list is generated up to [<em>L</em>("nn")], and then the following
 2229            * candidates are added:
 2230            *
 2231            * <ul><li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("NY")]
 2232            * <li> [<em>L</em>("no"), <em>C</em>("NO")]
 2233            * <li> [<em>L</em>("no")]
 2234            * <li> <code>Locale.ROOT</code>
 2235            * </ul>
 2236            *
 2237            * If the locale is exactly <code>Locale("no", "NO", "NY")</code>, it is first
 2238            * converted to <code>Locale("nn", "NO")</code> and then the above procedure is
 2239            * followed.
 2240            *
 2241            * <p>Also, Java treats the language "no" as a synonym of Norwegian
 2242            * Bokm&#xE5;l "nb".  Except for the single case <code>Locale("no",
 2243            * "NO", "NY")</code> (handled above), when an input <code>Locale</code>
 2244            * has language "no" or "nb", candidate <code>Locale</code>s with
 2245            * language code "no" and "nb" are interleaved, first using the
 2246            * requested language, then using its synonym. For example,
 2247            * <code>Locale("nb", "NO", "POSIX")</code> generates the following
 2248            * candidate list:
 2249            *
 2250            * <ul>
 2251            * <li> [<em>L</em>("nb"), <em>C</em>("NO"), <em>V</em>("POSIX")]
 2252            * <li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("POSIX")]
 2253            * <li> [<em>L</em>("nb"), <em>C</em>("NO")]
 2254            * <li> [<em>L</em>("no"), <em>C</em>("NO")]
 2255            * <li> [<em>L</em>("nb")]
 2256            * <li> [<em>L</em>("no")]
 2257            * <li> <code>Locale.ROOT</code>
 2258            * </ul>
 2259            *
 2260            * <code>Locale("no", "NO", "POSIX")</code> would generate the same list
 2261            * except that locales with "no" would appear before the corresponding
 2262            * locales with "nb".</li>
 2263            *
 2264            * </li>
 2265            * </ol>
 2266            *
 2267            * <p>The default implementation uses an {@link ArrayList} that
 2268            * overriding implementations may modify before returning it to the
 2269            * caller. However, a subclass must not modify it after it has
 2270            * been returned by <code>getCandidateLocales</code>.
 2271            *
 2272            * <p>For example, if the given <code>baseName</code> is "Messages"
 2273            * and the given <code>locale</code> is
 2274            * <code>Locale("ja",&nbsp;"",&nbsp;"XX")</code>, then a
 2275            * <code>List</code> of <code>Locale</code>s:
 2276            * <pre>
 2277            *     Locale("ja", "", "XX")
 2278            *     Locale("ja")
 2279            *     Locale.ROOT
 2280            * </pre>
 2281            * is returned. And if the resource bundles for the "ja" and
 2282            * "" <code>Locale</code>s are found, then the runtime resource
 2283            * lookup path (parent chain) is:
 2284            * <pre>
 2285            *     Messages_ja -> Messages
 2286            * </pre>
 2287            *
 2288            * @param baseName
 2289            *        the base name of the resource bundle, a fully
 2290            *        qualified class name
 2291            * @param locale
 2292            *        the locale for which a resource bundle is desired
 2293            * @return a <code>List</code> of candidate
 2294            *        <code>Locale</code>s for the given <code>locale</code>
 2295            * @exception NullPointerException
 2296            *        if <code>baseName</code> or <code>locale</code> is
 2297            *        <code>null</code>
 2298            */
 2299           public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 2300               if (baseName == null) {
 2301                   throw new NullPointerException();
 2302               }
 2303               return new ArrayList<>(CANDIDATES_CACHE.get(locale.getBaseLocale()));
 2304           }
 2305   
 2306           private static final CandidateListCache CANDIDATES_CACHE = new CandidateListCache();
 2307   
 2308           private static class CandidateListCache extends LocaleObjectCache<BaseLocale, List<Locale>> {
 2309               protected List<Locale> createObject(BaseLocale base) {
 2310                   String language = base.getLanguage();
 2311                   String script = base.getScript();
 2312                   String region = base.getRegion();
 2313                   String variant = base.getVariant();
 2314   
 2315                   // Special handling for Norwegian
 2316                   boolean isNorwegianBokmal = false;
 2317                   boolean isNorwegianNynorsk = false;
 2318                   if (language.equals("no")) {
 2319                       if (region.equals("NO") && variant.equals("NY")) {
 2320                           variant = "";
 2321                           isNorwegianNynorsk = true;
 2322                       } else {
 2323                           isNorwegianBokmal = true;
 2324                       }
 2325                   }
 2326                   if (language.equals("nb") || isNorwegianBokmal) {
 2327                       List<Locale> tmpList = getDefaultList("nb", script, region, variant);
 2328                       // Insert a locale replacing "nb" with "no" for every list entry
 2329                       List<Locale> bokmalList = new LinkedList<>();
 2330                       for (Locale l : tmpList) {
 2331                           bokmalList.add(l);
 2332                           if (l.getLanguage().length() == 0) {
 2333                               break;
 2334                           }
 2335                           bokmalList.add(Locale.getInstance("no", l.getScript(), l.getCountry(),
 2336                                   l.getVariant(), null));
 2337                       }
 2338                       return bokmalList;
 2339                   } else if (language.equals("nn") || isNorwegianNynorsk) {
 2340                       // Insert no_NO_NY, no_NO, no after nn
 2341                       List<Locale> nynorskList = getDefaultList("nn", script, region, variant);
 2342                       int idx = nynorskList.size() - 1;
 2343                       nynorskList.add(idx++, Locale.getInstance("no", "NO", "NY"));
 2344                       nynorskList.add(idx++, Locale.getInstance("no", "NO", ""));
 2345                       nynorskList.add(idx++, Locale.getInstance("no", "", ""));
 2346                       return nynorskList;
 2347                   }
 2348                   // Special handling for Chinese
 2349                   else if (language.equals("zh")) {
 2350                       if (script.length() == 0 && region.length() > 0) {
 2351                           // Supply script for users who want to use zh_Hans/zh_Hant
 2352                           // as bundle names (recommended for Java7+)
 2353                           if (region.equals("TW") || region.equals("HK") || region.equals("MO")) {
 2354                               script = "Hant";
 2355                           } else if (region.equals("CN") || region.equals("SG")) {
 2356                               script = "Hans";
 2357                           }
 2358                       } else if (script.length() > 0 && region.length() == 0) {
 2359                           // Supply region(country) for users who still package Chinese
 2360                           // bundles using old convension.
 2361                           if (script.equals("Hans")) {
 2362                               region = "CN";
 2363                           } else if (script.equals("Hant")) {
 2364                               region = "TW";
 2365                           }
 2366                       }
 2367                   }
 2368   
 2369                   return getDefaultList(language, script, region, variant);
 2370               }
 2371   
 2372               private static List<Locale> getDefaultList(String language, String script, String region, String variant) {
 2373                   List<String> variants = null;
 2374   
 2375                   if (variant.length() > 0) {
 2376                       variants = new LinkedList<>();
 2377                       int idx = variant.length();
 2378                       while (idx != -1) {
 2379                           variants.add(variant.substring(0, idx));
 2380                           idx = variant.lastIndexOf('_', --idx);
 2381                       }
 2382                   }
 2383   
 2384                   List<Locale> list = new LinkedList<>();
 2385   
 2386                   if (variants != null) {
 2387                       for (String v : variants) {
 2388                           list.add(Locale.getInstance(language, script, region, v, null));
 2389                       }
 2390                   }
 2391                   if (region.length() > 0) {
 2392                       list.add(Locale.getInstance(language, script, region, "", null));
 2393                   }
 2394                   if (script.length() > 0) {
 2395                       list.add(Locale.getInstance(language, script, "", "", null));
 2396   
 2397                       // With script, after truncating variant, region and script,
 2398                       // start over without script.
 2399                       if (variants != null) {
 2400                           for (String v : variants) {
 2401                               list.add(Locale.getInstance(language, "", region, v, null));
 2402                           }
 2403                       }
 2404                       if (region.length() > 0) {
 2405                           list.add(Locale.getInstance(language, "", region, "", null));
 2406                       }
 2407                   }
 2408                   if (language.length() > 0) {
 2409                       list.add(Locale.getInstance(language, "", "", "", null));
 2410                   }
 2411                   // Add root locale at the end
 2412                   list.add(Locale.ROOT);
 2413   
 2414                   return list;
 2415               }
 2416           }
 2417   
 2418           /**
 2419            * Returns a <code>Locale</code> to be used as a fallback locale for
 2420            * further resource bundle searches by the
 2421            * <code>ResourceBundle.getBundle</code> factory method. This method
 2422            * is called from the factory method every time when no resulting
 2423            * resource bundle has been found for <code>baseName</code> and
 2424            * <code>locale</code>, where locale is either the parameter for
 2425            * <code>ResourceBundle.getBundle</code> or the previous fallback
 2426            * locale returned by this method.
 2427            *
 2428            * <p>The method returns <code>null</code> if no further fallback
 2429            * search is desired.
 2430            *
 2431            * <p>The default implementation returns the {@linkplain
 2432            * Locale#getDefault() default <code>Locale</code>} if the given
 2433            * <code>locale</code> isn't the default one.  Otherwise,
 2434            * <code>null</code> is returned.
 2435            *
 2436            * @param baseName
 2437            *        the base name of the resource bundle, a fully
 2438            *        qualified class name for which
 2439            *        <code>ResourceBundle.getBundle</code> has been
 2440            *        unable to find any resource bundles (except for the
 2441            *        base bundle)
 2442            * @param locale
 2443            *        the <code>Locale</code> for which
 2444            *        <code>ResourceBundle.getBundle</code> has been
 2445            *        unable to find any resource bundles (except for the
 2446            *        base bundle)
 2447            * @return a <code>Locale</code> for the fallback search,
 2448            *        or <code>null</code> if no further fallback search
 2449            *        is desired.
 2450            * @exception NullPointerException
 2451            *        if <code>baseName</code> or <code>locale</code>
 2452            *        is <code>null</code>
 2453            */
 2454           public Locale getFallbackLocale(String baseName, Locale locale) {
 2455               if (baseName == null) {
 2456                   throw new NullPointerException();
 2457               }
 2458               Locale defaultLocale = Locale.getDefault();
 2459               return locale.equals(defaultLocale) ? null : defaultLocale;
 2460           }
 2461   
 2462           /**
 2463            * Instantiates a resource bundle for the given bundle name of the
 2464            * given format and locale, using the given class loader if
 2465            * necessary. This method returns <code>null</code> if there is no
 2466            * resource bundle available for the given parameters. If a resource
 2467            * bundle can't be instantiated due to an unexpected error, the
 2468            * error must be reported by throwing an <code>Error</code> or
 2469            * <code>Exception</code> rather than simply returning
 2470            * <code>null</code>.
 2471            *
 2472            * <p>If the <code>reload</code> flag is <code>true</code>, it
 2473            * indicates that this method is being called because the previously
 2474            * loaded resource bundle has expired.
 2475            *
 2476            * <p>The default implementation instantiates a
 2477            * <code>ResourceBundle</code> as follows.
 2478            *
 2479            * <ul>
 2480            *
 2481            * <li>The bundle name is obtained by calling {@link
 2482            * #toBundleName(String, Locale) toBundleName(baseName,
 2483            * locale)}.</li>
 2484            *
 2485            * <li>If <code>format</code> is <code>"java.class"</code>, the
 2486            * {@link Class} specified by the bundle name is loaded by calling
 2487            * {@link ClassLoader#loadClass(String)}. Then, a
 2488            * <code>ResourceBundle</code> is instantiated by calling {@link
 2489            * Class#newInstance()}.  Note that the <code>reload</code> flag is
 2490            * ignored for loading class-based resource bundles in this default
 2491            * implementation.</li>
 2492            *
 2493            * <li>If <code>format</code> is <code>"java.properties"</code>,
 2494            * {@link #toResourceName(String, String) toResourceName(bundlename,
 2495            * "properties")} is called to get the resource name.
 2496            * If <code>reload</code> is <code>true</code>, {@link
 2497            * ClassLoader#getResource(String) load.getResource} is called
 2498            * to get a {@link URL} for creating a {@link
 2499            * URLConnection}. This <code>URLConnection</code> is used to
 2500            * {@linkplain URLConnection#setUseCaches(boolean) disable the
 2501            * caches} of the underlying resource loading layers,
 2502            * and to {@linkplain URLConnection#getInputStream() get an
 2503            * <code>InputStream</code>}.
 2504            * Otherwise, {@link ClassLoader#getResourceAsStream(String)
 2505            * loader.getResourceAsStream} is called to get an {@link
 2506            * InputStream}. Then, a {@link
 2507            * PropertyResourceBundle} is constructed with the
 2508            * <code>InputStream</code>.</li>
 2509            *
 2510            * <li>If <code>format</code> is neither <code>"java.class"</code>
 2511            * nor <code>"java.properties"</code>, an
 2512            * <code>IllegalArgumentException</code> is thrown.</li>
 2513            *
 2514            * </ul>
 2515            *
 2516            * @param baseName
 2517            *        the base bundle name of the resource bundle, a fully
 2518            *        qualified class name
 2519            * @param locale
 2520            *        the locale for which the resource bundle should be
 2521            *        instantiated
 2522            * @param format
 2523            *        the resource bundle format to be loaded
 2524            * @param loader
 2525            *        the <code>ClassLoader</code> to use to load the bundle
 2526            * @param reload
 2527            *        the flag to indicate bundle reloading; <code>true</code>
 2528            *        if reloading an expired resource bundle,
 2529            *        <code>false</code> otherwise
 2530            * @return the resource bundle instance,
 2531            *        or <code>null</code> if none could be found.
 2532            * @exception NullPointerException
 2533            *        if <code>bundleName</code>, <code>locale</code>,
 2534            *        <code>format</code>, or <code>loader</code> is
 2535            *        <code>null</code>, or if <code>null</code> is returned by
 2536            *        {@link #toBundleName(String, Locale) toBundleName}
 2537            * @exception IllegalArgumentException
 2538            *        if <code>format</code> is unknown, or if the resource
 2539            *        found for the given parameters contains malformed data.
 2540            * @exception ClassCastException
 2541            *        if the loaded class cannot be cast to <code>ResourceBundle</code>
 2542            * @exception IllegalAccessException
 2543            *        if the class or its nullary constructor is not
 2544            *        accessible.
 2545            * @exception InstantiationException
 2546            *        if the instantiation of a class fails for some other
 2547            *        reason.
 2548            * @exception ExceptionInInitializerError
 2549            *        if the initialization provoked by this method fails.
 2550            * @exception SecurityException
 2551            *        If a security manager is present and creation of new
 2552            *        instances is denied. See {@link Class#newInstance()}
 2553            *        for details.
 2554            * @exception IOException
 2555            *        if an error occurred when reading resources using
 2556            *        any I/O operations
 2557            */
 2558           public ResourceBundle newBundle(String baseName, Locale locale, String format,
 2559                                           ClassLoader loader, boolean reload)
 2560                       throws IllegalAccessException, InstantiationException, IOException {
 2561               String bundleName = toBundleName(baseName, locale);
 2562               ResourceBundle bundle = null;
 2563               if (format.equals("java.class")) {
 2564                   try {
 2565                       Class<? extends ResourceBundle> bundleClass
 2566                           = (Class<? extends ResourceBundle>)loader.loadClass(bundleName);
 2567   
 2568                       // If the class isn't a ResourceBundle subclass, throw a
 2569                       // ClassCastException.
 2570                       if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
 2571                           bundle = bundleClass.newInstance();
 2572                       } else {
 2573                           throw new ClassCastException(bundleClass.getName()
 2574                                        + " cannot be cast to ResourceBundle");
 2575                       }
 2576                   } catch (ClassNotFoundException e) {
 2577                   }
 2578               } else if (format.equals("java.properties")) {
 2579                   final String resourceName = toResourceName(bundleName, "properties");
 2580                   final ClassLoader classLoader = loader;
 2581                   final boolean reloadFlag = reload;
 2582                   InputStream stream = null;
 2583                   try {
 2584                       stream = AccessController.doPrivileged(
 2585                           new PrivilegedExceptionAction<InputStream>() {
 2586                               public InputStream run() throws IOException {
 2587                                   InputStream is = null;
 2588                                   if (reloadFlag) {
 2589                                       URL url = classLoader.getResource(resourceName);
 2590                                       if (url != null) {
 2591                                           URLConnection connection = url.openConnection();
 2592                                           if (connection != null) {
 2593                                               // Disable caches to get fresh data for
 2594                                               // reloading.
 2595                                               connection.setUseCaches(false);
 2596                                               is = connection.getInputStream();
 2597                                           }
 2598                                       }
 2599                                   } else {
 2600                                       is = classLoader.getResourceAsStream(resourceName);
 2601                                   }
 2602                                   return is;
 2603                               }
 2604                           });
 2605                   } catch (PrivilegedActionException e) {
 2606                       throw (IOException) e.getException();
 2607                   }
 2608                   if (stream != null) {
 2609                       try {
 2610                           bundle = new PropertyResourceBundle(stream);
 2611                       } finally {
 2612                           stream.close();
 2613                       }
 2614                   }
 2615               } else {
 2616                   throw new IllegalArgumentException("unknown format: " + format);
 2617               }
 2618               return bundle;
 2619           }
 2620   
 2621           /**
 2622            * Returns the time-to-live (TTL) value for resource bundles that
 2623            * are loaded under this
 2624            * <code>ResourceBundle.Control</code>. Positive time-to-live values
 2625            * specify the number of milliseconds a bundle can remain in the
 2626            * cache without being validated against the source data from which
 2627            * it was constructed. The value 0 indicates that a bundle must be
 2628            * validated each time it is retrieved from the cache. {@link
 2629            * #TTL_DONT_CACHE} specifies that loaded resource bundles are not
 2630            * put in the cache. {@link #TTL_NO_EXPIRATION_CONTROL} specifies
 2631            * that loaded resource bundles are put in the cache with no
 2632            * expiration control.
 2633            *
 2634            * <p>The expiration affects only the bundle loading process by the
 2635            * <code>ResourceBundle.getBundle</code> factory method.  That is,
 2636            * if the factory method finds a resource bundle in the cache that
 2637            * has expired, the factory method calls the {@link
 2638            * #needsReload(String, Locale, String, ClassLoader, ResourceBundle,
 2639            * long) needsReload} method to determine whether the resource
 2640            * bundle needs to be reloaded. If <code>needsReload</code> returns
 2641            * <code>true</code>, the cached resource bundle instance is removed
 2642            * from the cache. Otherwise, the instance stays in the cache,
 2643            * updated with the new TTL value returned by this method.
 2644            *
 2645            * <p>All cached resource bundles are subject to removal from the
 2646            * cache due to memory constraints of the runtime environment.
 2647            * Returning a large positive value doesn't mean to lock loaded
 2648            * resource bundles in the cache.
 2649            *
 2650            * <p>The default implementation returns {@link #TTL_NO_EXPIRATION_CONTROL}.
 2651            *
 2652            * @param baseName
 2653            *        the base name of the resource bundle for which the
 2654            *        expiration value is specified.
 2655            * @param locale
 2656            *        the locale of the resource bundle for which the
 2657            *        expiration value is specified.
 2658            * @return the time (0 or a positive millisecond offset from the
 2659            *        cached time) to get loaded bundles expired in the cache,
 2660            *        {@link #TTL_NO_EXPIRATION_CONTROL} to disable the
 2661            *        expiration control, or {@link #TTL_DONT_CACHE} to disable
 2662            *        caching.
 2663            * @exception NullPointerException
 2664            *        if <code>baseName</code> or <code>locale</code> is
 2665            *        <code>null</code>
 2666            */
 2667           public long getTimeToLive(String baseName, Locale locale) {
 2668               if (baseName == null || locale == null) {
 2669                   throw new NullPointerException();
 2670               }
 2671               return TTL_NO_EXPIRATION_CONTROL;
 2672           }
 2673   
 2674           /**
 2675            * Determines if the expired <code>bundle</code> in the cache needs
 2676            * to be reloaded based on the loading time given by
 2677            * <code>loadTime</code> or some other criteria. The method returns
 2678            * <code>true</code> if reloading is required; <code>false</code>
 2679            * otherwise. <code>loadTime</code> is a millisecond offset since
 2680            * the <a href="Calendar.html#Epoch"> <code>Calendar</code>
 2681            * Epoch</a>.
 2682            *
 2683            * The calling <code>ResourceBundle.getBundle</code> factory method
 2684            * calls this method on the <code>ResourceBundle.Control</code>
 2685            * instance used for its current invocation, not on the instance
 2686            * used in the invocation that originally loaded the resource
 2687            * bundle.
 2688            *
 2689            * <p>The default implementation compares <code>loadTime</code> and
 2690            * the last modified time of the source data of the resource
 2691            * bundle. If it's determined that the source data has been modified
 2692            * since <code>loadTime</code>, <code>true</code> is
 2693            * returned. Otherwise, <code>false</code> is returned. This
 2694            * implementation assumes that the given <code>format</code> is the
 2695            * same string as its file suffix if it's not one of the default
 2696            * formats, <code>"java.class"</code> or
 2697            * <code>"java.properties"</code>.
 2698            *
 2699            * @param baseName
 2700            *        the base bundle name of the resource bundle, a
 2701            *        fully qualified class name
 2702            * @param locale
 2703            *        the locale for which the resource bundle
 2704            *        should be instantiated
 2705            * @param format
 2706            *        the resource bundle format to be loaded
 2707            * @param loader
 2708            *        the <code>ClassLoader</code> to use to load the bundle
 2709            * @param bundle
 2710            *        the resource bundle instance that has been expired
 2711            *        in the cache
 2712            * @param loadTime
 2713            *        the time when <code>bundle</code> was loaded and put
 2714            *        in the cache
 2715            * @return <code>true</code> if the expired bundle needs to be
 2716            *        reloaded; <code>false</code> otherwise.
 2717            * @exception NullPointerException
 2718            *        if <code>baseName</code>, <code>locale</code>,
 2719            *        <code>format</code>, <code>loader</code>, or
 2720            *        <code>bundle</code> is <code>null</code>
 2721            */
 2722           public boolean needsReload(String baseName, Locale locale,
 2723                                      String format, ClassLoader loader,
 2724                                      ResourceBundle bundle, long loadTime) {
 2725               if (bundle == null) {
 2726                   throw new NullPointerException();
 2727               }
 2728               if (format.equals("java.class") || format.equals("java.properties")) {
 2729                   format = format.substring(5);
 2730               }
 2731               boolean result = false;
 2732               try {
 2733                   String resourceName = toResourceName(toBundleName(baseName, locale), format);
 2734                   URL url = loader.getResource(resourceName);
 2735                   if (url != null) {
 2736                       long lastModified = 0;
 2737                       URLConnection connection = url.openConnection();
 2738                       if (connection != null) {
 2739                           // disable caches to get the correct data
 2740                           connection.setUseCaches(false);
 2741                           if (connection instanceof JarURLConnection) {
 2742                               JarEntry ent = ((JarURLConnection)connection).getJarEntry();
 2743                               if (ent != null) {
 2744                                   lastModified = ent.getTime();
 2745                                   if (lastModified == -1) {
 2746                                       lastModified = 0;
 2747                                   }
 2748                               }
 2749                           } else {
 2750                               lastModified = connection.getLastModified();
 2751                           }
 2752                       }
 2753                       result = lastModified >= loadTime;
 2754                   }
 2755               } catch (NullPointerException npe) {
 2756                   throw npe;
 2757               } catch (Exception e) {
 2758                   // ignore other exceptions
 2759               }
 2760               return result;
 2761           }
 2762   
 2763           /**
 2764            * Converts the given <code>baseName</code> and <code>locale</code>
 2765            * to the bundle name. This method is called from the default
 2766            * implementation of the {@link #newBundle(String, Locale, String,
 2767            * ClassLoader, boolean) newBundle} and {@link #needsReload(String,
 2768            * Locale, String, ClassLoader, ResourceBundle, long) needsReload}
 2769            * methods.
 2770            *
 2771            * <p>This implementation returns the following value:
 2772            * <pre>
 2773            *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
 2774            * </pre>
 2775            * where <code>language</code>, <code>script</code>, <code>country</code>,
 2776            * and <code>variant</code> are the language, script, country, and variant
 2777            * values of <code>locale</code>, respectively. Final component values that
 2778            * are empty Strings are omitted along with the preceding '_'.  When the
 2779            * script is empty, the script value is ommitted along with the preceding '_'.
 2780            * If all of the values are empty strings, then <code>baseName</code>
 2781            * is returned.
 2782            *
 2783            * <p>For example, if <code>baseName</code> is
 2784            * <code>"baseName"</code> and <code>locale</code> is
 2785            * <code>Locale("ja",&nbsp;"",&nbsp;"XX")</code>, then
 2786            * <code>"baseName_ja_&thinsp;_XX"</code> is returned. If the given
 2787            * locale is <code>Locale("en")</code>, then
 2788            * <code>"baseName_en"</code> is returned.
 2789            *
 2790            * <p>Overriding this method allows applications to use different
 2791            * conventions in the organization and packaging of localized
 2792            * resources.
 2793            *
 2794            * @param baseName
 2795            *        the base name of the resource bundle, a fully
 2796            *        qualified class name
 2797            * @param locale
 2798            *        the locale for which a resource bundle should be
 2799            *        loaded
 2800            * @return the bundle name for the resource bundle
 2801            * @exception NullPointerException
 2802            *        if <code>baseName</code> or <code>locale</code>
 2803            *        is <code>null</code>
 2804            */
 2805           public String toBundleName(String baseName, Locale locale) {
 2806               if (locale == Locale.ROOT) {
 2807                   return baseName;
 2808               }
 2809   
 2810               String language = locale.getLanguage();
 2811               String script = locale.getScript();
 2812               String country = locale.getCountry();
 2813               String variant = locale.getVariant();
 2814   
 2815               if (language == "" && country == "" && variant == "") {
 2816                   return baseName;
 2817               }
 2818   
 2819               StringBuilder sb = new StringBuilder(baseName);
 2820               sb.append('_');
 2821               if (script != "") {
 2822                   if (variant != "") {
 2823                       sb.append(language).append('_').append(script).append('_').append(country).append('_').append(variant);
 2824                   } else if (country != "") {
 2825                       sb.append(language).append('_').append(script).append('_').append(country);
 2826                   } else {
 2827                       sb.append(language).append('_').append(script);
 2828                   }
 2829               } else {
 2830                   if (variant != "") {
 2831                       sb.append(language).append('_').append(country).append('_').append(variant);
 2832                   } else if (country != "") {
 2833                       sb.append(language).append('_').append(country);
 2834                   } else {
 2835                       sb.append(language);
 2836                   }
 2837               }
 2838               return sb.toString();
 2839   
 2840           }
 2841   
 2842           /**
 2843            * Converts the given <code>bundleName</code> to the form required
 2844            * by the {@link ClassLoader#getResource ClassLoader.getResource}
 2845            * method by replacing all occurrences of <code>'.'</code> in
 2846            * <code>bundleName</code> with <code>'/'</code> and appending a
 2847            * <code>'.'</code> and the given file <code>suffix</code>. For
 2848            * example, if <code>bundleName</code> is
 2849            * <code>"foo.bar.MyResources_ja_JP"</code> and <code>suffix</code>
 2850            * is <code>"properties"</code>, then
 2851            * <code>"foo/bar/MyResources_ja_JP.properties"</code> is returned.
 2852            *
 2853            * @param bundleName
 2854            *        the bundle name
 2855            * @param suffix
 2856            *        the file type suffix
 2857            * @return the converted resource name
 2858            * @exception NullPointerException
 2859            *         if <code>bundleName</code> or <code>suffix</code>
 2860            *         is <code>null</code>
 2861            */
 2862           public final String toResourceName(String bundleName, String suffix) {
 2863               StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length());
 2864               sb.append(bundleName.replace('.', '/')).append('.').append(suffix);
 2865               return sb.toString();
 2866           }
 2867       }
 2868   
 2869       private static class SingleFormatControl extends Control {
 2870           private static final Control PROPERTIES_ONLY
 2871               = new SingleFormatControl(FORMAT_PROPERTIES);
 2872   
 2873           private static final Control CLASS_ONLY
 2874               = new SingleFormatControl(FORMAT_CLASS);
 2875   
 2876           private final List<String> formats;
 2877   
 2878           protected SingleFormatControl(List<String> formats) {
 2879               this.formats = formats;
 2880           }
 2881   
 2882           public List<String> getFormats(String baseName) {
 2883               if (baseName == null) {
 2884                   throw new NullPointerException();
 2885               }
 2886               return formats;
 2887           }
 2888       }
 2889   
 2890       private static final class NoFallbackControl extends SingleFormatControl {
 2891           private static final Control NO_FALLBACK
 2892               = new NoFallbackControl(FORMAT_DEFAULT);
 2893   
 2894           private static final Control PROPERTIES_ONLY_NO_FALLBACK
 2895               = new NoFallbackControl(FORMAT_PROPERTIES);
 2896   
 2897           private static final Control CLASS_ONLY_NO_FALLBACK
 2898               = new NoFallbackControl(FORMAT_CLASS);
 2899   
 2900           protected NoFallbackControl(List<String> formats) {
 2901               super(formats);
 2902           }
 2903   
 2904           public Locale getFallbackLocale(String baseName, Locale locale) {
 2905               if (baseName == null || locale == null) {
 2906                   throw new NullPointerException();
 2907               }
 2908               return null;
 2909           }
 2910       }
 2911   }

Home » openjdk-7 » java » util » [javadoc | source]