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         cases.addAll(makeMergeNullValueTestCases());
 745 
 746         return cases.iterator();
 747     }
 748 
 749     static Collection<Object[]> makeMergeTestCases() {
 750         Collection<Object[]> cases = new ArrayList<>();
 751 
 752         for( Object[] mapParams : makeAllRWMaps() ) {
 753             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
 754         }
 755 
 756         for( Object[] mapParams : makeAllRWMaps() ) {
 757             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
 758         }
 759 
 760         for( Object[] mapParams : makeAllRWMaps() ) {
 761             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
 762         }
 763 
 764         return cases;
 765     }
 766 
 767     static Collection<Object[]> makeMergeNullValueTestCases() {
 768         Collection<Object[]> cases = new ArrayList<>();
 769 
 770         for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
 771             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NULL, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
 772         }
 773 
 774         for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
 775             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NULL, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
 776         }
 777 
 778         for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
 779             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NULL, Merging.Merger.UNUSED, Merging.Value.ABSENT, Merging.Value.NULL });
 780         }
 781 
 782         for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
 783             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.NULL, Merging.Value.NULL, Merging.Merger.UNUSED, Merging.Value.ABSENT, Merging.Value.NULL });
 784         }
 785 
 786         for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
 787             cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.NULL, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
 788         }
 789 
 790         return cases;
 791     }
 792 
 793     public interface Thrower<T extends Throwable> {
 794 
 795         public void run() throws T;
 796     }
 797 
 798     public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable) {
 799         assertThrows(thrower, throwable, null);
 800     }
 801 
 802     public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable, String message) {
 803         Throwable thrown;
 804         try {
 805             thrower.run();
 806             thrown = null;
 807         } catch (Throwable caught) {
 808             thrown = caught;
 809         }
 810 
 811         assertInstance(thrown, throwable,
 812             ((null != message) ? message : "") +
 813             " Failed to throw " + throwable.getCanonicalName());
 814     }
 815 
 816     public static <T extends Throwable> void assertThrows(Class<T> throwable, String message, Thrower<T>... throwers) {
 817         for(Thrower<T> thrower : throwers) {
 818             assertThrows(thrower, throwable, message);
 819         }
 820     }
 821 
 822     public static void assertInstance(Object actual, Class<?> expected) {
 823         assertInstance(expected.isInstance(actual), null);
 824     }
 825 
 826     public static void assertInstance(Object actual, Class<?> expected, String message) {
 827         assertTrue(expected.isInstance(actual), message);
 828     }
 829 
 830     /**
 831      * A simple mutable map implementation that provides only default
 832      * implementations of all methods. ie. none of the Map interface default
 833      * methods have overridden implementations.
 834      *
 835      * @param <K> Type of keys
 836      * @param <V> Type of values
 837      */
 838     public static class ExtendsAbstractMap<M extends Map<K,V>, K, V> extends AbstractMap<K, V> {
 839 
 840         protected final M map;
 841 
 842         public ExtendsAbstractMap() { this( (M) new HashMap<K,V>()); }
 843 
 844         protected ExtendsAbstractMap(M map) { this.map = map; }
 845 
 846         public Set<Map.Entry<K, V>> entrySet() {
 847             return new AbstractSet<Map.Entry<K, V>>() {
 848                 public int size() {
 849                     return map.size();
 850                 }
 851 
 852                 public Iterator<Map.Entry<K,V>> iterator() {
 853                     final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator();
 854                     return new Iterator<Map.Entry<K,V>>() {
 855                        public boolean hasNext() { return source.hasNext(); }
 856                        public Map.Entry<K,V> next() { return source.next(); }
 857                        public void remove() { source.remove(); }
 858                     };
 859                 }
 860 
 861                 public boolean add(Map.Entry<K,V> e) {
 862                     return map.entrySet().add(e);
 863                 }
 864             };
 865         }
 866 
 867         public V put(K key, V value) {
 868             return map.put(key, value);
 869         }
 870     }
 871 
 872     /**
 873      * A simple mutable concurrent map implementation that provides only default
 874      * implementations of all methods. ie. none of the ConcurrentMap interface
 875      * default methods have overridden implementations.
 876      *
 877      * @param <K> Type of keys
 878      * @param <V> Type of values
 879      */
 880     public static class ImplementsConcurrentMap<K, V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> {
 881         public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); }
 882 
 883         // ConcurrentMap reabstracts these methods
 884 
 885         public V replace(K k, V v) { return map.replace(k, v); };
 886 
 887         public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); };
 888 
 889         public boolean remove(Object k, Object v) { return map.remove(k, v); }
 890 
 891         public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); }
 892     }
 893 }