8164547: Make java.lang.reflect.ClassLoaderValue public for internal use
Summary: Move it to jdk.internal.loader and make it public
Reviewed-by: shade, alanb
--- a/jdk/src/java.base/share/classes/java/lang/reflect/AbstractClassLoaderValue.java Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,431 +0,0 @@
-/*
- * 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 <CLV> the type of concrete ClassLoaderValue (this type)
- * @param <V> the type of values associated with ClassLoaderValue
- */
-abstract class AbstractClassLoaderValue<CLV extends AbstractClassLoaderValue<CLV, V>, 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 <K> the type of the key component.
- * @return a sub-ClassLoaderValue of this ClassLoaderValue for given key
- */
- public <K> Sub<K> 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<?, V> 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.<CLV>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<CLV, Object> 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.<CLV>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.
- * <p>
- * 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 this ClassLoaderValue and given
- * ClassLoader.
- * @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<CLV, Object> map = map(cl);
- @SuppressWarnings("unchecked")
- CLV clv = (CLV) this;
- Memoizer<CLV, V> 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.
- * <p>
- * 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<CLV, Object> map = map(cl);
- for (Iterator<CLV> 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 <CLV extends AbstractClassLoaderValue<CLV, ?>>
- ConcurrentHashMap<CLV, Object> map(ClassLoader cl) {
- return (ConcurrentHashMap<CLV, Object>)
- (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<?, V>) memoizerOrValue).get();
- } else {
- return (V) memoizerOrValue;
- }
- }
-
- /**
- * A memoized supplier that invokes given {@code mappingFunction} just once
- * and remembers the result or thrown exception for subsequent calls.
- * If given mappingFunction returns null, it is converted to NullPointerException,
- * thrown from the Memoizer's {@link #get()} method and remembered.
- * If the Memoizer is invoked recursively from the given {@code mappingFunction},
- * {@link RecursiveInvocationException} is thrown, but it is not remembered.
- * 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<CLV extends AbstractClassLoaderValue<CLV, V>, V>
- implements Supplier<V> {
-
- private final ClassLoader cl;
- private final CLV clv;
- private final BiFunction<? super ClassLoader, ? super CLV, ? extends V>
- mappingFunction;
-
- private volatile V v;
- private volatile Throwable t;
- private boolean inCall;
-
- Memoizer(ClassLoader cl,
- CLV clv,
- BiFunction<? super ClassLoader, ? super CLV, ? extends V>
- 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:
- * <pre>{@code
- * ClassLoaderValue<V> clv = new ClassLoaderValue<>();
- * ClassLoaderValue<V>.Sub<K1>.Sub<K2>.Sub<K3> clv_k123 =
- * clv.sub(k1).sub(k2).sub(k3);
- * }</pre>
- * From which individual components are accessible in a type-safe way:
- * <pre>{@code
- * K1 k1 = clv_k123.parent().parent().key();
- * K2 k2 = clv_k123.parent().key();
- * K3 k3 = clv_k123.key();
- * }</pre>
- * 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 <K> the type of {@link #key()} component contained in the
- * sub-ClassLoaderValue.
- */
- final class Sub<K> extends AbstractClassLoaderValue<Sub<K>, 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<CLV, V> 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<?, V> 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);
- }
- }
-}
--- a/jdk/src/java.base/share/classes/java/lang/reflect/ClassLoaderValue.java Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * 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.
- * <p>
- * 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.
- * <p>
- * Example usage:
- * <pre>{@code
- * // create a root instance which represents a namespace and declares the type of
- * // associated values (Class instances in this example)
- * static final ClassLoaderValue<Class<?>> proxyClasses = new ClassLoaderValue<>();
- *
- * // create a compound key composed of a Module and a list of interfaces
- * Module module = ...;
- * List<Class<?>> interfaces = ...;
- * ClassLoaderValue<Class<?>>.Sub<Module>.Sub<List<Class<?>>> 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<Class<?>> intfcs = ky.key();
- * Module m = ky.parent().key();
- * Class<?> clazz = defineProxyClass(ld, m, intfcs);
- * return clazz;
- * });
- * }</pre>
- * <p>
- * {@code classLoaderValue.<operation>(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 <V> 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<V>
- extends AbstractClassLoaderValue<ClassLoaderValue<V>, V> {
-
- /**
- * Constructs new root-ClassLoaderValue representing its own namespace.
- */
- public ClassLoaderValue() {}
-
- /**
- * @return the key component of this root-ClassLoaderValue (itself).
- */
- @Override
- public ClassLoaderValue<V> key() {
- return this;
- }
-
- /**
- * root-ClassLoaderValue can only be equal to itself and has no predecessors.
- */
- @Override
- public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {
- return equals(Objects.requireNonNull(clv));
- }
-}
--- a/jdk/src/java.base/share/classes/java/lang/reflect/Proxy.java Mon Aug 22 15:17:11 2016 +0900
+++ b/jdk/src/java.base/share/classes/java/lang/reflect/Proxy.java Mon Aug 22 13:16:51 2016 +0200
@@ -29,11 +29,9 @@
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -49,6 +47,7 @@
import jdk.internal.misc.VM;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
+import jdk.internal.loader.ClassLoaderValue;
import sun.reflect.misc.ReflectUtil;
import sun.security.action.GetPropertyAction;
import sun.security.util.SecurityConstants;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/loader/AbstractClassLoaderValue.java Mon Aug 22 13:16:51 2016 +0200
@@ -0,0 +1,431 @@
+/*
+ * 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 jdk.internal.loader;
+
+import jdk.internal.misc.JavaLangAccess;
+import jdk.internal.misc.SharedSecrets;
+
+import java.lang.reflect.UndeclaredThrowableException;
+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 <CLV> the type of concrete ClassLoaderValue (this type)
+ * @param <V> the type of values associated with ClassLoaderValue
+ */
+public abstract class AbstractClassLoaderValue<CLV extends AbstractClassLoaderValue<CLV, V>, 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 <K> the type of the key component.
+ * @return a sub-ClassLoaderValue of this ClassLoaderValue for given key
+ */
+ public <K> Sub<K> 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<?, V> 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.<CLV>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<CLV, Object> 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.<CLV>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.
+ * <p>
+ * 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 this ClassLoaderValue and given
+ * ClassLoader.
+ * @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<CLV, Object> map = map(cl);
+ @SuppressWarnings("unchecked")
+ CLV clv = (CLV) this;
+ Memoizer<CLV, V> 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.
+ * <p>
+ * 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<CLV, Object> map = map(cl);
+ for (Iterator<CLV> 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 <CLV extends AbstractClassLoaderValue<CLV, ?>>
+ ConcurrentHashMap<CLV, Object> map(ClassLoader cl) {
+ return (ConcurrentHashMap<CLV, Object>)
+ (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<?, V>) memoizerOrValue).get();
+ } else {
+ return (V) memoizerOrValue;
+ }
+ }
+
+ /**
+ * A memoized supplier that invokes given {@code mappingFunction} just once
+ * and remembers the result or thrown exception for subsequent calls.
+ * If given mappingFunction returns null, it is converted to NullPointerException,
+ * thrown from the Memoizer's {@link #get()} method and remembered.
+ * If the Memoizer is invoked recursively from the given {@code mappingFunction},
+ * {@link RecursiveInvocationException} is thrown, but it is not remembered.
+ * 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<CLV extends AbstractClassLoaderValue<CLV, V>, V>
+ implements Supplier<V> {
+
+ private final ClassLoader cl;
+ private final CLV clv;
+ private final BiFunction<? super ClassLoader, ? super CLV, ? extends V>
+ mappingFunction;
+
+ private volatile V v;
+ private volatile Throwable t;
+ private boolean inCall;
+
+ Memoizer(ClassLoader cl,
+ CLV clv,
+ BiFunction<? super ClassLoader, ? super CLV, ? extends V>
+ 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:
+ * <pre>{@code
+ * ClassLoaderValue<V> clv = new ClassLoaderValue<>();
+ * ClassLoaderValue<V>.Sub<K1>.Sub<K2>.Sub<K3> clv_k123 =
+ * clv.sub(k1).sub(k2).sub(k3);
+ * }</pre>
+ * From which individual components are accessible in a type-safe way:
+ * <pre>{@code
+ * K1 k1 = clv_k123.parent().parent().key();
+ * K2 k2 = clv_k123.parent().key();
+ * K3 k3 = clv_k123.key();
+ * }</pre>
+ * 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 <K> the type of {@link #key()} component contained in the
+ * sub-ClassLoaderValue.
+ */
+ public final class Sub<K> extends AbstractClassLoaderValue<Sub<K>, 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<CLV, V> 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<?, V> 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 Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/loader/ClassLoaderValue.java Mon Aug 22 13:16:51 2016 +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 jdk.internal.loader;
+
+import java.util.Objects;
+import java.util.function.BiFunction;
+
+/**
+ * root-ClassLoaderValue. Each instance defines a separate namespace for
+ * associated values.
+ * <p>
+ * 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.
+ * <p>
+ * Example usage:
+ * <pre>{@code
+ * // create a root instance which represents a namespace and declares the type of
+ * // associated values (Class instances in this example)
+ * static final ClassLoaderValue<Class<?>> proxyClasses = new ClassLoaderValue<>();
+ *
+ * // create a compound key composed of a Module and a list of interfaces
+ * Module module = ...;
+ * List<Class<?>> interfaces = ...;
+ * ClassLoaderValue<Class<?>>.Sub<Module>.Sub<List<Class<?>>> 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<Class<?>> intfcs = ky.key();
+ * Module m = ky.parent().key();
+ * Class<?> clazz = defineProxyClass(ld, m, intfcs);
+ * return clazz;
+ * });
+ * }</pre>
+ * <p>
+ * {@code classLoaderValue.<operation>(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 <V> the type of value(s) associated with the root-ClassLoaderValue and
+ * all its {@link #sub(Object) descendants}.
+ * @author Peter Levart
+ * @since 9
+ */
+public final class ClassLoaderValue<V>
+ extends AbstractClassLoaderValue<ClassLoaderValue<V>, V> {
+
+ /**
+ * Constructs new root-ClassLoaderValue representing its own namespace.
+ */
+ public ClassLoaderValue() {}
+
+ /**
+ * @return the key component of this root-ClassLoaderValue (itself).
+ */
+ @Override
+ public ClassLoaderValue<V> key() {
+ return this;
+ }
+
+ /**
+ * root-ClassLoaderValue can only be equal to itself and has no predecessors.
+ */
+ @Override
+ public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {
+ return equals(Objects.requireNonNull(clv));
+ }
+}
--- a/jdk/test/java/lang/reflect/ClassLoaderValue/Driver.java Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/*
- * 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
- * @bug 8152115
- * @summary functional and concurrency test for ClassLoaderValue
- * @build java.base/java.lang.reflect.ClassLoaderValueTest
- * @run main Driver
- */
-public class Driver {
- public static void main(String[] args) throws Exception {
- java.lang.reflect.ClassLoaderValueTest.main(args);
- }
-}
--- a/jdk/test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java Mon Aug 22 15:17:11 2016 +0900
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,249 +0,0 @@
-/*
- * 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<Integer> 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<Integer> clv : clvs) {
- for (ClassLoader ld : lds) {
- writeValidateOps(clv, ld, keys);
- }
- }
- for (ClassLoaderValue<Integer> 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<Integer> 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<Integer> 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<Integer>[] clvs,
- ClassLoader[] lds,
- Object[] keys,
- long millisRuntime) {
- ExecutorService exe = Executors.newCachedThreadPool();
- List<Future<?>> futures = new ArrayList<>();
- AtomicBoolean stop = new AtomicBoolean();
- for (ClassLoaderValue<Integer> 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);
- }
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/jdk/internal/loader/ClassLoaderValue/ClassLoaderValueTest.java Mon Aug 22 13:16:51 2016 +0200
@@ -0,0 +1,251 @@
+/*
+ * 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.
+ */
+
+import jdk.internal.loader.ClassLoaderValue;
+
+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;
+
+/**
+ * @test
+ * @bug 8152115
+ * @summary functional and concurrency test for ClassLoaderValue
+ * @modules java.base/jdk.internal.loader
+ * @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<Integer> 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<Integer> clv : clvs) {
+ for (ClassLoader ld : lds) {
+ writeValidateOps(clv, ld, keys);
+ }
+ }
+ for (ClassLoaderValue<Integer> 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<Integer> 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<Integer> 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<Integer>[] clvs,
+ ClassLoader[] lds,
+ Object[] keys,
+ long millisRuntime) {
+ ExecutorService exe = Executors.newCachedThreadPool();
+ List<Future<?>> futures = new ArrayList<>();
+ AtomicBoolean stop = new AtomicBoolean();
+ for (ClassLoaderValue<Integer> 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);
+ }
+ }
+}