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 }