/*
 * Copyright (c) 2016, 2019, 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 jdk.internal.loader;

import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.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-ClassLoaderValue and sub-ClassLoaderValue.
Type parameters:
  • <CLV> – the type of concrete ClassLoaderValue (this type)
  • <V> – the type of values associated with ClassLoaderValue
/** * 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.
/** * Sole constructor. */
AbstractClassLoaderValue() {}
Returns the key component of this ClassLoaderValue. The key component of the root-ClassLoaderValue is the ClassLoaderValue itself, while the key component of a sub-ClassLoaderValue is what was given to construct it.
Returns:the key component of this ClassLoaderValue.
/** * 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.
Params:
  • key – the key component of the sub-ClassLoaderValue.
Type parameters:
  • <K> – the type of the key component.
Returns:a sub-ClassLoaderValue of this ClassLoaderValue for given 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 true if this ClassLoaderValue is equal to given clv or if this ClassLoaderValue was derived from given clv by a chain of sub(Object) invocations.
Params:
  • clv – the ClassLoaderValue to test this against
Returns:if this ClassLoaderValue is equal to given clv or its descendant
/** * 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 null if there is none.
Params:
  • cl – the ClassLoader for the associated value
Returns:the value associated with this ClassLoaderValue and given ClassLoader or null if there is none.
/** * 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 v with this ClassLoaderValue and given ClassLoader and returns null if there was no previously associated value or does nothing and returns previously associated value if there was one.
Params:
  • cl – the ClassLoader for the associated value
  • v – the value to associate
Returns:previously associated value or null if there was none
/** * 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 v and returns true or does nothing and returns false if there is no currently associated value or it is not equal to given value v.
Params:
  • cl – the ClassLoader for the associated value
  • v – the value to compare with currently associated value
Returns:true if the association was removed or false if not
/** * 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 mappingFunction, associates it and returns it.

Computation and association of the computed value is performed atomically by the 1st thread that requests a particular association while holding a lock associated with this ClassLoaderValue and given ClassLoader. Nested calls from the mappingFunction to get, putIfAbsent or computeIfAbsent for the same association are not allowed and throw IllegalStateException. Nested call to remove for the same association is allowed but will always return 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.

Params:
  • cl – the ClassLoader for the associated value
  • mappingFunction – the function to compute the value
Throws:
Returns:the value associated with this ClassLoaderValue and given ClassLoader.
/** * 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 cl and this or descendants of this ClassLoaderValue. This is not an atomic operation. Other threads may see some associations be already removed and others still present while this method is executing.

The sole intention of this method is to cleanup after a unit test that tests ClassLoaderValue directly. It is not intended for use in actual algorithms.

Params:
  • cl – the associated ClassLoader of the values to be removed
/** * 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();
Returns:a ConcurrentHashMap for given ClassLoader
/** * @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)); }
Returns:value extracted from the Memoizer if given memoizerOrValue parameter is a Memoizer or just return given parameter.
/** * @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 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 get() method and remembered. If the Memoizer is invoked recursively from the given mappingFunction, RecursiveInvocationException is thrown, but it is not remembered. The in-flight call to the get() can still complete successfully if such exception is handled by the mappingFunction.
/** * 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 { @java.io.Serial private static final long serialVersionUID = 1L; RecursiveInvocationException() { super("Recursive call"); } } }
sub-ClassLoaderValue is an inner class of AbstractClassLoaderValue and also a subclass of it. It can therefore be instantiated as an inner class of either an instance of root-ClassLoaderValue or another instance of itself. This enables composing type-safe compound keys of arbitrary length:

ClassLoaderValue<V> clv = new ClassLoaderValue<>();
ClassLoaderValue<V>.Sub<K1>.Sub<K2>.Sub<K3> clv_k123 =
    clv.sub(k1).sub(k2).sub(k3);
From which individual components are accessible in a type-safe way:

K1 k1 = clv_k123.parent().parent().key();
K2 k2 = clv_k123.parent().key();
K3 k3 = clv_k123.key();
This allows specifying non-capturing lambdas for the mapping function of AbstractClassLoaderValue.computeIfAbsent(ClassLoader, BiFunction) operation that can access individual key components from passed-in sub-[sub-...]ClassLoaderValue instance in a type-safe way.
Type parameters:
  • <K> – the type of key() component contained in the sub-ClassLoaderValue.
/** * 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; }
Returns:the parent ClassLoaderValue this sub-ClassLoaderValue has been derived from.
/** * @return the parent ClassLoaderValue this sub-ClassLoaderValue * has been {@link #sub(Object) derived} from. */
public AbstractClassLoaderValue<CLV, V> parent() { return AbstractClassLoaderValue.this; }
Returns:the key component of this sub-ClassLoaderValue.
/** * @return the key component of this sub-ClassLoaderValue. */
@Override public K key() { return key; }
sub-ClassLoaderValue is a descendant of given clv if it is either equal to it or if its parent is a descendant of given clv.
/** * 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); } } }