Mercurial > hg > openjdk > jigsaw > jdk
changeset 7330:10fd3b4a77ae
Merge
author | chegar |
---|---|
date | Tue, 16 Apr 2013 21:04:33 +0100 |
parents | 9246b0fee2f2 (current diff) e4e9f6455f3c (diff) |
children | a3cc4b8e217a |
files | |
diffstat | 6 files changed, 2097 insertions(+), 51 deletions(-) [+] |
line wrap: on
line diff
--- a/src/share/classes/java/util/Collections.java Tue Apr 16 13:30:19 2013 +0100 +++ b/src/share/classes/java/util/Collections.java Tue Apr 16 21:04:33 2013 +0100 @@ -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/src/share/classes/java/util/HashMap.java Tue Apr 16 13:30:19 2013 +0100 +++ b/src/share/classes/java/util/HashMap.java Tue Apr 16 21:04:33 2013 +0100 @@ -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/src/share/classes/java/util/Hashtable.java Tue Apr 16 13:30:19 2013 +0100 +++ b/src/share/classes/java/util/Hashtable.java Tue Apr 16 21:04:33 2013 +0100 @@ -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/src/share/classes/java/util/Map.java Tue Apr 16 13:30:19 2013 +0100 +++ b/src/share/classes/java/util/Map.java Tue Apr 16 21:04:33 2013 +0100 @@ -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/src/share/classes/java/util/concurrent/ConcurrentMap.java Tue Apr 16 13:30:19 2013 +0100 +++ b/src/share/classes/java/util/concurrent/ConcurrentMap.java Tue Apr 16 21:04:33 2013 +0100 @@ -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/test/java/util/Map/Defaults.java Tue Apr 16 21:04:33 2013 +0100 @@ -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); } + } +}