1 package emissary.util; 2 3 import jakarta.annotation.Nullable; 4 5 import java.lang.ref.SoftReference; 6 7 /** 8 * This implements a simple caching mechanism for {@link Class#forName(String)}. For example if the same class name is 9 * looked up repeatedly, using this cache may be able to avoid a lot of JVM reflection overhead. 10 * 11 * <p> 12 * To use this, just call {@link ClassLookupCache#lookup(String)} where you would normally use 13 * {@link Class#forName(String)}. There are also methods for directly manipulating the cache but most uses can avoid 14 * those. 15 * 16 * <p> 17 * Note that the cache implementation may have a small capacity and/or be thread-specific, so storing something in the 18 * cache does not <i>guarantee</i> that it will be indefinitely cached or that the cached value will be visible to other 19 * threads. 20 */ 21 public final class ClassLookupCache { 22 23 /** 24 * A binding from a name string to a {@link Class} object. 25 * 26 * @param <T> The object type that will be produced by the class. 27 */ 28 private static final class NamedClass<T> { 29 30 /** The class name. */ 31 private final String className; 32 33 /** A class object that matches {@link #className}. */ 34 private final Class<T> clazz; 35 36 /** 37 * Create a new binding between a class name and a matching object. 38 * 39 * @param className the class name. 40 * @param clazz A class object that matches {@code className}. 41 */ 42 private NamedClass(final String className, final Class<T> clazz) { 43 this.className = className; 44 this.clazz = clazz; 45 } 46 47 /** 48 * Get the class object, if it has the given name. 49 * 50 * @param desiredName The class name to check. 51 * @return If this instance has the given {@code desiredName}, this returns its {@link Class} object. Otherwise 52 * {@code null}. 53 */ 54 @Nullable 55 public Class<T> getClass(final String desiredName) { 56 return this.className.equals(desiredName) ? this.clazz : null; 57 } 58 59 /** 60 * Get a binding between a class name and a matching object. 61 * 62 * <p> 63 * The reason we use a static builder method is that it can handle wildcards such as {@code <?>} more easily than when 64 * calling the constructor directly. 65 * 66 * @param className the class name. 67 * @param clazz A class object that matches {@code className}. 68 * @return A binding between the given name and class object. 69 */ 70 public static <T> NamedClass<T> getInstance(final String className, final Class<T> clazz) { 71 return new NamedClass<>(className, clazz); 72 } 73 } 74 75 /** 76 * A cached class object. If a thread is asked to repeatedly construct the same type of object, we can cache the class 77 * name lookup so that subsequent constructions can get to the {@link Class} object without doing a full lookup in the 78 * JVM. 79 */ 80 private static final ThreadLocal<SoftReference<NamedClass<?>>> cachedLookupResult = new ThreadLocal<>(); 81 82 /** 83 * Look up a class in the cache. 84 * 85 * @param className The class name to find. 86 * @return If the class name is currently known to the cache, the corresponding {@link Class} object is returned. 87 * Otherwise {@code null}. 88 */ 89 @Nullable 90 public static Class<?> get(final String className) { 91 // Currently there is at most one cached lookup per thread, so 92 // we can just check if the current thread knows about the 93 // given class name. 94 final SoftReference<NamedClass<?>> softResult = cachedLookupResult.get(); 95 if (softResult == null) { 96 return null; // Nothing is currently cached in this thread. 97 } 98 99 final NamedClass<?> actualResult = softResult.get(); 100 if (actualResult == null) { 101 return null; // There was something cached but it's been lost. 102 } 103 104 // We do have a cached lookup. It can be used iff it matches 105 // the given name. 106 return actualResult.getClass(className); 107 } 108 109 /** 110 * Store a class lookup in the cache. 111 * 112 * @param className The class name. 113 * @param clazz The class. Assumed to match {@code className}. 114 */ 115 public static void put(final String className, final Class<?> clazz) { 116 cachedLookupResult.set(new SoftReference<>(NamedClass.getInstance(className, clazz))); 117 } 118 119 /** 120 * Look up a class by name. This is basically a utility method that can be called instead of 121 * {@link Class#forName(String)}, and will try to use the cache to speed up the lookups. 122 * 123 * @param className The class name to get. 124 * @return The {@link Class} object corresponding to {@code className}. 125 * @throws ClassNotFoundException If the class name could not be resolved. 126 */ 127 public static Class<?> lookup(final String className) throws ClassNotFoundException { 128 final Class<?> cachedResult = get(className); 129 if (cachedResult != null) { 130 // We found the class in the cache. 131 return cachedResult; 132 } else { 133 // The given class name is not currently cached, so look 134 // it up directly. 135 final Class<?> uncachedResult = Class.forName(className); 136 137 // If we reach here, the class was found. Cache the 138 // result before returning. 139 put(className, uncachedResult); 140 return uncachedResult; 141 } 142 } 143 144 /** 145 * Destroy the ThreadLocal cache object 146 */ 147 public static void unload() { 148 cachedLookupResult.remove(); 149 } 150 151 /** This is a static utility class, so prevent instantiation. */ 152 private ClassLookupCache() {} 153 }