net.bytebuddy.TypeCache Maven / Gradle / Ivy
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.build.CachedReturnPlugin;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
*
* A cache for storing types without strongly referencing any class loader or type.
*
*
* Note: In order to clean obsolete class loader references from the map, {@link TypeCache#expungeStaleEntries()} must be called
* regularly. This can happen in a different thread, in custom intervals or on every use of the cache by creating an instance of
* {@link WithInlineExpunction}. This cache is fully thread-safe.
*
*
* Important: The behavior of a type cache might not be as expected. A class is only eligible for garbage collection once its class
* loader is eligible for garbage collection. At the same time, a garbage collector is only eligible for garbage collection once all of
* its classes are eligible for garbage collection. If this cache referenced the cached type strongly, this would never be the case which
* is why this cache maintains either strong or weak references. In the latter case, a type is typically retained until the last instance of
* the type is not eligible for garbage collection. With soft references, the type is typically retained until the next full garbage collection
* where all instances of the type are eligible for garbage collection.
*
*
* @param The type of the key that is used for identifying stored classes per class loader. Such keys must not strongly reference any
* types or class loaders without potentially corrupting the garbage eligibility of stored classes. As the storage is segmented
* by class loader, it is normally sufficient to store types by their name.
* @see WithInlineExpunction
* @see SimpleKey
*/
public class TypeCache extends ReferenceQueue {
/**
* Indicates that a type was not found.
*/
@AlwaysNull
private static final Class> NOT_FOUND = null;
/**
* The reference type to use for stored types.
*/
protected final Sort sort;
/**
* The underlying map containing cached objects.
*/
protected final ConcurrentMap> cache;
/**
* Creates a new type cache with strong references to the stored types.
*/
public TypeCache() {
this(Sort.STRONG);
}
/**
* Creates a new type cache.
*
* @param sort The reference type to use for stored types.
*/
public TypeCache(Sort sort) {
this.sort = sort;
cache = new ConcurrentHashMap>();
}
/**
* Finds a stored type or returns {@code null} if no type was stored.
*
* @param classLoader The class loader for which this type is stored or {@code null} for the bootstrap loader.
* @param key The key for the type in question.
* @return The stored type or {@code null} if no type was stored.
*/
@MaybeNull
@SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended.")
public Class> find(@MaybeNull ClassLoader classLoader, T key) {
ConcurrentMap storage = cache.get(new LookupKey(classLoader));
if (storage == null) {
return NOT_FOUND;
} else {
Object value = storage.get(key);
if (value == null) {
return NOT_FOUND;
} else if (value instanceof Reference>) {
return (Class>) ((Reference>) value).get();
} else {
return (Class>) value;
}
}
}
/**
* Inserts a new type into the cache. If a type with the same class loader and key was inserted previously, the cache is not updated.
*
* @param classLoader The class loader for which this type is stored or {@code null} for the bootstrap loader.
* @param key The key for the type in question.
* @param type The type to insert of no previous type was stored in the cache.
* @return The supplied type or a previously submitted type for the same class loader and key combination.
*/
@SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended.")
public Class> insert(@MaybeNull ClassLoader classLoader, T key, Class> type) {
ConcurrentMap storage = cache.get(new LookupKey(classLoader));
if (storage == null) {
storage = new ConcurrentHashMap();
ConcurrentMap previous = cache.putIfAbsent(new StorageKey(classLoader, this), storage);
if (previous != null) {
storage = previous;
}
}
Object value = sort.wrap(type), previous = storage.putIfAbsent(key, value);
while (previous != null) {
Class> previousType = (Class>) (previous instanceof Reference>
? ((Reference>) previous).get()
: previous);
if (previousType != null) {
return previousType;
} else if (storage.remove(key, previous)) {
previous = storage.putIfAbsent(key, value);
} else {
previous = storage.get(key);
if (previous == null) {
previous = storage.putIfAbsent(key, value);
}
}
}
return type;
}
/**
* Finds an existing type or inserts a new one if the previous type was not found.
*
* @param classLoader The class loader for which this type is stored or {@code null} for the bootstrap loader.
* @param key The key for the type in question.
* @param lazy A lazy creator for the type to insert of no previous type was stored in the cache.
* @return The lazily created type or a previously submitted type for the same class loader and key combination.
*/
public Class> findOrInsert(@MaybeNull ClassLoader classLoader, T key, Callable> lazy) {
Class> type = find(classLoader, key);
if (type != null) {
return type;
} else {
try {
return insert(classLoader, key, lazy.call());
} catch (Throwable throwable) {
throw new IllegalArgumentException("Could not create type", throwable);
}
}
}
/**
* Finds an existing type or inserts a new one if the previous type was not found.
*
* @param classLoader The class loader for which this type is stored or {@code null} for the bootstrap loader.
* @param key The key for the type in question.
* @param lazy A lazy creator for the type to insert of no previous type was stored in the cache.
* @param monitor A monitor to lock before creating the lazy type.
* @return The lazily created type or a previously submitted type for the same class loader and key combination.
*/
public Class> findOrInsert(@MaybeNull ClassLoader classLoader, T key, Callable> lazy, Object monitor) {
Class> type = find(classLoader, key);
if (type != null) {
return type;
} else {
synchronized (monitor) {
return findOrInsert(classLoader, key, lazy);
}
}
}
/**
* Removes any stale class loader entries from the cache.
*/
public void expungeStaleEntries() {
Reference> reference;
while ((reference = poll()) != null) {
cache.remove(reference);
}
}
/**
* Clears the entire cache.
*/
public void clear() {
cache.clear();
}
/**
* Determines the storage format for a cached type.
*/
public enum Sort {
/**
* Creates a cache where cached types are wrapped by {@link WeakReference}s.
*/
WEAK {
@Override
protected Reference> wrap(Class> type) {
return new WeakReference>(type);
}
},
/**
* Creates a cache where cached types are wrapped by {@link SoftReference}s.
*/
SOFT {
@Override
protected Reference> wrap(Class> type) {
return new SoftReference>(type);
}
},
/**
* Creates a cache where cached types are strongly referenced.
*/
STRONG {
@Override
protected Class> wrap(Class> type) {
return type;
}
};
/**
* Wraps a type as a {@link Reference}.
*
* @param type The type to wrap.
* @return The reference that represents the type.
*/
protected abstract Object wrap(Class> type);
}
/**
* A key used for looking up a previously inserted class loader cache.
*/
protected static class LookupKey {
/**
* The referenced class loader or {@code null} for the bootstrap loader.
*/
@MaybeNull
private final ClassLoader classLoader;
/**
* The class loader's identity hash code.
*/
private final int hashCode;
/**
* Creates a new lookup key.
*
* @param classLoader The represented class loader or {@code null} for the bootstrap loader.
*/
protected LookupKey(@MaybeNull ClassLoader classLoader) {
this.classLoader = classLoader;
hashCode = System.identityHashCode(classLoader);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
@SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended.")
public boolean equals(@MaybeNull Object other) {
if (this == other) {
return true;
} else if (other instanceof LookupKey) {
return classLoader == ((LookupKey) other).classLoader;
} else if (other instanceof StorageKey) {
StorageKey storageKey = (StorageKey) other;
return hashCode == storageKey.hashCode && classLoader == storageKey.get();
} else {
return false;
}
}
}
/**
* A key used for storing a class loader cache reference.
*/
protected static class StorageKey extends WeakReference {
/**
* The class loader's identity hash code.
*/
private final int hashCode;
/**
* Creates a new storage key.
*
* @param classLoader The represented class loader or {@code null} for the bootstrap loader.
* @param referenceQueue The reference queue to notify upon a garbage collection.
*/
protected StorageKey(@MaybeNull ClassLoader classLoader, ReferenceQueue super ClassLoader> referenceQueue) {
super(classLoader, referenceQueue);
hashCode = System.identityHashCode(classLoader);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
@SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended.")
public boolean equals(@MaybeNull Object other) {
if (this == other) {
return true;
} else if (other instanceof LookupKey) {
LookupKey lookupKey = (LookupKey) other;
return hashCode == lookupKey.hashCode && get() == lookupKey.classLoader;
} else if (other instanceof StorageKey) {
StorageKey storageKey = (StorageKey) other;
return hashCode == storageKey.hashCode && get() == storageKey.get();
} else {
return false;
}
}
}
/**
* An implementation of a {@link TypeCache} where obsolete references are cleared upon any call.
*
* @param The type of the key that is used for identifying stored classes per class loader. Such keys must not strongly reference any
* types or class loaders without potentially corrupting the garbage eligibility of stored classes. As the storage is segmented
* by class loader, it is normally sufficient to store types by their name.
* @see TypeCache
*/
public static class WithInlineExpunction extends TypeCache {
/**
* Creates a new type cache with inlined expunction and strong references to the stored types.
*/
public WithInlineExpunction() {
this(Sort.STRONG);
}
/**
* Creates a new type cache with inlined expunction.
*
* @param sort The reference type to use for stored types.
*/
public WithInlineExpunction(Sort sort) {
super(sort);
}
/**
* {@inheritDoc}
*/
public Class> find(@MaybeNull ClassLoader classLoader, S key) {
try {
return super.find(classLoader, key);
} finally {
expungeStaleEntries();
}
}
/**
* {@inheritDoc}
*/
public Class> insert(@MaybeNull ClassLoader classLoader, S key, Class> type) {
try {
return super.insert(classLoader, key, type);
} finally {
expungeStaleEntries();
}
}
/**
* {@inheritDoc}
*/
public Class> findOrInsert(@MaybeNull ClassLoader classLoader, S key, Callable> builder) {
try {
return super.findOrInsert(classLoader, key, builder);
} finally {
expungeStaleEntries();
}
}
/**
* {@inheritDoc}
*/
public Class> findOrInsert(@MaybeNull ClassLoader classLoader, S key, Callable> builder, Object monitor) {
try {
return super.findOrInsert(classLoader, key, builder, monitor);
} finally {
expungeStaleEntries();
}
}
}
/**
* A simple key based on a collection of types where no type is strongly referenced.
*/
public static class SimpleKey {
/**
* The referenced types.
*/
private final Set types;
/**
* Creates a simple cache key..
*
* @param type The first type to be represented by this key.
* @param additionalType Any additional types to be represented by this key.
*/
public SimpleKey(Class> type, Class>... additionalType) {
this(type, Arrays.asList(additionalType));
}
/**
* Creates a simple cache key..
*
* @param type The first type to be represented by this key.
* @param additionalTypes Any additional types to be represented by this key.
*/
public SimpleKey(Class> type, Collection extends Class>> additionalTypes) {
this(CompoundList.of(type, new ArrayList>(additionalTypes)));
}
/**
* Creates a simple cache key..
*
* @param types Any types to be represented by this key.
*/
public SimpleKey(Collection extends Class>> types) {
this.types = new HashSet();
for (Class> type : types) {
this.types.add(type.getName());
}
}
@Override
@CachedReturnPlugin.Enhance("hashCode")
public int hashCode() {
return types.hashCode();
}
@Override
public boolean equals(@MaybeNull Object other) {
if (this == other) {
return true;
} else if (other == null || getClass() != other.getClass()) {
return false;
}
SimpleKey simpleKey = (SimpleKey) other;
return types.equals(simpleKey.types);
}
}
}