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