View Javadoc
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 }