1 /*
2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3  * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
4  *
5  *
6  *
7  *
8  *
9  *
10  *
11  *
12  *
13  *
14  *
15  *
16  *
17  *
18  *
19  *
20  *
21  *
22  *
23  *
24  */

25 package java.lang.reflect;
26
27 import java.lang.ref.ReferenceQueue;
28 import java.lang.ref.WeakReference;
29 import java.util.Objects;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32 import java.util.function.BiFunction;
33 import java.util.function.Supplier;
34
35 /**
36  * Cache mapping pairs of {@code (key, sub-key) -> value}. Keys and values are
37  * weakly but sub-keys are strongly referenced.  Keys are passed directly to
38  * {@link #get} method which also takes a {@code parameter}. Sub-keys are
39  * calculated from keys and parameters using the {@code subKeyFactory} function
40  * passed to the constructor. Values are calculated from keys and parameters
41  * using the {@code valueFactory} function passed to the constructor.
42  * Keys can be {@code null} and are compared by identity while sub-keys returned by
43  * {@code subKeyFactory} or values returned by {@code valueFactory}
44  * can not be null. Sub-keys are compared using their {@link #equals} method.
45  * Entries are expunged from cache lazily on each invocation to {@link #get},
46  * {@link #containsValue} or {@link #size} methods when the WeakReferences to
47  * keys are cleared. Cleared WeakReferences to individual values don't cause
48  * expunging, but such entries are logically treated as non-existent and
49  * trigger re-evaluation of {@code valueFactory} on request for their
50  * key/subKey.
51  *
52  * @author Peter Levart
53  * @param <K> type of keys
54  * @param <P> type of parameters
55  * @param <V> type of values
56  */

57 final class WeakCache<K, P, V> {
58
59     private final ReferenceQueue<K> refQueue
60         = new ReferenceQueue<>();
61     // the key type is Object for supporting null key
62     private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
63         = new ConcurrentHashMap<>();
64     private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
65         = new ConcurrentHashMap<>();
66     private final BiFunction<K, P, ?> subKeyFactory;
67     private final BiFunction<K, P, V> valueFactory;
68
69     /**
70      * Construct an instance of {@code WeakCache}
71      *
72      * @param subKeyFactory a function mapping a pair of
73      *                      {@code (key, parameter) -> sub-key}
74      * @param valueFactory  a function mapping a pair of
75      *                      {@code (key, parameter) -> value}
76      * @throws NullPointerException if {@code subKeyFactory} or
77      *                              {@code valueFactory} is null.
78      */

79     public WeakCache(BiFunction<K, P, ?> subKeyFactory,
80                      BiFunction<K, P, V> valueFactory) {
81         this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
82         this.valueFactory = Objects.requireNonNull(valueFactory);
83     }
84
85     /**
86      * Look-up the value through the cache. This always evaluates the
87      * {@code subKeyFactory} function and optionally evaluates
88      * {@code valueFactory} function if there is no entry in the cache for given
89      * pair of (key, subKey) or the entry has already been cleared.
90      *
91      * @param key       possibly null key
92      * @param parameter parameter used together with key to create sub-key and
93      *                  value (should not be null)
94      * @return the cached value (never null)
95      * @throws NullPointerException if {@code parameter} passed in or
96      *                              {@code sub-key} calculated by
97      *                              {@code subKeyFactory} or {@code value}
98      *                              calculated by {@code valueFactory} is null.
99      */

100     public V get(K key, P parameter) {
101         Objects.requireNonNull(parameter);
102
103         expungeStaleEntries();
104
105         Object cacheKey = CacheKey.valueOf(key, refQueue);
106
107         // lazily install the 2nd level valuesMap for the particular cacheKey
108         ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
109         if (valuesMap == null) {
110             ConcurrentMap<Object, Supplier<V>> oldValuesMap
111                 = map.putIfAbsent(cacheKey,
112                                   valuesMap = new ConcurrentHashMap<>());
113             if (oldValuesMap != null) {
114                 valuesMap = oldValuesMap;
115             }
116         }
117
118         // create subKey and retrieve the possible Supplier<V> stored by that
119         // subKey from valuesMap
120         Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
121         Supplier<V> supplier = valuesMap.get(subKey);
122         Factory factory = null;
123
124         while (true) {
125             if (supplier != null) {
126                 // supplier might be a Factory or a CacheValue<V> instance
127                 V value = supplier.get();
128                 if (value != null) {
129                     return value;
130                 }
131             }
132             // else no supplier in cache
133             // or a supplier that returned null (could be a cleared CacheValue
134             // or a Factory that wasn't successful in installing the CacheValue)
135
136             // lazily construct a Factory
137             if (factory == null) {
138                 factory = new Factory(key, parameter, subKey, valuesMap);
139             }
140
141             if (supplier == null) {
142                 supplier = valuesMap.putIfAbsent(subKey, factory);
143                 if (supplier == null) {
144                     // successfully installed Factory
145                     supplier = factory;
146                 }
147                 // else retry with winning supplier
148             } else {
149                 if (valuesMap.replace(subKey, supplier, factory)) {
150                     // successfully replaced
151                     // cleared CacheEntry / unsuccessful Factory
152                     // with our Factory
153                     supplier = factory;
154                 } else {
155                     // retry with current supplier
156                     supplier = valuesMap.get(subKey);
157                 }
158             }
159         }
160     }
161
162     /**
163      * Checks whether the specified non-null value is already present in this
164      * {@code WeakCache}. The check is made using identity comparison regardless
165      * of whether value's class overrides {@link Object#equals} or not.
166      *
167      * @param value the non-null value to check
168      * @return true if given {@code value} is already cached
169      * @throws NullPointerException if value is null
170      */

171     public boolean containsValue(V value) {
172         Objects.requireNonNull(value);
173
174         expungeStaleEntries();
175         return reverseMap.containsKey(new LookupValue<>(value));
176     }
177
178     /**
179      * Returns the current number of cached entries that
180      * can decrease over time when keys/values are GC-ed.
181      */

182     public int size() {
183         expungeStaleEntries();
184         return reverseMap.size();
185     }
186
187     private void expungeStaleEntries() {
188         CacheKey<K> cacheKey;
189         while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
190             cacheKey.expungeFrom(map, reverseMap);
191         }
192     }
193
194     /**
195      * A factory {@link Supplier} that implements the lazy synchronized
196      * construction of the value and installment of it into the cache.
197      */

198     private final class Factory implements Supplier<V> {
199
200         private final K key;
201         private final P parameter;
202         private final Object subKey;
203         private final ConcurrentMap<Object, Supplier<V>> valuesMap;
204
205         Factory(K key, P parameter, Object subKey,
206                 ConcurrentMap<Object, Supplier<V>> valuesMap) {
207             this.key = key;
208             this.parameter = parameter;
209             this.subKey = subKey;
210             this.valuesMap = valuesMap;
211         }
212
213         @Override
214         public synchronized V get() { // serialize access
215             // re-check
216             Supplier<V> supplier = valuesMap.get(subKey);
217             if (supplier != this) {
218                 // something changed while we were waiting:
219                 // might be that we were replaced by a CacheValue
220                 // or were removed because of failure ->
221                 // return null to signal WeakCache.get() to retry
222                 // the loop
223                 return null;
224             }
225             // else still us (supplier == this)
226
227             // create new value
228             V value = null;
229             try {
230                 value = Objects.requireNonNull(valueFactory.apply(key, parameter));
231             } finally {
232                 if (value == null) { // remove us on failure
233                     valuesMap.remove(subKey, this);
234                 }
235             }
236             // the only path to reach here is with non-null value
237             assert value != null;
238
239             // wrap value with CacheValue (WeakReference)
240             CacheValue<V> cacheValue = new CacheValue<>(value);
241
242             // put into reverseMap
243             reverseMap.put(cacheValue, Boolean.TRUE);
244
245             // try replacing us with CacheValue (this should always succeed)
246             if (!valuesMap.replace(subKey, this, cacheValue)) {
247                 throw new AssertionError("Should not reach here");
248             }
249
250             // successfully replaced us with new CacheValue -> return the value
251             // wrapped by it
252             return value;
253         }
254     }
255
256     /**
257      * Common type of value suppliers that are holding a referent.
258      * The {@link #equals} and {@link #hashCode} of implementations is defined
259      * to compare the referent by identity.
260      */

261     private interface Value<V> extends Supplier<V> {}
262
263     /**
264      * An optimized {@link Value} used to look-up the value in
265      * {@link WeakCache#containsValue} method so that we are not
266      * constructing the whole {@link CacheValue} just to look-up the referent.
267      */

268     private static final class LookupValue<V> implements Value<V> {
269         private final V value;
270
271         LookupValue(V value) {
272             this.value = value;
273         }
274
275         @Override
276         public V get() {
277             return value;
278         }
279
280         @Override
281         public int hashCode() {
282             return System.identityHashCode(value); // compare by identity
283         }
284
285         @Override
286         public boolean equals(Object obj) {
287             return obj == this ||
288                    obj instanceof Value &&
289                    this.value == ((Value<?>) obj).get();  // compare by identity
290         }
291     }
292
293     /**
294      * A {@link Value} that weakly references the referent.
295      */

296     private static final class CacheValue<V>
297         extends WeakReference<V> implements Value<V>
298     {
299         private final int hash;
300
301         CacheValue(V value) {
302             super(value);
303             this.hash = System.identityHashCode(value); // compare by identity
304         }
305
306         @Override
307         public int hashCode() {
308             return hash;
309         }
310
311         @Override
312         public boolean equals(Object obj) {
313             V value;
314             return obj == this ||
315                    obj instanceof Value &&
316                    // cleared CacheValue is only equal to itself
317                    (value = get()) != null &&
318                    value == ((Value<?>) obj).get(); // compare by identity
319         }
320     }
321
322     /**
323      * CacheKey containing a weakly referenced {@code key}. It registers
324      * itself with the {@code refQueue} so that it can be used to expunge
325      * the entry when the {@link WeakReference} is cleared.
326      */

327     private static final class CacheKey<K> extends WeakReference<K> {
328
329         // a replacement for null keys
330         private static final Object NULL_KEY = new Object();
331
332         static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
333             return key == null
334                    // null key means we can't weakly reference it,
335                    // so we use a NULL_KEY singleton as cache key
336                    ? NULL_KEY
337                    // non-null key requires wrapping with a WeakReference
338                    : new CacheKey<>(key, refQueue);
339         }
340
341         private final int hash;
342
343         private CacheKey(K key, ReferenceQueue<K> refQueue) {
344             super(key, refQueue);
345             this.hash = System.identityHashCode(key);  // compare by identity
346         }
347
348         @Override
349         public int hashCode() {
350             return hash;
351         }
352
353         @Override
354         public boolean equals(Object obj) {
355             K key;
356             return obj == this ||
357                    obj != null &&
358                    obj.getClass() == this.getClass() &&
359                    // cleared CacheKey is only equal to itself
360                    (key = this.get()) != null &&
361                    // compare key by identity
362                    key == ((CacheKey<K>) obj).get();
363         }
364
365         void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
366                          ConcurrentMap<?, Boolean> reverseMap) {
367             // removing just by key is always safe here because after a CacheKey
368             // is cleared and enqueue-ed it is only equal to itself
369             // (see equals method)...
370             ConcurrentMap<?, ?> valuesMap = map.remove(this);
371             // remove also from reverseMap if needed
372             if (valuesMap != null) {
373                 for (Object cacheValue : valuesMap.values()) {
374                     reverseMap.remove(cacheValue);
375                 }
376             }
377         }
378     }
379 }
380
Powered by JavaMelody