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.
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 8336390 8338060
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.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.concurrent.ConcurrentHashMap;
43 import java.util.function.BooleanSupplier;
44 import java.util.function.Function;
45 import java.util.function.Supplier;
46 import java.util.stream.IntStream;
47
48 public class ReferencedKeyTest {
49 private static String BASE_KEY = "BASEKEY-";
50
51 // Return a String (identity object) that can be a key in WeakHashMap.
52 private static String genKey(int i) {
53 return BASE_KEY + i;
54 }
55
56 // Return a String of the letter 'a' plus the integer (0..0xffff)
57 private static String genValue(int i) {
58 return String.valueOf((char) ('a' + i));
59 }
60
61 public static void main(String[] args) {
62 mapTest(false, HashMap::new);
63 mapTest(true, HashMap::new);
64 mapTest(false, ConcurrentHashMap::new);
65 mapTest(true, ConcurrentHashMap::new);
66
67 setTest(false, HashMap::new);
68 setTest(true, HashMap::new);
69 setTest(false, ConcurrentHashMap::new);
70 setTest(true, ConcurrentHashMap::new);
71 }
72
73 static void assertTrue(boolean test, String message) {
74 if (!test) {
75 throw new RuntimeException(message);
76 }
77 }
78
79 static void mapTest(boolean isSoft, Supplier<Map<ReferenceKey<String>, String>> supplier) {
80 Map<String, String> map = ReferencedKeyMap.create(isSoft, supplier);
81 var strongKeys = populate(map); // Retain references to the keys
82 methods(map);
83 Reference.reachabilityFence(strongKeys);
84
85 strongKeys = null; // drop strong key references
86 if (!isSoft) {
87 if (!collect(() -> map.isEmpty())) {
88 throw new RuntimeException("WeakReference map not collecting!");
89 }
90 }
91 }
92
93 static void setTest(boolean isSoft, Supplier<Map<ReferenceKey<String>, ReferenceKey<String>>> supplier) {
94 ReferencedKeySet<String> set = ReferencedKeySet.create(isSoft, supplier);
95 var strongKeys = populate(set); // Retain references to the keys
96 methods(set);
97 Reference.reachabilityFence(strongKeys);
98
99 strongKeys = null; // drop strong key references
100 if (!isSoft) {
101 if (!collect(() -> set.isEmpty())) {
102 throw new RuntimeException("WeakReference set not collecting!");
103 }
104 }
105 }
106
107 static void methods(Map<String, String> map) {
108 assertTrue(map.size() == 26, "missing key");
109 assertTrue(map.containsKey(genKey('a' -'a')), "missing key");
110 assertTrue(map.get(genKey('b' -'a')).equals("b"), "wrong key");
111 assertTrue(map.containsValue("c"), "missing value");
112 map.remove(genKey('d' -'a'));
113 assertTrue(map.get(genKey('d' -'a')) == null, "not removed");
114 map.putAll(Map.of(genKey(1), "A", genKey(2), "B"));
115 assertTrue(map.get(genKey(2)).equals("B"), "collection not added");
116 assertTrue(map.containsKey(genKey(1)), "key missing");
117 assertTrue(map.containsValue("A"), "key missing");
118 assertTrue(map.entrySet().contains(Map.entry(genKey(1), "A")), "key missing");
119 map.putIfAbsent(genKey(3), "C");
120 assertTrue(map.get(genKey(3)).equals("C"), "key missing");
121 map.putIfAbsent(genKey(2), "D");
122 assertTrue(map.get(genKey(2)).equals("B"), "key replaced");
123 map.remove(genKey(3));
124 assertTrue(map.get(genKey(3)) == null, "key not removed");
125 map.replace(genKey(2), "D");
126 assertTrue(map.get(genKey(2)).equals("D"), "key not replaced");
127 map.replace(genKey(2), "B", "E");
128 assertTrue(map.get(genKey(2)).equals("D"), "key replaced");
129 }
130
131 static void methods(ReferencedKeySet<String> set) {
132 assertTrue(set.size() == 26, "missing key");
133 assertTrue(set.contains(genKey(3)), "missing key");
134 set.remove(genKey(3));
135 assertTrue(!set.contains(genKey(3)), "not removed");
136 String element1 = set.get(genKey(2));
137 String element2 = set.get(genKey(3));
138 String element3 = set.get(genKey(4));
139 String intern1 = set.intern(genKey(2));
140 String intern2 = set.intern(genKey(3));
141 String intern3 = set.intern(genKey(4), e -> e);
142 assertTrue(element1 != null, "missing key");
143 assertTrue(element2 == null, "not removed");
144 assertTrue(element1 == intern1, "intern failed"); // must be same object
145 assertTrue(intern2 != null, "intern failed");
146 assertTrue(element3 == intern3, "intern failed");
147
148 String value1 = genKey(999);
149 String value2 = genKey(999);
150 assertTrue(set.add(value1), "key not added");
151 assertTrue(!set.add(value1), "key added after second attempt");
152 assertTrue(!set.add(value2), "key should not have been added");
153 }
154
155 // Borrowed from jdk.test.lib.util.ForceGC but couldn't use from java.base/jdk.internal.util
156 static boolean collect(BooleanSupplier booleanSupplier) {
157 ReferenceQueue<Object> queue = new ReferenceQueue<>();
158 Object obj = new Object();
159 PhantomReference<Object> ref = new PhantomReference<>(obj, queue);
160 obj = null;
161 Reference.reachabilityFence(obj);
162 Reference.reachabilityFence(ref);
163 long timeout = 1000L;
164 long quanta = 200L;
165 long retries = timeout / quanta;
166
167 for (; retries >= 0; retries--) {
168 if (booleanSupplier.getAsBoolean()) {
169 return true;
170 }
171
172 System.gc();
173
174 try {
175 queue.remove(quanta);
176 } catch (InterruptedException ie) {
177 // ignore, the loop will try again
178 }
179 }
180
181 return booleanSupplier.getAsBoolean();
182 }
183
184 static List<String> populate(Map<String, String> map) {
185 var keyRefs = genStrings(0, 26, ReferencedKeyTest::genKey);
186 var valueRefs = genStrings(0, 26, ReferencedKeyTest::genValue);
187 for (int i = 0; i < keyRefs.size(); i++) {
188 map.put(keyRefs.get(i), valueRefs.get(i));
189 }
190 return keyRefs;
191 }
192
193 static List<String> populate(Set<String> set) {
194 var keyRefs = genStrings(0, 26, ReferencedKeyTest::genKey);
195 set.addAll(keyRefs);
196 return keyRefs;
197 }
198
199 // Generate a List of consecutive strings using a function int -> String
200 static List<String> genStrings(int min, int maxExclusive, Function<Integer, String> genString) {
201 return IntStream.range(min, maxExclusive).mapToObj(i -> genString.apply(i)).toList();
202 }
203 }