8016446: Improve forEach/replaceAll for Map, HashMap, Hashtable, IdentityHashMap, WeakHashMap, TreeMap, ConcurrentMap

Co-authored-by: Remi Forax <forax@univ-mlv.fr>
Reviewed-by: forax, mduigou, psandoz
This commit is contained in:
Mike Duigou 2013-06-18 16:03:10 -07:00
parent 6c9370029a
commit 4eeb7e8bc5
9 changed files with 474 additions and 83 deletions

View File

@ -29,6 +29,7 @@ import java.io.*;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
@ -1299,10 +1300,112 @@ public class HashMap<K,V>
*/ */
public V remove(Object key) { public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key); Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value); return (e == null ? null : e.value);
}
// optimized implementations of default methods in Map
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
if (nullKeyEntry != null) {
forEachNullKey(expectedModCount, action);
}
Object[] tab = this.table;
for (int index = 0; index < tab.length; index++) {
Object item = tab[index];
if (item == null) {
continue;
}
if (item instanceof HashMap.TreeBin) {
eachTreeNode(expectedModCount, ((TreeBin)item).first, action);
continue;
}
@SuppressWarnings("unchecked")
Entry<K, V> entry = (Entry<K, V>)item;
while (entry != null) {
action.accept(entry.key, entry.value);
entry = (Entry<K, V>)entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
} }
// optimized implementations of default methods in Map private void eachTreeNode(int expectedModCount, TreeNode<K, V> node, BiConsumer<? super K, ? super V> action) {
while (node != null) {
@SuppressWarnings("unchecked")
Entry<K, V> entry = (Entry<K, V>)node.entry;
action.accept(entry.key, entry.value);
node = (TreeNode<K, V>)entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
private void forEachNullKey(int expectedModCount, BiConsumer<? super K, ? super V> action) {
action.accept(null, nullKeyEntry.value);
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
final int expectedModCount = modCount;
if (nullKeyEntry != null) {
replaceforNullKey(expectedModCount, function);
}
Object[] tab = this.table;
for (int index = 0; index < tab.length; index++) {
Object item = tab[index];
if (item == null) {
continue;
}
if (item instanceof HashMap.TreeBin) {
replaceEachTreeNode(expectedModCount, ((TreeBin)item).first, function);
continue;
}
@SuppressWarnings("unchecked")
Entry<K, V> entry = (Entry<K, V>)item;
while (entry != null) {
entry.value = function.apply(entry.key, entry.value);
entry = (Entry<K, V>)entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
}
private void replaceEachTreeNode(int expectedModCount, TreeNode<K, V> node, BiFunction<? super K, ? super V, ? extends V> function) {
while (node != null) {
@SuppressWarnings("unchecked")
Entry<K, V> entry = (Entry<K, V>)node.entry;
entry.value = function.apply(entry.key, entry.value);
node = (TreeNode<K, V>)entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
private void replaceforNullKey(int expectedModCount, BiFunction<? super K, ? super V, ? extends V> function) {
nullKeyEntry.value = function.apply(null, nullKeyEntry.value);
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
@Override @Override
public V putIfAbsent(K key, V value) { public V putIfAbsent(K key, V value) {
@ -2297,12 +2400,12 @@ public class HashMap<K,V>
if (e == null) if (e == null)
throw new NoSuchElementException(); throw new NoSuchElementException();
if (e instanceof Entry) { if (e instanceof TreeNode) { // TreeBin
retVal = (Entry<K,V>)e;
next = ((Entry<K,V>)e).next;
} else { // TreeBin
retVal = (Entry<K,V>)((TreeNode)e).entry; retVal = (Entry<K,V>)((TreeNode)e).entry;
next = retVal.next; next = retVal.next;
} else {
retVal = (Entry<K,V>)e;
next = ((Entry<K,V>)e).next;
} }
if (next == null) { // Move to next bin if (next == null) { // Move to next bin

View File

@ -932,19 +932,39 @@ public class Hashtable<K,V>
public synchronized void forEach(BiConsumer<? super K, ? super V> action) { public synchronized void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action); // explicit check required in case Objects.requireNonNull(action); // explicit check required in case
// table is empty. // table is empty.
Entry<?,?>[] tab = table; final int expectedModCount = modCount;
for (Entry<?,?> entry : tab) {
Entry<?, ?>[] tab = table;
for (Entry<?, ?> entry : tab) {
while (entry != null) { while (entry != null) {
action.accept((K)entry.key, (V)entry.value); action.accept((K)entry.key, (V)entry.value);
entry = entry.next; entry = entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
} }
} }
} }
@Override @Override
public synchronized void replaceAll( public synchronized void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
BiFunction<? super K, ? super V, ? extends V> function) { Objects.requireNonNull(function); // explicit check required in case
Map.super.replaceAll(function); // table is empty.
final int expectedModCount = modCount;
Entry<K, V>[] tab = (Entry<K, V>[])table;
for (Entry<K, V> entry : tab) {
while (entry != null) {
entry.value = Objects.requireNonNull(
function.apply(entry.key, entry.value));
entry = entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
} }
@Override @Override
@ -1058,7 +1078,7 @@ public class Hashtable<K,V>
} }
@Override @Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { public synchronized V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction); Objects.requireNonNull(remappingFunction);
Entry<?,?> tab[] = table; Entry<?,?> tab[] = table;
@ -1087,7 +1107,7 @@ public class Hashtable<K,V>
} }
@Override @Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { public synchronized V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction); Objects.requireNonNull(remappingFunction);
Entry<?,?> tab[] = table; Entry<?,?> tab[] = table;
@ -1122,7 +1142,7 @@ public class Hashtable<K,V>
} }
@Override @Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { public synchronized V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction); Objects.requireNonNull(remappingFunction);
Entry<?,?> tab[] = table; Entry<?,?> tab[] = table;

View File

@ -27,6 +27,8 @@ package java.util;
import java.io.*; import java.io.*;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@ -1337,6 +1339,42 @@ public class IdentityHashMap<K,V>
tab[i + 1] = value; tab[i + 1] = value;
} }
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
int expectedModCount = modCount;
Object[] t = table;
for (int index = 0; index < t.length; index += 2) {
Object k = t[index];
if (k != null) {
action.accept((K) unmaskNull(k), (V) t[index + 1]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
int expectedModCount = modCount;
Object[] t = table;
for (int index = 0; index < t.length; index += 2) {
Object k = t[index];
if (k != null) {
t[index + 1] = function.apply((K) unmaskNull(k), (V) t[index + 1]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
}
/** /**
* Similar form as array-based Spliterators, but skips blank elements, * Similar form as array-based Spliterators, but skips blank elements,
* and guestimates size as decreasing by half per split. * and guestimates size as decreasing by half per split.

View File

@ -25,6 +25,8 @@
package java.util; package java.util;
import java.io.*; import java.io.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
/** /**
* <p>Hash table and linked list implementation of the <tt>Map</tt> interface, * <p>Hash table and linked list implementation of the <tt>Map</tt> interface,
@ -296,6 +298,32 @@ public class LinkedHashMap<K,V>
header.before = header.after = header; header.before = header.after = header;
} }
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
int expectedModCount = modCount;
for (Entry<K, V> entry = header.after; entry != header; entry = entry.after) {
action.accept(entry.key, entry.value);
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
int expectedModCount = modCount;
for (Entry<K, V> entry = header.after; entry != header; entry = entry.after) {
entry.value = function.apply(entry.key, entry.value);
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
/** /**
* LinkedHashMap entry. * LinkedHashMap entry.
*/ */

View File

@ -545,6 +545,7 @@ public interface Map<K,V> {
k = entry.getKey(); k = entry.getKey();
v = entry.getValue(); v = entry.getValue();
} catch(IllegalStateException ise) { } catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise); throw new ConcurrentModificationException(ise);
} }
action.accept(k, v); action.accept(k, v);
@ -599,9 +600,19 @@ public interface Map<K,V> {
k = entry.getKey(); k = entry.getKey();
v = entry.getValue(); v = entry.getValue();
} catch(IllegalStateException ise) { } catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise); throw new ConcurrentModificationException(ise);
} }
entry.setValue(function.apply(k, v));
} }
} }

View File

@ -25,6 +25,8 @@
package java.util; package java.util;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@ -945,6 +947,33 @@ public class TreeMap<K,V>
return tailMap(fromKey, true); return tailMap(fromKey, true);
} }
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
int expectedModCount = modCount;
for (Entry<K, V> e = getFirstEntry(); e != null; e = successor(e)) {
action.accept(e.key, e.value);
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
int expectedModCount = modCount;
for (Entry<K, V> e = getFirstEntry(); e != null; e = successor(e)) {
e.value = Objects.requireNonNull(function.apply(e.key, e.value));
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
// View class support // View class support
class Values extends AbstractCollection<V> { class Values extends AbstractCollection<V> {

View File

@ -28,6 +28,8 @@ package java.util;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -1036,6 +1038,48 @@ public class WeakHashMap<K,V>
} }
} }
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
int expectedModCount = modCount;
Entry<K, V>[] tab = getTable();
for (Entry<K, V> entry : tab) {
while (entry != null) {
Object key = entry.get();
if (key != null) {
action.accept((K)WeakHashMap.unmaskNull(key), entry.value);
}
entry = entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
int expectedModCount = modCount;
Entry<K, V>[] tab = getTable();;
for (Entry<K, V> entry : tab) {
while (entry != null) {
Object key = entry.get();
if (key != null) {
entry.value = function.apply((K)WeakHashMap.unmaskNull(key), entry.value);
}
entry = entry.next;
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
}
/** /**
* Similar form as other hash Spliterators, but skips dead * Similar form as other hash Spliterators, but skips dead
* elements. * elements.

View File

@ -35,6 +35,8 @@
package java.util.concurrent; package java.util.concurrent;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
/** /**
* A {@link java.util.Map} providing additional atomic * A {@link java.util.Map} providing additional atomic
@ -183,4 +185,26 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
* or value prevents it from being stored in this map * or value prevents it from being stored in this map
*/ */
V replace(K key, V value); V replace(K key, V value);
/**
* {@inheritDoc}
*
* @implNote This implementation assumes that the ConcurrentMap cannot
* contain null values and get() returning null unambiguously means the key
* is absent. Implementations which support null values
* <strong>must</strong> override this default implementation.
*/
@Override
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
forEach((k,v) -> {
while(!replace(k, v, function.apply(k, v))) {
// v changed or k is gone
if( (v = get(k)) == null) {
// k is no longer in the map.
break;
}
}
});
}
} }

View File

@ -36,6 +36,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Iterator; import java.util.Iterator;
@ -47,6 +48,7 @@ import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.BiFunction;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -60,14 +62,14 @@ import static org.testng.Assert.assertSame;
public class Defaults { public class Defaults {
@Test(dataProvider = "Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=all keys=withNull values=withNull")
public void testGetOrDefaultNulls(String description, Map<IntegerEnum, String> map) { public void testGetOrDefaultNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), description + ": null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), description + ": value not null");
assertSame(map.get(null), map.getOrDefault(null, EXTRA_VALUE), "values should match"); assertSame(map.get(null), map.getOrDefault(null, EXTRA_VALUE), description + ": values should match");
} }
@Test(dataProvider = "Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=all keys=all values=all")
public void testGetOrDefault(String description, Map<IntegerEnum, String> map) { public void testGetOrDefault(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1]), "expected key missing"); assertTrue(map.containsKey(KEYS[1]), "expected key missing");
assertSame(map.get(KEYS[1]), map.getOrDefault(KEYS[1], EXTRA_VALUE), "values should match"); assertSame(map.get(KEYS[1]), map.getOrDefault(KEYS[1], EXTRA_VALUE), "values should match");
@ -76,7 +78,7 @@ public class Defaults {
assertNull(map.getOrDefault(EXTRA_KEY, null), "null not returned as default"); assertNull(map.getOrDefault(EXTRA_KEY, null), "null not returned as default");
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testPutIfAbsentNulls(String description, Map<IntegerEnum, String> map) { public void testPutIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), "value not null");
@ -96,7 +98,7 @@ public class Defaults {
assertSame(map.get(null), EXTRA_VALUE, "value not expected"); assertSame(map.get(null), EXTRA_VALUE, "value not expected");
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testPutIfAbsent(String description, Map<IntegerEnum, String> map) { public void testPutIfAbsent(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object expected = map.get(KEYS[1]); Object expected = map.get(KEYS[1]);
@ -109,7 +111,7 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), EXTRA_VALUE); assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
} }
@Test(dataProvider = "Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=all keys=all values=all")
public void testForEach(String description, Map<IntegerEnum, String> map) { public void testForEach(String description, Map<IntegerEnum, String> map) {
IntegerEnum[] EACH_KEY = new IntegerEnum[map.size()]; IntegerEnum[] EACH_KEY = new IntegerEnum[map.size()];
@ -120,10 +122,43 @@ public class Defaults {
assertSame(v, map.get(k)); assertSame(v, map.get(k));
}); });
assertEquals(KEYS, EACH_KEY); assertEquals(KEYS, EACH_KEY, description);
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public static void testReplaceAll(String description, Map<IntegerEnum, String> map) {
IntegerEnum[] EACH_KEY = new IntegerEnum[map.size()];
Set<String> EACH_REPLACE = new HashSet<>(map.size());
map.replaceAll((k,v) -> {
int idx = (null == k) ? 0 : k.ordinal(); // substitute for index.
assertNull(EACH_KEY[idx]);
EACH_KEY[idx] = (idx == 0) ? KEYS[0] : k; // substitute for comparison.
assertSame(v, map.get(k));
String replacement = v + " replaced";
EACH_REPLACE.add(replacement);
return replacement;
});
assertEquals(KEYS, EACH_KEY, description);
assertEquals(map.values().size(), EACH_REPLACE.size(), description + EACH_REPLACE);
assertTrue(EACH_REPLACE.containsAll(map.values()), description + " : " + EACH_REPLACE + " != " + map.values());
assertTrue(map.values().containsAll(EACH_REPLACE), description + " : " + EACH_REPLACE + " != " + map.values());
}
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=nonNull values=nonNull")
public static void testReplaceAllNoNullReplacement(String description, Map<IntegerEnum, String> map) {
assertThrows(
() -> { map.replaceAll(null); },
NullPointerException.class,
description);
assertThrows(
() -> { map.replaceAll((k,v) -> null); },
NullPointerException.class,
description);
}
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public static void testRemoveNulls(String description, Map<IntegerEnum, String> map) { public static void testRemoveNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), "value not null");
@ -136,7 +171,7 @@ public class Defaults {
assertFalse(map.remove(null, null)); assertFalse(map.remove(null, null));
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public static void testRemove(String description, Map<IntegerEnum, String> map) { public static void testRemove(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object expected = map.get(KEYS[1]); Object expected = map.get(KEYS[1]);
@ -151,7 +186,7 @@ public class Defaults {
assertFalse(map.remove(EXTRA_KEY, EXTRA_VALUE)); assertFalse(map.remove(EXTRA_KEY, EXTRA_VALUE));
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testReplaceKVNulls(String description, Map<IntegerEnum, String> map) { public void testReplaceKVNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), "value not null");
@ -159,7 +194,7 @@ public class Defaults {
assertSame(map.get(null), EXTRA_VALUE); assertSame(map.get(null), EXTRA_VALUE);
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testReplaceKV(String description, Map<IntegerEnum, String> map) { public void testReplaceKV(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object expected = map.get(KEYS[1]); Object expected = map.get(KEYS[1]);
@ -177,7 +212,7 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), expected); assertSame(map.get(EXTRA_KEY), expected);
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testReplaceKVVNulls(String description, Map<IntegerEnum, String> map) { public void testReplaceKVVNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), "value not null");
@ -189,7 +224,7 @@ public class Defaults {
assertSame(map.get(null), EXTRA_VALUE); assertSame(map.get(null), EXTRA_VALUE);
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testReplaceKVV(String description, Map<IntegerEnum, String> map) { public void testReplaceKVV(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object expected = map.get(KEYS[1]); Object expected = map.get(KEYS[1]);
@ -212,7 +247,7 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), EXTRA_VALUE); assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testComputeIfAbsentNulls(String description, Map<IntegerEnum, String> map) { public void testComputeIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), "value not null");
@ -220,7 +255,7 @@ public class Defaults {
assertSame(map.get(null), EXTRA_VALUE, description); assertSame(map.get(null), EXTRA_VALUE, description);
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testComputeIfAbsent(String description, Map<IntegerEnum, String> map) { public void testComputeIfAbsent(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object expected = map.get(KEYS[1]); Object expected = map.get(KEYS[1]);
@ -234,7 +269,7 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), EXTRA_VALUE); assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testComputeIfPresentNulls(String description, Map<IntegerEnum, String> map) { public void testComputeIfPresentNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null)); assertTrue(map.containsKey(null));
assertNull(map.get(null)); assertNull(map.get(null));
@ -246,7 +281,7 @@ public class Defaults {
assertSame(map.get(null), null, description); assertSame(map.get(null), null, description);
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testComputeIfPresent(String description, Map<IntegerEnum, String> map) { public void testComputeIfPresent(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object value = map.get(KEYS[1]); Object value = map.get(KEYS[1]);
@ -267,7 +302,7 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), null); assertSame(map.get(EXTRA_KEY), null);
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testComputeNulls(String description, Map<IntegerEnum, String> map) { public void testComputeNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), "value not null");
@ -287,7 +322,7 @@ public class Defaults {
}), null, description); }), null, description);
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testCompute(String description, Map<IntegerEnum, String> map) { public void testCompute(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object value = map.get(KEYS[1]); Object value = map.get(KEYS[1]);
@ -314,7 +349,7 @@ public class Defaults {
} }
@Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testMergeNulls(String description, Map<IntegerEnum, String> map) { public void testMergeNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent"); assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null"); assertNull(map.get(null), "value not null");
@ -327,7 +362,7 @@ public class Defaults {
assertSame(map.get(null), EXTRA_VALUE, description); assertSame(map.get(null), EXTRA_VALUE, description);
} }
@Test(dataProvider = "R/W Map<IntegerEnum,String>") @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testMerge(String description, Map<IntegerEnum, String> map) { public void testMerge(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1])); assertTrue(map.containsKey(KEYS[1]));
Object value = map.get(KEYS[1]); Object value = map.get(KEYS[1]);
@ -391,52 +426,58 @@ public class Defaults {
private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY; private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY;
private static final String EXTRA_VALUE = String.valueOf(TEST_SIZE); private static final String EXTRA_VALUE = String.valueOf(TEST_SIZE);
@DataProvider(name = "Map<IntegerEnum,String>", parallel = true) @DataProvider(name = "Map<IntegerEnum,String> rw=all keys=all values=all", parallel = true)
public static Iterator<Object[]> allNullsMapProvider() { public static Iterator<Object[]> allMapProvider() {
return makeAllMaps().iterator(); return makeAllMaps().iterator();
} }
@DataProvider(name = "Nulls Map<IntegerEnum,String>", parallel = true) @DataProvider(name = "Map<IntegerEnum,String> rw=all keys=withNull values=withNull", parallel = true)
public static Iterator<Object[]> allMapProvider() { public static Iterator<Object[]> allMapWithNullsProvider() {
return makeRWMaps(true).iterator(); return makeAllMapsWithNulls().iterator();
} }
@DataProvider(name = "R/W Map<IntegerEnum,String>", parallel = true) @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=nonNull values=nonNull", parallel = true)
public static Iterator<Object[]> rwMapProvider() { public static Iterator<Object[]> rwNonNullMapProvider() {
return makeRWNoNullsMaps().iterator();
}
@DataProvider(name = "Map<IntegerEnum,String> rw=true keys=nonNull values=all", parallel = true)
public static Iterator<Object[]> rwNonNullKeysMapProvider() {
return makeRWMapsNoNulls().iterator(); return makeRWMapsNoNulls().iterator();
} }
@DataProvider(name = "R/W Nulls Map<IntegerEnum,String>", parallel = true) @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=all values=all", parallel = true)
public static Iterator<Object[]> rwNullsMapProvider() { public static Iterator<Object[]> rwMapProvider() {
return makeRWMaps(true).iterator(); return makeAllRWMaps().iterator();
} }
private static Collection<Object[]> makeAllMapsNoNulls() { @DataProvider(name = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull", parallel = true)
public static Iterator<Object[]> rwNullsMapProvider() {
return makeAllRWMapsWithNulls().iterator();
}
private static Collection<Object[]> makeAllRWMapsWithNulls() {
Collection<Object[]> all = new ArrayList<>(); Collection<Object[]> all = new ArrayList<>();
all.addAll(makeRWMaps(false)); all.addAll(makeRWMaps(true, true));
all.addAll(makeRWNoNullsMaps());
all.addAll(makeROMaps(false));
return all; return all;
} }
private static Collection<Object[]> makeRWMapsNoNulls() { private static Collection<Object[]> makeRWMapsNoNulls() {
Collection<Object[]> all = new ArrayList<>(); Collection<Object[]> all = new ArrayList<>();
all.addAll(makeRWMaps(false)); all.addAll(makeRWNoNullKeysMaps(false));
all.addAll(makeRWNoNullsMaps()); all.addAll(makeRWNoNullsMaps());
return all; return all;
} }
private static Collection<Object[]> makeAllMaps() { private static Collection<Object[]> makeAllROMaps() {
Collection<Object[]> all = new ArrayList<>(); Collection<Object[]> all = new ArrayList<>();
all.addAll(makeROMaps(false)); all.addAll(makeROMaps(false));
all.addAll(makeRWMaps(false));
all.addAll(makeRWNoNullsMaps());
all.addAll(makeRWMaps(true));
all.addAll(makeROMaps(true)); all.addAll(makeROMaps(true));
return all; return all;
@ -445,52 +486,99 @@ public class Defaults {
private static Collection<Object[]> makeAllRWMaps() { private static Collection<Object[]> makeAllRWMaps() {
Collection<Object[]> all = new ArrayList<>(); Collection<Object[]> all = new ArrayList<>();
all.addAll(makeRWMaps(false));
all.addAll(makeRWNoNullsMaps()); all.addAll(makeRWNoNullsMaps());
all.addAll(makeRWMaps(true)); all.addAll(makeRWMaps(false,true));
all.addAll(makeRWMaps(true,true));
all.addAll(makeRWNoNullKeysMaps(true));
return all;
}
private static Collection<Object[]> makeAllMaps() {
Collection<Object[]> all = new ArrayList<>();
all.addAll(makeAllROMaps());
all.addAll(makeAllRWMaps());
return all; return all;
} }
private static Collection<Object[]> makeRWMaps(boolean nulls) { private static Collection<Object[]> makeAllMapsWithNulls() {
Collection<Object[]> all = new ArrayList<>();
all.addAll(makeROMaps(true));
all.addAll(makeRWMaps(true,true));
return all;
}
/**
*
* @param nullKeys include null keys
* @param nullValues include null values
* @return
*/
private static Collection<Object[]> makeRWMaps(boolean nullKeys, boolean nullValues) {
return Arrays.asList( return Arrays.asList(
new Object[]{"HashMap", makeMap(HashMap::new, nulls)}, new Object[]{"HashMap", makeMap(HashMap::new, nullKeys, nullValues)},
new Object[]{"IdentityHashMap", makeMap(IdentityHashMap::new, nulls)}, new Object[]{"IdentityHashMap", makeMap(IdentityHashMap::new, nullKeys, nullValues)},
new Object[]{"LinkedHashMap", makeMap(LinkedHashMap::new, nulls)}, new Object[]{"LinkedHashMap", makeMap(LinkedHashMap::new, nullKeys, nullValues)},
new Object[]{"WeakHashMap", makeMap(WeakHashMap::new, nulls)}, new Object[]{"WeakHashMap", makeMap(WeakHashMap::new, nullKeys, nullValues)},
new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(makeMap(HashMap::new, nulls), IntegerEnum.class, String.class)}, new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(makeMap(HashMap::new, nullKeys, nullValues), IntegerEnum.class, String.class)},
new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(makeMap(HashMap::new, nulls))}, new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(makeMap(HashMap::new, nullKeys, nullValues))},
new Object[]{"ExtendsAbstractMap", makeMap(ExtendsAbstractMap::new, nulls)}); new Object[]{"ExtendsAbstractMap", makeMap(ExtendsAbstractMap::new, nullKeys, nullValues)});
}
/**
*
* @param nulls include null values
* @return
*/
private static Collection<Object[]> makeRWNoNullKeysMaps(boolean nulls) {
return Arrays.asList(
// null key hostile
new Object[]{"EnumMap", makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls)},
new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(makeMap(() -> new EnumMap(IntegerEnum.class), false, nulls))}
);
} }
private static Collection<Object[]> makeRWNoNullsMaps() { private static Collection<Object[]> makeRWNoNullsMaps() {
return Arrays.asList( return Arrays.asList(
// null hostile // null key and value hostile
new Object[]{"EnumMap", makeMap(() -> new EnumMap(IntegerEnum.class), false)}, new Object[]{"Hashtable", makeMap(Hashtable::new, false, false)},
new Object[]{"Hashtable", makeMap(Hashtable::new, false)}, new Object[]{"TreeMap", makeMap(TreeMap::new, false, false)},
new Object[]{"TreeMap", makeMap(TreeMap::new, false)}, new Object[]{"ConcurrentHashMap", makeMap(ConcurrentHashMap::new, false, false)},
new Object[]{"ConcurrentHashMap", makeMap(ConcurrentHashMap::new, false)}, new Object[]{"ConcurrentSkipListMap", makeMap(ConcurrentSkipListMap::new, false, false)},
new Object[]{"ConcurrentSkipListMap", makeMap(ConcurrentSkipListMap::new, false)}, new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(makeMap(ConcurrentHashMap::new, false, false), IntegerEnum.class, String.class)},
new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(makeMap(ConcurrentHashMap::new, false), IntegerEnum.class, String.class)}, new Object[]{"ImplementsConcurrentMap", makeMap(ImplementsConcurrentMap::new, false, false)}
new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(makeMap(() -> new EnumMap(IntegerEnum.class), false))}, );
new Object[]{"ImplementsConcurrentMap", makeMap(ImplementsConcurrentMap::new, false)});
} }
/**
*
* @param nulls include nulls
* @return
*/
private static Collection<Object[]> makeROMaps(boolean nulls) { private static Collection<Object[]> makeROMaps(boolean nulls) {
return Arrays.asList(new Object[][]{ return Arrays.asList(new Object[][]{
new Object[]{"Collections.unmodifiableMap(HashMap)", Collections.unmodifiableMap(makeMap(HashMap::new, nulls))} new Object[]{"Collections.unmodifiableMap(HashMap)", Collections.unmodifiableMap(makeMap(HashMap::new, nulls, nulls))}
}); });
} }
private static Map<IntegerEnum, String> makeMap(Supplier<Map<IntegerEnum, String>> supplier, boolean nulls) { /**
*
* @param supplier a supplier of mutable map instances.
*
* @param nullKeys include null keys
* @param nullValues include null values
* @return
*/
private static Map<IntegerEnum, String> makeMap(Supplier<Map<IntegerEnum, String>> supplier, boolean nullKeys, boolean nullValues) {
Map<IntegerEnum, String> result = supplier.get(); Map<IntegerEnum, String> result = supplier.get();
for (int each = 0; each < TEST_SIZE; each++) { for (int each = 0; each < TEST_SIZE; each++) {
if (nulls) { IntegerEnum key = nullKeys ? (each == 0) ? null : KEYS[each] : KEYS[each];
result.put((each == 0) ? null : KEYS[each], null); String value = nullValues ? (each == 0) ? null : VALUES[each] : VALUES[each];
} else {
result.put(KEYS[each], VALUES[each]); result.put(key, value);
}
} }
return result; return result;
@ -520,6 +608,12 @@ public class Defaults {
: "Failed to throw " + throwable.getCanonicalName()); : "Failed to throw " + throwable.getCanonicalName());
} }
public static <T extends Throwable> void assertThrows(Class<T> throwable, String message, Thrower<T>... throwers) {
for(Thrower<T> thrower : throwers) {
assertThrows(thrower, throwable, message);
}
}
public static <T> void assertInstance(T actual, Class<? extends T> expected) { public static <T> void assertInstance(T actual, Class<? extends T> expected) {
assertInstance(expected.isInstance(actual), null); assertInstance(expected.isInstance(actual), null);
} }