1 /*
2 * Copyright 2014 - 2020 Rafael Winterhalter
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.bytebuddy;
17
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import net.bytebuddy.build.CachedReturnPlugin;
20 import net.bytebuddy.utility.CompoundList;
21
22 import java.lang.ref.Reference;
23 import java.lang.ref.ReferenceQueue;
24 import java.lang.ref.SoftReference;
25 import java.lang.ref.WeakReference;
26 import java.util.*;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap;
30
31 /**
32 * <p>
33 * A cache for storing types without strongly referencing any class loader or type.
34 * </p>
35 * <p>
36 * <b>Note</b>: In order to clean obsolete class loader references from the map, {@link TypeCache#expungeStaleEntries()} must be called
37 * regularly. This can happen in a different thread, in custom intervals or on every use of the cache by creating an instance of
38 * {@link WithInlineExpunction}. This cache is fully thread-safe.
39 * </p>
40 * <p>
41 * <b>Important</b>: The behavior of a type cache might not be as expected. A class is only eligible for garbage collection once its class
42 * loader is eligible for garbage collection. At the same time, a garbage collector is only eligible for garbage collection once all of
43 * its classes are eligible for garbage collection. If this cache referenced the cached type strongly, this would never be the case which
44 * is why this cache maintains either strong or weak references. In the latter case, a type is typically retained until the last instance of
45 * the type is not eligible for garbage collection. With soft references, the type is typically retained until the next full garbage collection
46 * where all instances of the type are eligible for garbage collection.
47 * </p>
48 *
49 * @param <T> The type of the key that is used for identifying stored classes per class loader. Such keys must not strongly reference any
50 * types or class loaders without potentially corrupting the garbage eligibility of stored classes. As the storage is segmented
51 * by class loader, it is normally sufficient to store types by their name.
52 * @see WithInlineExpunction
53 * @see SimpleKey
54 */
55 public class TypeCache<T> extends ReferenceQueue<ClassLoader> {
56
57 /**
58 * Indicates that a type was not found.
59 */
60 private static final Class<?> NOT_FOUND = null;
61
62 /**
63 * The reference type to use for stored types.
64 */
65 protected final Sort sort;
66
67 /**
68 * The underlying map containing cached objects.
69 */
70 protected final ConcurrentMap<StorageKey, ConcurrentMap<T, Reference<Class<?>>>> cache;
71
72 /**
73 * Creates a new type cache.
74 *
75 * @param sort The reference type to use for stored types.
76 */
77 public TypeCache(Sort sort) {
78 this.sort = sort;
79 cache = new ConcurrentHashMap<StorageKey, ConcurrentMap<T, Reference<Class<?>>>>();
80 }
81
82 /**
83 * Finds a stored type or returns {@code null} if no type was stored.
84 *
85 * @param classLoader The class loader for which this type is stored.
86 * @param key The key for the type in question.
87 * @return The stored type or {@code null} if no type was stored.
88 */
89 @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended")
90 public Class<?> find(ClassLoader classLoader, T key) {
91 ConcurrentMap<T, Reference<Class<?>>> storage = cache.get(new LookupKey(classLoader));
92 if (storage == null) {
93 return NOT_FOUND;
94 } else {
95 Reference<Class<?>> reference = storage.get(key);
96 if (reference == null) {
97 return NOT_FOUND;
98 } else {
99 return reference.get();
100 }
101 }
102 }
103
104 /**
105 * 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.
106 *
107 * @param classLoader The class loader for which this type is stored.
108 * @param key The key for the type in question.
109 * @param type The type to insert of no previous type was stored in the cache.
110 * @return The supplied type or a previously submitted type for the same class loader and key combination.
111 */
112 @SuppressFBWarnings(value = "GC_UNRELATED_TYPES", justification = "Cross-comparison is intended")
113 public Class<?> insert(ClassLoader classLoader, T key, Class<?> type) {
114 ConcurrentMap<T, Reference<Class<?>>> storage = cache.get(new LookupKey(classLoader));
115 if (storage == null) {
116 storage = new ConcurrentHashMap<T, Reference<Class<?>>>();
117 ConcurrentMap<T, Reference<Class<?>>> previous = cache.putIfAbsent(new StorageKey(classLoader, this), storage);
118 if (previous != null) {
119 storage = previous;
120 }
121 }
122 Reference<Class<?>> reference = sort.wrap(type), previous = storage.putIfAbsent(key, reference);
123 while (previous != null) {
124 Class<?> previousType = previous.get();
125 if (previousType != null) {
126 return previousType;
127 } else if (storage.remove(key, previous)) {
128 previous = storage.putIfAbsent(key, reference);
129 } else {
130 previous = storage.get(key);
131 if (previous == null) {
132 previous = storage.putIfAbsent(key, reference);
133 }
134 }
135 }
136 return type;
137 }
138
139 /**
140 * Finds an existing type or inserts a new one if the previous type was not found.
141 *
142 * @param classLoader The class loader for which this type is stored.
143 * @param key The key for the type in question.
144 * @param lazy A lazy creator for the type to insert of no previous type was stored in the cache.
145 * @return The lazily created type or a previously submitted type for the same class loader and key combination.
146 */
147 public Class<?> findOrInsert(ClassLoader classLoader, T key, Callable<Class<?>> lazy) {
148 Class<?> type = find(classLoader, key);
149 if (type != null) {
150 return type;
151 } else {
152 try {
153 return insert(classLoader, key, lazy.call());
154 } catch (Throwable throwable) {
155 throw new IllegalArgumentException("Could not create type", throwable);
156 }
157 }
158 }
159
160 /**
161 * Finds an existing type or inserts a new one if the previous type was not found.
162 *
163 * @param classLoader The class loader for which this type is stored.
164 * @param key The key for the type in question.
165 * @param lazy A lazy creator for the type to insert of no previous type was stored in the cache.
166 * @param monitor A monitor to lock before creating the lazy type.
167 * @return The lazily created type or a previously submitted type for the same class loader and key combination.
168 */
169 public Class<?> findOrInsert(ClassLoader classLoader, T key, Callable<Class<?>> lazy, Object monitor) {
170 Class<?> type = find(classLoader, key);
171 if (type != null) {
172 return type;
173 } else {
174 synchronized (monitor) {
175 return findOrInsert(classLoader, key, lazy);
176 }
177 }
178 }
179
180 /**
181 * Removes any stale class loader entries from the cache.
182 */
183 public void expungeStaleEntries() {
184 Reference<?> reference;
185 while ((reference = poll()) != null) {
186 cache.remove(reference);
187 }
188 }
189
190 /**
191 * Clears the entire cache.
192 */
193 public void clear() {
194 cache.clear();
195 }
196
197 /**
198 * Determines the storage format for a cached type.
199 */
200 public enum Sort {
201
202 /**
203 * Creates a cache where cached types are wrapped by {@link WeakReference}s.
204 */
205 WEAK {
206 @Override
207 protected Reference<Class<?>> wrap(Class<?> type) {
208 return new WeakReference<Class<?>>(type);
209 }
210 },
211
212 /**
213 * Creates a cache where cached types are wrapped by {@link SoftReference}s.
214 */
215 SOFT {
216 @Override
217 protected Reference<Class<?>> wrap(Class<?> type) {
218 return new SoftReference<Class<?>>(type);
219 }
220 };
221
222 /**
223 * Wraps a type as a {@link Reference}.
224 *
225 * @param type The type to wrap.
226 * @return The reference that represents the type.
227 */
228 protected abstract Reference<Class<?>> wrap(Class<?> type);
229 }
230
231 /**
232 * A key used for looking up a previously inserted class loader cache.
233 */
234 protected static class LookupKey {
235
236 /**
237 * The referenced class loader.
238 */
239 private final ClassLoader classLoader;
240
241 /**
242 * The class loader's identity hash code.
243 */
244 private final int hashCode;
245
246 /**
247 * Creates a new lookup key.
248 *
249 * @param classLoader The represented class loader.
250 */
251 protected LookupKey(ClassLoader classLoader) {
252 this.classLoader = classLoader;
253 hashCode = System.identityHashCode(classLoader);
254 }
255
256 @Override
257 public int hashCode() {
258 return hashCode;
259 }
260
261 @Override
262 @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended")
263 public boolean equals(Object other) {
264 if (this == other) {
265 return true;
266 } else if (other instanceof LookupKey) {
267 return classLoader == ((LookupKey) other).classLoader;
268 } else if (other instanceof StorageKey) {
269 StorageKey storageKey = (StorageKey) other;
270 return hashCode == storageKey.hashCode && classLoader == storageKey.get();
271 } else {
272 return false;
273 }
274 }
275 }
276
277 /**
278 * A key used for storing a class loader cache reference.
279 */
280 protected static class StorageKey extends WeakReference<ClassLoader> {
281
282 /**
283 * The class loader's identity hash code.
284 */
285 private final int hashCode;
286
287 /**
288 * Creates a new storage key.
289 *
290 * @param classLoader The represented class loader.
291 * @param referenceQueue The reference queue to notify upon a garbage collection.
292 */
293 protected StorageKey(ClassLoader classLoader, ReferenceQueue<? super ClassLoader> referenceQueue) {
294 super(classLoader, referenceQueue);
295 hashCode = System.identityHashCode(classLoader);
296 }
297
298 @Override
299 public int hashCode() {
300 return hashCode;
301 }
302
303 @Override
304 @SuppressFBWarnings(value = "EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", justification = "Cross-comparison is intended")
305 public boolean equals(Object other) {
306 if (this == other) {
307 return true;
308 } else if (other instanceof LookupKey) {
309 LookupKey lookupKey = (LookupKey) other;
310 return hashCode == lookupKey.hashCode && get() == lookupKey.classLoader;
311 } else if (other instanceof StorageKey) {
312 StorageKey storageKey = (StorageKey) other;
313 return hashCode == storageKey.hashCode && get() == storageKey.get();
314 } else {
315 return false;
316 }
317 }
318 }
319
320 /**
321 * An implementation of a {@link TypeCache} where obsolete references are cleared upon any call.
322 *
323 * @param <S> The type of the key that is used for identifying stored classes per class loader. Such keys must not strongly reference any
324 * types or class loaders without potentially corrupting the garbage eligibility of stored classes. As the storage is segmented
325 * by class loader, it is normally sufficient to store types by their name.
326 * @see TypeCache
327 */
328 public static class WithInlineExpunction<S> extends TypeCache<S> {
329
330 /**
331 * Creates a new type cache with inlined expunction.
332 *
333 * @param sort The reference type to use for stored types.
334 */
335 public WithInlineExpunction(Sort sort) {
336 super(sort);
337 }
338
339 /**
340 * {@inheritDoc}
341 */
342 public Class<?> find(ClassLoader classLoader, S key) {
343 try {
344 return super.find(classLoader, key);
345 } finally {
346 expungeStaleEntries();
347 }
348 }
349
350 /**
351 * {@inheritDoc}
352 */
353 public Class<?> insert(ClassLoader classLoader, S key, Class<?> type) {
354 try {
355 return super.insert(classLoader, key, type);
356 } finally {
357 expungeStaleEntries();
358 }
359 }
360
361 /**
362 * {@inheritDoc}
363 */
364 public Class<?> findOrInsert(ClassLoader classLoader, S key, Callable<Class<?>> builder) {
365 try {
366 return super.findOrInsert(classLoader, key, builder);
367 } finally {
368 expungeStaleEntries();
369 }
370 }
371
372 /**
373 * {@inheritDoc}
374 */
375 public Class<?> findOrInsert(ClassLoader classLoader, S key, Callable<Class<?>> builder, Object monitor) {
376 try {
377 return super.findOrInsert(classLoader, key, builder, monitor);
378 } finally {
379 expungeStaleEntries();
380 }
381 }
382 }
383
384 /**
385 * A simple key based on a collection of types where no type is strongly referenced.
386 */
387 public static class SimpleKey {
388
389 /**
390 * The referenced types.
391 */
392 private final Set<String> types;
393
394 /**
395 * Creates a simple cache key..
396 *
397 * @param type The first type to be represented by this key.
398 * @param additionalType Any additional types to be represented by this key.
399 */
400 public SimpleKey(Class<?> type, Class<?>... additionalType) {
401 this(type, Arrays.asList(additionalType));
402 }
403
404 /**
405 * Creates a simple cache key..
406 *
407 * @param type The first type to be represented by this key.
408 * @param additionalTypes Any additional types to be represented by this key.
409 */
410 public SimpleKey(Class<?> type, Collection<? extends Class<?>> additionalTypes) {
411 this(CompoundList.of(type, new ArrayList<Class<?>>(additionalTypes)));
412 }
413
414 /**
415 * Creates a simple cache key..
416 *
417 * @param types Any types to be represented by this key.
418 */
419 public SimpleKey(Collection<? extends Class<?>> types) {
420 this.types = new HashSet<String>();
421 for (Class<?> type : types) {
422 this.types.add(type.getName());
423 }
424 }
425
426 @Override
427 @CachedReturnPlugin.Enhance
428 public int hashCode() {
429 return types.hashCode();
430 }
431
432 @Override
433 public boolean equals(Object other) {
434 if (this == other) {
435 return true;
436 } else if (other == null || getClass() != other.getClass()) {
437 return false;
438 }
439 SimpleKey simpleKey = (SimpleKey) other;
440 return types.equals(simpleKey.types);
441 }
442 }
443 }
444