8004518: Add in-place operations to Map
8010122: Add defaults for ConcurrentMap operations to Map
Reviewed-by: darcy, briangoetz, mduigou, dholmes, ulfzibis
Contributed-by: Doug Lea <dl@cs.oswego.edu>, Henry Jen <henry.jen@oracle.com>, Akhil Arora <akhil.arora@oracle.com>, Peter Levart <peter.levart@gmail.com>, Mike Duigou <mike.duigou@oracle.com>
--- a/jdk/src/share/classes/java/util/Collections.java Tue Apr 16 13:26:30 2013 +0100
+++ b/jdk/src/share/classes/java/util/Collections.java Tue Apr 16 11:17:19 2013 -0700
@@ -28,6 +28,9 @@
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
/**
* This class consists exclusively of static methods that operate on or return
@@ -264,8 +267,7 @@
}
private static <T>
- int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key)
- {
+ int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
@@ -441,21 +443,21 @@
/**
* Randomly permutes the specified list using a default source of
* randomness. All permutations occur with approximately equal
- * likelihood.<p>
+ * likelihood.
*
- * The hedge "approximately" is used in the foregoing description because
+ * <p>The hedge "approximately" is used in the foregoing description because
* default source of randomness is only approximately an unbiased source
* of independently chosen bits. If it were a perfect source of randomly
* chosen bits, then the algorithm would choose permutations with perfect
- * uniformity.<p>
+ * uniformity.
*
- * This implementation traverses the list backwards, from the last element
- * up to the second, repeatedly swapping a randomly selected element into
- * the "current position". Elements are randomly selected from the
+ * <p>This implementation traverses the list backwards, from the last
+ * element up to the second, repeatedly swapping a randomly selected element
+ * into the "current position". Elements are randomly selected from the
* portion of the list that runs from the first element to the current
- * position, inclusive.<p>
+ * position, inclusive.
*
- * This method runs in linear time. If the specified list does not
+ * <p>This method runs in linear time. If the specified list does not
* implement the {@link RandomAccess} interface and is large, this
* implementation dumps the specified list into an array before shuffling
* it, and dumps the shuffled array back into the list. This avoids the
@@ -469,9 +471,10 @@
public static void shuffle(List<?> list) {
Random rnd = r;
if (rnd == null)
- r = rnd = new Random();
+ r = rnd = new Random(); // harmless race.
shuffle(list, rnd);
}
+
private static Random r;
/**
@@ -1391,6 +1394,67 @@
public int hashCode() {return m.hashCode();}
public String toString() {return m.toString();}
+ // Override default methods in Map
+ @Override
+ @SuppressWarnings("unchecked")
+ public V getOrDefault(Object k, V defaultValue) {
+ // Safe cast as we don't change the value
+ return ((Map<K, V>)m).getOrDefault(k, defaultValue);
+ }
+
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ m.forEach(action);
+ }
+
+ @Override
+ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V merge(K key, V value,
+ BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
/**
* We need this class in addition to UnmodifiableSet as
* Map.Entries themselves permit modification of the backing Map
@@ -1590,9 +1654,9 @@
* </pre>
* Failure to follow this advice may result in non-deterministic behavior.
*
- * <p>The returned collection does <i>not</i> pass the <tt>hashCode</tt>
- * and <tt>equals</tt> operations through to the backing collection, but
- * relies on <tt>Object</tt>'s equals and hashCode methods. This is
+ * <p>The returned collection does <i>not</i> pass the {@code hashCode}
+ * and {@code equals} operations through to the backing collection, but
+ * relies on {@code Object}'s equals and hashCode methods. This is
* necessary to preserve the contracts of these operations in the case
* that the backing collection is a set or a list.<p>
*
@@ -2107,6 +2171,57 @@
public String toString() {
synchronized (mutex) {return m.toString();}
}
+
+ // Override default methods in Map
+ @Override
+ public V getOrDefault(Object k, V defaultValue) {
+ synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
+ }
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ synchronized (mutex) {m.forEach(action);}
+ }
+ @Override
+ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ synchronized (mutex) {m.replaceAll(function);}
+ }
+ @Override
+ public V putIfAbsent(K key, V value) {
+ synchronized (mutex) {return m.putIfAbsent(key, value);}
+ }
+ @Override
+ public boolean remove(Object key, Object value) {
+ synchronized (mutex) {return m.remove(key, value);}
+ }
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ synchronized (mutex) {return m.replace(key, oldValue, newValue);}
+ }
+ @Override
+ public V replace(K key, V value) {
+ synchronized (mutex) {return m.replace(key, value);}
+ }
+ @Override
+ public V computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction) {
+ synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
+ }
+ @Override
+ public V computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
+ }
+ @Override
+ public V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ synchronized (mutex) {return m.compute(key, remappingFunction);}
+ }
+ @Override
+ public V merge(K key, V value,
+ BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ synchronized (mutex) {return m.merge(key, value, remappingFunction);}
+ }
+
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
@@ -2326,6 +2441,8 @@
}
public Iterator<E> iterator() {
+ // JDK-6363904 - unwrapped iterator could be typecast to
+ // ListIterator with unsafe set()
final Iterator<E> it = c.iterator();
return new Iterator<E>() {
public boolean hasNext() { return it.hasNext(); }
@@ -2652,7 +2769,7 @@
public List<E> subList(int fromIndex, int toIndex) {
return new CheckedRandomAccessList<>(
- list.subList(fromIndex, toIndex), type);
+ list.subList(fromIndex, toIndex), type);
}
}
@@ -2717,14 +2834,24 @@
throw new ClassCastException(badValueMsg(value));
}
+ private BiFunction<? super K, ? super V, ? extends V> typeCheck(
+ BiFunction<? super K, ? super V, ? extends V> func) {
+ Objects.requireNonNull(func);
+ return (k, v) -> {
+ V newValue = func.apply(k, v);
+ typeCheck(k, newValue);
+ return newValue;
+ };
+ }
+
private String badKeyMsg(Object key) {
return "Attempt to insert " + key.getClass() +
- " key into map with key type " + keyType;
+ " key into map with key type " + keyType;
}
private String badValueMsg(Object value) {
return "Attempt to insert " + value.getClass() +
- " value into map with value type " + valueType;
+ " value into map with value type " + valueType;
}
CheckedMap(Map<K, V> m, Class<K> keyType, Class<V> valueType) {
@@ -2768,7 +2895,7 @@
Object v = e.getValue();
typeCheck(k, v);
checked.add(
- new AbstractMap.SimpleImmutableEntry<>((K) k, (V) v));
+ new AbstractMap.SimpleImmutableEntry<>((K)k, (V)v));
}
for (Map.Entry<K,V> e : checked)
m.put(e.getKey(), e.getValue());
@@ -2782,6 +2909,74 @@
return entrySet;
}
+ // Override default methods in Map
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ m.forEach(action);
+ }
+
+ @Override
+ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ m.replaceAll(typeCheck(function));
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ typeCheck(key, value);
+ return m.putIfAbsent(key, value);
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ return m.remove(key, value);
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ typeCheck(key, newValue);
+ return m.replace(key, oldValue, newValue);
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ typeCheck(key, value);
+ return m.replace(key, value);
+ }
+
+ @Override
+ public V computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction) {
+ Objects.requireNonNull(mappingFunction);
+ return m.computeIfAbsent(key, k -> {
+ V value = mappingFunction.apply(k);
+ typeCheck(k, value);
+ return value;
+ });
+ }
+
+ @Override
+ public V computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return m.computeIfPresent(key, typeCheck(remappingFunction));
+ }
+
+ @Override
+ public V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ return m.compute(key, typeCheck(remappingFunction));
+ }
+
+ @Override
+ public V merge(K key, V value,
+ BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+ return m.merge(key, value, (v1, v2) -> {
+ V newValue = remappingFunction.apply(v1, v2);
+ typeCheck(null, newValue);
+ return newValue;
+ });
+ }
+
/**
* We need this class in addition to CheckedSet as Map.Entry permits
* modification of the backing Map via the setValue operation. This
@@ -3456,6 +3651,67 @@
public int hashCode() {return 0;}
+ // Override default methods in Map
+ @Override
+ @SuppressWarnings("unchecked")
+ public V getOrDefault(Object k, V defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ Objects.requireNonNull(action);
+ }
+
+ @Override
+ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ Objects.requireNonNull(function);
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V merge(K key, V value,
+ BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
// Preserves singleton property
private Object readResolve() {
return EMPTY_MAP;
@@ -3619,6 +3875,65 @@
return values;
}
+ // Override default methods in Map
+ @Override
+ public V getOrDefault(Object key, V defaultValue) {
+ return eq(key, k) ? v : defaultValue;
+ }
+
+ @Override
+ public void forEach(BiConsumer<? super K, ? super V> action) {
+ action.accept(k, v);
+ }
+
+ @Override
+ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V merge(K key, V value,
+ BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
}
// Miscellaneous
--- a/jdk/src/share/classes/java/util/HashMap.java Tue Apr 16 13:26:30 2013 +0100
+++ b/jdk/src/share/classes/java/util/HashMap.java Tue Apr 16 11:17:19 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,11 @@
*/
package java.util;
+
import java.io.*;
+import java.util.function.Consumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
/**
* Hash table based implementation of the <tt>Map</tt> interface. This
@@ -376,6 +380,13 @@
return null == entry ? null : entry.getValue();
}
+ @Override
+ public V getOrDefault(Object key, V defaultValue) {
+ Entry<K,V> entry = getEntry(key);
+
+ return (entry == null) ? defaultValue : entry.getValue();
+ }
+
/**
* Returns <tt>true</tt> if this map contains a mapping for the
* specified key.
@@ -603,6 +614,261 @@
return (e == null ? null : e.value);
}
+ // optimized implementations of default methods in Map
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ if (table == EMPTY_TABLE) {
+ inflateTable(threshold);
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)table[i];
+ for(; e != null; e = e.next) {
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ if(e.value != null) {
+ return e.value;
+ }
+ e.value = value;
+ modCount++;
+ e.recordAccess(this);
+ return null;
+ }
+ }
+
+ modCount++;
+ addEntry(hash, key, value, i);
+ return null;
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ if (isEmpty()) {
+ return false;
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> prev = (Entry<K,V>)table[i];
+ Entry<K,V> e = prev;
+
+ while (e != null) {
+ Entry<K,V> next = e.next;
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ if (!Objects.equals(e.value, value)) {
+ return false;
+ }
+ modCount++;
+ size--;
+ if (prev == e)
+ table[i] = next;
+ else
+ prev.next = next;
+ e.recordRemoval(this);
+ return true;
+ }
+ prev = e;
+ e = next;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ if (isEmpty()) {
+ return false;
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)table[i];
+ for (; e != null; e = e.next) {
+ if (e.hash == hash && Objects.equals(e.key, key) && Objects.equals(e.value, oldValue)) {
+ e.value = newValue;
+ e.recordAccess(this);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ if (isEmpty()) {
+ return null;
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)table[i];
+ for (; e != null; e = e.next) {
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ V oldValue = e.value;
+ e.value = value;
+ e.recordAccess(this);
+ return oldValue;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ if (table == EMPTY_TABLE) {
+ inflateTable(threshold);
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)table[i];
+ for (; e != null; e = e.next) {
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ V oldValue = e.value;
+ return oldValue == null ? (e.value = mappingFunction.apply(key)) : oldValue;
+ }
+ }
+
+ V newValue = mappingFunction.apply(key);
+ if (newValue != null) {
+ modCount++;
+ addEntry(hash, key, newValue, i);
+ }
+
+ return newValue;
+ }
+
+ @Override
+ public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ if (isEmpty()) {
+ return null;
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> prev = (Entry<K,V>)table[i];
+ Entry<K,V> e = prev;
+
+ while (e != null) {
+ Entry<K,V> next = e.next;
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ V oldValue = e.value;
+ if (oldValue == null)
+ break;
+ V newValue = remappingFunction.apply(key, oldValue);
+ modCount++;
+ if (newValue == null) {
+ size--;
+ if (prev == e)
+ table[i] = next;
+ else
+ prev.next = next;
+ e.recordRemoval(this);
+ } else {
+ e.value = newValue;
+ e.recordAccess(this);
+ }
+ return newValue;
+ }
+ prev = e;
+ e = next;
+ }
+
+ return null;
+ }
+
+ @Override
+ public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ if (table == EMPTY_TABLE) {
+ inflateTable(threshold);
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> prev = (Entry<K,V>)table[i];
+ Entry<K,V> e = prev;
+
+ while (e != null) {
+ Entry<K,V> next = e.next;
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ V oldValue = e.value;
+ V newValue = remappingFunction.apply(key, oldValue);
+ if (newValue != oldValue) {
+ modCount++;
+ if (newValue == null) {
+ size--;
+ if (prev == e)
+ table[i] = next;
+ else
+ prev.next = next;
+ e.recordRemoval(this);
+ } else {
+ e.value = newValue;
+ e.recordAccess(this);
+ }
+ }
+ return newValue;
+ }
+ prev = e;
+ e = next;
+ }
+
+ V newValue = remappingFunction.apply(key, null);
+ if (newValue != null) {
+ modCount++;
+ addEntry(hash, key, newValue, i);
+ }
+
+ return newValue;
+ }
+
+ @Override
+ public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ if (table == EMPTY_TABLE) {
+ inflateTable(threshold);
+ }
+ int hash = (key == null) ? 0 : hash(key);
+ int i = indexFor(hash, table.length);
+ @SuppressWarnings("unchecked")
+ Entry<K,V> prev = (Entry<K,V>)table[i];
+ Entry<K,V> e = prev;
+
+ while (e != null) {
+ Entry<K,V> next = e.next;
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ V oldValue = e.value;
+ V newValue = remappingFunction.apply(oldValue, value);
+ modCount++;
+ if (newValue == null) {
+ size--;
+ if (prev == e)
+ table[i] = next;
+ else
+ prev.next = next;
+ e.recordRemoval(this);
+ } else {
+ e.value = newValue;
+ e.recordAccess(this);
+ }
+ return newValue;
+ }
+ prev = e;
+ e = next;
+ }
+
+ if (value != null) {
+ modCount++;
+ addEntry(hash, key, value, i);
+ }
+
+ return value;
+ }
+
+ // end of optimized implementations of default methods in Map
+
/**
* Removes and returns the entry associated with the specified key
* in the HashMap. Returns null if the HashMap contains no mapping
@@ -697,8 +963,8 @@
return containsNullValue();
Entry<?,?>[] tab = table;
- for (int i = 0; i < tab.length ; i++)
- for (Entry<?,?> e = tab[i] ; e != null ; e = e.next)
+ for (int i = 0; i < tab.length; i++)
+ for (Entry<?,?> e = tab[i]; e != null; e = e.next)
if (value.equals(e.value))
return true;
return false;
@@ -709,8 +975,8 @@
*/
private boolean containsNullValue() {
Entry<?,?>[] tab = table;
- for (int i = 0; i < tab.length ; i++)
- for (Entry<?,?> e = tab[i] ; e != null ; e = e.next)
+ for (int i = 0; i < tab.length; i++)
+ for (Entry<?,?> e = tab[i]; e != null; e = e.next)
if (e.value == null)
return true;
return false;
--- a/jdk/src/share/classes/java/util/Hashtable.java Tue Apr 16 13:26:30 2013 +0100
+++ b/jdk/src/share/classes/java/util/Hashtable.java Tue Apr 16 11:17:19 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,11 @@
*/
package java.util;
+
import java.io.*;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.BiFunction;
/**
* This class implements a hash table, which maps keys to values. Any
@@ -455,6 +459,26 @@
}
}
+ private void addEntry(int hash, K key, V value, int index) {
+ modCount++;
+
+ Entry<?,?> tab[] = table;
+ if (count >= threshold) {
+ // Rehash the table if the threshold is exceeded
+ rehash();
+
+ tab = table;
+ hash = hash(key);
+ index = (hash & 0x7FFFFFFF) % tab.length;
+ }
+
+ // Creates the new entry.
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>) tab[index];
+ tab[index] = new Entry<>(hash, key, value, e);
+ count++;
+ }
+
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
@@ -492,21 +516,7 @@
}
}
- modCount++;
- if (count >= threshold) {
- // Rehash the table if the threshold is exceeded
- rehash();
-
- tab = table;
- hash = hash(key);
- index = (hash & 0x7FFFFFFF) % tab.length;
- }
-
- // Creates the new entry.
- @SuppressWarnings("unchecked")
- Entry<K,V> e = (Entry<K,V>)tab[index];
- tab[index] = new Entry<>(hash, key, value, e);
- count++;
+ addEntry(hash, key, value, index);
return null;
}
@@ -892,6 +902,239 @@
return h;
}
+ @Override
+ public synchronized V getOrDefault(Object key, V defaultValue) {
+ V result = get(key);
+ return (null == result) ? defaultValue : result;
+ }
+
+ @Override
+ public synchronized void forEach(BiConsumer<? super K, ? super V> action) {
+ Objects.requireNonNull(action); // explicit check required in case
+ // table is empty.
+ Entry<?,?>[] tab = table;
+ for (Entry<?,?> entry : tab) {
+ while (entry != null) {
+ action.accept((K)entry.key, (V)entry.value);
+ entry = entry.next;
+ }
+ }
+ }
+
+ @Override
+ public synchronized void replaceAll(
+ BiFunction<? super K, ? super V, ? extends V> function) {
+ Map.super.replaceAll(function);
+ }
+
+ @Override
+ public synchronized V putIfAbsent(K key, V value) {
+ Objects.requireNonNull(value);
+
+ // Makes sure the key is not already in the hashtable.
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> entry = (Entry<K,V>)tab[index];
+ for (; entry != null; entry = entry.next) {
+ if ((entry.hash == hash) && entry.key.equals(key)) {
+ V old = entry.value;
+ if (old == null) {
+ entry.value = value;
+ }
+ return old;
+ }
+ }
+
+ addEntry(hash, key, value, index);
+ return null;
+ }
+
+ @Override
+ public synchronized boolean remove(Object key, Object value) {
+ Objects.requireNonNull(value);
+
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)tab[index];
+ for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ e.value = null;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public synchronized boolean replace(K key, V oldValue, V newValue) {
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)tab[index];
+ for (; e != null; e = e.next) {
+ if ((e.hash == hash) && e.key.equals(key)) {
+ if (e.value.equals(oldValue)) {
+ e.value = newValue;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public synchronized V replace(K key, V value) {
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)tab[index];
+ for (; e != null; e = e.next) {
+ if ((e.hash == hash) && e.key.equals(key)) {
+ V oldValue = e.value;
+ e.value = value;
+ return oldValue;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ Objects.requireNonNull(mappingFunction);
+
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)tab[index];
+ for (; e != null; e = e.next) {
+ if (e.hash == hash && e.key.equals(key)) {
+ // Hashtable not accept null value
+ return e.value;
+ }
+ }
+
+ V newValue = mappingFunction.apply(key);
+ if (newValue != null) {
+ addEntry(hash, key, newValue, index);
+ }
+
+ return newValue;
+ }
+
+ @Override
+ public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)tab[index];
+ for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if (e.hash == hash && e.key.equals(key)) {
+ V newValue = remappingFunction.apply(key, e.value);
+ if (newValue == null) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ } else {
+ e.value = newValue;
+ }
+ return newValue;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)tab[index];
+ for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ V newValue = remappingFunction.apply(key, e.value);
+ if (newValue == null) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ } else {
+ e.value = newValue;
+ }
+ return newValue;
+ }
+ }
+
+ V newValue = remappingFunction.apply(key, null);
+ if (newValue != null) {
+ addEntry(hash, key, newValue, index);
+ }
+
+ return newValue;
+ }
+
+ @Override
+ public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+
+ Entry<?,?> tab[] = table;
+ int hash = hash(key);
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ Entry<K,V> e = (Entry<K,V>)tab[index];
+ for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if (e.hash == hash && e.key.equals(key)) {
+ V newValue = remappingFunction.apply(e.value, value);
+ if (newValue == null) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ } else {
+ e.value = newValue;
+ }
+ return newValue;
+ }
+ }
+
+ if (value != null) {
+ addEntry(hash, key, value, index);
+ }
+
+ return value;
+ }
+
/**
* Save the state of the Hashtable to a stream (i.e., serialize it).
*
--- a/jdk/src/share/classes/java/util/Map.java Tue Apr 16 13:26:30 2013 +0100
+++ b/jdk/src/share/classes/java/util/Map.java Tue Apr 16 11:17:19 2013 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,10 @@
package java.util;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
/**
* An object that maps keys to values. A map cannot contain duplicate keys;
* each key can map to at most one value.
@@ -475,4 +479,613 @@
*/
int hashCode();
+ // Defaultable methods
+
+ /**
+ * Returns the value to which the specified key is mapped,
+ * or {@code defaultValue} if this map contains no mapping
+ * for the key.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or
+ * {@code defaultValue} if this map contains no mapping for the key
+ * @throws ClassCastException if the key is of an inappropriate type for
+ * this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key is null and this map
+ * does not permit null keys
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ */
+ default V getOrDefault(Object key, V defaultValue) {
+ V v;
+ return (((v = get(key)) != null) || containsKey(key))
+ ? v
+ : defaultValue;
+ }
+
+ /**
+ * Performs the given action on each entry in this map, in the order entries
+ * are returned by an entry set iterator (which may be unspecified), until
+ * all entries have been processed or the action throws an {@code Exception}.
+ * Exceptions thrown by the action are relayed to the caller.
+ *
+ * <p>The default implementation should be overridden by implementations if
+ * they can provide a more performant implementation than an iterator-based
+ * one.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * 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}:
+ * <pre> {@code
+ * for ((Map.Entry<K, V> entry : map.entrySet())
+ * action.accept(entry.getKey(), entry.getValue());
+ * }</pre>
+ *
+ * @param action The action to be performed for each entry
+ * @throws NullPointerException if the specified action is null
+ * @throws ConcurrentModificationException if an entry is found to be
+ * removed during iteration
+ * @since 1.8
+ */
+ 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) {
+ throw new ConcurrentModificationException(ise);
+ }
+ action.accept(k, v);
+ }
+ }
+
+ /**
+ * Replaces each entry's value with the result of invoking the given
+ * function on that entry, in the order entries are returned by an entry
+ * set iterator, until all entries have been processed or the function
+ * throws an exception.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec
+ * <p>The default implementation is equivalent to, for this {@code map}:
+ * <pre> {@code
+ * for ((Map.Entry<K, V> entry : map.entrySet())
+ * entry.setValue(function.apply(entry.getKey(), entry.getValue()));
+ * }</pre>
+ *
+ * @param function the function to apply to each entry
+ * @throws UnsupportedOperationException if the {@code set} operation
+ * is not supported by this map's entry set iterator.
+ * @throws ClassCastException if the class of a replacement value
+ * prevents it from being stored in this map
+ * @throws NullPointerException if the specified function is null, or the
+ * specified replacement value is null, and this map does not permit null
+ * values
+ * @throws ClassCastException if a replacement value is of an inappropriate
+ * type for this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if function or a replacement value is null,
+ * and this map does not permit null keys or values
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws IllegalArgumentException if some property of a replacement value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ConcurrentModificationException if an entry is found to be
+ * removed during iteration
+ * @since 1.8
+ */
+ default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ Objects.requireNonNull(function);
+ for (Map.Entry<K, V> entry : entrySet()) {
+ K k;
+ V v;
+ try {
+ k = entry.getKey();
+ v = entry.getValue();
+ } catch(IllegalStateException ise) {
+ throw new ConcurrentModificationException(ise);
+ }
+ entry.setValue(function.apply(k, v));
+ }
+ }
+
+ /**
+ * If the specified key is not already associated with a value (or is mapped
+ * to {@code null}) associates it with the given value and returns
+ * {@code null}, else returns the current value.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * 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}:
+ *
+ * <pre> {@code
+ * if (map.get(key) == null)
+ * return map.put(key, value);
+ * else
+ * return map.get(key);
+ * }</pre>
+ *
+ * @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
+ * {@code 1} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
+ * if the implementation supports null values.)
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (<a href="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="Collection.html#optional-restrictions">optional</a>)
+ * @throws ConcurrentModificationException if a modification of the map is
+ * detected during insertion of the value.
+ * @since 1.8
+ */
+ default V putIfAbsent(K key, V value) {
+ V v = get(key);
+ if (v == null) {
+ if (put(key, value) != null) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ return v;
+ }
+
+ /**
+ * Removes the entry for the specified key only if it is currently
+ * mapped to the specified value.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * 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}:
+ *
+ * <pre> {@code
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+ * map.remove(key);
+ * return true;
+ * } else
+ * return false;
+ * }</pre>
+ *
+ * @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
+ * @throws UnsupportedOperationException if the {@code remove} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @since 1.8
+ */
+ default boolean remove(Object key, Object value) {
+ Object curValue = get(key);
+ if (!Objects.equals(curValue, value) ||
+ (curValue == null && !containsKey(key))) {
+ return false;
+ }
+ remove(key);
+ return true;
+ }
+
+ /**
+ * Replaces the entry for the specified key only if currently
+ * mapped to the specified value.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * 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}:
+ *
+ * <pre> {@code
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+ * map.put(key, newValue);
+ * return true;
+ * } else
+ * return false;
+ * }</pre>
+ *
+ * @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
+ * @return {@code true} if the value was replaced
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of a specified key or value
+ * prevents it from being stored in this map
+ * @throws NullPointerException if a specified key or value is null,
+ * and this map does not permit null keys or values
+ * @throws IllegalArgumentException if some property of a specified key
+ * or value prevents it from being stored in this map
+ * @since 1.8
+ */
+ default boolean replace(K key, V oldValue, V newValue) {
+ Object curValue = get(key);
+ if (!Objects.equals(curValue, oldValue) ||
+ (curValue == null && !containsKey(key))) {
+ return false;
+ }
+ put(key, newValue);
+ return true;
+ }
+
+ /**
+ * Replaces the entry for the specified key only if it is
+ * currently mapped to some value.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * 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}:
+ *
+ * <pre> {@code
+ * if (map.containsKey(key)) {
+ * return map.put(key, value);
+ * } else
+ * return null;
+ * }</pre>
+ *
+ * @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
+ * {@code null} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
+ * if the implementation supports null values.)
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * @since 1.8
+ */
+ default V replace(K key, V value) {
+ return containsKey(key) ? put(key, value) : null;
+ }
+
+ /**
+ * If the specified key is not already associated with a value (or
+ * is mapped to {@code null}), attempts to compute its value using
+ * the given mapping function and enters it into this map unless
+ * {@code null}.
+ *
+ * <p>If the function returns {@code null} no mapping is recorded. If
+ * the function itself throws an (unchecked) exception, the
+ * exception is rethrown, and no mapping is recorded. The most
+ * common usage is to construct a new object serving as an initial
+ * mapped value or memoized result, as in:
+ *
+ * <pre> {@code
+ * map.computeIfAbsent(key, k -> new Value(f(k)));
+ * }</pre>
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * 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.
+ *
+ * @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)
+ * map.putIfAbsent(key, newValue);
+ * }
+ * }</pre>
+ *
+ * @param key key with which the specified value is to be associated
+ * @param mappingFunction the function to compute a value
+ * @return the current (existing or computed) value associated with
+ * the specified key, or null if the computed value is null
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * mappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @since 1.8
+ */
+ default V computeIfAbsent(K key,
+ Function<? super K, ? extends V> mappingFunction) {
+ V v, newValue;
+ return ((v = get(key)) == null &&
+ (newValue = mappingFunction.apply(key)) != null &&
+ (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
+ }
+
+ /**
+ * If the value for the specified key is present and non-null, attempts to
+ * compute a new mapping given the key and its current mapped value.
+ *
+ * <p>If the function returns {@code null}, the mapping is removed. If the
+ * function itself throws an (unchecked) exception, the exception is
+ * rethrown, and the current mapping is left unchanged.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * 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.
+ *
+ * @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>
+ *
+ * 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
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @since 1.8
+ */
+ default V computeIfPresent(K key,
+ BiFunction<? super K, ? super V, ? extends V> 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;
+ }
+
+ /**
+ * Attempts to compute a mapping for the specified key and its
+ * current mapped value (or {@code null} if there is no current
+ * mapping). For example, to either create or append a {@code
+ * String msg} to a value mapping:
+ *
+ * <pre> {@code
+ * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))}</pre>
+ * (Method {@link #merge merge()} is often simpler to use for such purposes.)
+ *
+ * <p>If the function returns {@code null}, the mapping is removed (or
+ * remains absent if initially absent). If the function itself throws an
+ * (unchecked) exception, the exception is rethrown, and the current mapping
+ * is left unchanged.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * 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.
+ *
+ * @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>
+ *
+ * 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
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @since 1.8
+ */
+ default V compute(K key,
+ BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ V oldValue = get(key);
+ for (;;) {
+ V newValue = remappingFunction.apply(key, oldValue);
+ if (oldValue != null) {
+ if (newValue != null) {
+ if (replace(key, oldValue, newValue))
+ return newValue;
+ } else if (remove(key, oldValue)) {
+ return null;
+ }
+ oldValue = get(key);
+ } else {
+ if (newValue != null) {
+ if ((oldValue = putIfAbsent(key, newValue)) == null)
+ return newValue;
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+
+ /**
+ * If the specified key is not already associated with a value or is
+ * associated with null, associates it with the given value.
+ * Otherwise, replaces the value with the results of the given
+ * remapping function, or removes if the result is {@code null}. This
+ * method may be of use when combining multiple mapped values for a key.
+ * For example, to either create or append a {@code String msg} to a
+ * value mapping:
+ *
+ * <pre> {@code
+ * map.merge(key, msg, String::concat)
+ * }</pre>
+ *
+ * <p>If the function returns {@code null}, the mapping is removed (or
+ * remains absent if initially absent). If the function itself throws an
+ * (unchecked) exception, the exception is rethrown, and the current mapping
+ * is left unchanged.
+ *
+ * <p>The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * 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.
+ *
+ * @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, oldValue);
+ * else if (oldValue == null)
+ * map.putIfAbsent(key, newValue);
+ * else
+ * map.replace(key, oldValue, newValue);
+ * }</pre>
+ *
+ * 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
+ * @return the new value associated with the specified key, or null if none
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (<a href="Collection.html#optional-restrictions">optional</a>)
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @since 1.8
+ */
+ default V merge(K key, V value,
+ BiFunction<? super V, ? super V, ? extends V> 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)) {
+ return null;
+ }
+ oldValue = get(key);
+ } else {
+ if (value == null) {
+ return null;
+ }
+
+ if ((oldValue = putIfAbsent(key, value)) == null) {
+ return value;
+ }
+ }
+ }
+ }
}
--- a/jdk/src/share/classes/java/util/concurrent/ConcurrentMap.java Tue Apr 16 13:26:30 2013 +0100
+++ b/jdk/src/share/classes/java/util/concurrent/ConcurrentMap.java Tue Apr 16 11:17:19 2013 -0700
@@ -38,7 +38,7 @@
/**
* A {@link java.util.Map} providing additional atomic
- * <tt>putIfAbsent</tt>, <tt>remove</tt>, and <tt>replace</tt> methods.
+ * {@code putIfAbsent}, {@code remove}, and {@code replace} methods.
*
* <p>Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
@@ -57,6 +57,21 @@
* @param <V> the type of mapped values
*/
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.
+ */
+ @Override
+ default V getOrDefault(Object key, V defaultValue) {
+ V v;
+ return ((v = get(key)) != null) ? v : defaultValue;
+ }
+
/**
* If the specified key is not already associated
* with a value, associate it with the given value.
@@ -91,7 +106,7 @@
* Removes the entry for a key only if currently mapped to a given value.
* This is equivalent to
* <pre> {@code
- * if (map.containsKey(key) && map.get(key).equals(value)) {
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
* map.remove(key);
* return true;
* } else
@@ -101,8 +116,8 @@
*
* @param key key with which the specified value is associated
* @param value value expected to be associated with the specified key
- * @return <tt>true</tt> if the value was removed
- * @throws UnsupportedOperationException if the <tt>remove</tt> operation
+ * @return {@code true} if the value was removed
+ * @throws UnsupportedOperationException if the {@code remove} operation
* is not supported by this map
* @throws ClassCastException if the key or value is of an inappropriate
* type for this map
@@ -117,7 +132,7 @@
* Replaces the entry for a key only if currently mapped to a given value.
* This is equivalent to
* <pre> {@code
- * if (map.containsKey(key) && map.get(key).equals(oldValue)) {
+ * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue)) {
* map.put(key, newValue);
* return true;
* } else
@@ -128,8 +143,8 @@
* @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
- * @return <tt>true</tt> if the value was replaced
- * @throws UnsupportedOperationException if the <tt>put</tt> operation
+ * @return {@code true} if the value was replaced
+ * @throws UnsupportedOperationException if the {@code put} operation
* is not supported by this map
* @throws ClassCastException if the class of a specified key or value
* prevents it from being stored in this map
@@ -154,11 +169,11 @@
* @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
- * <tt>null</tt> if there was no mapping for the key.
- * (A <tt>null</tt> return can also indicate that the map
- * previously associated <tt>null</tt> with the key,
+ * {@code null} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
* if the implementation supports null values.)
- * @throws UnsupportedOperationException if the <tt>put</tt> operation
+ * @throws UnsupportedOperationException if the {@code put} operation
* is not supported by this map
* @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Map/Defaults.java Tue Apr 16 11:17:19 2013 -0700
@@ -0,0 +1,594 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8010122 8004518
+ * @summary Test Map default methods
+ * @author Mike Duigou
+ * @run testng Defaults
+ */
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.function.Supplier;
+
+import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
+import static org.testng.Assert.fail;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+
+public class Defaults {
+
+ @Test(dataProvider = "Nulls Map<IntegerEnum,String>")
+ public void testGetOrDefaultNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertSame(map.get(null), map.getOrDefault(null, EXTRA_VALUE), "values should match");
+ }
+
+ @Test(dataProvider = "Map<IntegerEnum,String>")
+ public void testGetOrDefault(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(KEYS[1]), "expected key missing");
+ assertSame(map.get(KEYS[1]), map.getOrDefault(KEYS[1], EXTRA_VALUE), "values should match");
+ assertFalse(map.containsKey(EXTRA_KEY), "expected absent key");
+ assertSame(map.getOrDefault(EXTRA_KEY, EXTRA_VALUE), EXTRA_VALUE, "value not returned as default");
+ assertNull(map.getOrDefault(EXTRA_KEY, null), "null not returned as default");
+ }
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ public void testPutIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertNull(map.putIfAbsent(null, EXTRA_VALUE), "previous not null");
+ assertTrue(map.containsKey(null), "null key absent");
+ assertSame(map.get(null), EXTRA_VALUE, "unexpected value");
+ assertSame(map.putIfAbsent(null, null), EXTRA_VALUE, "previous not expected value");
+ assertTrue(map.containsKey(null), "null key absent");
+ assertSame(map.get(null), EXTRA_VALUE, "unexpected value");
+ assertSame(map.remove(null), EXTRA_VALUE, "removed unexpected value");
+
+ assertFalse(map.containsKey(null), description + ": key present after remove");
+ assertNull(map.putIfAbsent(null, null), "previous not null");
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertNull(map.putIfAbsent(null, EXTRA_VALUE), "previous not null");
+ assertSame(map.get(null), EXTRA_VALUE, "value not expected");
+ }
+
+ @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+ public void testPutIfAbsent(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(KEYS[1]));
+ Object expected = map.get(KEYS[1]);
+ assertTrue(null == expected || expected == VALUES[1]);
+ assertSame(map.putIfAbsent(KEYS[1], EXTRA_VALUE), expected);
+ assertSame(map.get(KEYS[1]), expected);
+
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertSame(map.putIfAbsent(EXTRA_KEY, EXTRA_VALUE), null);
+ assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+ }
+
+ @Test(dataProvider = "Nulls Map<IntegerEnum,String>")
+ public void testForEach(String description, Map<IntegerEnum, String> map) {
+ IntegerEnum[] EACH_KEY = new IntegerEnum[map.size()];
+
+ map.forEach((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));
+ });
+
+ assertEquals(KEYS, EACH_KEY);
+ }
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ public static void testRemoveNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertFalse(map.remove(null, EXTRA_VALUE), description);
+ assertTrue(map.containsKey(null));
+ assertNull(map.get(null));
+ assertTrue(map.remove(null, null));
+ assertFalse(map.containsKey(null));
+ assertNull(map.get(null));
+ assertFalse(map.remove(null, null));
+ }
+
+ @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+ public static void testRemove(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(KEYS[1]));
+ Object expected = map.get(KEYS[1]);
+ assertTrue(null == expected || expected == VALUES[1]);
+ assertFalse(map.remove(KEYS[1], EXTRA_VALUE), description);
+ assertSame(map.get(KEYS[1]), expected);
+ assertTrue(map.remove(KEYS[1], expected));
+ assertNull(map.get(KEYS[1]));
+ assertFalse(map.remove(KEYS[1], expected));
+
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertFalse(map.remove(EXTRA_KEY, EXTRA_VALUE));
+ }
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ public void testReplaceKVNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertSame(map.replace(null, EXTRA_VALUE), null);
+ assertSame(map.get(null), EXTRA_VALUE);
+ }
+
+ @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+ public void testReplaceKV(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(KEYS[1]));
+ Object expected = map.get(KEYS[1]);
+ assertTrue(null == expected || expected == VALUES[1]);
+ assertSame(map.replace(KEYS[1], EXTRA_VALUE), expected);
+ assertSame(map.get(KEYS[1]), EXTRA_VALUE);
+
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertNull(map.replace(EXTRA_KEY, EXTRA_VALUE));
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertNull(map.get(EXTRA_KEY));
+ assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
+ assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+ assertSame(map.replace(EXTRA_KEY, (String)expected), EXTRA_VALUE);
+ assertSame(map.get(EXTRA_KEY), expected);
+ }
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ public void testReplaceKVVNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertFalse(map.replace(null, EXTRA_VALUE, EXTRA_VALUE));
+ assertNull(map.get(null));
+ assertTrue(map.replace(null, null, EXTRA_VALUE));
+ assertSame(map.get(null), EXTRA_VALUE);
+ assertTrue(map.replace(null, EXTRA_VALUE, EXTRA_VALUE));
+ assertSame(map.get(null), EXTRA_VALUE);
+ }
+
+ @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+ public void testReplaceKVV(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(KEYS[1]));
+ Object expected = map.get(KEYS[1]);
+ assertTrue(null == expected || expected == VALUES[1]);
+ assertFalse(map.replace(KEYS[1], EXTRA_VALUE, EXTRA_VALUE));
+ assertSame(map.get(KEYS[1]), expected);
+ assertTrue(map.replace(KEYS[1], (String)expected, EXTRA_VALUE));
+ assertSame(map.get(KEYS[1]), EXTRA_VALUE);
+ assertTrue(map.replace(KEYS[1], EXTRA_VALUE, EXTRA_VALUE));
+ assertSame(map.get(KEYS[1]), EXTRA_VALUE);
+
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertFalse(map.replace(EXTRA_KEY, EXTRA_VALUE, EXTRA_VALUE));
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertNull(map.get(EXTRA_KEY));
+ assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
+ assertTrue(map.containsKey(EXTRA_KEY));
+ assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+ assertTrue(map.replace(EXTRA_KEY, EXTRA_VALUE, EXTRA_VALUE));
+ assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+ }
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ public void testComputeIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertSame(map.computeIfAbsent(null, (k) -> EXTRA_VALUE), EXTRA_VALUE, description);
+ assertSame(map.get(null), EXTRA_VALUE, description);
+ }
+
+ @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+ public void testComputeIfAbsent(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(KEYS[1]));
+ Object expected = map.get(KEYS[1]);
+ assertTrue(null == expected || expected == VALUES[1], description + String.valueOf(expected));
+ expected = (null == expected) ? EXTRA_VALUE : expected;
+ assertSame(map.computeIfAbsent(KEYS[1], (k) -> EXTRA_VALUE), expected, description);
+ assertSame(map.get(KEYS[1]), expected, description);
+
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertSame(map.computeIfAbsent(EXTRA_KEY, (k) -> EXTRA_VALUE), EXTRA_VALUE);
+ assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+ }
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ public void testComputeIfPresentNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null));
+ assertNull(map.get(null));
+ assertSame(map.computeIfPresent(null, (k, v) -> {
+ fail();
+ return EXTRA_VALUE;
+ }), null, description);
+ assertTrue(map.containsKey(null));
+ assertSame(map.get(null), null, description);
+ }
+
+ @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+ public void testComputeIfPresent(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));
+ Object expected = (null == value) ? null : EXTRA_VALUE;
+ assertSame(map.computeIfPresent(KEYS[1], (k, v) -> {
+ assertSame(v, value);
+ return EXTRA_VALUE;
+ }), expected, description);
+ assertSame(map.get(KEYS[1]), expected, description);
+
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertSame(map.computeIfPresent(EXTRA_KEY, (k, v) -> {
+ fail();
+ return EXTRA_VALUE;
+ }), null);
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertSame(map.get(EXTRA_KEY), null);
+ }
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ public void testComputeNulls(String description, Map<IntegerEnum, String> map) {
+ assertTrue(map.containsKey(null), "null key absent");
+ assertNull(map.get(null), "value not null");
+ assertSame(map.compute(null, (k, v) -> {
+ assertSame(k, null);
+ assertNull(v);
+ return EXTRA_VALUE;
+ }), EXTRA_VALUE, description);
+ assertTrue(map.containsKey(null));
+ assertSame(map.get(null), EXTRA_VALUE, description);
+ assertSame(map.remove(null), EXTRA_VALUE, "removed value not expected");
+ assertFalse(map.containsKey(null), "null key present");
+ assertSame(map.compute(null, (k, v) -> {
+ assertSame(k, null);
+ assertNull(v);
+ return null;
+ }), null, description);
+ }
+
+ @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+ public void testCompute(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.compute(KEYS[1], (k, v) -> {
+ assertSame(k, KEYS[1]);
+ assertSame(v, value);
+ return EXTRA_VALUE;
+ }), EXTRA_VALUE, description);
+ assertSame(map.get(KEYS[1]), EXTRA_VALUE, description);
+ assertNull(map.compute(KEYS[1], (k, v) -> {
+ assertSame(v, EXTRA_VALUE);
+ return null;
+ }), description);
+ assertFalse(map.containsKey(KEYS[1]));
+
+ assertFalse(map.containsKey(EXTRA_KEY));
+ assertSame(map.compute(EXTRA_KEY, (k, v) -> {
+ assertNull(v);
+ return EXTRA_VALUE;
+ }), EXTRA_VALUE);
+ assertTrue(map.containsKey(EXTRA_KEY));
+ assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+ }
+
+
+ @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+ 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 = "R/W Map<IntegerEnum,String>")
+ 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);
+ }
+
+ enum IntegerEnum {
+
+ e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
+ e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
+ e20, e21, e22, e23, e24, e25, e26, e27, e28, e29,
+ e30, e31, e32, e33, e34, e35, e36, e37, e38, e39,
+ e40, e41, e42, e43, e44, e45, e46, e47, e48, e49,
+ e50, e51, e52, e53, e54, e55, e56, e57, e58, e59,
+ e60, e61, e62, e63, e64, e65, e66, e67, e68, e69,
+ e70, e71, e72, e73, e74, e75, e76, e77, e78, e79,
+ e80, e81, e82, e83, e84, e85, e86, e87, e88, e89,
+ e90, e91, e92, e93, e94, e95, e96, e97, e98, e99,
+ EXTRA_KEY;
+ public static final int SIZE = values().length;
+ };
+ private static final int TEST_SIZE = IntegerEnum.SIZE - 1;
+ /**
+ * Realized keys ensure that there is always a hard ref to all test objects.
+ */
+ private static final IntegerEnum[] KEYS = new IntegerEnum[TEST_SIZE];
+ /**
+ * Realized values ensure that there is always a hard ref to all test
+ * objects.
+ */
+ private static final String[] VALUES = new String[TEST_SIZE];
+
+ static {
+ IntegerEnum[] keys = IntegerEnum.values();
+ for (int each = 0; each < TEST_SIZE; each++) {
+ KEYS[each] = keys[each];
+ VALUES[each] = String.valueOf(each);
+ }
+ }
+ private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY;
+ private static final String EXTRA_VALUE = String.valueOf(TEST_SIZE);
+
+ @DataProvider(name = "Map<IntegerEnum,String>", parallel = true)
+ public static Iterator<Object[]> allNullsMapProvider() {
+ return makeAllMaps().iterator();
+ }
+
+ @DataProvider(name = "Nulls Map<IntegerEnum,String>", parallel = true)
+ public static Iterator<Object[]> allMapProvider() {
+ return makeRWMaps(true).iterator();
+ }
+
+ @DataProvider(name = "R/W Map<IntegerEnum,String>", parallel = true)
+ public static Iterator<Object[]> rwMapProvider() {
+ return makeRWMapsNoNulls().iterator();
+ }
+
+ @DataProvider(name = "R/W Nulls Map<IntegerEnum,String>", parallel = true)
+ public static Iterator<Object[]> rwNullsMapProvider() {
+ return makeRWMaps(true).iterator();
+ }
+
+ private static Collection<Object[]> makeAllMapsNoNulls() {
+ Collection<Object[]> all = new ArrayList<>();
+
+ all.addAll(makeRWMaps(false));
+ all.addAll(makeRWNoNullsMaps());
+ all.addAll(makeROMaps(false));
+
+ return all;
+ }
+
+ private static Collection<Object[]> makeRWMapsNoNulls() {
+ Collection<Object[]> all = new ArrayList<>();
+
+ all.addAll(makeRWMaps(false));
+ all.addAll(makeRWNoNullsMaps());
+
+ return all;
+ }
+
+ private static Collection<Object[]> makeAllMaps() {
+ Collection<Object[]> all = new ArrayList<>();
+
+ all.addAll(makeROMaps(false));
+ all.addAll(makeRWMaps(false));
+ all.addAll(makeRWNoNullsMaps());
+ all.addAll(makeRWMaps(true));
+ all.addAll(makeROMaps(true));
+
+ return all;
+ }
+
+ private static Collection<Object[]> makeAllRWMaps() {
+ Collection<Object[]> all = new ArrayList<>();
+
+ all.addAll(makeRWMaps(false));
+ all.addAll(makeRWNoNullsMaps());
+ all.addAll(makeRWMaps(true));
+
+ return all;
+ }
+
+ private static Collection<Object[]> makeRWMaps(boolean nulls) {
+ return Arrays.asList(
+ new Object[]{"HashMap", makeMap(HashMap::new, nulls)},
+ new Object[]{"IdentityHashMap", makeMap(IdentityHashMap::new, nulls)},
+ new Object[]{"LinkedHashMap", makeMap(LinkedHashMap::new, nulls)},
+ new Object[]{"WeakHashMap", makeMap(WeakHashMap::new, nulls)},
+ new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(makeMap(HashMap::new, nulls), IntegerEnum.class, String.class)},
+ new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(makeMap(HashMap::new, nulls))},
+ new Object[]{"ExtendsAbstractMap", makeMap(ExtendsAbstractMap::new, nulls)});
+ }
+
+ private static Collection<Object[]> makeRWNoNullsMaps() {
+ return Arrays.asList(
+ // null hostile
+ new Object[]{"EnumMap", makeMap(() -> new EnumMap(IntegerEnum.class), false)},
+ new Object[]{"Hashtable", makeMap(Hashtable::new, false)},
+ new Object[]{"TreeMap", makeMap(TreeMap::new, false)},
+ new Object[]{"ConcurrentHashMap", makeMap(ConcurrentHashMap::new, false)},
+ new Object[]{"ConcurrentSkipListMap", makeMap(ConcurrentSkipListMap::new, false)},
+ new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(makeMap(ConcurrentHashMap::new, false), IntegerEnum.class, String.class)},
+ new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(makeMap(() -> new EnumMap(IntegerEnum.class), false))},
+ new Object[]{"ImplementsConcurrentMap", makeMap(ImplementsConcurrentMap::new, false)});
+ }
+
+ private static Collection<Object[]> makeROMaps(boolean nulls) {
+ return Arrays.asList(new Object[][]{
+ new Object[]{"Collections.unmodifiableMap(HashMap)", Collections.unmodifiableMap(makeMap(HashMap::new, nulls))}
+ });
+ }
+
+ private static Map<IntegerEnum, String> makeMap(Supplier<Map<IntegerEnum, String>> supplier, boolean nulls) {
+ Map<IntegerEnum, String> result = supplier.get();
+
+ for (int each = 0; each < TEST_SIZE; each++) {
+ if (nulls) {
+ result.put((each == 0) ? null : KEYS[each], null);
+ } else {
+ result.put(KEYS[each], VALUES[each]);
+ }
+ }
+
+ return result;
+ }
+
+ public interface Thrower<T extends Throwable> {
+
+ public void run() throws T;
+ }
+
+ public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable) {
+ assertThrows(thrower, throwable, null);
+ }
+
+ public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable, String message) {
+ Throwable result;
+ try {
+ thrower.run();
+ result = null;
+ } catch (Throwable caught) {
+ result = caught;
+ }
+
+ assertInstance(result, throwable,
+ (null != message)
+ ? message
+ : "Failed to throw " + throwable.getCanonicalName());
+ }
+
+ public static <T> void assertInstance(T actual, Class<? extends T> expected) {
+ assertInstance(expected.isInstance(actual), null);
+ }
+
+ public static <T> void assertInstance(T actual, Class<? extends T> expected, String message) {
+ assertTrue(expected.isInstance(actual), message);
+ }
+
+ /**
+ * A simple mutable map implementation that provides only default
+ * implementations of all methods. ie. none of the Map interface default
+ * methods have overridden implementations.
+ *
+ * @param <K> Type of keys
+ * @param <V> Type of values
+ */
+ public static class ExtendsAbstractMap<M extends Map<K,V>, K, V> extends AbstractMap<K, V> {
+
+ protected final M map;
+
+ public ExtendsAbstractMap() { this( (M) new HashMap<K,V>()); }
+
+ protected ExtendsAbstractMap(M map) { this.map = map; }
+
+ public Set<Map.Entry<K, V>> entrySet() {
+ return new AbstractSet<Map.Entry<K, V>>() {
+ public int size() {
+ return map.size();
+ }
+
+ public Iterator<Map.Entry<K,V>> iterator() {
+ final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator();
+ return new Iterator<Map.Entry<K,V>>() {
+ public boolean hasNext() { return source.hasNext(); }
+ public Map.Entry<K,V> next() { return source.next(); }
+ public void remove() { source.remove(); }
+ };
+ }
+
+ public boolean add(Map.Entry<K,V> e) {
+ return map.entrySet().add(e);
+ }
+ };
+ }
+
+ public V put(K key, V value) {
+ return map.put(key, value);
+ }
+ }
+
+ /**
+ * A simple mutable concurrent map implementation that provides only default
+ * implementations of all methods. ie. none of the ConcurrentMap interface
+ * default methods have overridden implementations.
+ *
+ * @param <K> Type of keys
+ * @param <V> Type of values
+ */
+ public static class ImplementsConcurrentMap<K, V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> {
+ public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); }
+
+ // ConcurrentMap reabstracts these methods
+
+ public V replace(K k, V v) { return map.replace(k, v); };
+
+ public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); };
+
+ public boolean remove(Object k, Object v) { return map.remove(k, v); }
+
+ public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); }
+ }
+}