8145164: Default implementation of ConcurrentMap::compute can throw NPE

Reviewed-by: martin, psandoz, chegar
This commit is contained in:
Doug Lea 2016-01-29 11:49:37 -08:00
parent 47e7f9dcdc
commit 7f6a9e3180
3 changed files with 324 additions and 134 deletions

View File

@ -649,7 +649,7 @@ public interface Map<K, V> {
try { try {
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. // this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise); throw new ConcurrentModificationException(ise);
} }
@ -704,7 +704,7 @@ public interface Map<K, V> {
try { try {
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. // this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise); throw new ConcurrentModificationException(ise);
} }
@ -714,7 +714,7 @@ public interface Map<K, V> {
try { try {
entry.setValue(v); entry.setValue(v);
} catch(IllegalStateException ise) { } catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map. // this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise); throw new ConcurrentModificationException(ise);
} }
@ -887,7 +887,7 @@ public interface Map<K, V> {
* or atomicity properties of this method. Any implementation providing * or atomicity properties of this method. Any implementation providing
* atomicity guarantees must override this method and document its * atomicity guarantees must override this method and document its
* concurrency properties. * concurrency properties.
* *
* @param key key with which the specified value is associated * @param key key with which the specified value is associated
* @param value value to be associated with the specified key * @param value value to be associated with the specified key
* @return the previous value associated with the specified key, or * @return the previous value associated with the specified key, or
@ -984,6 +984,9 @@ public interface Map<K, V> {
* @throws ClassCastException if the class of the specified key or value * @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map * prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>) * (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @since 1.8 * @since 1.8
*/ */
default V computeIfAbsent(K key, default V computeIfAbsent(K key,
@ -1058,6 +1061,9 @@ public interface Map<K, V> {
* @throws ClassCastException if the class of the specified key or value * @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map * prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>) * (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @since 1.8 * @since 1.8
*/ */
default V computeIfPresent(K key, default V computeIfPresent(K key,
@ -1103,7 +1109,7 @@ public interface Map<K, V> {
* <pre> {@code * <pre> {@code
* V oldValue = map.get(key); * V oldValue = map.get(key);
* V newValue = remappingFunction.apply(key, oldValue); * V newValue = remappingFunction.apply(key, oldValue);
* if (oldValue != null ) { * if (oldValue != null) {
* if (newValue != null) * if (newValue != null)
* map.put(key, newValue); * map.put(key, newValue);
* else * else
@ -1147,6 +1153,9 @@ public interface Map<K, V> {
* @throws ClassCastException if the class of the specified key or value * @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map * prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>) * (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @since 1.8 * @since 1.8
*/ */
default V compute(K key, default V compute(K key,
@ -1239,6 +1248,9 @@ public interface Map<K, V> {
* @throws ClassCastException if the class of the specified key or value * @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map * prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>) * (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified key is null and this map * @throws NullPointerException if the specified key is null and this map
* does not support null keys or the value or remappingFunction is * does not support null keys or the value or remappingFunction is
* null * null
@ -1251,7 +1263,7 @@ public interface Map<K, V> {
V oldValue = get(key); V oldValue = get(key);
V newValue = (oldValue == null) ? value : V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value); remappingFunction.apply(oldValue, value);
if(newValue == null) { if (newValue == null) {
remove(key); remove(key);
} else { } else {
put(key, newValue); put(key, newValue);

View File

@ -301,19 +301,15 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* *
* @implSpec * @implSpec
* The default implementation is equivalent to the following steps for this * The default implementation is equivalent to the following steps for this
* {@code map}, then returning the current value or {@code null} if now * {@code map}:
* absent:
* *
* <pre> {@code * <pre> {@code
* if (map.get(key) == null) { * V oldValue, newValue;
* V newValue = mappingFunction.apply(key); * return ((oldValue = map.get(key)) == null
* if (newValue != null) * && (newValue = mappingFunction.apply(key)) != null
* return map.putIfAbsent(key, newValue); * && (oldValue = map.putIfAbsent(key, newValue)) == null)
* }}</pre> * ? newValue
* * : oldValue;}</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 * <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is * values and {@code get()} returning null unambiguously means the key is
@ -323,16 +319,19 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* @throws UnsupportedOperationException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc} * @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc} * @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @since 1.8 * @since 1.8
*/ */
@Override @Override
default V computeIfAbsent(K key, default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) { Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction); Objects.requireNonNull(mappingFunction);
V v, newValue; V oldValue, newValue;
return ((v = get(key)) == null && return ((oldValue = get(key)) == null
(newValue = mappingFunction.apply(key)) != null && && (newValue = mappingFunction.apply(key)) != null
(v = putIfAbsent(key, newValue)) == null) ? newValue : v; && (oldValue = putIfAbsent(key, newValue)) == null)
? newValue
: oldValue;
} }
/** /**
@ -340,22 +339,19 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* *
* @implSpec * @implSpec
* The default implementation is equivalent to performing the following * The default implementation is equivalent to performing the following
* steps for this {@code map}, then returning the current value or * steps for this {@code map}:
* {@code null} if now absent:
* *
* <pre> {@code * <pre> {@code
* if (map.get(key) != null) { * for (V oldValue; (oldValue = map.get(key)) != null; ) {
* V oldValue = map.get(key);
* V newValue = remappingFunction.apply(key, oldValue); * V newValue = remappingFunction.apply(key, oldValue);
* if (newValue != null) * if ((newValue == null)
* map.replace(key, oldValue, newValue); * ? map.remove(key, oldValue)
* else * : map.replace(key, oldValue, newValue))
* map.remove(key, oldValue); * return newValue;
* }}</pre> * }
* * return null;}</pre>
* The default implementation may retry these steps when multiple threads * When multiple threads attempt updates, map operations and the
* attempt updates including potentially calling the remapping function * remapping function may be called multiple times.
* multiple times.
* *
* <p>This implementation assumes that the ConcurrentMap cannot contain null * <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is * values and {@code get()} returning null unambiguously means the key is
@ -365,22 +361,21 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* @throws UnsupportedOperationException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc} * @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc} * @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @since 1.8 * @since 1.8
*/ */
@Override @Override
default V computeIfPresent(K key, default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) { BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction); Objects.requireNonNull(remappingFunction);
V oldValue; for (V oldValue; (oldValue = get(key)) != null; ) {
while ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue); V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) { if ((newValue == null)
if (replace(key, oldValue, newValue)) ? remove(key, oldValue)
return newValue; : replace(key, oldValue, newValue))
} else if (remove(key, oldValue)) return newValue;
return null;
} }
return oldValue; return null;
} }
/** /**
@ -388,27 +383,23 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* *
* @implSpec * @implSpec
* The default implementation is equivalent to performing the following * The default implementation is equivalent to performing the following
* steps for this {@code map}, then returning the current value or * steps for this {@code map}:
* {@code null} if absent:
* *
* <pre> {@code * <pre> {@code
* V oldValue = map.get(key); * for (;;) {
* V newValue = remappingFunction.apply(key, oldValue); * V oldValue = map.get(key);
* if (oldValue != null ) { * V newValue = remappingFunction.apply(key, oldValue);
* if (newValue != null) * if (newValue != null) {
* map.replace(key, oldValue, newValue); * if ((oldValue != null)
* else * ? map.replace(key, oldValue, newValue)
* map.remove(key, oldValue); * : map.putIfAbsent(key, newValue) == null)
* } else { * return newValue;
* if (newValue != null) * } else if (oldValue == null || map.remove(key, oldValue)) {
* map.putIfAbsent(key, newValue);
* else
* return null; * return null;
* }
* }}</pre> * }}</pre>
* * When multiple threads attempt updates, map operations and the
* The default implementation may retry these steps when multiple * remapping function may be called multiple times.
* threads attempt updates including potentially calling the remapping
* function multiple times.
* *
* <p>This implementation assumes that the ConcurrentMap cannot contain null * <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is * values and {@code get()} returning null unambiguously means the key is
@ -418,50 +409,29 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* @throws UnsupportedOperationException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc} * @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc} * @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @since 1.8 * @since 1.8
*/ */
@Override @Override
default V compute(K key, default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) { BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction); retry: for (;;) {
V oldValue = get(key); V oldValue = get(key);
for (;;) { // if putIfAbsent fails, opportunistically use its return value
V newValue = remappingFunction.apply(key, oldValue); haveOldValue: for (;;) {
if (newValue == null) { V newValue = remappingFunction.apply(key, oldValue);
// delete mapping if (newValue != null) {
if (oldValue != null || containsKey(key)) { if (oldValue != null) {
// something to remove if (replace(key, oldValue, newValue))
if (remove(key, oldValue)) { return newValue;
// removed the old value as expected
return null;
} }
else if ((oldValue = putIfAbsent(key, newValue)) == null)
// some other value replaced old value. try again. return newValue;
oldValue = get(key); else continue haveOldValue;
} else { } else if (oldValue == null || remove(key, oldValue)) {
// nothing to do. Leave things as they were.
return null; return null;
} }
} else { continue retry;
// 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.
}
} }
} }
} }
@ -471,21 +441,25 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* *
* @implSpec * @implSpec
* The default implementation is equivalent to performing the following * The default implementation is equivalent to performing the following
* steps for this {@code map}, then returning the current value or * steps for this {@code map}:
* {@code null} if absent:
* *
* <pre> {@code * <pre> {@code
* V oldValue = map.get(key); * for (;;) {
* V newValue = (oldValue == null) ? value : * V oldValue = map.get(key);
* remappingFunction.apply(oldValue, value); * if (oldValue != null) {
* if (newValue == null) * V newValue = remappingFunction.apply(oldValue, value);
* map.remove(key); * if (newValue != null) {
* else * if (map.replace(key, oldValue, newValue))
* map.put(key, newValue);}</pre> * return newValue;
* * } else if (map.remove(key, oldValue)) {
* <p>The default implementation may retry these steps when multiple * return null;
* threads attempt updates including potentially calling the remapping * }
* function multiple times. * } else if (map.putIfAbsent(key, value) == null) {
* return value;
* }
* }}</pre>
* When multiple threads attempt updates, map operations and the
* remapping function may be called multiple times.
* *
* <p>This implementation assumes that the ConcurrentMap cannot contain null * <p>This implementation assumes that the ConcurrentMap cannot contain null
* values and {@code get()} returning null unambiguously means the key is * values and {@code get()} returning null unambiguously means the key is
@ -495,6 +469,7 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
* @throws UnsupportedOperationException {@inheritDoc} * @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc} * @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc} * @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @since 1.8 * @since 1.8
*/ */
@Override @Override
@ -502,20 +477,23 @@ public interface ConcurrentMap<K,V> extends Map<K,V> {
BiFunction<? super V, ? super V, ? extends V> remappingFunction) { BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction); Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value); Objects.requireNonNull(value);
V oldValue = get(key); retry: for (;;) {
for (;;) { V oldValue = get(key);
if (oldValue != null) { // if putIfAbsent fails, opportunistically use its return value
V newValue = remappingFunction.apply(oldValue, value); haveOldValue: for (;;) {
if (newValue != null) { if (oldValue != null) {
if (replace(key, oldValue, newValue)) V newValue = remappingFunction.apply(oldValue, value);
return newValue; if (newValue != null) {
} else if (remove(key, oldValue)) { if (replace(key, oldValue, newValue))
return null; return newValue;
} } else if (remove(key, oldValue)) {
oldValue = get(key); return null;
} else { }
if ((oldValue = putIfAbsent(key, value)) == null) { continue retry;
return value; } else {
if ((oldValue = putIfAbsent(key, value)) == null)
return value;
continue haveOldValue;
} }
} }
} }

View File

@ -48,11 +48,14 @@ 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.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import org.testng.annotations.DataProvider; import org.testng.annotations.DataProvider;
import static java.util.Objects.requireNonNull;
import static org.testng.Assert.fail; import static org.testng.Assert.fail;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue; import static org.testng.Assert.assertTrue;
@ -533,6 +536,191 @@ public class Defaults {
"Should throw NPE"); "Should throw NPE");
} }
/** A function that flipflops between running two other functions. */
static <T,U,V> BiFunction<T,U,V> twoStep(AtomicBoolean b,
BiFunction<T,U,V> first,
BiFunction<T,U,V> second) {
return (t, u) -> {
boolean bb = b.get();
try {
return (b.get() ? first : second).apply(t, u);
} finally {
b.set(!bb);
}};
}
/**
* Simulates races by modifying the map within the mapping function.
*/
@Test
public void testConcurrentMap_computeIfAbsent_racy() {
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>();
final Long two = 2L;
Function<Long,Long> f, g;
// race not detected if function returns null
f = (k) -> { map.put(two, 42L); return null; };
assertNull(map.computeIfAbsent(two, f));
assertEquals(42L, (long)map.get(two));
map.clear();
f = (k) -> { map.put(two, 42L); return 86L; };
assertEquals(42L, (long)map.computeIfAbsent(two, f));
assertEquals(42L, (long)map.get(two));
// mapping function ignored if value already exists
map.put(two, 99L);
assertEquals(99L, (long)map.computeIfAbsent(two, f));
assertEquals(99L, (long)map.get(two));
}
/**
* Simulates races by modifying the map within the remapping function.
*/
@Test
public void testConcurrentMap_computeIfPresent_racy() {
final AtomicBoolean b = new AtomicBoolean(true);
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>();
final Long two = 2L;
BiFunction<Long,Long,Long> f, g;
for (Long val : new Long[] { null, 86L }) {
map.clear();
// Function not invoked if no mapping exists
f = (k, v) -> { map.put(two, 42L); return val; };
assertNull(map.computeIfPresent(two, f));
assertNull(map.get(two));
map.put(two, 42L);
f = (k, v) -> { map.put(two, 86L); return val; };
g = (k, v) -> {
assertSame(two, k);
assertEquals(86L, (long)v);
return null;
};
assertNull(map.computeIfPresent(two, twoStep(b, f, g)));
assertFalse(map.containsKey(two));
assertTrue(b.get());
map.put(two, 42L);
f = (k, v) -> { map.put(two, 86L); return val; };
g = (k, v) -> {
assertSame(two, k);
assertEquals(86L, (long)v);
return 99L;
};
assertEquals(99L, (long)map.computeIfPresent(two, twoStep(b, f, g)));
assertTrue(map.containsKey(two));
assertTrue(b.get());
}
}
@Test
public void testConcurrentMap_compute_simple() {
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>();
BiFunction<Long,Long,Long> fun = (k, v) -> ((v == null) ? 0L : k + v);
assertEquals(Long.valueOf(0L), map.compute(3L, fun));
assertEquals(Long.valueOf(3L), map.compute(3L, fun));
assertEquals(Long.valueOf(6L), map.compute(3L, fun));
assertNull(map.compute(3L, (k, v) -> null));
assertTrue(map.isEmpty());
assertEquals(Long.valueOf(0L), map.compute(new Long(3L), fun));
assertEquals(Long.valueOf(3L), map.compute(new Long(3L), fun));
assertEquals(Long.valueOf(6L), map.compute(new Long(3L), fun));
assertNull(map.compute(3L, (k, v) -> null));
assertTrue(map.isEmpty());
}
/**
* Simulates races by modifying the map within the remapping function.
*/
@Test
public void testConcurrentMap_compute_racy() {
final AtomicBoolean b = new AtomicBoolean(true);
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>();
final Long two = 2L;
BiFunction<Long,Long,Long> f, g;
// null -> null is a no-op; race not detected
f = (k, v) -> { map.put(two, 42L); return null; };
assertNull(map.compute(two, f));
assertEquals(42L, (long)map.get(two));
for (Long val : new Long[] { null, 86L }) {
map.clear();
f = (k, v) -> { map.put(two, 42L); return 86L; };
g = (k, v) -> {
assertSame(two, k);
assertEquals(42L, (long)v);
return k + v;
};
assertEquals(44L, (long)map.compute(two, twoStep(b, f, g)));
assertEquals(44L, (long)map.get(two));
assertTrue(b.get());
f = (k, v) -> { map.remove(two); return val; };
g = (k, v) -> {
assertSame(two, k);
assertNull(v);
return 44L;
};
assertEquals(44L, (long)map.compute(two, twoStep(b, f, g)));
assertEquals(44L, (long)map.get(two));
assertTrue(map.containsKey(two));
assertTrue(b.get());
f = (k, v) -> { map.remove(two); return val; };
g = (k, v) -> {
assertSame(two, k);
assertNull(v);
return null;
};
assertNull(map.compute(two, twoStep(b, f, g)));
assertNull(map.get(two));
assertFalse(map.containsKey(two));
assertTrue(b.get());
}
}
/**
* Simulates races by modifying the map within the remapping function.
*/
@Test
public void testConcurrentMap_merge_racy() {
final AtomicBoolean b = new AtomicBoolean(true);
final ConcurrentMap<Long,Long> map = new ImplementsConcurrentMap<>();
final Long two = 2L;
BiFunction<Long,Long,Long> f, g;
for (Long val : new Long[] { null, 86L }) {
map.clear();
f = (v, w) -> { throw new AssertionError(); };
assertEquals(99L, (long)map.merge(two, 99L, f));
assertEquals(99L, (long)map.get(two));
f = (v, w) -> { map.put(two, 42L); return val; };
g = (v, w) -> {
assertEquals(42L, (long)v);
assertEquals(3L, (long)w);
return v + w;
};
assertEquals(45L, (long)map.merge(two, 3L, twoStep(b, f, g)));
assertEquals(45L, (long)map.get(two));
assertTrue(b.get());
f = (v, w) -> { map.remove(two); return val; };
g = (k, v) -> { throw new AssertionError(); };
assertEquals(55L, (long)map.merge(two, 55L, twoStep(b, f, g)));
assertEquals(55L, (long)map.get(two));
assertTrue(map.containsKey(two));
assertFalse(b.get()); b.set(true);
}
}
public enum IntegerEnum { public enum IntegerEnum {
e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
@ -838,13 +1026,13 @@ public class Defaults {
protected ExtendsAbstractMap(M map) { this.map = map; } protected ExtendsAbstractMap(M map) { this.map = map; }
public Set<Map.Entry<K,V>> entrySet() { @Override public Set<Map.Entry<K,V>> entrySet() {
return new AbstractSet<Map.Entry<K,V>>() { return new AbstractSet<Map.Entry<K,V>>() {
public int size() { @Override public int size() {
return map.size(); return map.size();
} }
public Iterator<Map.Entry<K,V>> iterator() { @Override public Iterator<Map.Entry<K,V>> iterator() {
final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator(); final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator();
return new Iterator<Map.Entry<K,V>>() { return new Iterator<Map.Entry<K,V>>() {
public boolean hasNext() { return source.hasNext(); } public boolean hasNext() { return source.hasNext(); }
@ -853,20 +1041,20 @@ public class Defaults {
}; };
} }
public boolean add(Map.Entry<K,V> e) { @Override public boolean add(Map.Entry<K,V> e) {
return map.entrySet().add(e); return map.entrySet().add(e);
} }
}; };
} }
public V put(K key, V value) { @Override public V put(K key, V value) {
return map.put(key, value); return map.put(key, value);
} }
} }
/** /**
* A simple mutable concurrent map implementation that provides only default * A simple mutable concurrent map implementation that provides only default
* implementations of all methods. ie. none of the ConcurrentMap interface * implementations of all methods, i.e. none of the ConcurrentMap interface
* default methods have overridden implementations. * default methods have overridden implementations.
* *
* @param <K> Type of keys * @param <K> Type of keys
@ -875,14 +1063,26 @@ public class Defaults {
public static class ImplementsConcurrentMap<K,V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> { public static class ImplementsConcurrentMap<K,V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> {
public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); } public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); }
// ConcurrentMap reabstracts these methods // ConcurrentMap reabstracts these methods.
//
// Unlike ConcurrentHashMap, we have zero tolerance for null values.
public V replace(K k, V v) { return map.replace(k, v); }; @Override public V replace(K k, V v) {
return map.replace(requireNonNull(k), requireNonNull(v));
}
public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); }; @Override public boolean replace(K k, V v, V vv) {
return map.replace(requireNonNull(k),
requireNonNull(v),
requireNonNull(vv));
}
public boolean remove(Object k, Object v) { return map.remove(k, v); } @Override public boolean remove(Object k, Object v) {
return map.remove(requireNonNull(k), requireNonNull(v));
}
public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); } @Override public V putIfAbsent(K k, V v) {
return map.putIfAbsent(requireNonNull(k), requireNonNull(v));
}
} }
} }