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