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