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