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")
  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(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
 292     public void testComputeIfAbsentNullFunction(String description, Map<IntegerEnum, String> map) {
 293         assertThrows( () -> { map.computeIfAbsent(KEYS[1], null);},
 294                 NullPointerException.class,
 295                 "Should throw NPE");
 296     }
 297 
 298     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
 299     public void testComputeIfPresentNulls(String description, Map<IntegerEnum, String> map) {
 300         assertTrue(map.containsKey(null), description + ": null key absent");
 301         assertNull(map.get(null), description + ": value not null");
 302         assertSame(map.computeIfPresent(null, (k, v) -> {
 303             fail(description + ": null value is not deemed present");
 304             return EXTRA_VALUE;
 305         }), null, description);
 306         assertTrue(map.containsKey(null));
 307         assertNull(map.get(null), description);
 308         assertNull(map.remove(EXTRA_KEY), description + ": unexpected mapping");
 309         assertNull(map.put(EXTRA_KEY, null), description + ": unexpected value");
 310         assertSame(map.computeIfPresent(EXTRA_KEY, (k, v) -> {
 311             fail(description + ": null value is not deemed present");
 312             return EXTRA_VALUE;
 313         }), null, description);
 314         assertNull(map.get(EXTRA_KEY), description + ": null mapping gone");
 315     }
 316 
 317     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
 318     public void testComputeIfPresent(String description, Map<IntegerEnum, String> map) {
 319         assertTrue(map.containsKey(KEYS[1]));
 320         Object value = map.get(KEYS[1]);
 321         assertTrue(null == value || value == VALUES[1], description + String.valueOf(value));
 322         Object expected = (null == value) ? null : EXTRA_VALUE;
 323         assertSame(map.computeIfPresent(KEYS[1], (k, v) -> {
 324             assertSame(v, value);
 325             return EXTRA_VALUE;
 326         }), expected, description);
 327         assertSame(map.get(KEYS[1]), expected, description);
 328 
 329         assertFalse(map.containsKey(EXTRA_KEY));
 330         assertSame(map.computeIfPresent(EXTRA_KEY, (k, v) -> {
 331             fail();
 332             return EXTRA_VALUE;
 333         }), null);
 334         assertFalse(map.containsKey(EXTRA_KEY));
 335         assertSame(map.get(EXTRA_KEY), null);
 336     }
 337 
 338     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
 339     public void testComputeIfPresentNullFunction(String description, Map<IntegerEnum, String> map) {
 340         assertThrows( () -> { map.computeIfPresent(KEYS[1], null);},
 341                 NullPointerException.class,
 342                 "Should throw NPE");
 343     }
 344 
 345      @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
 346     public void testComputeNulls(String description, Map<IntegerEnum, String> map) {
 347         assertTrue(map.containsKey(null), "null key absent");
 348         assertNull(map.get(null), "value not null");
 349         assertSame(map.compute(null, (k, v) -> {
 350             assertNull(k);
 351             assertNull(v);
 352             return null;
 353         }), null, description);
 354         assertFalse(map.containsKey(null), description + ": null key present.");
 355         assertSame(map.compute(null, (k, v) -> {
 356             assertSame(k, null);
 357             assertNull(v);
 358             return EXTRA_VALUE;
 359         }), EXTRA_VALUE, description);
 360         assertTrue(map.containsKey(null));
 361         assertSame(map.get(null), EXTRA_VALUE, description);
 362         assertSame(map.remove(null), EXTRA_VALUE, description + ": removed value not expected");
 363         // no mapping before and after
 364         assertFalse(map.containsKey(null), description + ": null key present");
 365         assertSame(map.compute(null, (k, v) -> {
 366             assertNull(k);
 367             assertNull(v);
 368             return null;
 369         }), null, description + ": expected null result" );
 370         assertFalse(map.containsKey(null), description + ": null key present");
 371         // compute with map not containing value
 372         assertNull(map.remove(EXTRA_KEY),  description + ": unexpected mapping");
 373         assertFalse(map.containsKey(EXTRA_KEY),  description + ": key present");
 374         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
 375             assertSame(k, EXTRA_KEY);
 376             assertNull(v);
 377             return null;
 378         }), null, description);
 379         assertFalse(map.containsKey(EXTRA_KEY),  description + ": null key present");
 380         // ensure removal.
 381         assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
 382         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
 383             assertSame(k, EXTRA_KEY);
 384             assertSame(v, EXTRA_VALUE);
 385             return null;
 386         }), null, description + ": null resulted expected");
 387         assertFalse(map.containsKey(EXTRA_KEY),  description + ": null key present");
 388        // compute with map containing null value
 389         assertNull(map.put(EXTRA_KEY, null),  description + ": unexpected value");
 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         assertNull(map.put(EXTRA_KEY, null),  description + ": unexpected value");
 397         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
 398             assertSame(k, EXTRA_KEY);
 399             assertNull(v);
 400             return EXTRA_VALUE;
 401         }), EXTRA_VALUE, description);
 402         assertTrue(map.containsKey(EXTRA_KEY), "null key present");
 403     }
 404 
 405     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
 406     public void testCompute(String description, Map<IntegerEnum, String> map) {
 407         assertTrue(map.containsKey(KEYS[1]));
 408         Object value = map.get(KEYS[1]);
 409         assertTrue(null == value || value == VALUES[1], description + String.valueOf(value));
 410         assertSame(map.compute(KEYS[1], (k, v) -> {
 411             assertSame(k, KEYS[1]);
 412             assertSame(v, value);
 413             return EXTRA_VALUE;
 414         }), EXTRA_VALUE, description);
 415         assertSame(map.get(KEYS[1]), EXTRA_VALUE, description);
 416         assertNull(map.compute(KEYS[1], (k, v) -> {
 417             assertSame(v, EXTRA_VALUE);
 418             return null;
 419         }), description);
 420         assertFalse(map.containsKey(KEYS[1]));
 421 
 422         assertFalse(map.containsKey(EXTRA_KEY));
 423         assertSame(map.compute(EXTRA_KEY, (k, v) -> {
 424             assertNull(v);
 425             return EXTRA_VALUE;
 426         }), EXTRA_VALUE);
 427         assertTrue(map.containsKey(EXTRA_KEY));
 428         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
 429     }
 430 
 431     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
 432     public void testComputeNullFunction(String description, Map<IntegerEnum, String> map) {
 433         assertThrows( () -> { map.compute(KEYS[1], null);},
 434                 NullPointerException.class,
 435                 "Should throw NPE");
 436     }
 437 
 438     @Test(dataProvider = "MergeCases")
 439     private void testMerge(String description, Map<IntegerEnum, String> map, Merging.Value oldValue, Merging.Value newValue, Merging.Merger merger, Merging.Value put, Merging.Value result) {
 440             // add and check initial conditions.
 441             switch(oldValue) {
 442                 case ABSENT :
 443                     map.remove(EXTRA_KEY);
 444                     assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
 445                     break;
 446                 case NULL :
 447                     map.put(EXTRA_KEY, null);
 448                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
 449                     assertNull(map.get(EXTRA_KEY), "wrong value");
 450                     break;
 451                 case OLDVALUE :
 452                     map.put(EXTRA_KEY, VALUES[1]);
 453                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
 454                     assertSame(map.get(EXTRA_KEY), VALUES[1], "wrong value");
 455                     break;
 456                 default:
 457                     fail("unexpected old value");
 458             }
 459 
 460             String returned = map.merge(EXTRA_KEY,
 461                 newValue == Merging.Value.NULL ? (String) null : VALUES[2],
 462                 merger
 463                 );
 464 
 465             // check result
 466 
 467             switch(result) {
 468                 case NULL :
 469                     assertNull(returned, "wrong value");
 470                     break;
 471                 case NEWVALUE :
 472                     assertSame(returned, VALUES[2], "wrong value");
 473                     break;
 474                 case RESULT :
 475                     assertSame(returned, VALUES[3], "wrong value");
 476                     break;
 477                 default:
 478                     fail("unexpected new value");
 479             }
 480 
 481             // check map
 482             switch(put) {
 483                 case ABSENT :
 484                     assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
 485                     break;
 486                 case NULL :
 487                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
 488                     assertNull(map.get(EXTRA_KEY), "wrong value");
 489                     break;
 490                 case NEWVALUE :
 491                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
 492                     assertSame(map.get(EXTRA_KEY), VALUES[2], "wrong value");
 493                     break;
 494                 case RESULT :
 495                     assertTrue(map.containsKey(EXTRA_KEY), "key absent");
 496                     assertSame(map.get(EXTRA_KEY), VALUES[3], "wrong value");
 497                     break;
 498                 default:
 499                     fail("unexpected new value");
 500             }
 501     }
 502 
 503     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
 504     public void testMergeNullMerger(String description, Map<IntegerEnum, String> map) {
 505         assertThrows( () -> { map.merge(KEYS[1], VALUES[1], null);},
 506                 NullPointerException.class,
 507                 "Should throw NPE");
 508     }
 509 
 510     public enum IntegerEnum {
 511 
 512         e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
 513         e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
 514         e20, e21, e22, e23, e24, e25, e26, e27, e28, e29,
 515         e30, e31, e32, e33, e34, e35, e36, e37, e38, e39,
 516         e40, e41, e42, e43, e44, e45, e46, e47, e48, e49,
 517         e50, e51, e52, e53, e54, e55, e56, e57, e58, e59,
 518         e60, e61, e62, e63, e64, e65, e66, e67, e68, e69,
 519         e70, e71, e72, e73, e74, e75, e76, e77, e78, e79,
 520         e80, e81, e82, e83, e84, e85, e86, e87, e88, e89,
 521         e90, e91, e92, e93, e94, e95, e96, e97, e98, e99,
 522         EXTRA_KEY;
 523         public static final int SIZE = values().length;
 524     };
 525     private static final int TEST_SIZE = IntegerEnum.SIZE - 1;
 526     /**
 527      * Realized keys ensure that there is always a hard ref to all test objects.
 528      */
 529     private static final IntegerEnum[] KEYS = new IntegerEnum[TEST_SIZE];
 530     /**
 531      * Realized values ensure that there is always a hard ref to all test
 532      * objects.
 533      */
 534     private static final String[] VALUES = new String[TEST_SIZE];
 535 
 536     static {
 537         IntegerEnum[] keys = IntegerEnum.values();
 538         for (int each = 0; each < TEST_SIZE; each++) {
 539             KEYS[each] = keys[each];
 540             VALUES[each] = String.valueOf(each);
 541         }
 542     }
 543 
 544     private static final IntegerEnum FIRST_KEY = KEYS[0];
 545     private static final String FIRST_VALUE = VALUES[0];
 546     private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY;
 547     private static final String EXTRA_VALUE = String.valueOf(TEST_SIZE);
 548 
 549     @DataProvider(name = "Map<IntegerEnum,String> rw=all keys=all values=all", parallel = true)
 550     public static Iterator<Object[]> allMapProvider() {
 551         return makeAllMaps().iterator();
 552     }
 553 
 554     @DataProvider(name = "Map<IntegerEnum,String> rw=all keys=withNull values=withNull", parallel = true)
 555     public static Iterator<Object[]> allMapWithNullsProvider() {
 556         return makeAllMapsWithNulls().iterator();
 557     }
 558 
 559     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=nonNull values=nonNull", parallel = true)
 560     public static Iterator<Object[]> rwNonNullMapProvider() {
 561         return makeRWNoNullsMaps().iterator();
 562     }
 563 
 564     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=nonNull values=all", parallel = true)
 565     public static Iterator<Object[]> rwNonNullKeysMapProvider() {
 566         return makeRWMapsNoNulls().iterator();
 567     }
 568 
 569     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=all values=all", parallel = true)
 570     public static Iterator<Object[]> rwMapProvider() {
 571         return makeAllRWMaps().iterator();
 572     }
 573 
 574     @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull", parallel = true)
 575     public static Iterator<Object[]> rwNullsMapProvider() {
 576         return makeAllRWMapsWithNulls().iterator();
 577     }
 578 
 579     private static Collection<Object[]> makeAllRWMapsWithNulls() {
 580         Collection<Object[]> all = new ArrayList<>();
 581 
 582         all.addAll(makeRWMaps(true, true));
 583 
 584         return all;
 585     }
 586 
 587 
 588     private static Collection<Object[]> makeRWMapsNoNulls() {
 589         Collection<Object[]> all = new ArrayList<>();
 590 
 591         all.addAll(makeRWNoNullKeysMaps(false));
 592         all.addAll(makeRWNoNullsMaps());
 593 
 594         return all;
 595     }
 596 
 597     private static Collection<Object[]> makeAllROMaps() {
 598         Collection<Object[]> all = new ArrayList<>();
 599 
 600         all.addAll(makeROMaps(false));
 601         all.addAll(makeROMaps(true));
 602 
 603         return all;
 604     }
 605 
 606     private static Collection<Object[]> makeAllRWMaps() {
 607         Collection<Object[]> all = new ArrayList<>();
 608 
 609         all.addAll(makeRWNoNullsMaps());
 610         all.addAll(makeRWMaps(false,true));
 611         all.addAll(makeRWMaps(true,true));
 612         all.addAll(makeRWNoNullKeysMaps(true));
 613         return all;
 614     }
 615 
 616     private static Collection<Object[]> makeAllMaps() {
 617         Collection<Object[]> all = new ArrayList<>();
 618 
 619         all.addAll(makeAllROMaps());
 620         all.addAll(makeAllRWMaps());
 621 
 622         return all;
 623     }
 624 
 625     private static Collection<Object[]> makeAllMapsWithNulls() {
 626         Collection<Object[]> all = new ArrayList<>();
 627 
 628         all.addAll(makeROMaps(true));
 629         all.addAll(makeRWMaps(true,true));
 630 
 631         return all;
 632     }
 633     /**
 634      *
 635      * @param nullKeys include null keys
 636      * @param nullValues include null values
 637      * @return
 638      */
 639     private static Collection<Object[]> makeRWMaps(boolean nullKeys, boolean nullValues) {
 640         return Arrays.asList(
 641             new Object[]{"HashMap", makeMap(HashMap::new, nullKeys, nullValues)},
 642             new Object[]{"IdentityHashMap", makeMap(IdentityHashMap::new, nullKeys, nullValues)},
 643             new Object[]{"LinkedHashMap", makeMap(LinkedHashMap::new, nullKeys, nullValues)},
 644             new Object[]{"WeakHashMap", makeMap(WeakHashMap::new, nullKeys, nullValues)},
 645             new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(makeMap(HashMap::new, nullKeys, nullValues), IntegerEnum.class, String.class)},
 646             new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(makeMap(HashMap::new, nullKeys, nullValues))},
 647             new Object[]{"ExtendsAbstractMap", makeMap(ExtendsAbstractMap::new, nullKeys, nullValues)});
 648     }
 649 
 650     /**
 651      *
 652      * @param nulls include null values
 653      * @return
 654      */
 655     private static Collection<Object[]> makeRWNoNullKeysMaps(boolean nulls) {
 656         return Arrays.asList(
 657                 // null key hostile
 658                 new Object[]{"EnumMap", makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls)},
 659                 new Object[]{"TreeMap", makeMap(TreeMap::new, false, nulls)},
 660                 new Object[]{"ExtendsAbstractMap(TreeMap)", makeMap(() -> {return new ExtendsAbstractMap(new TreeMap());}, false, nulls)},
 661                 new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls))}
 662                 );
 663     }
 664 
 665     private static Collection<Object[]> makeRWNoNullsMaps() {
 666         return Arrays.asList(
 667             // null key and value hostile
 668             new Object[]{"Hashtable", makeMap(Hashtable::new, false, false)},
 669             new Object[]{"ConcurrentHashMap", makeMap(ConcurrentHashMap::new, false, false)},
 670             new Object[]{"ConcurrentSkipListMap", makeMap(ConcurrentSkipListMap::new, false, false)},
 671             new Object[]{"Collections.synchronizedMap(ConcurrentHashMap)", Collections.synchronizedMap(makeMap(ConcurrentHashMap::new, false, false))},
 672             new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(makeMap(ConcurrentHashMap::new, false, false), IntegerEnum.class, String.class)},
 673             new Object[]{"ExtendsAbstractMap(ConcurrentHashMap)", makeMap(() -> {return new ExtendsAbstractMap(new ConcurrentHashMap());}, false, false)},
 674             new Object[]{"ImplementsConcurrentMap", makeMap(ImplementsConcurrentMap::new, false, false)}
 675             );
 676     }
 677 
 678     /**
 679      *
 680      * @param nulls include nulls
 681      * @return
 682      */
 683     private static Collection<Object[]> makeROMaps(boolean nulls) {
 684         return Arrays.asList(new Object[][]{
 685             new Object[]{"Collections.unmodifiableMap(HashMap)", Collections.unmodifiableMap(makeMap(HashMap::new, nulls, nulls))}
 686         });
 687     }
 688 
 689      /**
 690      *
 691      * @param supplier a supplier of mutable map instances.
 692      *
 693      * @param nullKeys   include null keys
 694      * @param nullValues include null values
 695      * @return
 696      */
 697     private static Map<IntegerEnum, String> makeMap(Supplier<Map<IntegerEnum, String>> supplier, boolean nullKeys, boolean nullValues) {
 698         Map<IntegerEnum, String> result = supplier.get();
 699 
 700         for (int each = 0; each < TEST_SIZE; each++) {
 701             IntegerEnum key = nullKeys ? (each == 0) ? null : KEYS[each] : KEYS[each];
 702             String value = nullValues ? (each == 0) ? null : VALUES[each] : VALUES[each];
 703 
 704             result.put(key, value);
 705         }
 706 
 707         return result;
 708     }
 709 
 710     static class Merging {
 711         public enum Value {
 712             ABSENT,
 713             NULL,
 714             OLDVALUE,
 715             NEWVALUE,
 716             RESULT
 717         }
 718 
 719         public enum Merger implements BiFunction<String,String,String> {
 720             UNUSED {
 721                 public String apply(String oldValue, String newValue) {
 722                     fail("should not be called");
 723                     return null;
 724                 }
 725             },
 726             NULL {
 727                 public String apply(String oldValue, String newValue) {
 728                     return null;
 729                 }
 730             },
 731             RESULT {
 732                 public String apply(String oldValue, String newValue) {
 733                     return VALUES[3];
 734                 }
 735             },
 736         }
 737     }
 738 
 739     @DataProvider(name = "MergeCases", parallel = true)
 740     public Iterator<Object[]> mergeCasesProvider() {
 741         Collection<Object[]> cases = new ArrayList<>();
 742 
 743         cases.addAll(makeMergeTestCases());
 744 
 745         return cases.iterator();
 746     }
 747 
 748     static Collection<Object[]> makeMergeTestCases() {
 749         Collection<Object[]> cases = new ArrayList<>();
 750 
 751         for( Object[] mapParams : makeAllRWMaps() ) {
 752             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
 753         }
 754 
 755         for( Object[] mapParams : makeAllRWMaps() ) {
 756             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
 757         }
 758 
 759         for( Object[] mapParams : makeAllRWMaps() ) {
 760             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
 761         }
 762 
 763         return cases;
 764     }
 765 
 766     public interface Thrower<T extends Throwable> {
 767 
 768         public void run() throws T;
 769     }
 770 
 771     public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable) {
 772         assertThrows(thrower, throwable, null);
 773     }
 774 
 775     public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable, String message) {
 776         Throwable thrown;
 777         try {
 778             thrower.run();
 779             thrown = null;
 780         } catch (Throwable caught) {
 781             thrown = caught;
 782         }
 783 
 784         assertInstance(thrown, throwable,
 785             ((null != message) ? message : "") +
 786             " Failed to throw " + throwable.getCanonicalName());
 787     }
 788 
 789     public static <T extends Throwable> void assertThrows(Class<T> throwable, String message, Thrower<T>... throwers) {
 790         for(Thrower<T> thrower : throwers) {
 791             assertThrows(thrower, throwable, message);
 792         }
 793     }
 794 
 795     public static void assertInstance(Object actual, Class<?> expected) {
 796         assertInstance(expected.isInstance(actual), null);
 797     }
 798 
 799     public static void assertInstance(Object actual, Class<?> expected, String message) {
 800         assertTrue(expected.isInstance(actual), message);
 801     }
 802 
 803     /**
 804      * A simple mutable map implementation that provides only default
 805      * implementations of all methods. ie. none of the Map interface default
 806      * methods have overridden implementations.
 807      *
 808      * @param <K> Type of keys
 809      * @param <V> Type of values
 810      */
 811     public static class ExtendsAbstractMap<M extends Map<K,V>, K, V> extends AbstractMap<K, V> {
 812 
 813         protected final M map;
 814 
 815         public ExtendsAbstractMap() { this( (M) new HashMap<K,V>()); }
 816 
 817         protected ExtendsAbstractMap(M map) { this.map = map; }
 818 
 819         public Set<Map.Entry<K, V>> entrySet() {
 820             return new AbstractSet<Map.Entry<K, V>>() {
 821                 public int size() {
 822                     return map.size();
 823                 }
 824 
 825                 public Iterator<Map.Entry<K,V>> iterator() {
 826                     final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator();
 827                     return new Iterator<Map.Entry<K,V>>() {
 828                        public boolean hasNext() { return source.hasNext(); }
 829                        public Map.Entry<K,V> next() { return source.next(); }
 830                        public void remove() { source.remove(); }
 831                     };
 832                 }
 833 
 834                 public boolean add(Map.Entry<K,V> e) {
 835                     return map.entrySet().add(e);
 836                 }
 837             };
 838         }
 839 
 840         public V put(K key, V value) {
 841             return map.put(key, value);
 842         }
 843     }
 844 
 845     /**
 846      * A simple mutable concurrent map implementation that provides only default
 847      * implementations of all methods. ie. none of the ConcurrentMap interface
 848      * default methods have overridden implementations.
 849      *
 850      * @param <K> Type of keys
 851      * @param <V> Type of values
 852      */
 853     public static class ImplementsConcurrentMap<K, V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> {
 854         public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); }
 855 
 856         // ConcurrentMap reabstracts these methods
 857 
 858         public V replace(K k, V v) { return map.replace(k, v); };
 859 
 860         public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); };
 861 
 862         public boolean remove(Object k, Object v) { return map.remove(k, v); }
 863 
 864         public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); }
 865     }
 866 }