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 }