ClassLookupCache.java
package emissary.util;
import java.lang.ref.SoftReference;
import javax.annotation.Nullable;
/**
* This implements a simple caching mechanism for {@link Class#forName(String)}. For example if the same class name is
* looked up repeatedly, using this cache may be able to avoid a lot of JVM reflection overhead.
*
* <p>
* To use this, just call {@link ClassLookupCache#lookup(String)} where you would normally use
* {@link Class#forName(String)}. There are also methods for directly manipulating the cache but most uses can avoid
* those.
*
* <p>
* Note that the cache implementation may have a small capacity and/or be thread-specific, so storing something in the
* cache does not <i>guarantee</i> that it will be indefinitely cached or that the cached value will be visible to other
* threads.
*/
public final class ClassLookupCache {
/**
* A binding from a name string to a {@link Class} object.
*
* @param <T> The object type that will be produced by the class.
*/
private static final class NamedClass<T> {
/** The class name. */
private final String className;
/** A class object that matches {@link #className}. */
private final Class<T> clazz;
/**
* Create a new binding between a class name and a matching object.
*
* @param className the class name.
* @param clazz A class object that matches {@code className}.
*/
private NamedClass(final String className, final Class<T> clazz) {
this.className = className;
this.clazz = clazz;
}
/**
* Get the class object, if it has the given name.
*
* @param desiredName The class name to check.
* @return If this instance has the given {@code desiredName}, this returns its {@link Class} object. Otherwise
* {@code null}.
*/
@Nullable
public Class<T> getClass(final String desiredName) {
return this.className.equals(desiredName) ? this.clazz : null;
}
/**
* Get a binding between a class name and a matching object.
*
* <p>
* The reason we use a static builder method is that it can handle wildcards such as {@code <?>} more easily than when
* calling the constructor directly.
*
* @param className the class name.
* @param clazz A class object that matches {@code className}.
* @return A binding between the given name and class object.
*/
public static <T> NamedClass<T> getInstance(final String className, final Class<T> clazz) {
return new NamedClass<>(className, clazz);
}
}
/**
* A cached class object. If a thread is asked to repeatedly construct the same type of object, we can cache the class
* name lookup so that subsequent constructions can get to the {@link Class} object without doing a full lookup in the
* JVM.
*/
private static final ThreadLocal<SoftReference<NamedClass<?>>> cachedLookupResult = new ThreadLocal<>();
/**
* Look up a class in the cache.
*
* @param className The class name to find.
* @return If the class name is currently known to the cache, the corresponding {@link Class} object is returned.
* Otherwise {@code null}.
*/
@Nullable
public static Class<?> get(final String className) {
// Currently there is at most one cached lookup per thread, so
// we can just check if the current thread knows about the
// given class name.
final SoftReference<NamedClass<?>> softResult = cachedLookupResult.get();
if (softResult == null) {
return null; // Nothing is currently cached in this thread.
}
final NamedClass<?> actualResult = softResult.get();
if (actualResult == null) {
return null; // There was something cached but it's been lost.
}
// We do have a cached lookup. It can be used iff it matches
// the given name.
return actualResult.getClass(className);
}
/**
* Store a class lookup in the cache.
*
* @param className The class name.
* @param clazz The class. Assumed to match {@code className}.
*/
public static void put(final String className, final Class<?> clazz) {
cachedLookupResult.set(new SoftReference<>(NamedClass.getInstance(className, clazz)));
}
/**
* Look up a class by name. This is basically a utility method that can be called instead of
* {@link Class#forName(String)}, and will try to use the cache to speed up the lookups.
*
* @param className The class name to get.
* @return The {@link Class} object corresponding to {@code className}.
* @throws ClassNotFoundException If the class name could not be resolved.
*/
public static Class<?> lookup(final String className) throws ClassNotFoundException {
final Class<?> cachedResult = get(className);
if (cachedResult != null) {
// We found the class in the cache.
return cachedResult;
} else {
// The given class name is not currently cached, so look
// it up directly.
final Class<?> uncachedResult = Class.forName(className);
// If we reach here, the class was found. Cache the
// result before returning.
put(className, uncachedResult);
return uncachedResult;
}
}
/**
* Destroy the ThreadLocal cache object
*/
public static void unload() {
cachedLookupResult.remove();
}
/** This is a static utility class, so prevent instantiation. */
private ClassLookupCache() {}
}