1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections4.map;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertFalse;
21 import static org.junit.jupiter.api.Assertions.assertNotNull;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertSame;
24 import static org.junit.jupiter.api.Assertions.assertThrows;
25 import static org.junit.jupiter.api.Assertions.assertTrue;
26 import static org.junit.jupiter.api.Assertions.fail;
27
28 import java.lang.ref.WeakReference;
29 import java.util.Iterator;
30 import java.util.Map;
31
32 import org.apache.commons.collections4.IterableMap;
33 import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength;
34 import org.junit.jupiter.api.Test;
35
36 /**
37 * Tests for ReferenceIdentityMap.
38 *
39 * @param <K> the key type.
40 * @param <V> the value type.
41 */
42 public class ReferenceIdentityMapTest<K, V> extends AbstractIterableMapTest<K, V> {
43
44 private static final Integer I1A = new Integer(1); // Cannot use valueOf here
45 private static final Integer I1B = new Integer(1);
46 private static final Integer I2A = new Integer(2);
47 private static final Integer I2B = new Integer(2);
48
49 @SuppressWarnings("unused")
50 private static void gc() {
51 try {
52 // trigger GC
53 final byte[][] tooLarge = new byte[1000000000][1000000000];
54 fail("you have too much RAM");
55 } catch (final OutOfMemoryError ex) {
56 System.gc(); // ignore
57 }
58 }
59
60 WeakReference<K> keyReference;
61
62 WeakReference<V> valueReference;
63
64 @SuppressWarnings("unchecked")
65 private Map<K, V> buildRefMap() {
66 final K key = (K) new Object();
67 final V value = (V) new Object();
68
69 keyReference = new WeakReference<>(key);
70 valueReference = new WeakReference<>(value);
71
72 final Map<K, V> testMap = new ReferenceIdentityMap<>(ReferenceStrength.WEAK, ReferenceStrength.HARD, true);
73 testMap.put(key, value);
74
75 assertEquals(value, testMap.get(key), "In map");
76 assertNotNull(keyReference.get(), "Weak reference released early (1)");
77 assertNotNull(valueReference.get(), "Weak reference released early (2)");
78 return testMap;
79 }
80
81 @Override
82 public String getCompatibilityVersion() {
83 return "4";
84 }
85
86 // public void testCreate() throws Exception {
87 // resetEmpty();
88 // writeExternalFormToDisk(
89 // (java.io.Serializable) map,
90 // "src/test/resources/data/test/ReferenceIdentityMap.emptyCollection.version4.obj");
91 // resetFull();
92 // writeExternalFormToDisk(
93 // (java.io.Serializable) map,
94 // "src/test/resources/data/test/ReferenceIdentityMap.fullCollection.version4.obj");
95 // }
96
97 @Override
98 public boolean isAllowNullKey() {
99 return false;
100 }
101
102 @Override
103 public boolean isAllowNullValueGet() {
104 return true;
105 }
106
107 @Override
108 public boolean isAllowNullValuePut() {
109 return false;
110 }
111
112 @Override
113 public Map<K, V> makeConfirmedMap() {
114 // Testing against another [collections] class generally isn't a good idea,
115 // but the closest alternative is IdentityHashMap, which propagates reference-equality down to keySet and values.
116 // arguably ReferenceIdentityMap should do the same but that's a later discussion.
117 return new IdentityMap<>();
118 }
119
120 /*
121 // Tests often fail because gc is uncontrollable
122
123 @Test
124 public void testPurge() {
125 ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
126 Object[] hard = new Object[10];
127 for (int i = 0; i < hard.length; i++) {
128 hard[i] = new Object();
129 map.put(hard[i], new Object());
130 }
131 gc();
132 assertTrue("map should be empty after purge of weak values", map.isEmpty());
133
134 for (int i = 0; i < hard.length; i++) {
135 map.put(new Object(), hard[i]);
136 }
137 gc();
138 assertTrue("map should be empty after purge of weak keys", map.isEmpty());
139
140 for (int i = 0; i < hard.length; i++) {
141 map.put(new Object(), hard[i]);
142 map.put(hard[i], new Object());
143 }
144
145 gc();
146 assertTrue("map should be empty after purge of weak keys and values", map.isEmpty());
147 }
148
149 @Test
150 public void testGetAfterGC() {
151 ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
152 for (int i = 0; i < 10; i++) {
153 map.put(Integer.valueOf(i), Integer.valueOf(i));
154 }
155
156 gc();
157 for (int i = 0; i < 10; i++) {
158 Integer I = Integer.valueOf(i);
159 assertTrue("map.containsKey should return false for GC'd element", !map.containsKey(I));
160 assertTrue("map.get should return null for GC'd element", map.get(I) == null);
161 }
162 }
163
164 @Test
165 public void testEntrySetIteratorAfterGC() {
166 ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
167 Object[] hard = new Object[10];
168 for (int i = 0; i < 10; i++) {
169 hard[i] = Integer.valueOf(10 + i);
170 map.put(Integer.valueOf(i), Integer.valueOf(i));
171 map.put(hard[i], hard[i]);
172 }
173
174 gc();
175 Iterator iterator = map.entrySet().iterator();
176 while (iterator.hasNext()) {
177 Map.Entry entry = (Map.Entry)iterator.next();
178 Integer key = (Integer)entry.getKey();
179 Integer value = (Integer)entry.getValue();
180 assertTrue("iterator should skip GC'd keys", key.intValue() >= 10);
181 assertTrue("iterator should skip GC'd values", value.intValue() >= 10);
182 }
183
184 }
185
186 @Test
187 public void testMapIteratorAfterGC() {
188 ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
189 Object[] hard = new Object[10];
190 for (int i = 0; i < 10; i++) {
191 hard[i] = Integer.valueOf(10 + i);
192 map.put(Integer.valueOf(i), Integer.valueOf(i));
193 map.put(hard[i], hard[i]);
194 }
195
196 gc();
197 MapIterator iterator = map.mapIterator();
198 while (iterator.hasNext()) {
199 Object key1 = iterator.next();
200 Integer key = (Integer) iterator.getKey();
201 Integer value = (Integer) iterator.getValue();
202 assertTrue("iterator keys should match", key == key1);
203 assertTrue("iterator should skip GC'd keys", key.intValue() >= 10);
204 assertTrue("iterator should skip GC'd values", value.intValue() >= 10);
205 }
206
207 }
208
209 @Test
210 public void testMapIteratorAfterGC2() {
211 ReferenceIdentityMap map = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.WEAK);
212 Object[] hard = new Object[10];
213 for (int i = 0; i < 10; i++) {
214 hard[i] = Integer.valueOf(10 + i);
215 map.put(Integer.valueOf(i), Integer.valueOf(i));
216 map.put(hard[i], hard[i]);
217 }
218
219 MapIterator iterator = map.mapIterator();
220 while (iterator.hasNext()) {
221 Object key1 = iterator.next();
222 gc();
223 Integer key = (Integer) iterator.getKey();
224 Integer value = (Integer) iterator.getValue();
225 assertTrue("iterator keys should match", key == key1);
226 assertTrue("iterator should skip GC'd keys", key.intValue() >= 10);
227 assertTrue("iterator should skip GC'd values", value.intValue() >= 10);
228 }
229
230 }
231 */
232
233 @Override
234 public ReferenceIdentityMap<K, V> makeObject() {
235 return new ReferenceIdentityMap<>(ReferenceStrength.WEAK, ReferenceStrength.WEAK);
236 }
237
238 @Test
239 @SuppressWarnings("unchecked")
240 public void testBasics() {
241 final IterableMap<K, V> map = new ReferenceIdentityMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD);
242 assertEquals(0, map.size());
243
244 map.put((K) I1A, (V) I2A);
245 assertEquals(1, map.size());
246 assertSame(I2A, map.get(I1A));
247 assertSame(null, map.get(I1B));
248 assertTrue(map.containsKey(I1A));
249 assertFalse(map.containsKey(I1B));
250 assertTrue(map.containsValue(I2A));
251 assertFalse(map.containsValue(I2B));
252
253 map.put((K) I1A, (V) I2B);
254 assertEquals(1, map.size());
255 assertSame(I2B, map.get(I1A));
256 assertSame(null, map.get(I1B));
257 assertTrue(map.containsKey(I1A));
258 assertFalse(map.containsKey(I1B));
259 assertFalse(map.containsValue(I2A));
260 assertTrue(map.containsValue(I2B));
261
262 map.put((K) I1B, (V) I2B);
263 assertEquals(2, map.size());
264 assertSame(I2B, map.get(I1A));
265 assertSame(I2B, map.get(I1B));
266 assertTrue(map.containsKey(I1A));
267 assertTrue(map.containsKey(I1B));
268 assertFalse(map.containsValue(I2A));
269 assertTrue(map.containsValue(I2B));
270 }
271
272 @Test
273 @SuppressWarnings("unchecked")
274 public void testHashEntry() {
275 final IterableMap<K, V> map = new ReferenceIdentityMap<>(ReferenceStrength.HARD, ReferenceStrength.HARD);
276
277 map.put((K) I1A, (V) I2A);
278 map.put((K) I1B, (V) I2A);
279
280 final Map.Entry<K, V> entry1 = map.entrySet().iterator().next();
281 final Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
282 final Map.Entry<K, V> entry2 = it.next();
283 final Map.Entry<K, V> entry3 = it.next();
284
285 assertTrue(entry1.equals(entry2));
286 assertTrue(entry2.equals(entry1));
287 assertFalse(entry1.equals(entry3));
288 }
289
290 @Test
291 @SuppressWarnings("unchecked")
292 public void testNullHandling() {
293 resetFull();
294 assertNull(getMap().get(null));
295 assertFalse(getMap().containsKey(null));
296 assertFalse(getMap().containsValue(null));
297 assertNull(getMap().remove(null));
298 assertFalse(getMap().entrySet().contains(null));
299 assertFalse(getMap().containsKey(null));
300 assertFalse(getMap().containsValue(null));
301 assertThrows(NullPointerException.class, () -> getMap().put(null, null));
302 assertThrows(NullPointerException.class, () -> getMap().put((K) new Object(), null));
303 assertThrows(NullPointerException.class, () -> getMap().put(null, (V) new Object()));
304 }
305
306 /** Tests whether purge values setting works */
307 @Test
308 public void testPurgeValues() throws Exception {
309 // many thanks to Juozas Baliuka for suggesting this method
310 final Map<K, V> testMap = buildRefMap();
311
312 int iterations = 0;
313 int bytz = 2;
314 while (true) {
315 System.gc();
316 if (iterations++ > 50) {
317 fail("Max iterations reached before resource released.");
318 }
319 testMap.isEmpty();
320 if (
321 keyReference.get() == null &&
322 valueReference.get() == null) {
323 break;
324
325 }
326 // create garbage:
327 @SuppressWarnings("unused")
328 final byte[] b = new byte[bytz];
329 bytz *= 2;
330 }
331 }
332
333 }