1 /*
  2  * Copyright (c) 2022, 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  * @run testng/othervm WeakValuePolicyTest
 27  * @summary Test WeakHashMap.ValuePolicy modes
 28  */
 29 import org.testng.annotations.DataProvider;
 30 import org.testng.annotations.Test;
 31 import org.testng.Assert;
 32 import static org.testng.Assert.*;
 33 
 34 import java.util.ArrayList;
 35 import java.util.Objects;
 36 import java.util.WeakHashMap;
 37 import java.lang.ref.Reference;
 38 import java.lang.ref.ReferenceQueue;
 39 import java.lang.ref.SoftReference;
 40 
 41 @Test
 42 public class WeakValuePolicyTest {
 43 
 44     record KeyValue(Object key, String value){};
 45 
 46     @DataProvider(name="Keys")
 47     KeyValue[] keys() {
 48         return new KeyValue[] {
 49                 new KeyValue(new IntValue(1), "IntValue(1)"),
 50                 new KeyValue(new StringValue("xyz"), "StringValue(\"xyz\")"),
 51                 new KeyValue(Integer.valueOf(2), "Integer.valueOf(2)"),
 52         };
 53     }
 54 
 55     @DataProvider(name="WeakHashMaps")
 56     Object[][] weakValuePolicy() {
 57         return new Object[][] {
 58                 {new WeakHashMap<Object, String>(0, 0.75f, WeakHashMap.ValuePolicy.SOFT)},
 59                 {new WeakHashMap<Object, String>(0, 0.75f, WeakHashMap.ValuePolicy.STRONG)},
 60         };
 61     }
 62 
 63     @Test(dataProvider="WeakHashMaps")
 64     public void putValueSoftStrong(WeakHashMap map) {
 65         WeakHashMap.ValuePolicy policy = map.valuePolicy();
 66         for (KeyValue kv : keys()) {
 67             System.out.println("k: " + kv.key);
 68             Assert.assertFalse(map.containsKey(kv.key), "map.contains on empty map: " + kv.key);
 69             Assert.assertNull(map.get(kv.key), "map.get on empty map: " + kv.key);
 70             var prev = map.put(kv.key, kv.value);
 71             Assert.assertNull(prev, "map.put on empty map did not return null: " + kv.key);
 72             Assert.assertEquals(map.get(kv.key), kv.value, "map.get after put: " + kv.key);
 73 
 74             forceGC();
 75 
 76             if (kv.key.getClass().isValue() &&
 77                     policy.equals(WeakHashMap.ValuePolicy.SOFT)) {
 78                 Assert.assertFalse(map.containsKey(kv.key), "map.containsKey after GC: " + kv.key);
 79                 String value = (String)map.get(kv.key);
 80                 Assert.assertNull(value, "map.get after GC should return null: " + kv.key);
 81             } else {
 82                 prev = map.remove(kv.key);
 83                 Assert.assertEquals(prev, kv.value, "map.remove: " + kv.key);
 84                 Assert.assertNull(map.get(kv.key), "map.get after remove: " + kv.key);
 85             }
 86             Assert.assertTrue(map.isEmpty(), "m.isEmpty()");
 87         }
 88     }
 89 
 90     @Test
 91     public void putValueDiscard() {
 92         final WeakHashMap<Object, String> map = new WeakHashMap<>(0, 0.75f, WeakHashMap.ValuePolicy.DISCARD);
 93         final IntValue intValue = new IntValue(1);
 94         String old = map.put(intValue, "IntValue(1)");
 95         Assert.assertNull(old, "old");
 96         old = map.get(intValue);
 97         Assert.assertNull(old, "get after put of discarded value");
 98     }
 99 
100     @Test
101     public void putValueThrows() {
102         final WeakHashMap<Object, String> map = new WeakHashMap<>(0, 0.75f, WeakHashMap.ValuePolicy.THROW);
103         Assert.assertThrows(IdentityException.class, () -> map.put(new IntValue(1), "IntValue(1)"));
104     }
105 
106     private void forceGC()  {
107         Object marker = new Object();
108         ReferenceQueue<Object> queue = new ReferenceQueue<>();
109         SoftReference expected = new SoftReference(marker, queue);
110         marker = null;
111         Reference<?> actual = waitForReference(queue);
112         assertEquals(actual, expected, "Unexpected Reference queued");
113     }
114 
115     /**
116      * Wait for any Reference to be enqueued to a ReferenceQueue.
117      * The garbage collector is invoked to find unreferenced objects.
118      *
119      * @param queue a ReferenceQueue
120      * @return true if the reference was enqueued, false if not enqueued within
121      */
122     private static Reference<?> waitForReference(ReferenceQueue<Object> queue) {
123         Objects.requireNonNull(queue, "queue should not be null");
124         ArrayList<Object> chunks = new ArrayList<>(10000);
125         try {
126             for (int i = 0; i < 10_000; i++) {
127                 chunks.add(new byte[100_000]);
128             }
129         } catch (OutOfMemoryError oome) {
130 
131         } finally {
132             chunks = null;
133         }
134         for (int retries = 100; retries > 0; retries--) {
135             try {
136                 var r = queue.remove(10L);
137                 if (r != null) {
138                     return r;
139                 }
140             } catch (InterruptedException ie) {
141                 // ignore, the loop will try again
142             }
143         }
144         return null;
145     }
146 
147     static value class IntValue {
148         int value;
149 
150         IntValue(int value) {
151             this.value = value;
152         }
153 
154         @java.lang.Override
155         public java.lang.String toString() {
156             return "IntValue{" + "value=" + value + '}';
157         }
158     }
159 
160     static value class StringValue {
161         String value;
162 
163         StringValue(String value) {
164             this.value = value;
165         }
166 
167         @java.lang.Override
168         public java.lang.String toString() {
169             return "StringValue{" + "value='" + value + '\'' + '}';
170         }
171     }
172 }