• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package jdk.internal.util;
27 
28 import java.lang.ref.Reference;
29 import java.lang.ref.ReferenceQueue;
30 import java.lang.ref.SoftReference;
31 import java.lang.ref.WeakReference;
32 import java.util.AbstractMap;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.Objects;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.concurrent.ConcurrentHashMap;
39 import java.util.function.Supplier;
40 import java.util.function.UnaryOperator;
41 import java.util.stream.Collectors;
42 import java.util.stream.Stream;
43 
44 import jdk.internal.access.SharedSecrets;
45 
46 /**
47  * This class provides management of {@link Map maps} where it is desirable to
48  * remove entries automatically when the key is garbage collected. This is
49  * accomplished by using a backing map where the keys are either a
50  * {@link WeakReference} or a {@link SoftReference}.
51  * <p>
52  * To create a {@link ReferencedKeyMap} the user must provide a {@link Supplier}
53  * of the backing map and whether {@link WeakReference} or
54  * {@link SoftReference} is to be used.
55  *
56  * {@snippet :
57  * // Use HashMap and WeakReference
58  * Map<Long, String> map = ReferencedKeyMap.create(false, HashMap::new);
59  * map.put(10_000_000L, "a");
60  * map.put(10_000_001L, "b");
61  * map.put(10_000_002L, "c");
62  * map.put(10_000_003L, "d");
63  * map.put(10_000_004L, "e");
64  *
65  * // Use ConcurrentHashMap and SoftReference
66  * map = ReferencedKeyMap.create(true, ConcurrentHashMap::new);
67  * map.put(20_000_000L, "v");
68  * map.put(20_000_001L, "w");
69  * map.put(20_000_002L, "x");
70  * map.put(20_000_003L, "y");
71  * map.put(20_000_004L, "z");
72  * }
73  *
74  * @implNote Care must be given that the backing map does replacement by
75  * replacing the value in the map entry instead of deleting the old entry and
76  * adding a new entry, otherwise replaced entries may end up with a strongly
77  * referenced key. {@link HashMap} and {@link ConcurrentHashMap} are known
78  * to be safe.
79  *
80  * @param <K> the type of keys maintained by this map
81  * @param <V> the type of mapped values
82  *
83  * @since 21
84  */
85 public final class ReferencedKeyMap<K, V> implements Map<K, V> {
86     /**
87      * true if {@link SoftReference} keys are to be used,
88      * {@link WeakReference} otherwise.
89      */
90     private final boolean isSoft;
91 
92     /**
93      * Backing {@link Map}.
94      */
95     private final Map<ReferenceKey<K>, V> map;
96 
97     /**
98      * {@link ReferenceQueue} for cleaning up entries.
99      */
100     private final ReferenceQueue<K> stale;
101 
102     /**
103      * Private constructor.
104      *
105      * @param isSoft          true if {@link SoftReference} keys are to
106      *                        be used, {@link WeakReference} otherwise.
107      * @param map             backing map
108      * @param stale           {@link ReferenceQueue} for cleaning up entries
109      */
ReferencedKeyMap(boolean isSoft, Map<ReferenceKey<K>, V> map, ReferenceQueue<K> stale)110     private ReferencedKeyMap(boolean isSoft, Map<ReferenceKey<K>, V> map, ReferenceQueue<K> stale) {
111         this.isSoft = isSoft;
112         this.map = map;
113         this.stale = stale;
114     }
115 
116     /**
117      * Create a new {@link ReferencedKeyMap} map.
118      *
119      * @param isSoft          true if {@link SoftReference} keys are to
120      *                        be used, {@link WeakReference} otherwise.
121      * @param supplier        {@link Supplier} of the backing map
122      *
123      * @return a new map with {@link Reference} keys
124      *
125      * @param <K> the type of keys maintained by the new map
126      * @param <V> the type of mapped values
127      */
128     // Android-changed: made private so no one tries to create a map with a non-native queue.
129     // public static <K, V> ReferencedKeyMap<K, V>
130     private static <K, V> ReferencedKeyMap<K, V>
create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier)131     create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) {
132         // Android-changed: only native monitors based queues are available.
133         // return create(isSoft, false, supplier);
134         return create(isSoft, true, supplier);
135     }
136 
137     /**
138      * Create a new {@link ReferencedKeyMap} map.
139      *
140      * @param isSoft          true if {@link SoftReference} keys are to
141      *                        be used, {@link WeakReference} otherwise.
142      * @param useNativeQueue  true if uses NativeReferenceQueue
143      *                        otherwise use {@link ReferenceQueue}.
144      * @param supplier        {@link Supplier} of the backing map
145      *
146      * @return a new map with {@link Reference} keys
147      *
148      * @param <K> the type of keys maintained by the new map
149      * @param <V> the type of mapped values
150      */
151     // Android-changed: made package-private so no one tries to create a map with a non-native queue
152     // public static <K, V> ReferencedKeyMap<K, V>
153     static <K, V> ReferencedKeyMap<K, V>
create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<K>, V>> supplier)154     create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<K>, V>> supplier) {
155         // Android-added: non-native queue is not available.
156         if (!useNativeQueue) {
157             throw new IllegalArgumentException("Only native queue is supported");
158         }
159         return new ReferencedKeyMap<K, V>(isSoft, supplier.get(),
160                 // Android-changed: non-native queue is not available and ReferenceQueue is
161                 // native monitors based.
162                 // useNativeQueue ? SharedSecrets.getJavaLangRefAccess().newNativeReferenceQueue()
163                 //                : new ReferenceQueue<>()
164                 new ReferenceQueue<>()
165                 );
166     }
167 
168     // Android-added: add a separate method with explicit name instead of passing boolean arg.
169     public static <K, V> ReferencedKeyMap<K, V>
createWithNativeQueue(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier)170     createWithNativeQueue(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) {
171         return create(isSoft, /* useNativeQueue= */ true, supplier);
172     }
173 
174     /**
175      * {@return a key suitable for a map entry}
176      *
177      * @param key unwrapped key
178      */
179     @SuppressWarnings("unchecked")
entryKey(Object key)180     private ReferenceKey<K> entryKey(Object key) {
181         if (isSoft) {
182             return new SoftReferenceKey<>((K)key, stale);
183         } else {
184             return new WeakReferenceKey<>((K)key, stale);
185         }
186     }
187 
188     /**
189      * {@return a key suitable for lookup}
190      *
191      * @param key unwrapped key
192      */
193     @SuppressWarnings("unchecked")
lookupKey(Object key)194     private ReferenceKey<K> lookupKey(Object key) {
195         return new StrongReferenceKey<>((K)key);
196     }
197 
198     @Override
size()199     public int size() {
200         removeStaleReferences();
201         return map.size();
202     }
203 
204     @Override
isEmpty()205     public boolean isEmpty() {
206         removeStaleReferences();
207         return map.isEmpty();
208     }
209 
210     @Override
containsKey(Object key)211     public boolean containsKey(Object key) {
212         Objects.requireNonNull(key, "key must not be null");
213         removeStaleReferences();
214         return map.containsKey(lookupKey(key));
215     }
216 
217     @Override
containsValue(Object value)218     public boolean containsValue(Object value) {
219         Objects.requireNonNull(value, "value must not be null");
220         removeStaleReferences();
221         return map.containsValue(value);
222     }
223 
224     @Override
get(Object key)225     public V get(Object key) {
226         Objects.requireNonNull(key, "key must not be null");
227         removeStaleReferences();
228         return map.get(lookupKey(key));
229     }
230 
231     @Override
put(K key, V newValue)232     public V put(K key, V newValue) {
233         Objects.requireNonNull(key, "key must not be null");
234         Objects.requireNonNull(newValue, "value must not be null");
235         removeStaleReferences();
236         ReferenceKey<K> entryKey = entryKey(key);
237         // If {@code put} returns non-null then was actually a {@code replace}
238         // and older key was used. In that case the new key was not used and the
239         // reference marked stale.
240         V oldValue = map.put(entryKey, newValue);
241         if (oldValue != null) {
242             entryKey.unused();
243         }
244         return oldValue;
245     }
246 
247     @Override
remove(Object key)248     public V remove(Object key) {
249         // Rely on gc to clean up old key.
250         return map.remove(lookupKey(key));
251     }
252 
253     @Override
putAll(Map<? extends K, ? extends V> m)254     public void putAll(Map<? extends K, ? extends V> m) {
255         removeStaleReferences();
256         for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
257             K key = entry.getKey();
258             V value = entry.getValue();
259             put(key, value);
260         }
261     }
262 
263     @Override
clear()264     public void clear() {
265         removeStaleReferences();
266         // Rely on gc to clean up old keys.
267         map.clear();
268     }
269 
270     /**
271      * Common routine for collecting the current set of keys.
272      *
273      * @return {@link Stream} of valid keys (unwrapped)
274      */
filterKeySet()275     private Stream<K> filterKeySet() {
276         return map.keySet()
277                 .stream()
278                 .map(ReferenceKey::get)
279                 .filter(Objects::nonNull);
280     }
281 
282     @Override
keySet()283     public Set<K> keySet() {
284         removeStaleReferences();
285         return filterKeySet().collect(Collectors.toSet());
286     }
287 
288     @Override
values()289     public Collection<V> values() {
290         removeStaleReferences();
291         return map.values();
292     }
293 
294     @Override
entrySet()295     public Set<Entry<K, V>> entrySet() {
296         removeStaleReferences();
297         return filterKeySet()
298                 .map(k -> new AbstractMap.SimpleEntry<>(k, get(k)))
299                 .collect(Collectors.toSet());
300     }
301 
302     @Override
putIfAbsent(K key, V newValue)303     public V putIfAbsent(K key, V newValue) {
304         removeStaleReferences();
305         ReferenceKey<K> entryKey = entryKey(key);
306         // If {@code putIfAbsent} returns non-null then was actually a
307         // {@code replace}  and older key was used. In that case the new key was
308         // not used and the reference marked stale.
309         V oldValue = map.putIfAbsent(entryKey, newValue);
310         if (oldValue != null) {
311             entryKey.unused();
312         }
313         return oldValue;
314     }
315 
316     @Override
remove(Object key, Object value)317     public boolean remove(Object key, Object value) {
318         // Rely on gc to clean up old key.
319         return map.remove(lookupKey(key), value);
320     }
321 
322     @Override
replace(K key, V oldValue, V newValue)323     public boolean replace(K key, V oldValue, V newValue) {
324         removeStaleReferences();
325         // If replace is successful then the older key will be used and the
326         // lookup key will suffice.
327         return map.replace(lookupKey(key), oldValue, newValue);
328     }
329 
330     @Override
replace(K key, V value)331     public V replace(K key, V value) {
332         removeStaleReferences();
333         // If replace is successful then the older key will be used and the
334         // lookup key will suffice.
335         return map.replace(lookupKey(key), value);
336     }
337 
338     @Override
toString()339     public String toString() {
340         removeStaleReferences();
341         return filterKeySet()
342                 .map(k -> k + "=" + get(k))
343                 .collect(Collectors.joining(", ", "{", "}"));
344     }
345 
346     /**
347      * Removes enqueued weak references from map.
348      */
removeStaleReferences()349     public void removeStaleReferences() {
350         while (true) {
351             Object key = stale.poll();
352             if (key == null) {
353                 break;
354             }
355             map.remove(key);
356         }
357     }
358 
359     /**
360      * Puts an entry where the key and the value are the same. Used for
361      * interning values in a set.
362      *
363      * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
364      * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
365      * be thrown.
366      *
367      * @param setMap    {@link ReferencedKeyMap} where interning takes place
368      * @param key       key to add
369      *
370      * @param <T> type of key
371      *
372      * @return the old key instance if found otherwise the new key instance
373      *
374      * @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
375      */
intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key)376     static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
377         T value = existingKey(setMap, key);
378         if (value != null) {
379             return value;
380         }
381         return internKey(setMap, key);
382     }
383 
384     /**
385      * Puts an entry where the key and the value are the same. Used for
386      * interning values in a set.
387      *
388      * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
389      * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
390      * be thrown.
391      *
392      * @param setMap    {@link ReferencedKeyMap} where interning takes place
393      * @param key       key to add
394      * @param interner  operation to apply to key before adding to map
395      *
396      * @param <T> type of key
397      *
398      * @return the old key instance if found otherwise the new key instance
399      *
400      * @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
401      *
402      * @implNote This version of intern should not be called during phase1
403      * using a lambda. Use an UnaryOperator instance instead.
404      */
intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key, UnaryOperator<T> interner)405     static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key, UnaryOperator<T> interner) {
406         T value = existingKey(setMap, key);
407         if (value != null) {
408             return value;
409         }
410         key = interner.apply(key);
411         return internKey(setMap, key);
412     }
413 
414     /**
415      * Check if the key already exists in the map.
416      *
417      * @param setMap    {@link ReferencedKeyMap} where interning takes place
418      * @param key       key to test
419      *
420      * @param <T> type of key
421      *
422      * @return key if found otherwise null
423      */
existingKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key)424     private static <T> T existingKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
425         setMap.removeStaleReferences();
426         ReferenceKey<T> entryKey = setMap.get(setMap.lookupKey(key));
427         return entryKey != null ? entryKey.get() : null;
428     }
429 
430     /**
431      * Attempt to add key to map.
432      *
433      * @param setMap    {@link ReferencedKeyMap} where interning takes place
434      * @param key       key to add
435      *
436      * @param <T> type of key
437      *
438      * @return the old key instance if found otherwise the new key instance
439      */
internKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key)440     private static <T> T internKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
441         ReferenceKey<T> entryKey = setMap.entryKey(key);
442         T interned;
443         do {
444             setMap.removeStaleReferences();
445             ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
446             if (existing == null) {
447                 return key;
448             } else {
449                 // If {@code putIfAbsent} returns non-null then was actually a
450                 // {@code replace} and older key was used. In that case the new
451                 // key was not used and the reference marked stale.
452                 interned = existing.get();
453                 if (interned != null) {
454                     entryKey.unused();
455                 }
456             }
457         } while (interned == null);
458         return interned;
459     }
460 
461 
462     /**
463      * Attempt to add key to map if absent.
464      *
465      * @param setMap    {@link ReferencedKeyMap} where interning takes place
466      * @param key       key to add
467      *
468      * @param <T> type of key
469      *
470      * @return true if the key was added
471      */
internAddKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key)472     static <T> boolean internAddKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
473         ReferenceKey<T> entryKey = setMap.entryKey(key);
474         setMap.removeStaleReferences();
475         ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
476         if (existing == null) {
477             return true;
478         } else {
479             // If {@code putIfAbsent} returns non-null then was actually a
480             // {@code replace} and older key was used. In that case the new
481             // key was not used and the reference marked stale.
482             entryKey.unused();
483             return false;
484         }
485      }
486 
487 }
488