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