java.lang.reflect.Proxy update to odjk8u60
Proxy class cache mechanism was rewritten using
new WeakCache class.
Change-Id: Ifad1f03fd7ee7098186120462c28a31ecc8c605d
Test: All *ProxyTest.java & flashed/booted device
Bug: 28666126
diff --git a/ojluni/src/main/java/java/lang/reflect/Proxy.java b/ojluni/src/main/java/java/lang/reflect/Proxy.java
index 209ccb1..c9e56cf 100644
--- a/ojluni/src/main/java/java/lang/reflect/Proxy.java
+++ b/ojluni/src/main/java/java/lang/reflect/Proxy.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2013, 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
@@ -26,25 +26,30 @@
package java.lang.reflect;
+
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
-import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiFunction;
+import libcore.util.EmptyArray;
import sun.reflect.CallerSensitive;
import sun.reflect.misc.ReflectUtil;
import sun.security.util.SecurityConstants;
-import libcore.util.EmptyArray;
/**
* {@code Proxy} provides static methods for creating dynamic proxy
@@ -54,16 +59,14 @@
* <p>To create a proxy for some interface {@code Foo}:
* <pre>
* InvocationHandler handler = new MyInvocationHandler(...);
- * Class proxyClass = Proxy.getProxyClass(
- * Foo.class.getClassLoader(), new Class[] { Foo.class });
- * Foo f = (Foo) proxyClass.
- * getConstructor(new Class[] { InvocationHandler.class }).
- * newInstance(new Object[] { handler });
+ * Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
+ * Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
+ * newInstance(handler);
* </pre>
* or more simply:
* <pre>
* Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
- * new Class[] { Foo.class },
+ * new Class<?>[] { Foo.class },
* handler);
* </pre>
*
@@ -92,7 +95,11 @@
* <p>A proxy class has the following properties:
*
* <ul>
- * <li>Proxy classes are public, final, and not abstract.
+ * <li>Proxy classes are <em>public, final, and not abstract</em> if
+ * all proxy interfaces are public.</li>
+ *
+ * <li>Proxy classes are <em>non-public, final, and not abstract</em> if
+ * any of the proxy interfaces is non-public.</li>
*
* <li>The unqualified name of a proxy class is unspecified. The space
* of class names that begin with the string {@code "$Proxy"}
@@ -236,23 +243,14 @@
private final static String proxyClassNamePrefix = "$Proxy";
/** parameter types of a proxy class constructor */
- private final static Class[] constructorParams =
+ private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
- /** maps a class loader to the proxy class cache for that loader */
- private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
- = new WeakHashMap<>();
-
- /** marks that a particular proxy class is currently being generated */
- private static Object pendingGenerationMarker = new Object();
-
- /** next number to use for generation of unique proxy class names */
- private static long nextUniqueNumber = 0;
- private static Object nextUniqueNumberLock = new Object();
-
- /** set of all generated proxy classes, for isProxyClass implementation */
- private static Map<Class<?>, Void> proxyClasses =
- Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());
+ /**
+ * a cache of proxy classes
+ */
+ private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
+ proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
/**
* the invocation handler for this proxy instance.
@@ -296,9 +294,13 @@
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
- * @param h the invocation handler for this proxy instance
+ * @param h the invocation handler for this proxy instance
+ *
+ * @throws NullPointerException if the given invocation handler, {@code h},
+ * is {@code null}.
*/
protected Proxy(InvocationHandler h) {
+ Objects.requireNonNull(h);
this.h = h;
}
@@ -306,9 +308,10 @@
* Returns the {@code java.lang.Class} object for a proxy class
* given a class loader and an array of interfaces. The proxy class
* will be defined by the specified class loader and will implement
- * all of the supplied interfaces. If a proxy class for the same
- * permutation of interfaces has already been defined by the class
- * loader, then the existing proxy class will be returned; otherwise,
+ * all of the supplied interfaces. If any of the given interfaces
+ * is non-public, the proxy class will be non-public. If a proxy class
+ * for the same permutation of interfaces has already been defined by the
+ * class loader, then the existing proxy class will be returned; otherwise,
* a proxy class for those interfaces will be generated dynamically
* and defined by the class loader.
*
@@ -373,6 +376,21 @@
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
+ * @throws SecurityException if a security manager, <em>s</em>, is present
+ * and any of the following conditions is met:
+ * <ul>
+ * <li> the given {@code loader} is {@code null} and
+ * the caller's class loader is not {@code null} and the
+ * invocation of {@link SecurityManager#checkPermission
+ * s.checkPermission} with
+ * {@code RuntimePermission("getClassLoader")} permission
+ * denies access.</li>
+ * <li> for each proxy interface, {@code intf},
+ * the caller's class loader is not the same as or an
+ * ancestor of the class loader for {@code intf} and
+ * invocation of {@link SecurityManager#checkPackageAccess
+ * s.checkPackageAccess()} denies access to {@code intf}.</li>
+ * </ul>
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}
*/
@@ -394,142 +412,204 @@
throw new IllegalArgumentException("interface limit exceeded");
}
- Class<?> proxyClass = null;
+ // If the proxy class defined by the given loader implementing
+ // the given interfaces exists, this will simply return the cached copy;
+ // otherwise, it will create the proxy class via the ProxyClassFactory
+ return proxyClassCache.get(loader, interfaces);
+ }
- /* collect interface names to use as key for proxy class cache */
- String[] interfaceNames = new String[interfaces.length];
+ /*
+ * a key used for proxy class with 0 implemented interfaces
+ */
+ private static final Object key0 = new Object();
- // for detecting duplicates
- Set<Class<?>> interfaceSet = new HashSet<>();
+ /*
+ * Key1 and Key2 are optimized for the common use of dynamic proxies
+ * that implement 1 or 2 interfaces.
+ */
- for (int i = 0; i < interfaces.length; i++) {
- /*
- * Verify that the class loader resolves the name of this
- * interface to the same Class object.
- */
- String interfaceName = interfaces[i].getName();
- Class<?> interfaceClass = null;
- try {
- interfaceClass = Class.forName(interfaceName, false, loader);
- } catch (ClassNotFoundException e) {
- }
- if (interfaceClass != interfaces[i]) {
- throw new IllegalArgumentException(
- interfaces[i] + " is not visible from class loader");
- }
+ /*
+ * a key used for proxy class with 1 implemented interface
+ */
+ private static final class Key1 extends WeakReference<Class<?>> {
+ private final int hash;
- /*
- * Verify that the Class object actually represents an
- * interface.
- */
- if (!interfaceClass.isInterface()) {
- throw new IllegalArgumentException(
- interfaceClass.getName() + " is not an interface");
- }
-
- /*
- * Verify that this interface is not a duplicate.
- */
- if (interfaceSet.contains(interfaceClass)) {
- throw new IllegalArgumentException(
- "repeated interface: " + interfaceClass.getName());
- }
- interfaceSet.add(interfaceClass);
-
- interfaceNames[i] = interfaceName;
+ Key1(Class<?> intf) {
+ super(intf);
+ this.hash = intf.hashCode();
}
- /*
- * Using string representations of the proxy interfaces as
- * keys in the proxy class cache (instead of their Class
- * objects) is sufficient because we require the proxy
- * interfaces to be resolvable by name through the supplied
- * class loader, and it has the advantage that using a string
- * representation of a class makes for an implicit weak
- * reference to the class.
- */
- List<String> key = Arrays.asList(interfaceNames);
-
- /*
- * Find or create the proxy class cache for the class loader.
- */
- Map<List<String>, Object> cache;
- synchronized (loaderToCache) {
- cache = loaderToCache.get(loader);
- if (cache == null) {
- cache = new HashMap<>();
- loaderToCache.put(loader, cache);
- }
- /*
- * This mapping will remain valid for the duration of this
- * method, without further synchronization, because the mapping
- * will only be removed if the class loader becomes unreachable.
- */
+ @Override
+ public int hashCode() {
+ return hash;
}
- /*
- * Look up the list of interfaces in the proxy class cache using
- * the key. This lookup will result in one of three possible
- * kinds of values:
- * null, if there is currently no proxy class for the list of
- * interfaces in the class loader,
- * the pendingGenerationMarker object, if a proxy class for the
- * list of interfaces is currently being generated,
- * or a weak reference to a Class object, if a proxy class for
- * the list of interfaces has already been generated.
- */
- synchronized (cache) {
- /*
- * Note that we need not worry about reaping the cache for
- * entries with cleared weak references because if a proxy class
- * has been garbage collected, its class loader will have been
- * garbage collected as well, so the entire cache will be reaped
- * from the loaderToCache map.
- */
- do {
- Object value = cache.get(key);
- if (value instanceof Reference) {
- proxyClass = (Class<?>) ((Reference) value).get();
+ @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<Class<?>> {
+ private final int hash;
+ private final WeakReference<Class<?>> ref2;
+
+ Key2(Class<?> intf1, Class<?> intf2) {
+ super(intf1);
+ hash = 31 * intf1.hashCode() + intf2.hashCode();
+ ref2 = new WeakReference<Class<?>>(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<Class<?>>[] refs;
+
+ @SuppressWarnings("unchecked")
+ KeyX(Class<?>[] interfaces) {
+ hash = Arrays.hashCode(interfaces);
+ refs = (WeakReference<Class<?>>[])new WeakReference<?>[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++) {
+ refs[i] = new WeakReference<>(interfaces[i]);
+ }
+ }
+
+ @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<Class<?>>[] refs1,
+ WeakReference<Class<?>>[] 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;
}
- if (proxyClass != null) {
- // proxy class already generated: return it
- return proxyClass;
- } else if (value == pendingGenerationMarker) {
- // proxy class being generated: wait for it
- try {
- cache.wait();
- } catch (InterruptedException e) {
- /*
- * The class generation that we are waiting for should
- * take a small, bounded time, so we can safely ignore
- * thread interrupts here.
- */
- }
- continue;
- } else {
- /*
- * No proxy class for this list of interfaces has been
- * generated or is being generated, so we will go and
- * generate it now. Mark it as pending generation.
- */
- cache.put(key, pendingGenerationMarker);
- break;
- }
- } while (true);
+ }
+ return true;
}
+ }
- try {
+ /**
+ * 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<ClassLoader, Class<?>[], Object>
+ {
+ @Override
+ public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
+ switch (interfaces.length) {
+ case 1: return new Key1(interfaces[0]); // the most frequent
+ case 2: return new Key2(interfaces[0], interfaces[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.
+ */
+ private static final class ProxyClassFactory
+ implements BiFunction<ClassLoader, Class<?>[], Class<?>>
+ {
+ // prefix for all proxy class names
+ private static final String proxyClassNamePrefix = "$Proxy";
+
+ // next number to use for generation of unique proxy class names
+ private static final AtomicLong nextUniqueNumber = new AtomicLong();
+
+ @Override
+ public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
+
+ Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
+ for (Class<?> intf : interfaces) {
+ /*
+ * Verify that the class loader resolves the name of this
+ * interface to the same Class object.
+ */
+ Class<?> interfaceClass = null;
+ try {
+ interfaceClass = Class.forName(intf.getName(), false, loader);
+ } catch (ClassNotFoundException e) {
+ }
+ if (interfaceClass != intf) {
+ throw new IllegalArgumentException(
+ intf + " is not visible from class loader");
+ }
+ /*
+ * Verify that the Class object actually represents an
+ * interface.
+ */
+ if (!interfaceClass.isInterface()) {
+ throw new IllegalArgumentException(
+ interfaceClass.getName() + " is not an interface");
+ }
+ /*
+ * Verify that this interface is not a duplicate.
+ */
+ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
+ throw new IllegalArgumentException(
+ "repeated interface: " + interfaceClass.getName());
+ }
+ }
+
String proxyPkg = null; // package to define proxy class in
+ int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
- for (int i = 0; i < interfaces.length; i++) {
- int flags = interfaces[i].getModifiers();
+ for (Class<?> intf : interfaces) {
+ int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
- String name = interfaces[i].getName();
+ accessFlags = Modifier.FINAL;
+ String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
@@ -560,36 +640,13 @@
/*
* Choose a name for the proxy class to generate.
*/
- final long num;
- synchronized (nextUniqueNumberLock) {
- num = nextUniqueNumber++;
- }
+ long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
- proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
- exceptionsArray);
- }
- // add to set of all generated proxy classes, for isProxyClass
- proxyClasses.put(proxyClass, null);
-
- } finally {
- /*
- * We must clean up the "pending generation" state of the proxy
- * class cache entry somehow. If a proxy class was successfully
- * generated, store it in the cache (with a weak reference);
- * otherwise, remove the reserved entry. In all cases, notify
- * all waiters on reserved entries in this cache.
- */
- synchronized (cache) {
- if (proxyClass != null) {
- cache.put(key, new WeakReference<Class<?>>(proxyClass));
- } else {
- cache.remove(key);
- }
- cache.notifyAll();
+ return generateProxy(proxyName, interfaces, loader, methodsArray,
+ exceptionsArray);
}
}
- return proxyClass;
}
/**
@@ -643,7 +700,6 @@
return intersection.toArray(new Class<?>[intersection.size()]);
}
-
/**
* Throws if any two methods in {@code methods} have the same name and
* parameters but incompatible return types.
@@ -696,15 +752,11 @@
}
}
+
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
- * handler. This method is equivalent to:
- * <pre>
- * Proxy.getProxyClass(loader, interfaces).
- * getConstructor(new Class[] { InvocationHandler.class }).
- * newInstance(new Object[] { handler });
- * </pre>
+ * handler.
*
* <p>{@code Proxy.newProxyInstance} throws
* {@code IllegalArgumentException} for the same reasons that
@@ -720,6 +772,27 @@
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
+ * @throws SecurityException if a security manager, <em>s</em>, is present
+ * and any of the following conditions is met:
+ * <ul>
+ * <li> the given {@code loader} is {@code null} and
+ * the caller's class loader is not {@code null} and the
+ * invocation of {@link SecurityManager#checkPermission
+ * s.checkPermission} with
+ * {@code RuntimePermission("getClassLoader")} permission
+ * denies access;</li>
+ * <li> for each proxy interface, {@code intf},
+ * the caller's class loader is not the same as or an
+ * ancestor of the class loader for {@code intf} and
+ * invocation of {@link SecurityManager#checkPackageAccess
+ * s.checkPackageAccess()} denies access to {@code intf};</li>
+ * <li> any of the given proxy interfaces is non-public and the
+ * caller class is not in the same {@linkplain Package runtime package}
+ * as the non-public interface and the invocation of
+ * {@link SecurityManager#checkPermission s.checkPermission} with
+ * {@code ReflectPermission("newProxyInPackage.{package name}")}
+ * permission denies access.</li>
+ * </ul>
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}, or
* if the invocation handler, {@code h}, is
@@ -731,38 +804,47 @@
InvocationHandler h)
throws IllegalArgumentException
{
- if (h == null) {
- throw new NullPointerException();
- }
+ Objects.requireNonNull(h);
+
+ final Class<?>[] intfs = interfaces.clone();
+ // Android-changed: sm is always null
+ // final SecurityManager sm = System.getSecurityManager();
+ // if (sm != null) {
+ // checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
+ // }
/*
* Look up or generate the designated proxy class.
*/
- Class<?> cl = getProxyClass0(loader, interfaces);
+ Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
- final Constructor<?> cons = cl.getConstructor(constructorParams);
- return newInstance(cons, h);
- } catch (NoSuchMethodException e) {
- throw new InternalError(e.toString());
- }
- }
+ // Android-changed: sm is always null
+ // if (sm != null) {
+ // checkNewProxyPermission(Reflection.getCallerClass(), cl);
+ // }
- private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
- try {
- return cons.newInstance(new Object[] {h} );
- } catch (IllegalAccessException | InstantiationException e) {
- throw new InternalError(e.toString());
+ final Constructor<?> cons = cl.getConstructor(constructorParams);
+ final InvocationHandler ih = h;
+ if (!Modifier.isPublic(cl.getModifiers())) {
+ // Android-changed: Removed AccessController.doPrivileged
+ cons.setAccessible(true);
+ }
+ return cons.newInstance(new Object[]{h});
+ } catch (IllegalAccessException|InstantiationException e) {
+ throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
- throw new InternalError(t.toString());
+ throw new InternalError(t.toString(), t);
}
+ } catch (NoSuchMethodException e) {
+ throw new InternalError(e.toString(), e);
}
}
@@ -781,11 +863,7 @@
* @throws NullPointerException if {@code cl} is {@code null}
*/
public static boolean isProxyClass(Class<?> cl) {
- if (cl == null) {
- throw new NullPointerException();
- }
-
- return proxyClasses.containsKey(cl);
+ return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}
/**
@@ -795,19 +873,40 @@
* @return the invocation handler for the proxy instance
* @throws IllegalArgumentException if the argument is not a
* proxy instance
+ * @throws SecurityException if a security manager, <em>s</em>, is present
+ * and the caller's class loader is not the same as or an
+ * ancestor of the class loader for the invocation handler
+ * and invocation of {@link SecurityManager#checkPackageAccess
+ * s.checkPackageAccess()} denies access to the invocation
+ * handler's class.
*/
+ @CallerSensitive
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
{
/*
* Verify that the object is actually a proxy instance.
*/
- if (!(proxy instanceof Proxy)) {
+ if (!isProxyClass(proxy.getClass())) {
throw new IllegalArgumentException("not a proxy instance");
}
- return ((Proxy) proxy).h;
+
+ final Proxy p = (Proxy) proxy;
+ final InvocationHandler ih = p.h;
+ // Android changed, System.getSecurityManager() is always null
+ // if (System.getSecurityManager() != null) {
+ // Class<?> ihClass = ih.getClass();
+ // Class<?> caller = Reflection.getCallerClass();
+ // if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),
+ // ihClass.getClassLoader()))
+ // {
+ // ReflectUtil.checkPackageAccess(ihClass);
+ // }
+ // }
+ return ih;
}
+ // Android-changed: helper for art native code.
private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
InvocationHandler h = proxy.h;
return h.invoke(proxy, method, args);
diff --git a/ojluni/src/main/java/java/lang/reflect/WeakCache.java b/ojluni/src/main/java/java/lang/reflect/WeakCache.java
new file mode 100644
index 0000000..4c28024
--- /dev/null
+++ b/ojluni/src/main/java/java/lang/reflect/WeakCache.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2013, 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 <K> type of keys
+ * @param <P> type of parameters
+ * @param <V> type of values
+ */
+final class WeakCache<K, P, V> {
+
+ private final ReferenceQueue<K> refQueue
+ = new ReferenceQueue<>();
+ // the key type is Object for supporting null key
+ private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
+ = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
+ = new ConcurrentHashMap<>();
+ private final BiFunction<K, P, ?> subKeyFactory;
+ private final BiFunction<K, P, V> 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<K, P, ?> subKeyFactory,
+ BiFunction<K, P, V> 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<Object, Supplier<V>> valuesMap = map.get(cacheKey);
+ if (valuesMap == null) {
+ ConcurrentMap<Object, Supplier<V>> oldValuesMap
+ = map.putIfAbsent(cacheKey,
+ valuesMap = new ConcurrentHashMap<>());
+ if (oldValuesMap != null) {
+ valuesMap = oldValuesMap;
+ }
+ }
+
+ // create subKey and retrieve the possible Supplier<V> stored by that
+ // subKey from valuesMap
+ Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
+ Supplier<V> supplier = valuesMap.get(subKey);
+ Factory factory = null;
+
+ while (true) {
+ if (supplier != null) {
+ // supplier might be a Factory or a CacheValue<V> 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();
+ }
+
+ private void expungeStaleEntries() {
+ CacheKey<K> cacheKey;
+ while ((cacheKey = (CacheKey<K>)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<V> {
+
+ private final K key;
+ private final P parameter;
+ private final Object subKey;
+ private final ConcurrentMap<Object, Supplier<V>> valuesMap;
+
+ Factory(K key, P parameter, Object subKey,
+ ConcurrentMap<Object, Supplier<V>> valuesMap) {
+ this.key = key;
+ this.parameter = parameter;
+ this.subKey = subKey;
+ this.valuesMap = valuesMap;
+ }
+
+ @Override
+ public synchronized V get() { // serialize access
+ // re-check
+ Supplier<V> 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<V> 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<V> extends Supplier<V> {}
+
+ /**
+ * 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<V> implements Value<V> {
+ 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<V>
+ extends WeakReference<V> implements Value<V>
+ {
+ 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<K> extends WeakReference<K> {
+
+ // a replacement for null keys
+ private static final Object NULL_KEY = new Object();
+
+ static <K> Object valueOf(K key, ReferenceQueue<K> 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<K> refQueue) {
+ super(key, refQueue);
+ this.hash = System.identityHashCode(key); // compare by identity
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ 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<K>) obj).get();
+ }
+
+ void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
+ ConcurrentMap<?, Boolean> 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);
+ }
+ }
+ }
+ }
+}
diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk
index 8c8700e..d27ef31 100644
--- a/openjdk_java_files.mk
+++ b/openjdk_java_files.mk
@@ -198,6 +198,7 @@
ojluni/src/main/java/java/lang/reflect/Type.java \
ojluni/src/main/java/java/lang/reflect/TypeVariable.java \
ojluni/src/main/java/java/lang/reflect/UndeclaredThrowableException.java \
+ ojluni/src/main/java/java/lang/reflect/WeakCache.java \
ojluni/src/main/java/java/lang/reflect/WildcardType.java \
ojluni/src/main/java/java/lang/reflect/package-info.java \
ojluni/src/main/java/java/lang/ref/PhantomReference.java \