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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.util;
23  
24  import org.junit.jupiter.api.BeforeEach;
25  import org.junit.jupiter.api.Test;
26  
27  import java.util.ConcurrentModificationException;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Map;
31  import java.util.NoSuchElementException;
32  import java.util.Random;
33  import java.util.Set;
34  
35  import static org.junit.jupiter.api.Assertions.assertEquals;
36  import static org.junit.jupiter.api.Assertions.assertFalse;
37  import static org.junit.jupiter.api.Assertions.assertNotEquals;
38  import static org.junit.jupiter.api.Assertions.assertTrue;
39  import static org.junit.jupiter.api.Assertions.fail;
40  
41  
42  /**
43   * Test cases for the {@link OpenIntToDoubleHashMap}.
44   */
45  class OpenIntToDoubleHashMapTest {
46  
47      private final Map<Integer, Double> javaMap = new HashMap<>();
48  
49      @BeforeEach
50      void setUp() throws Exception {
51          javaMap.put(50, 100.0);
52          javaMap.put(75, 75.0);
53          javaMap.put(25, 500.0);
54          javaMap.put(Integer.MAX_VALUE, Double.MAX_VALUE);
55          javaMap.put(0, -1.0);
56          javaMap.put(1, 0.0);
57          javaMap.put(33, -0.1);
58          javaMap.put(23234234, -242343.0);
59          javaMap.put(23321, Double.MIN_VALUE);
60          javaMap.put(-4444, 332.0);
61          javaMap.put(-1, -2323.0);
62          javaMap.put(Integer.MIN_VALUE, 44.0);
63  
64          /* Add a few more to cause the table to rehash */
65          javaMap.putAll(generate());
66  
67      }
68  
69      private Map<Integer, Double> generate() {
70          Map<Integer, Double> map = new HashMap<>();
71          Random r = new Random();
72          for (int i = 0; i < 2000; ++i)
73              map.put(r.nextInt(), r.nextDouble());
74          return map;
75      }
76  
77      private OpenIntToDoubleHashMap createFromJavaMap() {
78          OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
79          for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
80              map.put(mapEntry.getKey(), mapEntry.getValue());
81          }
82          return map;
83      }
84  
85      @Test
86      void testPutAndGetWith0ExpectedSize() {
87          OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap(0);
88          customAssertPutAndGet(map);
89      }
90  
91      @Test
92      void testPutAndGetWithExpectedSize() {
93          OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap(500);
94          customAssertPutAndGet(map);
95      }
96  
97      @Test
98      void testPutAndGet() {
99          OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
100         customAssertPutAndGet(map);
101     }
102 
103     private void customAssertPutAndGet(OpenIntToDoubleHashMap map) {
104         customAssertPutAndGet(map, 0, new HashSet<>());
105     }
106 
107     private void customAssertPutAndGet(OpenIntToDoubleHashMap map, int mapSize,
108                                        Set<Integer> keysInMap) {
109         assertEquals(mapSize, map.size());
110         for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
111             map.put(mapEntry.getKey(), mapEntry.getValue());
112             if (!keysInMap.contains(mapEntry.getKey()))
113                 ++mapSize;
114             assertEquals(mapSize, map.size());
115             assertTrue(Precision.equals(mapEntry.getValue(), map.get(mapEntry.getKey()), 1));
116         }
117     }
118 
119     @Test
120     void testPutAbsentOnExisting() {
121         OpenIntToDoubleHashMap map = createFromJavaMap();
122         int size = javaMap.size();
123         for (Map.Entry<Integer, Double> mapEntry : generateAbsent().entrySet()) {
124             map.put(mapEntry.getKey(), mapEntry.getValue());
125             assertEquals(++size, map.size());
126             assertTrue(Precision.equals(mapEntry.getValue(), map.get(mapEntry.getKey()), 1));
127         }
128     }
129 
130     @Test
131     void testPutOnExisting() {
132         OpenIntToDoubleHashMap map = createFromJavaMap();
133         for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
134             map.put(mapEntry.getKey(), mapEntry.getValue());
135             assertEquals(javaMap.size(), map.size());
136             assertTrue(Precision.equals(mapEntry.getValue(), map.get(mapEntry.getKey()), 1));
137         }
138     }
139 
140     @Test
141     void testGetAbsent() {
142         Map<Integer, Double> generated = generateAbsent();
143         OpenIntToDoubleHashMap map = createFromJavaMap();
144 
145         for (Map.Entry<Integer, Double> mapEntry : generated.entrySet())
146             assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
147     }
148 
149     @Test
150     void testGetFromEmpty() {
151         OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
152         assertTrue(Double.isNaN(map.get(5)));
153         assertTrue(Double.isNaN(map.get(0)));
154         assertTrue(Double.isNaN(map.get(50)));
155     }
156 
157     @Test
158     void testRemove() {
159         OpenIntToDoubleHashMap map = createFromJavaMap();
160         int mapSize = javaMap.size();
161         assertEquals(mapSize, map.size());
162         for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
163             map.remove(mapEntry.getKey());
164             assertEquals(--mapSize, map.size());
165             assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
166         }
167 
168         /* Ensure that put and get still work correctly after removals */
169         customAssertPutAndGet(map);
170     }
171 
172     /* This time only remove some entries */
173     @Test
174     void testRemove2() {
175         OpenIntToDoubleHashMap map = createFromJavaMap();
176         int mapSize = javaMap.size();
177         int count = 0;
178         Set<Integer> keysInMap = new HashSet<>(javaMap.keySet());
179         for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
180             keysInMap.remove(mapEntry.getKey());
181             map.remove(mapEntry.getKey());
182             assertEquals(--mapSize, map.size());
183             assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
184             if (count++ > 5)
185                 break;
186         }
187 
188         /* Ensure that put and get still work correctly after removals */
189         customAssertPutAndGet(map, mapSize, keysInMap);
190     }
191 
192     @Test
193     void testRemoveFromEmpty() {
194         OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
195         assertTrue(Double.isNaN(map.remove(50)));
196     }
197 
198     @Test
199     void testRemoveAbsent() {
200         Map<Integer, Double> generated = generateAbsent();
201 
202         OpenIntToDoubleHashMap map = createFromJavaMap();
203         int mapSize = map.size();
204 
205         for (Map.Entry<Integer, Double> mapEntry : generated.entrySet()) {
206             map.remove(mapEntry.getKey());
207             assertEquals(mapSize, map.size());
208             assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
209         }
210     }
211 
212     /**
213      * Returns a map with at least 100 elements where each element is absent from javaMap.
214      */
215     private Map<Integer, Double> generateAbsent() {
216         Map<Integer, Double> generated = new HashMap<>();
217         do {
218             generated.putAll(generate());
219             for (Integer key : javaMap.keySet())
220                 generated.remove(key);
221         } while (generated.size() < 100);
222         return generated;
223     }
224 
225     @Test
226     void testCopy() {
227         OpenIntToDoubleHashMap copy =
228             new OpenIntToDoubleHashMap(createFromJavaMap());
229         assertEquals(javaMap.size(), copy.size());
230 
231         for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet())
232             assertTrue(Precision.equals(mapEntry.getValue(), copy.get(mapEntry.getKey()), 1));
233     }
234 
235     @Test
236     void testContainsKey() {
237         OpenIntToDoubleHashMap map = createFromJavaMap();
238         for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
239             assertTrue(map.containsKey(mapEntry.getKey()));
240         }
241         for (Map.Entry<Integer, Double> mapEntry : generateAbsent().entrySet()) {
242             assertFalse(map.containsKey(mapEntry.getKey()));
243         }
244         for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
245             int key = mapEntry.getKey();
246             assertTrue(map.containsKey(key));
247             map.remove(key);
248             assertFalse(map.containsKey(key));
249         }
250     }
251 
252     @Test
253     void testIterator() {
254         OpenIntToDoubleHashMap map = createFromJavaMap();
255         OpenIntToDoubleHashMap.Iterator iterator = map.iterator();
256         for (int i = 0; i < map.size(); ++i) {
257             assertTrue(iterator.hasNext());
258             iterator.advance();
259             int key = iterator.key();
260             assertTrue(map.containsKey(key));
261             assertEquals(javaMap.get(key), map.get(key), 0);
262             assertEquals(javaMap.get(key), iterator.value(), 0);
263             assertTrue(javaMap.containsKey(key));
264         }
265         assertFalse(iterator.hasNext());
266         try {
267             iterator.advance();
268             fail("an exception should have been thrown");
269         } catch (NoSuchElementException nsee) {
270             // expected
271         }
272     }
273 
274     @Test
275     void testEquals() {
276         OpenIntToDoubleHashMap map1 = new OpenIntToDoubleHashMap();
277         map1.put(2,   2.5);
278         map1.put(17, -0.5);
279         map1.put(16,  0.0);
280         assertEquals(map1, map1);
281         OpenIntToDoubleHashMap map2 = new OpenIntToDoubleHashMap();
282         map2.put(17, -0.5);
283         map2.put(2,   2.5);
284         map2.put(16,  0.0);
285         assertEquals(map1, map2);
286         map2.put(16,  0.25);
287         assertNotEquals(map1, map2);
288         map2.put(16,  0.0);
289         assertEquals(map1, map2);
290         assertNotEquals("", map1);
291         assertNotEquals(null, map1);
292     }
293 
294     @Test
295     void testHashcode() {
296         OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
297         map.put(2,   2.5);
298         map.put(17, -0.5);
299         map.put(16,  0.0);
300         assertEquals(-686630537, map.hashCode());
301     }
302 
303     @Test
304     void testConcurrentModification() {
305         OpenIntToDoubleHashMap map = createFromJavaMap();
306         OpenIntToDoubleHashMap.Iterator iterator = map.iterator();
307         map.put(3, 3);
308         try {
309             iterator.advance();
310             fail("an exception should have been thrown");
311         } catch (ConcurrentModificationException cme) {
312             // expected
313         }
314     }
315 
316     /**
317      * Regression test for a bug in findInsertionIndex where the hashing in the second probing
318      * loop was inconsistent with the first causing duplicate keys after the right sequence
319      * of puts and removes.
320      */
321     @Test
322     void testPutKeysWithCollisions() {
323         OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
324         int key1 = -1996012590;
325         double value1 = 1.0;
326         map.put(key1, value1);
327         int key2 = 835099822;
328         map.put(key2, value1);
329         int key3 = 1008859686;
330         map.put(key3, value1);
331         assertTrue(Precision.equals(value1, map.get(key3), 1));
332         assertEquals(3, map.size());
333 
334         map.remove(key2);
335         double value2 = 2.0;
336         map.put(key3, value2);
337         assertTrue(Precision.equals(value2, map.get(key3), 1));
338         assertEquals(2, map.size());
339     }
340 
341     /**
342      * Similar to testPutKeysWithCollisions() but exercises the codepaths in a slightly
343      * different manner.
344      */
345     @Test
346     void testPutKeysWithCollision2() {
347         OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
348         int key1 = 837989881;
349         double value1 = 1.0;
350         map.put(key1, value1);
351         int key2 = 476463321;
352         map.put(key2, value1);
353         assertEquals(2, map.size());
354         assertTrue(Precision.equals(value1, map.get(key2), 1));
355 
356         map.remove(key1);
357         double value2 = 2.0;
358         map.put(key2, value2);
359         assertEquals(1, map.size());
360         assertTrue(Precision.equals(value2, map.get(key2), 1));
361     }
362 
363 }