1 /*
  2  * Copyright (c) 2023, 2024, 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      * @return a supplier to create a {@code ConcurrentHashMap} appropriate for use in the
104      *         create methods.
105      * @param <K> the type of keys maintained by the new map
106      * @param <V> the type of mapped values
107      */
108     public static <K, V> Supplier<Map<ReferenceKey<K>, V>> concurrentHashMapSupplier() {
109         return new Supplier<>() {
110             @Override
111             public Map<ReferenceKey<K>, V> get() {
112                 return new ConcurrentHashMap<>();
113             }
114         };
115     }
116 
117     /**
118      * Private constructor.
119      *
120      * @param isSoft          true if {@link SoftReference} keys are to
121      *                        be used, {@link WeakReference} otherwise.
122      * @param map             backing map
123      * @param stale           {@link ReferenceQueue} for cleaning up entries
124      */
125     private ReferencedKeyMap(boolean isSoft, Map<ReferenceKey<K>, V> map, ReferenceQueue<K> stale) {
126         this.isSoft = isSoft;
127         this.map = map;
128         this.stale = stale;
129     }
130 
131     /**
132      * Create a new {@link ReferencedKeyMap} map.
133      *
134      * @param isSoft          true if {@link SoftReference} keys are to
135      *                        be used, {@link WeakReference} otherwise.
136      * @param supplier        {@link Supplier} of the backing map
137      *
138      * @return a new map with {@link Reference} keys
139      *
140      * @param <K> the type of keys maintained by the new map
141      * @param <V> the type of mapped values
142      */
143     public static <K, V> ReferencedKeyMap<K, V>
144     create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) {
145         return create(isSoft, false, supplier);
146     }
147 
148     /**
149      * Create a new {@link ReferencedKeyMap} map.
150      *
151      * @param isSoft          true if {@link SoftReference} keys are to
152      *                        be used, {@link WeakReference} otherwise.
153      * @param useNativeQueue  true if uses NativeReferenceQueue
154      *                        otherwise use {@link ReferenceQueue}.
155      * @param supplier        {@link Supplier} of the backing map
156      *
157      * @return a new map with {@link Reference} keys
158      *
159      * @param <K> the type of keys maintained by the new map
160      * @param <V> the type of mapped values
161      */
162     public static <K, V> ReferencedKeyMap<K, V>
163     create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<K>, V>> supplier) {
164         return new ReferencedKeyMap<K, V>(isSoft, supplier.get(),
165                 useNativeQueue ? SharedSecrets.getJavaLangRefAccess().newNativeReferenceQueue()
166                                : new ReferenceQueue<>()
167                 );
168     }
169 
170     /**
171      * {@return a key suitable for a map entry}
172      *
173      * @param key unwrapped key
174      */
175     @SuppressWarnings("unchecked")
176     private ReferenceKey<K> entryKey(Object key) {
177         if (isSoft) {
178             return new SoftReferenceKey<>((K)key, stale);
179         } else {
180             return new WeakReferenceKey<>((K)key, stale);
181         }
182     }
183 
184     /**
185      * {@return a key suitable for lookup}
186      *
187      * @param key unwrapped key
188      */
189     @SuppressWarnings("unchecked")
190     private ReferenceKey<K> lookupKey(Object key) {
191         return new StrongReferenceKey<>((K)key);
192     }
193 
194     @Override
195     public int size() {
196         removeStaleReferences();
197         return map.size();
198     }
199 
200     @Override
201     public boolean isEmpty() {
202         removeStaleReferences();
203         return map.isEmpty();
204     }
205 
206     @Override
207     public boolean containsKey(Object key) {
208         Objects.requireNonNull(key, "key must not be null");
209         removeStaleReferences();
210         return map.containsKey(lookupKey(key));
211     }
212 
213     @Override
214     public boolean containsValue(Object value) {
215         Objects.requireNonNull(value, "value must not be null");
216         removeStaleReferences();
217         return map.containsValue(value);
218     }
219 
220     @Override
221     public V get(Object key) {
222         removeStaleReferences();
223         return getNoCheckStale(key);
224     }
225 
226     // Internal get(key) without removing stale references that would modify the keyset.
227     // Use when iterating or streaming over the keys to avoid ConcurrentModificationException.
228     private V getNoCheckStale(Object key) {
229         Objects.requireNonNull(key, "key must not be null");
230         return map.get(lookupKey(key));
231     }
232 
233     @Override
234     public V put(K key, V newValue) {
235         Objects.requireNonNull(key, "key must not be null");
236         Objects.requireNonNull(newValue, "value must not be null");
237         removeStaleReferences();
238         ReferenceKey<K> entryKey = entryKey(key);
239         // If {@code put} returns non-null then was actually a {@code replace}
240         // and older key was used. In that case the new key was not used and the
241         // reference marked stale.
242         V oldValue = map.put(entryKey, newValue);
243         if (oldValue != null) {
244             entryKey.unused();
245         }
246         return oldValue;
247     }
248 
249     @Override
250     public V remove(Object key) {
251         // Rely on gc to clean up old key.
252         return map.remove(lookupKey(key));
253     }
254 
255     @Override
256     public void putAll(Map<? extends K, ? extends V> m) {
257         removeStaleReferences();
258         for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
259             K key = entry.getKey();
260             V value = entry.getValue();
261             put(key, value);
262         }
263     }
264 
265     @Override
266     public void clear() {
267         removeStaleReferences();
268         // Rely on gc to clean up old keys.
269         map.clear();
270     }
271 
272     /**
273      * Common routine for collecting the current set of keys.
274      *
275      * @return {@link Stream} of valid keys (unwrapped)
276      */
277     private Stream<K> filterKeySet() {
278         return map.keySet()
279                 .stream()
280                 .map(ReferenceKey::get)
281                 .filter(Objects::nonNull);
282     }
283 
284     @Override
285     public Set<K> keySet() {
286         removeStaleReferences();
287         return filterKeySet().collect(Collectors.toSet());
288     }
289 
290     @Override
291     public Collection<V> values() {
292         removeStaleReferences();
293         return map.values();
294     }
295 
296     @Override
297     public Set<Entry<K, V>> entrySet() {
298         removeStaleReferences();
299         return filterKeySet()
300                 .map(k -> new AbstractMap.SimpleEntry<>(k, getNoCheckStale(k)))
301                 .collect(Collectors.toSet());
302     }
303 
304     @Override
305     public V putIfAbsent(K key, V newValue) {
306         removeStaleReferences();
307         ReferenceKey<K> entryKey = entryKey(key);
308         // If {@code putIfAbsent} returns non-null then was actually a
309         // {@code replace}  and older key was used. In that case the new key was
310         // not used and the reference marked stale.
311         V oldValue = map.putIfAbsent(entryKey, newValue);
312         if (oldValue != null) {
313             entryKey.unused();
314         }
315         return oldValue;
316     }
317 
318     @Override
319     public boolean remove(Object key, Object value) {
320         // Rely on gc to clean up old key.
321         return map.remove(lookupKey(key), value);
322     }
323 
324     @Override
325     public boolean replace(K key, V oldValue, V newValue) {
326         removeStaleReferences();
327         // If replace is successful then the older key will be used and the
328         // lookup key will suffice.
329         return map.replace(lookupKey(key), oldValue, newValue);
330     }
331 
332     @Override
333     public V replace(K key, V value) {
334         removeStaleReferences();
335         // If replace is successful then the older key will be used and the
336         // lookup key will suffice.
337         return map.replace(lookupKey(key), value);
338     }
339 
340     @Override
341     public String toString() {
342         removeStaleReferences();
343         return filterKeySet()
344                 .map(k -> k + "=" + getNoCheckStale(k))
345                 .collect(Collectors.joining(", ", "{", "}"));
346     }
347 
348     /**
349      * Removes enqueued weak references from map.
350      */
351     public void removeStaleReferences() {
352         while (true) {
353             Object key = stale.poll();
354             if (key == null) {
355                 break;
356             }
357             map.remove(key);
358         }
359     }
360 
361     /**
362      * Puts an entry where the key and the value are the same. Used for
363      * interning values in a set.
364      *
365      * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
366      * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
367      * be thrown.
368      *
369      * @param setMap    {@link ReferencedKeyMap} where interning takes place
370      * @param key       key to add
371      *
372      * @param <T> type of key
373      *
374      * @return the old key instance if found otherwise the new key instance
375      *
376      * @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
377      */
378     static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
379         T value = existingKey(setMap, key);
380         if (value != null) {
381             return value;
382         }
383         return internKey(setMap, key);
384     }
385 
386     /**
387      * Puts an entry where the key and the value are the same. Used for
388      * interning values in a set.
389      *
390      * @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
391      * is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
392      * be thrown.
393      *
394      * @param setMap    {@link ReferencedKeyMap} where interning takes place
395      * @param key       key to add
396      * @param interner  operation to apply to key before adding to map
397      *
398      * @param <T> type of key
399      *
400      * @return the old key instance if found otherwise the new key instance
401      *
402      * @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
403      *
404      * @implNote This version of intern should not be called during phase1
405      * using a lambda. Use an UnaryOperator instance instead.
406      */
407     static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key, UnaryOperator<T> interner) {
408         T value = existingKey(setMap, key);
409         if (value != null) {
410             return value;
411         }
412         key = interner.apply(key);
413         return internKey(setMap, key);
414     }
415 
416     /**
417      * Check if the key already exists in the map.
418      *
419      * @param setMap    {@link ReferencedKeyMap} where interning takes place
420      * @param key       key to test
421      *
422      * @param <T> type of key
423      *
424      * @return key if found otherwise null
425      */
426     private static <T> T existingKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
427         setMap.removeStaleReferences();
428         ReferenceKey<T> entryKey = setMap.get(setMap.lookupKey(key));
429         return entryKey != null ? entryKey.get() : null;
430     }
431 
432     /**
433      * Attempt to add key to map.
434      *
435      * @param setMap    {@link ReferencedKeyMap} where interning takes place
436      * @param key       key to add
437      *
438      * @param <T> type of key
439      *
440      * @return the old key instance if found otherwise the new key instance
441      */
442     private static <T> T internKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
443         ReferenceKey<T> entryKey = setMap.entryKey(key);
444         T interned;
445         do {
446             setMap.removeStaleReferences();
447             ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
448             if (existing == null) {
449                 return key;
450             } else {
451                 // If {@code putIfAbsent} returns non-null then was actually a
452                 // {@code replace} and older key was used. In that case the new
453                 // key was not used and the reference marked stale.
454                 interned = existing.get();
455                 if (interned != null) {
456                     entryKey.unused();
457                 }
458             }
459         } while (interned == null);
460         return interned;
461     }
462 
463 
464     /**
465      * Attempt to add key to map if absent.
466      *
467      * @param setMap    {@link ReferencedKeyMap} where interning takes place
468      * @param key       key to add
469      *
470      * @param <T> type of key
471      *
472      * @return true if the key was added
473      */
474     static <T> boolean internAddKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
475         ReferenceKey<T> entryKey = setMap.entryKey(key);
476         setMap.removeStaleReferences();
477         ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
478         if (existing == null) {
479             return true;
480         } else {
481             // If {@code putIfAbsent} returns non-null then was actually a
482             // {@code replace} and older key was used. In that case the new
483             // key was not used and the reference marked stale.
484             entryKey.unused();
485             return false;
486         }
487      }
488 
489 }