--- old/src/java.base/share/classes/java/util/ResourceBundle.java 2016-08-01 14:44:10.749485125 +0200 +++ new/src/java.base/share/classes/java/util/ResourceBundle.java 2016-08-01 14:44:10.681484603 +0200 @@ -44,7 +44,6 @@ 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; @@ -56,8 +55,6 @@ 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; @@ -66,6 +63,8 @@ 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; @@ -346,9 +345,6 @@ */ public abstract class ResourceBundle { - /** initial size of the bundle cache */ - private static final int INITIAL_CACHE_SIZE = 32; - static { SharedSecrets.setJavaUtilResourceBundleAccess( new JavaUtilResourceBundleAccess() { @@ -375,28 +371,22 @@ }); } - /** constant indicating that no resource bundle exists */ - private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() { - public Enumeration getKeys() { return null; } - protected Object handleGetObject(String key) { return null; } - public String toString() { return "NONEXISTENT_BUNDLE"; } - }; + /** + * special bundle type indicating that no resource bundle exists + */ + private static final class NONEXISTENT_BUNDLE extends ResourceBundle { + public Enumeration 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, module, bundle base name + * and locale. */ - private static final ConcurrentMap cacheList - = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE); + private static final ClassLoaderValue cache + = new ClassLoaderValue<>(); /** * Queue for reference objects referring to class loaders or bundles. @@ -426,12 +416,12 @@ * The parent bundle is searched by {@link #getObject getObject} * when this bundle does not contain a particular resource. */ - protected ResourceBundle parent = null; + protected ResourceBundle parent; /** * The locale for this bundle. */ - private Locale locale = null; + private Locale locale; /** * The base bundle name for this bundle. @@ -439,15 +429,26 @@ 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 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 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 CacheKey cacheKey; + private volatile long expirationTime; /** * A Set of the keys contained only in this ResourceBundle. @@ -618,76 +619,55 @@ * @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. - * 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. + * 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; - private KeyElementReference loaderRef; - private KeyElementReference 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; + // compose and return the cache key + ClassLoaderValue.Sub.Sub.Sub key() { + return cache.sub(module).sub(name).sub(locale); + } - // 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; + // 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; - // Hash code value cache to avoid recalculating the hash code - // of this instance. - private int hashCodeCache; - // ResourceBundleProviders for loading ResourceBundles - private ServiceLoader providers; - private boolean providersChecked; + private Iterable 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; } @@ -695,126 +675,30 @@ 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 getProviders() { - if (!providersChecked) { - providers = getServiceLoader(getModule(), name); - providersChecked = true; + Iterable 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) { - this.format = format; - } - private void setCause(Throwable cause) { if (this.cause == null) { this.cause = cause; @@ -841,54 +725,29 @@ 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 extends WeakReference - implements CacheKeyReference { - private final CacheKey cacheKey; - - KeyElementReference(T referent, ReferenceQueue q, CacheKey key) { - super(referent, q); - cacheKey = key; - } - - @Override - public CacheKey getCacheKey() { - return cacheKey; + return "LookupSession[" + name + ", lc=" + l + ", ldr=" + getLoader() + + "]"; } } /** - * 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 - implements CacheKeyReference { - private final CacheKey cacheKey; + private static class BundleReference extends SoftReference { + private final ClassLoader classLoader; + private final AbstractClassLoaderValue.Sub key; - BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) { + BundleReference(ResourceBundle referent, ReferenceQueue q, + ClassLoader classLoader, + AbstractClassLoaderValue.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); } } @@ -1620,18 +1479,16 @@ 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), @@ -1661,7 +1518,7 @@ 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 @@ -1687,7 +1544,7 @@ if (bundle == null) { if (baseBundle == null) { - throwMissingResourceException(baseName, locale, cacheKey.getCause()); + throwMissingResourceException(baseName, locale, loadSession.getCause()); } bundle = baseBundle; } @@ -1710,7 +1567,7 @@ return valid; } - private static ResourceBundle findBundle(CacheKey cacheKey, + private static ResourceBundle findBundle(LoadSession loadSession, Module module, List candidateLocales, List formats, @@ -1720,19 +1577,18 @@ 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 @@ -1740,8 +1596,8 @@ // 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) { @@ -1755,39 +1611,31 @@ } // 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 (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(); + 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; } @@ -1797,24 +1645,25 @@ /* * Loads a ResourceBundle in named modules */ - private static ResourceBundle loadBundle(CacheKey cacheKey, + private static ResourceBundle loadBundle(LoadSession loadSession, List 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()) { - bundle = loadBundleFromProviders(baseName, targetLocale, - cacheKey.getProviders(), cacheKey); + Iterable providers = loadSession.getProviders(); + if (providers != NO_PROVIDERS) { + bundle = loadBundleFromProviders(baseName, targetLocale, + providers, loadSession); if (bundle != null) { - cacheKey.setFormat(UNKNOWN_FORMAT); + 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 && !cacheKey.callerHasProvider()) { + if (bundle == null && !loadSession.callerHasProvider()) { String bundleName = control.toBundleName(baseName, targetLocale); for (String format : formats) { try { @@ -1833,34 +1682,41 @@ } if (bundle != null) { - cacheKey.setFormat(format); + bundle.format = format; break; } } catch (Exception e) { - cacheKey.setCause(e); + loadSession.setCause(e); } } } return bundle; } - private static ServiceLoader getServiceLoader(Module module, - String baseName) { + // 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 NO_PROVIDERS = + Collections.emptyList(); + + private static Iterable getProviders(Module module, + String baseName) { if (!module.isNamed()) { - return null; + return NO_PROVIDERS; } PrivilegedAction 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. - */ - private static ServiceLoader getServiceLoader(Module module, - ClassLoader loader, - String 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 getProviders(Module module, + ClassLoader loader, + String baseName) { // Look up + "Provider" String providerName = baseName + "Provider"; @@ -1887,10 +1743,9 @@ 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; } /** @@ -1898,33 +1753,30 @@ */ private static ResourceBundle loadBundleFromProviders(String baseName, Locale locale, - ServiceLoader providers, - CacheKey cacheKey) + Iterable providers, + LoadSession loadSession) { - if (providers == null) return null; - + // assert loadSession != null && providers != null; return AccessController.doPrivileged( new PrivilegedAction<>() { public ResourceBundle run() { - for (Iterator 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; } @@ -1935,32 +1787,32 @@ /* * Legacy mechanism to load resource bundles */ - private static ResourceBundle loadBundle(CacheKey cacheKey, + private static ResourceBundle loadBundle(LoadSession loadSession, List 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(); + 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. @@ -1973,7 +1825,7 @@ } private static boolean isValidBundle(ResourceBundle bundle) { - return bundle != null && bundle != NONEXISTENT_BUNDLE; + return bundle != null && !(bundle instanceof NONEXISTENT_BUNDLE); } /** @@ -1986,12 +1838,9 @@ if (bundle.expired) { return false; } - CacheKey key = bundle.cacheKey; - if (key != null) { - long expirationTime = key.expirationTime; - if (expirationTime >= 0 && expirationTime <= now) { - return false; - } + long expirationTime = bundle.expirationTime; + if (expirationTime >= 0 && expirationTime <= now) { + return false; } bundle = bundle.parent; } @@ -2020,15 +1869,15 @@ * 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 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. bundle.expire 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; } @@ -2037,7 +1886,7 @@ 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 @@ -2072,51 +1921,48 @@ // 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(bundle.getBaseBundleName(), + bundle.getLocale(), + bundle.format, + 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; } } @@ -2127,56 +1973,61 @@ /** * 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.Sub.Sub.Sub 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); } @@ -2229,9 +2080,7 @@ } private static void clearCache(ClassLoader loader, Module module) { - Set set = cacheList.keySet(); - set.stream().filter((key) -> (key.getLoader() == loader && key.getModule() == module)) - .forEach(set::remove); + cache.sub(module).removeAll(loader); } /**