Path: blob/master/src/java.base/share/classes/jdk/internal/loader/AbstractClassLoaderValue.java
41159 views
/*1* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.internal.loader;2627import jdk.internal.access.JavaLangAccess;28import jdk.internal.access.SharedSecrets;2930import java.lang.reflect.UndeclaredThrowableException;31import java.util.Iterator;32import java.util.Objects;33import java.util.concurrent.ConcurrentHashMap;34import java.util.function.BiFunction;35import java.util.function.Supplier;3637/**38* AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue}39* and {@link Sub sub}-ClassLoaderValue.40*41* @param <CLV> the type of concrete ClassLoaderValue (this type)42* @param <V> the type of values associated with ClassLoaderValue43*/44public abstract class AbstractClassLoaderValue<CLV extends AbstractClassLoaderValue<CLV, V>, V> {4546/**47* Sole constructor.48*/49AbstractClassLoaderValue() {}5051/**52* Returns the key component of this ClassLoaderValue. The key component of53* the root-{@link ClassLoaderValue} is the ClassLoaderValue itself,54* while the key component of a {@link #sub(Object) sub}-ClassLoaderValue55* is what was given to construct it.56*57* @return the key component of this ClassLoaderValue.58*/59public abstract Object key();6061/**62* Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given63* key component.64*65* @param key the key component of the sub-ClassLoaderValue.66* @param <K> the type of the key component.67* @return a sub-ClassLoaderValue of this ClassLoaderValue for given key68*/69public <K> Sub<K> sub(K key) {70return new Sub<>(key);71}7273/**74* Returns {@code true} if this ClassLoaderValue is equal to given {@code clv}75* or if this ClassLoaderValue was derived from given {@code clv} by a chain76* of {@link #sub(Object)} invocations.77*78* @param clv the ClassLoaderValue to test this against79* @return if this ClassLoaderValue is equal to given {@code clv} or80* its descendant81*/82public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv);8384/**85* Returns the value associated with this ClassLoaderValue and given ClassLoader86* or {@code null} if there is none.87*88* @param cl the ClassLoader for the associated value89* @return the value associated with this ClassLoaderValue and given ClassLoader90* or {@code null} if there is none.91*/92public V get(ClassLoader cl) {93Object val = AbstractClassLoaderValue.<CLV>map(cl).get(this);94try {95return extractValue(val);96} catch (Memoizer.RecursiveInvocationException e) {97// propagate recursive get() for the same key that is just98// being calculated in computeIfAbsent()99throw e;100} catch (Throwable t) {101// don't propagate exceptions thrown from Memoizer - pretend102// that there was no entry103// (computeIfAbsent invocation will try to remove it anyway)104return null;105}106}107108/**109* Associates given value {@code v} with this ClassLoaderValue and given110* ClassLoader and returns {@code null} if there was no previously associated111* value or does nothing and returns previously associated value if there112* was one.113*114* @param cl the ClassLoader for the associated value115* @param v the value to associate116* @return previously associated value or null if there was none117*/118public V putIfAbsent(ClassLoader cl, V v) {119ConcurrentHashMap<CLV, Object> map = map(cl);120@SuppressWarnings("unchecked")121CLV clv = (CLV) this;122while (true) {123try {124Object val = map.putIfAbsent(clv, v);125return extractValue(val);126} catch (Memoizer.RecursiveInvocationException e) {127// propagate RecursiveInvocationException for the same key that128// is just being calculated in computeIfAbsent129throw e;130} catch (Throwable t) {131// don't propagate exceptions thrown from foreign Memoizer -132// pretend that there was no entry and retry133// (foreign computeIfAbsent invocation will try to remove it anyway)134}135// TODO:136// Thread.onSpinLoop(); // when available137}138}139140/**141* Removes the value associated with this ClassLoaderValue and given142* ClassLoader if the associated value is equal to given value {@code v} and143* returns {@code true} or does nothing and returns {@code false} if there is144* no currently associated value or it is not equal to given value {@code v}.145*146* @param cl the ClassLoader for the associated value147* @param v the value to compare with currently associated value148* @return {@code true} if the association was removed or {@code false} if not149*/150public boolean remove(ClassLoader cl, Object v) {151return AbstractClassLoaderValue.<CLV>map(cl).remove(this, v);152}153154/**155* Returns the value associated with this ClassLoaderValue and given156* ClassLoader if there is one or computes the value by invoking given157* {@code mappingFunction}, associates it and returns it.158* <p>159* Computation and association of the computed value is performed atomically160* by the 1st thread that requests a particular association while holding a161* lock associated with this ClassLoaderValue and given ClassLoader.162* Nested calls from the {@code mappingFunction} to {@link #get},163* {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association164* are not allowed and throw {@link IllegalStateException}. Nested call to165* {@link #remove} for the same association is allowed but will always return166* {@code false} regardless of passed-in comparison value. Nested calls for167* other association(s) are allowed, but care should be taken to avoid168* deadlocks. When two threads perform nested computations of the overlapping169* set of associations they should always request them in the same order.170*171* @param cl the ClassLoader for the associated value172* @param mappingFunction the function to compute the value173* @return the value associated with this ClassLoaderValue and given174* ClassLoader.175* @throws IllegalStateException if a direct or indirect invocation from176* within given {@code mappingFunction} that177* computes the value of a particular association178* to {@link #get}, {@link #putIfAbsent} or179* {@link #computeIfAbsent}180* for the same association is attempted.181*/182public V computeIfAbsent(ClassLoader cl,183BiFunction<184? super ClassLoader,185? super CLV,186? extends V187> mappingFunction) throws IllegalStateException {188ConcurrentHashMap<CLV, Object> map = map(cl);189@SuppressWarnings("unchecked")190CLV clv = (CLV) this;191Memoizer<CLV, V> mv = null;192while (true) {193Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv);194if (val == null) {195if (mv == null) {196// create Memoizer lazily when 1st needed and restart loop197mv = new Memoizer<>(cl, clv, mappingFunction);198continue;199}200// mv != null, therefore sv == null was a result of successful201// putIfAbsent202try {203// trigger Memoizer to compute the value204V v = mv.get();205// attempt to replace our Memoizer with the value206map.replace(clv, mv, v);207// return computed value208return v;209} catch (Throwable t) {210// our Memoizer has thrown, attempt to remove it211map.remove(clv, mv);212// propagate exception because it's from our Memoizer213throw t;214}215} else {216try {217return extractValue(val);218} catch (Memoizer.RecursiveInvocationException e) {219// propagate recursive attempts to calculate the same220// value as being calculated at the moment221throw e;222} catch (Throwable t) {223// don't propagate exceptions thrown from foreign Memoizer -224// pretend that there was no entry and retry225// (foreign computeIfAbsent invocation will try to remove it anyway)226}227}228// TODO:229// Thread.onSpinLoop(); // when available230}231}232233/**234* Removes all values associated with given ClassLoader {@code cl} and235* {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants}236* of this ClassLoaderValue.237* This is not an atomic operation. Other threads may see some associations238* be already removed and others still present while this method is executing.239* <p>240* The sole intention of this method is to cleanup after a unit test that241* tests ClassLoaderValue directly. It is not intended for use in242* actual algorithms.243*244* @param cl the associated ClassLoader of the values to be removed245*/246public void removeAll(ClassLoader cl) {247ConcurrentHashMap<CLV, Object> map = map(cl);248for (Iterator<CLV> i = map.keySet().iterator(); i.hasNext(); ) {249if (i.next().isEqualOrDescendantOf(this)) {250i.remove();251}252}253}254255private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();256257/**258* @return a ConcurrentHashMap for given ClassLoader259*/260@SuppressWarnings("unchecked")261private static <CLV extends AbstractClassLoaderValue<CLV, ?>>262ConcurrentHashMap<CLV, Object> map(ClassLoader cl) {263return (ConcurrentHashMap<CLV, Object>)264(cl == null ? BootLoader.getClassLoaderValueMap()265: JLA.createOrGetClassLoaderValueMap(cl));266}267268/**269* @return value extracted from the {@link Memoizer} if given270* {@code memoizerOrValue} parameter is a {@code Memoizer} or271* just return given parameter.272*/273@SuppressWarnings("unchecked")274private V extractValue(Object memoizerOrValue) {275if (memoizerOrValue instanceof Memoizer) {276return ((Memoizer<?, V>) memoizerOrValue).get();277} else {278return (V) memoizerOrValue;279}280}281282/**283* A memoized supplier that invokes given {@code mappingFunction} just once284* and remembers the result or thrown exception for subsequent calls.285* If given mappingFunction returns null, it is converted to NullPointerException,286* thrown from the Memoizer's {@link #get()} method and remembered.287* If the Memoizer is invoked recursively from the given {@code mappingFunction},288* {@link RecursiveInvocationException} is thrown, but it is not remembered.289* The in-flight call to the {@link #get()} can still complete successfully if290* such exception is handled by the mappingFunction.291*/292private static final class Memoizer<CLV extends AbstractClassLoaderValue<CLV, V>, V>293implements Supplier<V> {294295private final ClassLoader cl;296private final CLV clv;297private final BiFunction<? super ClassLoader, ? super CLV, ? extends V>298mappingFunction;299300private volatile V v;301private volatile Throwable t;302private boolean inCall;303304Memoizer(ClassLoader cl,305CLV clv,306BiFunction<? super ClassLoader, ? super CLV, ? extends V>307mappingFunction308) {309this.cl = cl;310this.clv = clv;311this.mappingFunction = mappingFunction;312}313314@Override315public V get() throws RecursiveInvocationException {316V v = this.v;317if (v != null) return v;318Throwable t = this.t;319if (t == null) {320synchronized (this) {321if ((v = this.v) == null && (t = this.t) == null) {322if (inCall) {323throw new RecursiveInvocationException();324}325inCall = true;326try {327this.v = v = Objects.requireNonNull(328mappingFunction.apply(cl, clv));329} catch (Throwable x) {330this.t = t = x;331} finally {332inCall = false;333}334}335}336}337if (v != null) return v;338if (t instanceof Error) {339throw (Error) t;340} else if (t instanceof RuntimeException) {341throw (RuntimeException) t;342} else {343throw new UndeclaredThrowableException(t);344}345}346347static class RecursiveInvocationException extends IllegalStateException {348@java.io.Serial349private static final long serialVersionUID = 1L;350351RecursiveInvocationException() {352super("Recursive call");353}354}355}356357/**358* sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue}359* and also a subclass of it. It can therefore be instantiated as an inner360* class of either an instance of root-{@link ClassLoaderValue} or another361* instance of itself. This enables composing type-safe compound keys of362* arbitrary length:363* <pre>{@code364* ClassLoaderValue<V> clv = new ClassLoaderValue<>();365* ClassLoaderValue<V>.Sub<K1>.Sub<K2>.Sub<K3> clv_k123 =366* clv.sub(k1).sub(k2).sub(k3);367* }</pre>368* From which individual components are accessible in a type-safe way:369* <pre>{@code370* K1 k1 = clv_k123.parent().parent().key();371* K2 k2 = clv_k123.parent().key();372* K3 k3 = clv_k123.key();373* }</pre>374* This allows specifying non-capturing lambdas for the mapping function of375* {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can376* access individual key components from passed-in377* sub-[sub-...]ClassLoaderValue instance in a type-safe way.378*379* @param <K> the type of {@link #key()} component contained in the380* sub-ClassLoaderValue.381*/382public final class Sub<K> extends AbstractClassLoaderValue<Sub<K>, V> {383384private final K key;385386Sub(K key) {387this.key = key;388}389390/**391* @return the parent ClassLoaderValue this sub-ClassLoaderValue392* has been {@link #sub(Object) derived} from.393*/394public AbstractClassLoaderValue<CLV, V> parent() {395return AbstractClassLoaderValue.this;396}397398/**399* @return the key component of this sub-ClassLoaderValue.400*/401@Override402public K key() {403return key;404}405406/**407* sub-ClassLoaderValue is a descendant of given {@code clv} if it is408* either equal to it or if its {@link #parent() parent} is a409* descendant of given {@code clv}.410*/411@Override412public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {413return equals(Objects.requireNonNull(clv)) ||414parent().isEqualOrDescendantOf(clv);415}416417@Override418public boolean equals(Object o) {419if (this == o) return true;420if (!(o instanceof Sub)) return false;421@SuppressWarnings("unchecked")422Sub<?> that = (Sub<?>) o;423return this.parent().equals(that.parent()) &&424Objects.equals(this.key, that.key);425}426427@Override428public int hashCode() {429return 31 * parent().hashCode() +430Objects.hashCode(key);431}432}433}434435436