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