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