1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 /*
28  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
29  * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
30  *
31  * The original version of this source code and documentation
32  * is copyrighted and owned by Taligent, Inc., a wholly-owned
33  * subsidiary of IBM. These materials are provided under terms
34  * of a License Agreement between Taligent and Sun. This technology
35  * is protected by multiple US and International patents.
36  *
37  * This notice and attribution to Taligent may not be removed.
38  * Taligent is a registered trademark of Taligent, Inc.
39  *
40  */
41 
42 package java.util;
43 
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.UncheckedIOException;
48 import java.lang.ref.Reference;
49 import java.lang.ref.ReferenceQueue;
50 import java.lang.ref.SoftReference;
51 import java.lang.ref.WeakReference;
52 import java.lang.reflect.Constructor;
53 import java.lang.reflect.InvocationTargetException;
54 import java.lang.reflect.Modifier;
55 import java.net.JarURLConnection;
56 import java.net.URL;
57 import java.net.URLConnection;
58 import java.nio.charset.StandardCharsets;
59 import java.security.AccessController;
60 import java.security.PrivilegedAction;
61 import java.security.PrivilegedActionException;
62 import java.security.PrivilegedExceptionAction;
63 import java.util.concurrent.ConcurrentHashMap;
64 import java.util.concurrent.ConcurrentMap;
65 import java.util.jar.JarEntry;
66 import jdk.internal.reflect.CallerSensitive;
67 import jdk.internal.reflect.Reflection;
68 import sun.security.action.GetPropertyAction;
69 import sun.util.locale.BaseLocale;
70 import sun.util.locale.LocaleObjectCache;
71 
72 // Android-removed: Support for ResourceBundleControlProvider.
73 // Removed references to ResourceBundleControlProvider from the documentation.
74 // The service provider interface ResourceBundleControlProvider is not
75 // available on Android.
76 // Android-removed: Mentions of modules as they are not supported.
77 /**
78  *
79  * Resource bundles contain locale-specific objects.  When your program needs a
80  * locale-specific resource, a <code>String</code> for example, your program can
81  * load it from the resource bundle that is appropriate for the current user's
82  * locale. In this way, you can write program code that is largely independent
83  * of the user's locale isolating most, if not all, of the locale-specific
84  * information in resource bundles.
85  *
86  * <p>
87  * This allows you to write programs that can:
88  * <UL>
89  * <LI> be easily localized, or translated, into different languages
90  * <LI> handle multiple locales at once
91  * <LI> be easily modified later to support even more locales
92  * </UL>
93  *
94  * <P>
95  * Resource bundles belong to families whose members share a common base
96  * name, but whose names also have additional components that identify
97  * their locales. For example, the base name of a family of resource
98  * bundles might be "MyResources". The family should have a default
99  * resource bundle which simply has the same name as its family -
100  * "MyResources" - and will be used as the bundle of last resort if a
101  * specific locale is not supported. The family can then provide as
102  * many locale-specific members as needed, for example a German one
103  * named "MyResources_de".
104  *
105  * <P>
106  * Each resource bundle in a family contains the same items, but the items have
107  * been translated for the locale represented by that resource bundle.
108  * For example, both "MyResources" and "MyResources_de" may have a
109  * <code>String</code> that's used on a button for canceling operations.
110  * In "MyResources" the <code>String</code> may contain "Cancel" and in
111  * "MyResources_de" it may contain "Abbrechen".
112  *
113  * <P>
114  * If there are different resources for different countries, you
115  * can make specializations: for example, "MyResources_de_CH" contains objects for
116  * the German language (de) in Switzerland (CH). If you want to only
117  * modify some of the resources
118  * in the specialization, you can do so.
119  *
120  * <P>
121  * When your program needs a locale-specific object, it loads
122  * the <code>ResourceBundle</code> class using the
123  * {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
124  * method:
125  * <blockquote>
126  * <pre>
127  * ResourceBundle myResources =
128  *      ResourceBundle.getBundle("MyResources", currentLocale);
129  * </pre>
130  * </blockquote>
131  *
132  * <P>
133  * Resource bundles contain key/value pairs. The keys uniquely
134  * identify a locale-specific object in the bundle. Here's an
135  * example of a <code>ListResourceBundle</code> that contains
136  * two key/value pairs:
137  * <blockquote>
138  * <pre>
139  * public class MyResources extends ListResourceBundle {
140  *     protected Object[][] getContents() {
141  *         return new Object[][] {
142  *             // LOCALIZE THE SECOND STRING OF EACH ARRAY (e.g., "OK")
143  *             {"OkKey", "OK"},
144  *             {"CancelKey", "Cancel"},
145  *             // END OF MATERIAL TO LOCALIZE
146  *        };
147  *     }
148  * }
149  * </pre>
150  * </blockquote>
151  * Keys are always <code>String</code>s.
152  * In this example, the keys are "OkKey" and "CancelKey".
153  * In the above example, the values
154  * are also <code>String</code>s--"OK" and "Cancel"--but
155  * they don't have to be. The values can be any type of object.
156  *
157  * <P>
158  * You retrieve an object from resource bundle using the appropriate
159  * getter method. Because "OkKey" and "CancelKey"
160  * are both strings, you would use <code>getString</code> to retrieve them:
161  * <blockquote>
162  * <pre>
163  * button1 = new Button(myResources.getString("OkKey"));
164  * button2 = new Button(myResources.getString("CancelKey"));
165  * </pre>
166  * </blockquote>
167  * The getter methods all require the key as an argument and return
168  * the object if found. If the object is not found, the getter method
169  * throws a <code>MissingResourceException</code>.
170  *
171  * <P>
172  * Besides <code>getString</code>, <code>ResourceBundle</code> also provides
173  * a method for getting string arrays, <code>getStringArray</code>,
174  * as well as a generic <code>getObject</code> method for any other
175  * type of object. When using <code>getObject</code>, you'll
176  * have to cast the result to the appropriate type. For example:
177  * <blockquote>
178  * <pre>
179  * int[] myIntegers = (int[]) myResources.getObject("intList");
180  * </pre>
181  * </blockquote>
182  *
183  * <P>
184  * The Java Platform provides two subclasses of <code>ResourceBundle</code>,
185  * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
186  * that provide a fairly simple way to create resources.
187  * As you saw briefly in a previous example, <code>ListResourceBundle</code>
188  * manages its resource as a list of key/value pairs.
189  * <code>PropertyResourceBundle</code> uses a properties file to manage
190  * its resources.
191  *
192  * <p>
193  * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
194  * do not suit your needs, you can write your own <code>ResourceBundle</code>
195  * subclass.  Your subclasses must override two methods: <code>handleGetObject</code>
196  * and <code>getKeys()</code>.
197  *
198  * <p>
199  * The implementation of a {@code ResourceBundle} subclass must be thread-safe
200  * if it's simultaneously used by multiple threads. The default implementations
201  * of the non-abstract methods in this class, and the methods in the direct
202  * known concrete subclasses {@code ListResourceBundle} and
203  * {@code PropertyResourceBundle} are thread-safe.
204  *
205  * <h3>ResourceBundle.Control</h3>
206  *
207  * The {@link ResourceBundle.Control} class provides information necessary
208  * to perform the bundle loading process by the <code>getBundle</code>
209  * factory methods that take a <code>ResourceBundle.Control</code>
210  * instance. You can implement your own subclass in order to enable
211  * non-standard resource bundle formats, change the search strategy, or
212  * define caching parameters. Refer to the descriptions of the class and the
213  * {@link #getBundle(String, Locale, ClassLoader, Control) getBundle}
214  * factory method for details.
215  *
216  * <h3>Cache Management</h3>
217  *
218  * Resource bundle instances created by the <code>getBundle</code> factory
219  * methods are cached by default, and the factory methods return the same
220  * resource bundle instance multiple times if it has been
221  * cached. <code>getBundle</code> clients may clear the cache, manage the
222  * lifetime of cached resource bundle instances using time-to-live values,
223  * or specify not to cache resource bundle instances. Refer to the
224  * descriptions of the {@linkplain #getBundle(String, Locale, ClassLoader,
225  * Control) <code>getBundle</code> factory method}, {@link
226  * #clearCache(ClassLoader) clearCache}, {@link
227  * Control#getTimeToLive(String, Locale)
228  * ResourceBundle.Control.getTimeToLive}, and {@link
229  * Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle,
230  * long) ResourceBundle.Control.needsReload} for details.
231  *
232  * <h3>Example</h3>
233  *
234  * The following is a very simple example of a <code>ResourceBundle</code>
235  * subclass, <code>MyResources</code>, that manages two resources (for a larger number of
236  * resources you would probably use a <code>Map</code>).
237  * Notice that you don't need to supply a value if
238  * a "parent-level" <code>ResourceBundle</code> handles the same
239  * key with the same value (as for the okKey below).
240  * <blockquote>
241  * <pre>
242  * // default (English language, United States)
243  * public class MyResources extends ResourceBundle {
244  *     public Object handleGetObject(String key) {
245  *         if (key.equals("okKey")) return "Ok";
246  *         if (key.equals("cancelKey")) return "Cancel";
247  *         return null;
248  *     }
249  *
250  *     public Enumeration&lt;String&gt; getKeys() {
251  *         return Collections.enumeration(keySet());
252  *     }
253  *
254  *     // Overrides handleKeySet() so that the getKeys() implementation
255  *     // can rely on the keySet() value.
256  *     protected Set&lt;String&gt; handleKeySet() {
257  *         return new HashSet&lt;String&gt;(Arrays.asList("okKey", "cancelKey"));
258  *     }
259  * }
260  *
261  * // German language
262  * public class MyResources_de extends MyResources {
263  *     public Object handleGetObject(String key) {
264  *         // don't need okKey, since parent level handles it.
265  *         if (key.equals("cancelKey")) return "Abbrechen";
266  *         return null;
267  *     }
268  *
269  *     protected Set&lt;String&gt; handleKeySet() {
270  *         return new HashSet&lt;String&gt;(Arrays.asList("cancelKey"));
271  *     }
272  * }
273  * </pre>
274  * </blockquote>
275  * You do not have to restrict yourself to using a single family of
276  * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
277  * exception messages, <code>ExceptionResources</code>
278  * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
279  * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
280  * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
281  *
282  * @see ListResourceBundle
283  * @see PropertyResourceBundle
284  * @see MissingResourceException
285  * @since 1.1
286  * @revised 9
287  */
288 public abstract class ResourceBundle {
289 
290     /** initial size of the bundle cache */
291     private static final int INITIAL_CACHE_SIZE = 32;
292 
293     // Android-removed: JavaUtilResourceBundleAccess is absent.
294     /*
295     static {
296         SharedSecrets.setJavaUtilResourceBundleAccess(
297             new JavaUtilResourceBundleAccess() {
298                 @Override
299                 public void setParent(ResourceBundle bundle,
300                                       ResourceBundle parent) {
301                     bundle.setParent(parent);
302                 }
303 
304                 @Override
305                 public ResourceBundle getParent(ResourceBundle bundle) {
306                     return bundle.parent;
307                 }
308 
309                 @Override
310                 public void setLocale(ResourceBundle bundle, Locale locale) {
311                     bundle.locale = locale;
312                 }
313 
314                 @Override
315                 public void setName(ResourceBundle bundle, String name) {
316                     bundle.name = name;
317                 }
318 
319                 @Override
320                 public ResourceBundle getBundle(String baseName, Locale locale, Module module) {
321                     // use the given module as the caller to bypass the access check
322                     return getBundleImpl(module, module,
323                                          baseName, locale,
324                                          getDefaultControl(module, baseName));
325                 }
326 
327                 @Override
328                 public ResourceBundle newResourceBundle(Class<? extends ResourceBundle> bundleClass) {
329                     return ResourceBundleProviderHelper.newResourceBundle(bundleClass);
330                 }
331             });
332     }
333     */
334 
335     /** constant indicating that no resource bundle exists */
336     private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
337             public Enumeration<String> getKeys() { return null; }
338             protected Object handleGetObject(String key) { return null; }
339             public String toString() { return "NONEXISTENT_BUNDLE"; }
340         };
341 
342 
343     /**
344      * The cache is a map from cache keys (with bundle base name, locale, and
345      * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a
346      * BundleReference.
347      *
348      * The cache is a ConcurrentMap, allowing the cache to be searched
349      * concurrently by multiple threads.  This will also allow the cache keys
350      * to be reclaimed along with the ClassLoaders they reference.
351      *
352      * This variable would be better named "cache", but we keep the old
353      * name for compatibility with some workarounds for bug 4212439.
354      */
355     private static final ConcurrentMap<CacheKey, BundleReference> cacheList
356         = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
357 
358     /**
359      * Queue for reference objects referring to class loaders or bundles.
360      */
361     private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
362 
363     /**
364      * Returns the base name of this bundle, if known, or {@code null} if unknown.
365      *
366      * If not null, then this is the value of the {@code baseName} parameter
367      * that was passed to the {@code ResourceBundle.getBundle(...)} method
368      * when the resource bundle was loaded.
369      *
370      * @return The base name of the resource bundle, as provided to and expected
371      * by the {@code ResourceBundle.getBundle(...)} methods.
372      *
373      * @see #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader)
374      *
375      * @since 1.8
376      */
getBaseBundleName()377     public String getBaseBundleName() {
378         return name;
379     }
380 
381     /**
382      * The parent bundle of this bundle.
383      * The parent bundle is searched by {@link #getObject getObject}
384      * when this bundle does not contain a particular resource.
385      */
386     protected ResourceBundle parent = null;
387 
388     /**
389      * The locale for this bundle.
390      */
391     private Locale locale = null;
392 
393     /**
394      * The base bundle name for this bundle.
395      */
396     private String name;
397 
398     /**
399      * The flag indicating this bundle has expired in the cache.
400      */
401     private volatile boolean expired;
402 
403     /**
404      * The back link to the cache key. null if this bundle isn't in
405      * the cache (yet) or has expired.
406      */
407     private volatile CacheKey cacheKey;
408 
409     /**
410      * A Set of the keys contained only in this ResourceBundle.
411      */
412     private volatile Set<String> keySet;
413 
414     /**
415      * Sole constructor.  (For invocation by subclass constructors, typically
416      * implicit.)
417      */
ResourceBundle()418     public ResourceBundle() {
419     }
420 
421     /**
422      * Gets a string for the given key from this resource bundle or one of its parents.
423      * Calling this method is equivalent to calling
424      * <blockquote>
425      * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
426      * </blockquote>
427      *
428      * @param key the key for the desired string
429      * @exception NullPointerException if <code>key</code> is <code>null</code>
430      * @exception MissingResourceException if no object for the given key can be found
431      * @exception ClassCastException if the object found for the given key is not a string
432      * @return the string for the given key
433      */
getString(String key)434     public final String getString(String key) {
435         return (String) getObject(key);
436     }
437 
438     /**
439      * Gets a string array for the given key from this resource bundle or one of its parents.
440      * Calling this method is equivalent to calling
441      * <blockquote>
442      * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
443      * </blockquote>
444      *
445      * @param key the key for the desired string array
446      * @exception NullPointerException if <code>key</code> is <code>null</code>
447      * @exception MissingResourceException if no object for the given key can be found
448      * @exception ClassCastException if the object found for the given key is not a string array
449      * @return the string array for the given key
450      */
getStringArray(String key)451     public final String[] getStringArray(String key) {
452         return (String[]) getObject(key);
453     }
454 
455     /**
456      * Gets an object for the given key from this resource bundle or one of its parents.
457      * This method first tries to obtain the object from this resource bundle using
458      * {@link #handleGetObject(java.lang.String) handleGetObject}.
459      * If not successful, and the parent resource bundle is not null,
460      * it calls the parent's <code>getObject</code> method.
461      * If still not successful, it throws a MissingResourceException.
462      *
463      * @param key the key for the desired object
464      * @exception NullPointerException if <code>key</code> is <code>null</code>
465      * @exception MissingResourceException if no object for the given key can be found
466      * @return the object for the given key
467      */
getObject(String key)468     public final Object getObject(String key) {
469         Object obj = handleGetObject(key);
470         if (obj == null) {
471             if (parent != null) {
472                 obj = parent.getObject(key);
473             }
474             if (obj == null) {
475                 throw new MissingResourceException("Can't find resource for bundle "
476                                                    +this.getClass().getName()
477                                                    +", key "+key,
478                                                    this.getClass().getName(),
479                                                    key);
480             }
481         }
482         return obj;
483     }
484 
485     /**
486      * Returns the locale of this resource bundle. This method can be used after a
487      * call to getBundle() to determine whether the resource bundle returned really
488      * corresponds to the requested locale or is a fallback.
489      *
490      * @return the locale of this resource bundle
491      */
getLocale()492     public Locale getLocale() {
493         return locale;
494     }
495 
496     /*
497      * Automatic determination of the ClassLoader to be used to load
498      * resources on behalf of the client.
499      */
getLoader(Class<?> caller)500     private static ClassLoader getLoader(Class<?> caller) {
501         ClassLoader cl = caller == null ? null : caller.getClassLoader();
502         if (cl == null) {
503             // When the caller's loader is the boot class loader, cl is null
504             // here. In that case, ClassLoader.getSystemClassLoader() may
505             // return the same class loader that the application is
506             // using. We therefore use a wrapper ClassLoader to create a
507             // separate scope for bundles loaded on behalf of the Java
508             // runtime so that these bundles cannot be returned from the
509             // cache to the application (5048280).
510             cl = RBClassLoader.INSTANCE;
511         }
512         return cl;
513     }
514 
515     /**
516      * A wrapper of ClassLoader.getSystemClassLoader().
517      */
518     private static class RBClassLoader extends ClassLoader {
519         private static final RBClassLoader INSTANCE = AccessController.doPrivileged(
520                 new PrivilegedAction<RBClassLoader>() {
521                     public RBClassLoader run() {
522                         return new RBClassLoader();
523                     }
524                 });
525         private static final ClassLoader loader = ClassLoader.getSystemClassLoader();
526 
RBClassLoader()527         private RBClassLoader() {
528         }
loadClass(String name)529         public Class<?> loadClass(String name) throws ClassNotFoundException {
530             if (loader != null) {
531                 return loader.loadClass(name);
532             }
533             return Class.forName(name);
534         }
getResource(String name)535         public URL getResource(String name) {
536             if (loader != null) {
537                 return loader.getResource(name);
538             }
539             return ClassLoader.getSystemResource(name);
540         }
getResourceAsStream(String name)541         public InputStream getResourceAsStream(String name) {
542             if (loader != null) {
543                 return loader.getResourceAsStream(name);
544             }
545             return ClassLoader.getSystemResourceAsStream(name);
546         }
547     }
548 
549     // Android-removed: modules are not supported.
550     /*
551     private static ClassLoader getLoader(Module module) {
552         PrivilegedAction<ClassLoader> pa = module::getClassLoader;
553         return AccessController.doPrivileged(pa);
554     }
555 
556     /**
557      * @param module a non-null-screened module form the {@link CacheKey#getModule()}.
558      * @return the ClassLoader to use in {@link Control#needsReload}
559      *         and {@link Control#newBundle}
560      *
561     private static ClassLoader getLoaderForControl(Module module) {
562         ClassLoader loader = getLoader(module);
563         return loader == null ? ClassLoader.getPlatformClassLoader() : loader;
564     }
565     */
566 
567     /**
568      * Sets the parent bundle of this bundle.
569      * The parent bundle is searched by {@link #getObject getObject}
570      * when this bundle does not contain a particular resource.
571      *
572      * @param parent this bundle's parent bundle.
573      */
setParent(ResourceBundle parent)574     protected void setParent(ResourceBundle parent) {
575         assert parent != NONEXISTENT_BUNDLE;
576         this.parent = parent;
577     }
578 
579     // Android-changed: as modules are not supported keep using ClassLoader as part
580     // of CacheKey.
581     /**
582      * Key used for cached resource bundles.  The key checks the base
583      * name, the locale, and the class loader to determine if the
584      * resource is a match to the requested one. The loader may be
585      * null, but the base name and the locale must have a non-null
586      * value.
587      */
588     private static final class CacheKey {
589         // These three are the actual keys for lookup in Map.
590         private final String name;
591         private volatile Locale locale;
592         private final KeyElementReference<ClassLoader> loaderRef;
593         // Android-removed: modules are not supported.
594         /*
595         private final KeyElementReference<Module> moduleRef;
596         private final KeyElementReference<Module> callerRef;
597         // this is the part of hashCode that pertains to module and callerModule
598         // which can be GCed..
599         private final int modulesHash;
600         */
601 
602         // bundle format which is necessary for calling
603         // Control.needsReload().
604         private volatile String format;
605 
606         // These time values are in CacheKey so that NONEXISTENT_BUNDLE
607         // doesn't need to be cloned for caching.
608 
609         // The time when the bundle has been loaded
610         private volatile long loadTime;
611 
612         // The time when the bundle expires in the cache, or either
613         // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
614         private volatile long expirationTime;
615 
616         // Placeholder for an error report by a Throwable
617         private volatile Throwable cause;
618 
619         // Android-removed: Support for ResourceBundleControlProvider.
620         /*
621         // ResourceBundleProviders for loading ResourceBundles
622         private volatile ServiceLoader<ResourceBundleProvider> providers;
623         private volatile boolean providersChecked;
624 
625         // Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
626         private volatile Boolean callerHasProvider;
627         */
628 
629         // Android-changed: keep Java 8 constructor.
630         // CacheKey(String baseName, Locale locale, Module module, Module caller) {
CacheKey(String baseName, Locale locale, ClassLoader loader)631         CacheKey(String baseName, Locale locale, ClassLoader loader) {
632             // Objects.requireNonNull(module);
633             // Objects.requireNonNull(caller);
634 
635             this.name = baseName;
636             this.locale = locale;
637             // this.moduleRef = new KeyElementReference<>(module, referenceQueue, this);
638             // this.callerRef = new KeyElementReference<>(caller, referenceQueue, this);
639             // this.modulesHash = module.hashCode() ^ caller.hashCode();
640             if (loader == null) {
641                 this.loaderRef = null;
642             } else {
643                 this.loaderRef = new KeyElementReference<>(loader, referenceQueue, this);
644             }
645         }
646 
CacheKey(CacheKey src)647         CacheKey(CacheKey src) {
648             // Android-removed: modules are not supported.
649             /*
650             // Create References to src's modules
651             this.moduleRef = new KeyElementReference<>(
652                 Objects.requireNonNull(src.getModule()), referenceQueue, this);
653             this.callerRef = new KeyElementReference<>(
654                 Objects.requireNonNull(src.getCallerModule()), referenceQueue, this);
655             */
656             // Copy fields from src. ResourceBundleProviders related fields
657             // and "cause" should not be copied.
658             this.name = src.name;
659             this.locale = src.locale;
660             if (src.loaderRef == null) {
661                 this.loaderRef = null;
662             } else {
663                 ClassLoader loader = src.loaderRef.get();
664                 this.loaderRef = new KeyElementReference<>(loader, referenceQueue, this);
665             }
666             // Android-removed: modules are not supported.
667             // this.modulesHash = src.modulesHash;
668             this.format = src.format;
669             this.loadTime = src.loadTime;
670             this.expirationTime = src.expirationTime;
671         }
672 
getName()673         String getName() {
674             return name;
675         }
676 
getLocale()677         Locale getLocale() {
678             return locale;
679         }
680 
setLocale(Locale locale)681         CacheKey setLocale(Locale locale) {
682             this.locale = locale;
683             return this;
684         }
685 
686         // Android-removed: modules are not supported.
687         /*
688         Module getModule() {
689             return moduleRef.get();
690         }
691 
692         Module getCallerModule() {
693             return callerRef.get();
694         }
695 
696         ServiceLoader<ResourceBundleProvider> getProviders() {
697             if (!providersChecked) {
698                 providers = getServiceLoader(getModule(), name);
699                 providersChecked = true;
700             }
701             return providers;
702         }
703 
704         boolean hasProviders() {
705             return getProviders() != null;
706         }
707 
708         boolean callerHasProvider() {
709             return callerHasProvider == Boolean.TRUE;
710         }
711         */
712 
getLoader()713         ClassLoader getLoader() {
714             return loaderRef != null ? loaderRef.get() : null;
715         }
716 
717         @Override
equals(Object other)718         public boolean equals(Object other) {
719             if (this == other) {
720                 return true;
721             }
722             try {
723                 final CacheKey otherEntry = (CacheKey)other;
724                 // Android-removed: modules are not supported.
725                 //quick check to see if they are not equal
726                 // if (modulesHash != otherEntry.modulesHash) {
727                 //     return false;
728                 // }
729                 //are the names the same?
730                 if (!name.equals(otherEntry.name)) {
731                     return false;
732                 }
733                 // are the locales the same?
734                 if (!locale.equals(otherEntry.locale)) {
735                     return false;
736                 }
737 
738                 //are refs (both non-null) or (both null)?
739                 if (loaderRef == null) {
740                     return otherEntry.loaderRef == null;
741                 }
742                 ClassLoader loader = loaderRef.get();
743                 return (otherEntry.loaderRef != null)
744                         // with a null reference we can no longer find
745                         // out which class loader was referenced; so
746                         // treat it as unequal
747                         && (loader != null)
748                         && (otherEntry.loaderRef.refersTo(loader));
749                 // Android-removed: modules are not supported.
750                 /*
751                 // are modules and callerModules the same and non-null?
752                 Module module = getModule();
753                 Module caller = getCallerModule();
754                 return ((module != null) && (module.equals(otherEntry.getModule())) &&
755                         (caller != null) && (caller.equals(otherEntry.getCallerModule())));
756                 */
757             } catch (NullPointerException | ClassCastException e) {
758             }
759             return false;
760         }
761 
762         @Override
hashCode()763         public int hashCode() {
764             int hashCode = (name.hashCode() << 3) ^ locale.hashCode();
765             ClassLoader loader = getLoader();
766             if (loader != null) {
767                 hashCode ^= loader.hashCode();
768             }
769             return hashCode;
770         }
771 
getFormat()772         String getFormat() {
773             return format;
774         }
775 
setFormat(String format)776         void setFormat(String format) {
777             this.format = format;
778         }
779 
setCause(Throwable cause)780         private void setCause(Throwable cause) {
781             if (this.cause == null) {
782                 this.cause = cause;
783             } else {
784                 // Override the cause if the previous one is
785                 // ClassNotFoundException.
786                 if (this.cause instanceof ClassNotFoundException) {
787                     this.cause = cause;
788                 }
789             }
790         }
791 
getCause()792         private Throwable getCause() {
793             return cause;
794         }
795 
796         @Override
toString()797         public String toString() {
798             String l = locale.toString();
799             if (l.isEmpty()) {
800                 if (!locale.getVariant().isEmpty()) {
801                     l = "__" + locale.getVariant();
802                 } else {
803                     l = "\"\"";
804                 }
805             }
806             return "CacheKey[" + name +
807                    ", locale=" + l +
808                    ", classLoader" + getLoader() +
809                    // Android-removed: modules are not supported.
810                    // ", module=" + getModule() +
811                    // ", callerModule=" + getCallerModule() +
812                    ", format=" + format +
813                    "]";
814         }
815     }
816 
817     /**
818      * The common interface to get a CacheKey in LoaderReference and
819      * BundleReference.
820      */
821     private static interface CacheKeyReference {
getCacheKey()822         public CacheKey getCacheKey();
823     }
824 
825     /**
826      * References to a CacheKey element as a WeakReference so that it can be
827      * garbage collected when nobody else is using it.
828      */
829     private static class KeyElementReference<T> extends WeakReference<T>
830                                                 implements CacheKeyReference {
831         private final CacheKey cacheKey;
832 
KeyElementReference(T referent, ReferenceQueue<Object> q, CacheKey key)833         KeyElementReference(T referent, ReferenceQueue<Object> q, CacheKey key) {
834             super(referent, q);
835             cacheKey = key;
836         }
837 
838         @Override
getCacheKey()839         public CacheKey getCacheKey() {
840             return cacheKey;
841         }
842     }
843 
844     /**
845      * References to bundles are soft references so that they can be garbage
846      * collected when they have no hard references.
847      */
848     private static class BundleReference extends SoftReference<ResourceBundle>
849                                          implements CacheKeyReference {
850         private final CacheKey cacheKey;
851 
BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key)852         BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key) {
853             super(referent, q);
854             cacheKey = key;
855         }
856 
857         @Override
getCacheKey()858         public CacheKey getCacheKey() {
859             return cacheKey;
860         }
861     }
862 
863     /**
864      * Gets a resource bundle using the specified base name, the default locale,
865      * and the caller's class loader. Calling this method is equivalent to calling
866      * <blockquote>
867      * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
868      * </blockquote>
869      *
870      * @param baseName the base name of the resource bundle, a fully qualified class name
871      * @exception java.lang.NullPointerException
872      *     if <code>baseName</code> is <code>null</code>
873      * @exception MissingResourceException
874      *     if no resource bundle for the specified base name can be found
875      * @return a resource bundle for the given base name and the default locale
876      *
877      * @see <a href="#default_behavior">Resource Bundle Search and Loading Strategy</a>
878      */
879     @CallerSensitive
getBundle(String baseName)880     public static final ResourceBundle getBundle(String baseName)
881     {
882         Class<?> caller = Reflection.getCallerClass();
883         return getBundleImpl(baseName, Locale.getDefault(),
884                              caller, getDefaultControl(caller, baseName));
885     }
886 
887     /**
888      * Returns a resource bundle using the specified base name, the
889      * default locale and the specified control. Calling this method
890      * is equivalent to calling
891      * <pre>
892      * getBundle(baseName, Locale.getDefault(),
893      *           this.getClass().getClassLoader(), control),
894      * </pre>
895      * except that <code>getClassLoader()</code> is run with the security
896      * privileges of <code>ResourceBundle</code>.  See {@link
897      * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
898      * complete description of the resource bundle loading process with a
899      * <code>ResourceBundle.Control</code>.
900      *
901      * @param baseName
902      *        the base name of the resource bundle, a fully qualified class
903      *        name
904      * @param control
905      *        the control which gives information for the resource bundle
906      *        loading process
907      * @return a resource bundle for the given base name and the default locale
908      * @throws NullPointerException
909      *         if <code>baseName</code> or <code>control</code> is
910      *         <code>null</code>
911      * @throws MissingResourceException
912      *         if no resource bundle for the specified base name can be found
913      * @throws IllegalArgumentException
914      *         if the given <code>control</code> doesn't perform properly
915      *         (e.g., <code>control.getCandidateLocales</code> returns null.)
916      *         Note that validation of <code>control</code> is performed as
917      *         needed.
918      * @since 1.6
919      * @revised 9
920      */
921     @CallerSensitive
getBundle(String baseName, Control control)922     public static final ResourceBundle getBundle(String baseName,
923                                                  Control control) {
924         Class<?> caller = Reflection.getCallerClass();
925         Locale targetLocale = Locale.getDefault();
926         // Android-removed: modules are not supported.
927         // checkNamedModule(caller);
928         return getBundleImpl(baseName, targetLocale, caller, control);
929     }
930 
931     /**
932      * Gets a resource bundle using the specified base name and locale,
933      * and the caller's class loader. Calling this method is equivalent to calling
934      * <blockquote>
935      * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
936      * </blockquote>
937      *
938      * @param baseName
939      *        the base name of the resource bundle, a fully qualified class name
940      * @param locale
941      *        the locale for which a resource bundle is desired
942      * @exception NullPointerException
943      *        if <code>baseName</code> or <code>locale</code> is <code>null</code>
944      * @exception MissingResourceException
945      *        if no resource bundle for the specified base name can be found
946      * @return a resource bundle for the given base name and locale
947      *
948      * @see <a href="#default_behavior">Resource Bundle Search and Loading Strategy</a>
949      */
950     @CallerSensitive
getBundle(String baseName, Locale locale)951     public static final ResourceBundle getBundle(String baseName,
952                                                  Locale locale)
953     {
954         Class<?> caller = Reflection.getCallerClass();
955         return getBundleImpl(baseName, locale,
956                              caller, getDefaultControl(caller, baseName));
957     }
958 
959     // Android-removed: modules are not supported.
960     /*
961      * Gets a resource bundle using the specified base name and the default locale
962      * on behalf of the specified module. This method is equivalent to calling
963      * <blockquote>
964      * <code>getBundle(baseName, Locale.getDefault(), module)</code>
965      * </blockquote>
966      *
967      * @param baseName the base name of the resource bundle,
968      *                 a fully qualified class name
969      * @param module   the module for which the resource bundle is searched
970      * @throws NullPointerException
971      *         if {@code baseName} or {@code module} is {@code null}
972      * @throws SecurityException
973      *         if a security manager exists and the caller is not the specified
974      *         module and doesn't have {@code RuntimePermission("getClassLoader")}
975      * @throws MissingResourceException
976      *         if no resource bundle for the specified base name can be found in the
977      *         specified module
978      * @return a resource bundle for the given base name and the default locale
979      * @since 9
980      * @spec JPMS
981      * @see ResourceBundleProvider
982      * @see <a href="#default_behavior">Resource Bundle Search and Loading Strategy</a>
983      * @see <a href="#resource-bundle-modules">Resource Bundles and Named Modules</a>
984      *
985     @CallerSensitive
986     public static ResourceBundle getBundle(String baseName, Module module) {
987         return getBundleFromModule(Reflection.getCallerClass(), module, baseName,
988                                    Locale.getDefault(),
989                                    getDefaultControl(module, baseName));
990     }
991 
992     /*
993      * Gets a resource bundle using the specified base name and locale
994      * on behalf of the specified module.
995      *
996      * <p> Resource bundles in named modules may be encapsulated.  When
997      * the resource bundle is loaded from a
998      * {@linkplain ResourceBundleProvider service provider}, the caller module
999      * must have an appropriate <i>uses</i> clause in its <i>module descriptor</i>
1000      * to declare that the module uses of {@link ResourceBundleProvider}
1001      * for the named resource bundle.
1002      * Otherwise, it will load the resource bundles that are local in the
1003      * given module as if calling {@link Module#getResourceAsStream(String)}
1004      * or that are visible to the class loader of the given module
1005      * as if calling {@link ClassLoader#getResourceAsStream(String)}.
1006      * When the resource bundle is loaded from the specified module, it is
1007      * subject to the encapsulation rules specified by
1008      * {@link Module#getResourceAsStream Module.getResourceAsStream}.
1009      *
1010      * <p>
1011      * If the given {@code module} is an unnamed module, then this method is
1012      * equivalent to calling {@link #getBundle(String, Locale, ClassLoader)
1013      * getBundle(baseName, targetLocale, module.getClassLoader()} to load
1014      * resource bundles that are visible to the class loader of the given
1015      * unnamed module. Custom {@link java.util.spi.ResourceBundleControlProvider}
1016      * implementations, if present, will only be invoked if the specified
1017      * module is an unnamed module.
1018      *
1019      * @param baseName the base name of the resource bundle,
1020      *                 a fully qualified class name
1021      * @param targetLocale the locale for which a resource bundle is desired
1022      * @param module   the module for which the resource bundle is searched
1023      * @throws NullPointerException
1024      *         if {@code baseName}, {@code targetLocale}, or {@code module} is
1025      *         {@code null}
1026      * @throws SecurityException
1027      *         if a security manager exists and the caller is not the specified
1028      *         module and doesn't have {@code RuntimePermission("getClassLoader")}
1029      * @throws MissingResourceException
1030      *         if no resource bundle for the specified base name and locale can
1031      *         be found in the specified {@code module}
1032      * @return a resource bundle for the given base name and locale in the module
1033      * @since 9
1034      * @spec JPMS
1035      * @see <a href="#default_behavior">Resource Bundle Search and Loading Strategy</a>
1036      * @see <a href="#resource-bundle-modules">Resource Bundles and Named Modules</a>
1037      *
1038     @CallerSensitive
1039     public static ResourceBundle getBundle(String baseName, Locale targetLocale, Module module) {
1040         return getBundleFromModule(Reflection.getCallerClass(), module, baseName, targetLocale,
1041                                    getDefaultControl(module, baseName));
1042     }
1043     */
1044 
1045     /**
1046      * Returns a resource bundle using the specified base name, target
1047      * locale and control, and the caller's class loader. Calling this
1048      * method is equivalent to calling
1049      * <pre>
1050      * getBundle(baseName, targetLocale, this.getClass().getClassLoader(),
1051      *           control),
1052      * </pre>
1053      * except that <code>getClassLoader()</code> is run with the security
1054      * privileges of <code>ResourceBundle</code>.  See {@link
1055      * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
1056      * complete description of the resource bundle loading process with a
1057      * <code>ResourceBundle.Control</code>.
1058      *
1059      * @param baseName
1060      *        the base name of the resource bundle, a fully qualified
1061      *        class name
1062      * @param targetLocale
1063      *        the locale for which a resource bundle is desired
1064      * @param control
1065      *        the control which gives information for the resource
1066      *        bundle loading process
1067      * @return a resource bundle for the given base name and a
1068      *         <code>Locale</code> in <code>locales</code>
1069      * @throws NullPointerException
1070      *         if <code>baseName</code>, <code>locales</code> or
1071      *         <code>control</code> is <code>null</code>
1072      * @throws MissingResourceException
1073      *         if no resource bundle for the specified base name in any
1074      *         of the <code>locales</code> can be found.
1075      * @throws IllegalArgumentException
1076      *         if the given <code>control</code> doesn't perform properly
1077      *         (e.g., <code>control.getCandidateLocales</code> returns null.)
1078      *         Note that validation of <code>control</code> is performed as
1079      *         needed.
1080      * @since 1.6
1081      * @revised 9
1082      */
1083     @CallerSensitive
getBundle(String baseName, Locale targetLocale, Control control)1084     public static final ResourceBundle getBundle(String baseName, Locale targetLocale,
1085                                                  Control control) {
1086         Class<?> caller = Reflection.getCallerClass();
1087         // Android-removed: modules are not supported.
1088         // checkNamedModule(caller);
1089         return getBundleImpl(baseName, targetLocale, caller, control);
1090     }
1091 
1092     // Android-removed: Support for ResourceBundleControlProvider.
1093     // Removed documentation referring to that SPI.
1094     /**
1095      * Gets a resource bundle using the specified base name, locale, and class
1096      * loader.
1097      *
1098      * This is equivalent to calling:
1099      * <blockquote><pre>
1100      * getBundle(baseName, targetLocale, loader, control)
1101      * </pre></blockquote>
1102      * passing a default instance of {@link Control}.
1103      * Refer to the
1104      * description of <a href="#modify_default_behavior">modifying the default
1105      * behavior</a>. The following describes the default behavior.
1106      *
1107      * <p>
1108      * <b><a id="default_behavior">Resource Bundle Search and Loading Strategy</a></b>
1109      *
1110      * <p><code>getBundle</code> uses the base name, the specified locale, and
1111      * the default locale (obtained from {@link java.util.Locale#getDefault()
1112      * Locale.getDefault}) to generate a sequence of <a
1113      * id="candidates"><em>candidate bundle names</em></a>.  If the specified
1114      * locale's language, script, country, and variant are all empty strings,
1115      * then the base name is the only candidate bundle name.  Otherwise, a list
1116      * of candidate locales is generated from the attribute values of the
1117      * specified locale (language, script, country and variant) and appended to
1118      * the base name.  Typically, this will look like the following:
1119      *
1120      * <pre>
1121      *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
1122      *     baseName + "_" + language + "_" + script + "_" + country
1123      *     baseName + "_" + language + "_" + script
1124      *     baseName + "_" + language + "_" + country + "_" + variant
1125      *     baseName + "_" + language + "_" + country
1126      *     baseName + "_" + language
1127      * </pre>
1128      *
1129      * <p>Candidate bundle names where the final component is an empty string
1130      * are omitted, along with the underscore.  For example, if country is an
1131      * empty string, the second and the fifth candidate bundle names above
1132      * would be omitted.  Also, if script is an empty string, the candidate names
1133      * including script are omitted.  For example, a locale with language "de"
1134      * and variant "JAVA" will produce candidate names with base name
1135      * "MyResource" below.
1136      *
1137      * <pre>
1138      *     MyResource_de__JAVA
1139      *     MyResource_de
1140      * </pre>
1141      *
1142      * In the case that the variant contains one or more underscores ('_'), a
1143      * sequence of bundle names generated by truncating the last underscore and
1144      * the part following it is inserted after a candidate bundle name with the
1145      * original variant.  For example, for a locale with language "en", script
1146      * "Latn, country "US" and variant "WINDOWS_VISTA", and bundle base name
1147      * "MyResource", the list of candidate bundle names below is generated:
1148      *
1149      * <pre>
1150      * MyResource_en_Latn_US_WINDOWS_VISTA
1151      * MyResource_en_Latn_US_WINDOWS
1152      * MyResource_en_Latn_US
1153      * MyResource_en_Latn
1154      * MyResource_en_US_WINDOWS_VISTA
1155      * MyResource_en_US_WINDOWS
1156      * MyResource_en_US
1157      * MyResource_en
1158      * </pre>
1159      *
1160      * <blockquote><b>Note:</b> For some <code>Locale</code>s, the list of
1161      * candidate bundle names contains extra names, or the order of bundle names
1162      * is slightly modified.  See the description of the default implementation
1163      * of {@link Control#getCandidateLocales(String, Locale)
1164      * getCandidateLocales} for details.</blockquote>
1165      *
1166      * <p><code>getBundle</code> then iterates over the candidate bundle names
1167      * to find the first one for which it can <em>instantiate</em> an actual
1168      * resource bundle. It uses the default controls' {@link Control#getFormats
1169      * getFormats} method, which generates two bundle names for each generated
1170      * name, the first a class name and the second a properties file name. For
1171      * each candidate bundle name, it attempts to create a resource bundle:
1172      *
1173      * <ul><li>First, it attempts to load a class using the generated class name.
1174      * If such a class can be found and loaded using the specified class
1175      * loader, is assignment compatible with ResourceBundle, is accessible from
1176      * ResourceBundle, and can be instantiated, <code>getBundle</code> creates a
1177      * new instance of this class and uses it as the <em>result resource
1178      * bundle</em>.
1179      *
1180      * <li>Otherwise, <code>getBundle</code> attempts to locate a property
1181      * resource file using the generated properties file name.  It generates a
1182      * path name from the candidate bundle name by replacing all "." characters
1183      * with "/" and appending the string ".properties".  It attempts to find a
1184      * "resource" with this name using {@link
1185      * java.lang.ClassLoader#getResource(java.lang.String)
1186      * ClassLoader.getResource}.  (Note that a "resource" in the sense of
1187      * <code>getResource</code> has nothing to do with the contents of a
1188      * resource bundle, it is just a container of data, such as a file.)  If it
1189      * finds a "resource", it attempts to create a new {@link
1190      * PropertyResourceBundle} instance from its contents.  If successful, this
1191      * instance becomes the <em>result resource bundle</em>.  </ul>
1192      *
1193      * <p>This continues until a result resource bundle is instantiated or the
1194      * list of candidate bundle names is exhausted.  If no matching resource
1195      * bundle is found, the default control's {@link Control#getFallbackLocale
1196      * getFallbackLocale} method is called, which returns the current default
1197      * locale.  A new sequence of candidate locale names is generated using this
1198      * locale and searched again, as above.
1199      *
1200      * <p>If still no result bundle is found, the base name alone is looked up. If
1201      * this still fails, a <code>MissingResourceException</code> is thrown.
1202      *
1203      * <p><a id="parent_chain"> Once a result resource bundle has been found,
1204      * its <em>parent chain</em> is instantiated</a>.  If the result bundle already
1205      * has a parent (perhaps because it was returned from a cache) the chain is
1206      * complete.
1207      *
1208      * <p>Otherwise, <code>getBundle</code> examines the remainder of the
1209      * candidate locale list that was used during the pass that generated the
1210      * result resource bundle.  (As before, candidate bundle names where the
1211      * final component is an empty string are omitted.)  When it comes to the
1212      * end of the candidate list, it tries the plain bundle name.  With each of the
1213      * candidate bundle names it attempts to instantiate a resource bundle (first
1214      * looking for a class and then a properties file, as described above).
1215      *
1216      * <p>Whenever it succeeds, it calls the previously instantiated resource
1217      * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method
1218      * with the new resource bundle.  This continues until the list of names
1219      * is exhausted or the current bundle already has a non-null parent.
1220      *
1221      * <p>Once the parent chain is complete, the bundle is returned.
1222      *
1223      * <p><b>Note:</b> <code>getBundle</code> caches instantiated resource
1224      * bundles and might return the same resource bundle instance multiple times.
1225      *
1226      * <p><b>Note:</b>The <code>baseName</code> argument should be a fully
1227      * qualified class name. However, for compatibility with earlier versions,
1228      * Java SE Runtime Environments do not verify this, and so it is
1229      * possible to access <code>PropertyResourceBundle</code>s by specifying a
1230      * path name (using "/") instead of a fully qualified class name (using
1231      * ".").
1232      *
1233      * <p><a id="default_behavior_example">
1234      * <strong>Example:</strong></a>
1235      * <p>
1236      * The following class and property files are provided:
1237      * <ul>
1238      *     <li>MyResources.class
1239      *     <li>MyResources.properties
1240      *     <li>MyResources_fr.properties
1241      *     <li>MyResources_fr_CH.class
1242      *     <li>MyResources_fr_CH.properties
1243      *     <li>MyResources_en.properties
1244      *     <li>MyResources_es_ES.class
1245      * </ul>
1246      *
1247      * The contents of all files are valid (that is, public non-abstract
1248      * subclasses of <code>ResourceBundle</code> for the ".class" files,
1249      * syntactically correct ".properties" files).  The default locale is
1250      * <code>Locale("en", "GB")</code>.
1251      *
1252      * <p>Calling <code>getBundle</code> with the locale arguments below will
1253      * instantiate resource bundles as follows:
1254      *
1255      * <table class="striped">
1256      * <caption style="display:none">getBundle() locale to resource bundle mapping</caption>
1257      * <thead>
1258      * <tr><th scope="col">Locale</th><th scope="col">Resource bundle</th></tr>
1259      * </thead>
1260      * <tbody>
1261      * <tr><th scope="row">Locale("fr", "CH")</th><td>MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class</td></tr>
1262      * <tr><th scope="row">Locale("fr", "FR")</th><td>MyResources_fr.properties, parent MyResources.class</td></tr>
1263      * <tr><th scope="row">Locale("de", "DE")</th><td>MyResources_en.properties, parent MyResources.class</td></tr>
1264      * <tr><th scope="row">Locale("en", "US")</th><td>MyResources_en.properties, parent MyResources.class</td></tr>
1265      * <tr><th scope="row">Locale("es", "ES")</th><td>MyResources_es_ES.class, parent MyResources.class</td></tr>
1266      * </tbody>
1267      * </table>
1268      *
1269      * <p>The file MyResources_fr_CH.properties is never used because it is
1270      * hidden by the MyResources_fr_CH.class. Likewise, MyResources.properties
1271      * is also hidden by MyResources.class.
1272      *
1273      * @param baseName the base name of the resource bundle, a fully qualified class name
1274      * @param locale the locale for which a resource bundle is desired
1275      * @param loader the class loader from which to load the resource bundle
1276      * @return a resource bundle for the given base name and locale
1277      * @exception java.lang.NullPointerException
1278      *        if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
1279      * @exception MissingResourceException
1280      *        if no resource bundle for the specified base name can be found
1281      * @since 1.2
1282      * @revised 9
1283      */
1284     @CallerSensitive
getBundle(String baseName, Locale locale, ClassLoader loader)1285     public static ResourceBundle getBundle(String baseName, Locale locale,
1286                                            ClassLoader loader)
1287     {
1288         if (loader == null) {
1289             throw new NullPointerException();
1290         }
1291         Class<?> caller = Reflection.getCallerClass();
1292         return getBundleImpl(baseName, locale, caller, loader, getDefaultControl(caller, baseName));
1293     }
1294 
1295     /**
1296      * Returns a resource bundle using the specified base name, target
1297      * locale, class loader and control. Unlike the {@link
1298      * #getBundle(String, Locale, ClassLoader) getBundle}
1299      * factory methods with no {@code control} argument, the given
1300      * <code>control</code> specifies how to locate and instantiate resource
1301      * bundles. Conceptually, the bundle loading process with the given
1302      * <code>control</code> is performed in the following steps.
1303      *
1304      * <ol>
1305      * <li>This factory method looks up the resource bundle in the cache for
1306      * the specified <code>baseName</code>, <code>targetLocale</code> and
1307      * <code>loader</code>.  If the requested resource bundle instance is
1308      * found in the cache and the time-to-live periods of the instance and
1309      * all of its parent instances have not expired, the instance is returned
1310      * to the caller. Otherwise, this factory method proceeds with the
1311      * loading process below.</li>
1312      *
1313      * <li>The {@link ResourceBundle.Control#getFormats(String)
1314      * control.getFormats} method is called to get resource bundle formats
1315      * to produce bundle or resource names. The strings
1316      * <code>"java.class"</code> and <code>"java.properties"</code>
1317      * designate class-based and {@linkplain PropertyResourceBundle
1318      * property}-based resource bundles, respectively. Other strings
1319      * starting with <code>"java."</code> are reserved for future extensions
1320      * and must not be used for application-defined formats. Other strings
1321      * designate application-defined formats.</li>
1322      *
1323      * <li>The {@link ResourceBundle.Control#getCandidateLocales(String,
1324      * Locale) control.getCandidateLocales} method is called with the target
1325      * locale to get a list of <em>candidate <code>Locale</code>s</em> for
1326      * which resource bundles are searched.</li>
1327      *
1328      * <li>The {@link ResourceBundle.Control#newBundle(String, Locale,
1329      * String, ClassLoader, boolean) control.newBundle} method is called to
1330      * instantiate a <code>ResourceBundle</code> for the base bundle name, a
1331      * candidate locale, and a format. (Refer to the note on the cache
1332      * lookup below.) This step is iterated over all combinations of the
1333      * candidate locales and formats until the <code>newBundle</code> method
1334      * returns a <code>ResourceBundle</code> instance or the iteration has
1335      * used up all the combinations. For example, if the candidate locales
1336      * are <code>Locale("de", "DE")</code>, <code>Locale("de")</code> and
1337      * <code>Locale("")</code> and the formats are <code>"java.class"</code>
1338      * and <code>"java.properties"</code>, then the following is the
1339      * sequence of locale-format combinations to be used to call
1340      * <code>control.newBundle</code>.
1341      *
1342      * <table class=striped style="width: 50%; text-align: left; margin-left: 40px;">
1343      * <caption style="display:none">locale-format combinations for newBundle</caption>
1344      * <thead>
1345      * <tr>
1346      * <th scope="col">Index</th>
1347      * <th scope="col"><code>Locale</code></th>
1348      * <th scope="col"><code>format</code></th>
1349      * </tr>
1350      * </thead>
1351      * <tbody>
1352      * <tr>
1353      * <th scope="row">1</th>
1354      * <td><code>Locale("de", "DE")</code></td>
1355      * <td><code>java.class</code></td>
1356      * </tr>
1357      * <tr>
1358      * <th scope="row">2</th>
1359      * <td><code>Locale("de", "DE")</code></td>
1360      * <td><code>java.properties</code></td>
1361      * </tr>
1362      * <tr>
1363      * <th scope="row">3</th>
1364      * <td><code>Locale("de")</code></td>
1365      * <td><code>java.class</code></td>
1366      * </tr>
1367      * <tr>
1368      * <th scope="row">4</th>
1369      * <td><code>Locale("de")</code></td>
1370      * <td><code>java.properties</code></td>
1371      * </tr>
1372      * <tr>
1373      * <th scope="row">5</th>
1374      * <td><code>Locale("")</code></td>
1375      * <td><code>java.class</code></td>
1376      * </tr>
1377      * <tr>
1378      * <th scope="row">6</th>
1379      * <td><code>Locale("")</code></td>
1380      * <td><code>java.properties</code></td>
1381      * </tr>
1382      * </tbody>
1383      * </table>
1384      * </li>
1385      *
1386      * <li>If the previous step has found no resource bundle, proceed to
1387      * Step 6. If a bundle has been found that is a base bundle (a bundle
1388      * for <code>Locale("")</code>), and the candidate locale list only contained
1389      * <code>Locale("")</code>, return the bundle to the caller. If a bundle
1390      * has been found that is a base bundle, but the candidate locale list
1391      * contained locales other than Locale(""), put the bundle on hold and
1392      * proceed to Step 6. If a bundle has been found that is not a base
1393      * bundle, proceed to Step 7.</li>
1394      *
1395      * <li>The {@link ResourceBundle.Control#getFallbackLocale(String,
1396      * Locale) control.getFallbackLocale} method is called to get a fallback
1397      * locale (alternative to the current target locale) to try further
1398      * finding a resource bundle. If the method returns a non-null locale,
1399      * it becomes the next target locale and the loading process starts over
1400      * from Step 3. Otherwise, if a base bundle was found and put on hold in
1401      * a previous Step 5, it is returned to the caller now. Otherwise, a
1402      * MissingResourceException is thrown.</li>
1403      *
1404      * <li>At this point, we have found a resource bundle that's not the
1405      * base bundle. If this bundle set its parent during its instantiation,
1406      * it is returned to the caller. Otherwise, its <a
1407      * href="./ResourceBundle.html#parent_chain">parent chain</a> is
1408      * instantiated based on the list of candidate locales from which it was
1409      * found. Finally, the bundle is returned to the caller.</li>
1410      * </ol>
1411      *
1412      * <p>During the resource bundle loading process above, this factory
1413      * method looks up the cache before calling the {@link
1414      * Control#newBundle(String, Locale, String, ClassLoader, boolean)
1415      * control.newBundle} method.  If the time-to-live period of the
1416      * resource bundle found in the cache has expired, the factory method
1417      * calls the {@link ResourceBundle.Control#needsReload(String, Locale,
1418      * String, ClassLoader, ResourceBundle, long) control.needsReload}
1419      * method to determine whether the resource bundle needs to be reloaded.
1420      * If reloading is required, the factory method calls
1421      * <code>control.newBundle</code> to reload the resource bundle.  If
1422      * <code>control.newBundle</code> returns <code>null</code>, the factory
1423      * method puts a dummy resource bundle in the cache as a mark of
1424      * nonexistent resource bundles in order to avoid lookup overhead for
1425      * subsequent requests. Such dummy resource bundles are under the same
1426      * expiration control as specified by <code>control</code>.
1427      *
1428      * <p>All resource bundles loaded are cached by default. Refer to
1429      * {@link Control#getTimeToLive(String,Locale)
1430      * control.getTimeToLive} for details.
1431      *
1432      * <p>The following is an example of the bundle loading process with the
1433      * default <code>ResourceBundle.Control</code> implementation.
1434      *
1435      * <p>Conditions:
1436      * <ul>
1437      * <li>Base bundle name: <code>foo.bar.Messages</code>
1438      * <li>Requested <code>Locale</code>: {@link Locale#ITALY}</li>
1439      * <li>Default <code>Locale</code>: {@link Locale#FRENCH}</li>
1440      * <li>Available resource bundles:
1441      * <code>foo/bar/Messages_fr.properties</code> and
1442      * <code>foo/bar/Messages.properties</code></li>
1443      * </ul>
1444      *
1445      * <p>First, <code>getBundle</code> tries loading a resource bundle in
1446      * the following sequence.
1447      *
1448      * <ul>
1449      * <li>class <code>foo.bar.Messages_it_IT</code>
1450      * <li>file <code>foo/bar/Messages_it_IT.properties</code>
1451      * <li>class <code>foo.bar.Messages_it</code></li>
1452      * <li>file <code>foo/bar/Messages_it.properties</code></li>
1453      * <li>class <code>foo.bar.Messages</code></li>
1454      * <li>file <code>foo/bar/Messages.properties</code></li>
1455      * </ul>
1456      *
1457      * <p>At this point, <code>getBundle</code> finds
1458      * <code>foo/bar/Messages.properties</code>, which is put on hold
1459      * because it's the base bundle.  <code>getBundle</code> calls {@link
1460      * Control#getFallbackLocale(String, Locale)
1461      * control.getFallbackLocale("foo.bar.Messages", Locale.ITALY)} which
1462      * returns <code>Locale.FRENCH</code>. Next, <code>getBundle</code>
1463      * tries loading a bundle in the following sequence.
1464      *
1465      * <ul>
1466      * <li>class <code>foo.bar.Messages_fr</code></li>
1467      * <li>file <code>foo/bar/Messages_fr.properties</code></li>
1468      * <li>class <code>foo.bar.Messages</code></li>
1469      * <li>file <code>foo/bar/Messages.properties</code></li>
1470      * </ul>
1471      *
1472      * <p><code>getBundle</code> finds
1473      * <code>foo/bar/Messages_fr.properties</code> and creates a
1474      * <code>ResourceBundle</code> instance. Then, <code>getBundle</code>
1475      * sets up its parent chain from the list of the candidate locales.  Only
1476      * <code>foo/bar/Messages.properties</code> is found in the list and
1477      * <code>getBundle</code> creates a <code>ResourceBundle</code> instance
1478      * that becomes the parent of the instance for
1479      * <code>foo/bar/Messages_fr.properties</code>.
1480      *
1481      * @param baseName
1482      *        the base name of the resource bundle, a fully qualified
1483      *        class name
1484      * @param targetLocale
1485      *        the locale for which a resource bundle is desired
1486      * @param loader
1487      *        the class loader from which to load the resource bundle
1488      * @param control
1489      *        the control which gives information for the resource
1490      *        bundle loading process
1491      * @return a resource bundle for the given base name and locale
1492      * @throws NullPointerException
1493      *         if <code>baseName</code>, <code>targetLocale</code>,
1494      *         <code>loader</code>, or <code>control</code> is
1495      *         <code>null</code>
1496      * @throws MissingResourceException
1497      *         if no resource bundle for the specified base name can be found
1498      * @throws IllegalArgumentException
1499      *         if the given <code>control</code> doesn't perform properly
1500      *         (e.g., <code>control.getCandidateLocales</code> returns null.)
1501      *         Note that validation of <code>control</code> is performed as
1502      *         needed.
1503      * @since 1.6
1504      * @revised 9
1505      */
1506     @CallerSensitive
getBundle(String baseName, Locale targetLocale, ClassLoader loader, Control control)1507     public static ResourceBundle getBundle(String baseName, Locale targetLocale,
1508                                            ClassLoader loader, Control control) {
1509         if (loader == null || control == null) {
1510             throw new NullPointerException();
1511         }
1512         Class<?> caller = Reflection.getCallerClass();
1513         // Android-removed: modules are not supported.
1514         // checkNamedModule(caller);
1515         return getBundleImpl(baseName, targetLocale, caller, loader, control);
1516     }
1517 
getDefaultControl(String baseName)1518     private static Control getDefaultControl(String baseName) {
1519         // Android-removed: Support for ResourceBundleControlProvider.
1520         /*
1521         if (providers != null) {
1522             for (ResourceBundleControlProvider provider : providers) {
1523                 Control control = provider.getControl(baseName);
1524                 if (control != null) {
1525                     return control;
1526                 }
1527             }
1528         }
1529         */
1530         return Control.INSTANCE;
1531     }
1532 
getDefaultControl(Class<?> caller, String baseName)1533     private static Control getDefaultControl(Class<?> caller, String baseName) {
1534         // Android-removed: modules are not supported.
1535         // return getDefaultControl(caller.getModule(), baseName);
1536         return getDefaultControl(baseName);
1537     }
1538 
1539     // Android-removed: modules are not supported
1540     /*
1541     private static Control getDefaultControl(Module targetModule, String baseName) {
1542         return targetModule.isNamed() ?
1543             Control.INSTANCE :
1544             ResourceBundleControlProviderHolder.getControl(baseName);
1545     }
1546     */
1547 
1548     // Android-removed: support for ResourceBundleControlProvider
1549     /*
1550     private static class ResourceBundleControlProviderHolder {
1551         private static final PrivilegedAction<List<ResourceBundleControlProvider>> pa =
1552             () -> {
1553                 return Collections.unmodifiableList(
1554                     ServiceLoader.load(ResourceBundleControlProvider.class,
1555                                        ClassLoader.getSystemClassLoader()).stream()
1556                         .map(ServiceLoader.Provider::get)
1557                         .collect(Collectors.toList()));
1558             };
1559 
1560         private static final List<ResourceBundleControlProvider> CONTROL_PROVIDERS =
1561             AccessController.doPrivileged(pa);
1562 
1563         private static Control getControl(String baseName) {
1564             return CONTROL_PROVIDERS.isEmpty() ?
1565                 Control.INSTANCE :
1566                 CONTROL_PROVIDERS.stream()
1567                     .flatMap(provider -> Stream.ofNullable(provider.getControl(baseName)))
1568                     .findFirst()
1569                     .orElse(Control.INSTANCE);
1570         }
1571     }
1572     */
1573 
1574     // Android-removed: modules are not supported
1575     /*
1576     private static void checkNamedModule(Class<?> caller) {
1577         if (caller.getModule().isNamed()) {
1578             throw new UnsupportedOperationException(
1579                     "ResourceBundle.Control not supported in named modules");
1580         }
1581     }
1582     */
1583 
getBundleImpl(String baseName, Locale locale, Class<?> caller, Control control)1584     private static ResourceBundle getBundleImpl(String baseName,
1585                                                 Locale locale,
1586                                                 Class<?> caller,
1587                                                 Control control) {
1588         return getBundleImpl(baseName, locale, caller, caller.getClassLoader(), control);
1589     }
1590 
1591     /**
1592      * This method will find resource bundles using the legacy mechanism
1593      * if the caller is unnamed module or the given class loader is
1594      * not the class loader of the caller module getting the resource
1595      * bundle, i.e. find the class that is visible to the class loader
1596      * and properties from unnamed module.
1597      *
1598      * The module-aware resource bundle lookup mechanism will load
1599      * the service providers using the service loader mechanism
1600      * as well as properties local in the caller module.
1601      */
getBundleImpl(String baseName, Locale locale, Class<?> caller, ClassLoader loader, Control control)1602     private static ResourceBundle getBundleImpl(String baseName,
1603                                                 Locale locale,
1604                                                 Class<?> caller,
1605                                                 ClassLoader loader,
1606                                                 Control control) {
1607         if (caller == null) {
1608             throw new InternalError("null caller");
1609         }
1610 
1611         // Android-removed: modules are not supported.
1612         /*
1613         Module callerModule = caller.getModule();
1614 
1615         // get resource bundles for a named module only if loader is the module's class loader
1616         if (callerModule.isNamed() && loader == getLoader(callerModule)) {
1617             return getBundleImpl(callerModule, callerModule, baseName, locale, control);
1618         }
1619 
1620         // find resource bundles from unnamed module of given class loader
1621         // Java agent can add to the bootclasspath e.g. via
1622         // java.lang.instrument.Instrumentation and load classes in unnamed module.
1623         // It may call RB::getBundle that will end up here with loader == null.
1624         Module unnamedModule = loader != null
1625             ? loader.getUnnamedModule()
1626             : BootLoader.getUnnamedModule();
1627         */
1628         // return getBundleImpl(callerModule, unnamedModule, baseName, locale, control);
1629         return getBundleImpl(baseName, locale, loader, control);
1630     }
1631 
1632     // Android-removed: modules are not supported.
1633     /*
1634     private static ResourceBundle getBundleFromModule(Class<?> caller,
1635                                                       Module module,
1636                                                       String baseName,
1637                                                       Locale locale,
1638                                                       Control control) {
1639         Objects.requireNonNull(module);
1640         Module callerModule = caller.getModule();
1641         if (callerModule != module) {
1642             SecurityManager sm = System.getSecurityManager();
1643             if (sm != null) {
1644                 sm.checkPermission(GET_CLASSLOADER_PERMISSION);
1645             }
1646         }
1647         return getBundleImpl(callerModule, module, baseName, locale, control);
1648     }
1649     */
1650 
1651     // Android-changed: modules are not supported.
1652     // private static ResourceBundle getBundleImpl(Module callerModule,
1653     //                                          Module module,
getBundleImpl(String baseName, Locale locale, ClassLoader loader, Control control)1654     private static ResourceBundle getBundleImpl(String baseName, Locale locale,
1655                                                 ClassLoader loader, Control control) {
1656         if (locale == null || control == null) {
1657             throw new NullPointerException();
1658         }
1659 
1660         // We create a CacheKey here for use by this call. The base name
1661         // and modules will never change during the bundle loading
1662         // process. We have to make sure that the locale is set before
1663         // using it as a cache key.
1664         // CacheKey cacheKey = new CacheKey(baseName, locale, module, callerModule);
1665         CacheKey cacheKey = new CacheKey(baseName, locale, loader);
1666         ResourceBundle bundle = null;
1667 
1668         // Quick lookup of the cache.
1669         BundleReference bundleRef = cacheList.get(cacheKey);
1670         if (bundleRef != null) {
1671             bundle = bundleRef.get();
1672             bundleRef = null;
1673         }
1674 
1675         // If this bundle and all of its parents are valid (not expired),
1676         // then return this bundle. If any of the bundles is expired, we
1677         // don't call control.needsReload here but instead drop into the
1678         // complete loading process below.
1679         if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
1680             return bundle;
1681         }
1682 
1683         // No valid bundle was found in the cache, so we need to load the
1684         // resource bundle and its parents.
1685 
1686         boolean isKnownControl = (control == Control.INSTANCE) ||
1687                                    (control instanceof SingleFormatControl);
1688         List<String> formats = control.getFormats(baseName);
1689         if (!isKnownControl && !checkList(formats)) {
1690             throw new IllegalArgumentException("Invalid Control: getFormats");
1691         }
1692 
1693         ResourceBundle baseBundle = null;
1694         for (Locale targetLocale = locale;
1695              targetLocale != null;
1696              targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
1697             List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
1698             if (!isKnownControl && !checkList(candidateLocales)) {
1699                 throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
1700             }
1701             /*
1702             bundle = findBundle(callerModule, module, cacheKey,
1703                                 candidateLocales, formats, 0, control, baseBundle);
1704             */
1705             bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);
1706 
1707             // If the loaded bundle is the base bundle and exactly for the
1708             // requested locale or the only candidate locale, then take the
1709             // bundle as the resulting one. If the loaded bundle is the base
1710             // bundle, it's put on hold until we finish processing all
1711             // fallback locales.
1712             if (isValidBundle(bundle)) {
1713                 boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
1714                 if (!isBaseBundle || bundle.locale.equals(locale)
1715                     || (candidateLocales.size() == 1
1716                         && bundle.locale.equals(candidateLocales.get(0)))) {
1717                     break;
1718                 }
1719 
1720                 // If the base bundle has been loaded, keep the reference in
1721                 // baseBundle so that we can avoid any redundant loading in case
1722                 // the control specify not to cache bundles.
1723                 if (isBaseBundle && baseBundle == null) {
1724                     baseBundle = bundle;
1725                 }
1726             }
1727         }
1728 
1729         if (bundle == null) {
1730             if (baseBundle == null) {
1731                 throwMissingResourceException(baseName, locale, cacheKey.getCause());
1732             }
1733             bundle = baseBundle;
1734         }
1735 
1736         // keep callerModule and module reachable for as long as we are operating
1737         // with WeakReference(s) to them (in CacheKey)...
1738         // Reference.reachabilityFence(callerModule);
1739         // Reference.reachabilityFence(module);
1740 
1741         return bundle;
1742     }
1743 
1744     /**
1745      * Checks if the given <code>List</code> is not null, not empty,
1746      * not having null in its elements.
1747      */
checkList(List<?> a)1748     private static boolean checkList(List<?> a) {
1749         boolean valid = (a != null && !a.isEmpty());
1750         if (valid) {
1751             int size = a.size();
1752             for (int i = 0; valid && i < size; i++) {
1753                 valid = (a.get(i) != null);
1754             }
1755         }
1756         return valid;
1757     }
1758 
1759     // private static ResourceBundle findBundle(Module callerModule,
1760     //                                         Module module,
findBundle(CacheKey cacheKey, List<Locale> candidateLocales, List<String> formats, int index, Control control, ResourceBundle baseBundle)1761     private static ResourceBundle findBundle(CacheKey cacheKey,
1762                                              List<Locale> candidateLocales,
1763                                              List<String> formats,
1764                                              int index,
1765                                              Control control,
1766                                              ResourceBundle baseBundle) {
1767         Locale targetLocale = candidateLocales.get(index);
1768         ResourceBundle parent = null;
1769         if (index != candidateLocales.size() - 1) {
1770             /*
1771             parent = findBundle(callerModule, module, cacheKey,
1772                                 candidateLocales, formats, index + 1,
1773                                 control, baseBundle);
1774             */
1775             parent = findBundle(cacheKey, candidateLocales, formats, index + 1,
1776                                 control, baseBundle);
1777         } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
1778             return baseBundle;
1779         }
1780 
1781         // Before we do the real loading work, see whether we need to
1782         // do some housekeeping: If references to modules or
1783         // resource bundles have been nulled out, remove all related
1784         // information from the cache.
1785         Object ref;
1786         while ((ref = referenceQueue.poll()) != null) {
1787             cacheList.remove(((CacheKeyReference)ref).getCacheKey());
1788         }
1789 
1790         // flag indicating the resource bundle has expired in the cache
1791         boolean expiredBundle = false;
1792 
1793         // First, look up the cache to see if it's in the cache, without
1794         // attempting to load bundle.
1795         cacheKey.setLocale(targetLocale);
1796         ResourceBundle bundle = findBundleInCache(cacheKey, control);
1797         if (isValidBundle(bundle)) {
1798             expiredBundle = bundle.expired;
1799             if (!expiredBundle) {
1800                 // If its parent is the one asked for by the candidate
1801                 // locales (the runtime lookup path), we can take the cached
1802                 // one. (If it's not identical, then we'd have to check the
1803                 // parent's parents to be consistent with what's been
1804                 // requested.)
1805                 if (bundle.parent == parent) {
1806                     return bundle;
1807                 }
1808                 // Otherwise, remove the cached one since we can't keep
1809                 // the same bundles having different parents.
1810                 BundleReference bundleRef = cacheList.get(cacheKey);
1811                 // Android-changed: Use refersTo().
1812                 if (bundleRef != null && bundleRef.refersTo(bundle)) {
1813                     cacheList.remove(cacheKey, bundleRef);
1814                 }
1815             }
1816         }
1817 
1818         if (bundle != NONEXISTENT_BUNDLE) {
1819             trace("findBundle: %d %s %s formats: %s%n", index, candidateLocales, cacheKey, formats);
1820             /*
1821             if (module.isNamed()) {
1822                 bundle = loadBundle(cacheKey, formats, control, module, callerModule);
1823             } else {
1824                 bundle = loadBundle(cacheKey, formats, control, expiredBundle);
1825             }
1826             */
1827             bundle = loadBundle(cacheKey, formats, control, expiredBundle);
1828             if (bundle != null) {
1829                 if (bundle.parent == null) {
1830                     bundle.setParent(parent);
1831                 }
1832                 bundle.locale = targetLocale;
1833                 bundle = putBundleInCache(cacheKey, bundle, control);
1834                 return bundle;
1835             }
1836 
1837             // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
1838             // instance for the locale.
1839             putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control);
1840         }
1841         return parent;
1842     }
1843 
1844     private static final String UNKNOWN_FORMAT = "";
1845 
1846 
1847     // Android-removed: modules are not supported.
1848     /*
1849      * Loads a ResourceBundle in named modules
1850      *
1851     private static ResourceBundle loadBundle(CacheKey cacheKey,
1852                                              List<String> formats,
1853                                              Control control,
1854                                              Module module,
1855                                              Module callerModule) {
1856         String baseName = cacheKey.getName();
1857         Locale targetLocale = cacheKey.getLocale();
1858 
1859         ResourceBundle bundle = null;
1860         if (cacheKey.hasProviders()) {
1861             if (callerModule == module) {
1862                 bundle = loadBundleFromProviders(baseName,
1863                                                  targetLocale,
1864                                                  cacheKey.getProviders(),
1865                                                  cacheKey);
1866             } else {
1867                 // load from provider if the caller module has access to the
1868                 // service type and also declares `uses`
1869                 ClassLoader loader = getLoader(module);
1870                 Class<ResourceBundleProvider> svc =
1871                     getResourceBundleProviderType(baseName, loader);
1872                 if (svc != null
1873                         && Reflection.verifyModuleAccess(callerModule, svc)
1874                         && callerModule.canUse(svc)) {
1875                     bundle = loadBundleFromProviders(baseName,
1876                                                      targetLocale,
1877                                                      cacheKey.getProviders(),
1878                                                      cacheKey);
1879                 }
1880             }
1881 
1882             if (bundle != null) {
1883                 cacheKey.setFormat(UNKNOWN_FORMAT);
1884             }
1885         }
1886 
1887         // If none of providers returned a bundle and the caller has no provider,
1888         // look up module-local bundles or from the class path
1889         if (bundle == null && !cacheKey.callerHasProvider()) {
1890             for (String format : formats) {
1891                 try {
1892                     switch (format) {
1893                     case "java.class":
1894                         bundle = ResourceBundleProviderHelper
1895                             .loadResourceBundle(callerModule, module, baseName, targetLocale);
1896 
1897                         break;
1898                     case "java.properties":
1899                         bundle = ResourceBundleProviderHelper
1900                             .loadPropertyResourceBundle(callerModule, module, baseName, targetLocale);
1901                         break;
1902                     default:
1903                         throw new InternalError("unexpected format: " + format);
1904                     }
1905 
1906                     if (bundle != null) {
1907                         cacheKey.setFormat(format);
1908                         break;
1909                     }
1910                 } catch (LinkageError|Exception e) {
1911                     cacheKey.setCause(e);
1912                 }
1913             }
1914         }
1915         return bundle;
1916     }
1917 
1918     /*
1919      * Returns a ServiceLoader that will find providers that are bound to
1920      * a given named module.
1921      *
1922     private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
1923                                                                           String baseName)
1924     {
1925         if (!module.isNamed()) {
1926             return null;
1927         }
1928 
1929         ClassLoader loader = getLoader(module);
1930         Class<ResourceBundleProvider> service =
1931                 getResourceBundleProviderType(baseName, loader);
1932         if (service != null && Reflection.verifyModuleAccess(module, service)) {
1933             try {
1934                 // locate providers that are visible to the class loader
1935                 // ServiceConfigurationError will be thrown if the module
1936                 // does not declare `uses` the service type
1937                 return ServiceLoader.load(service, loader, module);
1938             } catch (ServiceConfigurationError e) {
1939                 // "uses" not declared
1940                 return null;
1941             }
1942         }
1943         return null;
1944     }
1945 
1946     /**
1947      * Returns the service type of the given baseName that is visible
1948      * to the given class loader
1949      *
1950     private static Class<ResourceBundleProvider>
1951             getResourceBundleProviderType(String baseName, ClassLoader loader)
1952     {
1953         // Look up <packagename> + ".spi." + <name>"Provider"
1954         int i = baseName.lastIndexOf('.');
1955         if (i <= 0) {
1956             return null;
1957         }
1958 
1959         String name = baseName.substring(i+1, baseName.length()) + "Provider";
1960         String providerName = baseName.substring(0, i) + ".spi." + name;
1961 
1962         // Use the class loader of the getBundle caller so that the caller's
1963         // visibility of the provider type is checked.
1964         return AccessController.doPrivileged(
1965             new PrivilegedAction<>() {
1966                 @Override
1967                 public Class<ResourceBundleProvider> run() {
1968                     try {
1969                         Class<?> c = Class.forName(providerName, false, loader);
1970                         if (ResourceBundleProvider.class.isAssignableFrom(c)) {
1971                             @SuppressWarnings("unchecked")
1972                             Class<ResourceBundleProvider> s = (Class<ResourceBundleProvider>) c;
1973                             return s;
1974                         }
1975                     } catch (ClassNotFoundException e) {}
1976                     return null;
1977                 }
1978             });
1979     }
1980 
1981     /**
1982      * Loads ResourceBundle from service providers.
1983      *
1984     private static ResourceBundle loadBundleFromProviders(String baseName,
1985                                                           Locale locale,
1986                                                           ServiceLoader<ResourceBundleProvider> providers,
1987                                                           CacheKey cacheKey)
1988     {
1989         if (providers == null) return null;
1990 
1991         return AccessController.doPrivileged(
1992                 new PrivilegedAction<>() {
1993                     public ResourceBundle run() {
1994                         for (Iterator<ResourceBundleProvider> itr = providers.iterator(); itr.hasNext(); ) {
1995                             try {
1996                                 ResourceBundleProvider provider = itr.next();
1997                                 if (cacheKey != null && cacheKey.callerHasProvider == null
1998                                         && cacheKey.getModule() == provider.getClass().getModule()) {
1999                                     cacheKey.callerHasProvider = Boolean.TRUE;
2000                                 }
2001                                 ResourceBundle bundle = provider.getBundle(baseName, locale);
2002                                 trace("provider %s %s locale: %s bundle: %s%n", provider, baseName, locale, bundle);
2003                                 if (bundle != null) {
2004                                     return bundle;
2005                                 }
2006                             } catch (ServiceConfigurationError | SecurityException e) {
2007                                 if (cacheKey != null) {
2008                                     cacheKey.setCause(e);
2009                                 }
2010                             }
2011                         }
2012                         if (cacheKey != null && cacheKey.callerHasProvider == null) {
2013                             cacheKey.callerHasProvider = Boolean.FALSE;
2014                         }
2015                         return null;
2016                     }
2017                 });
2018 
2019     }
2020     */
2021 
2022     /*
2023      * Legacy mechanism to load resource bundles
2024      */
loadBundle(CacheKey cacheKey, List<String> formats, Control control, boolean reload)2025     private static ResourceBundle loadBundle(CacheKey cacheKey,
2026                                              List<String> formats,
2027                                              Control control,
2028                                              boolean reload) {
2029 
2030         // Here we actually load the bundle in the order of formats
2031         // specified by the getFormats() value.
2032         Locale targetLocale = cacheKey.getLocale();
2033 
2034         // Android-removed: modules are not supported
2035         /*
2036         Module module = cacheKey.getModule();
2037         if (module == null) {
2038             // should not happen
2039             throw new InternalError(
2040                 "Module for cache key: " + cacheKey + " has been GCed.");
2041         }
2042         ClassLoader loader = getLoaderForControl(module);
2043         */
2044 
2045         ResourceBundle bundle = null;
2046         for (String format : formats) {
2047             try {
2048                 // ResourceBundle.Control.newBundle may be overridden
2049                 bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
2050                                            cacheKey.getLoader(), reload);
2051             } catch (LinkageError | Exception error) {
2052                 // We need to handle the LinkageError case due to
2053                 // inconsistent case-sensitivity in ClassLoader.
2054                 // See 6572242 for details.
2055                 cacheKey.setCause(error);
2056             }
2057             if (bundle != null) {
2058                 // Set the format in the cache key so that it can be
2059                 // used when calling needsReload later.
2060                 cacheKey.setFormat(format);
2061                 bundle.name = cacheKey.getName();
2062                 bundle.locale = targetLocale;
2063                 // Bundle provider might reuse instances. So we should make
2064                 // sure to clear the expired flag here.
2065                 bundle.expired = false;
2066                 break;
2067             }
2068         }
2069 
2070         return bundle;
2071     }
2072 
isValidBundle(ResourceBundle bundle)2073     private static boolean isValidBundle(ResourceBundle bundle) {
2074         return bundle != null && bundle != NONEXISTENT_BUNDLE;
2075     }
2076 
2077     /**
2078      * Determines whether any of resource bundles in the parent chain,
2079      * including the leaf, have expired.
2080      */
hasValidParentChain(ResourceBundle bundle)2081     private static boolean hasValidParentChain(ResourceBundle bundle) {
2082         long now = System.currentTimeMillis();
2083         while (bundle != null) {
2084             if (bundle.expired) {
2085                 return false;
2086             }
2087             CacheKey key = bundle.cacheKey;
2088             if (key != null) {
2089                 long expirationTime = key.expirationTime;
2090                 if (expirationTime >= 0 && expirationTime <= now) {
2091                     return false;
2092                 }
2093             }
2094             bundle = bundle.parent;
2095         }
2096         return true;
2097     }
2098 
2099     /**
2100      * Throw a MissingResourceException with proper message
2101      */
throwMissingResourceException(String baseName, Locale locale, Throwable cause)2102     private static void throwMissingResourceException(String baseName,
2103                                                       Locale locale,
2104                                                       Throwable cause) {
2105         // If the cause is a MissingResourceException, avoid creating
2106         // a long chain. (6355009)
2107         if (cause instanceof MissingResourceException) {
2108             cause = null;
2109         }
2110         throw new MissingResourceException("Can't find bundle for base name "
2111                                            + baseName + ", locale " + locale,
2112                                            baseName + "_" + locale, // className
2113                                            "",                      // key
2114                                            cause);
2115     }
2116 
2117     /**
2118      * Finds a bundle in the cache. Any expired bundles are marked as
2119      * `expired' and removed from the cache upon return.
2120      *
2121      * @param cacheKey the key to look up the cache
2122      * @param control the Control to be used for the expiration control
2123      * @return the cached bundle, or null if the bundle is not found in the
2124      * cache or its parent has expired. <code>bundle.expire</code> is true
2125      * upon return if the bundle in the cache has expired.
2126      */
findBundleInCache(CacheKey cacheKey, Control control)2127     private static ResourceBundle findBundleInCache(CacheKey cacheKey,
2128                                                     Control control) {
2129         BundleReference bundleRef = cacheList.get(cacheKey);
2130         if (bundleRef == null) {
2131             return null;
2132         }
2133         ResourceBundle bundle = bundleRef.get();
2134         if (bundle == null) {
2135             return null;
2136         }
2137         ResourceBundle p = bundle.parent;
2138         assert p != NONEXISTENT_BUNDLE;
2139         // If the parent has expired, then this one must also expire. We
2140         // check only the immediate parent because the actual loading is
2141         // done from the root (base) to leaf (child) and the purpose of
2142         // checking is to propagate expiration towards the leaf. For
2143         // example, if the requested locale is ja_JP_JP and there are
2144         // bundles for all of the candidates in the cache, we have a list,
2145         //
2146         // base <- ja <- ja_JP <- ja_JP_JP
2147         //
2148         // If ja has expired, then it will reload ja and the list becomes a
2149         // tree.
2150         //
2151         // base <- ja (new)
2152         //  "   <- ja (expired) <- ja_JP <- ja_JP_JP
2153         //
2154         // When looking up ja_JP in the cache, it finds ja_JP in the cache
2155         // which references to the expired ja. Then, ja_JP is marked as
2156         // expired and removed from the cache. This will be propagated to
2157         // ja_JP_JP.
2158         //
2159         // Now, it's possible, for example, that while loading new ja_JP,
2160         // someone else has started loading the same bundle and finds the
2161         // base bundle has expired. Then, what we get from the first
2162         // getBundle call includes the expired base bundle. However, if
2163         // someone else didn't start its loading, we wouldn't know if the
2164         // base bundle has expired at the end of the loading process. The
2165         // expiration control doesn't guarantee that the returned bundle and
2166         // its parents haven't expired.
2167         //
2168         // We could check the entire parent chain to see if there's any in
2169         // the chain that has expired. But this process may never end. An
2170         // extreme case would be that getTimeToLive returns 0 and
2171         // needsReload always returns true.
2172         if (p != null && p.expired) {
2173             assert bundle != NONEXISTENT_BUNDLE;
2174             bundle.expired = true;
2175             bundle.cacheKey = null;
2176             cacheList.remove(cacheKey, bundleRef);
2177             bundle = null;
2178         } else {
2179             CacheKey key = bundleRef.getCacheKey();
2180             long expirationTime = key.expirationTime;
2181             if (!bundle.expired && expirationTime >= 0 &&
2182                 expirationTime <= System.currentTimeMillis()) {
2183                 // its TTL period has expired.
2184                 if (bundle != NONEXISTENT_BUNDLE) {
2185                     // Synchronize here to call needsReload to avoid
2186                     // redundant concurrent calls for the same bundle.
2187                     synchronized (bundle) {
2188                         expirationTime = key.expirationTime;
2189                         if (!bundle.expired && expirationTime >= 0 &&
2190                             expirationTime <= System.currentTimeMillis()) {
2191                             try {
2192                                 // Android-changed: modules are not supported.
2193                                 /*
2194                                 Module module = cacheKey.getModule();
2195                                 bundle.expired =
2196                                    module == null || // already GCed
2197                                 */
2198                                 bundle.expired = control.needsReload(key.getName(),
2199                                                         key.getLocale(),
2200                                                         key.getFormat(),
2201                                                         // getLoaderForControl(module),
2202                                                         key.getLoader(),
2203                                                         bundle,
2204                                                         key.loadTime);
2205                             } catch (Exception e) {
2206                                 cacheKey.setCause(e);
2207                             }
2208                             if (bundle.expired) {
2209                                 // If the bundle needs to be reloaded, then
2210                                 // remove the bundle from the cache, but
2211                                 // return the bundle with the expired flag
2212                                 // on.
2213                                 bundle.cacheKey = null;
2214                                 cacheList.remove(cacheKey, bundleRef);
2215                             } else {
2216                                 // Update the expiration control info. and reuse
2217                                 // the same bundle instance
2218                                 setExpirationTime(key, control);
2219                             }
2220                         }
2221                     }
2222                 } else {
2223                     // We just remove NONEXISTENT_BUNDLE from the cache.
2224                     cacheList.remove(cacheKey, bundleRef);
2225                     bundle = null;
2226                 }
2227             }
2228         }
2229         return bundle;
2230     }
2231 
2232     /**
2233      * Put a new bundle in the cache.
2234      *
2235      * @param cacheKey the key for the resource bundle
2236      * @param bundle the resource bundle to be put in the cache
2237      * @return the ResourceBundle for the cacheKey; if someone has put
2238      * the bundle before this call, the one found in the cache is
2239      * returned.
2240      */
putBundleInCache(CacheKey cacheKey, ResourceBundle bundle, Control control)2241     private static ResourceBundle putBundleInCache(CacheKey cacheKey,
2242                                                    ResourceBundle bundle,
2243                                                    Control control) {
2244         setExpirationTime(cacheKey, control);
2245         if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) {
2246             CacheKey key = new CacheKey(cacheKey);
2247             BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key);
2248             bundle.cacheKey = key;
2249 
2250             // Put the bundle in the cache if it's not been in the cache.
2251             BundleReference result = cacheList.putIfAbsent(key, bundleRef);
2252 
2253             // If someone else has put the same bundle in the cache before
2254             // us and it has not expired, we should use the one in the cache.
2255             if (result != null) {
2256                 ResourceBundle rb = result.get();
2257                 if (rb != null && !rb.expired) {
2258                     // Clear the back link to the cache key
2259                     bundle.cacheKey = null;
2260                     bundle = rb;
2261                     // Clear the reference in the BundleReference so that
2262                     // it won't be enqueued.
2263                     bundleRef.clear();
2264                 } else {
2265                     // Replace the invalid (garbage collected or expired)
2266                     // instance with the valid one.
2267                     cacheList.put(key, bundleRef);
2268                 }
2269             }
2270         }
2271         return bundle;
2272     }
2273 
setExpirationTime(CacheKey cacheKey, Control control)2274     private static void setExpirationTime(CacheKey cacheKey, Control control) {
2275         long ttl = control.getTimeToLive(cacheKey.getName(),
2276                                          cacheKey.getLocale());
2277         if (ttl >= 0) {
2278             // If any expiration time is specified, set the time to be
2279             // expired in the cache.
2280             long now = System.currentTimeMillis();
2281             cacheKey.loadTime = now;
2282             cacheKey.expirationTime = now + ttl;
2283         } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
2284             cacheKey.expirationTime = ttl;
2285         } else {
2286             throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
2287         }
2288     }
2289 
2290     /**
2291      * Removes all resource bundles from the cache that have been loaded
2292      * using the caller's class loader.
2293      *
2294      * @since 1.6
2295      * @revised 9
2296      * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2297      */
2298     @CallerSensitive
clearCache()2299     public static final void clearCache() {
2300         // Android-changed: modules are not supported.
2301         /*
2302         Class<?> caller = Reflection.getCallerClass();
2303         cacheList.keySet().removeIf(
2304             key -> key.getCallerModule() == caller.getModule()
2305         );
2306         */
2307         clearCache(getLoader(Reflection.getCallerClass()));
2308     }
2309 
2310     /**
2311      * Removes all resource bundles from the cache that have been loaded
2312      * by the given class loader.
2313      *
2314      * @param loader the class loader
2315      * @exception NullPointerException if <code>loader</code> is null
2316      * @since 1.6
2317      * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2318      */
clearCache(ClassLoader loader)2319     public static final void clearCache(ClassLoader loader) {
2320         Objects.requireNonNull(loader);
2321         // Android-changed: modules are no supported.
2322         /*
2323         cacheList.keySet().removeIf(
2324             key -> {
2325                 Module m;
2326                 return (m = key.getModule()) != null &&
2327                        getLoader(m) == loader;
2328             }
2329         );
2330         */
2331         cacheList.keySet().removeIf(key -> key.getLoader() == loader);
2332     }
2333 
2334     /**
2335      * Gets an object for the given key from this resource bundle.
2336      * Returns null if this resource bundle does not contain an
2337      * object for the given key.
2338      *
2339      * @param key the key for the desired object
2340      * @exception NullPointerException if <code>key</code> is <code>null</code>
2341      * @return the object for the given key, or null
2342      */
handleGetObject(String key)2343     protected abstract Object handleGetObject(String key);
2344 
2345     /**
2346      * Returns an enumeration of the keys.
2347      *
2348      * @return an <code>Enumeration</code> of the keys contained in
2349      *         this <code>ResourceBundle</code> and its parent bundles.
2350      */
getKeys()2351     public abstract Enumeration<String> getKeys();
2352 
2353     /**
2354      * Determines whether the given <code>key</code> is contained in
2355      * this <code>ResourceBundle</code> or its parent bundles.
2356      *
2357      * @param key
2358      *        the resource <code>key</code>
2359      * @return <code>true</code> if the given <code>key</code> is
2360      *        contained in this <code>ResourceBundle</code> or its
2361      *        parent bundles; <code>false</code> otherwise.
2362      * @exception NullPointerException
2363      *         if <code>key</code> is <code>null</code>
2364      * @since 1.6
2365      */
containsKey(String key)2366     public boolean containsKey(String key) {
2367         if (key == null) {
2368             throw new NullPointerException();
2369         }
2370         for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
2371             if (rb.handleKeySet().contains(key)) {
2372                 return true;
2373             }
2374         }
2375         return false;
2376     }
2377 
2378     /**
2379      * Returns a <code>Set</code> of all keys contained in this
2380      * <code>ResourceBundle</code> and its parent bundles.
2381      *
2382      * @return a <code>Set</code> of all keys contained in this
2383      *         <code>ResourceBundle</code> and its parent bundles.
2384      * @since 1.6
2385      */
keySet()2386     public Set<String> keySet() {
2387         Set<String> keys = new HashSet<>();
2388         for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
2389             keys.addAll(rb.handleKeySet());
2390         }
2391         return keys;
2392     }
2393 
2394     /**
2395      * Returns a <code>Set</code> of the keys contained <em>only</em>
2396      * in this <code>ResourceBundle</code>.
2397      *
2398      * <p>The default implementation returns a <code>Set</code> of the
2399      * keys returned by the {@link #getKeys() getKeys} method except
2400      * for the ones for which the {@link #handleGetObject(String)
2401      * handleGetObject} method returns <code>null</code>. Once the
2402      * <code>Set</code> has been created, the value is kept in this
2403      * <code>ResourceBundle</code> in order to avoid producing the
2404      * same <code>Set</code> in subsequent calls. Subclasses can
2405      * override this method for faster handling.
2406      *
2407      * @return a <code>Set</code> of the keys contained only in this
2408      *        <code>ResourceBundle</code>
2409      * @since 1.6
2410      */
handleKeySet()2411     protected Set<String> handleKeySet() {
2412         if (keySet == null) {
2413             synchronized (this) {
2414                 if (keySet == null) {
2415                     Set<String> keys = new HashSet<>();
2416                     Enumeration<String> enumKeys = getKeys();
2417                     while (enumKeys.hasMoreElements()) {
2418                         String key = enumKeys.nextElement();
2419                         if (handleGetObject(key) != null) {
2420                             keys.add(key);
2421                         }
2422                     }
2423                     keySet = keys;
2424                 }
2425             }
2426         }
2427         return keySet;
2428     }
2429 
2430 
2431 
2432     /**
2433      * <code>ResourceBundle.Control</code> defines a set of callback methods
2434      * that are invoked by the {@link ResourceBundle#getBundle(String,
2435      * Locale, ClassLoader, Control) ResourceBundle.getBundle} factory
2436      * methods during the bundle loading process. In other words, a
2437      * <code>ResourceBundle.Control</code> collaborates with the factory
2438      * methods for loading resource bundles. The default implementation of
2439      * the callback methods provides the information necessary for the
2440      * factory methods to perform the <a
2441      * href="./ResourceBundle.html#default_behavior">default behavior</a>.
2442      *
2443      * <p>In addition to the callback methods, the {@link
2444      * #toBundleName(String, Locale) toBundleName} and {@link
2445      * #toResourceName(String, String) toResourceName} methods are defined
2446      * primarily for convenience in implementing the callback
2447      * methods. However, the <code>toBundleName</code> method could be
2448      * overridden to provide different conventions in the organization and
2449      * packaging of localized resources.  The <code>toResourceName</code>
2450      * method is <code>final</code> to avoid use of wrong resource and class
2451      * name separators.
2452      *
2453      * <p>Two factory methods, {@link #getControl(List)} and {@link
2454      * #getNoFallbackControl(List)}, provide
2455      * <code>ResourceBundle.Control</code> instances that implement common
2456      * variations of the default bundle loading process.
2457      *
2458      * <p>The formats returned by the {@link Control#getFormats(String)
2459      * getFormats} method and candidate locales returned by the {@link
2460      * ResourceBundle.Control#getCandidateLocales(String, Locale)
2461      * getCandidateLocales} method must be consistent in all
2462      * <code>ResourceBundle.getBundle</code> invocations for the same base
2463      * bundle. Otherwise, the <code>ResourceBundle.getBundle</code> methods
2464      * may return unintended bundles. For example, if only
2465      * <code>"java.class"</code> is returned by the <code>getFormats</code>
2466      * method for the first call to <code>ResourceBundle.getBundle</code>
2467      * and only <code>"java.properties"</code> for the second call, then the
2468      * second call will return the class-based one that has been cached
2469      * during the first call.
2470      *
2471      * <p>A <code>ResourceBundle.Control</code> instance must be thread-safe
2472      * if it's simultaneously used by multiple threads.
2473      * <code>ResourceBundle.getBundle</code> does not synchronize to call
2474      * the <code>ResourceBundle.Control</code> methods. The default
2475      * implementations of the methods are thread-safe.
2476      *
2477      * <p>Applications can specify <code>ResourceBundle.Control</code>
2478      * instances returned by the <code>getControl</code> factory methods or
2479      * created from a subclass of <code>ResourceBundle.Control</code> to
2480      * customize the bundle loading process. The following are examples of
2481      * changing the default bundle loading process.
2482      *
2483      * <p><b>Example 1</b>
2484      *
2485      * <p>The following code lets <code>ResourceBundle.getBundle</code> look
2486      * up only properties-based resources.
2487      *
2488      * <pre>
2489      * import java.util.*;
2490      * import static java.util.ResourceBundle.Control.*;
2491      * ...
2492      * ResourceBundle bundle =
2493      *   ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"),
2494      *                            ResourceBundle.Control.getControl(FORMAT_PROPERTIES));
2495      * </pre>
2496      *
2497      * Given the resource bundles in the <a
2498      * href="./ResourceBundle.html#default_behavior_example">example</a> in
2499      * the <code>ResourceBundle.getBundle</code> description, this
2500      * <code>ResourceBundle.getBundle</code> call loads
2501      * <code>MyResources_fr_CH.properties</code> whose parent is
2502      * <code>MyResources_fr.properties</code> whose parent is
2503      * <code>MyResources.properties</code>. (<code>MyResources_fr_CH.properties</code>
2504      * is not hidden, but <code>MyResources_fr_CH.class</code> is.)
2505      *
2506      * <p><b>Example 2</b>
2507      *
2508      * <p>The following is an example of loading XML-based bundles
2509      * using {@link Properties#loadFromXML(java.io.InputStream)
2510      * Properties.loadFromXML}.
2511      *
2512      * <pre>
2513      * ResourceBundle rb = ResourceBundle.getBundle("Messages",
2514      *     new ResourceBundle.Control() {
2515      *         public List&lt;String&gt; getFormats(String baseName) {
2516      *             if (baseName == null)
2517      *                 throw new NullPointerException();
2518      *             return Arrays.asList("xml");
2519      *         }
2520      *         public ResourceBundle newBundle(String baseName,
2521      *                                         Locale locale,
2522      *                                         String format,
2523      *                                         ClassLoader loader,
2524      *                                         boolean reload)
2525      *                          throws IllegalAccessException,
2526      *                                 InstantiationException,
2527      *                                 IOException {
2528      *             if (baseName == null || locale == null
2529      *                   || format == null || loader == null)
2530      *                 throw new NullPointerException();
2531      *             ResourceBundle bundle = null;
2532      *             if (format.equals("xml")) {
2533      *                 String bundleName = toBundleName(baseName, locale);
2534      *                 String resourceName = toResourceName(bundleName, format);
2535      *                 InputStream stream = null;
2536      *                 if (reload) {
2537      *                     URL url = loader.getResource(resourceName);
2538      *                     if (url != null) {
2539      *                         URLConnection connection = url.openConnection();
2540      *                         if (connection != null) {
2541      *                             // Disable caches to get fresh data for
2542      *                             // reloading.
2543      *                             connection.setUseCaches(false);
2544      *                             stream = connection.getInputStream();
2545      *                         }
2546      *                     }
2547      *                 } else {
2548      *                     stream = loader.getResourceAsStream(resourceName);
2549      *                 }
2550      *                 if (stream != null) {
2551      *                     BufferedInputStream bis = new BufferedInputStream(stream);
2552      *                     bundle = new XMLResourceBundle(bis);
2553      *                     bis.close();
2554      *                 }
2555      *             }
2556      *             return bundle;
2557      *         }
2558      *     });
2559      *
2560      * ...
2561      *
2562      * private static class XMLResourceBundle extends ResourceBundle {
2563      *     private Properties props;
2564      *     XMLResourceBundle(InputStream stream) throws IOException {
2565      *         props = new Properties();
2566      *         props.loadFromXML(stream);
2567      *     }
2568      *     protected Object handleGetObject(String key) {
2569      *         return props.getProperty(key);
2570      *     }
2571      *     public Enumeration&lt;String&gt; getKeys() {
2572      *         ...
2573      *     }
2574      * }
2575      * </pre>
2576      *
2577      * @since 1.6
2578      * @revised 9
2579      */
2580     public static class Control {
2581         /**
2582          * The default format <code>List</code>, which contains the strings
2583          * <code>"java.class"</code> and <code>"java.properties"</code>, in
2584          * this order. This <code>List</code> is unmodifiable.
2585          *
2586          * @see #getFormats(String)
2587          */
2588         public static final List<String> FORMAT_DEFAULT
2589             = List.of("java.class", "java.properties");
2590 
2591         /**
2592          * The class-only format <code>List</code> containing
2593          * <code>"java.class"</code>. This <code>List</code> is unmodifiable.
2594          *
2595          * @see #getFormats(String)
2596          */
2597         public static final List<String> FORMAT_CLASS = List.of("java.class");
2598 
2599         /**
2600          * The properties-only format <code>List</code> containing
2601          * <code>"java.properties"</code>. This <code>List</code> is unmodifiable.
2602          *
2603          * @see #getFormats(String)
2604          */
2605         public static final List<String> FORMAT_PROPERTIES
2606             = List.of("java.properties");
2607 
2608         /**
2609          * The time-to-live constant for not caching loaded resource bundle
2610          * instances.
2611          *
2612          * @see #getTimeToLive(String, Locale)
2613          */
2614         public static final long TTL_DONT_CACHE = -1;
2615 
2616         /**
2617          * The time-to-live constant for disabling the expiration control
2618          * for loaded resource bundle instances in the cache.
2619          *
2620          * @see #getTimeToLive(String, Locale)
2621          */
2622         public static final long TTL_NO_EXPIRATION_CONTROL = -2;
2623 
2624         private static final Control INSTANCE = new Control();
2625 
2626         /**
2627          * Sole constructor. (For invocation by subclass constructors,
2628          * typically implicit.)
2629          */
Control()2630         protected Control() {
2631         }
2632 
2633         /**
2634          * Returns a <code>ResourceBundle.Control</code> in which the {@link
2635          * #getFormats(String) getFormats} method returns the specified
2636          * <code>formats</code>. The <code>formats</code> must be equal to
2637          * one of {@link Control#FORMAT_PROPERTIES}, {@link
2638          * Control#FORMAT_CLASS} or {@link
2639          * Control#FORMAT_DEFAULT}. <code>ResourceBundle.Control</code>
2640          * instances returned by this method are singletons and thread-safe.
2641          *
2642          * <p>Specifying {@link Control#FORMAT_DEFAULT} is equivalent to
2643          * instantiating the <code>ResourceBundle.Control</code> class,
2644          * except that this method returns a singleton.
2645          *
2646          * @param formats
2647          *        the formats to be returned by the
2648          *        <code>ResourceBundle.Control.getFormats</code> method
2649          * @return a <code>ResourceBundle.Control</code> supporting the
2650          *        specified <code>formats</code>
2651          * @exception NullPointerException
2652          *        if <code>formats</code> is <code>null</code>
2653          * @exception IllegalArgumentException
2654          *        if <code>formats</code> is unknown
2655          */
getControl(List<String> formats)2656         public static final Control getControl(List<String> formats) {
2657             if (formats.equals(Control.FORMAT_PROPERTIES)) {
2658                 return SingleFormatControl.PROPERTIES_ONLY;
2659             }
2660             if (formats.equals(Control.FORMAT_CLASS)) {
2661                 return SingleFormatControl.CLASS_ONLY;
2662             }
2663             if (formats.equals(Control.FORMAT_DEFAULT)) {
2664                 return Control.INSTANCE;
2665             }
2666             throw new IllegalArgumentException();
2667         }
2668 
2669         /**
2670          * Returns a <code>ResourceBundle.Control</code> in which the {@link
2671          * #getFormats(String) getFormats} method returns the specified
2672          * <code>formats</code> and the {@link
2673          * Control#getFallbackLocale(String, Locale) getFallbackLocale}
2674          * method returns <code>null</code>. The <code>formats</code> must
2675          * be equal to one of {@link Control#FORMAT_PROPERTIES}, {@link
2676          * Control#FORMAT_CLASS} or {@link Control#FORMAT_DEFAULT}.
2677          * <code>ResourceBundle.Control</code> instances returned by this
2678          * method are singletons and thread-safe.
2679          *
2680          * @param formats
2681          *        the formats to be returned by the
2682          *        <code>ResourceBundle.Control.getFormats</code> method
2683          * @return a <code>ResourceBundle.Control</code> supporting the
2684          *        specified <code>formats</code> with no fallback
2685          *        <code>Locale</code> support
2686          * @exception NullPointerException
2687          *        if <code>formats</code> is <code>null</code>
2688          * @exception IllegalArgumentException
2689          *        if <code>formats</code> is unknown
2690          */
getNoFallbackControl(List<String> formats)2691         public static final Control getNoFallbackControl(List<String> formats) {
2692             if (formats.equals(Control.FORMAT_DEFAULT)) {
2693                 return NoFallbackControl.NO_FALLBACK;
2694             }
2695             if (formats.equals(Control.FORMAT_PROPERTIES)) {
2696                 return NoFallbackControl.PROPERTIES_ONLY_NO_FALLBACK;
2697             }
2698             if (formats.equals(Control.FORMAT_CLASS)) {
2699                 return NoFallbackControl.CLASS_ONLY_NO_FALLBACK;
2700             }
2701             throw new IllegalArgumentException();
2702         }
2703 
2704         /**
2705          * Returns a <code>List</code> of <code>String</code>s containing
2706          * formats to be used to load resource bundles for the given
2707          * <code>baseName</code>. The <code>ResourceBundle.getBundle</code>
2708          * factory method tries to load resource bundles with formats in the
2709          * order specified by the list. The list returned by this method
2710          * must have at least one <code>String</code>. The predefined
2711          * formats are <code>"java.class"</code> for class-based resource
2712          * bundles and <code>"java.properties"</code> for {@linkplain
2713          * PropertyResourceBundle properties-based} ones. Strings starting
2714          * with <code>"java."</code> are reserved for future extensions and
2715          * must not be used by application-defined formats.
2716          *
2717          * <p>It is not a requirement to return an immutable (unmodifiable)
2718          * <code>List</code>.  However, the returned <code>List</code> must
2719          * not be mutated after it has been returned by
2720          * <code>getFormats</code>.
2721          *
2722          * <p>The default implementation returns {@link #FORMAT_DEFAULT} so
2723          * that the <code>ResourceBundle.getBundle</code> factory method
2724          * looks up first class-based resource bundles, then
2725          * properties-based ones.
2726          *
2727          * @param baseName
2728          *        the base name of the resource bundle, a fully qualified class
2729          *        name
2730          * @return a <code>List</code> of <code>String</code>s containing
2731          *        formats for loading resource bundles.
2732          * @exception NullPointerException
2733          *        if <code>baseName</code> is null
2734          * @see #FORMAT_DEFAULT
2735          * @see #FORMAT_CLASS
2736          * @see #FORMAT_PROPERTIES
2737          */
getFormats(String baseName)2738         public List<String> getFormats(String baseName) {
2739             if (baseName == null) {
2740                 throw new NullPointerException();
2741             }
2742             return FORMAT_DEFAULT;
2743         }
2744 
2745         /**
2746          * Returns a <code>List</code> of <code>Locale</code>s as candidate
2747          * locales for <code>baseName</code> and <code>locale</code>. This
2748          * method is called by the <code>ResourceBundle.getBundle</code>
2749          * factory method each time the factory method tries finding a
2750          * resource bundle for a target <code>Locale</code>.
2751          *
2752          * <p>The sequence of the candidate locales also corresponds to the
2753          * runtime resource lookup path (also known as the <I>parent
2754          * chain</I>), if the corresponding resource bundles for the
2755          * candidate locales exist and their parents are not defined by
2756          * loaded resource bundles themselves.  The last element of the list
2757          * must be a {@linkplain Locale#ROOT root locale} if it is desired to
2758          * have the base bundle as the terminal of the parent chain.
2759          *
2760          * <p>If the given locale is equal to <code>Locale.ROOT</code> (the
2761          * root locale), a <code>List</code> containing only the root
2762          * <code>Locale</code> must be returned. In this case, the
2763          * <code>ResourceBundle.getBundle</code> factory method loads only
2764          * the base bundle as the resulting resource bundle.
2765          *
2766          * <p>It is not a requirement to return an immutable (unmodifiable)
2767          * <code>List</code>. However, the returned <code>List</code> must not
2768          * be mutated after it has been returned by
2769          * <code>getCandidateLocales</code>.
2770          *
2771          * <p>The default implementation returns a <code>List</code> containing
2772          * <code>Locale</code>s using the rules described below.  In the
2773          * description below, <em>L</em>, <em>S</em>, <em>C</em> and <em>V</em>
2774          * respectively represent non-empty language, script, country, and
2775          * variant.  For example, [<em>L</em>, <em>C</em>] represents a
2776          * <code>Locale</code> that has non-empty values only for language and
2777          * country.  The form <em>L</em>("xx") represents the (non-empty)
2778          * language value is "xx".  For all cases, <code>Locale</code>s whose
2779          * final component values are empty strings are omitted.
2780          *
2781          * <ol><li>For an input <code>Locale</code> with an empty script value,
2782          * append candidate <code>Locale</code>s by omitting the final component
2783          * one by one as below:
2784          *
2785          * <ul>
2786          * <li> [<em>L</em>, <em>C</em>, <em>V</em>] </li>
2787          * <li> [<em>L</em>, <em>C</em>] </li>
2788          * <li> [<em>L</em>] </li>
2789          * <li> <code>Locale.ROOT</code> </li>
2790          * </ul></li>
2791          *
2792          * <li>For an input <code>Locale</code> with a non-empty script value,
2793          * append candidate <code>Locale</code>s by omitting the final component
2794          * up to language, then append candidates generated from the
2795          * <code>Locale</code> with country and variant restored:
2796          *
2797          * <ul>
2798          * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V</em>]</li>
2799          * <li> [<em>L</em>, <em>S</em>, <em>C</em>]</li>
2800          * <li> [<em>L</em>, <em>S</em>]</li>
2801          * <li> [<em>L</em>, <em>C</em>, <em>V</em>]</li>
2802          * <li> [<em>L</em>, <em>C</em>]</li>
2803          * <li> [<em>L</em>]</li>
2804          * <li> <code>Locale.ROOT</code></li>
2805          * </ul></li>
2806          *
2807          * <li>For an input <code>Locale</code> with a variant value consisting
2808          * of multiple subtags separated by underscore, generate candidate
2809          * <code>Locale</code>s by omitting the variant subtags one by one, then
2810          * insert them after every occurrence of <code> Locale</code>s with the
2811          * full variant value in the original list.  For example, if
2812          * the variant consists of two subtags <em>V1</em> and <em>V2</em>:
2813          *
2814          * <ul>
2815          * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]</li>
2816          * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>]</li>
2817          * <li> [<em>L</em>, <em>S</em>, <em>C</em>]</li>
2818          * <li> [<em>L</em>, <em>S</em>]</li>
2819          * <li> [<em>L</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]</li>
2820          * <li> [<em>L</em>, <em>C</em>, <em>V1</em>]</li>
2821          * <li> [<em>L</em>, <em>C</em>]</li>
2822          * <li> [<em>L</em>]</li>
2823          * <li> <code>Locale.ROOT</code></li>
2824          * </ul></li>
2825          *
2826          * <li>Special cases for Chinese.  When an input <code>Locale</code> has the
2827          * language "zh" (Chinese) and an empty script value, either "Hans" (Simplified) or
2828          * "Hant" (Traditional) might be supplied, depending on the country.
2829          * When the country is "CN" (China) or "SG" (Singapore), "Hans" is supplied.
2830          * When the country is "HK" (Hong Kong SAR China), "MO" (Macau SAR China),
2831          * or "TW" (Taiwan), "Hant" is supplied.  For all other countries or when the country
2832          * is empty, no script is supplied.  For example, for <code>Locale("zh", "CN")
2833          * </code>, the candidate list will be:
2834          * <ul>
2835          * <li> [<em>L</em>("zh"), <em>S</em>("Hans"), <em>C</em>("CN")]</li>
2836          * <li> [<em>L</em>("zh"), <em>S</em>("Hans")]</li>
2837          * <li> [<em>L</em>("zh"), <em>C</em>("CN")]</li>
2838          * <li> [<em>L</em>("zh")]</li>
2839          * <li> <code>Locale.ROOT</code></li>
2840          * </ul>
2841          *
2842          * For <code>Locale("zh", "TW")</code>, the candidate list will be:
2843          * <ul>
2844          * <li> [<em>L</em>("zh"), <em>S</em>("Hant"), <em>C</em>("TW")]</li>
2845          * <li> [<em>L</em>("zh"), <em>S</em>("Hant")]</li>
2846          * <li> [<em>L</em>("zh"), <em>C</em>("TW")]</li>
2847          * <li> [<em>L</em>("zh")]</li>
2848          * <li> <code>Locale.ROOT</code></li>
2849          * </ul></li>
2850          *
2851          * <li>Special cases for Norwegian.  Both <code>Locale("no", "NO",
2852          * "NY")</code> and <code>Locale("nn", "NO")</code> represent Norwegian
2853          * Nynorsk.  When a locale's language is "nn", the standard candidate
2854          * list is generated up to [<em>L</em>("nn")], and then the following
2855          * candidates are added:
2856          *
2857          * <ul><li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("NY")]</li>
2858          * <li> [<em>L</em>("no"), <em>C</em>("NO")]</li>
2859          * <li> [<em>L</em>("no")]</li>
2860          * <li> <code>Locale.ROOT</code></li>
2861          * </ul>
2862          *
2863          * If the locale is exactly <code>Locale("no", "NO", "NY")</code>, it is first
2864          * converted to <code>Locale("nn", "NO")</code> and then the above procedure is
2865          * followed.
2866          *
2867          * <p>Also, Java treats the language "no" as a synonym of Norwegian
2868          * Bokm&#xE5;l "nb".  Except for the single case <code>Locale("no",
2869          * "NO", "NY")</code> (handled above), when an input <code>Locale</code>
2870          * has language "no" or "nb", candidate <code>Locale</code>s with
2871          * language code "no" and "nb" are interleaved, first using the
2872          * requested language, then using its synonym. For example,
2873          * <code>Locale("nb", "NO", "POSIX")</code> generates the following
2874          * candidate list:
2875          *
2876          * <ul>
2877          * <li> [<em>L</em>("nb"), <em>C</em>("NO"), <em>V</em>("POSIX")]</li>
2878          * <li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("POSIX")]</li>
2879          * <li> [<em>L</em>("nb"), <em>C</em>("NO")]</li>
2880          * <li> [<em>L</em>("no"), <em>C</em>("NO")]</li>
2881          * <li> [<em>L</em>("nb")]</li>
2882          * <li> [<em>L</em>("no")]</li>
2883          * <li> <code>Locale.ROOT</code></li>
2884          * </ul>
2885          *
2886          * <code>Locale("no", "NO", "POSIX")</code> would generate the same list
2887          * except that locales with "no" would appear before the corresponding
2888          * locales with "nb".</li>
2889          * </ol>
2890          *
2891          * <p>The default implementation uses an {@link ArrayList} that
2892          * overriding implementations may modify before returning it to the
2893          * caller. However, a subclass must not modify it after it has
2894          * been returned by <code>getCandidateLocales</code>.
2895          *
2896          * <p>For example, if the given <code>baseName</code> is "Messages"
2897          * and the given <code>locale</code> is
2898          * <code>Locale("ja",&nbsp;"",&nbsp;"XX")</code>, then a
2899          * <code>List</code> of <code>Locale</code>s:
2900          * <pre>
2901          *     Locale("ja", "", "XX")
2902          *     Locale("ja")
2903          *     Locale.ROOT
2904          * </pre>
2905          * is returned. And if the resource bundles for the "ja" and
2906          * "" <code>Locale</code>s are found, then the runtime resource
2907          * lookup path (parent chain) is:
2908          * <pre>{@code
2909          *     Messages_ja -> Messages
2910          * }</pre>
2911          *
2912          * @param baseName
2913          *        the base name of the resource bundle, a fully
2914          *        qualified class name
2915          * @param locale
2916          *        the locale for which a resource bundle is desired
2917          * @return a <code>List</code> of candidate
2918          *        <code>Locale</code>s for the given <code>locale</code>
2919          * @exception NullPointerException
2920          *        if <code>baseName</code> or <code>locale</code> is
2921          *        <code>null</code>
2922          */
getCandidateLocales(String baseName, Locale locale)2923         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
2924             if (baseName == null) {
2925                 throw new NullPointerException();
2926             }
2927             return new ArrayList<>(CANDIDATES_CACHE.get(locale.getBaseLocale()));
2928         }
2929 
2930         private static final CandidateListCache CANDIDATES_CACHE = new CandidateListCache();
2931 
2932         private static class CandidateListCache extends LocaleObjectCache<BaseLocale, List<Locale>> {
createObject(BaseLocale base)2933             protected List<Locale> createObject(BaseLocale base) {
2934                 String language = base.getLanguage();
2935                 String script = base.getScript();
2936                 String region = base.getRegion();
2937                 String variant = base.getVariant();
2938 
2939                 // Special handling for Norwegian
2940                 boolean isNorwegianBokmal = false;
2941                 boolean isNorwegianNynorsk = false;
2942                 if (language.equals("no")) {
2943                     if (region.equals("NO") && variant.equals("NY")) {
2944                         variant = "";
2945                         isNorwegianNynorsk = true;
2946                     } else {
2947                         isNorwegianBokmal = true;
2948                     }
2949                 }
2950                 if (language.equals("nb") || isNorwegianBokmal) {
2951                     List<Locale> tmpList = getDefaultList("nb", script, region, variant);
2952                     // Insert a locale replacing "nb" with "no" for every list entry
2953                     List<Locale> bokmalList = new LinkedList<>();
2954                     for (Locale l : tmpList) {
2955                         bokmalList.add(l);
2956                         if (l.getLanguage().isEmpty()) {
2957                             break;
2958                         }
2959                         bokmalList.add(Locale.getInstance("no", l.getScript(), l.getCountry(),
2960                                 l.getVariant(), null));
2961                     }
2962                     return bokmalList;
2963                 } else if (language.equals("nn") || isNorwegianNynorsk) {
2964                     // Insert no_NO_NY, no_NO, no after nn
2965                     List<Locale> nynorskList = getDefaultList("nn", script, region, variant);
2966                     int idx = nynorskList.size() - 1;
2967                     nynorskList.add(idx++, Locale.getInstance("no", "NO", "NY"));
2968                     nynorskList.add(idx++, Locale.getInstance("no", "NO", ""));
2969                     nynorskList.add(idx++, Locale.getInstance("no", "", ""));
2970                     return nynorskList;
2971                 }
2972                 // Special handling for Chinese
2973                 else if (language.equals("zh")) {
2974                     if (script.isEmpty() && !region.isEmpty()) {
2975                         // Supply script for users who want to use zh_Hans/zh_Hant
2976                         // as bundle names (recommended for Java7+)
2977                         switch (region) {
2978                         case "TW":
2979                         case "HK":
2980                         case "MO":
2981                             script = "Hant";
2982                             break;
2983                         case "CN":
2984                         case "SG":
2985                             script = "Hans";
2986                             break;
2987                         }
2988                     }
2989                 }
2990 
2991                 return getDefaultList(language, script, region, variant);
2992             }
2993 
getDefaultList(String language, String script, String region, String variant)2994             private static List<Locale> getDefaultList(String language, String script, String region, String variant) {
2995                 List<String> variants = null;
2996 
2997                 if (!variant.isEmpty()) {
2998                     variants = new LinkedList<>();
2999                     int idx = variant.length();
3000                     while (idx != -1) {
3001                         variants.add(variant.substring(0, idx));
3002                         idx = variant.lastIndexOf('_', --idx);
3003                     }
3004                 }
3005 
3006                 List<Locale> list = new LinkedList<>();
3007 
3008                 if (variants != null) {
3009                     for (String v : variants) {
3010                         list.add(Locale.getInstance(language, script, region, v, null));
3011                     }
3012                 }
3013                 if (!region.isEmpty()) {
3014                     list.add(Locale.getInstance(language, script, region, "", null));
3015                 }
3016                 if (!script.isEmpty()) {
3017                     list.add(Locale.getInstance(language, script, "", "", null));
3018                     // Special handling for Chinese
3019                     if (language.equals("zh")) {
3020                         if (region.isEmpty()) {
3021                             // Supply region(country) for users who still package Chinese
3022                             // bundles using old convension.
3023                             switch (script) {
3024                                 case "Hans":
3025                                     region = "CN";
3026                                     break;
3027                                 case "Hant":
3028                                     region = "TW";
3029                                     break;
3030                             }
3031                         }
3032                     }
3033 
3034                     // With script, after truncating variant, region and script,
3035                     // start over without script.
3036                     if (variants != null) {
3037                         for (String v : variants) {
3038                             list.add(Locale.getInstance(language, "", region, v, null));
3039                         }
3040                     }
3041                     if (!region.isEmpty()) {
3042                         list.add(Locale.getInstance(language, "", region, "", null));
3043                     }
3044                 }
3045                 if (!language.isEmpty()) {
3046                     list.add(Locale.getInstance(language, "", "", "", null));
3047                 }
3048                 // Add root locale at the end
3049                 list.add(Locale.ROOT);
3050 
3051                 return list;
3052             }
3053         }
3054 
3055         /**
3056          * Returns a <code>Locale</code> to be used as a fallback locale for
3057          * further resource bundle searches by the
3058          * <code>ResourceBundle.getBundle</code> factory method. This method
3059          * is called from the factory method every time when no resulting
3060          * resource bundle has been found for <code>baseName</code> and
3061          * <code>locale</code>, where locale is either the parameter for
3062          * <code>ResourceBundle.getBundle</code> or the previous fallback
3063          * locale returned by this method.
3064          *
3065          * <p>The method returns <code>null</code> if no further fallback
3066          * search is desired.
3067          *
3068          * <p>The default implementation returns the {@linkplain
3069          * Locale#getDefault() default <code>Locale</code>} if the given
3070          * <code>locale</code> isn't the default one.  Otherwise,
3071          * <code>null</code> is returned.
3072          *
3073          * @param baseName
3074          *        the base name of the resource bundle, a fully
3075          *        qualified class name for which
3076          *        <code>ResourceBundle.getBundle</code> has been
3077          *        unable to find any resource bundles (except for the
3078          *        base bundle)
3079          * @param locale
3080          *        the <code>Locale</code> for which
3081          *        <code>ResourceBundle.getBundle</code> has been
3082          *        unable to find any resource bundles (except for the
3083          *        base bundle)
3084          * @return a <code>Locale</code> for the fallback search,
3085          *        or <code>null</code> if no further fallback search
3086          *        is desired.
3087          * @exception NullPointerException
3088          *        if <code>baseName</code> or <code>locale</code>
3089          *        is <code>null</code>
3090          */
getFallbackLocale(String baseName, Locale locale)3091         public Locale getFallbackLocale(String baseName, Locale locale) {
3092             if (baseName == null) {
3093                 throw new NullPointerException();
3094             }
3095             Locale defaultLocale = Locale.getDefault();
3096             return locale.equals(defaultLocale) ? null : defaultLocale;
3097         }
3098 
3099         /**
3100          * Instantiates a resource bundle for the given bundle name of the
3101          * given format and locale, using the given class loader if
3102          * necessary. This method returns <code>null</code> if there is no
3103          * resource bundle available for the given parameters. If a resource
3104          * bundle can't be instantiated due to an unexpected error, the
3105          * error must be reported by throwing an <code>Error</code> or
3106          * <code>Exception</code> rather than simply returning
3107          * <code>null</code>.
3108          *
3109          * <p>If the <code>reload</code> flag is <code>true</code>, it
3110          * indicates that this method is being called because the previously
3111          * loaded resource bundle has expired.
3112          *
3113          * <p>The default implementation instantiates a
3114          * <code>ResourceBundle</code> as follows.
3115          *
3116          * <ul>
3117          *
3118          * <li>The bundle name is obtained by calling {@link
3119          * #toBundleName(String, Locale) toBundleName(baseName,
3120          * locale)}.</li>
3121          *
3122          * <li>If <code>format</code> is <code>"java.class"</code>, the
3123          * {@link Class} specified by the bundle name is loaded with the
3124          * given class loader. If the {@code Class} is found and accessible
3125          * then the <code>ResourceBundle</code> is instantiated.  The
3126          * resource bundle is accessible if the package of the bundle class file
3127          * is open unconditionally; otherwise, {@code IllegalAccessException}
3128          * will be thrown.
3129          * Note that the <code>reload</code> flag is ignored for loading
3130          * class-based resource bundles in this default implementation.
3131          * </li>
3132          *
3133          * <li>If <code>format</code> is <code>"java.properties"</code>,
3134          * {@link #toResourceName(String, String) toResourceName(bundlename,
3135          * "properties")} is called to get the resource name.
3136          * If <code>reload</code> is <code>true</code>, {@link
3137          * ClassLoader#getResource(String) load.getResource} is called
3138          * to get a {@link URL} for creating a {@link
3139          * URLConnection}. This <code>URLConnection</code> is used to
3140          * {@linkplain URLConnection#setUseCaches(boolean) disable the
3141          * caches} of the underlying resource loading layers,
3142          * and to {@linkplain URLConnection#getInputStream() get an
3143          * <code>InputStream</code>}.
3144          * Otherwise, {@link ClassLoader#getResourceAsStream(String)
3145          * loader.getResourceAsStream} is called to get an {@link
3146          * InputStream}. Then, a {@link
3147          * PropertyResourceBundle} is constructed with the
3148          * <code>InputStream</code>.</li>
3149          *
3150          * <li>If <code>format</code> is neither <code>"java.class"</code>
3151          * nor <code>"java.properties"</code>, an
3152          * <code>IllegalArgumentException</code> is thrown.</li>
3153          *
3154          * </ul>
3155          *
3156          * @param baseName
3157          *        the base bundle name of the resource bundle, a fully
3158          *        qualified class name
3159          * @param locale
3160          *        the locale for which the resource bundle should be
3161          *        instantiated
3162          * @param format
3163          *        the resource bundle format to be loaded
3164          * @param loader
3165          *        the <code>ClassLoader</code> to use to load the bundle
3166          * @param reload
3167          *        the flag to indicate bundle reloading; <code>true</code>
3168          *        if reloading an expired resource bundle,
3169          *        <code>false</code> otherwise
3170          * @return the resource bundle instance,
3171          *        or <code>null</code> if none could be found.
3172          * @exception NullPointerException
3173          *        if <code>bundleName</code>, <code>locale</code>,
3174          *        <code>format</code>, or <code>loader</code> is
3175          *        <code>null</code>, or if <code>null</code> is returned by
3176          *        {@link #toBundleName(String, Locale) toBundleName}
3177          * @exception IllegalArgumentException
3178          *        if <code>format</code> is unknown, or if the resource
3179          *        found for the given parameters contains malformed data.
3180          * @exception ClassCastException
3181          *        if the loaded class cannot be cast to <code>ResourceBundle</code>
3182          * @exception IllegalAccessException
3183          *        if the class or its nullary constructor is not
3184          *        accessible.
3185          * @exception InstantiationException
3186          *        if the instantiation of a class fails for some other
3187          *        reason.
3188          * @exception ExceptionInInitializerError
3189          *        if the initialization provoked by this method fails.
3190          * @exception SecurityException
3191          *        If a security manager is present and creation of new
3192          *        instances is denied. See {@link Class#newInstance()}
3193          *        for details.
3194          * @exception IOException
3195          *        if an error occurred when reading resources using
3196          *        any I/O operations
3197          * @revised 9
3198          */
newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)3199         public ResourceBundle newBundle(String baseName, Locale locale, String format,
3200                                         ClassLoader loader, boolean reload)
3201                     throws IllegalAccessException, InstantiationException, IOException {
3202             /*
3203              * Legacy mechanism to locate resource bundle in unnamed module only
3204              * that is visible to the given loader and accessible to the given caller.
3205              */
3206             String bundleName = toBundleName(baseName, locale);
3207             ResourceBundle bundle = null;
3208             if (format.equals("java.class")) {
3209                 try {
3210                     Class<?> c = loader.loadClass(bundleName);
3211                     // If the class isn't a ResourceBundle subclass, throw a
3212                     // ClassCastException.
3213                     if (ResourceBundle.class.isAssignableFrom(c)) {
3214                         @SuppressWarnings("unchecked")
3215                         Class<ResourceBundle> bundleClass = (Class<ResourceBundle>)c;
3216                         // Android-removed: modules are not supported.
3217                         /*
3218                         Module m = bundleClass.getModule();
3219 
3220                         // To access a resource bundle in a named module,
3221                         // either class-based or properties-based, the resource
3222                         // bundle must be opened unconditionally,
3223                         // same rule as accessing a resource file.
3224                         if (m.isNamed() && !m.isOpen(bundleClass.getPackageName())) {
3225                             throw new IllegalAccessException("unnamed module can't load " +
3226                                 bundleClass.getName() + " in " + m.toString());
3227                         }
3228                         */
3229                         try {
3230                             // bundle in a unnamed module
3231                             Constructor<ResourceBundle> ctor = bundleClass.getConstructor();
3232                             if (!Modifier.isPublic(ctor.getModifiers())) {
3233                                 return null;
3234                             }
3235 
3236                             // java.base may not be able to read the bundleClass's module.
3237                             PrivilegedAction<Void> pa1 = () -> { ctor.setAccessible(true); return null; };
3238                             AccessController.doPrivileged(pa1);
3239                             bundle = ctor.newInstance((Object[]) null);
3240                         } catch (InvocationTargetException e) {
3241                             uncheckedThrow(e);
3242                         }
3243                     } else {
3244                         throw new ClassCastException(c.getName()
3245                                 + " cannot be cast to ResourceBundle");
3246                     }
3247                 } catch (ClassNotFoundException|NoSuchMethodException e) {
3248                 }
3249             } else if (format.equals("java.properties")) {
3250                 final String resourceName = toResourceName0(bundleName, "properties");
3251                 if (resourceName == null) {
3252                     return bundle;
3253                 }
3254 
3255                 final boolean reloadFlag = reload;
3256                 InputStream stream = null;
3257                 try {
3258                     stream = AccessController.doPrivileged(
3259                         new PrivilegedExceptionAction<>() {
3260                             public InputStream run() throws IOException {
3261                                 URL url = loader.getResource(resourceName);
3262                                 if (url == null) return null;
3263 
3264                                 URLConnection connection = url.openConnection();
3265                                 if (reloadFlag) {
3266                                     // Disable caches to get fresh data for
3267                                     // reloading.
3268                                     connection.setUseCaches(false);
3269                                 }
3270                                 return connection.getInputStream();
3271                             }
3272                         });
3273                 } catch (PrivilegedActionException e) {
3274                     throw (IOException) e.getException();
3275                 }
3276                 if (stream != null) {
3277                     try {
3278                         // Android-changed: Use UTF-8 for property based resources. b/26879578
3279                         // bundle = new PropertyResourceBundle(stream);
3280                         bundle = new PropertyResourceBundle(
3281                                 new InputStreamReader(stream, StandardCharsets.UTF_8));
3282                     } finally {
3283                         stream.close();
3284                     }
3285                 }
3286             } else {
3287                 throw new IllegalArgumentException("unknown format: " + format);
3288             }
3289             return bundle;
3290         }
3291 
3292         /**
3293          * Returns the time-to-live (TTL) value for resource bundles that
3294          * are loaded under this
3295          * <code>ResourceBundle.Control</code>. Positive time-to-live values
3296          * specify the number of milliseconds a bundle can remain in the
3297          * cache without being validated against the source data from which
3298          * it was constructed. The value 0 indicates that a bundle must be
3299          * validated each time it is retrieved from the cache. {@link
3300          * #TTL_DONT_CACHE} specifies that loaded resource bundles are not
3301          * put in the cache. {@link #TTL_NO_EXPIRATION_CONTROL} specifies
3302          * that loaded resource bundles are put in the cache with no
3303          * expiration control.
3304          *
3305          * <p>The expiration affects only the bundle loading process by the
3306          * <code>ResourceBundle.getBundle</code> factory method.  That is,
3307          * if the factory method finds a resource bundle in the cache that
3308          * has expired, the factory method calls the {@link
3309          * #needsReload(String, Locale, String, ClassLoader, ResourceBundle,
3310          * long) needsReload} method to determine whether the resource
3311          * bundle needs to be reloaded. If <code>needsReload</code> returns
3312          * <code>true</code>, the cached resource bundle instance is removed
3313          * from the cache. Otherwise, the instance stays in the cache,
3314          * updated with the new TTL value returned by this method.
3315          *
3316          * <p>All cached resource bundles are subject to removal from the
3317          * cache due to memory constraints of the runtime environment.
3318          * Returning a large positive value doesn't mean to lock loaded
3319          * resource bundles in the cache.
3320          *
3321          * <p>The default implementation returns {@link #TTL_NO_EXPIRATION_CONTROL}.
3322          *
3323          * @param baseName
3324          *        the base name of the resource bundle for which the
3325          *        expiration value is specified.
3326          * @param locale
3327          *        the locale of the resource bundle for which the
3328          *        expiration value is specified.
3329          * @return the time (0 or a positive millisecond offset from the
3330          *        cached time) to get loaded bundles expired in the cache,
3331          *        {@link #TTL_NO_EXPIRATION_CONTROL} to disable the
3332          *        expiration control, or {@link #TTL_DONT_CACHE} to disable
3333          *        caching.
3334          * @exception NullPointerException
3335          *        if <code>baseName</code> or <code>locale</code> is
3336          *        <code>null</code>
3337          */
getTimeToLive(String baseName, Locale locale)3338         public long getTimeToLive(String baseName, Locale locale) {
3339             if (baseName == null || locale == null) {
3340                 throw new NullPointerException();
3341             }
3342             return TTL_NO_EXPIRATION_CONTROL;
3343         }
3344 
3345         /**
3346          * Determines if the expired <code>bundle</code> in the cache needs
3347          * to be reloaded based on the loading time given by
3348          * <code>loadTime</code> or some other criteria. The method returns
3349          * <code>true</code> if reloading is required; <code>false</code>
3350          * otherwise. <code>loadTime</code> is a millisecond offset since
3351          * the <a href="Calendar.html#Epoch"> <code>Calendar</code>
3352          * Epoch</a>.
3353          *
3354          * <p>
3355          * The calling <code>ResourceBundle.getBundle</code> factory method
3356          * calls this method on the <code>ResourceBundle.Control</code>
3357          * instance used for its current invocation, not on the instance
3358          * used in the invocation that originally loaded the resource
3359          * bundle.
3360          *
3361          * <p>The default implementation compares <code>loadTime</code> and
3362          * the last modified time of the source data of the resource
3363          * bundle. If it's determined that the source data has been modified
3364          * since <code>loadTime</code>, <code>true</code> is
3365          * returned. Otherwise, <code>false</code> is returned. This
3366          * implementation assumes that the given <code>format</code> is the
3367          * same string as its file suffix if it's not one of the default
3368          * formats, <code>"java.class"</code> or
3369          * <code>"java.properties"</code>.
3370          *
3371          * @param baseName
3372          *        the base bundle name of the resource bundle, a
3373          *        fully qualified class name
3374          * @param locale
3375          *        the locale for which the resource bundle
3376          *        should be instantiated
3377          * @param format
3378          *        the resource bundle format to be loaded
3379          * @param loader
3380          *        the <code>ClassLoader</code> to use to load the bundle
3381          * @param bundle
3382          *        the resource bundle instance that has been expired
3383          *        in the cache
3384          * @param loadTime
3385          *        the time when <code>bundle</code> was loaded and put
3386          *        in the cache
3387          * @return <code>true</code> if the expired bundle needs to be
3388          *        reloaded; <code>false</code> otherwise.
3389          * @exception NullPointerException
3390          *        if <code>baseName</code>, <code>locale</code>,
3391          *        <code>format</code>, <code>loader</code>, or
3392          *        <code>bundle</code> is <code>null</code>
3393          */
needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime)3394         public boolean needsReload(String baseName, Locale locale,
3395                                    String format, ClassLoader loader,
3396                                    ResourceBundle bundle, long loadTime) {
3397             if (bundle == null) {
3398                 throw new NullPointerException();
3399             }
3400             if (format.equals("java.class") || format.equals("java.properties")) {
3401                 format = format.substring(5);
3402             }
3403             boolean result = false;
3404             try {
3405                 String resourceName = toResourceName0(toBundleName(baseName, locale), format);
3406                 if (resourceName == null) {
3407                     return result;
3408                 }
3409                 URL url = loader.getResource(resourceName);
3410                 if (url != null) {
3411                     long lastModified = 0;
3412                     URLConnection connection = url.openConnection();
3413                     if (connection != null) {
3414                         // disable caches to get the correct data
3415                         connection.setUseCaches(false);
3416                         if (connection instanceof JarURLConnection) {
3417                             JarEntry ent = ((JarURLConnection)connection).getJarEntry();
3418                             if (ent != null) {
3419                                 lastModified = ent.getTime();
3420                                 if (lastModified == -1) {
3421                                     lastModified = 0;
3422                                 }
3423                             }
3424                         } else {
3425                             lastModified = connection.getLastModified();
3426                         }
3427                     }
3428                     result = lastModified >= loadTime;
3429                 }
3430             } catch (NullPointerException npe) {
3431                 throw npe;
3432             } catch (Exception e) {
3433                 // ignore other exceptions
3434             }
3435             return result;
3436         }
3437 
3438         /**
3439          * Converts the given <code>baseName</code> and <code>locale</code>
3440          * to the bundle name. This method is called from the default
3441          * implementation of the {@link #newBundle(String, Locale, String,
3442          * ClassLoader, boolean) newBundle} and {@link #needsReload(String,
3443          * Locale, String, ClassLoader, ResourceBundle, long) needsReload}
3444          * methods.
3445          *
3446          * <p>This implementation returns the following value:
3447          * <pre>
3448          *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
3449          * </pre>
3450          * where <code>language</code>, <code>script</code>, <code>country</code>,
3451          * and <code>variant</code> are the language, script, country, and variant
3452          * values of <code>locale</code>, respectively. Final component values that
3453          * are empty Strings are omitted along with the preceding '_'.  When the
3454          * script is empty, the script value is omitted along with the preceding '_'.
3455          * If all of the values are empty strings, then <code>baseName</code>
3456          * is returned.
3457          *
3458          * <p>For example, if <code>baseName</code> is
3459          * <code>"baseName"</code> and <code>locale</code> is
3460          * <code>Locale("ja",&nbsp;"",&nbsp;"XX")</code>, then
3461          * <code>"baseName_ja_&thinsp;_XX"</code> is returned. If the given
3462          * locale is <code>Locale("en")</code>, then
3463          * <code>"baseName_en"</code> is returned.
3464          *
3465          * <p>Overriding this method allows applications to use different
3466          * conventions in the organization and packaging of localized
3467          * resources.
3468          *
3469          * @param baseName
3470          *        the base name of the resource bundle, a fully
3471          *        qualified class name
3472          * @param locale
3473          *        the locale for which a resource bundle should be
3474          *        loaded
3475          * @return the bundle name for the resource bundle
3476          * @exception NullPointerException
3477          *        if <code>baseName</code> or <code>locale</code>
3478          *        is <code>null</code>
3479          */
toBundleName(String baseName, Locale locale)3480         public String toBundleName(String baseName, Locale locale) {
3481             if (locale == Locale.ROOT) {
3482                 return baseName;
3483             }
3484 
3485             String language = locale.getLanguage();
3486             String script = locale.getScript();
3487             String country = locale.getCountry();
3488             String variant = locale.getVariant();
3489 
3490             if (language == "" && country == "" && variant == "") {
3491                 return baseName;
3492             }
3493 
3494             StringBuilder sb = new StringBuilder(baseName);
3495             sb.append('_');
3496             if (script != "") {
3497                 if (variant != "") {
3498                     sb.append(language).append('_').append(script).append('_').append(country).append('_').append(variant);
3499                 } else if (country != "") {
3500                     sb.append(language).append('_').append(script).append('_').append(country);
3501                 } else {
3502                     sb.append(language).append('_').append(script);
3503                 }
3504             } else {
3505                 if (variant != "") {
3506                     sb.append(language).append('_').append(country).append('_').append(variant);
3507                 } else if (country != "") {
3508                     sb.append(language).append('_').append(country);
3509                 } else {
3510                     sb.append(language);
3511                 }
3512             }
3513             return sb.toString();
3514 
3515         }
3516 
3517         /**
3518          * Converts the given {@code bundleName} to the form required
3519          * by the {@link ClassLoader#getResource ClassLoader.getResource}
3520          * method by replacing all occurrences of {@code '.'} in
3521          * {@code bundleName} with {@code '/'} and appending a
3522          * {@code '.'} and the given file {@code suffix}. For
3523          * example, if {@code bundleName} is
3524          * {@code "foo.bar.MyResources_ja_JP"} and {@code suffix}
3525          * is {@code "properties"}, then
3526          * {@code "foo/bar/MyResources_ja_JP.properties"} is returned.
3527          *
3528          * @param bundleName
3529          *        the bundle name
3530          * @param suffix
3531          *        the file type suffix
3532          * @return the converted resource name
3533          * @exception NullPointerException
3534          *         if {@code bundleName} or {@code suffix}
3535          *         is {@code null}
3536          */
toResourceName(String bundleName, String suffix)3537         public final String toResourceName(String bundleName, String suffix) {
3538             StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length());
3539             sb.append(bundleName.replace('.', '/')).append('.').append(suffix);
3540             return sb.toString();
3541         }
3542 
toResourceName0(String bundleName, String suffix)3543         private String toResourceName0(String bundleName, String suffix) {
3544             // application protocol check
3545             if (bundleName.contains("://")) {
3546                 return null;
3547             } else {
3548                 return toResourceName(bundleName, suffix);
3549             }
3550         }
3551     }
3552 
3553     @SuppressWarnings("unchecked")
uncheckedThrow(Throwable t)3554     private static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
3555         if (t != null)
3556             throw (T)t;
3557         else
3558             throw new Error("Unknown Exception");
3559     }
3560 
3561     private static class SingleFormatControl extends Control {
3562         private static final Control PROPERTIES_ONLY
3563             = new SingleFormatControl(FORMAT_PROPERTIES);
3564 
3565         private static final Control CLASS_ONLY
3566             = new SingleFormatControl(FORMAT_CLASS);
3567 
3568         private final List<String> formats;
3569 
SingleFormatControl(List<String> formats)3570         protected SingleFormatControl(List<String> formats) {
3571             this.formats = formats;
3572         }
3573 
getFormats(String baseName)3574         public List<String> getFormats(String baseName) {
3575             if (baseName == null) {
3576                 throw new NullPointerException();
3577             }
3578             return formats;
3579         }
3580     }
3581 
3582     private static final class NoFallbackControl extends SingleFormatControl {
3583         private static final Control NO_FALLBACK
3584             = new NoFallbackControl(FORMAT_DEFAULT);
3585 
3586         private static final Control PROPERTIES_ONLY_NO_FALLBACK
3587             = new NoFallbackControl(FORMAT_PROPERTIES);
3588 
3589         private static final Control CLASS_ONLY_NO_FALLBACK
3590             = new NoFallbackControl(FORMAT_CLASS);
3591 
NoFallbackControl(List<String> formats)3592         protected NoFallbackControl(List<String> formats) {
3593             super(formats);
3594         }
3595 
getFallbackLocale(String baseName, Locale locale)3596         public Locale getFallbackLocale(String baseName, Locale locale) {
3597             if (baseName == null || locale == null) {
3598                 throw new NullPointerException();
3599             }
3600             return null;
3601         }
3602     }
3603 
3604     // Android-removed: modules are not supported.
3605     /*
3606     private static class ResourceBundleProviderHelper {
3607         /**
3608          * Returns a new ResourceBundle instance of the given bundleClass
3609          *
3610         static ResourceBundle newResourceBundle(Class<? extends ResourceBundle> bundleClass) {
3611             try {
3612                 @SuppressWarnings("unchecked")
3613                 Constructor<? extends ResourceBundle> ctor =
3614                     bundleClass.getConstructor();
3615                 if (!Modifier.isPublic(ctor.getModifiers())) {
3616                     return null;
3617                 }
3618                 // java.base may not be able to read the bundleClass's module.
3619                 PrivilegedAction<Void> pa = () -> { ctor.setAccessible(true); return null;};
3620                 AccessController.doPrivileged(pa);
3621                 try {
3622                     return ctor.newInstance((Object[]) null);
3623                 } catch (InvocationTargetException e) {
3624                     uncheckedThrow(e);
3625                 } catch (InstantiationException | IllegalAccessException e) {
3626                     throw new InternalError(e);
3627                 }
3628             } catch (NoSuchMethodException e) {
3629                 throw new InternalError(e);
3630             }
3631             return null;
3632         }
3633 
3634         /**
3635          * Loads a {@code ResourceBundle} of the given {@code bundleName} local to
3636          * the given {@code module}. If not found, search the bundle class
3637          * that is visible from the module's class loader.
3638          *
3639          * The caller module is used for access check only.
3640          *
3641         static ResourceBundle loadResourceBundle(Module callerModule,
3642                                                  Module module,
3643                                                  String baseName,
3644                                                  Locale locale)
3645         {
3646             String bundleName = Control.INSTANCE.toBundleName(baseName, locale);
3647             try {
3648                 PrivilegedAction<Class<?>> pa = () -> Class.forName(module, bundleName);
3649                 Class<?> c = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION);
3650                 trace("local in %s %s caller %s: %s%n", module, bundleName, callerModule, c);
3651 
3652                 if (c == null) {
3653                     // if not found from the given module, locate resource bundle
3654                     // that is visible to the module's class loader
3655                     ClassLoader loader = getLoader(module);
3656                     if (loader != null) {
3657                         c = Class.forName(bundleName, false, loader);
3658                     } else {
3659                         c = BootLoader.loadClassOrNull(bundleName);
3660                     }
3661                     trace("loader for %s %s caller %s: %s%n", module, bundleName, callerModule, c);
3662                 }
3663 
3664                 if (c != null && ResourceBundle.class.isAssignableFrom(c)) {
3665                     @SuppressWarnings("unchecked")
3666                     Class<ResourceBundle> bundleClass = (Class<ResourceBundle>) c;
3667                     Module m = bundleClass.getModule();
3668                     if (!isAccessible(callerModule, m, bundleClass.getPackageName())) {
3669                         trace("   %s does not have access to %s/%s%n", callerModule,
3670                               m.getName(), bundleClass.getPackageName());
3671                         return null;
3672                     }
3673 
3674                     return newResourceBundle(bundleClass);
3675                 }
3676             } catch (ClassNotFoundException e) {}
3677             return null;
3678         }
3679 
3680         /**
3681          * Tests if resources of the given package name from the given module are
3682          * open to the caller module.
3683          *
3684         static boolean isAccessible(Module callerModule, Module module, String pn) {
3685             if (!module.isNamed() || callerModule == module)
3686                 return true;
3687 
3688             return module.isOpen(pn, callerModule);
3689         }
3690 
3691         /**
3692          * Loads properties of the given {@code bundleName} local in the given
3693          * {@code module}.  If the .properties is not found or not open
3694          * to the caller module to access, it will find the resource that
3695          * is visible to the module's class loader.
3696          *
3697          * The caller module is used for access check only.
3698          *
3699         static ResourceBundle loadPropertyResourceBundle(Module callerModule,
3700                                                          Module module,
3701                                                          String baseName,
3702                                                          Locale locale)
3703             throws IOException
3704         {
3705             String bundleName = Control.INSTANCE.toBundleName(baseName, locale);
3706 
3707             PrivilegedAction<InputStream> pa = () -> {
3708                 try {
3709                     String resourceName = Control.INSTANCE
3710                         .toResourceName0(bundleName, "properties");
3711                     if (resourceName == null) {
3712                         return null;
3713                     }
3714                     trace("local in %s %s caller %s%n", module, resourceName, callerModule);
3715 
3716                     // if the package is in the given module but not opened
3717                     // locate it from the given module first.
3718                     String pn = toPackageName(bundleName);
3719                     trace("   %s/%s is accessible to %s : %s%n",
3720                             module.getName(), pn, callerModule,
3721                             isAccessible(callerModule, module, pn));
3722                     if (isAccessible(callerModule, module, pn)) {
3723                         InputStream in = module.getResourceAsStream(resourceName);
3724                         if (in != null) {
3725                             return in;
3726                         }
3727                     }
3728 
3729                     ClassLoader loader = module.getClassLoader();
3730                     trace("loader for %s %s caller %s%n", module, resourceName, callerModule);
3731 
3732                     try {
3733                         if (loader != null) {
3734                             return loader.getResourceAsStream(resourceName);
3735                         } else {
3736                             URL url = BootLoader.findResource(resourceName);
3737                             if (url != null) {
3738                                 return url.openStream();
3739                             }
3740                         }
3741                     } catch (Exception e) {}
3742                     return null;
3743 
3744                 } catch (IOException e) {
3745                     throw new UncheckedIOException(e);
3746                 }
3747             };
3748 
3749             try (InputStream stream = AccessController.doPrivileged(pa)) {
3750                 if (stream != null) {
3751                     return new PropertyResourceBundle(stream);
3752                 } else {
3753                     return null;
3754                 }
3755             } catch (UncheckedIOException e) {
3756                 throw e.getCause();
3757             }
3758         }
3759 
3760         private static String toPackageName(String bundleName) {
3761             int i = bundleName.lastIndexOf('.');
3762             return i != -1 ? bundleName.substring(0, i) : "";
3763         }
3764     }
3765     */
3766 
3767     private static final boolean TRACE_ON = Boolean.valueOf(
3768         GetPropertyAction.privilegedGetProperty("resource.bundle.debug", "false"));
3769 
trace(String format, Object... params)3770     private static void trace(String format, Object... params) {
3771         if (TRACE_ON)
3772             System.out.format(format, params);
3773     }
3774 }
3775