27 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
28 * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
29 *
30 * The original version of this source code and documentation
31 * is copyrighted and owned by Taligent, Inc., a wholly-owned
32 * subsidiary of IBM. These materials are provided under terms
33 * of a License Agreement between Taligent and Sun. This technology
34 * is protected by multiple US and International patents.
35 *
36 * This notice and attribution to Taligent may not be removed.
37 * Taligent is a registered trademark of Taligent, Inc.
38 *
39 */
40
41 package java.util;
42
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.lang.ref.ReferenceQueue;
46 import java.lang.ref.SoftReference;
47 import java.lang.ref.WeakReference;
48 import java.lang.reflect.Constructor;
49 import java.lang.reflect.InvocationTargetException;
50 import java.lang.reflect.Modifier;
51 import java.lang.reflect.Module;
52 import java.net.JarURLConnection;
53 import java.net.URL;
54 import java.net.URLConnection;
55 import java.security.AccessController;
56 import java.security.PrivilegedAction;
57 import java.security.PrivilegedActionException;
58 import java.security.PrivilegedExceptionAction;
59 import java.util.concurrent.ConcurrentHashMap;
60 import java.util.concurrent.ConcurrentMap;
61 import java.util.jar.JarEntry;
62 import java.util.spi.ResourceBundleControlProvider;
63 import java.util.spi.ResourceBundleProvider;
64
65 import jdk.internal.misc.JavaUtilResourceBundleAccess;
66 import jdk.internal.misc.SharedSecrets;
67 import jdk.internal.reflect.CallerSensitive;
68 import jdk.internal.reflect.Reflection;
69 import sun.util.locale.BaseLocale;
70 import sun.util.locale.LocaleObjectCache;
71 import sun.util.locale.provider.ResourceBundleProviderSupport;
72 import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
73
74
75 /**
76 *
77 * Resource bundles contain locale-specific objects. When your program needs a
78 * locale-specific resource, a <code>String</code> for example, your program can
79 * load it from the resource bundle that is appropriate for the current user's
80 * locale. In this way, you can write program code that is largely independent
81 * of the user's locale isolating most, if not all, of the locale-specific
82 * information in resource bundles.
83 *
84 * <p>
85 * This allows you to write programs that can:
86 * <UL>
87 * <LI> be easily localized, or translated, into different languages
88 * <LI> handle multiple locales at once
329 * return new HashSet<String>(Arrays.asList("cancelKey"));
330 * }
331 * }
332 * </pre>
333 * </blockquote>
334 * You do not have to restrict yourself to using a single family of
335 * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
336 * exception messages, <code>ExceptionResources</code>
337 * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
338 * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
339 * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
340 *
341 * @see ListResourceBundle
342 * @see PropertyResourceBundle
343 * @see MissingResourceException
344 * @see ResourceBundleProvider
345 * @since 1.1
346 */
347 public abstract class ResourceBundle {
348
349 /** initial size of the bundle cache */
350 private static final int INITIAL_CACHE_SIZE = 32;
351
352 static {
353 SharedSecrets.setJavaUtilResourceBundleAccess(
354 new JavaUtilResourceBundleAccess() {
355 @Override
356 public void setParent(ResourceBundle bundle,
357 ResourceBundle parent) {
358 bundle.setParent(parent);
359 }
360
361 @Override
362 public ResourceBundle getParent(ResourceBundle bundle) {
363 return bundle.parent;
364 }
365
366 @Override
367 public void setLocale(ResourceBundle bundle, Locale locale) {
368 bundle.locale = locale;
369 }
370
371 @Override
372 public void setName(ResourceBundle bundle, String name) {
373 bundle.name = name;
374 }
375 });
376 }
377
378 /** constant indicating that no resource bundle exists */
379 private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
380 public Enumeration<String> getKeys() { return null; }
381 protected Object handleGetObject(String key) { return null; }
382 public String toString() { return "NONEXISTENT_BUNDLE"; }
383 };
384
385
386 /**
387 * The cache is a map from cache keys (with bundle base name, locale, and
388 * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a
389 * BundleReference.
390 *
391 * The cache is a ConcurrentMap, allowing the cache to be searched
392 * concurrently by multiple threads. This will also allow the cache keys
393 * to be reclaimed along with the ClassLoaders they reference.
394 *
395 * This variable would be better named "cache", but we keep the old
396 * name for compatibility with some workarounds for bug 4212439.
397 */
398 private static final ConcurrentMap<CacheKey, BundleReference> cacheList
399 = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
400
401 /**
402 * Queue for reference objects referring to class loaders or bundles.
403 */
404 private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
405
406 /**
407 * Returns the base name of this bundle, if known, or {@code null} if unknown.
408 *
409 * If not null, then this is the value of the {@code baseName} parameter
410 * that was passed to the {@code ResourceBundle.getBundle(...)} method
411 * when the resource bundle was loaded.
412 *
413 * @return The base name of the resource bundle, as provided to and expected
414 * by the {@code ResourceBundle.getBundle(...)} methods.
415 *
416 * @see #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader)
417 *
418 * @since 1.8
419 */
420 public String getBaseBundleName() {
421 return name;
422 }
423
424 /**
425 * The parent bundle of this bundle.
426 * The parent bundle is searched by {@link #getObject getObject}
427 * when this bundle does not contain a particular resource.
428 */
429 protected ResourceBundle parent = null;
430
431 /**
432 * The locale for this bundle.
433 */
434 private Locale locale = null;
435
436 /**
437 * The base bundle name for this bundle.
438 */
439 private String name;
440
441 /**
442 * The flag indicating this bundle has expired in the cache.
443 */
444 private volatile boolean expired;
445
446 /**
447 * The back link to the cache key. null if this bundle isn't in
448 * the cache (yet) or has expired.
449 */
450 private volatile CacheKey cacheKey;
451
452 /**
453 * A Set of the keys contained only in this ResourceBundle.
454 */
455 private volatile Set<String> keySet;
456
457 private static final List<ResourceBundleControlProvider> providers;
458
459 static {
460 List<ResourceBundleControlProvider> list = null;
461 ServiceLoader<ResourceBundleControlProvider> serviceLoaders
462 = ServiceLoader.loadInstalled(ResourceBundleControlProvider.class);
463 for (ResourceBundleControlProvider provider : serviceLoaders) {
464 if (list == null) {
465 list = new ArrayList<>();
466 }
467 list.add(provider);
468 }
469 providers = list;
470 }
601 }
602 return ClassLoader.getSystemResource(name);
603 }
604 public InputStream getResourceAsStream(String name) {
605 ClassLoader loader = ClassLoader.getSystemClassLoader();
606 if (loader != null) {
607 return loader.getResourceAsStream(name);
608 }
609 return ClassLoader.getSystemResourceAsStream(name);
610 }
611 }
612
613 /**
614 * Sets the parent bundle of this bundle.
615 * The parent bundle is searched by {@link #getObject getObject}
616 * when this bundle does not contain a particular resource.
617 *
618 * @param parent this bundle's parent bundle.
619 */
620 protected void setParent(ResourceBundle parent) {
621 assert parent != NONEXISTENT_BUNDLE;
622 this.parent = parent;
623 }
624
625 /**
626 * Key used for cached resource bundles. The key checks the base
627 * name, the locale, the class loader, and the caller module
628 * to determine if the resource is a match to the requested one.
629 * The loader may be null, but the base name, the locale and
630 * module must have a non-null value.
631 */
632 private static class CacheKey implements Cloneable {
633 // These four are the actual keys for lookup in Map.
634 private String name;
635 private Locale locale;
636 private KeyElementReference<ClassLoader> loaderRef;
637 private KeyElementReference<Module> moduleRef;
638
639
640 // bundle format which is necessary for calling
641 // Control.needsReload().
642 private String format;
643
644 // These time values are in CacheKey so that NONEXISTENT_BUNDLE
645 // doesn't need to be cloned for caching.
646
647 // The time when the bundle has been loaded
648 private volatile long loadTime;
649
650 // The time when the bundle expires in the cache, or either
651 // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
652 private volatile long expirationTime;
653
654 // Placeholder for an error report by a Throwable
655 private Throwable cause;
656
657 // Hash code value cache to avoid recalculating the hash code
658 // of this instance.
659 private int hashCodeCache;
660
661 // ResourceBundleProviders for loading ResourceBundles
662 private ServiceLoader<ResourceBundleProvider> providers;
663 private boolean providersChecked;
664
665 // Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
666 private Boolean callerHasProvider;
667
668 CacheKey(String baseName, Locale locale, ClassLoader loader, Module module) {
669 Objects.requireNonNull(module);
670
671 this.name = baseName;
672 this.locale = locale;
673 if (loader == null) {
674 this.loaderRef = null;
675 } else {
676 this.loaderRef = new KeyElementReference<>(loader, referenceQueue, this);
677 }
678 this.moduleRef = new KeyElementReference<>(module, referenceQueue, this);
679 calculateHashCode();
680 }
681
682 String getName() {
683 return name;
684 }
685
686 CacheKey setName(String baseName) {
687 if (!this.name.equals(baseName)) {
688 this.name = baseName;
689 calculateHashCode();
690 }
691 return this;
692 }
693
694 Locale getLocale() {
695 return locale;
696 }
697
698 CacheKey setLocale(Locale locale) {
699 if (!this.locale.equals(locale)) {
700 this.locale = locale;
701 calculateHashCode();
702 }
703 return this;
704 }
705
706 ClassLoader getLoader() {
707 return (loaderRef != null) ? loaderRef.get() : null;
708 }
709
710 Module getModule() {
711 return moduleRef.get();
712 }
713
714 ServiceLoader<ResourceBundleProvider> getProviders() {
715 if (!providersChecked) {
716 providers = getServiceLoader(getModule(), name);
717 providersChecked = true;
718 }
719 return providers;
720 }
721
722 boolean hasProviders() {
723 return getProviders() != null;
724 }
725
726 boolean callerHasProvider() {
727 return callerHasProvider == Boolean.TRUE;
728 }
729
730 @Override
731 public boolean equals(Object other) {
732 if (this == other) {
733 return true;
734 }
735 try {
736 final CacheKey otherEntry = (CacheKey)other;
737 //quick check to see if they are not equal
738 if (hashCodeCache != otherEntry.hashCodeCache) {
739 return false;
740 }
741 //are the names the same?
742 if (!name.equals(otherEntry.name)) {
743 return false;
744 }
745 // are the locales the same?
746 if (!locale.equals(otherEntry.locale)) {
747 return false;
748 }
749 //are refs (both non-null) or (both null)?
750 if (loaderRef == null) {
751 return otherEntry.loaderRef == null;
752 }
753 ClassLoader loader = getLoader();
754 Module module = getModule();
755 return (otherEntry.loaderRef != null)
756 // with a null reference we can no longer find
757 // out which class loader or module was referenced; so
758 // treat it as unequal
759 && (loader != null)
760 && (loader == otherEntry.getLoader())
761 && (module != null)
762 && (module.equals(otherEntry.getModule()));
763 } catch (NullPointerException | ClassCastException e) {
764 }
765 return false;
766 }
767
768 @Override
769 public int hashCode() {
770 return hashCodeCache;
771 }
772
773 private void calculateHashCode() {
774 hashCodeCache = name.hashCode() << 3;
775 hashCodeCache ^= locale.hashCode();
776 ClassLoader loader = getLoader();
777 if (loader != null) {
778 hashCodeCache ^= loader.hashCode();
779 }
780 Module module = getModule();
781 if (module != null) {
782 hashCodeCache ^= module.hashCode();
783 }
784 }
785
786 @Override
787 public Object clone() {
788 try {
789 CacheKey clone = (CacheKey) super.clone();
790 if (loaderRef != null) {
791 clone.loaderRef = new KeyElementReference<>(getLoader(),
792 referenceQueue, clone);
793 }
794 clone.moduleRef = new KeyElementReference<>(getModule(),
795 referenceQueue, clone);
796 // Clear the reference to ResourceBundleProviders and the flag
797 clone.providers = null;
798 clone.providersChecked = false;
799 // Clear the reference to a Throwable
800 clone.cause = null;
801 // Clear callerHasProvider
802 clone.callerHasProvider = null;
803 return clone;
804 } catch (CloneNotSupportedException e) {
805 //this should never happen
806 throw new InternalError(e);
807 }
808 }
809
810 String getFormat() {
811 return format;
812 }
813
814 void setFormat(String format) {
815 this.format = format;
816 }
817
818 private void setCause(Throwable cause) {
819 if (this.cause == null) {
820 this.cause = cause;
821 } else {
822 // Override the cause if the previous one is
823 // ClassNotFoundException.
824 if (this.cause instanceof ClassNotFoundException) {
825 this.cause = cause;
826 }
827 }
828 }
829
830 private Throwable getCause() {
831 return cause;
832 }
833
834 @Override
835 public String toString() {
836 String l = locale.toString();
837 if (l.length() == 0) {
838 if (locale.getVariant().length() != 0) {
839 l = "__" + locale.getVariant();
840 } else {
841 l = "\"\"";
842 }
843 }
844 return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
845 + "(format=" + format + ")]";
846 }
847 }
848
849 /**
850 * The common interface to get a CacheKey in LoaderReference and
851 * BundleReference.
852 */
853 private static interface CacheKeyReference {
854 public CacheKey getCacheKey();
855 }
856
857 /**
858 * References to a CacheKey element as a WeakReference so that it can be
859 * garbage collected when nobody else is using it.
860 */
861 private static class KeyElementReference<T> extends WeakReference<T>
862 implements CacheKeyReference {
863 private final CacheKey cacheKey;
864
865 KeyElementReference(T referent, ReferenceQueue<Object> q, CacheKey key) {
866 super(referent, q);
867 cacheKey = key;
868 }
869
870 @Override
871 public CacheKey getCacheKey() {
872 return cacheKey;
873 }
874 }
875
876 /**
877 * References to bundles are soft references so that they can be garbage
878 * collected when they have no hard references.
879 */
880 private static class BundleReference extends SoftReference<ResourceBundle>
881 implements CacheKeyReference {
882 private final CacheKey cacheKey;
883
884 BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key) {
885 super(referent, q);
886 cacheKey = key;
887 }
888
889 @Override
890 public CacheKey getCacheKey() {
891 return cacheKey;
892 }
893 }
894
895 /**
896 * Gets a resource bundle using the specified base name, the default locale,
897 * and the caller's class loader. Calling this method is equivalent to calling
898 * <blockquote>
899 * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
900 * </blockquote>
901 * except that <code>getClassLoader()</code> is run with the security
902 * privileges of <code>ResourceBundle</code>.
903 * See {@link #getBundle(String, Locale, ClassLoader) getBundle}
904 * for a complete description of the search and instantiation strategy.
905 *
906 * @param baseName the base name of the resource bundle, a fully qualified class name
907 * @exception java.lang.NullPointerException
908 * if <code>baseName</code> is <code>null</code>
909 * @exception MissingResourceException
910 * if no resource bundle for the specified base name can be found
911 * @return a resource bundle for the given base name and the default locale
1603 Control control) {
1604 Objects.requireNonNull(module);
1605 if (caller.getModule() != module) {
1606 SecurityManager sm = System.getSecurityManager();
1607 if (sm != null) {
1608 sm.checkPermission(GET_CLASSLOADER_PERMISSION);
1609 }
1610 }
1611 return getBundleImpl(baseName, locale, getLoader(module), module, control);
1612 }
1613
1614 private static ResourceBundle getBundleImpl(String baseName,
1615 Locale locale,
1616 ClassLoader loader,
1617 Module module,
1618 Control control) {
1619 if (locale == null || control == null) {
1620 throw new NullPointerException();
1621 }
1622
1623 // We create a CacheKey here for use by this call. The base name
1624 // loader, and module will never change during the bundle loading
1625 // process. We have to make sure that the locale is set before
1626 // using it as a cache key.
1627 CacheKey cacheKey = new CacheKey(baseName, locale, loader, module);
1628 ResourceBundle bundle = null;
1629
1630 // Quick lookup of the cache.
1631 BundleReference bundleRef = cacheList.get(cacheKey);
1632 if (bundleRef != null) {
1633 bundle = bundleRef.get();
1634 bundleRef = null;
1635 }
1636
1637 // If this bundle and all of its parents are valid (not expired),
1638 // then return this bundle. If any of the bundles is expired, we
1639 // don't call control.needsReload here but instead drop into the
1640 // complete loading process below.
1641 if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
1642 return bundle;
1643 }
1644
1645 // No valid bundle was found in the cache, so we need to load the
1646 // resource bundle and its parents.
1647
1648 boolean isKnownControl = (control == Control.INSTANCE) ||
1649 (control instanceof SingleFormatControl);
1650 List<String> formats = control.getFormats(baseName);
1651 if (!isKnownControl && !checkList(formats)) {
1652 throw new IllegalArgumentException("Invalid Control: getFormats");
1653 }
1654
1655 ResourceBundle baseBundle = null;
1656 for (Locale targetLocale = locale;
1657 targetLocale != null;
1658 targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
1659 List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
1660 if (!isKnownControl && !checkList(candidateLocales)) {
1661 throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
1662 }
1663
1664 bundle = findBundle(cacheKey, module, candidateLocales, formats, 0, control, baseBundle);
1665
1666 // If the loaded bundle is the base bundle and exactly for the
1667 // requested locale or the only candidate locale, then take the
1668 // bundle as the resulting one. If the loaded bundle is the base
1669 // bundle, it's put on hold until we finish processing all
1670 // fallback locales.
1671 if (isValidBundle(bundle)) {
1672 boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
1673 if (!isBaseBundle || bundle.locale.equals(locale)
1674 || (candidateLocales.size() == 1
1675 && bundle.locale.equals(candidateLocales.get(0)))) {
1676 break;
1677 }
1678
1679 // If the base bundle has been loaded, keep the reference in
1680 // baseBundle so that we can avoid any redundant loading in case
1681 // the control specify not to cache bundles.
1682 if (isBaseBundle && baseBundle == null) {
1683 baseBundle = bundle;
1684 }
1685 }
1686 }
1687
1688 if (bundle == null) {
1689 if (baseBundle == null) {
1690 throwMissingResourceException(baseName, locale, cacheKey.getCause());
1691 }
1692 bundle = baseBundle;
1693 }
1694
1695 return bundle;
1696 }
1697
1698 /**
1699 * Checks if the given <code>List</code> is not null, not empty,
1700 * not having null in its elements.
1701 */
1702 private static boolean checkList(List<?> a) {
1703 boolean valid = (a != null && !a.isEmpty());
1704 if (valid) {
1705 int size = a.size();
1706 for (int i = 0; valid && i < size; i++) {
1707 valid = (a.get(i) != null);
1708 }
1709 }
1710 return valid;
1711 }
1712
1713 private static ResourceBundle findBundle(CacheKey cacheKey,
1714 Module module,
1715 List<Locale> candidateLocales,
1716 List<String> formats,
1717 int index,
1718 Control control,
1719 ResourceBundle baseBundle) {
1720 Locale targetLocale = candidateLocales.get(index);
1721 ResourceBundle parent = null;
1722 if (index != candidateLocales.size() - 1) {
1723 parent = findBundle(cacheKey, module, candidateLocales, formats, index + 1,
1724 control, baseBundle);
1725 } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
1726 return baseBundle;
1727 }
1728
1729 // Before we do the real loading work, see whether we need to
1730 // do some housekeeping: If references to class loaders or
1731 // resource bundles have been nulled out, remove all related
1732 // information from the cache.
1733 Object ref;
1734 while ((ref = referenceQueue.poll()) != null) {
1735 cacheList.remove(((CacheKeyReference)ref).getCacheKey());
1736 }
1737
1738 // flag indicating the resource bundle has expired in the cache
1739 boolean expiredBundle = false;
1740
1741 // First, look up the cache to see if it's in the cache, without
1742 // attempting to load bundle.
1743 cacheKey.setLocale(targetLocale);
1744 ResourceBundle bundle = findBundleInCache(cacheKey, control);
1745 if (isValidBundle(bundle)) {
1746 expiredBundle = bundle.expired;
1747 if (!expiredBundle) {
1748 // If its parent is the one asked for by the candidate
1749 // locales (the runtime lookup path), we can take the cached
1750 // one. (If it's not identical, then we'd have to check the
1751 // parent's parents to be consistent with what's been
1752 // requested.)
1753 if (bundle.parent == parent) {
1754 return bundle;
1755 }
1756 // Otherwise, remove the cached one since we can't keep
1757 // the same bundles having different parents.
1758 BundleReference bundleRef = cacheList.get(cacheKey);
1759 if (bundleRef != null && bundleRef.get() == bundle) {
1760 cacheList.remove(cacheKey, bundleRef);
1761 }
1762 }
1763 }
1764
1765 if (bundle != NONEXISTENT_BUNDLE) {
1766 CacheKey constKey = (CacheKey) cacheKey.clone();
1767
1768 try {
1769 if (module.isNamed()) {
1770 bundle = loadBundle(cacheKey, formats, control, module);
1771 } else {
1772 bundle = loadBundle(cacheKey, formats, control, expiredBundle);
1773 }
1774 if (bundle != null) {
1775 if (bundle.parent == null) {
1776 bundle.setParent(parent);
1777 }
1778 bundle.locale = targetLocale;
1779 bundle = putBundleInCache(cacheKey, bundle, control);
1780 return bundle;
1781 }
1782
1783 // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
1784 // instance for the locale.
1785 putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control);
1786 } finally {
1787 if (constKey.getCause() instanceof InterruptedException) {
1788 Thread.currentThread().interrupt();
1789 }
1790 }
1791 }
1792 return parent;
1793 }
1794
1795 private static final String UNKNOWN_FORMAT = "";
1796
1797 /*
1798 * Loads a ResourceBundle in named modules
1799 */
1800 private static ResourceBundle loadBundle(CacheKey cacheKey,
1801 List<String> formats,
1802 Control control,
1803 Module module) {
1804 String baseName = cacheKey.getName();
1805 Locale targetLocale = cacheKey.getLocale();
1806
1807 ResourceBundle bundle = null;
1808 if (cacheKey.hasProviders()) {
1809 bundle = loadBundleFromProviders(baseName, targetLocale,
1810 cacheKey.getProviders(), cacheKey);
1811 if (bundle != null) {
1812 cacheKey.setFormat(UNKNOWN_FORMAT);
1813 }
1814 }
1815 // If none of providers returned a bundle and the caller has no provider,
1816 // look up module-local bundles.
1817 if (bundle == null && !cacheKey.callerHasProvider()) {
1818 String bundleName = control.toBundleName(baseName, targetLocale);
1819 for (String format : formats) {
1820 try {
1821 switch (format) {
1822 case "java.class":
1823 PrivilegedAction<ResourceBundle> pa = ()
1824 -> ResourceBundleProviderSupport
1825 .loadResourceBundle(module, bundleName);
1826 bundle = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION);
1827 break;
1828 case "java.properties":
1829 bundle = ResourceBundleProviderSupport.loadPropertyResourceBundle(module, bundleName);
1830 break;
1831 default:
1832 throw new InternalError("unexpected format: " + format);
1833 }
1834
1835 if (bundle != null) {
1836 cacheKey.setFormat(format);
1837 break;
1838 }
1839 } catch (Exception e) {
1840 cacheKey.setCause(e);
1841 }
1842 }
1843 }
1844 return bundle;
1845 }
1846
1847 private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
1848 String baseName) {
1849 if (!module.isNamed()) {
1850 return null;
1851 }
1852 PrivilegedAction<ClassLoader> pa = module::getClassLoader;
1853 ClassLoader loader = AccessController.doPrivileged(pa);
1854 return getServiceLoader(module, loader, baseName);
1855 }
1856
1857 /**
1858 * Returns a ServiceLoader that will find providers that are bound to
1859 * a given module that may be named or unnamed.
1860 */
1861 private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
1862 ClassLoader loader,
1863 String baseName)
1864 {
1865 // Look up <baseName> + "Provider"
1866 String providerName = baseName + "Provider";
1867 // Use the class loader of the getBundle caller so that the caller's
1868 // visibility of the provider type is checked.
1869 Class<ResourceBundleProvider> service = AccessController.doPrivileged(
1870 new PrivilegedAction<>() {
1871 @Override
1872 public Class<ResourceBundleProvider> run() {
1873 try {
1874 Class<?> c = Class.forName(providerName, false, loader);
1875 if (ResourceBundleProvider.class.isAssignableFrom(c)) {
1876 @SuppressWarnings("unchecked")
1877 Class<ResourceBundleProvider> s = (Class<ResourceBundleProvider>) c;
1878 return s;
1879 }
1880 } catch (ClassNotFoundException e) {}
1881 return null;
1882 }
1883 });
1884
1885 if (service != null && Reflection.verifyModuleAccess(module, service)) {
1886 try {
1887 return ServiceLoader.load(service, loader, module);
1888 } catch (ServiceConfigurationError e) {
1889 // "uses" not declared: load bundle local in the module
1890 return null;
1891 }
1892 }
1893 return null;
1894 }
1895
1896 /**
1897 * Loads ResourceBundle from service providers.
1898 */
1899 private static ResourceBundle loadBundleFromProviders(String baseName,
1900 Locale locale,
1901 ServiceLoader<ResourceBundleProvider> providers,
1902 CacheKey cacheKey)
1903 {
1904 if (providers == null) return null;
1905
1906 return AccessController.doPrivileged(
1907 new PrivilegedAction<>() {
1908 public ResourceBundle run() {
1909 for (Iterator<ResourceBundleProvider> itr = providers.iterator(); itr.hasNext(); ) {
1910 try {
1911 ResourceBundleProvider provider = itr.next();
1912 if (cacheKey != null && cacheKey.callerHasProvider == null
1913 && cacheKey.getModule() == provider.getClass().getModule()) {
1914 cacheKey.callerHasProvider = Boolean.TRUE;
1915 }
1916 ResourceBundle bundle = provider.getBundle(baseName, locale);
1917 if (bundle != null) {
1918 return bundle;
1919 }
1920 } catch (ServiceConfigurationError | SecurityException e) {
1921 if (cacheKey != null) {
1922 cacheKey.setCause(e);
1923 }
1924 }
1925 }
1926 if (cacheKey != null && cacheKey.callerHasProvider == null) {
1927 cacheKey.callerHasProvider = Boolean.FALSE;
1928 }
1929 return null;
1930 }
1931 });
1932
1933 }
1934
1935 /*
1936 * Legacy mechanism to load resource bundles
1937 */
1938 private static ResourceBundle loadBundle(CacheKey cacheKey,
1939 List<String> formats,
1940 Control control,
1941 boolean reload) {
1942
1943 // Here we actually load the bundle in the order of formats
1944 // specified by the getFormats() value.
1945 Locale targetLocale = cacheKey.getLocale();
1946
1947 ResourceBundle bundle = null;
1948 for (String format : formats) {
1949 try {
1950 // ResourceBundle.Control.newBundle may be overridden
1951 bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
1952 cacheKey.getLoader(), reload);
1953 } catch (LinkageError | Exception error) {
1954 // We need to handle the LinkageError case due to
1955 // inconsistent case-sensitivity in ClassLoader.
1956 // See 6572242 for details.
1957 cacheKey.setCause(error);
1958 }
1959 if (bundle != null) {
1960 // Set the format in the cache key so that it can be
1961 // used when calling needsReload later.
1962 cacheKey.setFormat(format);
1963 bundle.name = cacheKey.getName();
1964 bundle.locale = targetLocale;
1965 // Bundle provider might reuse instances. So we should make
1966 // sure to clear the expired flag here.
1967 bundle.expired = false;
1968 break;
1969 }
1970 }
1971
1972 return bundle;
1973 }
1974
1975 private static boolean isValidBundle(ResourceBundle bundle) {
1976 return bundle != null && bundle != NONEXISTENT_BUNDLE;
1977 }
1978
1979 /**
1980 * Determines whether any of resource bundles in the parent chain,
1981 * including the leaf, have expired.
1982 */
1983 private static boolean hasValidParentChain(ResourceBundle bundle) {
1984 long now = System.currentTimeMillis();
1985 while (bundle != null) {
1986 if (bundle.expired) {
1987 return false;
1988 }
1989 CacheKey key = bundle.cacheKey;
1990 if (key != null) {
1991 long expirationTime = key.expirationTime;
1992 if (expirationTime >= 0 && expirationTime <= now) {
1993 return false;
1994 }
1995 }
1996 bundle = bundle.parent;
1997 }
1998 return true;
1999 }
2000
2001 /**
2002 * Throw a MissingResourceException with proper message
2003 */
2004 private static void throwMissingResourceException(String baseName,
2005 Locale locale,
2006 Throwable cause) {
2007 // If the cause is a MissingResourceException, avoid creating
2008 // a long chain. (6355009)
2009 if (cause instanceof MissingResourceException) {
2010 cause = null;
2011 }
2012 throw new MissingResourceException("Can't find bundle for base name "
2013 + baseName + ", locale " + locale,
2014 baseName + "_" + locale, // className
2015 "", // key
2016 cause);
2017 }
2018
2019 /**
2020 * Finds a bundle in the cache. Any expired bundles are marked as
2021 * `expired' and removed from the cache upon return.
2022 *
2023 * @param cacheKey the key to look up the cache
2024 * @param control the Control to be used for the expiration control
2025 * @return the cached bundle, or null if the bundle is not found in the
2026 * cache or its parent has expired. <code>bundle.expire</code> is true
2027 * upon return if the bundle in the cache has expired.
2028 */
2029 private static ResourceBundle findBundleInCache(CacheKey cacheKey,
2030 Control control) {
2031 BundleReference bundleRef = cacheList.get(cacheKey);
2032 if (bundleRef == null) {
2033 return null;
2034 }
2035 ResourceBundle bundle = bundleRef.get();
2036 if (bundle == null) {
2037 return null;
2038 }
2039 ResourceBundle p = bundle.parent;
2040 assert p != NONEXISTENT_BUNDLE;
2041 // If the parent has expired, then this one must also expire. We
2042 // check only the immediate parent because the actual loading is
2043 // done from the root (base) to leaf (child) and the purpose of
2044 // checking is to propagate expiration towards the leaf. For
2045 // example, if the requested locale is ja_JP_JP and there are
2046 // bundles for all of the candidates in the cache, we have a list,
2047 //
2048 // base <- ja <- ja_JP <- ja_JP_JP
2049 //
2050 // If ja has expired, then it will reload ja and the list becomes a
2051 // tree.
2052 //
2053 // base <- ja (new)
2054 // " <- ja (expired) <- ja_JP <- ja_JP_JP
2055 //
2056 // When looking up ja_JP in the cache, it finds ja_JP in the cache
2057 // which references to the expired ja. Then, ja_JP is marked as
2058 // expired and removed from the cache. This will be propagated to
2059 // ja_JP_JP.
2060 //
2061 // Now, it's possible, for example, that while loading new ja_JP,
2062 // someone else has started loading the same bundle and finds the
2063 // base bundle has expired. Then, what we get from the first
2064 // getBundle call includes the expired base bundle. However, if
2065 // someone else didn't start its loading, we wouldn't know if the
2066 // base bundle has expired at the end of the loading process. The
2067 // expiration control doesn't guarantee that the returned bundle and
2068 // its parents haven't expired.
2069 //
2070 // We could check the entire parent chain to see if there's any in
2071 // the chain that has expired. But this process may never end. An
2072 // extreme case would be that getTimeToLive returns 0 and
2073 // needsReload always returns true.
2074 if (p != null && p.expired) {
2075 assert bundle != NONEXISTENT_BUNDLE;
2076 bundle.expired = true;
2077 bundle.cacheKey = null;
2078 cacheList.remove(cacheKey, bundleRef);
2079 bundle = null;
2080 } else {
2081 CacheKey key = bundleRef.getCacheKey();
2082 long expirationTime = key.expirationTime;
2083 if (!bundle.expired && expirationTime >= 0 &&
2084 expirationTime <= System.currentTimeMillis()) {
2085 // its TTL period has expired.
2086 if (bundle != NONEXISTENT_BUNDLE) {
2087 // Synchronize here to call needsReload to avoid
2088 // redundant concurrent calls for the same bundle.
2089 synchronized (bundle) {
2090 expirationTime = key.expirationTime;
2091 if (!bundle.expired && expirationTime >= 0 &&
2092 expirationTime <= System.currentTimeMillis()) {
2093 try {
2094 bundle.expired = control.needsReload(key.getName(),
2095 key.getLocale(),
2096 key.getFormat(),
2097 key.getLoader(),
2098 bundle,
2099 key.loadTime);
2100 } catch (Exception e) {
2101 cacheKey.setCause(e);
2102 }
2103 if (bundle.expired) {
2104 // If the bundle needs to be reloaded, then
2105 // remove the bundle from the cache, but
2106 // return the bundle with the expired flag
2107 // on.
2108 bundle.cacheKey = null;
2109 cacheList.remove(cacheKey, bundleRef);
2110 } else {
2111 // Update the expiration control info. and reuse
2112 // the same bundle instance
2113 setExpirationTime(key, control);
2114 }
2115 }
2116 }
2117 } else {
2118 // We just remove NONEXISTENT_BUNDLE from the cache.
2119 cacheList.remove(cacheKey, bundleRef);
2120 bundle = null;
2121 }
2122 }
2123 }
2124 return bundle;
2125 }
2126
2127 /**
2128 * Put a new bundle in the cache.
2129 *
2130 * @param cacheKey the key for the resource bundle
2131 * @param bundle the resource bundle to be put in the cache
2132 * @return the ResourceBundle for the cacheKey; if someone has put
2133 * the bundle before this call, the one found in the cache is
2134 * returned.
2135 */
2136 private static ResourceBundle putBundleInCache(CacheKey cacheKey,
2137 ResourceBundle bundle,
2138 Control control) {
2139 setExpirationTime(cacheKey, control);
2140 if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) {
2141 CacheKey key = (CacheKey) cacheKey.clone();
2142 BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key);
2143 bundle.cacheKey = key;
2144
2145 // Put the bundle in the cache if it's not been in the cache.
2146 BundleReference result = cacheList.putIfAbsent(key, bundleRef);
2147
2148 // If someone else has put the same bundle in the cache before
2149 // us and it has not expired, we should use the one in the cache.
2150 if (result != null) {
2151 ResourceBundle rb = result.get();
2152 if (rb != null && !rb.expired) {
2153 // Clear the back link to the cache key
2154 bundle.cacheKey = null;
2155 bundle = rb;
2156 // Clear the reference in the BundleReference so that
2157 // it won't be enqueued.
2158 bundleRef.clear();
2159 } else {
2160 // Replace the invalid (garbage collected or expired)
2161 // instance with the valid one.
2162 cacheList.put(key, bundleRef);
2163 }
2164 }
2165 }
2166 return bundle;
2167 }
2168
2169 private static void setExpirationTime(CacheKey cacheKey, Control control) {
2170 long ttl = control.getTimeToLive(cacheKey.getName(),
2171 cacheKey.getLocale());
2172 if (ttl >= 0) {
2173 // If any expiration time is specified, set the time to be
2174 // expired in the cache.
2175 long now = System.currentTimeMillis();
2176 cacheKey.loadTime = now;
2177 cacheKey.expirationTime = now + ttl;
2178 } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
2179 cacheKey.expirationTime = ttl;
2180 } else {
2181 throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
2182 }
2183 }
2184
2185 /**
2186 * Removes all resource bundles from the cache that have been loaded
2187 * by the caller's module using the caller's class loader.
2188 *
2189 * @since 1.6
2190 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2191 */
2192 @CallerSensitive
2193 public static final void clearCache() {
2194 Class<?> caller = Reflection.getCallerClass();
2195 clearCache(getLoader(caller), caller.getModule());
2196 }
2197
2198 /**
2199 * Removes all resource bundles from the cache that have been loaded
2212
2213 /**
2214 * Removes all resource bundles from the cache that have been loaded by the
2215 * given {@code module}.
2216 *
2217 * @param module the module
2218 * @throws NullPointerException
2219 * if {@code module} is {@code null}
2220 * @throws SecurityException
2221 * if the caller doesn't have the permission to
2222 * {@linkplain Module#getClassLoader() get the class loader}
2223 * of the given {@code module}
2224 * @since 9
2225 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2226 */
2227 public static final void clearCache(Module module) {
2228 clearCache(module.getClassLoader(), module);
2229 }
2230
2231 private static void clearCache(ClassLoader loader, Module module) {
2232 Set<CacheKey> set = cacheList.keySet();
2233 set.stream().filter((key) -> (key.getLoader() == loader && key.getModule() == module))
2234 .forEach(set::remove);
2235 }
2236
2237 /**
2238 * Gets an object for the given key from this resource bundle.
2239 * Returns null if this resource bundle does not contain an
2240 * object for the given key.
2241 *
2242 * @param key the key for the desired object
2243 * @exception NullPointerException if <code>key</code> is <code>null</code>
2244 * @return the object for the given key, or null
2245 */
2246 protected abstract Object handleGetObject(String key);
2247
2248 /**
2249 * Returns an enumeration of the keys.
2250 *
2251 * @return an <code>Enumeration</code> of the keys contained in
2252 * this <code>ResourceBundle</code> and its parent bundles.
2253 */
2254 public abstract Enumeration<String> getKeys();
|
27 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
28 * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
29 *
30 * The original version of this source code and documentation
31 * is copyrighted and owned by Taligent, Inc., a wholly-owned
32 * subsidiary of IBM. These materials are provided under terms
33 * of a License Agreement between Taligent and Sun. This technology
34 * is protected by multiple US and International patents.
35 *
36 * This notice and attribution to Taligent may not be removed.
37 * Taligent is a registered trademark of Taligent, Inc.
38 *
39 */
40
41 package java.util;
42
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.lang.ref.ReferenceQueue;
46 import java.lang.ref.SoftReference;
47 import java.lang.reflect.Constructor;
48 import java.lang.reflect.InvocationTargetException;
49 import java.lang.reflect.Modifier;
50 import java.lang.reflect.Module;
51 import java.net.JarURLConnection;
52 import java.net.URL;
53 import java.net.URLConnection;
54 import java.security.AccessController;
55 import java.security.PrivilegedAction;
56 import java.security.PrivilegedActionException;
57 import java.security.PrivilegedExceptionAction;
58 import java.util.jar.JarEntry;
59 import java.util.spi.ResourceBundleControlProvider;
60 import java.util.spi.ResourceBundleProvider;
61
62 import jdk.internal.misc.JavaUtilResourceBundleAccess;
63 import jdk.internal.misc.SharedSecrets;
64 import jdk.internal.reflect.CallerSensitive;
65 import jdk.internal.reflect.Reflection;
66 import jdk.internal.util.concurrent.AbstractClassLoaderValue;
67 import jdk.internal.util.concurrent.ClassLoaderValue;
68 import sun.util.locale.BaseLocale;
69 import sun.util.locale.LocaleObjectCache;
70 import sun.util.locale.provider.ResourceBundleProviderSupport;
71 import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
72
73
74 /**
75 *
76 * Resource bundles contain locale-specific objects. When your program needs a
77 * locale-specific resource, a <code>String</code> for example, your program can
78 * load it from the resource bundle that is appropriate for the current user's
79 * locale. In this way, you can write program code that is largely independent
80 * of the user's locale isolating most, if not all, of the locale-specific
81 * information in resource bundles.
82 *
83 * <p>
84 * This allows you to write programs that can:
85 * <UL>
86 * <LI> be easily localized, or translated, into different languages
87 * <LI> handle multiple locales at once
328 * return new HashSet<String>(Arrays.asList("cancelKey"));
329 * }
330 * }
331 * </pre>
332 * </blockquote>
333 * You do not have to restrict yourself to using a single family of
334 * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
335 * exception messages, <code>ExceptionResources</code>
336 * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
337 * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
338 * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
339 *
340 * @see ListResourceBundle
341 * @see PropertyResourceBundle
342 * @see MissingResourceException
343 * @see ResourceBundleProvider
344 * @since 1.1
345 */
346 public abstract class ResourceBundle {
347
348 static {
349 SharedSecrets.setJavaUtilResourceBundleAccess(
350 new JavaUtilResourceBundleAccess() {
351 @Override
352 public void setParent(ResourceBundle bundle,
353 ResourceBundle parent) {
354 bundle.setParent(parent);
355 }
356
357 @Override
358 public ResourceBundle getParent(ResourceBundle bundle) {
359 return bundle.parent;
360 }
361
362 @Override
363 public void setLocale(ResourceBundle bundle, Locale locale) {
364 bundle.locale = locale;
365 }
366
367 @Override
368 public void setName(ResourceBundle bundle, String name) {
369 bundle.name = name;
370 }
371 });
372 }
373
374 /**
375 * special bundle type indicating that no resource bundle exists
376 */
377 private static final class NONEXISTENT_BUNDLE extends ResourceBundle {
378 public Enumeration<String> getKeys() { return null; }
379 protected Object handleGetObject(String key) { return null; }
380 public String toString() { return "NONEXISTENT_BUNDLE"; }
381 }
382
383
384 /**
385 * The cache of BundleReference(s) per class loader, module, bundle base name
386 * and locale.
387 */
388 private static final ClassLoaderValue<BundleReference> cache
389 = new ClassLoaderValue<>();
390
391 /**
392 * Queue for reference objects referring to class loaders or bundles.
393 */
394 private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
395
396 /**
397 * Returns the base name of this bundle, if known, or {@code null} if unknown.
398 *
399 * If not null, then this is the value of the {@code baseName} parameter
400 * that was passed to the {@code ResourceBundle.getBundle(...)} method
401 * when the resource bundle was loaded.
402 *
403 * @return The base name of the resource bundle, as provided to and expected
404 * by the {@code ResourceBundle.getBundle(...)} methods.
405 *
406 * @see #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader)
407 *
408 * @since 1.8
409 */
410 public String getBaseBundleName() {
411 return name;
412 }
413
414 /**
415 * The parent bundle of this bundle.
416 * The parent bundle is searched by {@link #getObject getObject}
417 * when this bundle does not contain a particular resource.
418 */
419 protected ResourceBundle parent;
420
421 /**
422 * The locale for this bundle.
423 */
424 private Locale locale;
425
426 /**
427 * The base bundle name for this bundle.
428 */
429 private String name;
430
431 /**
432 * Bundle format which is necessary for calling
433 * Control.needsReload().
434 */
435 private String format;
436
437 /**
438 * The flag indicating this bundle has expired in the cache.
439 */
440 private volatile boolean expired;
441
442 /**
443 * The time when the bundle has been loaded
444 */
445 private volatile long loadTime;
446
447 /**
448 * The time when the bundle expires in the cache, or either
449 * Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
450 */
451 private volatile long expirationTime;
452
453 /**
454 * A Set of the keys contained only in this ResourceBundle.
455 */
456 private volatile Set<String> keySet;
457
458 private static final List<ResourceBundleControlProvider> providers;
459
460 static {
461 List<ResourceBundleControlProvider> list = null;
462 ServiceLoader<ResourceBundleControlProvider> serviceLoaders
463 = ServiceLoader.loadInstalled(ResourceBundleControlProvider.class);
464 for (ResourceBundleControlProvider provider : serviceLoaders) {
465 if (list == null) {
466 list = new ArrayList<>();
467 }
468 list.add(provider);
469 }
470 providers = list;
471 }
602 }
603 return ClassLoader.getSystemResource(name);
604 }
605 public InputStream getResourceAsStream(String name) {
606 ClassLoader loader = ClassLoader.getSystemClassLoader();
607 if (loader != null) {
608 return loader.getResourceAsStream(name);
609 }
610 return ClassLoader.getSystemResourceAsStream(name);
611 }
612 }
613
614 /**
615 * Sets the parent bundle of this bundle.
616 * The parent bundle is searched by {@link #getObject getObject}
617 * when this bundle does not contain a particular resource.
618 *
619 * @param parent this bundle's parent bundle.
620 */
621 protected void setParent(ResourceBundle parent) {
622 assert !(parent instanceof NONEXISTENT_BUNDLE);
623 this.parent = parent;
624 }
625
626 /**
627 * A session object used during the {@link #getBundle} call.
628 * The loader may be null (in which case it is considered to be the
629 * bootstrap class loader), but the module, base name, and locale must have
630 * a non-null value.
631 */
632 private static class LoadSession {
633 // These four are the actual keys for lookup in cache.
634 private ClassLoader loader;
635 private Module module;
636 private String name;
637 private Locale locale;
638
639 // compose and return the cache key
640 ClassLoaderValue<BundleReference>.Sub<Module>.Sub<String>.Sub<Locale> key() {
641 return cache.sub(module).sub(name).sub(locale);
642 }
643
644 // return a bundle reference from cache or null
645 BundleReference getFromCache() {
646 return key().get(loader);
647 }
648
649 // Placeholder for an error report by a Throwable
650 private Throwable cause;
651
652 // ResourceBundleProviders for loading ResourceBundles
653 private Iterable<ResourceBundleProvider> providers;
654
655 // Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
656 private Boolean callerHasProvider;
657
658 LoadSession(String baseName, Locale locale, ClassLoader loader, Module module) {
659 this.name = Objects.requireNonNull(baseName);
660 this.locale = Objects.requireNonNull(locale);
661 this.loader = loader;
662 this.module = Objects.requireNonNull(module);
663 }
664
665 String getName() {
666 return name;
667 }
668
669 LoadSession setName(String baseName) {
670 this.name = Objects.requireNonNull(baseName);
671 return this;
672 }
673
674 Locale getLocale() {
675 return locale;
676 }
677
678 LoadSession setLocale(Locale locale) {
679 this.locale = Objects.requireNonNull(locale);
680 return this;
681 }
682
683 ClassLoader getLoader() {
684 return loader;
685 }
686
687 Module getModule() {
688 return module;
689 }
690
691 Iterable<ResourceBundleProvider> getProviders() {
692 if (providers == null) {
693 providers = ResourceBundle.getProviders(module, name);
694 }
695 return providers;
696 }
697
698 boolean callerHasProvider() {
699 return callerHasProvider == Boolean.TRUE;
700 }
701
702 private void setCause(Throwable cause) {
703 if (this.cause == null) {
704 this.cause = cause;
705 } else {
706 // Override the cause if the previous one is
707 // ClassNotFoundException.
708 if (this.cause instanceof ClassNotFoundException) {
709 this.cause = cause;
710 }
711 }
712 }
713
714 private Throwable getCause() {
715 return cause;
716 }
717
718 @Override
719 public String toString() {
720 String l = locale.toString();
721 if (l.length() == 0) {
722 if (locale.getVariant().length() != 0) {
723 l = "__" + locale.getVariant();
724 } else {
725 l = "\"\"";
726 }
727 }
728 return "LookupSession[" + name + ", lc=" + l + ", ldr=" + getLoader()
729 + "]";
730 }
731 }
732
733 /**
734 * References to bundles are soft references so that they can be cleared
735 * when GC demands to free some heap.
736 */
737 private static class BundleReference extends SoftReference<ResourceBundle> {
738 private final ClassLoader classLoader;
739 private final AbstractClassLoaderValue<?, BundleReference>.Sub<?> key;
740
741 BundleReference(ResourceBundle referent, ReferenceQueue<Object> q,
742 ClassLoader classLoader,
743 AbstractClassLoaderValue<?, BundleReference>.Sub<?> key) {
744 super(referent, q);
745 this.classLoader = classLoader;
746 this.key = key;
747 }
748
749 void remove() {
750 key.remove(classLoader, this);
751 }
752 }
753
754 /**
755 * Gets a resource bundle using the specified base name, the default locale,
756 * and the caller's class loader. Calling this method is equivalent to calling
757 * <blockquote>
758 * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
759 * </blockquote>
760 * except that <code>getClassLoader()</code> is run with the security
761 * privileges of <code>ResourceBundle</code>.
762 * See {@link #getBundle(String, Locale, ClassLoader) getBundle}
763 * for a complete description of the search and instantiation strategy.
764 *
765 * @param baseName the base name of the resource bundle, a fully qualified class name
766 * @exception java.lang.NullPointerException
767 * if <code>baseName</code> is <code>null</code>
768 * @exception MissingResourceException
769 * if no resource bundle for the specified base name can be found
770 * @return a resource bundle for the given base name and the default locale
1462 Control control) {
1463 Objects.requireNonNull(module);
1464 if (caller.getModule() != module) {
1465 SecurityManager sm = System.getSecurityManager();
1466 if (sm != null) {
1467 sm.checkPermission(GET_CLASSLOADER_PERMISSION);
1468 }
1469 }
1470 return getBundleImpl(baseName, locale, getLoader(module), module, control);
1471 }
1472
1473 private static ResourceBundle getBundleImpl(String baseName,
1474 Locale locale,
1475 ClassLoader loader,
1476 Module module,
1477 Control control) {
1478 if (locale == null || control == null) {
1479 throw new NullPointerException();
1480 }
1481
1482 // We create a LookupSession here for use by this call. The base name
1483 // loader, and module will never change during the bundle loading
1484 // process.
1485 LoadSession loadSession = new LoadSession(baseName, locale, loader, module);
1486
1487 // Quick lookup of the cache.
1488 ResourceBundle bundle = null;
1489 BundleReference bundleRef = loadSession.getFromCache();
1490 if (bundleRef != null) {
1491 bundle = bundleRef.get();
1492 }
1493
1494 // If this bundle and all of its parents are valid (not expired),
1495 // then return this bundle. If any of the bundles is expired, we
1496 // don't call control.needsReload here but instead drop into the
1497 // complete loading process below.
1498 if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
1499 return bundle;
1500 }
1501
1502 // No valid bundle was found in the cache, so we need to load the
1503 // resource bundle and its parents.
1504
1505 boolean isKnownControl = (control == Control.INSTANCE) ||
1506 (control instanceof SingleFormatControl);
1507 List<String> formats = control.getFormats(baseName);
1508 if (!isKnownControl && !checkList(formats)) {
1509 throw new IllegalArgumentException("Invalid Control: getFormats");
1510 }
1511
1512 ResourceBundle baseBundle = null;
1513 for (Locale targetLocale = locale;
1514 targetLocale != null;
1515 targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
1516 List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
1517 if (!isKnownControl && !checkList(candidateLocales)) {
1518 throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
1519 }
1520
1521 bundle = findBundle(loadSession, module, candidateLocales, formats, 0, control, baseBundle);
1522
1523 // If the loaded bundle is the base bundle and exactly for the
1524 // requested locale or the only candidate locale, then take the
1525 // bundle as the resulting one. If the loaded bundle is the base
1526 // bundle, it's put on hold until we finish processing all
1527 // fallback locales.
1528 if (isValidBundle(bundle)) {
1529 boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
1530 if (!isBaseBundle || bundle.locale.equals(locale)
1531 || (candidateLocales.size() == 1
1532 && bundle.locale.equals(candidateLocales.get(0)))) {
1533 break;
1534 }
1535
1536 // If the base bundle has been loaded, keep the reference in
1537 // baseBundle so that we can avoid any redundant loading in case
1538 // the control specify not to cache bundles.
1539 if (isBaseBundle && baseBundle == null) {
1540 baseBundle = bundle;
1541 }
1542 }
1543 }
1544
1545 if (bundle == null) {
1546 if (baseBundle == null) {
1547 throwMissingResourceException(baseName, locale, loadSession.getCause());
1548 }
1549 bundle = baseBundle;
1550 }
1551
1552 return bundle;
1553 }
1554
1555 /**
1556 * Checks if the given <code>List</code> is not null, not empty,
1557 * not having null in its elements.
1558 */
1559 private static boolean checkList(List<?> a) {
1560 boolean valid = (a != null && !a.isEmpty());
1561 if (valid) {
1562 int size = a.size();
1563 for (int i = 0; valid && i < size; i++) {
1564 valid = (a.get(i) != null);
1565 }
1566 }
1567 return valid;
1568 }
1569
1570 private static ResourceBundle findBundle(LoadSession loadSession,
1571 Module module,
1572 List<Locale> candidateLocales,
1573 List<String> formats,
1574 int index,
1575 Control control,
1576 ResourceBundle baseBundle) {
1577 Locale targetLocale = candidateLocales.get(index);
1578 ResourceBundle parent = null;
1579 if (index != candidateLocales.size() - 1) {
1580 parent = findBundle(loadSession, module, candidateLocales, formats, index + 1,
1581 control, baseBundle);
1582 } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
1583 return baseBundle;
1584 }
1585
1586 // Before we do the real loading work, see whether we need to
1587 // do some housekeeping: If soft references to resource bundles
1588 // have been nulled out, remove all related information from the cache.
1589 Object ref;
1590 while ((ref = referenceQueue.poll()) != null) {
1591 ((BundleReference) ref).remove();
1592 }
1593
1594 // flag indicating the resource bundle has expired in the cache
1595 boolean expiredBundle = false;
1596
1597 // First, look up the cache to see if it's in the cache, without
1598 // attempting to load bundle.
1599 loadSession.setLocale(targetLocale);
1600 ResourceBundle bundle = findBundleInCache(loadSession, control);
1601 if (isValidBundle(bundle)) {
1602 expiredBundle = bundle.expired;
1603 if (!expiredBundle) {
1604 // If its parent is the one asked for by the candidate
1605 // locales (the runtime lookup path), we can take the cached
1606 // one. (If it's not identical, then we'd have to check the
1607 // parent's parents to be consistent with what's been
1608 // requested.)
1609 if (bundle.parent == parent) {
1610 return bundle;
1611 }
1612 // Otherwise, remove the cached one since we can't keep
1613 // the same bundles having different parents.
1614 BundleReference bundleRef = loadSession.getFromCache();
1615 if (bundleRef != null && bundleRef.get() == bundle) {
1616 bundleRef.remove();
1617 }
1618 }
1619 }
1620
1621 if (!(bundle instanceof NONEXISTENT_BUNDLE)) {
1622 if (module.isNamed()) {
1623 bundle = loadBundle(loadSession, formats, control, module);
1624 } else {
1625 bundle = loadBundle(loadSession, formats, control, expiredBundle);
1626 }
1627 if (bundle != null) {
1628 if (bundle.parent == null) {
1629 bundle.setParent(parent);
1630 }
1631 bundle.locale = targetLocale;
1632 bundle = putBundleInCache(loadSession, bundle, control);
1633 return bundle;
1634 }
1635
1636 // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
1637 // instance for the locale.
1638 putBundleInCache(loadSession, new NONEXISTENT_BUNDLE(), control);
1639 }
1640 return parent;
1641 }
1642
1643 private static final String UNKNOWN_FORMAT = "";
1644
1645 /*
1646 * Loads a ResourceBundle in named modules
1647 */
1648 private static ResourceBundle loadBundle(LoadSession loadSession,
1649 List<String> formats,
1650 Control control,
1651 Module module) {
1652 String baseName = loadSession.getName();
1653 Locale targetLocale = loadSession.getLocale();
1654
1655 ResourceBundle bundle = null;
1656 Iterable<ResourceBundleProvider> providers = loadSession.getProviders();
1657 if (providers != NO_PROVIDERS) {
1658 bundle = loadBundleFromProviders(baseName, targetLocale,
1659 providers, loadSession);
1660 if (bundle != null) {
1661 bundle.format = UNKNOWN_FORMAT;
1662 }
1663 }
1664 // If none of providers returned a bundle and the caller has no provider,
1665 // look up module-local bundles.
1666 if (bundle == null && !loadSession.callerHasProvider()) {
1667 String bundleName = control.toBundleName(baseName, targetLocale);
1668 for (String format : formats) {
1669 try {
1670 switch (format) {
1671 case "java.class":
1672 PrivilegedAction<ResourceBundle> pa = ()
1673 -> ResourceBundleProviderSupport
1674 .loadResourceBundle(module, bundleName);
1675 bundle = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION);
1676 break;
1677 case "java.properties":
1678 bundle = ResourceBundleProviderSupport.loadPropertyResourceBundle(module, bundleName);
1679 break;
1680 default:
1681 throw new InternalError("unexpected format: " + format);
1682 }
1683
1684 if (bundle != null) {
1685 bundle.format = format;
1686 break;
1687 }
1688 } catch (Exception e) {
1689 loadSession.setCause(e);
1690 }
1691 }
1692 }
1693 return bundle;
1694 }
1695
1696 // An instance that signals getting providers for unnamed module or
1697 // inability to get providers as opposed to successfully getting 0 providers
1698 // from a named module.
1699 private static final Iterable<ResourceBundleProvider> NO_PROVIDERS =
1700 Collections.emptyList();
1701
1702 private static Iterable<ResourceBundleProvider> getProviders(Module module,
1703 String baseName) {
1704 if (!module.isNamed()) {
1705 return NO_PROVIDERS;
1706 }
1707 PrivilegedAction<ClassLoader> pa = module::getClassLoader;
1708 ClassLoader loader = AccessController.doPrivileged(pa);
1709 return getProviders(module, loader, baseName);
1710 }
1711
1712 /**
1713 * Returns a ServiceLoader that will find providers that are bound to
1714 * a given module that may be named or unnamed or GET_PROVIDERS_FAILED instance
1715 * if unsuccessful.
1716 */
1717 private static Iterable<ResourceBundleProvider> getProviders(Module module,
1718 ClassLoader loader,
1719 String baseName)
1720 {
1721 // Look up <baseName> + "Provider"
1722 String providerName = baseName + "Provider";
1723 // Use the class loader of the getBundle caller so that the caller's
1724 // visibility of the provider type is checked.
1725 Class<ResourceBundleProvider> service = AccessController.doPrivileged(
1726 new PrivilegedAction<>() {
1727 @Override
1728 public Class<ResourceBundleProvider> run() {
1729 try {
1730 Class<?> c = Class.forName(providerName, false, loader);
1731 if (ResourceBundleProvider.class.isAssignableFrom(c)) {
1732 @SuppressWarnings("unchecked")
1733 Class<ResourceBundleProvider> s = (Class<ResourceBundleProvider>) c;
1734 return s;
1735 }
1736 } catch (ClassNotFoundException e) {}
1737 return null;
1738 }
1739 });
1740
1741 if (service != null && Reflection.verifyModuleAccess(module, service)) {
1742 try {
1743 return ServiceLoader.load(service, loader, module);
1744 } catch (ServiceConfigurationError e) {
1745 // "uses" not declared: load bundle local in the module
1746 }
1747 }
1748 return NO_PROVIDERS;
1749 }
1750
1751 /**
1752 * Loads ResourceBundle from service providers.
1753 */
1754 private static ResourceBundle loadBundleFromProviders(String baseName,
1755 Locale locale,
1756 Iterable<ResourceBundleProvider> providers,
1757 LoadSession loadSession)
1758 {
1759 // assert loadSession != null && providers != null;
1760 return AccessController.doPrivileged(
1761 new PrivilegedAction<>() {
1762 public ResourceBundle run() {
1763 for (ResourceBundleProvider provider : providers) {
1764 try {
1765 if (loadSession.callerHasProvider == null &&
1766 loadSession.getModule() == provider.getClass().getModule())
1767 {
1768 loadSession.callerHasProvider = Boolean.TRUE;
1769 }
1770 ResourceBundle bundle = provider.getBundle(baseName, locale);
1771 if (bundle != null) {
1772 return bundle;
1773 }
1774 } catch (ServiceConfigurationError | SecurityException e) {
1775 loadSession.setCause(e);
1776 }
1777 }
1778 if (loadSession.callerHasProvider == null) {
1779 loadSession.callerHasProvider = Boolean.FALSE;
1780 }
1781 return null;
1782 }
1783 });
1784
1785 }
1786
1787 /*
1788 * Legacy mechanism to load resource bundles
1789 */
1790 private static ResourceBundle loadBundle(LoadSession loadSession,
1791 List<String> formats,
1792 Control control,
1793 boolean reload) {
1794
1795 // Here we actually load the bundle in the order of formats
1796 // specified by the getFormats() value.
1797 Locale targetLocale = loadSession.getLocale();
1798
1799 ResourceBundle bundle = null;
1800 for (String format : formats) {
1801 try {
1802 // ResourceBundle.Control.newBundle may be overridden
1803 bundle = control.newBundle(loadSession.getName(), targetLocale, format,
1804 loadSession.getLoader(), reload);
1805 } catch (LinkageError | Exception error) {
1806 // We need to handle the LinkageError case due to
1807 // inconsistent case-sensitivity in ClassLoader.
1808 // See 6572242 for details.
1809 loadSession.setCause(error);
1810 }
1811 if (bundle != null) {
1812 // Set the format in the cache key so that it can be
1813 // used when calling needsReload later.
1814 bundle.format = format;
1815 bundle.name = loadSession.getName();
1816 bundle.locale = targetLocale;
1817 // Bundle provider might reuse instances. So we should make
1818 // sure to clear the expired flag here.
1819 bundle.expired = false;
1820 break;
1821 }
1822 }
1823
1824 return bundle;
1825 }
1826
1827 private static boolean isValidBundle(ResourceBundle bundle) {
1828 return bundle != null && !(bundle instanceof NONEXISTENT_BUNDLE);
1829 }
1830
1831 /**
1832 * Determines whether any of resource bundles in the parent chain,
1833 * including the leaf, have expired.
1834 */
1835 private static boolean hasValidParentChain(ResourceBundle bundle) {
1836 long now = System.currentTimeMillis();
1837 while (bundle != null) {
1838 if (bundle.expired) {
1839 return false;
1840 }
1841 long expirationTime = bundle.expirationTime;
1842 if (expirationTime >= 0 && expirationTime <= now) {
1843 return false;
1844 }
1845 bundle = bundle.parent;
1846 }
1847 return true;
1848 }
1849
1850 /**
1851 * Throw a MissingResourceException with proper message
1852 */
1853 private static void throwMissingResourceException(String baseName,
1854 Locale locale,
1855 Throwable cause) {
1856 // If the cause is a MissingResourceException, avoid creating
1857 // a long chain. (6355009)
1858 if (cause instanceof MissingResourceException) {
1859 cause = null;
1860 }
1861 throw new MissingResourceException("Can't find bundle for base name "
1862 + baseName + ", locale " + locale,
1863 baseName + "_" + locale, // className
1864 "", // key
1865 cause);
1866 }
1867
1868 /**
1869 * Finds a bundle in the cache. Any expired bundles are marked as
1870 * `expired' and removed from the cache upon return.
1871 *
1872 * @param loadSession the load session used look up the cache
1873 * @param control the Control to be used for the expiration control
1874 * @return the cached bundle, or null if the bundle is not found in the
1875 * cache or its parent has expired. <code>bundle.expire</code> is true
1876 * upon return if the bundle in the cache has expired.
1877 */
1878 private static ResourceBundle findBundleInCache(LoadSession loadSession,
1879 Control control) {
1880 BundleReference bundleRef = loadSession.getFromCache();
1881 if (bundleRef == null) {
1882 return null;
1883 }
1884 ResourceBundle bundle = bundleRef.get();
1885 if (bundle == null) {
1886 return null;
1887 }
1888 ResourceBundle p = bundle.parent;
1889 assert !(p instanceof NONEXISTENT_BUNDLE);
1890 // If the parent has expired, then this one must also expire. We
1891 // check only the immediate parent because the actual loading is
1892 // done from the root (base) to leaf (child) and the purpose of
1893 // checking is to propagate expiration towards the leaf. For
1894 // example, if the requested locale is ja_JP_JP and there are
1895 // bundles for all of the candidates in the cache, we have a list,
1896 //
1897 // base <- ja <- ja_JP <- ja_JP_JP
1898 //
1899 // If ja has expired, then it will reload ja and the list becomes a
1900 // tree.
1901 //
1902 // base <- ja (new)
1903 // " <- ja (expired) <- ja_JP <- ja_JP_JP
1904 //
1905 // When looking up ja_JP in the cache, it finds ja_JP in the cache
1906 // which references to the expired ja. Then, ja_JP is marked as
1907 // expired and removed from the cache. This will be propagated to
1908 // ja_JP_JP.
1909 //
1910 // Now, it's possible, for example, that while loading new ja_JP,
1911 // someone else has started loading the same bundle and finds the
1912 // base bundle has expired. Then, what we get from the first
1913 // getBundle call includes the expired base bundle. However, if
1914 // someone else didn't start its loading, we wouldn't know if the
1915 // base bundle has expired at the end of the loading process. The
1916 // expiration control doesn't guarantee that the returned bundle and
1917 // its parents haven't expired.
1918 //
1919 // We could check the entire parent chain to see if there's any in
1920 // the chain that has expired. But this process may never end. An
1921 // extreme case would be that getTimeToLive returns 0 and
1922 // needsReload always returns true.
1923 if (p != null && p.expired) {
1924 assert !(bundle instanceof NONEXISTENT_BUNDLE);
1925 bundle.expired = true;
1926 bundleRef.remove();
1927 bundle = null;
1928 } else {
1929 long expirationTime = bundle.expirationTime;
1930 if (!bundle.expired && expirationTime >= 0 &&
1931 expirationTime <= System.currentTimeMillis()) {
1932 // its TTL period has expired.
1933 if (!(bundle instanceof NONEXISTENT_BUNDLE)) {
1934 // Synchronize here to call needsReload to avoid
1935 // redundant concurrent calls for the same bundle.
1936 synchronized (bundle) {
1937 expirationTime = bundle.expirationTime;
1938 if (!bundle.expired && expirationTime >= 0 &&
1939 expirationTime <= System.currentTimeMillis()) {
1940 try {
1941 bundle.expired = control.needsReload(bundle.getBaseBundleName(),
1942 bundle.getLocale(),
1943 bundle.format,
1944 loadSession.getLoader(),
1945 bundle,
1946 bundle.loadTime);
1947 } catch (Exception e) {
1948 loadSession.setCause(e);
1949 }
1950 if (bundle.expired) {
1951 // If the bundle needs to be reloaded, then
1952 // remove the bundle from the cache, but
1953 // return the bundle with the expired flag
1954 // on.
1955 bundleRef.remove();
1956 } else {
1957 // Update the expiration control info. and reuse
1958 // the same bundle instance
1959 setExpirationTime(bundle, loadSession, control);
1960 }
1961 }
1962 }
1963 } else {
1964 // We just remove NONEXISTENT_BUNDLE from the cache.
1965 bundleRef.remove();
1966 bundle = null;
1967 }
1968 }
1969 }
1970 return bundle;
1971 }
1972
1973 /**
1974 * Put a new bundle in the cache.
1975 *
1976 * @param loadSession the key for the resource bundle
1977 * @param bundle the resource bundle to be put in the cache
1978 * @return the ResourceBundle for the cacheKey; if someone has put
1979 * the bundle before this call, the one found in the cache is
1980 * returned.
1981 */
1982 private static ResourceBundle putBundleInCache(LoadSession loadSession,
1983 ResourceBundle bundle,
1984 Control control) {
1985 setExpirationTime(bundle, loadSession, control);
1986 if (bundle.expirationTime != Control.TTL_DONT_CACHE) {
1987 ClassLoaderValue<BundleReference>.Sub<Module>.Sub<String>.Sub<Locale> key
1988 = loadSession.key();
1989 ClassLoader loader = loadSession.getLoader();
1990 BundleReference bundleRef = new BundleReference(
1991 bundle, referenceQueue, loader, key);
1992
1993 // Put the bundle in the cache if it's not been in the cache.
1994 BundleReference oldBundleRef = key.putIfAbsent(loader, bundleRef);
1995
1996 // If someone else has put the same bundle in the cache before
1997 // us and it has not expired, we should use the one in the cache.
1998 while (oldBundleRef != null) {
1999 ResourceBundle rb = oldBundleRef.get();
2000 if (rb != null && !rb.expired) {
2001 return rb;
2002 } else {
2003 // Try to replace the invalid (garbage collected or expired)
2004 // instance with the valid one.
2005 if (key.replace(loader, oldBundleRef, bundleRef)) {
2006 break;
2007 } else {
2008 // Someone else must have already replaced it or it was
2009 // removed. Retry putting the bundle in the cache.
2010 oldBundleRef = key.putIfAbsent(loader, bundleRef);
2011 }
2012 }
2013 }
2014 }
2015 return bundle;
2016 }
2017
2018 private static void setExpirationTime(ResourceBundle bundle,
2019 LoadSession loadSession,
2020 Control control) {
2021 long ttl = control.getTimeToLive(loadSession.getName(),
2022 loadSession.getLocale());
2023 if (ttl >= 0) {
2024 // If any expiration time is specified, set the time to be
2025 // expired in the cache.
2026 long now = System.currentTimeMillis();
2027 bundle.loadTime = now;
2028 bundle.expirationTime = now + ttl;
2029 } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
2030 bundle.expirationTime = ttl;
2031 } else {
2032 throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
2033 }
2034 }
2035
2036 /**
2037 * Removes all resource bundles from the cache that have been loaded
2038 * by the caller's module using the caller's class loader.
2039 *
2040 * @since 1.6
2041 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2042 */
2043 @CallerSensitive
2044 public static final void clearCache() {
2045 Class<?> caller = Reflection.getCallerClass();
2046 clearCache(getLoader(caller), caller.getModule());
2047 }
2048
2049 /**
2050 * Removes all resource bundles from the cache that have been loaded
2063
2064 /**
2065 * Removes all resource bundles from the cache that have been loaded by the
2066 * given {@code module}.
2067 *
2068 * @param module the module
2069 * @throws NullPointerException
2070 * if {@code module} is {@code null}
2071 * @throws SecurityException
2072 * if the caller doesn't have the permission to
2073 * {@linkplain Module#getClassLoader() get the class loader}
2074 * of the given {@code module}
2075 * @since 9
2076 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2077 */
2078 public static final void clearCache(Module module) {
2079 clearCache(module.getClassLoader(), module);
2080 }
2081
2082 private static void clearCache(ClassLoader loader, Module module) {
2083 cache.sub(module).removeAll(loader);
2084 }
2085
2086 /**
2087 * Gets an object for the given key from this resource bundle.
2088 * Returns null if this resource bundle does not contain an
2089 * object for the given key.
2090 *
2091 * @param key the key for the desired object
2092 * @exception NullPointerException if <code>key</code> is <code>null</code>
2093 * @return the object for the given key, or null
2094 */
2095 protected abstract Object handleGetObject(String key);
2096
2097 /**
2098 * Returns an enumeration of the keys.
2099 *
2100 * @return an <code>Enumeration</code> of the keys contained in
2101 * this <code>ResourceBundle</code> and its parent bundles.
2102 */
2103 public abstract Enumeration<String> getKeys();
|