< prev index next >
src/java.base/share/classes/java/util/ResourceBundle.java
Print this page
@@ -38,15 +38,24 @@
*
*/
package java.util;
+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 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;
@@ -54,23 +63,14 @@
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;
/**
*
@@ -373,32 +373,25 @@
bundle.name = name;
}
});
}
- /** constant indicating that no resource bundle exists */
- private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
+ /**
+ * 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 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.
+ * The cache of BundleReference(s) per class loader, bundle base name and locale.
*/
- private static final ConcurrentMap<CacheKey, BundleReference> cacheList
- = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
+ 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<>();
@@ -442,14 +435,19 @@
* 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.
+ * The time when the bundle has been loaded
*/
- private volatile CacheKey cacheKey;
+ 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,199 +614,93 @@
* 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;
+ assert !(parent instanceof 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.
+ * A session object used during the {@link #getBundle} call.
* 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 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;
- private KeyElementReference<ClassLoader> loaderRef;
- private KeyElementReference<Module> moduleRef;
+ // 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);
+ }
// 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;
+ private Iterable<ResourceBundleProvider> providers;
// 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();
+ 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;
}
- CacheKey setName(String baseName) {
- if (!this.name.equals(baseName)) {
- this.name = baseName;
- calculateHashCode();
- }
+ LoadSession setName(String baseName) {
+ this.name = Objects.requireNonNull(baseName);
return this;
}
Locale getLocale() {
return locale;
}
- CacheKey setLocale(Locale locale) {
- if (!this.locale.equals(locale)) {
- this.locale = locale;
- calculateHashCode();
- }
+ LoadSession setLocale(Locale locale) {
+ this.locale = Objects.requireNonNull(locale);
return this;
}
ClassLoader getLoader() {
- return (loaderRef != null) ? loaderRef.get() : null;
+ return loader;
}
Module getModule() {
- return moduleRef.get();
+ return module;
}
- ServiceLoader<ResourceBundleProvider> getProviders() {
- if (!providersChecked) {
- providers = getServiceLoader(getModule(), name);
- providersChecked = true;
+ Iterable<ResourceBundleProvider> getProviders() {
+ if (providers == null) {
+ providers = ResourceBundle.getProviders(module, name);
}
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) {
@@ -839,58 +731,33 @@
l = "__" + locale.getVariant();
} else {
l = "\"\"";
}
}
- return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
+ return "LookupSession[" + 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.
+ * 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>
- implements CacheKeyReference {
- private final CacheKey cacheKey;
-
- BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key) {
+ 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);
- cacheKey = key;
+ this.classLoader = classLoader;
+ this.key = key;
}
- @Override
- public CacheKey getCacheKey() {
- return cacheKey;
+ void remove() {
+ key.remove(classLoader, this);
}
}
/**
* Gets a resource bundle using the specified base name, the default locale,
@@ -1618,22 +1485,20 @@
Control control) {
if (locale == null || control == null) {
throw new NullPointerException();
}
- // We create a CacheKey here for use by this call. The base name
+ // We create a LookupSession 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;
+ // process.
+ LoadSession loadSession = new LoadSession(baseName, locale, loader, module);
// Quick lookup of the cache.
- BundleReference bundleRef = cacheList.get(cacheKey);
+ ResourceBundle bundle = null;
+ BundleReference bundleRef = loadSession.getFromCache();
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
@@ -1659,11 +1524,11 @@
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);
+ 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,11 +1550,11 @@
}
}
if (bundle == null) {
if (baseBundle == null) {
- throwMissingResourceException(baseName, locale, cacheKey.getCause());
+ throwMissingResourceException(baseName, locale, loadSession.getCause());
}
bundle = baseBundle;
}
return bundle;
@@ -1708,42 +1573,41 @@
}
}
return valid;
}
- private static ResourceBundle findBundle(CacheKey cacheKey,
+ 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(cacheKey, module, candidateLocales, formats, index + 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 references to class loaders or
- // resource bundles have been nulled out, remove all related
- // information from the cache.
+ // 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) {
- cacheList.remove(((CacheKeyReference)ref).getCacheKey());
+ ((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.
- cacheKey.setLocale(targetLocale);
- ResourceBundle bundle = findBundleInCache(cacheKey, control);
+ 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,70 +1617,63 @@
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);
+ BundleReference bundleRef = loadSession.getFromCache();
if (bundleRef != null && bundleRef.get() == bundle) {
- cacheList.remove(cacheKey, bundleRef);
+ bundleRef.remove();
}
}
}
- if (bundle != NONEXISTENT_BUNDLE) {
- CacheKey constKey = (CacheKey) cacheKey.clone();
-
- try {
+ if (!(bundle instanceof NONEXISTENT_BUNDLE)) {
if (module.isNamed()) {
- bundle = loadBundle(cacheKey, formats, control, module);
+ bundle = loadBundle(loadSession, formats, control, module);
} else {
- bundle = loadBundle(cacheKey, formats, control, expiredBundle);
+ bundle = loadBundle(loadSession, formats, control, expiredBundle);
}
if (bundle != null) {
if (bundle.parent == null) {
bundle.setParent(parent);
}
bundle.locale = targetLocale;
- bundle = putBundleInCache(cacheKey, bundle, control);
+ 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(cacheKey, NONEXISTENT_BUNDLE, control);
- } finally {
- if (constKey.getCause() instanceof InterruptedException) {
- Thread.currentThread().interrupt();
- }
- }
+ putBundleInCache(loadSession, new NONEXISTENT_BUNDLE(), control);
}
return parent;
}
private static final String UNKNOWN_FORMAT = "";
/*
* Loads a ResourceBundle in named modules
*/
- private static ResourceBundle loadBundle(CacheKey cacheKey,
+ private static ResourceBundle loadBundle(LoadSession loadSession,
List<String> formats,
Control control,
Module module) {
- String baseName = cacheKey.getName();
- Locale targetLocale = cacheKey.getLocale();
+ String baseName = loadSession.getName();
+ Locale targetLocale = loadSession.getLocale();
ResourceBundle bundle = null;
- if (cacheKey.hasProviders()) {
+ Iterable<ResourceBundleProvider> providers = loadSession.getProviders();
+ if (providers != NO_PROVIDERS) {
bundle = loadBundleFromProviders(baseName, targetLocale,
- cacheKey.getProviders(), cacheKey);
+ providers, loadSession);
if (bundle != null) {
- cacheKey.setFormat(UNKNOWN_FORMAT);
+ loadSession.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()) {
+ if (bundle == null && !loadSession.callerHasProvider()) {
String bundleName = control.toBundleName(baseName, targetLocale);
for (String format : formats) {
try {
switch (format) {
case "java.class":
@@ -1831,36 +1688,43 @@
default:
throw new InternalError("unexpected format: " + format);
}
if (bundle != null) {
- cacheKey.setFormat(format);
+ loadSession.setFormat(format);
break;
}
} catch (Exception e) {
- cacheKey.setCause(e);
+ loadSession.setCause(e);
}
}
}
return bundle;
}
- private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
+ // 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 null;
+ return NO_PROVIDERS;
}
PrivilegedAction<ClassLoader> pa = module::getClassLoader;
ClassLoader loader = AccessController.doPrivileged(pa);
- return getServiceLoader(module, loader, baseName);
+ 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.
+ * a given module that may be named or unnamed or GET_PROVIDERS_FAILED instance
+ * if unsuccessful.
*/
- private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
+ private static Iterable<ResourceBundleProvider> getProviders(Module module,
ClassLoader loader,
String baseName)
{
// Look up <baseName> + "Provider"
String providerName = baseName + "Provider";
@@ -1885,84 +1749,80 @@
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;
+ return NO_PROVIDERS;
}
/**
* Loads ResourceBundle from service providers.
*/
private static ResourceBundle loadBundleFromProviders(String baseName,
Locale locale,
- ServiceLoader<ResourceBundleProvider> providers,
- CacheKey cacheKey)
+ Iterable<ResourceBundleProvider> providers,
+ LoadSession loadSession)
{
- if (providers == null) return null;
-
+ // assert cacheKey != null && providers != null;
return AccessController.doPrivileged(
new PrivilegedAction<>() {
public ResourceBundle run() {
- for (Iterator<ResourceBundleProvider> itr = providers.iterator(); itr.hasNext(); ) {
+ for (ResourceBundleProvider provider : providers) {
try {
- ResourceBundleProvider provider = itr.next();
- if (cacheKey != null && cacheKey.callerHasProvider == null
- && cacheKey.getModule() == provider.getClass().getModule()) {
- cacheKey.callerHasProvider = Boolean.TRUE;
+ 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) {
- if (cacheKey != null) {
- cacheKey.setCause(e);
+ loadSession.setCause(e);
}
}
- }
- if (cacheKey != null && cacheKey.callerHasProvider == null) {
- cacheKey.callerHasProvider = Boolean.FALSE;
+ if (loadSession.callerHasProvider == null) {
+ loadSession.callerHasProvider = Boolean.FALSE;
}
return null;
}
});
}
/*
* Legacy mechanism to load resource bundles
*/
- private static ResourceBundle loadBundle(CacheKey cacheKey,
+ 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 = cacheKey.getLocale();
+ Locale targetLocale = loadSession.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);
+ 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.
- cacheKey.setCause(error);
+ loadSession.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();
+ loadSession.setFormat(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,11 +1831,11 @@
return bundle;
}
private static boolean isValidBundle(ResourceBundle bundle) {
- return bundle != null && bundle != NONEXISTENT_BUNDLE;
+ return bundle != null && !(bundle instanceof NONEXISTENT_BUNDLE);
}
/**
* Determines whether any of resource bundles in the parent chain,
* including the leaf, have expired.
@@ -1984,17 +1844,14 @@
long now = System.currentTimeMillis();
while (bundle != null) {
if (bundle.expired) {
return false;
}
- CacheKey key = bundle.cacheKey;
- if (key != null) {
- long expirationTime = key.expirationTime;
+ long expirationTime = bundle.expirationTime;
if (expirationTime >= 0 && expirationTime <= now) {
return false;
}
- }
bundle = bundle.parent;
}
return true;
}
@@ -2018,28 +1875,28 @@
/**
* 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 loadSession 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,
+ private static ResourceBundle findBundleInCache(LoadSession loadSession,
Control control) {
- BundleReference bundleRef = cacheList.get(cacheKey);
+ BundleReference bundleRef = loadSession.getFromCache();
if (bundleRef == null) {
return null;
}
ResourceBundle bundle = bundleRef.get();
if (bundle == null) {
return null;
}
ResourceBundle p = bundle.parent;
- assert p != NONEXISTENT_BUNDLE;
+ 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,115 +1927,117 @@
// 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;
+ assert !(bundle instanceof NONEXISTENT_BUNDLE);
bundle.expired = true;
- bundle.cacheKey = null;
- cacheList.remove(cacheKey, bundleRef);
+ bundleRef.remove();
bundle = null;
} else {
- CacheKey key = bundleRef.getCacheKey();
- long expirationTime = key.expirationTime;
+ long expirationTime = bundle.expirationTime;
if (!bundle.expired && expirationTime >= 0 &&
expirationTime <= System.currentTimeMillis()) {
// its TTL period has expired.
- if (bundle != NONEXISTENT_BUNDLE) {
+ if (!(bundle instanceof NONEXISTENT_BUNDLE)) {
// Synchronize here to call needsReload to avoid
// redundant concurrent calls for the same bundle.
synchronized (bundle) {
- expirationTime = key.expirationTime;
+ expirationTime = bundle.expirationTime;
if (!bundle.expired && expirationTime >= 0 &&
expirationTime <= System.currentTimeMillis()) {
try {
- bundle.expired = control.needsReload(key.getName(),
- key.getLocale(),
- key.getFormat(),
- key.getLoader(),
+ bundle.expired = control.needsReload(loadSession.getName(),
+ loadSession.getLocale(),
+ loadSession.getFormat(),
+ loadSession.getLoader(),
bundle,
- key.loadTime);
+ bundle.loadTime);
} catch (Exception e) {
- cacheKey.setCause(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.
- bundle.cacheKey = null;
- cacheList.remove(cacheKey, bundleRef);
+ bundleRef.remove();
} else {
// Update the expiration control info. and reuse
// the same bundle instance
- setExpirationTime(key, control);
+ setExpirationTime(bundle, loadSession, control);
}
}
}
} else {
// We just remove NONEXISTENT_BUNDLE from the cache.
- cacheList.remove(cacheKey, bundleRef);
+ bundleRef.remove();
bundle = null;
}
}
}
return bundle;
}
/**
* Put a new bundle in the cache.
*
- * @param cacheKey the key for the resource bundle
+ * @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(CacheKey cacheKey,
+ private static ResourceBundle putBundleInCache(LoadSession loadSession,
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;
+ 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 result = cacheList.putIfAbsent(key, bundleRef);
+ 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.
- if (result != null) {
- ResourceBundle rb = result.get();
+ while (oldBundleRef != null) {
+ ResourceBundle rb = oldBundleRef.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();
+ return rb;
} else {
- // Replace the invalid (garbage collected or expired)
+ // Try to replace the invalid (garbage collected or expired)
// instance with the valid one.
- cacheList.put(key, bundleRef);
+ 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(CacheKey cacheKey, Control control) {
- long ttl = control.getTimeToLive(cacheKey.getName(),
- cacheKey.getLocale());
+ 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();
- cacheKey.loadTime = now;
- cacheKey.expirationTime = now + ttl;
+ bundle.loadTime = now;
+ bundle.expirationTime = now + ttl;
} else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
- cacheKey.expirationTime = ttl;
+ bundle.expirationTime = ttl;
} else {
throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
}
}
@@ -2227,13 +2086,11 @@
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);
+ 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 >