362 }
363
364 @Override
365 public ResourceBundle getParent(ResourceBundle bundle) {
366 return bundle.parent;
367 }
368
369 @Override
370 public void setLocale(ResourceBundle bundle, Locale locale) {
371 bundle.locale = locale;
372 }
373
374 @Override
375 public void setName(ResourceBundle bundle, String name) {
376 bundle.name = name;
377 }
378
379 @Override
380 public ResourceBundle getBundle(String baseName, Locale locale, Module module) {
381 // use the given module as the caller to bypass the access check
382 return getBundleImpl(module, module, getLoader(module),
383 baseName, locale, Control.INSTANCE);
384 }
385
386 @Override
387 public ResourceBundle newResourceBundle(Class<? extends ResourceBundle> bundleClass) {
388 return ResourceBundleProviderHelper.newResourceBundle(bundleClass);
389 }
390 });
391 }
392
393 /** constant indicating that no resource bundle exists */
394 private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
395 public Enumeration<String> getKeys() { return null; }
396 protected Object handleGetObject(String key) { return null; }
397 public String toString() { return "NONEXISTENT_BUNDLE"; }
398 };
399
400
401 /**
402 * The cache is a map from cache keys (with bundle base name, locale, and
549 +this.getClass().getName()
550 +", key "+key,
551 this.getClass().getName(),
552 key);
553 }
554 }
555 return obj;
556 }
557
558 /**
559 * Returns the locale of this resource bundle. This method can be used after a
560 * call to getBundle() to determine whether the resource bundle returned really
561 * corresponds to the requested locale or is a fallback.
562 *
563 * @return the locale of this resource bundle
564 */
565 public Locale getLocale() {
566 return locale;
567 }
568
569 /*
570 * Automatic determination of the ClassLoader to be used to load
571 * resources on behalf of the client.
572 */
573 private static ClassLoader getLoader(Class<?> caller) {
574 ClassLoader cl = caller == null ? null : caller.getClassLoader();
575 if (cl == null) {
576 // When the caller's loader is the boot class loader, cl is null
577 // here. In that case, ClassLoader.getSystemClassLoader() may
578 // return the same class loader that the application is
579 // using. We therefore use a wrapper ClassLoader to create a
580 // separate scope for bundles loaded on behalf of the Java
581 // runtime so that these bundles cannot be returned from the
582 // cache to the application (5048280).
583 cl = RBClassLoader.INSTANCE;
584 }
585 return cl;
586 }
587
588 private static ClassLoader getLoader(Module module) {
589 PrivilegedAction<ClassLoader> pa = module::getClassLoader;
590 return AccessController.doPrivileged(pa);
591 }
592
593 /**
594 * A wrapper of ClassLoader.getSystemClassLoader().
595 */
596 private static class RBClassLoader extends ClassLoader {
597 private static final RBClassLoader INSTANCE = AccessController.doPrivileged(
598 new PrivilegedAction<RBClassLoader>() {
599 public RBClassLoader run() {
600 return new RBClassLoader();
601 }
602 });
603 private RBClassLoader() {
604 }
605 public Class<?> loadClass(String name) throws ClassNotFoundException {
606 ClassLoader loader = ClassLoader.getSystemClassLoader();
607 if (loader != null) {
608 return loader.loadClass(name);
609 }
610 return Class.forName(name);
611 }
612 public URL getResource(String name) {
613 ClassLoader loader = ClassLoader.getSystemClassLoader();
614 if (loader != null) {
615 return loader.getResource(name);
616 }
617 return ClassLoader.getSystemResource(name);
618 }
619 public InputStream getResourceAsStream(String name) {
620 ClassLoader loader = ClassLoader.getSystemClassLoader();
621 if (loader != null) {
622 return loader.getResourceAsStream(name);
623 }
624 return ClassLoader.getSystemResourceAsStream(name);
625 }
626 }
627
628 /**
629 * Sets the parent bundle of this bundle.
630 * The parent bundle is searched by {@link #getObject getObject}
631 * when this bundle does not contain a particular resource.
632 *
633 * @param parent this bundle's parent bundle.
634 */
635 protected void setParent(ResourceBundle parent) {
636 assert parent != NONEXISTENT_BUNDLE;
637 this.parent = parent;
638 }
639
640 /**
641 * Key used for cached resource bundles. The key checks the base
642 * name, the locale, the class loader, and the caller module
643 * to determine if the resource is a match to the requested one.
644 * The loader may be null, but the base name, the locale and
645 * module must have a non-null value.
646 */
647 private static class CacheKey implements Cloneable {
648 // These four are the actual keys for lookup in Map.
649 private String name;
650 private Locale locale;
651 private KeyElementReference<ClassLoader> loaderRef;
652 private KeyElementReference<Module> moduleRef;
653
654
655 // bundle format which is necessary for calling
656 // Control.needsReload().
657 private String format;
658
659 // These time values are in CacheKey so that NONEXISTENT_BUNDLE
660 // doesn't need to be cloned for caching.
661
662 // The time when the bundle has been loaded
663 private volatile long loadTime;
664
665 // The time when the bundle expires in the cache, or either
666 // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
667 private volatile long expirationTime;
668
669 // Placeholder for an error report by a Throwable
670 private Throwable cause;
671
672 // Hash code value cache to avoid recalculating the hash code
673 // of this instance.
674 private int hashCodeCache;
675
676 // ResourceBundleProviders for loading ResourceBundles
677 private ServiceLoader<ResourceBundleProvider> providers;
678 private boolean providersChecked;
679
680 // Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
681 private Boolean callerHasProvider;
682
683 CacheKey(String baseName, Locale locale, ClassLoader loader, Module module) {
684 Objects.requireNonNull(module);
685
686 this.name = baseName;
687 this.locale = locale;
688 if (loader == null) {
689 this.loaderRef = null;
690 } else {
691 this.loaderRef = new KeyElementReference<>(loader, referenceQueue, this);
692 }
693 this.moduleRef = new KeyElementReference<>(module, referenceQueue, this);
694 calculateHashCode();
695 }
696
697 String getName() {
698 return name;
699 }
700
701 CacheKey setName(String baseName) {
702 if (!this.name.equals(baseName)) {
703 this.name = baseName;
704 calculateHashCode();
705 }
706 return this;
707 }
708
709 Locale getLocale() {
710 return locale;
711 }
712
713 CacheKey setLocale(Locale locale) {
714 if (!this.locale.equals(locale)) {
715 this.locale = locale;
716 calculateHashCode();
717 }
718 return this;
719 }
720
721 ClassLoader getLoader() {
722 return (loaderRef != null) ? loaderRef.get() : null;
723 }
724
725 Module getModule() {
726 return moduleRef.get();
727 }
728
729 ServiceLoader<ResourceBundleProvider> getProviders() {
730 if (!providersChecked) {
731 providers = getServiceLoader(getModule(), name);
732 providersChecked = true;
733 }
734 return providers;
735 }
736
737 boolean hasProviders() {
738 return getProviders() != null;
739 }
740
741 boolean callerHasProvider() {
742 return callerHasProvider == Boolean.TRUE;
743 }
744
745 @Override
746 public boolean equals(Object other) {
747 if (this == other) {
748 return true;
749 }
750 try {
751 final CacheKey otherEntry = (CacheKey)other;
752 //quick check to see if they are not equal
753 if (hashCodeCache != otherEntry.hashCodeCache) {
754 return false;
755 }
756 //are the names the same?
757 if (!name.equals(otherEntry.name)) {
758 return false;
759 }
760 // are the locales the same?
761 if (!locale.equals(otherEntry.locale)) {
762 return false;
763 }
764 //are refs (both non-null) or (both null)?
765 if (loaderRef == null) {
766 return otherEntry.loaderRef == null;
767 }
768 ClassLoader loader = getLoader();
769 Module module = getModule();
770 return (otherEntry.loaderRef != null)
771 // with a null reference we can no longer find
772 // out which class loader or module was referenced; so
773 // treat it as unequal
774 && (loader != null)
775 && (loader == otherEntry.getLoader())
776 && (module != null)
777 && (module.equals(otherEntry.getModule()));
778 } catch (NullPointerException | ClassCastException e) {
779 }
780 return false;
781 }
782
783 @Override
784 public int hashCode() {
785 return hashCodeCache;
786 }
787
788 private void calculateHashCode() {
789 hashCodeCache = name.hashCode() << 3;
790 hashCodeCache ^= locale.hashCode();
791 ClassLoader loader = getLoader();
792 if (loader != null) {
793 hashCodeCache ^= loader.hashCode();
794 }
795 Module module = getModule();
796 if (module != null) {
797 hashCodeCache ^= module.hashCode();
798 }
799 }
800
801 @Override
802 public Object clone() {
803 try {
804 CacheKey clone = (CacheKey) super.clone();
805 if (loaderRef != null) {
806 clone.loaderRef = new KeyElementReference<>(getLoader(),
807 referenceQueue, clone);
808 }
809 clone.moduleRef = new KeyElementReference<>(getModule(),
810 referenceQueue, clone);
811 // Clear the reference to ResourceBundleProviders and the flag
812 clone.providers = null;
813 clone.providersChecked = false;
814 // Clear the reference to a Throwable
815 clone.cause = null;
816 // Clear callerHasProvider
817 clone.callerHasProvider = null;
818 return clone;
819 } catch (CloneNotSupportedException e) {
820 //this should never happen
821 throw new InternalError(e);
822 }
823 }
824
825 String getFormat() {
826 return format;
827 }
828
829 void setFormat(String format) {
830 this.format = format;
839 if (this.cause instanceof ClassNotFoundException) {
840 this.cause = cause;
841 }
842 }
843 }
844
845 private Throwable getCause() {
846 return cause;
847 }
848
849 @Override
850 public String toString() {
851 String l = locale.toString();
852 if (l.length() == 0) {
853 if (locale.getVariant().length() != 0) {
854 l = "__" + locale.getVariant();
855 } else {
856 l = "\"\"";
857 }
858 }
859 return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
860 + "(format=" + format + ")]";
861 }
862 }
863
864 /**
865 * The common interface to get a CacheKey in LoaderReference and
866 * BundleReference.
867 */
868 private static interface CacheKeyReference {
869 public CacheKey getCacheKey();
870 }
871
872 /**
873 * References to a CacheKey element as a WeakReference so that it can be
874 * garbage collected when nobody else is using it.
875 */
876 private static class KeyElementReference<T> extends WeakReference<T>
877 implements CacheKeyReference {
878 private final CacheKey cacheKey;
879
880 KeyElementReference(T referent, ReferenceQueue<Object> q, CacheKey key) {
1577 Control control = provider.getControl(baseName);
1578 if (control != null) {
1579 return control;
1580 }
1581 }
1582 }
1583 return Control.INSTANCE;
1584 }
1585
1586 private static void checkNamedModule(Class<?> caller) {
1587 if (caller.getModule().isNamed()) {
1588 throw new UnsupportedOperationException(
1589 "ResourceBundle.Control not supported in named modules");
1590 }
1591 }
1592
1593 private static ResourceBundle getBundleImpl(String baseName,
1594 Locale locale,
1595 Class<?> caller,
1596 Control control) {
1597 return getBundleImpl(baseName, locale, caller, getLoader(caller), control);
1598 }
1599
1600 /**
1601 * This method will find resource bundles using the legacy mechanism
1602 * if the caller is unnamed module or the given class loader is
1603 * not the class loader of the caller module getting the resource
1604 * bundle, i.e. find the class that is visible to the class loader
1605 * and properties from unnamed module.
1606 *
1607 * The module-aware resource bundle lookup mechanism will load
1608 * the service providers using the service loader mechanism
1609 * as well as properties local in the caller module.
1610 */
1611 private static ResourceBundle getBundleImpl(String baseName,
1612 Locale locale,
1613 Class<?> caller,
1614 ClassLoader loader,
1615 Control control) {
1616 if (caller != null && caller.getModule().isNamed()) {
1617 Module module = caller.getModule();
1618 ClassLoader ml = getLoader(module);
1619 // get resource bundles for a named module only
1620 // if loader is the module's class loader
1621 if (loader == ml || (ml == null && loader == RBClassLoader.INSTANCE)) {
1622 return getBundleImpl(module, module, loader, baseName, locale, control);
1623 }
1624 }
1625 // find resource bundles from unnamed module
1626 Module unnamedModule = loader != null
1627 ? loader.getUnnamedModule()
1628 : ClassLoader.getSystemClassLoader().getUnnamedModule();
1629
1630 if (caller == null) {
1631 throw new InternalError("null caller");
1632 }
1633
1634 Module callerModule = caller.getModule();
1635 return getBundleImpl(callerModule, unnamedModule, loader, baseName, locale, control);
1636 }
1637
1638 private static ResourceBundle getBundleFromModule(Class<?> caller,
1639 Module module,
1640 String baseName,
1641 Locale locale,
1642 Control control) {
1643 Objects.requireNonNull(module);
1644 Module callerModule = caller.getModule();
1645 if (callerModule != module) {
1646 SecurityManager sm = System.getSecurityManager();
1647 if (sm != null) {
1648 sm.checkPermission(GET_CLASSLOADER_PERMISSION);
1649 }
1650 }
1651 return getBundleImpl(callerModule, module, getLoader(module), baseName, locale, control);
1652 }
1653
1654 private static ResourceBundle getBundleImpl(Module callerModule,
1655 Module module,
1656 ClassLoader loader,
1657 String baseName,
1658 Locale locale,
1659 Control control) {
1660 if (locale == null || control == null) {
1661 throw new NullPointerException();
1662 }
1663
1664 // We create a CacheKey here for use by this call. The base name
1665 // loader, and module will never change during the bundle loading
1666 // process. We have to make sure that the locale is set before
1667 // using it as a cache key.
1668 CacheKey cacheKey = new CacheKey(baseName, locale, loader, module);
1669 ResourceBundle bundle = null;
1670
1671 // Quick lookup of the cache.
1672 BundleReference bundleRef = cacheList.get(cacheKey);
1673 if (bundleRef != null) {
1674 bundle = bundleRef.get();
1675 bundleRef = null;
1676 }
1677
1678 // If this bundle and all of its parents are valid (not expired),
1679 // then return this bundle. If any of the bundles is expired, we
1680 // don't call control.needsReload here but instead drop into the
1681 // complete loading process below.
1682 if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
1683 return bundle;
1684 }
1685
1686 // No valid bundle was found in the cache, so we need to load the
1687 // resource bundle and its parents.
1688
2000 cacheKey.callerHasProvider = Boolean.FALSE;
2001 }
2002 return null;
2003 }
2004 });
2005
2006 }
2007
2008 /*
2009 * Legacy mechanism to load resource bundles
2010 */
2011 private static ResourceBundle loadBundle(CacheKey cacheKey,
2012 List<String> formats,
2013 Control control,
2014 boolean reload) {
2015
2016 // Here we actually load the bundle in the order of formats
2017 // specified by the getFormats() value.
2018 Locale targetLocale = cacheKey.getLocale();
2019
2020 ResourceBundle bundle = null;
2021 for (String format : formats) {
2022 try {
2023 // ResourceBundle.Control.newBundle may be overridden
2024 bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
2025 cacheKey.getLoader(), reload);
2026 } catch (LinkageError | Exception error) {
2027 // We need to handle the LinkageError case due to
2028 // inconsistent case-sensitivity in ClassLoader.
2029 // See 6572242 for details.
2030 cacheKey.setCause(error);
2031 }
2032 if (bundle != null) {
2033 // Set the format in the cache key so that it can be
2034 // used when calling needsReload later.
2035 cacheKey.setFormat(format);
2036 bundle.name = cacheKey.getName();
2037 bundle.locale = targetLocale;
2038 // Bundle provider might reuse instances. So we should make
2039 // sure to clear the expired flag here.
2040 bundle.expired = false;
2041 break;
2042 }
2043 }
2044
2045 return bundle;
2147 if (p != null && p.expired) {
2148 assert bundle != NONEXISTENT_BUNDLE;
2149 bundle.expired = true;
2150 bundle.cacheKey = null;
2151 cacheList.remove(cacheKey, bundleRef);
2152 bundle = null;
2153 } else {
2154 CacheKey key = bundleRef.getCacheKey();
2155 long expirationTime = key.expirationTime;
2156 if (!bundle.expired && expirationTime >= 0 &&
2157 expirationTime <= System.currentTimeMillis()) {
2158 // its TTL period has expired.
2159 if (bundle != NONEXISTENT_BUNDLE) {
2160 // Synchronize here to call needsReload to avoid
2161 // redundant concurrent calls for the same bundle.
2162 synchronized (bundle) {
2163 expirationTime = key.expirationTime;
2164 if (!bundle.expired && expirationTime >= 0 &&
2165 expirationTime <= System.currentTimeMillis()) {
2166 try {
2167 bundle.expired = control.needsReload(key.getName(),
2168 key.getLocale(),
2169 key.getFormat(),
2170 key.getLoader(),
2171 bundle,
2172 key.loadTime);
2173 } catch (Exception e) {
2174 cacheKey.setCause(e);
2175 }
2176 if (bundle.expired) {
2177 // If the bundle needs to be reloaded, then
2178 // remove the bundle from the cache, but
2179 // return the bundle with the expired flag
2180 // on.
2181 bundle.cacheKey = null;
2182 cacheList.remove(cacheKey, bundleRef);
2183 } else {
2184 // Update the expiration control info. and reuse
2185 // the same bundle instance
2186 setExpirationTime(key, control);
2187 }
2188 }
2189 }
2190 } else {
2248 long now = System.currentTimeMillis();
2249 cacheKey.loadTime = now;
2250 cacheKey.expirationTime = now + ttl;
2251 } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
2252 cacheKey.expirationTime = ttl;
2253 } else {
2254 throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
2255 }
2256 }
2257
2258 /**
2259 * Removes all resource bundles from the cache that have been loaded
2260 * by the caller's module using the caller's class loader.
2261 *
2262 * @since 1.6
2263 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2264 */
2265 @CallerSensitive
2266 public static final void clearCache() {
2267 Class<?> caller = Reflection.getCallerClass();
2268 clearCache(getLoader(caller), caller.getModule());
2269 }
2270
2271 /**
2272 * Removes all resource bundles from the cache that have been loaded
2273 * by the caller's module using the given class loader.
2274 *
2275 * @param loader the class loader
2276 * @exception NullPointerException if <code>loader</code> is null
2277 * @since 1.6
2278 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2279 */
2280 @CallerSensitive
2281 public static final void clearCache(ClassLoader loader) {
2282 Objects.requireNonNull(loader);
2283 clearCache(loader, Reflection.getCallerClass().getModule());
2284 }
2285
2286 /**
2287 * Removes all resource bundles from the cache that have been loaded by the
2288 * given {@code module}.
2289 *
2290 * @param module the module
2291 * @throws NullPointerException
2292 * if {@code module} is {@code null}
2293 * @throws SecurityException
2294 * if the caller doesn't have the permission to
2295 * {@linkplain Module#getClassLoader() get the class loader}
2296 * of the given {@code module}
2297 * @since 9
2298 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2299 */
2300 public static final void clearCache(Module module) {
2301 clearCache(module.getClassLoader(), module);
2302 }
2303
2304 private static void clearCache(ClassLoader loader, Module module) {
2305 Set<CacheKey> set = cacheList.keySet();
2306 set.stream().filter((key) -> (key.getLoader() == loader && key.getModule() == module))
2307 .forEach(set::remove);
2308 }
2309
2310 /**
2311 * Gets an object for the given key from this resource bundle.
2312 * Returns null if this resource bundle does not contain an
2313 * object for the given key.
2314 *
2315 * @param key the key for the desired object
2316 * @exception NullPointerException if <code>key</code> is <code>null</code>
2317 * @return the object for the given key, or null
2318 */
2319 protected abstract Object handleGetObject(String key);
2320
2321 /**
2322 * Returns an enumeration of the keys.
2323 *
2324 * @return an <code>Enumeration</code> of the keys contained in
2325 * this <code>ResourceBundle</code> and its parent bundles.
2326 */
2327 public abstract Enumeration<String> getKeys();
|
362 }
363
364 @Override
365 public ResourceBundle getParent(ResourceBundle bundle) {
366 return bundle.parent;
367 }
368
369 @Override
370 public void setLocale(ResourceBundle bundle, Locale locale) {
371 bundle.locale = locale;
372 }
373
374 @Override
375 public void setName(ResourceBundle bundle, String name) {
376 bundle.name = name;
377 }
378
379 @Override
380 public ResourceBundle getBundle(String baseName, Locale locale, Module module) {
381 // use the given module as the caller to bypass the access check
382 return getBundleImpl(module, module,
383 baseName, locale, Control.INSTANCE);
384 }
385
386 @Override
387 public ResourceBundle newResourceBundle(Class<? extends ResourceBundle> bundleClass) {
388 return ResourceBundleProviderHelper.newResourceBundle(bundleClass);
389 }
390 });
391 }
392
393 /** constant indicating that no resource bundle exists */
394 private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
395 public Enumeration<String> getKeys() { return null; }
396 protected Object handleGetObject(String key) { return null; }
397 public String toString() { return "NONEXISTENT_BUNDLE"; }
398 };
399
400
401 /**
402 * The cache is a map from cache keys (with bundle base name, locale, and
549 +this.getClass().getName()
550 +", key "+key,
551 this.getClass().getName(),
552 key);
553 }
554 }
555 return obj;
556 }
557
558 /**
559 * Returns the locale of this resource bundle. This method can be used after a
560 * call to getBundle() to determine whether the resource bundle returned really
561 * corresponds to the requested locale or is a fallback.
562 *
563 * @return the locale of this resource bundle
564 */
565 public Locale getLocale() {
566 return locale;
567 }
568
569 private static ClassLoader getLoader(Module module) {
570 PrivilegedAction<ClassLoader> pa = module::getClassLoader;
571 return AccessController.doPrivileged(pa);
572 }
573
574 /**
575 * @param module a non-null-screened module form the {@link CacheKey#getModule()}.
576 * @return the ClassLoader to use in {@link Control#needsReload}
577 * and {@link Control#newBundle}
578 */
579 private static ClassLoader getLoaderForControl(Module module) {
580 ClassLoader loader = getLoader(module);
581 return loader == null ? ClassLoader.getSystemClassLoader() : loader;
582 }
583
584 /**
585 * Sets the parent bundle of this bundle.
586 * The parent bundle is searched by {@link #getObject getObject}
587 * when this bundle does not contain a particular resource.
588 *
589 * @param parent this bundle's parent bundle.
590 */
591 protected void setParent(ResourceBundle parent) {
592 assert parent != NONEXISTENT_BUNDLE;
593 this.parent = parent;
594 }
595
596 /**
597 * Key used for cached resource bundles. The key checks the base
598 * name, the locale, the bundle module, and the caller module
599 * to determine if the resource is a match to the requested one.
600 * The base name, the locale and both modules must have a non-null value.
601 */
602 private static class CacheKey implements Cloneable {
603 // These four are the actual keys for lookup in Map.
604 private String name;
605 private Locale locale;
606 private KeyElementReference<Module> moduleRef;
607 private KeyElementReference<Module> callerRef;
608 // this is the part of hashCode that pertains to module and callerModule
609 // which can be GCed..
610 private int modulesHash;
611
612 // bundle format which is necessary for calling
613 // Control.needsReload().
614 private String format;
615
616 // These time values are in CacheKey so that NONEXISTENT_BUNDLE
617 // doesn't need to be cloned for caching.
618
619 // The time when the bundle has been loaded
620 private volatile long loadTime;
621
622 // The time when the bundle expires in the cache, or either
623 // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
624 private volatile long expirationTime;
625
626 // Placeholder for an error report by a Throwable
627 private Throwable cause;
628
629 // ResourceBundleProviders for loading ResourceBundles
630 private ServiceLoader<ResourceBundleProvider> providers;
631 private boolean providersChecked;
632
633 // Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
634 private Boolean callerHasProvider;
635
636 CacheKey(String baseName, Locale locale, Module module, Module caller) {
637 Objects.requireNonNull(module);
638 Objects.requireNonNull(caller);
639
640 this.name = baseName;
641 this.locale = locale;
642 this.moduleRef = new KeyElementReference<>(module, referenceQueue, this);
643 this.callerRef = new KeyElementReference<>(caller, referenceQueue, this);
644 this.modulesHash = module.hashCode() ^ caller.hashCode();
645 }
646
647 String getName() {
648 return name;
649 }
650
651 CacheKey setName(String baseName) {
652 this.name = baseName;
653 return this;
654 }
655
656 Locale getLocale() {
657 return locale;
658 }
659
660 CacheKey setLocale(Locale locale) {
661 this.locale = locale;
662 return this;
663 }
664
665 Module getModule() {
666 return moduleRef == null ? null : moduleRef.get();
667 }
668
669 Module getCallerModule() {
670 return callerRef == null ? null : callerRef.get();
671 }
672
673 ServiceLoader<ResourceBundleProvider> getProviders() {
674 if (!providersChecked) {
675 providers = getServiceLoader(getModule(), name);
676 providersChecked = true;
677 }
678 return providers;
679 }
680
681 boolean hasProviders() {
682 return getProviders() != null;
683 }
684
685 boolean callerHasProvider() {
686 return callerHasProvider == Boolean.TRUE;
687 }
688
689 @Override
690 public boolean equals(Object other) {
691 if (this == other) {
692 return true;
693 }
694 try {
695 final CacheKey otherEntry = (CacheKey)other;
696 //quick check to see if they are not equal
697 if (modulesHash != otherEntry.modulesHash) {
698 return false;
699 }
700 //are the names the same?
701 if (!name.equals(otherEntry.name)) {
702 return false;
703 }
704 // are the locales the same?
705 if (!locale.equals(otherEntry.locale)) {
706 return false;
707 }
708 // are modules and callerModules the same and non-null?
709 Module module = getModule();
710 Module caller = getCallerModule();
711 return ((module != null) && (module.equals(otherEntry.getModule())) &&
712 (caller != null) && (caller.equals(otherEntry.getCallerModule())));
713 } catch (NullPointerException | ClassCastException e) {
714 }
715 return false;
716 }
717
718 @Override
719 public int hashCode() {
720 return (name.hashCode() << 3) ^ locale.hashCode() ^ modulesHash;
721 }
722
723 @Override
724 public Object clone() {
725 try {
726 CacheKey clone = (CacheKey) super.clone();
727
728 Module module = getModule();
729 clone.moduleRef =
730 module == null
731 ? null // don't ever create a Reference for a null referent
732 // because it will never be enqueued and consequently
733 // its CacheKey will never be removed from cacheList...
734 : new KeyElementReference<>(module, referenceQueue, clone);
735
736 Module caller = getCallerModule();
737 clone.callerRef =
738 caller == null
739 ? null
740 : new KeyElementReference<>(caller, referenceQueue, clone);
741
742 // Clear the reference to ResourceBundleProviders and the flag
743 clone.providers = null;
744 clone.providersChecked = false;
745 // Clear the reference to a Throwable
746 clone.cause = null;
747 // Clear callerHasProvider
748 clone.callerHasProvider = null;
749 return clone;
750 } catch (CloneNotSupportedException e) {
751 //this should never happen
752 throw new InternalError(e);
753 }
754 }
755
756 String getFormat() {
757 return format;
758 }
759
760 void setFormat(String format) {
761 this.format = format;
770 if (this.cause instanceof ClassNotFoundException) {
771 this.cause = cause;
772 }
773 }
774 }
775
776 private Throwable getCause() {
777 return cause;
778 }
779
780 @Override
781 public String toString() {
782 String l = locale.toString();
783 if (l.length() == 0) {
784 if (locale.getVariant().length() != 0) {
785 l = "__" + locale.getVariant();
786 } else {
787 l = "\"\"";
788 }
789 }
790 return "CacheKey[" + name +
791 ", locale=" + l +
792 ", module=" + getModule() +
793 ", callerModule=" + getCallerModule() +
794 ", format=" + format +
795 "]";
796 }
797 }
798
799 /**
800 * The common interface to get a CacheKey in LoaderReference and
801 * BundleReference.
802 */
803 private static interface CacheKeyReference {
804 public CacheKey getCacheKey();
805 }
806
807 /**
808 * References to a CacheKey element as a WeakReference so that it can be
809 * garbage collected when nobody else is using it.
810 */
811 private static class KeyElementReference<T> extends WeakReference<T>
812 implements CacheKeyReference {
813 private final CacheKey cacheKey;
814
815 KeyElementReference(T referent, ReferenceQueue<Object> q, CacheKey key) {
1512 Control control = provider.getControl(baseName);
1513 if (control != null) {
1514 return control;
1515 }
1516 }
1517 }
1518 return Control.INSTANCE;
1519 }
1520
1521 private static void checkNamedModule(Class<?> caller) {
1522 if (caller.getModule().isNamed()) {
1523 throw new UnsupportedOperationException(
1524 "ResourceBundle.Control not supported in named modules");
1525 }
1526 }
1527
1528 private static ResourceBundle getBundleImpl(String baseName,
1529 Locale locale,
1530 Class<?> caller,
1531 Control control) {
1532 return getBundleImpl(baseName, locale, caller, caller.getClassLoader(), control);
1533 }
1534
1535 /**
1536 * This method will find resource bundles using the legacy mechanism
1537 * if the caller is unnamed module or the given class loader is
1538 * not the class loader of the caller module getting the resource
1539 * bundle, i.e. find the class that is visible to the class loader
1540 * and properties from unnamed module.
1541 *
1542 * The module-aware resource bundle lookup mechanism will load
1543 * the service providers using the service loader mechanism
1544 * as well as properties local in the caller module.
1545 */
1546 private static ResourceBundle getBundleImpl(String baseName,
1547 Locale locale,
1548 Class<?> caller,
1549 ClassLoader loader,
1550 Control control) {
1551 if (caller == null) {
1552 throw new InternalError("null caller");
1553 }
1554 Module callerModule = caller.getModule();
1555
1556 // get resource bundles for a named module only if loader is the module's class loader
1557 if (callerModule.isNamed() && loader == getLoader(callerModule)) {
1558 return getBundleImpl(callerModule, callerModule, baseName, locale, control);
1559 }
1560
1561 // there's no code in unnamed module of bootstrap class loader so loader
1562 // must be non-null (non-bootstrap) if the caller is from unnamed module
1563 if (loader == null) {
1564 throw new InternalError("null loader");
1565 }
1566
1567 // find resource bundles from unnamed module of given class loader
1568 Module unnamedModule = loader.getUnnamedModule();
1569 return getBundleImpl(callerModule, unnamedModule, baseName, locale, control);
1570 }
1571
1572 private static ResourceBundle getBundleFromModule(Class<?> caller,
1573 Module module,
1574 String baseName,
1575 Locale locale,
1576 Control control) {
1577 Objects.requireNonNull(module);
1578 Module callerModule = caller.getModule();
1579 if (callerModule != module) {
1580 SecurityManager sm = System.getSecurityManager();
1581 if (sm != null) {
1582 sm.checkPermission(GET_CLASSLOADER_PERMISSION);
1583 }
1584 }
1585 return getBundleImpl(callerModule, module, baseName, locale, control);
1586 }
1587
1588 private static ResourceBundle getBundleImpl(Module callerModule,
1589 Module module,
1590 String baseName,
1591 Locale locale,
1592 Control control) {
1593 if (locale == null || control == null) {
1594 throw new NullPointerException();
1595 }
1596
1597 // We create a CacheKey here for use by this call. The base name
1598 // loader, and module will never change during the bundle loading
1599 // process. We have to make sure that the locale is set before
1600 // using it as a cache key.
1601 CacheKey cacheKey = new CacheKey(baseName, locale, module, callerModule);
1602 ResourceBundle bundle = null;
1603
1604 // Quick lookup of the cache.
1605 BundleReference bundleRef = cacheList.get(cacheKey);
1606 if (bundleRef != null) {
1607 bundle = bundleRef.get();
1608 bundleRef = null;
1609 }
1610
1611 // If this bundle and all of its parents are valid (not expired),
1612 // then return this bundle. If any of the bundles is expired, we
1613 // don't call control.needsReload here but instead drop into the
1614 // complete loading process below.
1615 if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
1616 return bundle;
1617 }
1618
1619 // No valid bundle was found in the cache, so we need to load the
1620 // resource bundle and its parents.
1621
1933 cacheKey.callerHasProvider = Boolean.FALSE;
1934 }
1935 return null;
1936 }
1937 });
1938
1939 }
1940
1941 /*
1942 * Legacy mechanism to load resource bundles
1943 */
1944 private static ResourceBundle loadBundle(CacheKey cacheKey,
1945 List<String> formats,
1946 Control control,
1947 boolean reload) {
1948
1949 // Here we actually load the bundle in the order of formats
1950 // specified by the getFormats() value.
1951 Locale targetLocale = cacheKey.getLocale();
1952
1953 Module module = cacheKey.getModule();
1954 if (module == null) {
1955 // should not happen
1956 throw new InternalError(
1957 "Module for cache key: " + cacheKey + " has been GCed.");
1958 }
1959 ClassLoader loader = getLoaderForControl(module);
1960
1961 ResourceBundle bundle = null;
1962 for (String format : formats) {
1963 try {
1964 // ResourceBundle.Control.newBundle may be overridden
1965 bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
1966 loader, reload);
1967 } catch (LinkageError | Exception error) {
1968 // We need to handle the LinkageError case due to
1969 // inconsistent case-sensitivity in ClassLoader.
1970 // See 6572242 for details.
1971 cacheKey.setCause(error);
1972 }
1973 if (bundle != null) {
1974 // Set the format in the cache key so that it can be
1975 // used when calling needsReload later.
1976 cacheKey.setFormat(format);
1977 bundle.name = cacheKey.getName();
1978 bundle.locale = targetLocale;
1979 // Bundle provider might reuse instances. So we should make
1980 // sure to clear the expired flag here.
1981 bundle.expired = false;
1982 break;
1983 }
1984 }
1985
1986 return bundle;
2088 if (p != null && p.expired) {
2089 assert bundle != NONEXISTENT_BUNDLE;
2090 bundle.expired = true;
2091 bundle.cacheKey = null;
2092 cacheList.remove(cacheKey, bundleRef);
2093 bundle = null;
2094 } else {
2095 CacheKey key = bundleRef.getCacheKey();
2096 long expirationTime = key.expirationTime;
2097 if (!bundle.expired && expirationTime >= 0 &&
2098 expirationTime <= System.currentTimeMillis()) {
2099 // its TTL period has expired.
2100 if (bundle != NONEXISTENT_BUNDLE) {
2101 // Synchronize here to call needsReload to avoid
2102 // redundant concurrent calls for the same bundle.
2103 synchronized (bundle) {
2104 expirationTime = key.expirationTime;
2105 if (!bundle.expired && expirationTime >= 0 &&
2106 expirationTime <= System.currentTimeMillis()) {
2107 try {
2108 Module module = cacheKey.getModule();
2109 bundle.expired =
2110 module == null || // already GCed
2111 control.needsReload(key.getName(),
2112 key.getLocale(),
2113 key.getFormat(),
2114 getLoaderForControl(module),
2115 bundle,
2116 key.loadTime);
2117 } catch (Exception e) {
2118 cacheKey.setCause(e);
2119 }
2120 if (bundle.expired) {
2121 // If the bundle needs to be reloaded, then
2122 // remove the bundle from the cache, but
2123 // return the bundle with the expired flag
2124 // on.
2125 bundle.cacheKey = null;
2126 cacheList.remove(cacheKey, bundleRef);
2127 } else {
2128 // Update the expiration control info. and reuse
2129 // the same bundle instance
2130 setExpirationTime(key, control);
2131 }
2132 }
2133 }
2134 } else {
2192 long now = System.currentTimeMillis();
2193 cacheKey.loadTime = now;
2194 cacheKey.expirationTime = now + ttl;
2195 } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
2196 cacheKey.expirationTime = ttl;
2197 } else {
2198 throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
2199 }
2200 }
2201
2202 /**
2203 * Removes all resource bundles from the cache that have been loaded
2204 * by the caller's module using the caller's class loader.
2205 *
2206 * @since 1.6
2207 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2208 */
2209 @CallerSensitive
2210 public static final void clearCache() {
2211 Class<?> caller = Reflection.getCallerClass();
2212 clearCacheImpl(caller.getModule(), caller.getClassLoader());
2213 }
2214
2215 /**
2216 * Removes all resource bundles from the cache that have been loaded
2217 * by the caller's module using the given class loader.
2218 *
2219 * @param loader the class loader
2220 * @exception NullPointerException if <code>loader</code> is null
2221 * @since 1.6
2222 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2223 */
2224 @CallerSensitive
2225 public static final void clearCache(ClassLoader loader) {
2226 Objects.requireNonNull(loader);
2227 Class<?> caller = Reflection.getCallerClass();
2228 clearCacheImpl(caller.getModule(), loader);
2229 }
2230
2231 /**
2232 * Removes all resource bundles from the cache that have been loaded by the
2233 * given {@code module}.
2234 *
2235 * @param module the module
2236 * @throws NullPointerException
2237 * if {@code module} is {@code null}
2238 * @throws SecurityException
2239 * if the caller doesn't have the permission to
2240 * {@linkplain Module#getClassLoader() get the class loader}
2241 * of the given {@code module}
2242 * @since 9
2243 * @see ResourceBundle.Control#getTimeToLive(String,Locale)
2244 */
2245 public static final void clearCache(Module module) {
2246 Objects.requireNonNull(module);
2247 clearCacheImpl(module, module.getClassLoader());
2248 }
2249
2250 private static void clearCacheImpl(Module callerModule, ClassLoader loader) {
2251 cacheList.keySet().removeIf(
2252 key -> key.getCallerModule() == callerModule &&
2253 getLoader(key.getModule()) == loader
2254 );
2255 }
2256
2257 /**
2258 * Gets an object for the given key from this resource bundle.
2259 * Returns null if this resource bundle does not contain an
2260 * object for the given key.
2261 *
2262 * @param key the key for the desired object
2263 * @exception NullPointerException if <code>key</code> is <code>null</code>
2264 * @return the object for the given key, or null
2265 */
2266 protected abstract Object handleGetObject(String key);
2267
2268 /**
2269 * Returns an enumeration of the keys.
2270 *
2271 * @return an <code>Enumeration</code> of the keys contained in
2272 * this <code>ResourceBundle</code> and its parent bundles.
2273 */
2274 public abstract Enumeration<String> getKeys();
|