< prev index next >
src/java.base/share/classes/java/util/ResourceBundle.java
Print this page
*** 42,52 ****
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
- import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Module;
import java.net.JarURLConnection;
--- 42,51 ----
*** 54,73 ****
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry;
import java.util.spi.ResourceBundleControlProvider;
import java.util.spi.ResourceBundleProvider;
import jdk.internal.misc.JavaUtilResourceBundleAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import sun.util.locale.BaseLocale;
import sun.util.locale.LocaleObjectCache;
import sun.util.locale.provider.ResourceBundleProviderSupport;
import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
--- 53,72 ----
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.jar.JarEntry;
import java.util.spi.ResourceBundleControlProvider;
import java.util.spi.ResourceBundleProvider;
import jdk.internal.misc.JavaUtilResourceBundleAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
+ import jdk.internal.util.concurrent.AbstractClassLoaderValue;
+ import jdk.internal.util.concurrent.ClassLoaderValue;
import sun.util.locale.BaseLocale;
import sun.util.locale.LocaleObjectCache;
import sun.util.locale.provider.ResourceBundleProviderSupport;
import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
*** 344,356 ****
* @see ResourceBundleProvider
* @since 1.1
*/
public abstract class ResourceBundle {
- /** initial size of the bundle cache */
- private static final int INITIAL_CACHE_SIZE = 32;
-
static {
SharedSecrets.setJavaUtilResourceBundleAccess(
new JavaUtilResourceBundleAccess() {
@Override
public void setParent(ResourceBundle bundle,
--- 343,352 ----
*** 373,404 ****
bundle.name = name;
}
});
}
! /** constant indicating that no resource bundle exists */
! private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
public Enumeration<String> getKeys() { return null; }
protected Object handleGetObject(String key) { return null; }
public String toString() { return "NONEXISTENT_BUNDLE"; }
! };
/**
! * The cache is a map from cache keys (with bundle base name, locale, and
! * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a
! * BundleReference.
! *
! * The cache is a ConcurrentMap, allowing the cache to be searched
! * concurrently by multiple threads. This will also allow the cache keys
! * to be reclaimed along with the ClassLoaders they reference.
! *
! * This variable would be better named "cache", but we keep the old
! * name for compatibility with some workarounds for bug 4212439.
*/
! private static final ConcurrentMap<CacheKey, BundleReference> cacheList
! = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
/**
* Queue for reference objects referring to class loaders or bundles.
*/
private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
--- 369,394 ----
bundle.name = name;
}
});
}
! /**
! * special bundle type indicating that no resource bundle exists
! */
! private static final class NONEXISTENT_BUNDLE extends ResourceBundle {
public Enumeration<String> getKeys() { return null; }
protected Object handleGetObject(String key) { return null; }
public String toString() { return "NONEXISTENT_BUNDLE"; }
! }
/**
! * The cache of BundleReference(s) per class loader, module, bundle base name
! * and locale.
*/
! private static final ClassLoaderValue<BundleReference> cache
! = new ClassLoaderValue<>();
/**
* Queue for reference objects referring to class loaders or bundles.
*/
private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
*** 424,455 ****
/**
* The parent bundle of this bundle.
* The parent bundle is searched by {@link #getObject getObject}
* when this bundle does not contain a particular resource.
*/
! protected ResourceBundle parent = null;
/**
* The locale for this bundle.
*/
! private Locale locale = null;
/**
* The base bundle name for this bundle.
*/
private String name;
/**
* The flag indicating this bundle has expired in the cache.
*/
private volatile boolean expired;
/**
! * The back link to the cache key. null if this bundle isn't in
! * the cache (yet) or has expired.
*/
! private volatile CacheKey cacheKey;
/**
* A Set of the keys contained only in this ResourceBundle.
*/
private volatile Set<String> keySet;
--- 414,456 ----
/**
* The parent bundle of this bundle.
* The parent bundle is searched by {@link #getObject getObject}
* when this bundle does not contain a particular resource.
*/
! protected ResourceBundle parent;
/**
* The locale for this bundle.
*/
! private Locale locale;
/**
* The base bundle name for this bundle.
*/
private String name;
/**
+ * Bundle format which is necessary for calling
+ * Control.needsReload().
+ */
+ private String format;
+
+ /**
* The flag indicating this bundle has expired in the cache.
*/
private volatile boolean expired;
/**
! * The time when the bundle has been loaded
*/
! private volatile long loadTime;
!
! /**
! * The time when the bundle expires in the cache, or either
! * Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
! */
! private volatile long expirationTime;
/**
* A Set of the keys contained only in this ResourceBundle.
*/
private volatile Set<String> keySet;
*** 616,822 ****
* when this bundle does not contain a particular resource.
*
* @param parent this bundle's parent bundle.
*/
protected void setParent(ResourceBundle parent) {
! assert parent != NONEXISTENT_BUNDLE;
this.parent = parent;
}
/**
! * Key used for cached resource bundles. The key checks the base
! * name, the locale, the class loader, and the caller module
! * to determine if the resource is a match to the requested one.
! * The loader may be null, but the base name, the locale and
! * module must have a non-null value.
! */
! private static class CacheKey implements Cloneable {
! // These four are the actual keys for lookup in Map.
private String name;
private Locale locale;
- private KeyElementReference<ClassLoader> loaderRef;
- private KeyElementReference<Module> moduleRef;
-
-
- // bundle format which is necessary for calling
- // Control.needsReload().
- private String format;
-
- // These time values are in CacheKey so that NONEXISTENT_BUNDLE
- // doesn't need to be cloned for caching.
! // The time when the bundle has been loaded
! private volatile long loadTime;
! // The time when the bundle expires in the cache, or either
! // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
! private volatile long expirationTime;
// Placeholder for an error report by a Throwable
private Throwable cause;
- // Hash code value cache to avoid recalculating the hash code
- // of this instance.
- private int hashCodeCache;
-
// ResourceBundleProviders for loading ResourceBundles
! private ServiceLoader<ResourceBundleProvider> providers;
! private boolean providersChecked;
// Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
private Boolean callerHasProvider;
! CacheKey(String baseName, Locale locale, ClassLoader loader, Module module) {
! Objects.requireNonNull(module);
!
! this.name = baseName;
! this.locale = locale;
! if (loader == null) {
! this.loaderRef = null;
! } else {
! this.loaderRef = new KeyElementReference<>(loader, referenceQueue, this);
! }
! this.moduleRef = new KeyElementReference<>(module, referenceQueue, this);
! calculateHashCode();
}
String getName() {
return name;
}
! CacheKey setName(String baseName) {
! if (!this.name.equals(baseName)) {
! this.name = baseName;
! calculateHashCode();
! }
return this;
}
Locale getLocale() {
return locale;
}
! CacheKey setLocale(Locale locale) {
! if (!this.locale.equals(locale)) {
! this.locale = locale;
! calculateHashCode();
! }
return this;
}
ClassLoader getLoader() {
! return (loaderRef != null) ? loaderRef.get() : null;
}
Module getModule() {
! return moduleRef.get();
}
! ServiceLoader<ResourceBundleProvider> getProviders() {
! if (!providersChecked) {
! providers = getServiceLoader(getModule(), name);
! providersChecked = true;
}
return providers;
}
- boolean hasProviders() {
- return getProviders() != null;
- }
-
boolean callerHasProvider() {
return callerHasProvider == Boolean.TRUE;
}
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- try {
- final CacheKey otherEntry = (CacheKey)other;
- //quick check to see if they are not equal
- if (hashCodeCache != otherEntry.hashCodeCache) {
- return false;
- }
- //are the names the same?
- if (!name.equals(otherEntry.name)) {
- return false;
- }
- // are the locales the same?
- if (!locale.equals(otherEntry.locale)) {
- return false;
- }
- //are refs (both non-null) or (both null)?
- if (loaderRef == null) {
- return otherEntry.loaderRef == null;
- }
- ClassLoader loader = getLoader();
- Module module = getModule();
- return (otherEntry.loaderRef != null)
- // with a null reference we can no longer find
- // out which class loader or module was referenced; so
- // treat it as unequal
- && (loader != null)
- && (loader == otherEntry.getLoader())
- && (module != null)
- && (module.equals(otherEntry.getModule()));
- } catch (NullPointerException | ClassCastException e) {
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return hashCodeCache;
- }
-
- private void calculateHashCode() {
- hashCodeCache = name.hashCode() << 3;
- hashCodeCache ^= locale.hashCode();
- ClassLoader loader = getLoader();
- if (loader != null) {
- hashCodeCache ^= loader.hashCode();
- }
- Module module = getModule();
- if (module != null) {
- hashCodeCache ^= module.hashCode();
- }
- }
-
- @Override
- public Object clone() {
- try {
- CacheKey clone = (CacheKey) super.clone();
- if (loaderRef != null) {
- clone.loaderRef = new KeyElementReference<>(getLoader(),
- referenceQueue, clone);
- }
- clone.moduleRef = new KeyElementReference<>(getModule(),
- referenceQueue, clone);
- // Clear the reference to ResourceBundleProviders and the flag
- clone.providers = null;
- clone.providersChecked = false;
- // Clear the reference to a Throwable
- clone.cause = null;
- // Clear callerHasProvider
- clone.callerHasProvider = null;
- return clone;
- } catch (CloneNotSupportedException e) {
- //this should never happen
- throw new InternalError(e);
- }
- }
-
- String getFormat() {
- return format;
- }
-
- void setFormat(String format) {
- this.format = format;
- }
-
private void setCause(Throwable cause) {
if (this.cause == null) {
this.cause = cause;
} else {
// Override the cause if the previous one is
--- 617,706 ----
* when this bundle does not contain a particular resource.
*
* @param parent this bundle's parent bundle.
*/
protected void setParent(ResourceBundle parent) {
! assert !(parent instanceof NONEXISTENT_BUNDLE);
this.parent = parent;
}
/**
! * A session object used during the {@link #getBundle} call.
! * The loader may be null (in which case it is considered to be the
! * bootstrap class loader), but the module, base name, and locale must have
! * a non-null value.
! */
! private static class LoadSession {
! // These four are the actual keys for lookup in cache.
! private ClassLoader loader;
! private Module module;
private String name;
private Locale locale;
! // compose and return the cache key
! ClassLoaderValue<BundleReference>.Sub<Module>.Sub<String>.Sub<Locale> key() {
! return cache.sub(module).sub(name).sub(locale);
! }
! // return a bundle reference from cache or null
! BundleReference getFromCache() {
! return key().get(loader);
! }
// Placeholder for an error report by a Throwable
private Throwable cause;
// ResourceBundleProviders for loading ResourceBundles
! private Iterable<ResourceBundleProvider> providers;
// Boolean.TRUE if the factory method caller provides a ResourceBundleProvier.
private Boolean callerHasProvider;
! LoadSession(String baseName, Locale locale, ClassLoader loader, Module module) {
! this.name = Objects.requireNonNull(baseName);
! this.locale = Objects.requireNonNull(locale);
! this.loader = loader;
! this.module = Objects.requireNonNull(module);
}
String getName() {
return name;
}
! LoadSession setName(String baseName) {
! this.name = Objects.requireNonNull(baseName);
return this;
}
Locale getLocale() {
return locale;
}
! LoadSession setLocale(Locale locale) {
! this.locale = Objects.requireNonNull(locale);
return this;
}
ClassLoader getLoader() {
! return loader;
}
Module getModule() {
! return module;
}
! Iterable<ResourceBundleProvider> getProviders() {
! if (providers == null) {
! providers = ResourceBundle.getProviders(module, name);
}
return providers;
}
boolean callerHasProvider() {
return callerHasProvider == Boolean.TRUE;
}
private void setCause(Throwable cause) {
if (this.cause == null) {
this.cause = cause;
} else {
// Override the cause if the previous one is
*** 839,896 ****
l = "__" + locale.getVariant();
} else {
l = "\"\"";
}
}
! return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
! + "(format=" + format + ")]";
}
}
/**
! * The common interface to get a CacheKey in LoaderReference and
! * BundleReference.
! */
! private static interface CacheKeyReference {
! public CacheKey getCacheKey();
! }
!
! /**
! * References to a CacheKey element as a WeakReference so that it can be
! * garbage collected when nobody else is using it.
*/
! private static class KeyElementReference<T> extends WeakReference<T>
! implements CacheKeyReference {
! private final CacheKey cacheKey;
! KeyElementReference(T referent, ReferenceQueue<Object> q, CacheKey key) {
super(referent, q);
! cacheKey = key;
}
! @Override
! public CacheKey getCacheKey() {
! return cacheKey;
! }
! }
!
! /**
! * References to bundles are soft references so that they can be garbage
! * collected when they have no hard references.
! */
! private static class BundleReference extends SoftReference<ResourceBundle>
! implements CacheKeyReference {
! private final CacheKey cacheKey;
!
! BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key) {
! super(referent, q);
! cacheKey = key;
! }
!
! @Override
! public CacheKey getCacheKey() {
! return cacheKey;
}
}
/**
* Gets a resource bundle using the specified base name, the default locale,
--- 723,755 ----
l = "__" + locale.getVariant();
} else {
l = "\"\"";
}
}
! return "LookupSession[" + name + ", lc=" + l + ", ldr=" + getLoader()
! + "]";
}
}
/**
! * References to bundles are soft references so that they can be cleared
! * when GC demands to free some heap.
*/
! private static class BundleReference extends SoftReference<ResourceBundle> {
! private final ClassLoader classLoader;
! private final AbstractClassLoaderValue<?, BundleReference>.Sub<?> key;
! BundleReference(ResourceBundle referent, ReferenceQueue<Object> q,
! ClassLoader classLoader,
! AbstractClassLoaderValue<?, BundleReference>.Sub<?> key) {
super(referent, q);
! this.classLoader = classLoader;
! this.key = key;
}
! void remove() {
! key.remove(classLoader, this);
}
}
/**
* Gets a resource bundle using the specified base name, the default locale,
*** 1618,1639 ****
Control control) {
if (locale == null || control == null) {
throw new NullPointerException();
}
! // We create a CacheKey here for use by this call. The base name
// loader, and module will never change during the bundle loading
! // process. We have to make sure that the locale is set before
! // using it as a cache key.
! CacheKey cacheKey = new CacheKey(baseName, locale, loader, module);
! ResourceBundle bundle = null;
// Quick lookup of the cache.
! BundleReference bundleRef = cacheList.get(cacheKey);
if (bundleRef != null) {
bundle = bundleRef.get();
- bundleRef = null;
}
// If this bundle and all of its parents are valid (not expired),
// then return this bundle. If any of the bundles is expired, we
// don't call control.needsReload here but instead drop into the
--- 1477,1496 ----
Control control) {
if (locale == null || control == null) {
throw new NullPointerException();
}
! // We create a LookupSession here for use by this call. The base name
// loader, and module will never change during the bundle loading
! // process.
! LoadSession loadSession = new LoadSession(baseName, locale, loader, module);
// Quick lookup of the cache.
! ResourceBundle bundle = null;
! BundleReference bundleRef = loadSession.getFromCache();
if (bundleRef != null) {
bundle = bundleRef.get();
}
// If this bundle and all of its parents are valid (not expired),
// then return this bundle. If any of the bundles is expired, we
// don't call control.needsReload here but instead drop into the
*** 1659,1669 ****
List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
if (!isKnownControl && !checkList(candidateLocales)) {
throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
}
! bundle = findBundle(cacheKey, module, candidateLocales, formats, 0, control, baseBundle);
// If the loaded bundle is the base bundle and exactly for the
// requested locale or the only candidate locale, then take the
// bundle as the resulting one. If the loaded bundle is the base
// bundle, it's put on hold until we finish processing all
--- 1516,1526 ----
List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
if (!isKnownControl && !checkList(candidateLocales)) {
throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
}
! bundle = findBundle(loadSession, module, candidateLocales, formats, 0, control, baseBundle);
// If the loaded bundle is the base bundle and exactly for the
// requested locale or the only candidate locale, then take the
// bundle as the resulting one. If the loaded bundle is the base
// bundle, it's put on hold until we finish processing all
*** 1685,1695 ****
}
}
if (bundle == null) {
if (baseBundle == null) {
! throwMissingResourceException(baseName, locale, cacheKey.getCause());
}
bundle = baseBundle;
}
return bundle;
--- 1542,1552 ----
}
}
if (bundle == null) {
if (baseBundle == null) {
! throwMissingResourceException(baseName, locale, loadSession.getCause());
}
bundle = baseBundle;
}
return bundle;
*** 1708,1749 ****
}
}
return valid;
}
! private static ResourceBundle findBundle(CacheKey cacheKey,
Module module,
List<Locale> candidateLocales,
List<String> formats,
int index,
Control control,
ResourceBundle baseBundle) {
Locale targetLocale = candidateLocales.get(index);
ResourceBundle parent = null;
if (index != candidateLocales.size() - 1) {
! parent = findBundle(cacheKey, module, candidateLocales, formats, index + 1,
control, baseBundle);
} else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
return baseBundle;
}
// Before we do the real loading work, see whether we need to
! // do some housekeeping: If references to class loaders or
! // resource bundles have been nulled out, remove all related
! // information from the cache.
Object ref;
while ((ref = referenceQueue.poll()) != null) {
! cacheList.remove(((CacheKeyReference)ref).getCacheKey());
}
// flag indicating the resource bundle has expired in the cache
boolean expiredBundle = false;
// First, look up the cache to see if it's in the cache, without
// attempting to load bundle.
! cacheKey.setLocale(targetLocale);
! ResourceBundle bundle = findBundleInCache(cacheKey, control);
if (isValidBundle(bundle)) {
expiredBundle = bundle.expired;
if (!expiredBundle) {
// If its parent is the one asked for by the candidate
// locales (the runtime lookup path), we can take the cached
--- 1565,1605 ----
}
}
return valid;
}
! private static ResourceBundle findBundle(LoadSession loadSession,
Module module,
List<Locale> candidateLocales,
List<String> formats,
int index,
Control control,
ResourceBundle baseBundle) {
Locale targetLocale = candidateLocales.get(index);
ResourceBundle parent = null;
if (index != candidateLocales.size() - 1) {
! parent = findBundle(loadSession, module, candidateLocales, formats, index + 1,
control, baseBundle);
} else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
return baseBundle;
}
// Before we do the real loading work, see whether we need to
! // do some housekeeping: If soft references to resource bundles
! // have been nulled out, remove all related information from the cache.
Object ref;
while ((ref = referenceQueue.poll()) != null) {
! ((BundleReference) ref).remove();
}
// flag indicating the resource bundle has expired in the cache
boolean expiredBundle = false;
// First, look up the cache to see if it's in the cache, without
// attempting to load bundle.
! loadSession.setLocale(targetLocale);
! ResourceBundle bundle = findBundleInCache(loadSession, control);
if (isValidBundle(bundle)) {
expiredBundle = bundle.expired;
if (!expiredBundle) {
// If its parent is the one asked for by the candidate
// locales (the runtime lookup path), we can take the cached
*** 1753,1822 ****
if (bundle.parent == parent) {
return bundle;
}
// Otherwise, remove the cached one since we can't keep
// the same bundles having different parents.
! BundleReference bundleRef = cacheList.get(cacheKey);
if (bundleRef != null && bundleRef.get() == bundle) {
! cacheList.remove(cacheKey, bundleRef);
}
}
}
! if (bundle != NONEXISTENT_BUNDLE) {
! CacheKey constKey = (CacheKey) cacheKey.clone();
!
! try {
if (module.isNamed()) {
! bundle = loadBundle(cacheKey, formats, control, module);
} else {
! bundle = loadBundle(cacheKey, formats, control, expiredBundle);
}
if (bundle != null) {
if (bundle.parent == null) {
bundle.setParent(parent);
}
bundle.locale = targetLocale;
! bundle = putBundleInCache(cacheKey, bundle, control);
return bundle;
}
// Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
// instance for the locale.
! putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control);
! } finally {
! if (constKey.getCause() instanceof InterruptedException) {
! Thread.currentThread().interrupt();
! }
! }
}
return parent;
}
private static final String UNKNOWN_FORMAT = "";
/*
* Loads a ResourceBundle in named modules
*/
! private static ResourceBundle loadBundle(CacheKey cacheKey,
List<String> formats,
Control control,
Module module) {
! String baseName = cacheKey.getName();
! Locale targetLocale = cacheKey.getLocale();
ResourceBundle bundle = null;
! if (cacheKey.hasProviders()) {
bundle = loadBundleFromProviders(baseName, targetLocale,
! cacheKey.getProviders(), cacheKey);
if (bundle != null) {
! cacheKey.setFormat(UNKNOWN_FORMAT);
}
}
// If none of providers returned a bundle and the caller has no provider,
// look up module-local bundles.
! if (bundle == null && !cacheKey.callerHasProvider()) {
String bundleName = control.toBundleName(baseName, targetLocale);
for (String format : formats) {
try {
switch (format) {
case "java.class":
--- 1609,1671 ----
if (bundle.parent == parent) {
return bundle;
}
// Otherwise, remove the cached one since we can't keep
// the same bundles having different parents.
! BundleReference bundleRef = loadSession.getFromCache();
if (bundleRef != null && bundleRef.get() == bundle) {
! bundleRef.remove();
}
}
}
! if (!(bundle instanceof NONEXISTENT_BUNDLE)) {
if (module.isNamed()) {
! bundle = loadBundle(loadSession, formats, control, module);
} else {
! bundle = loadBundle(loadSession, formats, control, expiredBundle);
}
if (bundle != null) {
if (bundle.parent == null) {
bundle.setParent(parent);
}
bundle.locale = targetLocale;
! bundle = putBundleInCache(loadSession, bundle, control);
return bundle;
}
// Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
// instance for the locale.
! putBundleInCache(loadSession, new NONEXISTENT_BUNDLE(), control);
}
return parent;
}
private static final String UNKNOWN_FORMAT = "";
/*
* Loads a ResourceBundle in named modules
*/
! private static ResourceBundle loadBundle(LoadSession loadSession,
List<String> formats,
Control control,
Module module) {
! String baseName = loadSession.getName();
! Locale targetLocale = loadSession.getLocale();
ResourceBundle bundle = null;
! Iterable<ResourceBundleProvider> providers = loadSession.getProviders();
! if (providers != NO_PROVIDERS) {
bundle = loadBundleFromProviders(baseName, targetLocale,
! providers, loadSession);
if (bundle != null) {
! bundle.format = UNKNOWN_FORMAT;
}
}
// If none of providers returned a bundle and the caller has no provider,
// look up module-local bundles.
! if (bundle == null && !loadSession.callerHasProvider()) {
String bundleName = control.toBundleName(baseName, targetLocale);
for (String format : formats) {
try {
switch (format) {
case "java.class":
*** 1831,1866 ****
default:
throw new InternalError("unexpected format: " + format);
}
if (bundle != null) {
! cacheKey.setFormat(format);
break;
}
} catch (Exception e) {
! cacheKey.setCause(e);
}
}
}
return bundle;
}
! private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
String baseName) {
if (!module.isNamed()) {
! return null;
}
PrivilegedAction<ClassLoader> pa = module::getClassLoader;
ClassLoader loader = AccessController.doPrivileged(pa);
! return getServiceLoader(module, loader, baseName);
}
/**
* Returns a ServiceLoader that will find providers that are bound to
! * a given module that may be named or unnamed.
*/
! private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
ClassLoader loader,
String baseName)
{
// Look up <baseName> + "Provider"
String providerName = baseName + "Provider";
--- 1680,1722 ----
default:
throw new InternalError("unexpected format: " + format);
}
if (bundle != null) {
! bundle.format = format;
break;
}
} catch (Exception e) {
! loadSession.setCause(e);
}
}
}
return bundle;
}
! // An instance that signals getting providers for unnamed module or
! // inability to get providers as opposed to successfully getting 0 providers
! // from a named module.
! private static final Iterable<ResourceBundleProvider> NO_PROVIDERS =
! Collections.emptyList();
!
! private static Iterable<ResourceBundleProvider> getProviders(Module module,
String baseName) {
if (!module.isNamed()) {
! return NO_PROVIDERS;
}
PrivilegedAction<ClassLoader> pa = module::getClassLoader;
ClassLoader loader = AccessController.doPrivileged(pa);
! return getProviders(module, loader, baseName);
}
/**
* Returns a ServiceLoader that will find providers that are bound to
! * a given module that may be named or unnamed or GET_PROVIDERS_FAILED instance
! * if unsuccessful.
*/
! private static Iterable<ResourceBundleProvider> getProviders(Module module,
ClassLoader loader,
String baseName)
{
// Look up <baseName> + "Provider"
String providerName = baseName + "Provider";
*** 1885,1968 ****
if (service != null && Reflection.verifyModuleAccess(module, service)) {
try {
return ServiceLoader.load(service, loader, module);
} catch (ServiceConfigurationError e) {
// "uses" not declared: load bundle local in the module
- return null;
}
}
! return null;
}
/**
* Loads ResourceBundle from service providers.
*/
private static ResourceBundle loadBundleFromProviders(String baseName,
Locale locale,
! ServiceLoader<ResourceBundleProvider> providers,
! CacheKey cacheKey)
{
! if (providers == null) return null;
!
return AccessController.doPrivileged(
new PrivilegedAction<>() {
public ResourceBundle run() {
! for (Iterator<ResourceBundleProvider> itr = providers.iterator(); itr.hasNext(); ) {
try {
! ResourceBundleProvider provider = itr.next();
! if (cacheKey != null && cacheKey.callerHasProvider == null
! && cacheKey.getModule() == provider.getClass().getModule()) {
! cacheKey.callerHasProvider = Boolean.TRUE;
}
ResourceBundle bundle = provider.getBundle(baseName, locale);
if (bundle != null) {
return bundle;
}
} catch (ServiceConfigurationError | SecurityException e) {
! if (cacheKey != null) {
! cacheKey.setCause(e);
}
}
! }
! if (cacheKey != null && cacheKey.callerHasProvider == null) {
! cacheKey.callerHasProvider = Boolean.FALSE;
}
return null;
}
});
}
/*
* Legacy mechanism to load resource bundles
*/
! private static ResourceBundle loadBundle(CacheKey cacheKey,
List<String> formats,
Control control,
boolean reload) {
// Here we actually load the bundle in the order of formats
// specified by the getFormats() value.
! Locale targetLocale = cacheKey.getLocale();
ResourceBundle bundle = null;
for (String format : formats) {
try {
// ResourceBundle.Control.newBundle may be overridden
! bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
! cacheKey.getLoader(), reload);
} catch (LinkageError | Exception error) {
// We need to handle the LinkageError case due to
// inconsistent case-sensitivity in ClassLoader.
// See 6572242 for details.
! cacheKey.setCause(error);
}
if (bundle != null) {
// Set the format in the cache key so that it can be
// used when calling needsReload later.
! cacheKey.setFormat(format);
! bundle.name = cacheKey.getName();
bundle.locale = targetLocale;
// Bundle provider might reuse instances. So we should make
// sure to clear the expired flag here.
bundle.expired = false;
break;
--- 1741,1820 ----
if (service != null && Reflection.verifyModuleAccess(module, service)) {
try {
return ServiceLoader.load(service, loader, module);
} catch (ServiceConfigurationError e) {
// "uses" not declared: load bundle local in the module
}
}
! return NO_PROVIDERS;
}
/**
* Loads ResourceBundle from service providers.
*/
private static ResourceBundle loadBundleFromProviders(String baseName,
Locale locale,
! Iterable<ResourceBundleProvider> providers,
! LoadSession loadSession)
{
! // assert loadSession != null && providers != null;
return AccessController.doPrivileged(
new PrivilegedAction<>() {
public ResourceBundle run() {
! for (ResourceBundleProvider provider : providers) {
try {
! if (loadSession.callerHasProvider == null &&
! loadSession.getModule() == provider.getClass().getModule())
! {
! loadSession.callerHasProvider = Boolean.TRUE;
}
ResourceBundle bundle = provider.getBundle(baseName, locale);
if (bundle != null) {
return bundle;
}
} catch (ServiceConfigurationError | SecurityException e) {
! loadSession.setCause(e);
}
}
! if (loadSession.callerHasProvider == null) {
! loadSession.callerHasProvider = Boolean.FALSE;
}
return null;
}
});
}
/*
* Legacy mechanism to load resource bundles
*/
! private static ResourceBundle loadBundle(LoadSession loadSession,
List<String> formats,
Control control,
boolean reload) {
// Here we actually load the bundle in the order of formats
// specified by the getFormats() value.
! Locale targetLocale = loadSession.getLocale();
ResourceBundle bundle = null;
for (String format : formats) {
try {
// ResourceBundle.Control.newBundle may be overridden
! bundle = control.newBundle(loadSession.getName(), targetLocale, format,
! loadSession.getLoader(), reload);
} catch (LinkageError | Exception error) {
// We need to handle the LinkageError case due to
// inconsistent case-sensitivity in ClassLoader.
// See 6572242 for details.
! loadSession.setCause(error);
}
if (bundle != null) {
// Set the format in the cache key so that it can be
// used when calling needsReload later.
! bundle.format = format;
! bundle.name = loadSession.getName();
bundle.locale = targetLocale;
// Bundle provider might reuse instances. So we should make
// sure to clear the expired flag here.
bundle.expired = false;
break;
*** 1971,1981 ****
return bundle;
}
private static boolean isValidBundle(ResourceBundle bundle) {
! return bundle != null && bundle != NONEXISTENT_BUNDLE;
}
/**
* Determines whether any of resource bundles in the parent chain,
* including the leaf, have expired.
--- 1823,1833 ----
return bundle;
}
private static boolean isValidBundle(ResourceBundle bundle) {
! return bundle != null && !(bundle instanceof NONEXISTENT_BUNDLE);
}
/**
* Determines whether any of resource bundles in the parent chain,
* including the leaf, have expired.
*** 1984,2000 ****
long now = System.currentTimeMillis();
while (bundle != null) {
if (bundle.expired) {
return false;
}
! CacheKey key = bundle.cacheKey;
! if (key != null) {
! long expirationTime = key.expirationTime;
if (expirationTime >= 0 && expirationTime <= now) {
return false;
}
- }
bundle = bundle.parent;
}
return true;
}
--- 1836,1849 ----
long now = System.currentTimeMillis();
while (bundle != null) {
if (bundle.expired) {
return false;
}
! long expirationTime = bundle.expirationTime;
if (expirationTime >= 0 && expirationTime <= now) {
return false;
}
bundle = bundle.parent;
}
return true;
}
*** 2018,2045 ****
/**
* Finds a bundle in the cache. Any expired bundles are marked as
* `expired' and removed from the cache upon return.
*
! * @param cacheKey the key to look up the cache
* @param control the Control to be used for the expiration control
* @return the cached bundle, or null if the bundle is not found in the
* cache or its parent has expired. <code>bundle.expire</code> is true
* upon return if the bundle in the cache has expired.
*/
! private static ResourceBundle findBundleInCache(CacheKey cacheKey,
Control control) {
! BundleReference bundleRef = cacheList.get(cacheKey);
if (bundleRef == null) {
return null;
}
ResourceBundle bundle = bundleRef.get();
if (bundle == null) {
return null;
}
ResourceBundle p = bundle.parent;
! assert p != NONEXISTENT_BUNDLE;
// If the parent has expired, then this one must also expire. We
// check only the immediate parent because the actual loading is
// done from the root (base) to leaf (child) and the purpose of
// checking is to propagate expiration towards the leaf. For
// example, if the requested locale is ja_JP_JP and there are
--- 1867,1894 ----
/**
* Finds a bundle in the cache. Any expired bundles are marked as
* `expired' and removed from the cache upon return.
*
! * @param loadSession the load session used look up the cache
* @param control the Control to be used for the expiration control
* @return the cached bundle, or null if the bundle is not found in the
* cache or its parent has expired. <code>bundle.expire</code> is true
* upon return if the bundle in the cache has expired.
*/
! private static ResourceBundle findBundleInCache(LoadSession loadSession,
Control control) {
! BundleReference bundleRef = loadSession.getFromCache();
if (bundleRef == null) {
return null;
}
ResourceBundle bundle = bundleRef.get();
if (bundle == null) {
return null;
}
ResourceBundle p = bundle.parent;
! assert !(p instanceof NONEXISTENT_BUNDLE);
// If the parent has expired, then this one must also expire. We
// check only the immediate parent because the actual loading is
// done from the root (base) to leaf (child) and the purpose of
// checking is to propagate expiration towards the leaf. For
// example, if the requested locale is ja_JP_JP and there are
*** 2070,2184 ****
// We could check the entire parent chain to see if there's any in
// the chain that has expired. But this process may never end. An
// extreme case would be that getTimeToLive returns 0 and
// needsReload always returns true.
if (p != null && p.expired) {
! assert bundle != NONEXISTENT_BUNDLE;
bundle.expired = true;
! bundle.cacheKey = null;
! cacheList.remove(cacheKey, bundleRef);
bundle = null;
} else {
! CacheKey key = bundleRef.getCacheKey();
! long expirationTime = key.expirationTime;
if (!bundle.expired && expirationTime >= 0 &&
expirationTime <= System.currentTimeMillis()) {
// its TTL period has expired.
! if (bundle != NONEXISTENT_BUNDLE) {
// Synchronize here to call needsReload to avoid
// redundant concurrent calls for the same bundle.
synchronized (bundle) {
! expirationTime = key.expirationTime;
if (!bundle.expired && expirationTime >= 0 &&
expirationTime <= System.currentTimeMillis()) {
try {
! bundle.expired = control.needsReload(key.getName(),
! key.getLocale(),
! key.getFormat(),
! key.getLoader(),
bundle,
! key.loadTime);
} catch (Exception e) {
! cacheKey.setCause(e);
}
if (bundle.expired) {
// If the bundle needs to be reloaded, then
// remove the bundle from the cache, but
// return the bundle with the expired flag
// on.
! bundle.cacheKey = null;
! cacheList.remove(cacheKey, bundleRef);
} else {
// Update the expiration control info. and reuse
// the same bundle instance
! setExpirationTime(key, control);
}
}
}
} else {
// We just remove NONEXISTENT_BUNDLE from the cache.
! cacheList.remove(cacheKey, bundleRef);
bundle = null;
}
}
}
return bundle;
}
/**
* Put a new bundle in the cache.
*
! * @param cacheKey the key for the resource bundle
* @param bundle the resource bundle to be put in the cache
* @return the ResourceBundle for the cacheKey; if someone has put
* the bundle before this call, the one found in the cache is
* returned.
*/
! private static ResourceBundle putBundleInCache(CacheKey cacheKey,
ResourceBundle bundle,
Control control) {
! setExpirationTime(cacheKey, control);
! if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) {
! CacheKey key = (CacheKey) cacheKey.clone();
! BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key);
! bundle.cacheKey = key;
// Put the bundle in the cache if it's not been in the cache.
! BundleReference result = cacheList.putIfAbsent(key, bundleRef);
// If someone else has put the same bundle in the cache before
// us and it has not expired, we should use the one in the cache.
! if (result != null) {
! ResourceBundle rb = result.get();
if (rb != null && !rb.expired) {
! // Clear the back link to the cache key
! bundle.cacheKey = null;
! bundle = rb;
! // Clear the reference in the BundleReference so that
! // it won't be enqueued.
! bundleRef.clear();
} else {
! // Replace the invalid (garbage collected or expired)
// instance with the valid one.
! cacheList.put(key, bundleRef);
}
}
}
return bundle;
}
! private static void setExpirationTime(CacheKey cacheKey, Control control) {
! long ttl = control.getTimeToLive(cacheKey.getName(),
! cacheKey.getLocale());
if (ttl >= 0) {
// If any expiration time is specified, set the time to be
// expired in the cache.
long now = System.currentTimeMillis();
! cacheKey.loadTime = now;
! cacheKey.expirationTime = now + ttl;
} else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
! cacheKey.expirationTime = ttl;
} else {
throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
}
}
--- 1919,2035 ----
// We could check the entire parent chain to see if there's any in
// the chain that has expired. But this process may never end. An
// extreme case would be that getTimeToLive returns 0 and
// needsReload always returns true.
if (p != null && p.expired) {
! assert !(bundle instanceof NONEXISTENT_BUNDLE);
bundle.expired = true;
! bundleRef.remove();
bundle = null;
} else {
! long expirationTime = bundle.expirationTime;
if (!bundle.expired && expirationTime >= 0 &&
expirationTime <= System.currentTimeMillis()) {
// its TTL period has expired.
! if (!(bundle instanceof NONEXISTENT_BUNDLE)) {
// Synchronize here to call needsReload to avoid
// redundant concurrent calls for the same bundle.
synchronized (bundle) {
! expirationTime = bundle.expirationTime;
if (!bundle.expired && expirationTime >= 0 &&
expirationTime <= System.currentTimeMillis()) {
try {
! bundle.expired = control.needsReload(bundle.getBaseBundleName(),
! bundle.getLocale(),
! bundle.format,
! loadSession.getLoader(),
bundle,
! bundle.loadTime);
} catch (Exception e) {
! loadSession.setCause(e);
}
if (bundle.expired) {
// If the bundle needs to be reloaded, then
// remove the bundle from the cache, but
// return the bundle with the expired flag
// on.
! bundleRef.remove();
} else {
// Update the expiration control info. and reuse
// the same bundle instance
! setExpirationTime(bundle, loadSession, control);
}
}
}
} else {
// We just remove NONEXISTENT_BUNDLE from the cache.
! bundleRef.remove();
bundle = null;
}
}
}
return bundle;
}
/**
* Put a new bundle in the cache.
*
! * @param loadSession the key for the resource bundle
* @param bundle the resource bundle to be put in the cache
* @return the ResourceBundle for the cacheKey; if someone has put
* the bundle before this call, the one found in the cache is
* returned.
*/
! private static ResourceBundle putBundleInCache(LoadSession loadSession,
ResourceBundle bundle,
Control control) {
! setExpirationTime(bundle, loadSession, control);
! if (bundle.expirationTime != Control.TTL_DONT_CACHE) {
! ClassLoaderValue<BundleReference>.Sub<Module>.Sub<String>.Sub<Locale> key
! = loadSession.key();
! ClassLoader loader = loadSession.getLoader();
! BundleReference bundleRef = new BundleReference(
! bundle, referenceQueue, loader, key);
// Put the bundle in the cache if it's not been in the cache.
! BundleReference oldBundleRef = key.putIfAbsent(loader, bundleRef);
// If someone else has put the same bundle in the cache before
// us and it has not expired, we should use the one in the cache.
! while (oldBundleRef != null) {
! ResourceBundle rb = oldBundleRef.get();
if (rb != null && !rb.expired) {
! return rb;
} else {
! // Try to replace the invalid (garbage collected or expired)
// instance with the valid one.
! if (key.replace(loader, oldBundleRef, bundleRef)) {
! break;
! } else {
! // Someone else must have already replaced it or it was
! // removed. Retry putting the bundle in the cache.
! oldBundleRef = key.putIfAbsent(loader, bundleRef);
! }
}
}
}
return bundle;
}
! private static void setExpirationTime(ResourceBundle bundle,
! LoadSession loadSession,
! Control control) {
! long ttl = control.getTimeToLive(loadSession.getName(),
! loadSession.getLocale());
if (ttl >= 0) {
// If any expiration time is specified, set the time to be
// expired in the cache.
long now = System.currentTimeMillis();
! bundle.loadTime = now;
! bundle.expirationTime = now + ttl;
} else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
! bundle.expirationTime = ttl;
} else {
throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
}
}
*** 2227,2239 ****
public static final void clearCache(Module module) {
clearCache(module.getClassLoader(), module);
}
private static void clearCache(ClassLoader loader, Module module) {
! Set<CacheKey> set = cacheList.keySet();
! set.stream().filter((key) -> (key.getLoader() == loader && key.getModule() == module))
! .forEach(set::remove);
}
/**
* Gets an object for the given key from this resource bundle.
* Returns null if this resource bundle does not contain an
--- 2078,2088 ----
public static final void clearCache(Module module) {
clearCache(module.getClassLoader(), module);
}
private static void clearCache(ClassLoader loader, Module module) {
! cache.sub(module).removeAll(loader);
}
/**
* Gets an object for the given key from this resource bundle.
* Returns null if this resource bundle does not contain an
< prev index next >