--- old/src/java.base/share/classes/java/lang/ClassLoader.java 2016-04-08 10:43:26.696655166 +0200 +++ new/src/java.base/share/classes/java/lang/ClassLoader.java 2016-04-08 10:43:26.594654602 +0200 @@ -2625,6 +2625,25 @@ // the ServiceCatalog for modules associated with this class loader. private volatile ServicesCatalog servicesCatalog; + /** + * Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s) + * associated with this ClassLoader, creating it if it doesn't already exist. + */ + ConcurrentHashMap createOrGetClassLoaderValueMap() { + ConcurrentHashMap map = classLoaderValueMap; + if (map == null) { + map = new ConcurrentHashMap<>(); + boolean set = trySetObjectField("classLoaderValueMap", map); + if (!set) { + // beaten by someone else + map = classLoaderValueMap; + } + } + return map; + } + + // the storage for ClassLoaderValue(s) associated with this ClassLoader + private volatile ConcurrentHashMap classLoaderValueMap; /** * Attempts to atomically set a volatile field in this object. Returns --- old/src/java.base/share/classes/java/lang/System.java 2016-04-08 10:43:26.952656581 +0200 +++ new/src/java.base/share/classes/java/lang/System.java 2016-04-08 10:43:26.857656056 +0200 @@ -49,6 +49,7 @@ import java.security.PrivilegedAction; import java.nio.channels.Channel; import java.nio.channels.spi.SelectorProvider; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import java.util.Objects; @@ -2026,6 +2027,9 @@ public ServicesCatalog createOrGetServicesCatalog(ClassLoader cl) { return cl.createOrGetServicesCatalog(); } + public ConcurrentHashMap createOrGetClassLoaderValueMap(ClassLoader cl) { + return cl.createOrGetClassLoaderValueMap(); + } public Class findBootstrapClassOrNull(ClassLoader cl, String name) { return cl.findBootstrapClassOrNull(name); } --- old/src/java.base/share/classes/java/lang/reflect/Proxy.java 2016-04-08 10:43:27.199657947 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/Proxy.java 2016-04-08 10:43:27.105657427 +0200 @@ -25,7 +25,6 @@ package java.lang.reflect; -import java.lang.ref.WeakReference; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; @@ -39,10 +38,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.BiFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -284,6 +281,13 @@ { InvocationHandler.class }; /** + * a cache of proxy constructors with + * {@link Constructor#setAccessible(boolean) accessible} flag already set + */ + private static final ClassLoaderValue> proxyCache = + new ClassLoaderValue<>(); + + /** * the invocation handler for this proxy instance. * @serial */ @@ -361,14 +365,41 @@ Class... interfaces) throws IllegalArgumentException { - final List> intfs = List.of(interfaces); // interfaces cloned - final SecurityManager sm = System.getSecurityManager(); - final Class caller = Reflection.getCallerClass(); - if (sm != null) { - checkProxyAccess(caller, loader, intfs); - } + Class caller = System.getSecurityManager() == null + ? null + : Reflection.getCallerClass(); + + return getProxyConstructor(caller, loader, interfaces) + .getDeclaringClass(); + } - return new ProxyBuilder(loader, intfs).build(); + private static Constructor getProxyConstructor(Class caller, // null if no SecurityManager + ClassLoader loader, + Class... interfaces) + throws IllegalArgumentException + { + // optimization for single interface + if (interfaces.length == 1) { + Class intf = interfaces[0]; + if (caller != null) { + checkProxyAccess(caller, loader, intf); + } + return proxyCache.sub(intf).computeIfAbsent( + loader, + (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() + ); + } else { + // interfaces cloned + final Class[] intfsArray = interfaces.clone(); + if (caller != null) { + checkProxyAccess(caller, loader, intfsArray); + } + final List> intfs = Arrays.asList(intfsArray); + return proxyCache.sub(intfs).computeIfAbsent( + loader, + (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() + ); + } } /* @@ -391,7 +422,7 @@ */ private static void checkProxyAccess(Class caller, ClassLoader loader, - List> interfaces) + Class ... interfaces) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { @@ -399,147 +430,18 @@ if (VM.isSystemDomainLoader(loader) && !VM.isSystemDomainLoader(ccl)) { sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); } - ReflectUtil.checkProxyPackageAccess(ccl, interfaces.toArray(EMPTY_CLASS_ARRAY)); - } - } - - /* - * a key used for proxy class with 0 implemented interfaces - */ - private static final Object key0 = new Object(); - - /* - * Key1 and Key2 are optimized for the common use of dynamic proxies - * that implement 1 or 2 interfaces. - */ - - /* - * a key used for proxy class with 1 implemented interface - */ - private static final class Key1 extends WeakReference> { - private final int hash; - - Key1(Class intf) { - super(intf); - this.hash = intf.hashCode(); - } - - @Override - public int hashCode() { - return hash; - } - - @Override - public boolean equals(Object obj) { - Class intf; - return this == obj || - obj != null && - obj.getClass() == Key1.class && - (intf = get()) != null && - intf == ((Key1) obj).get(); - } - } - - /* - * a key used for proxy class with 2 implemented interfaces - */ - private static final class Key2 extends WeakReference> { - private final int hash; - private final WeakReference> ref2; - - Key2(Class intf1, Class intf2) { - super(intf1); - hash = 31 * intf1.hashCode() + intf2.hashCode(); - ref2 = new WeakReference<>(intf2); - } - - @Override - public int hashCode() { - return hash; - } - - @Override - public boolean equals(Object obj) { - Class intf1, intf2; - return this == obj || - obj != null && - obj.getClass() == Key2.class && - (intf1 = get()) != null && - intf1 == ((Key2) obj).get() && - (intf2 = ref2.get()) != null && - intf2 == ((Key2) obj).ref2.get(); - } - } - - /* - * a key used for proxy class with any number of implemented interfaces - * (used here for 3 or more only) - */ - private static final class KeyX { - private final int hash; - private final WeakReference>[] refs; - - @SuppressWarnings("unchecked") - KeyX(List> interfaces) { - hash = Arrays.hashCode(interfaces.toArray()); - refs = (WeakReference>[])new WeakReference[interfaces.size()]; - int i = 0; - for (Class intf : interfaces) { - refs[i++] = new WeakReference<>(intf); - } - } - - @Override - public int hashCode() { - return hash; - } - - @Override - public boolean equals(Object obj) { - return this == obj || - obj != null && - obj.getClass() == KeyX.class && - equals(refs, ((KeyX) obj).refs); - } - - private static boolean equals(WeakReference>[] refs1, - WeakReference>[] refs2) { - if (refs1.length != refs2.length) { - return false; - } - for (int i = 0; i < refs1.length; i++) { - Class intf = refs1[i].get(); - if (intf == null || intf != refs2[i].get()) { - return false; - } - } - return true; + ReflectUtil.checkProxyPackageAccess(ccl, interfaces); } } /** - * A function that maps an array of interfaces to an optimal key where - * Class objects representing interfaces are weakly referenced. - */ - private static final class KeyFactory - implements BiFunction>, Object> - { - @Override - public Object apply(T t, List> interfaces) { - switch (interfaces.size()) { - case 1: return new Key1(interfaces.get(0)); // the most frequent - case 2: return new Key2(interfaces.get(0), interfaces.get(1)); - case 0: return key0; - default: return new KeyX(interfaces); - } - } - } - - /** - * A factory function that generates, defines and returns the proxy class - * given the ClassLoader and array of interfaces. + * Builder for a proxy class. + * + * If the module is not specified in this ProxyBuilder constructor, + * it will map from the given loader and interfaces to the module + * in which the proxy class will be defined. */ - private static final class ProxyClassFactory { + private static final class ProxyBuilder { private static final Unsafe UNSAFE = Unsafe.getUnsafe(); // prefix for all proxy class names @@ -548,6 +450,10 @@ // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); + // a reverse cache of defined proxy classes + private static final ClassLoaderValue reverseProxyCache = + new ClassLoaderValue<>(); + private static Class defineProxyClass(Module m, List> interfaces) { String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; @@ -601,8 +507,11 @@ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags); try { - return UNSAFE.defineClass(proxyName, proxyClassFile, 0, proxyClassFile.length, - loader, null); + Class pc = UNSAFE.defineClass(proxyName, proxyClassFile, + 0, proxyClassFile.length, + loader, null); + reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE); + return pc; } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the @@ -616,35 +525,14 @@ } /** - * Test if the given class is a proxy class + * Test if given class is a class defined by + * {@link #defineProxyClass(Module, List)} */ static boolean isProxyClass(Class c) { - return proxyCache.containsValue(c); - } - - /** - * Returns the proxy class. It will return the cached proxy class - * if exists; otherwise, it will create the proxy class and store in - * the cache. - */ - static Class get(Module module, List> interfaces) { - return proxyCache.get(module, interfaces); + return Boolean.TRUE.equals( + reverseProxyCache.sub(c).get(c.getClassLoader())); } - /** - * a cache of proxy classes in the named and unnamed module - */ - private static final WeakCache>, Class> proxyCache = - new WeakCache<>(new KeyFactory(), - new BiFunction>, Class>() { - @Override - public Class apply(Module m, List> interfaces) { - Objects.requireNonNull(m); - return defineProxyClass(m, interfaces); - } - }); - - private static boolean isExportedType(Class c) { String pn = c.getPackageName(); return Modifier.isPublic(c.getModifiers()) && c.getModule().isExported(pn); @@ -685,25 +573,18 @@ } }); - private static final boolean isDebug() { + private static boolean isDebug() { return !DEBUG.isEmpty(); } - private static final boolean isDebug(String flag) { + private static boolean isDebug(String flag) { return DEBUG.equals(flag); } - } - /** - * Builder for a proxy class. - * - * If the module is not specified in this ProxyBuilder constructor, - * it will map from the given loader and interfaces to the module - * in which the proxy class will be defined. - */ - private static final class ProxyBuilder { - final ClassLoader loader; - final List> interfaces; - final Module module; + // ProxyBuilder instance members start here.... + + private final ClassLoader loader; + private final List> interfaces; + private final Module module; ProxyBuilder(ClassLoader loader, List> interfaces) { if (!VM.isModuleSystemInited()) { throw new InternalError("Proxy is not supported until module system is fully initialzed"); @@ -723,16 +604,34 @@ assert getLoader(module) == loader; } + ProxyBuilder(ClassLoader loader, Class intf) { + this(loader, Collections.singletonList(intf)); + } + /** - * Generate a proxy class. If the target module does not have any + * Generate a proxy class and return its proxy Constructor with + * accessible flag already set. If the target module does not have access * to any interface types, IllegalAccessError will be thrown by the VM * at defineClass time. * * Must call the checkProxyAccess method to perform permission checks * before calling this. */ - Class build() { - return ProxyClassFactory.get(module, interfaces); + Constructor build() { + Class proxyClass = defineProxyClass(module, interfaces); + final Constructor cons; + try { + cons = proxyClass.getConstructor(constructorParams); + } catch (NoSuchMethodException e) { + throw new InternalError(e.toString(), e); + } + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + cons.setAccessible(true); + return null; + } + }); + return cons; } /** @@ -742,9 +641,9 @@ * @throws IllegalArgumentException if it violates the restrictions specified * in {@link Proxy#newProxyInstance} */ - static void validateProxyInterfaces(ClassLoader loader, - List> interfaces, - Set> refTypes) + private static void validateProxyInterfaces(ClassLoader loader, + List> interfaces, + Set> refTypes) { Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.size()); for (Class intf : interfaces) { @@ -779,10 +678,11 @@ * Returns all types referenced by all public method signatures of * the proxy interfaces */ - static Set> referencedTypes(ClassLoader loader, List> interfaces) { + private static Set> referencedTypes(ClassLoader loader, + List> interfaces) { return interfaces.stream() .flatMap(intf -> Stream.of(intf.getMethods()) - .flatMap(m -> methodRefTypes(m)) + .flatMap(ProxyBuilder::methodRefTypes) .map(ProxyBuilder::getElementType) .filter(t -> !t.isPrimitive())) .collect(Collectors.toSet()); @@ -792,11 +692,11 @@ * Extracts all types referenced on a method signature including * its return type, parameter types, and exception types. */ - static Stream> methodRefTypes(Method m) { + private static Stream> methodRefTypes(Method m) { return Stream.of(new Class[] { m.getReturnType() }, m.getParameterTypes(), m.getExceptionTypes()) - .flatMap(a -> Stream.of(a)); + .flatMap(Stream::of); } /** @@ -813,7 +713,9 @@ * package. Reads edge and qualified exports are added for * dynamic module to access. */ - static Module mapToModule(ClassLoader loader, List> interfaces, Set> refTypes) { + private static Module mapToModule(ClassLoader loader, + List> interfaces, + Set> refTypes) { Map, Module> modulePrivateTypes = new HashMap<>(); Map, Module> packagePrivateTypes = new HashMap<>(); for (Class intf : interfaces) { @@ -884,10 +786,9 @@ Set> visited = new HashSet<>(); while (!deque.isEmpty()) { Class c = deque.poll(); - if (visited.contains(c)) { + if (!visited.add(c)) { continue; } - visited.add(c); ensureAccess(target, c); // add all superinterfaces @@ -906,7 +807,7 @@ /* * Ensure the given module can access the given class. */ - static void ensureAccess(Module target, Class c) { + private static void ensureAccess(Module target, Class c) { Module m = c.getModule(); // add read edge and qualified export for the target module to access if (!target.canRead(m)) { @@ -921,7 +822,7 @@ /* * Ensure the given class is visible to the class loader. */ - static void ensureVisible(ClassLoader ld, Class c) { + private static void ensureVisible(ClassLoader ld, Class c) { Class type = null; try { type = Class.forName(c.getName(), false, ld); @@ -933,7 +834,7 @@ } } - static Class getElementType(Class type) { + private static Class getElementType(Class type) { Class e = type; while (e.isArray()) { e = e.getComponentType(); @@ -941,7 +842,8 @@ return e; } - private static final WeakHashMap dynProxyModules = new WeakHashMap<>(); + private static final ClassLoaderValue dynProxyModules = + new ClassLoaderValue<>(); private static final AtomicInteger counter = new AtomicInteger(); /* @@ -950,12 +852,12 @@ * * Each class loader will have one dynamic module. */ - static Module getDynamicModule(ClassLoader loader) { - return dynProxyModules.computeIfAbsent(loader, ld -> { + private static Module getDynamicModule(ClassLoader loader) { + return dynProxyModules.computeIfAbsent(loader, (ld, clv) -> { // create a dynamic module and setup module access String mn = "jdk.proxy" + counter.incrementAndGet(); String pn = PROXY_PACKAGE_PREFIX + "." + mn; - Module m = Modules.defineModule(loader, mn, Collections.singleton(pn)); + Module m = Modules.defineModule(ld, mn, Collections.singleton(pn)); Modules.addReads(m, Proxy.class.getModule()); // java.base to create proxy instance Modules.addExports(m, pn, Object.class.getModule()); @@ -1062,40 +964,31 @@ InvocationHandler h) { Objects.requireNonNull(h); - final List> intfs = List.of(interfaces); // interfaces cloned - final SecurityManager sm = System.getSecurityManager(); - final Class caller = Reflection.getCallerClass(); - if (sm != null) { - checkProxyAccess(caller, loader, intfs); - } + final Class caller = System.getSecurityManager() == null + ? null + : Reflection.getCallerClass(); /* - * Look up or generate the designated proxy class. + * Look up or generate the designated proxy class and its constructor. */ - Class cl = new ProxyBuilder(loader, intfs).build(); + Constructor cons = getProxyConstructor(caller, loader, interfaces); - return newProxyInstance(cl, caller, h); + return newProxyInstance(caller, cons, h); } - private static Object newProxyInstance(Class proxyClass, Class caller, InvocationHandler h) { + private static Object newProxyInstance(Class caller, // null if no SecurityManager + Constructor cons, + InvocationHandler h) { /* * Invoke its constructor with the designated invocation handler. */ try { - final SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - checkNewProxyPermission(caller, proxyClass); + if (caller != null) { + checkNewProxyPermission(caller, cons.getDeclaringClass()); } - final Constructor cons = proxyClass.getConstructor(constructorParams); - AccessController.doPrivileged(new PrivilegedAction() { - public Void run() { - cons.setAccessible(true); - return null; - } - }); return cons.newInstance(new Object[]{h}); - } catch (IllegalAccessException | InstantiationException | NoSuchMethodException e) { + } catch (IllegalAccessException | InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); @@ -1150,7 +1043,7 @@ * @throws NullPointerException if {@code cl} is {@code null} */ public static boolean isProxyClass(Class cl) { - return Proxy.class.isAssignableFrom(cl) && ProxyClassFactory.isProxyClass(cl); + return Proxy.class.isAssignableFrom(cl) && ProxyBuilder.isProxyClass(cl); } /** --- old/src/java.base/share/classes/jdk/internal/loader/BootLoader.java 2016-04-08 10:43:27.454659357 +0200 +++ new/src/java.base/share/classes/jdk/internal/loader/BootLoader.java 2016-04-08 10:43:27.354658804 +0200 @@ -39,6 +39,7 @@ import java.util.Arrays; import java.util.Enumeration; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import java.util.stream.Stream; @@ -68,6 +69,10 @@ // ServiceCatalog for the boot class loader private static final ServicesCatalog SERVICES_CATALOG = new ServicesCatalog(); + // ClassLoaderValue map for boot class loader + private static final ConcurrentHashMap CLASS_LOADER_VALUE_MAP = + new ConcurrentHashMap<>(); + /** * Returns the unnamed module for the boot loader. */ @@ -83,6 +88,13 @@ } /** + * Returns the ClassLoaderValue map for the boot class loader. + */ + public static ConcurrentHashMap getClassLoaderValueMap() { + return CLASS_LOADER_VALUE_MAP; + } + + /** * Register a module with this class loader so that its classes (and * resources) become visible via this class loader. */ --- old/src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java 2016-04-08 10:43:27.715660801 +0200 +++ new/src/java.base/share/classes/jdk/internal/misc/JavaLangAccess.java 2016-04-08 10:43:27.615660247 +0200 @@ -33,6 +33,7 @@ import java.net.URL; import java.security.AccessControlContext; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import jdk.internal.module.ServicesCatalog; @@ -146,6 +147,12 @@ ServicesCatalog createOrGetServicesCatalog(ClassLoader cl); /** + * Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s) + * associated with the given class loader, creating it if it doesn't already exist. + */ + ConcurrentHashMap createOrGetClassLoaderValueMap(ClassLoader cl); + + /** * Returns a class loaded by the bootstrap class loader. */ Class findBootstrapClassOrNull(ClassLoader cl, String name); --- /dev/null 2016-04-05 09:31:53.373418148 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/AbstractClassLoaderValue.java 2016-04-08 10:43:27.878661702 +0200 @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.reflect; + +import jdk.internal.loader.BootLoader; +import jdk.internal.misc.JavaLangAccess; +import jdk.internal.misc.SharedSecrets; + +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +/** + * AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue} + * and {@link Sub sub}-ClassLoaderValue. + * + * @param the type of concrete ClassLoaderValue (this type) + * @param the type of values associated with ClassLoaderValue + */ +abstract class AbstractClassLoaderValue, V> { + + /** + * Sole constructor. + */ + AbstractClassLoaderValue() {} + + /** + * Returns the key component of this ClassLoaderValue. The key component of + * the root-{@link ClassLoaderValue} is the ClassLoaderValue itself, + * while the key component of a {@link #sub(Object) sub}-ClassLoaderValue + * is what was given to construct it. + * + * @return the key component of this ClassLoaderValue. + */ + public abstract Object key(); + + /** + * Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given + * key component. + * + * @param key the key component of the sub-ClassLoaderValue. + * @param the type of the key component. + * @return a sub-ClassLoaderValue of this ClassLoaderValue for given key + */ + public Sub sub(K key) { + return new Sub<>(key); + } + + /** + * Returns {@code true} if this ClassLoaderValue is equal to given {@code clv} + * or if this ClassLoaderValue was derived from given {@code clv} by a chain + * of {@link #sub(Object)} invocations. + * + * @param clv the ClassLoaderValue to test this against + * @return if this ClassLoaderValue is equal to given {@code clv} or + * its descendant + */ + public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv); + + /** + * Returns the value associated with this ClassLoaderValue and given ClassLoader + * or {@code null} if there is none. + * + * @param cl the ClassLoader for the associated value + * @return the value associated with this ClassLoaderValue and given ClassLoader + * or {@code null} if there is none. + */ + public V get(ClassLoader cl) { + Object val = AbstractClassLoaderValue.map(cl).get(this); + try { + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate recursive get() for the same key that is just + // being calculated in computeIfAbsent() + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from Memoizer - pretend + // that there was no entry + // (computeIfAbsent invocation will try to remove it anyway) + return null; + } + } + + /** + * Associates given value {@code v} with this ClassLoaderValue and given + * ClassLoader and returns {@code null} if there was no previously associated + * value or does nothing and returns previously associated value if there + * was one. + * + * @param cl the ClassLoader for the associated value + * @param v the value to associate + * @return previously associated value or null if there was none + */ + public V putIfAbsent(ClassLoader cl, V v) { + ConcurrentHashMap map = map(cl); + @SuppressWarnings("unchecked") + CLV clv = (CLV) this; + while (true) { + try { + Object val = map.putIfAbsent(clv, v); + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate RecursiveInvocationException for the same key that + // is just being calculated in computeIfAbsent + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from foreign Memoizer - + // pretend that there was no entry and retry + // (foreign computeIfAbsent invocation will try to remove it anyway) + } + // TODO: + // Thread.onSpinLoop(); // when available + } + } + + /** + * Removes the value associated with this ClassLoaderValue and given + * ClassLoader if the associated value is equal to given value {@code v} and + * returns {@code true} or does nothing and returns {@code false} if there is + * no currently associated value or it is not equal to given value {@code v}. + * + * @param cl the ClassLoader for the associated value + * @param v the value to compare with currently associated value + * @return {@code true} if the association was removed or {@code false} if not + */ + public boolean remove(ClassLoader cl, Object v) { + return AbstractClassLoaderValue.map(cl).remove(this, v); + } + + /** + * Returns the value associated with this ClassLoaderValue and given + * ClassLoader if there is one or computes the value by invoking given + * {@code mappingFunction}, associates it and returns it. + *

+ * Computation and association of the computed value is performed atomically + * by the 1st thread that requests a particular association while holding a + * lock associated with this ClassLoaderValue and given ClassLoader. + * Nested calls from the {@code mappingFunction} to {@link #get}, + * {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association + * are not allowed and throw {@link IllegalStateException}. Nested call to + * {@link #remove} for the same association is allowed but will always return + * {@code false} regardless of passed-in comparison value. Nested calls for + * other association(s) are allowed, but care should be taken to avoid + * deadlocks. When two threads perform nested computations of the overlapping + * set of associations they should always request them in the same order. + * + * @param cl the ClassLoader for the associated value + * @param mappingFunction the function to compute the value + * @return the value associated with given ClassLoader and key components. + * @throws IllegalStateException if a direct or indirect invocation from + * within given {@code mappingFunction} that + * computes the value of a particular association + * to {@link #get}, {@link #putIfAbsent} or + * {@link #computeIfAbsent} + * for the same association is attempted. + */ + public V computeIfAbsent(ClassLoader cl, + BiFunction< + ? super ClassLoader, + ? super CLV, + ? extends V + > mappingFunction) throws IllegalStateException { + ConcurrentHashMap map = map(cl); + @SuppressWarnings("unchecked") + CLV clv = (CLV) this; + Memoizer mv = null; + while (true) { + Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv); + if (val == null) { + if (mv == null) { + // create Memoizer lazily when 1st needed and restart loop + mv = new Memoizer<>(cl, clv, mappingFunction); + continue; + } + // mv != null, therefore sv == null was a result of successful + // putIfAbsent + try { + // trigger Memoizer to compute the value + V v = mv.get(); + // attempt to replace our Memoizer with the value + map.replace(clv, mv, v); + // return computed value + return v; + } catch (Throwable t) { + // our Memoizer has thrown, attempt to remove it + map.remove(clv, mv); + // propagate exception because it's from our Memoizer + throw t; + } + } else { + try { + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate recursive attempts to calculate the same + // value as being calculated at the moment + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from foreign Memoizer - + // pretend that there was no entry and retry + // (foreign computeIfAbsent invocation will try to remove it anyway) + } + } + // TODO: + // Thread.onSpinLoop(); // when available + } + } + + /** + * Removes all values associated with given ClassLoader {@code cl} and + * {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants} + * of this ClassLoaderValue. + * This is not an atomic operation. Other threads may see some associations + * be already removed and others still present while this method is executing. + *

+ * The sole intention of this method is to cleanup after a unit test that + * tests ClassLoaderValue directly. It is not intended for use in + * actual algorithms. + * + * @param cl the associated ClassLoader of the values to be removed + */ + public void removeAll(ClassLoader cl) { + ConcurrentHashMap map = map(cl); + for (Iterator i = map.keySet().iterator(); i.hasNext(); ) { + if (i.next().isEqualOrDescendantOf(this)) { + i.remove(); + } + } + } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + /** + * @return a ConcurrentHashMap for given ClassLoader + */ + @SuppressWarnings("unchecked") + private static > + ConcurrentHashMap map(ClassLoader cl) { + return (ConcurrentHashMap) + (cl == null + ? BootLoader.getClassLoaderValueMap() + : JLA.createOrGetClassLoaderValueMap(cl) + ); + } + + /** + * @return value extracted from the {@link Memoizer} if given + * {@code memoizerOrValue} parameter is a {@code Memoizer} or + * just return given parameter. + */ + @SuppressWarnings("unchecked") + private V extractValue(Object memoizerOrValue) { + if (memoizerOrValue instanceof Memoizer) { + return ((Memoizer) memoizerOrValue).get(); + } else { + return (V) memoizerOrValue; + } + } + + /** + * A memorizing supplier that invokes given {@code mappingFunction} just once + * and then returns memorized result or throws memorized exception. + * If given mappingFunction returns null, it is converted to NullPointerException, + * thrown from the Memoizer's {@link #get()} method and memorized. + * If the Memoizer is invoked recursively from the given {@code mappingFunction}, + * {@link RecursiveInvocationException} is thrown, but not memorized. + * The in-flight call to the {@link #get()} can still complete successfully if + * such exception is handled by the mappingFunction. + */ + private static final class Memoizer, V> + implements Supplier { + + private final ClassLoader cl; + private final CLV clv; + private final BiFunction + mappingFunction; + + private volatile V v; + private volatile Throwable t; + private boolean inCall; + + Memoizer(ClassLoader cl, + CLV clv, + BiFunction + mappingFunction + ) { + this.cl = cl; + this.clv = clv; + this.mappingFunction = mappingFunction; + } + + @Override + public V get() throws RecursiveInvocationException { + V v = this.v; + if (v != null) return v; + Throwable t = this.t; + if (t == null) { + synchronized (this) { + if ((v = this.v) == null && (t = this.t) == null) { + if (inCall) { + throw new RecursiveInvocationException(); + } + inCall = true; + try { + this.v = v = Objects.requireNonNull( + mappingFunction.apply(cl, clv)); + } catch (Throwable x) { + this.t = t = x; + } finally { + inCall = false; + } + } + } + } + if (v != null) return v; + if (t instanceof Error) { + throw (Error) t; + } else if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else { + throw new UndeclaredThrowableException(t); + } + } + + static class RecursiveInvocationException extends IllegalStateException { + private static final long serialVersionUID = 1L; + + RecursiveInvocationException() { + super("Recursive call"); + } + } + } + + /** + * sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue} + * and also a subclass of it. It can therefore be instantiated as an inner + * class of either an instance of root-{@link ClassLoaderValue} or another + * instance of itself. This enables composing type-safe compound keys of + * arbitrary length: + *

{@code
+     * ClassLoaderValue clv = new ClassLoaderValue<>();
+     * ClassLoaderValue.Sub.Sub.Sub clv_k123 =
+     *     clv.sub(k1).sub(k2).sub(k3);
+     * }
+ * From which individual components are accessible in a type-safe way: + *
{@code
+     * K1 k1 = clv_k123.parent().parent().key();
+     * K2 k2 = clv_k123.parent().key();
+     * K3 k3 = clv_k123.key();
+     * }
+ * This allows specifying non-capturing lambdas for the mapping function of + * {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can + * access individual key components from passed-in + * sub-[sub-...]ClassLoaderValue instance in a type-safe way. + * + * @param the type of {@link #key()} component contained in the + * sub-ClassLoaderValue. + */ + final class Sub extends AbstractClassLoaderValue, V> { + + private final K key; + + Sub(K key) { + this.key = key; + } + + /** + * @return the parent ClassLoaderValue this sub-ClassLoaderValue + * has been {@link #sub(Object) derived} from. + */ + public AbstractClassLoaderValue parent() { + return AbstractClassLoaderValue.this; + } + + /** + * @return the key component of this sub-ClassLoaderValue. + */ + @Override + public K key() { + return key; + } + + /** + * sub-ClassLoaderValue is a descendant of given {@code clv} if it is + * either equal to it or if its {@link #parent() parent} is a + * descendant of given {@code clv}. + */ + @Override + public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { + return equals(Objects.requireNonNull(clv)) || + parent().isEqualOrDescendantOf(clv); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Sub)) return false; + @SuppressWarnings("unchecked") + Sub that = (Sub) o; + return this.parent().equals(that.parent()) && + Objects.equals(this.key, that.key); + } + + @Override + public int hashCode() { + return 31 * parent().hashCode() + + Objects.hashCode(key); + } + } +} --- /dev/null 2016-04-05 09:31:53.373418148 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/ClassLoaderValue.java 2016-04-08 10:43:28.146663184 +0200 @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.reflect; + +import java.util.Objects; +import java.util.function.BiFunction; + +/** + * root-ClassLoaderValue. Each instance defines a separate namespace for + * associated values. + *

+ * ClassLoaderValue allows associating a + * {@link #computeIfAbsent(ClassLoader, BiFunction) computed} non-null value with + * a {@code (ClassLoader, keys...)} tuple. The associated value, as well as the + * keys are strongly reachable from the associated ClassLoader so care should be + * taken to use such keys and values that only reference types resolvable from + * the associated ClassLoader. Failing that, ClassLoader leaks are inevitable. + *

+ * Example usage: + *

{@code
+ * // create a root instance which represents a namespace and declares the type of
+ * // associated values (Class instances in this example)
+ * static final ClassLoaderValue> proxyClasses = new ClassLoaderValue<>();
+ *
+ * // create a compound key composed of a Module and a list of interfaces
+ * Module module = ...;
+ * List> interfaces = ...;
+ * ClassLoaderValue>.Sub.Sub>> key =
+ *     proxyClasses.sub(module).sub(interfaces);
+ *
+ * // use the compound key together with ClassLoader to lazily associate
+ * // the value with tuple (loader, module, interfaces) and return it
+ * ClassLoader loader = ...;
+ * Class proxyClass = key.computeIfAbsent(loader, (ld, ky) -> {
+ *     List> intfcs = ky.key();
+ *     Module m = ky.parent().key();
+ *     Class clazz = defineProxyClass(ld, m, intfcs);
+ *     return clazz;
+ * });
+ * }
+ *

+ * {@code classLoaderValue.(classLoader, ...)} represents an operation + * to {@link #get}, {@link #putIfAbsent}, {@link #computeIfAbsent} or {@link #remove} + * a value associated with a (classLoader, classLoaderValue) tuple. ClassLoader + * instances and root-{@link ClassLoaderValue} instances are compared using + * identity equality while {@link Sub sub}-ClassLoaderValue instances define + * {@link #equals(Object) equality} in terms of equality of its + * {@link Sub#parent() parent} ClassLoaderValue and its + * {@link #key() key} component. + * + * @param the type of value(s) associated with the root-ClassLoaderValue and + * all its {@link #sub(Object) descendants}. + * @author Peter Levart + * @since 9 + */ +final class ClassLoaderValue + extends AbstractClassLoaderValue, V> { + + /** + * Constructs new root-ClassLoaderValue representing its own namespace. + */ + public ClassLoaderValue() {} + + /** + * @return the key component of this root-ClassLoaderValue (itself). + */ + @Override + public ClassLoaderValue key() { + return this; + } + + /** + * root-ClassLoaderValue can only be equal to itself and has no predecessors. + */ + @Override + public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { + return equals(Objects.requireNonNull(clv)); + } +} --- /dev/null 2016-04-05 09:31:53.373418148 +0200 +++ new/test/java/lang/reflect/ClassLoaderValue/Driver.java 2016-04-08 10:43:28.396664566 +0200 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @build java.base/java.lang.reflect.ClassLoaderValueTest + * @run main Driver + * @bug 8152115 + * @summary functional and concurrency test for ClassLoaderValue + */ +public class Driver { + public static void main(String[] args) throws Exception { + java.lang.reflect.ClassLoaderValueTest.main(args); + } +} --- /dev/null 2016-04-05 09:31:53.373418148 +0200 +++ new/test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java 2016-04-08 10:43:28.638665905 +0200 @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.reflect; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Functional and concurrency test for ClassLoaderValue + * + * @author Peter Levart + */ +public class ClassLoaderValueTest { + + @SuppressWarnings("unchecked") + public static void main(String[] args) throws Exception { + + ClassLoaderValue[] clvs = {new ClassLoaderValue<>(), + new ClassLoaderValue<>()}; + + ClassLoader[] lds = {ClassLoader.getSystemClassLoader(), + ClassLoader.getPlatformClassLoader(), + null /* bootstrap class loader */}; + + Integer[] keys = new Integer[32]; + for (int i = 0; i < keys.length; i++) { + keys[i] = i + 128; + } + + try (AutoCloseable cleanup = () -> { + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + clv.removeAll(ld); + } + } + }) { + // 1st just one sequential pass of single-threaded validation + // which is easier to debug if it fails... + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + writeValidateOps(clv, ld, keys); + } + } + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + readValidateOps(clv, ld, keys); + } + } + + // 2nd the same in concurrent setting that also validates + // failure-isolation between threads and data-isolation between + // regions - (ClassLoader, ClassLoaderValue) pairs - of the storage + testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3)); + } + } + + static void writeValidateOps(ClassLoaderValue clv, + ClassLoader ld, + Object[] keys) { + for (int i = 0; i < keys.length; i++) { + Object k = keys[i]; + Integer v1 = i; + Integer v2 = i + 333; + Integer pv; + boolean success; + + pv = clv.sub(k).putIfAbsent(ld, v1); + assertEquals(pv, null); + assertEquals(clv.sub(k).get(ld), v1); + + pv = clv.sub(k).putIfAbsent(ld, v2); + assertEquals(pv, v1); + assertEquals(clv.sub(k).get(ld), v1); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, false); + assertEquals(clv.sub(k).get(ld), v1); + + success = clv.sub(k).remove(ld, v1); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + pv = clv.sub(k).putIfAbsent(ld, v2); + assertEquals(pv, null); + assertEquals(clv.sub(k).get(ld), v2); + + pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1); + assertEquals(pv, v2); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v1); + assertEquals(success, false); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { + try { + // nested get for same key should throw + clv_k.get(_ld); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + try { + // nested putIfAbsent for same key should throw + clv_k.putIfAbsent(_ld, v1); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + // nested remove for for same key and any value (even null) + // should return false + assertEquals(clv_k.remove(_ld, null), false); + assertEquals(clv_k.remove(_ld, v1), false); + assertEquals(clv_k.remove(_ld, v2), false); + try { + // nested computeIfAbsent for same key should throw + clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + // if everything above has been handled, we should succeed... + return v2; + }); + // ... and the result should be reflected in the CLV + assertEquals(pv, v2); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + try { + clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { + throw new UnsupportedOperationException(); + }); + throw new AssertionError("Unexpected code path"); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(clv.sub(k).get(ld), null); + } + } + + static void readValidateOps(ClassLoaderValue clv, + ClassLoader ld, + Object[] keys) { + for (int i = 0; i < keys.length; i++) { + Object k = keys[i]; + Integer v1 = i; + Integer v2 = i + 333; + Integer rv = clv.sub(k).get(ld); + if (!(rv == null || rv.equals(v1) || rv.equals(v2))) { + throw new AssertionError("Unexpected value: " + rv + + ", expected one of: null, " + v1 + ", " + v2); + } + } + } + + static void testConcurrentIsolation(ClassLoaderValue[] clvs, + ClassLoader[] lds, + Object[] keys, + long millisRuntime) { + ExecutorService exe = Executors.newCachedThreadPool(); + List> futures = new ArrayList<>(); + AtomicBoolean stop = new AtomicBoolean(); + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + // submit a task that exercises a mix of modifying + // and reading-validating operations in an isolated + // part of the storage. If isolation is violated, + // validation operations are expected to fail. + futures.add(exe.submit(() -> { + do { + writeValidateOps(clv, ld, keys); + } while (!stop.get()); + })); + // submit a task that just reads from the same part of + // the storage as above task. It should not disturb + // above task in any way and this task should never + // exhibit any failure although above task produces + // regular failures during lazy computation + futures.add(exe.submit(() -> { + do { + readValidateOps(clv, ld, keys); + } while (!stop.get()); + })); + } + } + // wait for some time + try { + Thread.sleep(millisRuntime); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + // stop tasks + stop.set(true); + // collect results + AssertionError error = null; + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + if (error == null) error = new AssertionError("Failure"); + error.addSuppressed(e); + } + } + exe.shutdown(); + if (error != null) throw error; + } + + static void assertEquals(Object actual, Object expected) { + if (!Objects.equals(actual, expected)) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } +} --- old/src/java.base/share/classes/java/lang/reflect/WeakCache.java 2016-04-08 10:43:29.002667917 +0200 +++ /dev/null 2016-04-05 09:31:53.373418148 +0200 @@ -1,381 +0,0 @@ -/* - * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package java.lang.reflect; - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.BiFunction; -import java.util.function.Supplier; - -/** - * Cache mapping pairs of {@code (key, sub-key) -> value}. Keys and values are - * weakly but sub-keys are strongly referenced. Keys are passed directly to - * {@link #get} method which also takes a {@code parameter}. Sub-keys are - * calculated from keys and parameters using the {@code subKeyFactory} function - * passed to the constructor. Values are calculated from keys and parameters - * using the {@code valueFactory} function passed to the constructor. - * Keys can be {@code null} and are compared by identity while sub-keys returned by - * {@code subKeyFactory} or values returned by {@code valueFactory} - * can not be null. Sub-keys are compared using their {@link #equals} method. - * Entries are expunged from cache lazily on each invocation to {@link #get}, - * {@link #containsValue} or {@link #size} methods when the WeakReferences to - * keys are cleared. Cleared WeakReferences to individual values don't cause - * expunging, but such entries are logically treated as non-existent and - * trigger re-evaluation of {@code valueFactory} on request for their - * key/subKey. - * - * @author Peter Levart - * @param type of keys - * @param

type of parameters - * @param type of values - */ -final class WeakCache { - - private final ReferenceQueue refQueue - = new ReferenceQueue<>(); - // the key type is Object for supporting null key - private final ConcurrentMap>> map - = new ConcurrentHashMap<>(); - private final ConcurrentMap, Boolean> reverseMap - = new ConcurrentHashMap<>(); - private final BiFunction subKeyFactory; - private final BiFunction valueFactory; - - /** - * Construct an instance of {@code WeakCache} - * - * @param subKeyFactory a function mapping a pair of - * {@code (key, parameter) -> sub-key} - * @param valueFactory a function mapping a pair of - * {@code (key, parameter) -> value} - * @throws NullPointerException if {@code subKeyFactory} or - * {@code valueFactory} is null. - */ - public WeakCache(BiFunction subKeyFactory, - BiFunction valueFactory) { - this.subKeyFactory = Objects.requireNonNull(subKeyFactory); - this.valueFactory = Objects.requireNonNull(valueFactory); - } - - /** - * Look-up the value through the cache. This always evaluates the - * {@code subKeyFactory} function and optionally evaluates - * {@code valueFactory} function if there is no entry in the cache for given - * pair of (key, subKey) or the entry has already been cleared. - * - * @param key possibly null key - * @param parameter parameter used together with key to create sub-key and - * value (should not be null) - * @return the cached value (never null) - * @throws NullPointerException if {@code parameter} passed in or - * {@code sub-key} calculated by - * {@code subKeyFactory} or {@code value} - * calculated by {@code valueFactory} is null. - */ - public V get(K key, P parameter) { - Objects.requireNonNull(parameter); - - expungeStaleEntries(); - - Object cacheKey = CacheKey.valueOf(key, refQueue); - - // lazily install the 2nd level valuesMap for the particular cacheKey - ConcurrentMap> valuesMap = map.get(cacheKey); - if (valuesMap == null) { - ConcurrentMap> oldValuesMap - = map.putIfAbsent(cacheKey, - valuesMap = new ConcurrentHashMap<>()); - if (oldValuesMap != null) { - valuesMap = oldValuesMap; - } - } - - // create subKey and retrieve the possible Supplier stored by that - // subKey from valuesMap - Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); - Supplier supplier = valuesMap.get(subKey); - Factory factory = null; - - while (true) { - if (supplier != null) { - // supplier might be a Factory or a CacheValue instance - V value = supplier.get(); - if (value != null) { - return value; - } - } - // else no supplier in cache - // or a supplier that returned null (could be a cleared CacheValue - // or a Factory that wasn't successful in installing the CacheValue) - - // lazily construct a Factory - if (factory == null) { - factory = new Factory(key, parameter, subKey, valuesMap); - } - - if (supplier == null) { - supplier = valuesMap.putIfAbsent(subKey, factory); - if (supplier == null) { - // successfully installed Factory - supplier = factory; - } - // else retry with winning supplier - } else { - if (valuesMap.replace(subKey, supplier, factory)) { - // successfully replaced - // cleared CacheEntry / unsuccessful Factory - // with our Factory - supplier = factory; - } else { - // retry with current supplier - supplier = valuesMap.get(subKey); - } - } - } - } - - /** - * Checks whether the specified non-null value is already present in this - * {@code WeakCache}. The check is made using identity comparison regardless - * of whether value's class overrides {@link Object#equals} or not. - * - * @param value the non-null value to check - * @return true if given {@code value} is already cached - * @throws NullPointerException if value is null - */ - public boolean containsValue(V value) { - Objects.requireNonNull(value); - - expungeStaleEntries(); - return reverseMap.containsKey(new LookupValue<>(value)); - } - - /** - * Returns the current number of cached entries that - * can decrease over time when keys/values are GC-ed. - */ - public int size() { - expungeStaleEntries(); - return reverseMap.size(); - } - - @SuppressWarnings("unchecked") // refQueue.poll actually returns CacheKey - private void expungeStaleEntries() { - CacheKey cacheKey; - while ((cacheKey = (CacheKey)refQueue.poll()) != null) { - cacheKey.expungeFrom(map, reverseMap); - } - } - - /** - * A factory {@link Supplier} that implements the lazy synchronized - * construction of the value and installment of it into the cache. - */ - private final class Factory implements Supplier { - - private final K key; - private final P parameter; - private final Object subKey; - private final ConcurrentMap> valuesMap; - - Factory(K key, P parameter, Object subKey, - ConcurrentMap> valuesMap) { - this.key = key; - this.parameter = parameter; - this.subKey = subKey; - this.valuesMap = valuesMap; - } - - @Override - public synchronized V get() { // serialize access - // re-check - Supplier supplier = valuesMap.get(subKey); - if (supplier != this) { - // something changed while we were waiting: - // might be that we were replaced by a CacheValue - // or were removed because of failure -> - // return null to signal WeakCache.get() to retry - // the loop - return null; - } - // else still us (supplier == this) - - // create new value - V value = null; - try { - value = Objects.requireNonNull(valueFactory.apply(key, parameter)); - } finally { - if (value == null) { // remove us on failure - valuesMap.remove(subKey, this); - } - } - // the only path to reach here is with non-null value - assert value != null; - - // wrap value with CacheValue (WeakReference) - CacheValue cacheValue = new CacheValue<>(value); - - // try replacing us with CacheValue (this should always succeed) - if (valuesMap.replace(subKey, this, cacheValue)) { - // put also in reverseMap - reverseMap.put(cacheValue, Boolean.TRUE); - } else { - throw new AssertionError("Should not reach here"); - } - - // successfully replaced us with new CacheValue -> return the value - // wrapped by it - return value; - } - } - - /** - * Common type of value suppliers that are holding a referent. - * The {@link #equals} and {@link #hashCode} of implementations is defined - * to compare the referent by identity. - */ - private interface Value extends Supplier {} - - /** - * An optimized {@link Value} used to look-up the value in - * {@link WeakCache#containsValue} method so that we are not - * constructing the whole {@link CacheValue} just to look-up the referent. - */ - private static final class LookupValue implements Value { - private final V value; - - LookupValue(V value) { - this.value = value; - } - - @Override - public V get() { - return value; - } - - @Override - public int hashCode() { - return System.identityHashCode(value); // compare by identity - } - - @Override - public boolean equals(Object obj) { - return obj == this || - obj instanceof Value && - this.value == ((Value) obj).get(); // compare by identity - } - } - - /** - * A {@link Value} that weakly references the referent. - */ - private static final class CacheValue - extends WeakReference implements Value - { - private final int hash; - - CacheValue(V value) { - super(value); - this.hash = System.identityHashCode(value); // compare by identity - } - - @Override - public int hashCode() { - return hash; - } - - @Override - public boolean equals(Object obj) { - V value; - return obj == this || - obj instanceof Value && - // cleared CacheValue is only equal to itself - (value = get()) != null && - value == ((Value) obj).get(); // compare by identity - } - } - - /** - * CacheKey containing a weakly referenced {@code key}. It registers - * itself with the {@code refQueue} so that it can be used to expunge - * the entry when the {@link WeakReference} is cleared. - */ - private static final class CacheKey extends WeakReference { - - // a replacement for null keys - private static final Object NULL_KEY = new Object(); - - static Object valueOf(K key, ReferenceQueue refQueue) { - return key == null - // null key means we can't weakly reference it, - // so we use a NULL_KEY singleton as cache key - ? NULL_KEY - // non-null key requires wrapping with a WeakReference - : new CacheKey<>(key, refQueue); - } - - private final int hash; - - private CacheKey(K key, ReferenceQueue refQueue) { - super(key, refQueue); - this.hash = System.identityHashCode(key); // compare by identity - } - - @Override - public int hashCode() { - return hash; - } - - @Override - @SuppressWarnings("unchecked") - public boolean equals(Object obj) { - K key; - return obj == this || - obj != null && - obj.getClass() == this.getClass() && - // cleared CacheKey is only equal to itself - (key = this.get()) != null && - // compare key by identity - key == ((CacheKey) obj).get(); // Cast is safe from getClass check - } - - void expungeFrom(ConcurrentMap> map, - ConcurrentMap reverseMap) { - // removing just by key is always safe here because after a CacheKey - // is cleared and enqueue-ed it is only equal to itself - // (see equals method)... - ConcurrentMap valuesMap = map.remove(this); - // remove also from reverseMap if needed - if (valuesMap != null) { - for (Object cacheValue : valuesMap.values()) { - reverseMap.remove(cacheValue); - } - } - } - } -}