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