--- old/src/share/classes/java/util/HashMap.java 2013-10-09 20:32:53.928222556 -0700
+++ new/src/share/classes/java/util/HashMap.java 2013-10-09 20:32:53.768222549 -0700
@@ -1230,7 +1230,11 @@
}
}
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);
--- old/src/share/classes/java/util/Map.java 2013-10-09 20:32:54.672222592 -0700
+++ new/src/share/classes/java/util/Map.java 2013-10-09 20:32:54.516222585 -0700
@@ -577,6 +577,7 @@
* @throws NullPointerException if the specified key is null and this map
* does not permit null keys
* (optional)
+ * @since 1.8
*/
default V getOrDefault(Object key, V defaultValue) {
V v;
@@ -788,7 +789,9 @@
(curValue == null && !containsKey(key))) {
return false;
}
- remove(key);
+ if(curValue != remove(key)) {
+ throw new ConcurrentModificationException("value unexpectedly changed");
+ }
return true;
}
@@ -800,7 +803,7 @@
* or atomicity properties of this method. Any implementation providing
* atomicity guarantees must override this method and document its
* concurrency properties.
- *
+ *
* @implSpec
* The default implementation is equivalent to, for this {@code map}:
*
@@ -840,8 +843,10 @@
(curValue == null && !containsKey(key))) {
return false;
}
- put(key, newValue);
- return true;
+ if(curValue == put(key, newValue)) {
+ return true;
+ }
+ throw new ConcurrentModificationException("value unexpectedly changed");
}
/**
@@ -883,7 +888,15 @@
* @since 1.8
*/
default V replace(K key, V value) {
- return containsKey(key) ? put(key, value) : null;
+ V curValue;
+ if(((curValue = get(key)) != null) || containsKey(key)) {
+ if(curValue == put(key, value)) {
+ return curValue;
+ }
+ } else {
+ return curValue;
+ }
+ throw new ConcurrentModificationException("value unexpectedly changed");
}
/**
@@ -908,8 +921,7 @@
* concurrency properties. In particular, all implementations of
* subinterface {@link java.util.concurrent.ConcurrentMap} must document
* whether the function is applied once atomically only if the value is not
- * present. Any class that permits null values must document
- * whether and how this method distinguishes absence from null mappings.
+ * present.
*
* @implSpec
* The default implementation is equivalent to the following
@@ -942,10 +954,17 @@
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;
+ V v;
+ if ((v = get(key)) == null) {
+ V newValue;
+ if ((newValue = mappingFunction.apply(key)) != null) {
+ if (null == putIfAbsent(key, newValue))
+ return newValue;
+ throw new ConcurrentModificationException("expected value changed");
+ }
+ }
+
+ return v;
}
/**
@@ -981,9 +1000,6 @@
* }
* }
*
- * In concurrent contexts, the default implementation may retry
- * these steps when multiple threads attempt updates.
- *
* @param key key with which the specified value is to be associated
* @param remappingFunction the function to compute a value
* @return the new value associated with the specified key, or null if none
@@ -1002,15 +1018,17 @@
BiFunction super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
- while ((oldValue = get(key)) != null) {
+ if((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 null;
+ } else {
+ return null;
}
- return oldValue;
+ throw new ConcurrentModificationException("unexpected value change");
}
/**
@@ -1058,9 +1076,6 @@
* }
* }
*
- * In concurrent contexts, the default implementation may retry
- * these steps when multiple threads attempt updates.
- *
* @param key key with which the specified value is to be associated
* @param remappingFunction the function to compute a value
* @return the new value associated with the specified key, or null if none
@@ -1079,45 +1094,37 @@
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.
+
+ 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;
}
} 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.
+ // 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;
+ }
+ } else {
+ // add (replace if oldValue was null)
+ if (putIfAbsent(key, newValue) == null) {
+ // replaced
+ return newValue;
}
}
}
+ throw new ConcurrentModificationException("expected value change");
}
/**
@@ -1144,8 +1151,7 @@
* concurrency properties. In particular, all implementations of
* subinterface {@link java.util.concurrent.ConcurrentMap} must document
* whether the function is applied once atomically only if the value is not
- * present. Any class that permits null values must document
- * whether and how this method distinguishes absence from null mappings.
+ * present.
*
* @implSpec
* The default implementation is equivalent to performing the
@@ -1164,9 +1170,6 @@
* map.replace(key, oldValue, newValue);
* }
*
- * In concurrent contexts, the default implementation may retry
- * these steps when multiple threads attempt updates.
- *
* @param key key with which the specified value is to be associated
* @param value the value to use if absent
* @param remappingFunction the function to recompute a value if present
@@ -1186,25 +1189,25 @@
BiFunction super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
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)) {
+ 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;
+ }
+ } else {
+ if (value == null) {
+ if(remove(key) == null) {
return null;
}
- oldValue = get(key);
} else {
- if (value == null) {
- return null;
- }
-
- if ((oldValue = putIfAbsent(key, value)) == null) {
+ if (putIfAbsent(key, value) == null) {
return value;
}
}
}
+ throw new ConcurrentModificationException("unexpected value change");
}
}
--- old/src/share/classes/java/util/concurrent/ConcurrentMap.java 2013-10-09 20:32:55.416222628 -0700
+++ new/src/share/classes/java/util/concurrent/ConcurrentMap.java 2013-10-09 20:32:55.260222621 -0700
@@ -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,9 @@
* {@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
+ * must override this default implementation.
*/
@Override
default V getOrDefault(Object key, V defaultValue) {
@@ -74,6 +76,33 @@
return ((v = get(key)) != null) ? v : defaultValue;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @implNote This implementation assumes that {@code IllegalStateException}
+ * thrown by {code getKey()} or {@code getValue()} signal that the entry has
+ * been removed and cannot be processed. Operation continues for subsequent
+ * entries.
+ *
+ * @throws NullPointerException {@inheritDoc}
+ */
+ @Override
+ default void forEach(BiConsumer super K, ? super V> action) {
+ Objects.requireNonNull(action);
+ for (Map.Entry 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.
@@ -86,6 +115,9 @@
*
* 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 +134,7 @@
* @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.
@@ -116,6 +148,9 @@
*
* 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
@@ -142,6 +177,9 @@
*
* 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
@@ -168,6 +206,9 @@
*
* 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,7 +230,10 @@
/**
* {@inheritDoc}
*
- * @implNote This implementation assumes that the ConcurrentMap cannot
+ * @implNote This default implementation may retry these steps when multiple
+ * threads attempt updates.
+ *
+ * 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.
@@ -207,4 +251,145 @@
}
});
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @implNote The default implementation may retry these steps when multiple
+ * threads attempt updates.
+ *
+ * The default 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.
+ */
+ @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}
+ *
+ * @implNote The default implementation may retry these steps when multiple
+ * threads attempt updates.
+ *
+ * The default 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.
+ */
+ @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}
+ *
+ * @implNote The default implementation may retry these steps when multiple
+ * threads attempt updates.
+ *
+ * The default 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.
+ */
+ @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}
+ *
+ * @implNote The default implementation may retry these steps when multiple
+ * threads attempt updates.
+ *
+ * The default 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.
+ */
+ @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;
+ }
+ }
+ }
+ }
}
--- old/test/java/util/Map/Defaults.java 2013-10-09 20:32:56.148222664 -0700
+++ new/test/java/util/Map/Defaults.java 2013-10-09 20:32:55.984222656 -0700
@@ -41,6 +41,7 @@
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Set;
@@ -288,19 +289,11 @@
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 rw=true keys=all values=all")
+ public void testComputeIfAbsentNullFunction(String description, Map map) {
+ assertThrows( () -> { map.computeIfAbsent(KEYS[1], null);},
+ NullPointerException.class,
+ "Should throw NPE");
}
@Test(dataProvider = "Map rw=true keys=withNull values=withNull")
@@ -343,22 +336,14 @@
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 rw=true keys=all values=all")
+ public void testComputeIfPresentNullFunction(String description, Map map) {
+ assertThrows( () -> { map.computeIfPresent(KEYS[1], null);},
+ NullPointerException.class,
+ "Should throw NPE");
}
- @Test(dataProvider = "Map rw=true keys=withNull values=withNull")
+ @Test(dataProvider = "Map rw=true keys=withNull values=withNull")
public void testComputeNulls(String description, Map map) {
assertTrue(map.containsKey(null), "null key absent");
assertNull(map.get(null), "value not null");
@@ -444,78 +429,86 @@
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 rw=true keys=withNull values=withNull")
- public void testMergeNulls(String description, Map 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 rw=true keys=all values=all")
- public void testMerge(String description, Map 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);
- }
+ public void testComputeNullFunction(String description, Map map) {
+ assertThrows( () -> { map.compute(KEYS[1], null);},
+ NullPointerException.class,
+ "Should throw NPE");
+ }
+
+ @Test(dataProvider = "MergeCases")
+ private void testMerge(String description, Map 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 testMergeNPEHashMap() {
- Object value = new HashMap().merge(KEYS[1], VALUES[1], null);
- }
+ // check result
- @Test(expectedExceptions = {NullPointerException.class})
- public void testMergeNPEHashtable() {
- Object value = new Hashtable().merge(KEYS[1], VALUES[1], null);
+ 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 rw=true keys=all values=all")
+ public void testMergeNullMerger(String description, Map 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 +708,89 @@
return result;
}
+ static class Merging {
+ public enum Value {
+ ABSENT,
+ NULL,
+ OLDVALUE,
+ NEWVALUE,
+ RESULT
+ }
+
+ public enum Merger implements BiFunction {
+ 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