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