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.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test
 26  * @bug 8285932 8310913
 27  * @summary Test features provided by the ReferencedKeyMap/ReferencedKeySet classes.
 28  * @modules java.base/jdk.internal.util
 29  * @compile --patch-module java.base=${test.src} ReferencedKeyTest.java
 30  * @run main/othervm --patch-module java.base=${test.class.path} jdk.internal.util.ReferencedKeyTest
 31  */
 32 
 33 package jdk.internal.util;
 34 
 35 import java.lang.ref.PhantomReference;
 36 import java.lang.ref.Reference;
 37 import java.lang.ref.ReferenceQueue;
 38 import java.util.HashMap;
 39 import java.util.Map;
 40 import java.util.Set;
 41 import java.util.concurrent.ConcurrentHashMap;
 42 import java.util.function.BooleanSupplier;
 43 import java.util.function.Supplier;
 44 
 45 public class ReferencedKeyTest {
 46     static long BASE_KEY = 10_000_000L;
 47 
 48     public static void main(String[] args) {
 49         mapTest(false, HashMap::new);
 50         mapTest(true, HashMap::new);
 51         mapTest(false, ConcurrentHashMap::new);
 52         mapTest(true, ConcurrentHashMap::new);
 53 
 54         setTest(false, HashMap::new);
 55         setTest(true, HashMap::new);
 56         setTest(false, ConcurrentHashMap::new);
 57         setTest(true, ConcurrentHashMap::new);
 58     }
 59 
 60     static void assertTrue(boolean test, String message) {
 61         if (!test) {
 62             throw new RuntimeException(message);
 63         }
 64     }
 65 
 66     static void mapTest(boolean isSoft, Supplier<Map<ReferenceKey<Long>, String>> supplier) {
 67         Map<Long, String> map = ReferencedKeyMap.create(isSoft, supplier);
 68         populate(map);
 69         if (!isSoft) {
 70             if (!collect(() -> map.isEmpty())) {
 71                 throw new RuntimeException("WeakReference map not collecting!");
 72             }
 73         }
 74         populate(map);
 75         methods(map);
 76     }
 77 
 78     static void setTest(boolean isSoft, Supplier<Map<ReferenceKey<Long>, ReferenceKey<Long>>> supplier) {
 79         ReferencedKeySet<Long> set = ReferencedKeySet.create(isSoft, supplier);
 80         populate(set);
 81         if (!isSoft) {
 82             if (!collect(() -> set.isEmpty())) {
 83                 throw new RuntimeException("WeakReference set not collecting!");
 84             }
 85         }
 86         populate(set);
 87         methods(set);
 88     }
 89 
 90     static void methods(Map<Long, String> map) {
 91         assertTrue(map.size() == 26, "missing key");
 92         assertTrue(map.containsKey(BASE_KEY + 'a' -'a'), "missing key");
 93         assertTrue(map.get(BASE_KEY + 'b' -'a').equals("b"), "wrong key");
 94         assertTrue(map.containsValue("c"), "missing value");
 95         map.remove(BASE_KEY + 'd' -'a');
 96         assertTrue(map.get(BASE_KEY + 'd' -'a') == null, "not removed");
 97         map.putAll(Map.of(1L, "A", 2L, "B"));
 98         assertTrue(map.get(2L).equals("B"), "collection not added");
 99         assertTrue(map.containsKey(1L), "key missing");
100         assertTrue(map.containsValue("A"), "key missing");
101         assertTrue(map.entrySet().contains(Map.entry(1L, "A")), "key missing");
102         map.putIfAbsent(3L, "C");
103         assertTrue(map.get(3L).equals("C"), "key missing");
104         map.putIfAbsent(2L, "D");
105         assertTrue(map.get(2L).equals("B"), "key replaced");
106         map.remove(3L);
107         assertTrue(map.get(3L) == null, "key not removed");
108         map.replace(2L, "D");
109         assertTrue(map.get(2L).equals("D"), "key not replaced");
110         map.replace(2L, "B", "E");
111         assertTrue(map.get(2L).equals("D"), "key replaced");
112     }
113 
114     static void methods(ReferencedKeySet<Long> set) {
115         assertTrue(set.size() == 26, "missing key");
116         assertTrue(set.contains(BASE_KEY + 3), "missing key");
117         set.remove(BASE_KEY + 3);
118         assertTrue(!set.contains(BASE_KEY + 3), "not removed");
119         Long element1 = set.get(BASE_KEY + 2);
120         Long element2 = set.get(BASE_KEY + 3);
121         Long element3 = set.get(BASE_KEY + 4);
122         Long intern1 = set.intern(BASE_KEY + 2);
123         Long intern2 = set.intern(BASE_KEY + 3);
124         Long intern3 = set.intern(BASE_KEY + 4, e -> e);
125         assertTrue(element1 != null, "missing key");
126         assertTrue(element2 == null, "not removed");
127         assertTrue(element1 == intern1, "intern failed"); // must be same object
128         assertTrue(intern2 != null, "intern failed");
129         assertTrue(element3 == intern3, "intern failed");
130 
131         Long value1 = Long.valueOf(BASE_KEY + 999);
132         Long value2 = Long.valueOf(BASE_KEY + 999);
133         assertTrue(set.add(value1), "key not added");
134         assertTrue(!set.add(value1), "key added after second attempt");
135         assertTrue(!set.add(value2), "key should not have been added");
136     }
137 
138     // Borrowed from jdk.test.lib.util.ForceGC but couldn't use from java.base/jdk.internal.util
139     static boolean collect(BooleanSupplier booleanSupplier) {
140         ReferenceQueue<Object> queue = new ReferenceQueue<>();
141         Object obj = new Object();
142         PhantomReference<Object> ref = new PhantomReference<>(obj, queue);
143         obj = null;
144         Reference.reachabilityFence(obj);
145         Reference.reachabilityFence(ref);
146         long timeout = 1000L;
147         long quanta = 200L;
148         long retries = timeout / quanta;
149 
150         for (; retries >= 0; retries--) {
151             if (booleanSupplier.getAsBoolean()) {
152                 return true;
153             }
154 
155             System.gc();
156 
157             try {
158                 queue.remove(quanta);
159             } catch (InterruptedException ie) {
160                 // ignore, the loop will try again
161             }
162         }
163 
164         return booleanSupplier.getAsBoolean();
165     }
166 
167     static void populate(Map<Long, String> map) {
168         for (int i = 0; i < 26; i++) {
169             Long key = BASE_KEY + i;
170             String value = String.valueOf((char) ('a' + i));
171             map.put(key, value);
172         }
173     }
174 
175     static void populate(Set<Long> set) {
176         for (int i = 0; i < 26; i++) {
177             Long value = BASE_KEY + i;
178             set.add(value);
179         }
180     }
181 }