1 /*
2  * Copyright (c) 2013, 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  * @bug 8010122 8004518 8024331 8024688
27  * @summary Test Map default methods
28  * @author Mike Duigou
29  * @run testng Defaults
30  */
31 import java.util.AbstractMap;
32 import java.util.AbstractSet;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.EnumMap;
38 import java.util.HashMap;
39 import java.util.Hashtable;
40 import java.util.HashSet;
41 import java.util.IdentityHashMap;
42 import java.util.Iterator;
43 import java.util.LinkedHashMap;
44 import java.util.Map;
45 import java.util.TreeMap;
46 import java.util.Set;
47 import java.util.WeakHashMap;
48 import java.util.concurrent.ConcurrentMap;
49 import java.util.concurrent.ConcurrentHashMap;
50 import java.util.concurrent.ConcurrentSkipListMap;
51 import java.util.function.BiFunction;
52 import java.util.function.Supplier;
53 
54 import org.testng.annotations.Test;
55 import org.testng.annotations.DataProvider;
56 import static org.testng.Assert.fail;
57 import static org.testng.Assert.assertEquals;
58 import static org.testng.Assert.assertTrue;
59 import static org.testng.Assert.assertFalse;
60 import static org.testng.Assert.assertNull;
61 import static org.testng.Assert.assertSame;
62 
63 public class Defaults {
64 
65     @Test(dataProvider = "Map<IntegerEnum,String> rw=all keys=withNull values=withNull")
testGetOrDefaultNulls(String description, Map<IntegerEnum, String> map)66     public void testGetOrDefaultNulls(String description, Map<IntegerEnum, String> map) {
67         assertTrue(map.containsKey(null), description + ": null key absent");
68         assertNull(map.get(null), description + ": value not null");
69         assertSame(map.get(null), map.getOrDefault(null, EXTRA_VALUE), description + ": values should match");
70     }
71 
72     @Test(dataProvider = "Map<IntegerEnum,String> rw=all keys=all values=all")
testGetOrDefault(String description, Map<IntegerEnum, String> map)73     public void testGetOrDefault(String description, Map<IntegerEnum, String> map) {
74         assertTrue(map.containsKey(KEYS[1]), "expected key missing");
75         assertSame(map.get(KEYS[1]), map.getOrDefault(KEYS[1], EXTRA_VALUE), "values should match");
76         assertFalse(map.containsKey(EXTRA_KEY), "expected absent key");
77         assertSame(map.getOrDefault(EXTRA_KEY, EXTRA_VALUE), EXTRA_VALUE, "value not returned as default");
78         assertNull(map.getOrDefault(EXTRA_KEY, null), "null not returned as default");
79     }
80 
81     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
testPutIfAbsentNulls(String description, Map<IntegerEnum, String> map)82     public void testPutIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
83         // null -> null
84         assertTrue(map.containsKey(null), "null key absent");
85         assertNull(map.get(null), "value not null");
86         assertNull(map.putIfAbsent(null, EXTRA_VALUE), "previous not null");
87         // null -> EXTRA_VALUE
88         assertTrue(map.containsKey(null), "null key absent");
89         assertSame(map.get(null), EXTRA_VALUE, "unexpected value");
90         assertSame(map.putIfAbsent(null, null), EXTRA_VALUE, "previous not expected value");
91         assertTrue(map.containsKey(null), "null key absent");
92         assertSame(map.get(null), EXTRA_VALUE, "unexpected value");
93         assertSame(map.remove(null), EXTRA_VALUE, "removed unexpected value");
94         // null -> <absent>
95 
96         assertFalse(map.containsKey(null), description + ": key present after remove");
97         assertNull(map.putIfAbsent(null, null), "previous not null");
98         // null -> null
99         assertTrue(map.containsKey(null), "null key absent");
100         assertNull(map.get(null), "value not null");
101         assertNull(map.putIfAbsent(null, EXTRA_VALUE), "previous not null");
102         assertSame(map.get(null), EXTRA_VALUE, "value not expected");
103     }
104 
105     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testPutIfAbsent(String description, Map<IntegerEnum, String> map)106     public void testPutIfAbsent(String description, Map<IntegerEnum, String> map) {
107         // 1 -> 1
108         assertTrue(map.containsKey(KEYS[1]));
109         Object expected = map.get(KEYS[1]);
110         assertTrue(null == expected || expected == VALUES[1]);
111         assertSame(map.putIfAbsent(KEYS[1], EXTRA_VALUE), expected);
112         assertSame(map.get(KEYS[1]), expected);
113 
114         // EXTRA_KEY -> <absent>
115         assertFalse(map.containsKey(EXTRA_KEY));
116         assertSame(map.putIfAbsent(EXTRA_KEY, EXTRA_VALUE), null);
117         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
118         assertSame(map.putIfAbsent(EXTRA_KEY, VALUES[2]), EXTRA_VALUE);
119         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
120     }
121 
122     @Test(dataProvider = "Map<IntegerEnum,String> rw=all keys=all values=all")
testForEach(String description, Map<IntegerEnum, String> map)123     public void testForEach(String description, Map<IntegerEnum, String> map) {
124         IntegerEnum[] EACH_KEY = new IntegerEnum[map.size()];
125 
126         map.forEach((k, v) -> {
127             int idx = (null == k) ? 0 : k.ordinal(); // substitute for index.
128             assertNull(EACH_KEY[idx]);
129             EACH_KEY[idx] = (idx == 0) ? KEYS[0] : k; // substitute for comparison.
130             assertSame(v, map.get(k));
131         });
132 
133         assertEquals(KEYS, EACH_KEY, description);
134     }
135 
136     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testReplaceAll(String description, Map<IntegerEnum, String> map)137     public static void testReplaceAll(String description, Map<IntegerEnum, String> map) {
138         IntegerEnum[] EACH_KEY = new IntegerEnum[map.size()];
139         Set<String> EACH_REPLACE = new HashSet<>(map.size());
140 
141         map.replaceAll((k,v) -> {
142             int idx = (null == k) ? 0 : k.ordinal(); // substitute for index.
143             assertNull(EACH_KEY[idx]);
144             EACH_KEY[idx] = (idx == 0) ? KEYS[0] : k; // substitute for comparison.
145             assertSame(v, map.get(k));
146             String replacement = v + " replaced";
147             EACH_REPLACE.add(replacement);
148             return replacement;
149         });
150 
151         assertEquals(KEYS, EACH_KEY, description);
152         assertEquals(map.values().size(), EACH_REPLACE.size(), description + EACH_REPLACE);
153         assertTrue(EACH_REPLACE.containsAll(map.values()), description + " : " + EACH_REPLACE + " != " + map.values());
154         assertTrue(map.values().containsAll(EACH_REPLACE), description + " : " + EACH_REPLACE + " != " + map.values());
155     }
156 
157     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=nonNull values=nonNull")
testReplaceAllNoNullReplacement(String description, Map<IntegerEnum, String> map)158     public static void testReplaceAllNoNullReplacement(String description, Map<IntegerEnum, String> map) {
159         assertThrows(
160             () -> { map.replaceAll(null); },
161             NullPointerException.class,
162             description);
163         assertThrows(
164             () -> { map.replaceAll((k,v) -> null); },
165             NullPointerException.class,
166             description + " should not allow replacement with null value");
167     }
168 
169     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
testRemoveNulls(String description, Map<IntegerEnum, String> map)170     public static void testRemoveNulls(String description, Map<IntegerEnum, String> map) {
171         assertTrue(map.containsKey(null), "null key absent");
172         assertNull(map.get(null), "value not null");
173         assertFalse(map.remove(null, EXTRA_VALUE), description);
174         assertTrue(map.containsKey(null));
175         assertNull(map.get(null));
176         assertTrue(map.remove(null, null));
177         assertFalse(map.containsKey(null));
178         assertNull(map.get(null));
179         assertFalse(map.remove(null, null));
180     }
181 
182     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testRemove(String description, Map<IntegerEnum, String> map)183     public static void testRemove(String description, Map<IntegerEnum, String> map) {
184         assertTrue(map.containsKey(KEYS[1]));
185         Object expected = map.get(KEYS[1]);
186         assertTrue(null == expected || expected == VALUES[1]);
187         assertFalse(map.remove(KEYS[1], EXTRA_VALUE), description);
188         assertSame(map.get(KEYS[1]), expected);
189         assertTrue(map.remove(KEYS[1], expected));
190         assertNull(map.get(KEYS[1]));
191         assertFalse(map.remove(KEYS[1], expected));
192 
193         assertFalse(map.containsKey(EXTRA_KEY));
194         assertFalse(map.remove(EXTRA_KEY, EXTRA_VALUE));
195     }
196 
197     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
testReplaceKVNulls(String description, Map<IntegerEnum, String> map)198     public void testReplaceKVNulls(String description, Map<IntegerEnum, String> map) {
199         assertTrue(map.containsKey(null), "null key absent");
200         assertNull(map.get(null), "value not null");
201         assertSame(map.replace(null, EXTRA_VALUE), null);
202         assertSame(map.get(null), EXTRA_VALUE);
203     }
204 
205     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=nonNull values=nonNull")
testReplaceKVNoNulls(String description, Map<IntegerEnum, String> map)206     public void testReplaceKVNoNulls(String description, Map<IntegerEnum, String> map) {
207         assertTrue(map.containsKey(FIRST_KEY), "expected key missing");
208         assertSame(map.get(FIRST_KEY), FIRST_VALUE, "found wrong value");
209         assertThrows( () -> {map.replace(FIRST_KEY, null);}, NullPointerException.class, description + ": should throw NPE");
210         assertSame(map.replace(FIRST_KEY, EXTRA_VALUE), FIRST_VALUE, description + ": replaced wrong value");
211         assertSame(map.get(FIRST_KEY), EXTRA_VALUE, "found wrong value");
212     }
213 
214     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testReplaceKV(String description, Map<IntegerEnum, String> map)215     public void testReplaceKV(String description, Map<IntegerEnum, String> map) {
216         assertTrue(map.containsKey(KEYS[1]));
217         Object expected = map.get(KEYS[1]);
218         assertTrue(null == expected || expected == VALUES[1]);
219         assertSame(map.replace(KEYS[1], EXTRA_VALUE), expected);
220         assertSame(map.get(KEYS[1]), EXTRA_VALUE);
221 
222         assertFalse(map.containsKey(EXTRA_KEY));
223         assertNull(map.replace(EXTRA_KEY, EXTRA_VALUE));
224         assertFalse(map.containsKey(EXTRA_KEY));
225         assertNull(map.get(EXTRA_KEY));
226         assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
227         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
228         assertSame(map.replace(EXTRA_KEY, (String)expected), EXTRA_VALUE);
229         assertSame(map.get(EXTRA_KEY), expected);
230     }
231 
232     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
testReplaceKVVNulls(String description, Map<IntegerEnum, String> map)233     public void testReplaceKVVNulls(String description, Map<IntegerEnum, String> map) {
234         assertTrue(map.containsKey(null), "null key absent");
235         assertNull(map.get(null), "value not null");
236         assertFalse(map.replace(null, EXTRA_VALUE, EXTRA_VALUE));
237         assertNull(map.get(null));
238         assertTrue(map.replace(null, null, EXTRA_VALUE));
239         assertSame(map.get(null), EXTRA_VALUE);
240         assertTrue(map.replace(null, EXTRA_VALUE, EXTRA_VALUE));
241         assertSame(map.get(null), EXTRA_VALUE);
242     }
243 
244     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=nonNull values=nonNull")
testReplaceKVVNoNulls(String description, Map<IntegerEnum, String> map)245     public void testReplaceKVVNoNulls(String description, Map<IntegerEnum, String> map) {
246         assertTrue(map.containsKey(FIRST_KEY), "expected key missing");
247         assertSame(map.get(FIRST_KEY), FIRST_VALUE, "found wrong value");
248         assertThrows( () -> {map.replace(FIRST_KEY, FIRST_VALUE, null);}, NullPointerException.class, description + ": should throw NPE");
249         assertThrows( () -> {if (!map.replace(FIRST_KEY, null, EXTRA_VALUE)) throw new NullPointerException("default returns false rather than throwing");}, NullPointerException.class,  description + ": should throw NPE");
250         assertTrue(map.replace(FIRST_KEY, FIRST_VALUE, EXTRA_VALUE), description + ": replaced wrong value");
251         assertSame(map.get(FIRST_KEY), EXTRA_VALUE, "found wrong value");
252     }
253 
254     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testReplaceKVV(String description, Map<IntegerEnum, String> map)255     public void testReplaceKVV(String description, Map<IntegerEnum, String> map) {
256         assertTrue(map.containsKey(KEYS[1]));
257         Object expected = map.get(KEYS[1]);
258         assertTrue(null == expected || expected == VALUES[1]);
259         assertFalse(map.replace(KEYS[1], EXTRA_VALUE, EXTRA_VALUE));
260         assertSame(map.get(KEYS[1]), expected);
261         assertTrue(map.replace(KEYS[1], (String)expected, EXTRA_VALUE));
262         assertSame(map.get(KEYS[1]), EXTRA_VALUE);
263         assertTrue(map.replace(KEYS[1], EXTRA_VALUE, EXTRA_VALUE));
264         assertSame(map.get(KEYS[1]), EXTRA_VALUE);
265 
266         assertFalse(map.containsKey(EXTRA_KEY));
267         assertFalse(map.replace(EXTRA_KEY, EXTRA_VALUE, EXTRA_VALUE));
268         assertFalse(map.containsKey(EXTRA_KEY));
269         assertNull(map.get(EXTRA_KEY));
270         assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
271         assertTrue(map.containsKey(EXTRA_KEY));
272         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
273         assertTrue(map.replace(EXTRA_KEY, EXTRA_VALUE, EXTRA_VALUE));
274         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
275     }
276 
277     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
testComputeIfAbsentNulls(String description, Map<IntegerEnum, String> map)278     public void testComputeIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
279         // null -> null
280         assertTrue(map.containsKey(null), "null key absent");
281         assertNull(map.get(null), "value not null");
282         assertSame(map.computeIfAbsent(null, (k) -> null), null,  "not expected result");
283         assertTrue(map.containsKey(null), "null key absent");
284         assertNull(map.get(null), "value not null");
285         assertSame(map.computeIfAbsent(null, (k) -> EXTRA_VALUE), EXTRA_VALUE, "not mapped to result");
286         // null -> EXTRA_VALUE
287         assertTrue(map.containsKey(null), "null key absent");
288         assertSame(map.get(null), EXTRA_VALUE,  "not expected value");
289         assertSame(map.remove(null), EXTRA_VALUE, "removed unexpected value");
290         // null -> <absent>
291         assertFalse(map.containsKey(null), "null key present");
292         assertSame(map.computeIfAbsent(null, (k) -> EXTRA_VALUE), EXTRA_VALUE, "not mapped to result");
293         // null -> EXTRA_VALUE
294         assertTrue(map.containsKey(null), "null key absent");
295         assertSame(map.get(null), EXTRA_VALUE,  "not expected value");
296     }
297 
298     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testComputeIfAbsent(String description, Map<IntegerEnum, String> map)299     public void testComputeIfAbsent(String description, Map<IntegerEnum, String> map) {
300         // 1 -> 1
301         assertTrue(map.containsKey(KEYS[1]));
302         Object expected = map.get(KEYS[1]);
303         assertTrue(null == expected || expected == VALUES[1], description + String.valueOf(expected));
304         expected = (null == expected) ? EXTRA_VALUE : expected;
305         assertSame(map.computeIfAbsent(KEYS[1], (k) -> EXTRA_VALUE), expected, description);
306         assertSame(map.get(KEYS[1]), expected, description);
307 
308         // EXTRA_KEY -> <absent>
309         assertFalse(map.containsKey(EXTRA_KEY));
310         assertNull(map.computeIfAbsent(EXTRA_KEY, (k) -> null));
311         assertFalse(map.containsKey(EXTRA_KEY));
312         assertSame(map.computeIfAbsent(EXTRA_KEY, (k) -> EXTRA_VALUE), EXTRA_VALUE);
313         // EXTRA_KEY -> EXTRA_VALUE
314         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
315     }
316 
317     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testComputeIfAbsentNullFunction(String description, Map<IntegerEnum, String> map)318     public void testComputeIfAbsentNullFunction(String description, Map<IntegerEnum, String> map) {
319         assertThrows( () -> { map.computeIfAbsent(KEYS[1], null);},
320                 NullPointerException.class,
321                 "Should throw NPE");
322     }
323 
324     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
testComputeIfPresentNulls(String description, Map<IntegerEnum, String> map)325     public void testComputeIfPresentNulls(String description, Map<IntegerEnum, String> map) {
326         assertTrue(map.containsKey(null), description + ": null key absent");
327         assertNull(map.get(null), description + ": value not null");
328         assertSame(map.computeIfPresent(null, (k, v) -> {
329             fail(description + ": null value is not deemed present");
330             return EXTRA_VALUE;
331         }), null, description);
332         assertTrue(map.containsKey(null));
333         assertNull(map.get(null), description);
334         assertNull(map.remove(EXTRA_KEY), description + ": unexpected mapping");
335         assertNull(map.put(EXTRA_KEY, null), description + ": unexpected value");
336         assertSame(map.computeIfPresent(EXTRA_KEY, (k, v) -> {
337             fail(description + ": null value is not deemed present");
338             return EXTRA_VALUE;
339         }), null, description);
340         assertNull(map.get(EXTRA_KEY), description + ": null mapping gone");
341     }
342 
343     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testComputeIfPresent(String description, Map<IntegerEnum, String> map)344     public void testComputeIfPresent(String description, Map<IntegerEnum, String> map) {
345         assertTrue(map.containsKey(KEYS[1]));
346         Object value = map.get(KEYS[1]);
347         assertTrue(null == value || value == VALUES[1], description + String.valueOf(value));
348         Object expected = (null == value) ? null : EXTRA_VALUE;
349         assertSame(map.computeIfPresent(KEYS[1], (k, v) -> {
350             assertSame(v, value);
351             return EXTRA_VALUE;
352         }), expected, description);
353         assertSame(map.get(KEYS[1]), expected, description);
354 
355         assertFalse(map.containsKey(EXTRA_KEY));
356         assertSame(map.computeIfPresent(EXTRA_KEY, (k, v) -> {
357             fail();
358             return EXTRA_VALUE;
359         }), null);
360         assertFalse(map.containsKey(EXTRA_KEY));
361         assertSame(map.get(EXTRA_KEY), null);
362     }
363 
364     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testComputeIfPresentNullFunction(String description, Map<IntegerEnum, String> map)365     public void testComputeIfPresentNullFunction(String description, Map<IntegerEnum, String> map) {
366         assertThrows( () -> { map.computeIfPresent(KEYS[1], null);},
367                 NullPointerException.class,
368                 "Should throw NPE");
369     }
370 
371      @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
testComputeNulls(String description, Map<IntegerEnum, String> map)372     public void testComputeNulls(String description, Map<IntegerEnum, String> map) {
373         assertTrue(map.containsKey(null), "null key absent");
374         assertNull(map.get(null), "value not null");
375         assertSame(map.compute(null, (k, v) -> {
376             assertNull(k);
377             assertNull(v);
378             return null;
379         }), null, description);
380         assertFalse(map.containsKey(null), description + ": null key present.");
381         assertSame(map.compute(null, (k, v) -> {
382             assertSame(k, null);
383             assertNull(v);
384             return EXTRA_VALUE;
385         }), EXTRA_VALUE, description);
386         assertTrue(map.containsKey(null));
387         assertSame(map.get(null), EXTRA_VALUE, description);
388         assertSame(map.remove(null), EXTRA_VALUE, description + ": removed value not expected");
389         // no mapping before and after
390         assertFalse(map.containsKey(null), description + ": null key present");
391         assertSame(map.compute(null, (k, v) -> {
392             assertNull(k);
393             assertNull(v);
394             return null;
395         }), null, description + ": expected null result" );
396         assertFalse(map.containsKey(null), description + ": null key present");
397         // compute with map not containing value
398         assertNull(map.remove(EXTRA_KEY),  description + ": unexpected mapping");
399         assertFalse(map.containsKey(EXTRA_KEY),  description + ": key present");
400         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
401             assertSame(k, EXTRA_KEY);
402             assertNull(v);
403             return null;
404         }), null, description);
405         assertFalse(map.containsKey(EXTRA_KEY),  description + ": null key present");
406         // ensure removal.
407         assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
408         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
409             assertSame(k, EXTRA_KEY);
410             assertSame(v, EXTRA_VALUE);
411             return null;
412         }), null, description + ": null resulted expected");
413         assertFalse(map.containsKey(EXTRA_KEY),  description + ": null key present");
414        // compute with map containing null value
415         assertNull(map.put(EXTRA_KEY, null),  description + ": unexpected value");
416         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
417             assertSame(k, EXTRA_KEY);
418             assertNull(v);
419             return null;
420         }), null, description);
421         assertFalse(map.containsKey(EXTRA_KEY),  description + ": null key present");
422         assertNull(map.put(EXTRA_KEY, null),  description + ": unexpected value");
423         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
424             assertSame(k, EXTRA_KEY);
425             assertNull(v);
426             return EXTRA_VALUE;
427         }), EXTRA_VALUE, description);
428         assertTrue(map.containsKey(EXTRA_KEY), "null key present");
429     }
430 
431     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testCompute(String description, Map<IntegerEnum, String> map)432     public void testCompute(String description, Map<IntegerEnum, String> map) {
433         assertTrue(map.containsKey(KEYS[1]));
434         Object value = map.get(KEYS[1]);
435         assertTrue(null == value || value == VALUES[1], description + String.valueOf(value));
436         assertSame(map.compute(KEYS[1], (k, v) -> {
437             assertSame(k, KEYS[1]);
438             assertSame(v, value);
439             return EXTRA_VALUE;
440         }), EXTRA_VALUE, description);
441         assertSame(map.get(KEYS[1]), EXTRA_VALUE, description);
442         assertNull(map.compute(KEYS[1], (k, v) -> {
443             assertSame(v, EXTRA_VALUE);
444             return null;
445         }), description);
446         assertFalse(map.containsKey(KEYS[1]));
447 
448         assertFalse(map.containsKey(EXTRA_KEY));
449         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
450             assertNull(v);
451             return EXTRA_VALUE;
452         }), EXTRA_VALUE);
453         assertTrue(map.containsKey(EXTRA_KEY));
454         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
455     }
456 
457     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testComputeNullFunction(String description, Map<IntegerEnum, String> map)458     public void testComputeNullFunction(String description, Map<IntegerEnum, String> map) {
459         assertThrows( () -> { map.compute(KEYS[1], null);},
460                 NullPointerException.class,
461                 "Should throw NPE");
462     }
463 
464     @Test(dataProvider = "MergeCases")
testMerge(String description, Map<IntegerEnum, String> map, Merging.Value oldValue, Merging.Value newValue, Merging.Merger merger, Merging.Value put, Merging.Value result)465     private void testMerge(String description, Map<IntegerEnum, String> map, Merging.Value oldValue, Merging.Value newValue, Merging.Merger merger, Merging.Value put, Merging.Value result) {
466             // add and check initial conditions.
467             switch(oldValue) {
468                 case ABSENT :
469                     map.remove(EXTRA_KEY);
470                     assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
471                     break;
472                 case NULL :
473                     map.put(EXTRA_KEY, null);
474                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
475                     assertNull(map.get(EXTRA_KEY), "wrong value");
476                     break;
477                 case OLDVALUE :
478                     map.put(EXTRA_KEY, VALUES[1]);
479                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
480                     assertSame(map.get(EXTRA_KEY), VALUES[1], "wrong value");
481                     break;
482                 default:
483                     fail("unexpected old value");
484             }
485 
486             String returned = map.merge(EXTRA_KEY,
487                 newValue == Merging.Value.NULL ? (String) null : VALUES[2],
488                 merger
489                 );
490 
491             // check result
492 
493             switch(result) {
494                 case NULL :
495                     assertNull(returned, "wrong value");
496                     break;
497                 case NEWVALUE :
498                     assertSame(returned, VALUES[2], "wrong value");
499                     break;
500                 case RESULT :
501                     assertSame(returned, VALUES[3], "wrong value");
502                     break;
503                 default:
504                     fail("unexpected new value");
505             }
506 
507             // check map
508             switch(put) {
509                 case ABSENT :
510                     assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
511                     break;
512                 case NULL :
513                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
514                     assertNull(map.get(EXTRA_KEY), "wrong value");
515                     break;
516                 case NEWVALUE :
517                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
518                     assertSame(map.get(EXTRA_KEY), VALUES[2], "wrong value");
519                     break;
520                 case RESULT :
521                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
522                     assertSame(map.get(EXTRA_KEY), VALUES[3], "wrong value");
523                     break;
524                 default:
525                     fail("unexpected new value");
526             }
527     }
528 
529     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
testMergeNullMerger(String description, Map<IntegerEnum, String> map)530     public void testMergeNullMerger(String description, Map<IntegerEnum, String> map) {
531         assertThrows( () -> { map.merge(KEYS[1], VALUES[1], null);},
532                 NullPointerException.class,
533                 "Should throw NPE");
534     }
535 
536     public enum IntegerEnum {
537 
538         e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
539         e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
540         e20, e21, e22, e23, e24, e25, e26, e27, e28, e29,
541         e30, e31, e32, e33, e34, e35, e36, e37, e38, e39,
542         e40, e41, e42, e43, e44, e45, e46, e47, e48, e49,
543         e50, e51, e52, e53, e54, e55, e56, e57, e58, e59,
544         e60, e61, e62, e63, e64, e65, e66, e67, e68, e69,
545         e70, e71, e72, e73, e74, e75, e76, e77, e78, e79,
546         e80, e81, e82, e83, e84, e85, e86, e87, e88, e89,
547         e90, e91, e92, e93, e94, e95, e96, e97, e98, e99,
548         EXTRA_KEY;
549         public static final int SIZE = values().length;
550     };
551     private static final int TEST_SIZE = IntegerEnum.SIZE - 1;
552     /**
553      * Realized keys ensure that there is always a hard ref to all test objects.
554      */
555     private static final IntegerEnum[] KEYS = new IntegerEnum[TEST_SIZE];
556     /**
557      * Realized values ensure that there is always a hard ref to all test
558      * objects.
559      */
560     private static final String[] VALUES = new String[TEST_SIZE];
561 
562     static {
563         IntegerEnum[] keys = IntegerEnum.values();
564         for (int each = 0; each < TEST_SIZE; each++) {
565             KEYS[each] = keys[each];
566             VALUES[each] = String.valueOf(each);
567         }
568     }
569 
570     private static final IntegerEnum FIRST_KEY = KEYS[0];
571     private static final String FIRST_VALUE = VALUES[0];
572     private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY;
573     private static final String EXTRA_VALUE = String.valueOf(TEST_SIZE);
574 
575     @DataProvider(name = "Map<IntegerEnum,String> rw=all keys=all values=all", parallel = true)
allMapProvider()576     public static Iterator<Object[]> allMapProvider() {
577         return makeAllMaps().iterator();
578     }
579 
580     @DataProvider(name = "Map<IntegerEnum,String> rw=all keys=withNull values=withNull", parallel = true)
allMapWithNullsProvider()581     public static Iterator<Object[]> allMapWithNullsProvider() {
582         return makeAllMapsWithNulls().iterator();
583     }
584 
585     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=nonNull values=nonNull", parallel = true)
rwNonNullMapProvider()586     public static Iterator<Object[]> rwNonNullMapProvider() {
587         return makeRWNoNullsMaps().iterator();
588     }
589 
590     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=nonNull values=all", parallel = true)
rwNonNullKeysMapProvider()591     public static Iterator<Object[]> rwNonNullKeysMapProvider() {
592         return makeRWMapsNoNulls().iterator();
593     }
594 
595     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=all values=all", parallel = true)
rwMapProvider()596     public static Iterator<Object[]> rwMapProvider() {
597         return makeAllRWMaps().iterator();
598     }
599 
600     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull", parallel = true)
rwNullsMapProvider()601     public static Iterator<Object[]> rwNullsMapProvider() {
602         return makeAllRWMapsWithNulls().iterator();
603     }
604 
makeAllRWMapsWithNulls()605     private static Collection<Object[]> makeAllRWMapsWithNulls() {
606         Collection<Object[]> all = new ArrayList<>();
607 
608         all.addAll(makeRWMaps(true, true));
609 
610         return all;
611     }
612 
613 
makeRWMapsNoNulls()614     private static Collection<Object[]> makeRWMapsNoNulls() {
615         Collection<Object[]> all = new ArrayList<>();
616 
617         all.addAll(makeRWNoNullKeysMaps(false));
618         all.addAll(makeRWNoNullsMaps());
619 
620         return all;
621     }
622 
makeAllROMaps()623     private static Collection<Object[]> makeAllROMaps() {
624         Collection<Object[]> all = new ArrayList<>();
625 
626         all.addAll(makeROMaps(false));
627         all.addAll(makeROMaps(true));
628 
629         return all;
630     }
631 
makeAllRWMaps()632     private static Collection<Object[]> makeAllRWMaps() {
633         Collection<Object[]> all = new ArrayList<>();
634 
635         all.addAll(makeRWNoNullsMaps());
636         all.addAll(makeRWMaps(false,true));
637         all.addAll(makeRWMaps(true,true));
638         all.addAll(makeRWNoNullKeysMaps(true));
639         return all;
640     }
641 
makeAllMaps()642     private static Collection<Object[]> makeAllMaps() {
643         Collection<Object[]> all = new ArrayList<>();
644 
645         all.addAll(makeAllROMaps());
646         all.addAll(makeAllRWMaps());
647 
648         return all;
649     }
650 
makeAllMapsWithNulls()651     private static Collection<Object[]> makeAllMapsWithNulls() {
652         Collection<Object[]> all = new ArrayList<>();
653 
654         all.addAll(makeROMaps(true));
655         all.addAll(makeRWMaps(true,true));
656 
657         return all;
658     }
659     /**
660      *
661      * @param nullKeys include null keys
662      * @param nullValues include null values
663      * @return
664      */
makeRWMaps(boolean nullKeys, boolean nullValues)665     private static Collection<Object[]> makeRWMaps(boolean nullKeys, boolean nullValues) {
666         return Arrays.asList(
667             new Object[]{"HashMap", makeMap(HashMap::new, nullKeys, nullValues)},
668             new Object[]{"IdentityHashMap", makeMap(IdentityHashMap::new, nullKeys, nullValues)},
669             new Object[]{"LinkedHashMap", makeMap(LinkedHashMap::new, nullKeys, nullValues)},
670             new Object[]{"WeakHashMap", makeMap(WeakHashMap::new, nullKeys, nullValues)},
671             new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(makeMap(HashMap::new, nullKeys, nullValues), IntegerEnum.class, String.class)},
672             new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(makeMap(HashMap::new, nullKeys, nullValues))},
673             new Object[]{"ExtendsAbstractMap", makeMap(ExtendsAbstractMap::new, nullKeys, nullValues)});
674     }
675 
676     /**
677      *
678      * @param nulls include null values
679      * @return
680      */
makeRWNoNullKeysMaps(boolean nulls)681     private static Collection<Object[]> makeRWNoNullKeysMaps(boolean nulls) {
682         return Arrays.asList(
683                 // null key hostile
684                 new Object[]{"EnumMap", makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls)},
685                 new Object[]{"TreeMap", makeMap(TreeMap::new, false, nulls)},
686                 new Object[]{"ExtendsAbstractMap(TreeMap)", makeMap(() -> {return new ExtendsAbstractMap(new TreeMap());}, false, nulls)},
687                 new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls))}
688                 );
689     }
690 
691     private static Collection<Object[]> makeRWNoNullsMaps() {
692         return Arrays.asList(
693             // null key and value hostile
694             new Object[]{"Hashtable", makeMap(Hashtable::new, false, false)},
695             new Object[]{"ConcurrentHashMap", makeMap(ConcurrentHashMap::new, false, false)},
696             new Object[]{"ConcurrentSkipListMap", makeMap(ConcurrentSkipListMap::new, false, false)},
697             new Object[]{"Collections.synchronizedMap(ConcurrentHashMap)", Collections.synchronizedMap(makeMap(ConcurrentHashMap::new, false, false))},
698             new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(makeMap(ConcurrentHashMap::new, false, false), IntegerEnum.class, String.class)},
699             new Object[]{"ExtendsAbstractMap(ConcurrentHashMap)", makeMap(() -> {return new ExtendsAbstractMap(new ConcurrentHashMap());}, false, false)},
700             new Object[]{"ImplementsConcurrentMap", makeMap(ImplementsConcurrentMap::new, false, false)}
701             );
702     }
703 
704     /**
705      *
706      * @param nulls include nulls
707      * @return
708      */
709     private static Collection<Object[]> makeROMaps(boolean nulls) {
710         return Arrays.asList(new Object[][]{
711             new Object[]{"Collections.unmodifiableMap(HashMap)", Collections.unmodifiableMap(makeMap(HashMap::new, nulls, nulls))}
712         });
713     }
714 
715      /**
716      *
717      * @param supplier a supplier of mutable map instances.
718      *
719      * @param nullKeys   include null keys
720      * @param nullValues include null values
721      * @return
722      */
723     private static Map<IntegerEnum, String> makeMap(Supplier<Map<IntegerEnum, String>> supplier, boolean nullKeys, boolean nullValues) {
724         Map<IntegerEnum, String> result = supplier.get();
725 
726         for (int each = 0; each < TEST_SIZE; each++) {
727             IntegerEnum key = nullKeys ? (each == 0) ? null : KEYS[each] : KEYS[each];
728             String value = nullValues ? (each == 0) ? null : VALUES[each] : VALUES[each];
729 
730             result.put(key, value);
731         }
732 
733         return result;
734     }
735 
736     static class Merging {
737         public enum Value {
738             ABSENT,
739             NULL,
740             OLDVALUE,
741             NEWVALUE,
742             RESULT
743         }
744 
745         public enum Merger implements BiFunction<String,String,String> {
746             UNUSED {
747                 public String apply(String oldValue, String newValue) {
748                     fail("should not be called");
749                     return null;
750                 }
751             },
752             NULL {
753                 public String apply(String oldValue, String newValue) {
754                     return null;
755                 }
756             },
757             RESULT {
758                 public String apply(String oldValue, String newValue) {
759                     return VALUES[3];
760                 }
761             },
762         }
763     }
764 
765     @DataProvider(name = "MergeCases", parallel = true)
766     public Iterator<Object[]> mergeCasesProvider() {
767         Collection<Object[]> cases = new ArrayList<>();
768 
769         cases.addAll(makeMergeTestCases());
770 
771         return cases.iterator();
772     }
773 
774     static Collection<Object[]> makeMergeTestCases() {
775         Collection<Object[]> cases = new ArrayList<>();
776 
777         for( Object[] mapParams : makeAllRWMaps() ) {
778             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
779         }
780 
781         for( Object[] mapParams : makeAllRWMaps() ) {
782             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
783         }
784 
785         for( Object[] mapParams : makeAllRWMaps() ) {
786             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
787         }
788 
789         return cases;
790     }
791 
792     public interface Thrower<T extends Throwable> {
793 
794         public void run() throws T;
795     }
796 
797     public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable) {
798         assertThrows(thrower, throwable, null);
799     }
800 
801     public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable, String message) {
802         Throwable thrown;
803         try {
804             thrower.run();
805             thrown = null;
806         } catch (Throwable caught) {
807             thrown = caught;
808         }
809 
810         assertInstance(thrown, throwable,
811             ((null != message) ? message : "") +
812             " Failed to throw " + throwable.getCanonicalName());
813     }
814 
815     public static <T extends Throwable> void assertThrows(Class<T> throwable, String message, Thrower<T>... throwers) {
816         for(Thrower<T> thrower : throwers) {
817             assertThrows(thrower, throwable, message);
818         }
819     }
820 
821     public static void assertInstance(Object actual, Class<?> expected) {
822         assertInstance(expected.isInstance(actual), null);
823     }
824 
825     public static void assertInstance(Object actual, Class<?> expected, String message) {
826         assertTrue(expected.isInstance(actual), message);
827     }
828 
829     /**
830      * A simple mutable map implementation that provides only default
831      * implementations of all methods. ie. none of the Map interface default
832      * methods have overridden implementations.
833      *
834      * @param <K> Type of keys
835      * @param <V> Type of values
836      */
837     public static class ExtendsAbstractMap<M extends Map<K,V>, K, V> extends AbstractMap<K, V> {
838 
839         protected final M map;
840 
841         public ExtendsAbstractMap() { this( (M) new HashMap<K,V>()); }
842 
843         protected ExtendsAbstractMap(M map) { this.map = map; }
844 
845         public Set<Map.Entry<K, V>> entrySet() {
846             return new AbstractSet<Map.Entry<K, V>>() {
847                 public int size() {
848                     return map.size();
849                 }
850 
851                 public Iterator<Map.Entry<K,V>> iterator() {
852                     final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator();
853                     return new Iterator<Map.Entry<K,V>>() {
854                        public boolean hasNext() { return source.hasNext(); }
855                        public Map.Entry<K,V> next() { return source.next(); }
856                        public void remove() { source.remove(); }
857                     };
858                 }
859 
860                 public boolean add(Map.Entry<K,V> e) {
861                     return map.entrySet().add(e);
862                 }
863             };
864         }
865 
866         public V put(K key, V value) {
867             return map.put(key, value);
868         }
869     }
870 
871     /**
872      * A simple mutable concurrent map implementation that provides only default
873      * implementations of all methods. ie. none of the ConcurrentMap interface
874      * default methods have overridden implementations.
875      *
876      * @param <K> Type of keys
877      * @param <V> Type of values
878      */
879     public static class ImplementsConcurrentMap<K, V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> {
880         public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); }
881 
882         // ConcurrentMap reabstracts these methods
883 
884         public V replace(K k, V v) { return map.replace(k, v); };
885 
886         public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); };
887 
888         public boolean remove(Object k, Object v) { return map.remove(k, v); }
889 
890         public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); }
891     }
892 }
893