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 }