提交 0ed38d0f 编写于 作者: M mduigou

8024688: further split Map and ConcurrentMap defaults eliminating looping from...

8024688: further split Map and ConcurrentMap defaults eliminating looping from Map defaults, Map.merge fixes and doc fixes.
Reviewed-by: psandoz, dholmes
上级 91781e1e
......@@ -915,7 +915,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<K,V>(HashMap.this, 0, -1, 0, 0);
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
......@@ -959,7 +959,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
public final Iterator<V> iterator() { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return new ValueSpliterator<K,V>(HashMap.this, 0, -1, 0, 0);
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
......@@ -1022,7 +1022,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<K,V>(HashMap.this, 0, -1, 0, 0);
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
......@@ -1042,19 +1042,23 @@ public class HashMap<K,V> extends AbstractMap<K,V>
// Overrides of JDK8 Map extension methods
@Override
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
@Override
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key, value, true, true);
}
@Override
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
......@@ -1066,6 +1070,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
return false;
}
@Override
public V replace(K key, V value) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) != null) {
......@@ -1077,6 +1082,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
return null;
}
@Override
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
if (mappingFunction == null)
......@@ -1150,6 +1156,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
return null;
}
@Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
......@@ -1202,6 +1209,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
return v;
}
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
......@@ -1230,7 +1238,11 @@ public class HashMap<K,V> extends AbstractMap<K,V>
}
}
if (old != null) {
V v = remappingFunction.apply(old.value, value);
V v;
if (old.value != null)
v = remappingFunction.apply(old.value, value);
else
v = value;
if (v != null) {
old.value = v;
afterNodeAccess(old);
......@@ -1254,6 +1266,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
return value;
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
......@@ -1269,6 +1282,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Node<K,V>[] tab;
if (function == null)
......@@ -1295,6 +1309,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
* @return a shallow copy of this map
*/
@SuppressWarnings("unchecked")
@Override
public Object clone() {
HashMap<K,V> result;
try {
......@@ -1496,7 +1511,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
public KeySpliterator<K,V> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null) ? null :
new KeySpliterator<K,V>(map, lo, index = mid, est >>>= 1,
new KeySpliterator<>(map, lo, index = mid, est >>>= 1,
expectedModCount);
}
......@@ -1568,7 +1583,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
public ValueSpliterator<K,V> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null) ? null :
new ValueSpliterator<K,V>(map, lo, index = mid, est >>>= 1,
new ValueSpliterator<>(map, lo, index = mid, est >>>= 1,
expectedModCount);
}
......@@ -1639,7 +1654,7 @@ public class HashMap<K,V> extends AbstractMap<K,V>
public EntrySpliterator<K,V> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null) ? null :
new EntrySpliterator<K,V>(map, lo, index = mid, est >>>= 1,
new EntrySpliterator<>(map, lo, index = mid, est >>>= 1,
expectedModCount);
}
......@@ -1714,22 +1729,22 @@ public class HashMap<K,V> extends AbstractMap<K,V>
// Create a regular (non-tree) node
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<K,V>(hash, key, value, next);
return new Node<>(hash, key, value, next);
}
// For conversion from TreeNodes to plain nodes
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
return new Node<K,V>(p.hash, p.key, p.value, next);
return new Node<>(p.hash, p.key, p.value, next);
}
// Create a tree bin node
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
return new TreeNode<K,V>(hash, key, value, next);
return new TreeNode<>(hash, key, value, next);
}
// For treeifyBin
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<K,V>(p.hash, p.key, p.value, next);
return new TreeNode<>(p.hash, p.key, p.value, next);
}
/**
......
......@@ -36,7 +36,9 @@
package java.util.concurrent;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* A {@link java.util.Map} providing thread safety and atomicity
......@@ -64,9 +66,13 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
* {@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 must override this
* default implementation.
* contain null values and {@code get()} returning null unambiguously means
* the key is absent. Implementations which support null values
* <strong>must</strong> override this default implementation.
*
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
default V getOrDefault(Object key, V defaultValue) {
......@@ -74,6 +80,41 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
return ((v = get(key)) != null) ? v : defaultValue;
}
/**
* {@inheritDoc}
*
* @implSpec The default implementation is equivalent to, for this
* {@code map}:
* <pre> {@code
* for ((Map.Entry<K, V> entry : map.entrySet())
* action.accept(entry.getKey(), entry.getValue());
* }</pre>
*
* @implNote The default implementation assumes that
* {@code IllegalStateException} thrown by {@code getKey()} or
* {@code getValue()} indicates that the entry has been removed and cannot
* be processed. Operation continues for subsequent entries.
*
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
continue;
}
action.accept(k, v);
}
}
/**
* If the specified key is not already associated
* with a value, associate it with the given value.
......@@ -82,10 +123,14 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
* if (!map.containsKey(key))
* return map.put(key, value);
* else
* return map.get(key);}</pre>
* return map.get(key);
* }</pre>
*
* except that the action is performed atomically.
*
* @implNote This implementation intentionally re-abstracts the
* inappropriate default provided in {@code Map}.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with the specified key, or
......@@ -102,7 +147,7 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
*/
V putIfAbsent(K key, V value);
V putIfAbsent(K key, V value);
/**
* Removes the entry for a key only if currently mapped to a given value.
......@@ -112,10 +157,14 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
* map.remove(key);
* return true;
* } else
* return false;}</pre>
* return false;
* }</pre>
*
* except that the action is performed atomically.
*
* @implNote This implementation intentionally re-abstracts the
* inappropriate default provided in {@code Map}.
*
* @param key key with which the specified value is associated
* @param value value expected to be associated with the specified key
* @return {@code true} if the value was removed
......@@ -138,10 +187,14 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
* map.put(key, newValue);
* return true;
* } else
* return false;}</pre>
* return false;
* }</pre>
*
* except that the action is performed atomically.
*
* @implNote This implementation intentionally re-abstracts the
* inappropriate default provided in {@code Map}.
*
* @param key key with which the specified value is associated
* @param oldValue value expected to be associated with the specified key
* @param newValue value to be associated with the specified key
......@@ -164,10 +217,14 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
* if (map.containsKey(key)) {
* return map.put(key, value);
* } else
* return null;}</pre>
* return null;
* }</pre>
*
* except that the action is performed atomically.
*
* @implNote This implementation intentionally re-abstracts the
* inappropriate default provided in {@code Map}.
*
* @param key key with which the specified value is associated
* @param value value to be associated with the specified key
* @return the previous value associated with the specified key, or
......@@ -189,10 +246,30 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
/**
* {@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.
* @implSpec
* <p>The default implementation is equivalent to, for this {@code map}:
* <pre> {@code
* for ((Map.Entry<K, V> entry : map.entrySet())
* do {
* K k = entry.getKey();
* V v = entry.getValue();
* } while(!replace(k, v, function.apply(k, v)));
* }</pre>
*
* The default implementation may retry these steps when multiple
* threads attempt updates including potentially calling the function
* repeatedly for a given key.
*
* <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is
* absent. Implementations which support null values <strong>must</strong>
* override this default implementation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @since 1.8
*/
@Override
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
......@@ -200,11 +277,243 @@ public interface ConcurrentMap<K, V> extends Map<K, V> {
forEach((k,v) -> {
while(!replace(k, v, function.apply(k, v))) {
// v changed or k is gone
if( (v = get(k)) == null) {
if ( (v = get(k)) == null) {
// k is no longer in the map.
break;
}
}
});
}
/**
* {@inheritDoc}
*
* @implSpec
* The default implementation is equivalent to the following steps for this
* {@code map}, then returning the current value or {@code null} if now
* absent:
*
* <pre> {@code
* if (map.get(key) == null) {
* V newValue = mappingFunction.apply(key);
* if (newValue != null)
* return map.putIfAbsent(key, newValue);
* }
* }</pre>
*
* The default implementation may retry these steps when multiple
* threads attempt updates including potentially calling the mapping
* function multiple times.
*
* <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is
* absent. Implementations which support null values <strong>must</strong>
* override this default implementation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v, newValue;
return ((v = get(key)) == null &&
(newValue = mappingFunction.apply(key)) != null &&
(v = putIfAbsent(key, newValue)) == null) ? newValue : v;
}
/**
* {@inheritDoc}
*
* @implSpec
* The default implementation is equivalent to performing the following
* steps for this {@code map}, then returning the current value or
* {@code null} if now absent. :
*
* <pre> {@code
* if (map.get(key) != null) {
* V oldValue = map.get(key);
* V newValue = remappingFunction.apply(key, oldValue);
* if (newValue != null)
* map.replace(key, oldValue, newValue);
* else
* map.remove(key, oldValue);
* }
* }</pre>
*
* The default implementation may retry these steps when multiple threads
* attempt updates including potentially calling the remapping function
* multiple times.
*
* <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is
* absent. Implementations which support null values <strong>must</strong>
* override this default implementation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
while((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
if (replace(key, oldValue, newValue))
return newValue;
} else if (remove(key, oldValue))
return null;
}
return oldValue;
}
/**
* {@inheritDoc}
*
* @implSpec
* The default implementation is equivalent to performing the following
* steps for this {@code map}, then returning the current value or
* {@code null} if absent:
*
* <pre> {@code
* V oldValue = map.get(key);
* V newValue = remappingFunction.apply(key, oldValue);
* if (oldValue != null ) {
* if (newValue != null)
* map.replace(key, oldValue, newValue);
* else
* map.remove(key, oldValue);
* } else {
* if (newValue != null)
* map.putIfAbsent(key, newValue);
* else
* return null;
* }
* }</pre>
*
* The default implementation may retry these steps when multiple
* threads attempt updates including potentially calling the remapping
* function multiple times.
*
* <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is
* absent. Implementations which support null values <strong>must</strong>
* override this default implementation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
for(;;) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
if (remove(key, oldValue)) {
// removed the old value as expected
return null;
}
// some other value replaced old value. try again.
oldValue = get(key);
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
if (oldValue != null) {
// replace
if (replace(key, oldValue, newValue)) {
// replaced as expected.
return newValue;
}
// some other value replaced old value. try again.
oldValue = get(key);
} else {
// add (replace if oldValue was null)
if ((oldValue = putIfAbsent(key, newValue)) == null) {
// replaced
return newValue;
}
// some other value replaced old value. try again.
}
}
}
}
/**
* {@inheritDoc}
*
* @implSpec
* The default implementation is equivalent to performing the
* following steps for this {@code map}, then returning the
* current value or {@code null} if absent:
*
* <pre> {@code
* V oldValue = map.get(key);
* V newValue = (oldValue == null) ? value :
* remappingFunction.apply(oldValue, value);
* if (newValue == null)
* map.remove(key);
* else if (oldValue == null)
* map.remove(key);
* else
* map.put(key, newValue);
* }</pre>
*
* <p>The default implementation may retry these steps when multiple
* threads attempt updates including potentially calling the remapping
* function multiple times.
*
* <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is
* absent. Implementations which support null values <strong>must</strong>
* override this default implementation.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
for (;;) {
if (oldValue != null) {
V newValue = remappingFunction.apply(oldValue, value);
if (newValue != null) {
if (replace(key, oldValue, newValue))
return newValue;
} else if (remove(key, oldValue)) {
return null;
}
oldValue = get(key);
} else {
if ((oldValue = putIfAbsent(key, value)) == null) {
return value;
}
}
}
}
}
......@@ -23,7 +23,7 @@
/*
* @test
* @bug 8010122 8004518 8024331
* @bug 8010122 8004518 8024331 8024688
* @summary Test Map default methods
* @author Mike Duigou
* @run testng Defaults
......@@ -36,8 +36,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
......@@ -288,19 +288,11 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeIfAbsentNPEHashMap() {
Object value = new HashMap().computeIfAbsent(KEYS[1], null);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeIfAbsentNPEHashtable() {
Object value = new Hashtable().computeIfAbsent(KEYS[1], null);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeIfAbsentNPETreeMap() {
Object value = new TreeMap().computeIfAbsent(KEYS[1], null);
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testComputeIfAbsentNullFunction(String description, Map<IntegerEnum, String> map) {
assertThrows( () -> { map.computeIfAbsent(KEYS[1], null);},
NullPointerException.class,
"Should throw NPE");
}
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
......@@ -343,22 +335,14 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), null);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeIfPresentNPEHashMap() {
Object value = new HashMap().computeIfPresent(KEYS[1], null);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeIfPresentNPEHashtable() {
Object value = new Hashtable().computeIfPresent(KEYS[1], null);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeIfPresentNPETreeMap() {
Object value = new TreeMap().computeIfPresent(KEYS[1], null);
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testComputeIfPresentNullFunction(String description, Map<IntegerEnum, String> map) {
assertThrows( () -> { map.computeIfPresent(KEYS[1], null);},
NullPointerException.class,
"Should throw NPE");
}
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testComputeNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null");
......@@ -444,78 +428,86 @@ public class Defaults {
assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeNPEHashMap() {
Object value = new HashMap().compute(KEYS[1], null);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeNPEHashtable() {
Object value = new Hashtable().compute(KEYS[1], null);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testComputeNPETreeMap() {
Object value = new TreeMap().compute(KEYS[1], null);
}
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
public void testMergeNulls(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null");
assertSame(map.merge(null, EXTRA_VALUE, (v, vv) -> {
assertNull(v);
assertSame(vv, EXTRA_VALUE);
return vv;
}), EXTRA_VALUE, description);
assertTrue(map.containsKey(null));
assertSame(map.get(null), EXTRA_VALUE, description);
}
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testMerge(String description, Map<IntegerEnum, String> map) {
assertTrue(map.containsKey(KEYS[1]));
Object value = map.get(KEYS[1]);
assertTrue(null == value || value == VALUES[1], description + String.valueOf(value));
assertSame(map.merge(KEYS[1], EXTRA_VALUE, (v, vv) -> {
assertSame(v, value);
assertSame(vv, EXTRA_VALUE);
return vv;
}), EXTRA_VALUE, description);
assertSame(map.get(KEYS[1]), EXTRA_VALUE, description);
assertNull(map.merge(KEYS[1], EXTRA_VALUE, (v, vv) -> {
assertSame(v, EXTRA_VALUE);
assertSame(vv, EXTRA_VALUE);
return null;
}), description);
assertFalse(map.containsKey(KEYS[1]));
assertFalse(map.containsKey(EXTRA_KEY));
assertSame(map.merge(EXTRA_KEY, EXTRA_VALUE, (v, vv) -> {
assertNull(v);
assertSame(vv, EXTRA_VALUE);
return EXTRA_VALUE;
}), EXTRA_VALUE);
assertTrue(map.containsKey(EXTRA_KEY));
assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
}
@Test(expectedExceptions = {NullPointerException.class})
public void testMergeNPEHashMap() {
Object value = new HashMap().merge(KEYS[1], VALUES[1], null);
}
public void testComputeNullFunction(String description, Map<IntegerEnum, String> map) {
assertThrows( () -> { map.compute(KEYS[1], null);},
NullPointerException.class,
"Should throw NPE");
}
@Test(dataProvider = "MergeCases")
private void testMerge(String description, Map<IntegerEnum, String> map, Merging.Value oldValue, Merging.Value newValue, Merging.Merger merger, Merging.Value put, Merging.Value result) {
// add and check initial conditions.
switch(oldValue) {
case ABSENT :
map.remove(EXTRA_KEY);
assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
break;
case NULL :
map.put(EXTRA_KEY, null);
assertTrue(map.containsKey(EXTRA_KEY), "key absent");
assertNull(map.get(EXTRA_KEY), "wrong value");
break;
case OLDVALUE :
map.put(EXTRA_KEY, VALUES[1]);
assertTrue(map.containsKey(EXTRA_KEY), "key absent");
assertSame(map.get(EXTRA_KEY), VALUES[1], "wrong value");
break;
default:
fail("unexpected old value");
}
String returned = map.merge(EXTRA_KEY,
newValue == Merging.Value.NULL ? (String) null : VALUES[2],
merger
);
@Test(expectedExceptions = {NullPointerException.class})
public void testMergeNPEHashtable() {
Object value = new Hashtable().merge(KEYS[1], VALUES[1], null);
// check result
switch(result) {
case NULL :
assertNull(returned, "wrong value");
break;
case NEWVALUE :
assertSame(returned, VALUES[2], "wrong value");
break;
case RESULT :
assertSame(returned, VALUES[3], "wrong value");
break;
default:
fail("unexpected new value");
}
// check map
switch(put) {
case ABSENT :
assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
break;
case NULL :
assertTrue(map.containsKey(EXTRA_KEY), "key absent");
assertNull(map.get(EXTRA_KEY), "wrong value");
break;
case NEWVALUE :
assertTrue(map.containsKey(EXTRA_KEY), "key absent");
assertSame(map.get(EXTRA_KEY), VALUES[2], "wrong value");
break;
case RESULT :
assertTrue(map.containsKey(EXTRA_KEY), "key absent");
assertSame(map.get(EXTRA_KEY), VALUES[3], "wrong value");
break;
default:
fail("unexpected new value");
}
}
@Test(expectedExceptions = {NullPointerException.class})
public void testMergeNPETreeMap() {
Object value = new TreeMap().merge(KEYS[1], VALUES[1], null);
@Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
public void testMergeNullMerger(String description, Map<IntegerEnum, String> map) {
assertThrows( () -> { map.merge(KEYS[1], VALUES[1], null);},
NullPointerException.class,
"Should throw NPE");
}
enum IntegerEnum {
public enum IntegerEnum {
e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
......@@ -715,6 +707,89 @@ public class Defaults {
return result;
}
static class Merging {
public enum Value {
ABSENT,
NULL,
OLDVALUE,
NEWVALUE,
RESULT
}
public enum Merger implements BiFunction<String,String,String> {
UNUSED {
public String apply(String oldValue, String newValue) {
fail("should not be called");
return null;
}
},
NULL {
public String apply(String oldValue, String newValue) {
return null;
}
},
RESULT {
public String apply(String oldValue, String newValue) {
return VALUES[3];
}
},
}
}
@DataProvider(name = "MergeCases", parallel = true)
public Iterator<Object[]> mergeCasesProvider() {
Collection<Object[]> cases = new ArrayList<>();
cases.addAll(makeMergeTestCases());
cases.addAll(makeMergeNullValueTestCases());
return cases.iterator();
}
static Collection<Object[]> makeMergeTestCases() {
Collection<Object[]> cases = new ArrayList<>();
for( Object[] mapParams : makeAllRWMaps() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
}
for( Object[] mapParams : makeAllRWMaps() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
}
for( Object[] mapParams : makeAllRWMaps() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
}
return cases;
}
static Collection<Object[]> makeMergeNullValueTestCases() {
Collection<Object[]> cases = new ArrayList<>();
for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NULL, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
}
for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NULL, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
}
for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NULL, Merging.Merger.UNUSED, Merging.Value.ABSENT, Merging.Value.NULL });
}
for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.NULL, Merging.Value.NULL, Merging.Merger.UNUSED, Merging.Value.ABSENT, Merging.Value.NULL });
}
for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.NULL, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
}
return cases;
}
public interface Thrower<T extends Throwable> {
public void run() throws T;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册