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 nullif 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 nullif 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