changeset: 184674:6692dc62c75c tag: tip user: Vladimir Voskresensky date: Sun Dec 12 00:48:09 2010 +0300 summary: patch for #192759 - Missing WeakSet.putIfAbsent diff --git a/cnd.utils/src/org/netbeans/modules/cnd/utils/cache/WeakSharedSet.java b/cnd.utils/src/org/netbeans/modules/cnd/utils/cache/WeakSharedSet.java deleted file mode 100644 --- a/cnd.utils/src/org/netbeans/modules/cnd/utils/cache/WeakSharedSet.java +++ /dev/null @@ -1,1199 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. - * - * Oracle and Java are registered trademarks of Oracle and/or its affiliates. - * Other names may be trademarks of their respective owners. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * Contributor(s): - * - * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun - * Microsystems, Inc. All Rights Reserved. - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - */ -/* - * @(#)WeakSharedSet.java 0.2 07/02/26 - */ - -package org.netbeans.modules.cnd.utils.cache; - -import java.io.IOException; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.AbstractCollection; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; - -/** - * This class provides storage functionality with Weak-referenced entries and - * one new method putIfAbsent (backed by a hash table) - * Access to set should be syncronized if used from different threads - * - * @see #putIfAbsent(Object) - * @author Vladimir Voskresensky - */ -@SuppressWarnings("unchecked") -public class WeakSharedSet extends AbstractSet implements Set { - private final SharedKeyWeakHashMap m; // The backing map - private transient Set s; // Its keySet - - /** - * Constructs a new, empty WeakSharedSet with the given initial - * capacity and the given load factor. - * - * @param initialCapacity The initial capacity of the WeakSharedSet - * @param loadFactor The load factor of the WeakSharedSet - * @throws IllegalArgumentException if the initial capacity is negative, - * or if the load factor is nonpositive. - */ - public WeakSharedSet(int initialCapacity, float loadFactor) { - m = new SharedKeyWeakHashMap(initialCapacity, loadFactor); - s = m.keySet(); - } - - /** - * Constructs a new, empty WeakSharedSet with the given initial - * capacity and the default load factor (0.75). - * - * @param initialCapacity The initial capacity of the WeakSharedSet - * @throws IllegalArgumentException if the initial capacity is negative - */ - public WeakSharedSet(int initialCapacity) { - this(initialCapacity, SharedKeyWeakHashMap.DEFAULT_LOAD_FACTOR); - } - - /** - * Constructs a new, empty WeakSharedSet with the default initial - * capacity (16) and load factor (0.75). - */ - public WeakSharedSet() { - m = new SharedKeyWeakHashMap(); - s = m.keySet(); - } - - /** - * Constructs a new WeakSharedSet with the same mappings as the - * specified map. The WeakSharedSet is created with the default - * load factor (0.75) and an initial capacity sufficient to hold the - * mappings in the specified map. - * - * @param m the map whose mappings are to be placed in this map - * @throws NullPointerException if the specified map is null - */ - public WeakSharedSet(Set s) { - this(Math.max((int) (s.size() / SharedKeyWeakHashMap.DEFAULT_LOAD_FACTOR) + 1, 16), - SharedKeyWeakHashMap.DEFAULT_LOAD_FACTOR); - addAll(s); - } - - @Override - public void clear() { m.clear(); } - public int size() { return m.size(); } - @Override - public boolean isEmpty() { return m.isEmpty(); } - @Override - public boolean contains(Object o) { return m.containsKey(o); } - @Override - public boolean remove(Object o) { return m.remove(o) != null; } - public void resize(int newCapacity){ - if (size()==0) { - m.resize(newCapacity); - } - } - - /** - * it is expected that method putIfAbsent is used instead of add - */ - @Override - public boolean add(E e) { return m.put(e, null) == null; } - public Iterator iterator() { return s.iterator(); } - @Override - public Object[] toArray() { return s.toArray(); } - @Override - public T[] toArray(T[] a) { return s.toArray(a); } - @Override - public String toString() { return s.toString(); } - @Override - public int hashCode() { return s.hashCode(); } - @Override - public boolean equals(Object o) { return o == this || s.equals(o); } - @Override - public boolean containsAll(Collection c) {return s.containsAll(c);} - @Override - public boolean removeAll(Collection c) {return s.removeAll(c);} - @Override - public boolean retainAll(Collection c) {return s.retainAll(c);} - // addAll is the only inherited implementation - - - /** - * Put object in this set if equal one is not yet in set. - * Returns previous set entry if equal object is already in set. - * - * @param e object to put in set. - * @return the previous set entry equals with e, or - * passed object e if there were not entry in set. - */ - public E putIfAbsent(E e) { return m.putIfAbsent(e); } - - private static final long serialVersionUID = 2454657854757543876L; - - private void readObject(java.io.ObjectInputStream stream) - throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - s = m.keySet(); - } - - // delegate class with only one special method putOrGet - // remove entry value field for performance and memory consumption - // all other is copied from java.util.WeakHashMap - private static class SharedKeyWeakHashMap - extends AbstractMap - implements Map { - - /** - * The default initial capacity -- MUST be a power of two. - */ - private static final int DEFAULT_INITIAL_CAPACITY = 16; - - /** - * The maximum capacity, used if a higher value is implicitly specified - * by either of the constructors with arguments. - * MUST be a power of two <= 1<<30. - */ - private static final int MAXIMUM_CAPACITY = 1 << 30; - - /** - * The load fast used when none specified in constructor. - */ - private static final float DEFAULT_LOAD_FACTOR = 0.75f; - - /** - * The table, resized as necessary. Length MUST Always be a power of two. - */ - private Entry[] table; - - /** - * The number of key-value mappings contained in this weak hash map. - */ - private int size; - - /** - * The next size value at which to resize (capacity * load factor). - */ - private int threshold; - - /** - * The load factor for the hash table. - */ - private final float loadFactor; - - /** - * Reference queue for cleared WeakEntries - */ - private final ReferenceQueue queue = new ReferenceQueue(); - - /** - * The number of times this SharedKeyWeakHashMap has been structurally modified. - * Structural modifications are those that change the number of - * mappings in the map or otherwise modify its internal structure - * (e.g., rehash). This field is used to make iterators on - * Collection-views of the map fail-fast. - * - * @see ConcurrentModificationException - */ - private volatile int modCount; - - /** - * Constructs a new, empty SharedKeyWeakHashMap with the given initial - * capacity and the given load factor. - * - * @param initialCapacity The initial capacity of the SharedKeyWeakHashMap - * @param loadFactor The load factor of the SharedKeyWeakHashMap - * @throws IllegalArgumentException if the initial capacity is negative, - * or if the load factor is nonpositive. - */ - public SharedKeyWeakHashMap(int initialCapacity, float loadFactor) { - if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal Initial Capacity: "+ // NOI18N - initialCapacity); - if (initialCapacity > MAXIMUM_CAPACITY) - initialCapacity = MAXIMUM_CAPACITY; - - if (loadFactor <= 0 || Float.isNaN(loadFactor)) - throw new IllegalArgumentException("Illegal Load factor: "+ // NOI18N - loadFactor); - int capacity = 1; - while (capacity < initialCapacity) - capacity <<= 1; - table = new Entry[capacity]; - this.loadFactor = loadFactor; - threshold = (int)(capacity * loadFactor); - } - - /** - * Constructs a new, empty SharedKeyWeakHashMap with the given initial - * capacity and the default load factor (0.75). - * - * @param initialCapacity The initial capacity of the SharedKeyWeakHashMap - * @throws IllegalArgumentException if the initial capacity is negative - */ - public SharedKeyWeakHashMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } - - /** - * Constructs a new, empty SharedKeyWeakHashMap with the default initial - * capacity (16) and load factor (0.75). - */ - public SharedKeyWeakHashMap() { - this.loadFactor = DEFAULT_LOAD_FACTOR; - threshold = DEFAULT_INITIAL_CAPACITY; - table = new Entry[DEFAULT_INITIAL_CAPACITY]; - } - - /** - * Constructs a new SharedKeyWeakHashMap with the same mappings as the - * specified map. The SharedKeyWeakHashMap is created with the default - * load factor (0.75) and an initial capacity sufficient to hold the - * mappings in the specified map. - * - * @param m the map whose mappings are to be placed in this map - * @throws NullPointerException if the specified map is null - * @since 1.3 - */ - public SharedKeyWeakHashMap(Map m) { - this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 16), - DEFAULT_LOAD_FACTOR); - putAll(m); - } - - // internal utilities - - /** - * Value representing null keys inside tables. - */ - private static final Object NULL_KEY = new Object(); - - /** - * Use NULL_KEY for key if it is null. - */ - private static Object maskNull(Object key) { - return (key == null ? NULL_KEY : key); - } - - /** - * Returns internal representation of null key back to caller as null. - */ - private static K unmaskNull(Object key) { - return (K) (key == NULL_KEY ? null : key); - } - - /** - * Checks for equality of non-null reference x and possibly-null y. By - * default uses Object.equals. - */ - static boolean eq(Object x, Object y) { - return x == y || x.equals(y); - } - - /** - * Returns index for hash code h. - */ - static int indexFor(int h, int length) { - return h & (length-1); - } - - /** - * Expunges stale entries from the table. - */ - private void expungeStaleEntries() { - Entry e; - while ( (e = (Entry) queue.poll()) != null) { - int h = e.hash; - int i = indexFor(h, table.length); - - Entry prev = table[i]; - Entry p = prev; - while (p != null) { - Entry next = p.next; - if (p == e) { - if (prev == e) - table[i] = next; - else - prev.next = next; - e.next = null; // Help GC - //e.value = null; // " " - size--; - break; - } - prev = p; - p = next; - } - } - } - - /** - * Returns the table after first expunging stale entries. - */ - private Entry[] getTable() { - expungeStaleEntries(); - return table; - } - - /** - * Returns the number of key-value mappings in this map. - * This result is a snapshot, and may not reflect unprocessed - * entries that will be removed before next attempted access - * because they are no longer referenced. - */ - @Override - public int size() { - if (size == 0) - return 0; - expungeStaleEntries(); - return size; - } - - /** - * Returns true if this map contains no key-value mappings. - * This result is a snapshot, and may not reflect unprocessed - * entries that will be removed before next attempted access - * because they are no longer referenced. - */ - @Override - public boolean isEmpty() { - return size() == 0; - } - - /** - * Returns the value to which the specified key is mapped, - * or {@code null} if this map contains no mapping for the key. - * - *

More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code (key==null ? k==null : - * key.equals(k))}, then this method returns {@code v}; otherwise - * it returns {@code null}. (There can be at most one such mapping.) - * - *

A return value of {@code null} does not necessarily - * indicate that the map contains no mapping for the key; it's also - * possible that the map explicitly maps the key to {@code null}. - * The {@link #containsKey containsKey} operation may be used to - * distinguish these two cases. - * - * @see #put(Object, Object) - */ - @Override - public V get(Object key) { - return null; - } - - /** - * Returns true if this map contains a mapping for the - * specified key. - * - * @param key The key whose presence in this map is to be tested - * @return true if there is a mapping for key; - * false otherwise - */ - @Override - public boolean containsKey(Object key) { - return getEntry(key) != null; - } - - /** - * Returns the entry associated with the specified key in this map. - * Returns null if the map contains no mapping for this key. - */ - Entry getEntry(Object key) { - Object k = maskNull(key); - int h = hash(k.hashCode()); - Entry[] tab = getTable(); - int index = indexFor(h, tab.length); - Entry e = tab[index]; - while (e != null && !(e.hash == h && eq(k, e.get()))) - e = e.next; - return e; - } - - /** - * Associates the specified value with the specified key in this map. - * If the map previously contained a mapping for this key, the old - * value is replaced. - * - * @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 key, or - * null if there was no mapping for key. - * (A null return can also indicate that the map - * previously associated null with key.) - */ - @Override - public V put(K key, V value) { - K k = (K) maskNull(key); - int h = hash(k.hashCode()); - Entry[] tab = getTable(); - int i = indexFor(h, tab.length); - - for (Entry e = tab[i]; e != null; e = e.next) { - if (h == e.hash && eq(k, e.get())) { - return null; - } - } - - modCount++; - Entry e = tab[i]; - tab[i] = new Entry(k, queue, h, e); - if (++size >= threshold) - resize(tab.length * 2); - return null; - } - - /** - * Rehashes the contents of this map into a new array with a - * larger capacity. This method is called automatically when the - * number of keys in this map reaches its threshold. - * - * If current capacity is MAXIMUM_CAPACITY, this method does not - * resize the map, but sets threshold to Integer.MAX_VALUE. - * This has the effect of preventing future calls. - * - * @param newCapacity the new capacity, MUST be a power of two; - * must be greater than current capacity unless current - * capacity is MAXIMUM_CAPACITY (in which case value - * is irrelevant). - */ - void resize(int newCapacity) { - Entry[] oldTable = getTable(); - int oldCapacity = oldTable.length; - if (oldCapacity == MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return; - } - - Entry[] newTable = new Entry[newCapacity]; - transfer(oldTable, newTable); - table = newTable; - - /* - * If ignoring null elements and processing ref queue caused massive - * shrinkage, then restore old table. This should be rare, but avoids - * unbounded expansion of garbage-filled tables. - */ - if (size >= threshold / 2) { - threshold = (int)(newCapacity * loadFactor); - } else { - expungeStaleEntries(); - transfer(newTable, oldTable); - table = oldTable; - } - } - - /** Transfers all entries from src to dest tables */ - private void transfer(Entry[] src, Entry[] dest) { - for (int j = 0; j < src.length; ++j) { - Entry e = src[j]; - src[j] = null; - while (e != null) { - Entry next = e.next; - Object key = e.get(); - if (key == null) { - e.next = null; // Help GC - size--; - } else { - int i = indexFor(e.hash, dest.length); - e.next = dest[i]; - dest[i] = e; - } - e = next; - } - } - } - - /** - * Copies all of the mappings from the specified map to this map. - * These mappings will replace any mappings that this map had for any - * of the keys currently in the specified map. - * - * @param m mappings to be stored in this map. - * @throws NullPointerException if the specified map is null. - */ - @Override - public void putAll(Map m) { - int numKeysToBeAdded = m.size(); - if (numKeysToBeAdded == 0) - return; - - /* - * Expand the map if the map if the number of mappings to be added - * is greater than or equal to threshold. This is conservative; the - * obvious condition is (m.size() + size) >= threshold, but this - * condition could result in a map with twice the appropriate capacity, - * if the keys to be added overlap with the keys already in this map. - * By using the conservative calculation, we subject ourself - * to at most one extra resize. - */ - if (numKeysToBeAdded > threshold) { - int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); - if (targetCapacity > MAXIMUM_CAPACITY) - targetCapacity = MAXIMUM_CAPACITY; - int newCapacity = table.length; - while (newCapacity < targetCapacity) - newCapacity <<= 1; - if (newCapacity > table.length) - resize(newCapacity); - } - - for (Map.Entry e : m.entrySet()) - put(e.getKey(), null); - } - - /** - * Removes the mapping for a key from this weak hash map if it is present. - * More formally, if this map contains a mapping from key k to - * value v such that (key==null ? k==null : - * key.equals(k)), that mapping is removed. (The map can contain - * at most one such mapping.) - * - *

Returns the value to which this map previously associated the key, - * or null if the map contained no mapping for the key. A - * return value of null does not necessarily indicate - * that the map contained no mapping for the key; it's also possible - * that the map explicitly mapped the key to null. - * - *

The map will not contain a mapping for the specified key once the - * call returns. - * - * @param key key whose mapping is to be removed from the map - * @return the previous value associated with key, or - * null if there was no mapping for key - */ - @Override - public V remove(Object key) { - Object k = maskNull(key); - int h = hash(k.hashCode()); - Entry[] tab = getTable(); - int i = indexFor(h, tab.length); - Entry prev = tab[i]; - Entry e = prev; - - while (e != null) { - Entry next = e.next; - if (h == e.hash && eq(k, e.get())) { - modCount++; - size--; - if (prev == e) - tab[i] = next; - else - prev.next = next; - return null; - } - prev = e; - e = next; - } - - return null; - } - - - - /** Special version of remove needed by Entry set */ - Entry removeMapping(Object o) { - if (!(o instanceof Map.Entry)) - return null; - Entry[] tab = getTable(); - Map.Entry entry = (Map.Entry)o; - Object k = maskNull(entry.getKey()); - int h = hash(k.hashCode()); - int i = indexFor(h, tab.length); - Entry prev = tab[i]; - Entry e = prev; - - while (e != null) { - Entry next = e.next; - if (h == e.hash && e.equals(entry)) { - modCount++; - size--; - if (prev == e) - tab[i] = next; - else - prev.next = next; - return e; - } - prev = e; - e = next; - } - - return null; - } - - /** - * Removes all of the mappings from this map. - * The map will be empty after this call returns. - */ - @Override - public void clear() { - // clear out ref queue. We don't need to expunge entries - // since table is getting cleared. - while (queue.poll() != null) {} - - modCount++; - Entry[] tab = table; - for (int i = 0; i < tab.length; ++i) - tab[i] = null; - size = 0; - - // Allocation of array may have caused GC, which may have caused - // additional entries to go stale. Removing these entries from the - // reference queue will make them eligible for reclamation. - while (queue.poll() != null) {} - } - - /** - * Returns true if this map maps one or more keys to the - * specified value. - * - * @param value value whose presence in this map is to be tested - * @return true if this map maps one or more keys to the - * specified value - */ - @Override - public boolean containsValue(Object value) { - if (value==null) - return containsNullValue(); - return false; - } - - /** - * Special-case code for containsValue with null argument - */ - private boolean containsNullValue() { - Entry[] tab = getTable(); - for (int i = tab.length ; i-- > 0 ;) { - for (Entry e = tab[i] ; e != null ; e = e.next) { - return true; - } - } - return false; - } - - /** - * The entries in this hash table extend WeakReference, using its main ref - * field as the key. - */ - private static class Entry extends WeakReference implements Map.Entry { - private final int hash; - private Entry next; - - /** - * Creates new entry. - */ - Entry(K key, - ReferenceQueue queue, - int hash, Entry next) { - super(key, queue); - this.hash = hash; - this.next = next; - } - - public K getKey() { - return SharedKeyWeakHashMap.unmaskNull(get()); - } - - public V getValue() { - return null; - } - - public V setValue(V newValue) { - return null; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry)o; - Object k1 = getKey(); - Object k2 = e.getKey(); - if (k1 == k2 || (k1 != null && k1.equals(k2))) { - return true; - } - return false; - } - - @Override - public int hashCode() { - Object k = getKey(); - return (k==null ? 0 : k.hashCode()); - } - - @Override - public String toString() { - return "" + getKey(); // NOI18N - } - } - - /** - * Have to copy AbstractMap.SimpleEntry, - * since it appears only in jdk 1.6 - */ - private static class SimpleEntry - implements Map.Entry, java.io.Serializable { - private static final long serialVersionUID = -8499721149061103585L; - - private final K key; - - /** - * Creates an entry representing a mapping from the specified - * key to the specified value. - * - * @param key the key represented by this entry - * @param value the value represented by this entry - */ - public SimpleEntry(K key) { - this.key = key; - } - - /** - * Creates an entry representing the same mapping as the - * specified entry. - * - * @param entry the entry to copy - */ - public SimpleEntry(Map.Entry entry) { - this.key = entry.getKey(); - } - - /** - * Returns the key corresponding to this entry. - * - * @return the key corresponding to this entry - */ - public K getKey() { - return key; - } - - /** - * Returns the value corresponding to this entry. - * - * @return the value corresponding to this entry - */ - public V getValue() { - return null; - } - - /** - * Replaces the value corresponding to this entry with the specified - * value. - * - * @param value new value to be stored in this entry - * @return the old value corresponding to the entry - */ - public V setValue(V value) { - return null; - } - - /** - * Compares the specified object with this entry for equality. - * Returns {@code true} if the given object is also a map entry and - * the two entries represent the same mapping. More formally, two - * entries {@code e1} and {@code e2} represent the same mapping - * if

-             *   (e1.getKey()==null ?
-             *    e2.getKey()==null :
-             *    e1.getKey().equals(e2.getKey()))
- * This ensures that the {@code equals} method works properly across - * different implementations of the {@code Map.Entry} interface. - * - * @param o object to be compared for equality with this map entry - * @return {@code true} if the specified object is equal to this map - * entry - * @see #hashCode - */ - @Override - public boolean equals(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry)o; - return eq(key, e.getKey()); - } - - /** - * Returns the hash code value for this map entry. The hash code - * of a map entry {@code e} is defined to be:
-             *   (e.getKey()==null   ? 0 : e.getKey().hashCode())
- * This ensures that {@code e1.equals(e2)} implies that - * {@code e1.hashCode()==e2.hashCode()} for any two Entries - * {@code e1} and {@code e2}, as required by the general - * contract of {@link Object#hashCode}. - * - * @return the hash code value for this map entry - * @see #equals - */ - @Override - public int hashCode() { - return (key == null ? 0 : key.hashCode()); - } - - /** - * Returns a String representation of this map entry. This - * implementation returns the string representation of this - * entry's key followed by the equals character ("=") - * followed by the string representation of this entry's value. - * - * @return a String representation of this map entry - */ - @Override - public String toString() { - return "" + key; // NOI18N - } - - } - - private abstract class HashIterator implements Iterator { - int index; - Entry entry = null; - Entry lastReturned = null; - int expectedModCount = modCount; - - /** - * Strong reference needed to avoid disappearance of key - * between hasNext and next - */ - Object nextKey = null; - - /** - * Strong reference needed to avoid disappearance of key - * between nextEntry() and any use of the entry - */ - Object currentKey = null; - - HashIterator() { - index = (size() != 0 ? table.length : 0); - } - - public boolean hasNext() { - Entry[] t = table; - - while (nextKey == null) { - Entry e = entry; - int i = index; - while (e == null && i > 0) - e = t[--i]; - entry = e; - index = i; - if (e == null) { - currentKey = null; - return false; - } - nextKey = e.get(); // hold on to key in strong ref - if (nextKey == null) - entry = entry.next; - } - return true; - } - - /** The common parts of next() across different types of iterators */ - protected Entry nextEntry() { - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - if (nextKey == null && !hasNext()) - throw new NoSuchElementException(); - - lastReturned = entry; - entry = entry.next; - currentKey = nextKey; - nextKey = null; - return lastReturned; - } - - public void remove() { - if (lastReturned == null) - throw new IllegalStateException(); - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); - - SharedKeyWeakHashMap.this.remove(currentKey); - expectedModCount = modCount; - lastReturned = null; - currentKey = null; - } - - } - - private class ValueIterator extends HashIterator { - public V next() { - nextEntry(); - return null; - } - } - - private class KeyIterator extends HashIterator { - public K next() { - return nextEntry().getKey(); - } - } - - private class EntryIterator extends HashIterator> { - public Map.Entry next() { - return nextEntry(); - } - } - - // Views - - private transient Set> entrySet = null; - - /** - * Returns a {@link Set} view of the keys contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. If the map is modified - * while an iteration over the set is in progress (except through - * the iterator's own remove operation), the results of - * the iteration are undefined. The set supports element removal, - * which removes the corresponding mapping from the map, via the - * Iterator.remove, Set.remove, - * removeAll, retainAll, and clear - * operations. It does not support the add or addAll - * operations. - */ - @Override - public Set keySet() { - Set ks = keySet; - return (ks != null ? ks : (keySet = new KeySet())); - } - - private class KeySet extends AbstractSet { - public Iterator iterator() { - return new KeyIterator(); - } - - public int size() { - return SharedKeyWeakHashMap.this.size(); - } - - @Override - public boolean contains(Object o) { - return containsKey(o); - } - - @Override - public boolean remove(Object o) { - if (containsKey(o)) { - SharedKeyWeakHashMap.this.remove(o); - return true; - } else - return false; - } - - @Override - public void clear() { - SharedKeyWeakHashMap.this.clear(); - } - } - - /** - * Returns a {@link Collection} view of the values contained in this map. - * The collection is backed by the map, so changes to the map are - * reflected in the collection, and vice-versa. If the map is - * modified while an iteration over the collection is in progress - * (except through the iterator's own remove operation), - * the results of the iteration are undefined. The collection - * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Collection.remove, removeAll, - * retainAll and clear operations. It does not - * support the add or addAll operations. - */ - @Override - public Collection values() { - Collection vs = values; - return (vs != null ? vs : (values = new Values())); - } - - private class Values extends AbstractCollection { - public Iterator iterator() { - return new ValueIterator(); - } - - public int size() { - return SharedKeyWeakHashMap.this.size(); - } - - @Override - public boolean contains(Object o) { - return containsValue(o); - } - - @Override - public void clear() { - SharedKeyWeakHashMap.this.clear(); - } - } - - /** - * Returns a {@link Set} view of the mappings contained in this map. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. If the map is modified - * while an iteration over the set is in progress (except through - * the iterator's own remove operation, or through the - * setValue operation on a map entry returned by the - * iterator) the results of the iteration are undefined. The set - * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Set.remove, removeAll, retainAll and - * clear operations. It does not support the - * add or addAll operations. - */ - public Set> entrySet() { - Set> es = entrySet; - return es != null ? es : (entrySet = new EntrySet()); - } - - private class EntrySet extends AbstractSet> { - public Iterator> iterator() { - return new EntryIterator(); - } - - @Override - public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry)o; - Entry candidate = getEntry(e.getKey()); - return candidate != null && candidate.equals(e); - } - - @Override - public boolean remove(Object o) { - return removeMapping(o) != null; - } - - public int size() { - return SharedKeyWeakHashMap.this.size(); - } - - @Override - public void clear() { - SharedKeyWeakHashMap.this.clear(); - } - - private List> deepCopy() { - List> list = new ArrayList>(size()); - for (Map.Entry e : this) - // have to use own class since AbstractMap.SimpleEntry is appears only in jdk 1.6 - list.add(new /*AbstractMap.*/SimpleEntry(e)); - return list; - } - - @Override - public Object[] toArray() { - return deepCopy().toArray(); - } - - @Override - public T[] toArray(T[] a) { - return deepCopy().toArray(a); - } - } - - //////////////////////////////////////////////////////////////////////////// - // new changes - - /** - * Applies a supplemental hash function to a given hashCode, which - * defends against poor quality hash functions. This is critical - * because HashMap uses power-of-two length hash tables, that - * otherwise encounter collisions for hashCodes that do not differ - * in lower bits. Note: Null keys always map to hash 0, thus index 0. - */ - static int hash(int h) { - // This function ensures that hashCodes that differ only by - // constant multiples at each bit position have a bounded - // number of collisions (approximately 8 at default load factor). - h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); - } - - // Views - - /** - * Each of these fields are initialized to contain an instance of the - * appropriate view the first time this view is requested. The views are - * stateless, so there's no reason to create more than one of each. - */ - transient volatile Set keySet = null; - transient volatile Collection values = null; - - /** - * Put specified key in this set if key is not yet in set. - * returns previous value in set if key already in set. - * - * @param key key to put in set. - * @return the previous set entry equals with key, or - * new key if there were not entry in set. - */ - private K putIfAbsent(K key) { - K k = (K) maskNull(key); - int h = hash(k.hashCode()); - Entry[] tab = getTable(); - int i = indexFor(h, tab.length); - - Entry e = tab[i]; - while (e != null) { - if (e.hash == h) { - K refedKey = e.get(); - if (eq(k, refedKey)) { - return refedKey; - } - } - e = e.next; - } - - modCount++; - e = tab[i]; - tab[i] = new Entry(k, queue, h, e); - if (++size >= threshold) - resize(tab.length * 2); - return k; - } - } -} diff --git a/openide.util/src/org/openide/util/WeakSet.java b/openide.util/src/org/openide/util/WeakSet.java --- a/openide.util/src/org/openide/util/WeakSet.java +++ b/openide.util/src/org/openide/util/WeakSet.java @@ -27,7 +27,7 @@ * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original - * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL @@ -41,542 +41,1159 @@ * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ +/* + * @(#)WeakSet.java 0.2 07/02/26 + */ package org.openide.util; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; +import java.util.AbstractCollection; +import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; -/** Set which holds its members by using of WeakReferences. -* MT level: unsafe. - *

Note: you can instead use - *

- * Set<T> s = Collections.newSetFromMap(new WeakHashMap<T, Boolean>());
- * 
-* -* @author Ales Novak -*/ -public class WeakSet extends AbstractSet implements Cloneable, Serializable { - static final long serialVersionUID = 3062376055928236721L; +/** + * This class provides storage functionality with Weak-referenced entries and + * one new method putIfAbsent (backed by a hash table) + * Access to set should be syncronized if used from different threads + * + * @see #putIfAbsent(Object) + * @author Vladimir Voskresensky + */ +@SuppressWarnings("unchecked") +public class WeakSet extends AbstractSet implements Set { + private final SharedKeyWeakHashMap m; // The backing map + private transient Set s; // Its keySet - /** load factor */ - private float loadFactor; - - /** Number of items. */ - private int size; - - /** Modification count */ - private long modcount; - - /** Reference queue of collected weak refs */ - private transient ReferenceQueue refq; - - /** Count of null in this set */ - long nullCount; - - /** An array of Entries */ - private transient Entry[] entries; - transient Entry iterChain; - - /** Constructs a new set. */ - public WeakSet() { - this(11, 0.75f); + /** + * Constructs a new, empty WeakSet with the given initial + * capacity and the given load factor. + * + * @param initialCapacity The initial capacity of the WeakSet + * @param loadFactor The load factor of the WeakSet + * @throws IllegalArgumentException if the initial capacity is negative, + * or if the load factor is nonpositive. + */ + public WeakSet(int initialCapacity, float loadFactor) { + m = new SharedKeyWeakHashMap(initialCapacity, loadFactor); + s = m.keySet(); } - /** Constructs a new set containing the elements in the specified collection. - * @param c a collection to add - */ - public WeakSet(Collection c) { - this(); - addAll(c); + /** + * Constructs a new, empty WeakSet with the given initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity The initial capacity of the WeakSet + * @throws IllegalArgumentException if the initial capacity is negative + */ + public WeakSet(int initialCapacity) { + this(initialCapacity, SharedKeyWeakHashMap.DEFAULT_LOAD_FACTOR); } - /** Constructs a new, empty set; - * @param initialCapacity initial capacity - */ - public WeakSet(int initialCapacity) { - this(initialCapacity, 0.75f); + /** + * Constructs a new, empty WeakSet with the default initial + * capacity (16) and load factor (0.75). + */ + public WeakSet() { + m = new SharedKeyWeakHashMap(); + s = m.keySet(); } - /** Constructs a new, empty set; - * - * @param initialCapacity initial capacity - * @param loadFactor load factor - */ - public WeakSet(int initialCapacity, float loadFactor) { - if ((initialCapacity <= 0) || (loadFactor <= 0)) { - throw new IllegalArgumentException(); + /** + * Constructs a new WeakSet with the same mappings as the + * specified map. The WeakSet is created with the default + * load factor (0.75) and an initial capacity sufficient to hold the + * mappings in the specified map. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + */ + public WeakSet(Set s) { + this(Math.max((int) (s.size() / SharedKeyWeakHashMap.DEFAULT_LOAD_FACTOR) + 1, 16), + SharedKeyWeakHashMap.DEFAULT_LOAD_FACTOR); + addAll(s); + } + + @Override + public void clear() { m.clear(); } + public int size() { return m.size(); } + @Override + public boolean isEmpty() { return m.isEmpty(); } + @Override + public boolean contains(Object o) { return m.containsKey(o); } + @Override + public boolean remove(Object o) { return m.remove(o) != null; } + public void resize(int newCapacity){ + if (size()==0) { + m.resize(newCapacity); + } + } + + /** + * @see #putIfAbsent + */ + @Override + public boolean add(E e) { return m.put(e, null) == null; } + public Iterator iterator() { return s.iterator(); } + @Override + public Object[] toArray() { return s.toArray(); } + @Override + public T[] toArray(T[] a) { return s.toArray(a); } + @Override + public String toString() { return s.toString(); } + @Override + public int hashCode() { return s.hashCode(); } + @Override + public boolean equals(Object o) { return o == this || s.equals(o); } + @Override + public boolean containsAll(Collection c) {return s.containsAll(c);} + @Override + public boolean removeAll(Collection c) {return s.removeAll(c);} + @Override + public boolean retainAll(Collection c) {return s.retainAll(c);} + // addAll is the only inherited implementation + + + /** + * Put object in this set if equal one is not yet in set. + * Returns previous set entry if equal object is already in set. + * + * @param e object to put in set. + * @return the previous set entry equals with e, or + * passed object e if there were not entry in set. + */ + public E putIfAbsent(E e) { return m.putIfAbsent(e); } + + private static final long serialVersionUID = 2454657854757543876L; + + private void readObject(java.io.ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + s = m.keySet(); + } + + // delegate class with only one special method putOrGet + // remove entry value field for performance and memory consumption + // all other is copied from java.util.WeakHashMap + private static class SharedKeyWeakHashMap + extends AbstractMap + implements Map { + + /** + * The default initial capacity -- MUST be a power of two. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load fast used when none specified in constructor. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The table, resized as necessary. Length MUST Always be a power of two. + */ + private Entry[] table; + + /** + * The number of key-value mappings contained in this weak hash map. + */ + private int size; + + /** + * The next size value at which to resize (capacity * load factor). + */ + private int threshold; + + /** + * The load factor for the hash table. + */ + private final float loadFactor; + + /** + * Reference queue for cleared WeakEntries + */ + private final ReferenceQueue queue = new ReferenceQueue(); + + /** + * The number of times this SharedKeyWeakHashMap has been structurally modified. + * Structural modifications are those that change the number of + * mappings in the map or otherwise modify its internal structure + * (e.g., rehash). This field is used to make iterators on + * Collection-views of the map fail-fast. + * + * @see ConcurrentModificationException + */ + private volatile int modCount; + + /** + * Constructs a new, empty SharedKeyWeakHashMap with the given initial + * capacity and the given load factor. + * + * @param initialCapacity The initial capacity of the SharedKeyWeakHashMap + * @param loadFactor The load factor of the SharedKeyWeakHashMap + * @throws IllegalArgumentException if the initial capacity is negative, + * or if the load factor is nonpositive. + */ + public SharedKeyWeakHashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Initial Capacity: "+ // NOI18N + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal Load factor: "+ // NOI18N + loadFactor); + int capacity = 1; + while (capacity < initialCapacity) + capacity <<= 1; + table = new Entry[capacity]; + this.loadFactor = loadFactor; + threshold = (int)(capacity * loadFactor); } - size = 0; - modcount = 0; - this.loadFactor = loadFactor; - nullCount = 0; - refq = new ReferenceQueue(); - entries = Entry.createArray(initialCapacity); - iterChain = null; - } - - /** - * logs iterator chain (for debugging) - * @param msg - */ - void logIterChain(String msg) { - Logger log = Logger.getLogger(WeakSet.class.getName()); - log.log(Level.FINE, msg); - if (iterChain == null) { - log.log(Level.FINE, "Empty"); - return; - } - StringBuilder str = new StringBuilder(); - Entry it = iterChain; - str.append(size + ": "); - while (it != null) { - str.append(it.get() + "(" + it.hashcode + ")" + "->"); - it = it.iterChainNext; - } - log.log(Level.FINE, str.toString()); - } - - /** Adds the specified element to this set if it is not already present. - * - * @param o an Object to add - */ - public boolean add(E o) { - if (o == null) { - size++; - nullCount++; - modcount++; - - return true; + /** + * Constructs a new, empty SharedKeyWeakHashMap with the given initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity The initial capacity of the SharedKeyWeakHashMap + * @throws IllegalArgumentException if the initial capacity is negative + */ + public SharedKeyWeakHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); } - Entry e = object2Entry(o); + /** + * Constructs a new, empty SharedKeyWeakHashMap with the default initial + * capacity (16) and load factor (0.75). + */ + public SharedKeyWeakHashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; + threshold = DEFAULT_INITIAL_CAPACITY; + table = new Entry[DEFAULT_INITIAL_CAPACITY]; + } - if (e != null) { + /** + * Constructs a new SharedKeyWeakHashMap with the same mappings as the + * specified map. The SharedKeyWeakHashMap is created with the default + * load factor (0.75) and an initial capacity sufficient to hold the + * mappings in the specified map. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + * @since 1.3 + */ + public SharedKeyWeakHashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 16), + DEFAULT_LOAD_FACTOR); + putAll(m); + } + + // internal utilities + + /** + * Value representing null keys inside tables. + */ + private static final Object NULL_KEY = new Object(); + + /** + * Use NULL_KEY for key if it is null. + */ + private static Object maskNull(Object key) { + return (key == null ? NULL_KEY : key); + } + + /** + * Returns internal representation of null key back to caller as null. + */ + private static K unmaskNull(Object key) { + return (K) (key == NULL_KEY ? null : key); + } + + /** + * Checks for equality of non-null reference x and possibly-null y. By + * default uses Object.equals. + */ + static boolean eq(Object x, Object y) { + return x == y || x.equals(y); + } + + /** + * Returns index for hash code h. + */ + static int indexFor(int h, int length) { + return h & (length-1); + } + + /** + * Expunges stale entries from the table. + */ + private void expungeStaleEntries() { + Entry e; + while ( (e = (Entry) queue.poll()) != null) { + int h = e.hash; + int i = indexFor(h, table.length); + + Entry prev = table[i]; + Entry p = prev; + while (p != null) { + Entry next = p.next; + if (p == e) { + if (prev == e) + table[i] = next; + else + prev.next = next; + e.next = null; // Help GC + //e.value = null; // " " + size--; + break; + } + prev = p; + p = next; + } + } + } + + /** + * Returns the table after first expunging stale entries. + */ + private Entry[] getTable() { + expungeStaleEntries(); + return table; + } + + /** + * Returns the number of key-value mappings in this map. + * This result is a snapshot, and may not reflect unprocessed + * entries that will be removed before next attempted access + * because they are no longer referenced. + */ + @Override + public int size() { + if (size == 0) + return 0; + expungeStaleEntries(); + return size; + } + + /** + * Returns true if this map contains no key-value mappings. + * This result is a snapshot, and may not reflect unprocessed + * entries that will be removed before next attempted access + * because they are no longer referenced. + */ + @Override + public boolean isEmpty() { + return size() == 0; + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==null ? k==null : + * key.equals(k))}, then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ + @Override + public V get(Object key) { + return null; + } + + /** + * Returns true if this map contains a mapping for the + * specified key. + * + * @param key The key whose presence in this map is to be tested + * @return true if there is a mapping for key; + * false otherwise + */ + @Override + public boolean containsKey(Object key) { + return getEntry(key) != null; + } + + /** + * Returns the entry associated with the specified key in this map. + * Returns null if the map contains no mapping for this key. + */ + Entry getEntry(Object key) { + Object k = maskNull(key); + int h = hash(k.hashCode()); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + while (e != null && !(e.hash == h && eq(k, e.get()))) + e = e.next; + return e; + } + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for this key, the old + * value is replaced. + * + * @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 key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + @Override + public V put(K key, V value) { + K k = (K) maskNull(key); + int h = hash(k.hashCode()); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + + for (Entry e = tab[i]; e != null; e = e.next) { + if (h == e.hash && eq(k, e.get())) { + return null; + } + } + + modCount++; + Entry e = tab[i]; + tab[i] = new Entry(k, queue, h, e); + if (++size >= threshold) + resize(tab.length * 2); + return null; + } + + /** + * Rehashes the contents of this map into a new array with a + * larger capacity. This method is called automatically when the + * number of keys in this map reaches its threshold. + * + * If current capacity is MAXIMUM_CAPACITY, this method does not + * resize the map, but sets threshold to Integer.MAX_VALUE. + * This has the effect of preventing future calls. + * + * @param newCapacity the new capacity, MUST be a power of two; + * must be greater than current capacity unless current + * capacity is MAXIMUM_CAPACITY (in which case value + * is irrelevant). + */ + void resize(int newCapacity) { + Entry[] oldTable = getTable(); + int oldCapacity = oldTable.length; + if (oldCapacity == MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return; + } + + Entry[] newTable = new Entry[newCapacity]; + transfer(oldTable, newTable); + table = newTable; + + /* + * If ignoring null elements and processing ref queue caused massive + * shrinkage, then restore old table. This should be rare, but avoids + * unbounded expansion of garbage-filled tables. + */ + if (size >= threshold / 2) { + threshold = (int)(newCapacity * loadFactor); + } else { + expungeStaleEntries(); + transfer(newTable, oldTable); + table = oldTable; + } + } + + /** Transfers all entries from src to dest tables */ + private void transfer(Entry[] src, Entry[] dest) { + for (int j = 0; j < src.length; ++j) { + Entry e = src[j]; + src[j] = null; + while (e != null) { + Entry next = e.next; + Object key = e.get(); + if (key == null) { + e.next = null; // Help GC + size--; + } else { + int i = indexFor(e.hash, dest.length); + e.next = dest[i]; + dest[i] = e; + } + e = next; + } + } + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for any + * of the keys currently in the specified map. + * + * @param m mappings to be stored in this map. + * @throws NullPointerException if the specified map is null. + */ + @Override + public void putAll(Map m) { + int numKeysToBeAdded = m.size(); + if (numKeysToBeAdded == 0) + return; + + /* + * Expand the map if the map if the number of mappings to be added + * is greater than or equal to threshold. This is conservative; the + * obvious condition is (m.size() + size) >= threshold, but this + * condition could result in a map with twice the appropriate capacity, + * if the keys to be added overlap with the keys already in this map. + * By using the conservative calculation, we subject ourself + * to at most one extra resize. + */ + if (numKeysToBeAdded > threshold) { + int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); + if (targetCapacity > MAXIMUM_CAPACITY) + targetCapacity = MAXIMUM_CAPACITY; + int newCapacity = table.length; + while (newCapacity < targetCapacity) + newCapacity <<= 1; + if (newCapacity > table.length) + resize(newCapacity); + } + + for (Map.Entry e : m.entrySet()) + put(e.getKey(), null); + } + + /** + * Removes the mapping for a key from this weak hash map if it is present. + * More formally, if this map contains a mapping from key k to + * value v such that (key==null ? k==null : + * key.equals(k)), that mapping is removed. (The map can contain + * at most one such mapping.) + * + *

Returns the value to which this map previously associated the key, + * or null if the map contained no mapping for the key. A + * return value of null does not necessarily indicate + * that the map contained no mapping for the key; it's also possible + * that the map explicitly mapped the key to null. + * + *

The map will not contain a mapping for the specified key once the + * call returns. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with key, or + * null if there was no mapping for key + */ + @Override + public V remove(Object key) { + Object k = maskNull(key); + int h = hash(k.hashCode()); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && eq(k, e.get())) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return null; + } + prev = e; + e = next; + } + + return null; + } + + + + /** Special version of remove needed by Entry set */ + Entry removeMapping(Object o) { + if (!(o instanceof Map.Entry)) + return null; + Entry[] tab = getTable(); + Map.Entry entry = (Map.Entry)o; + Object k = maskNull(entry.getKey()); + int h = hash(k.hashCode()); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && e.equals(entry)) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return e; + } + prev = e; + e = next; + } + + return null; + } + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + @Override + public void clear() { + // clear out ref queue. We don't need to expunge entries + // since table is getting cleared. + while (queue.poll() != null) {} + + modCount++; + Entry[] tab = table; + for (int i = 0; i < tab.length; ++i) + tab[i] = null; + size = 0; + + // Allocation of array may have caused GC, which may have caused + // additional entries to go stale. Removing these entries from the + // reference queue will make them eligible for reclamation. + while (queue.poll() != null) {} + } + + /** + * Returns true if this map maps one or more keys to the + * specified value. + * + * @param value value whose presence in this map is to be tested + * @return true if this map maps one or more keys to the + * specified value + */ + @Override + public boolean containsValue(Object value) { + if (value==null) + return containsNullValue(); return false; } - modcount++; - size++; - - int hash = hashIt(o); - Entry next = entries[hash]; - iterChain = entries[hash] = new Entry(this, o, refq, next, iterChain); - rehash(); - - return true; - } - - /** Removes all of the elements from this set. */ - public void clear() { - for (int i = 0; i < entries.length; i++) { - entries[i] = null; + /** + * Special-case code for containsValue with null argument + */ + private boolean containsNullValue() { + Entry[] tab = getTable(); + for (int i = tab.length ; i-- > 0 ;) { + for (Entry e = tab[i] ; e != null ; e = e.next) { + return true; + } + } + return false; } - nullCount = 0; - modcount++; - size = 0; - iterChain = null; - } + /** + * The entries in this hash table extend WeakReference, using its main ref + * field as the key. + */ + private static class Entry extends WeakReference implements Map.Entry { + private final int hash; + private Entry next; - /** Returns a shallow copy of this WeakSet instance: the elements themselves are not cloned. */ - public Object clone() { - WeakSet nws = new WeakSet(1, loadFactor); - nws.size = size; - nws.nullCount = nullCount; - - Entry[] cloned = Entry.createArray(entries.length); - nws.entries = cloned; - - for (int i = 0; i < cloned.length; i++) { - Object ref; - - if ((entries[i] == null) || ((ref = entries[i].get()) == null)) { - cloned[i] = null; - } else { - cloned[i] = ((entries[i] == null) ? null : entries[i].clone(nws.refq)); - ref = null; + /** + * Creates new entry. + */ + Entry(K key, + ReferenceQueue queue, + int hash, Entry next) { + super(key, queue); + this.hash = hash; + this.next = next; } - // chains into nws iterator chain - Entry entry = cloned[i]; + public K getKey() { + return SharedKeyWeakHashMap.unmaskNull(get()); + } - while (entry != null) { - entry.chainIntoIter(nws.iterChain); - nws.iterChain = entry; - entry = entry.next; + public V getValue() { + return null; + } + + public V setValue(V newValue) { + return null; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + Object k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + return true; + } + return false; + } + + @Override + public int hashCode() { + Object k = getKey(); + return (k==null ? 0 : k.hashCode()); + } + + @Override + public String toString() { + return "" + getKey(); // NOI18N } } - return nws; - } + /** + * Have to copy AbstractMap.SimpleEntry, + * since it appears only in jdk 1.6 + */ + private static class SimpleEntry + implements Map.Entry, java.io.Serializable { + private static final long serialVersionUID = -8499721149061103585L; - /** Returns true if this set contains the specified element. - * - * @param o an Object to examine - */ - public boolean contains(Object o) { - if (o == null) { - return nullCount > 0; + private final K key; + + /** + * Creates an entry representing a mapping from the specified + * key to the specified value. + * + * @param key the key represented by this entry + * @param value the value represented by this entry + */ + public SimpleEntry(K key) { + this.key = key; + } + + /** + * Creates an entry representing the same mapping as the + * specified entry. + * + * @param entry the entry to copy + */ + public SimpleEntry(Map.Entry entry) { + this.key = entry.getKey(); + } + + /** + * Returns the key corresponding to this entry. + * + * @return the key corresponding to this entry + */ + public K getKey() { + return key; + } + + /** + * Returns the value corresponding to this entry. + * + * @return the value corresponding to this entry + */ + public V getValue() { + return null; + } + + /** + * Replaces the value corresponding to this entry with the specified + * value. + * + * @param value new value to be stored in this entry + * @return the old value corresponding to the entry + */ + public V setValue(V value) { + return null; + } + + /** + * Compares the specified object with this entry for equality. + * Returns {@code true} if the given object is also a map entry and + * the two entries represent the same mapping. More formally, two + * entries {@code e1} and {@code e2} represent the same mapping + * if

+             *   (e1.getKey()==null ?
+             *    e2.getKey()==null :
+             *    e1.getKey().equals(e2.getKey()))
+ * This ensures that the {@code equals} method works properly across + * different implementations of the {@code Map.Entry} interface. + * + * @param o object to be compared for equality with this map entry + * @return {@code true} if the specified object is equal to this map + * entry + * @see #hashCode + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + return eq(key, e.getKey()); + } + + /** + * Returns the hash code value for this map entry. The hash code + * of a map entry {@code e} is defined to be:
+             *   (e.getKey()==null   ? 0 : e.getKey().hashCode())
+ * This ensures that {@code e1.equals(e2)} implies that + * {@code e1.hashCode()==e2.hashCode()} for any two Entries + * {@code e1} and {@code e2}, as required by the general + * contract of {@link Object#hashCode}. + * + * @return the hash code value for this map entry + * @see #equals + */ + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()); + } + + /** + * Returns a String representation of this map entry. This + * implementation returns the string representation of this + * entry's key followed by the equals character ("=") + * followed by the string representation of this entry's value. + * + * @return a String representation of this map entry + */ + @Override + public String toString() { + return "" + key; // NOI18N + } + } - return object2Entry(o) != null; - } + private abstract class HashIterator implements Iterator { + int index; + Entry entry = null; + Entry lastReturned = null; + int expectedModCount = modCount; - /** Returns true if this set contains no elements. - */ - public boolean isEmpty() { - return ((nullCount == 0) && (size() == 0)); - } + /** + * Strong reference needed to avoid disappearance of key + * between hasNext and next + */ + Object nextKey = null; - /** Returns an iterator over the elements in this set. */ - public Iterator iterator() { - return new WeakSetIterator(); - } + /** + * Strong reference needed to avoid disappearance of key + * between nextEntry() and any use of the entry + */ + Object currentKey = null; - /** Removes the given element from this set if it is present. - * - * @param o an Object to remove - * @return true if and only if the Object was successfuly removed. - */ - public boolean remove(Object o) { - if (o == null) { - if (nullCount > 0) { - nullCount--; - modcount++; - size--; + HashIterator() { + index = (size() != 0 ? table.length : 0); } - return true; + public boolean hasNext() { + Entry[] t = table; + + while (nextKey == null) { + Entry e = entry; + int i = index; + while (e == null && i > 0) + e = t[--i]; + entry = e; + index = i; + if (e == null) { + currentKey = null; + return false; + } + nextKey = e.get(); // hold on to key in strong ref + if (nextKey == null) + entry = entry.next; + } + return true; + } + + /** The common parts of next() across different types of iterators */ + protected Entry nextEntry() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (nextKey == null && !hasNext()) + throw new NoSuchElementException(); + + lastReturned = entry; + entry = entry.next; + currentKey = nextKey; + nextKey = null; + return lastReturned; + } + + public void remove() { + if (lastReturned == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + + SharedKeyWeakHashMap.this.remove(currentKey); + expectedModCount = modCount; + lastReturned = null; + currentKey = null; + } + } - Entry e = object2Entry(o); - - if (e != null) { - modcount++; - size--; - e.remove(); - rehash(); - - return true; - } - - return false; - } - - /** @return the number of elements in this set (its cardinality). */ - public int size() { - checkRefQueue(); - - return size; - } - - public T[] toArray(T[] array) { - ArrayList list = new ArrayList(array.length); - Iterator it = iterator(); - - while (it.hasNext()) { - list.add(it.next()); - } - - return list.toArray(array); - } - - public Object[] toArray() { - ArrayList list = new ArrayList(); - Iterator it = iterator(); - - while (it.hasNext()) { - list.add(it.next()); - } - - return list.toArray(); - } - - // #14772 - public String toString() { - StringBuffer buf = new StringBuffer(); - Iterator e = iterator(); - buf.append("["); - - while (e.hasNext()) { - buf.append(String.valueOf(e.next())); - - if (e.hasNext()) { - buf.append(", "); + private class ValueIterator extends HashIterator { + public V next() { + nextEntry(); + return null; } } - buf.append("]"); - - return buf.toString(); - } - - /** Checks if the queue is empty if not pending weak refs are removed. */ - void checkRefQueue() { - for (;;) { - Entry entry = Entry.class.cast(refq.poll()); - - if (entry == null) { - break; - } - - entry.remove(); - size--; - } - } - - /** @return modcount */ - long modCount() { - return modcount; - } - - /** @return an index to entries array */ - int hashIt(Object o) { - return (o.hashCode() & 0x7fffffff) % entries.length; - } - - /** rehashes this Set */ - void rehash() { - /* - float currentLF = ((float) size) / ((float) entries.length); - if (currentLF < loadFactor) { - return; - } - */ - } - - /** @return an Entry with given object */ - private Entry object2Entry(Object o) { - checkRefQueue(); // clear ref q - - int hash = hashIt(o); - Entry e = entries[hash]; - - if (e == null) { - return null; - } - - while ((e != null) && !e.equals(o)) { - e = e.next; - } - - return e; - } - - private void writeObject(ObjectOutputStream obtos) - throws IOException { - obtos.defaultWriteObject(); - obtos.writeObject(toArray()); - } - - @SuppressWarnings("unchecked") - private void readObject(ObjectInputStream obtis) throws IOException, ClassNotFoundException { - obtis.defaultReadObject(); - - Object[] arr = (Object[]) obtis.readObject(); - entries = new Entry[(int) (size * 1.5)]; - refq = new ReferenceQueue(); - - for (int i = 0; i < arr.length; i++) { - add((E)arr[i]); - } - } - - class WeakSetIterator implements Iterator { - Entry current; - Entry next; - E currentObj; - E nextObj; - final long myModcount; - long myNullCount; - - WeakSetIterator() { - myModcount = modCount(); - myNullCount = nullCount; - current = null; - next = null; - - Entry ee = iterChain; - - if (ee == null) { - return; - } - - E o = ee.get(); - - while (ee.isEnqueued()) { - ee = ee.iterChainNext; - - if (ee == null) { - return; - } - - o = ee.get(); - } - - nextObj = o; - next = ee; - } - - public boolean hasNext() { - checkModcount(); - - return ((myNullCount > 0) || (next != null)); - } - - public E next() { - checkModcount(); - checkRefQueue(); - - if (myNullCount > 0) { - myNullCount--; - - return null; - } else { - if (next == null) { - throw new java.util.NoSuchElementException(); - } - - current = next; - currentObj = nextObj; - - // move to next requested - do { - next = next.iterChainNext; - - if (next == null) { - break; - } - - nextObj = next.get(); - } while (next.isEnqueued()); - - return currentObj; + private class KeyIterator extends HashIterator { + public K next() { + return nextEntry().getKey(); } } - public void remove() { - checkModcount(); - - if (current == null) { - throw new IllegalStateException(); - } - - current.remove(); - size--; - } - - void checkModcount() { - if (myModcount != modCount()) { - throw new ConcurrentModificationException(); - } - } - } - - /** Entries of this set */ - static class Entry extends WeakReference { - /** reference to outer WeakSet */ - private WeakSet set; - - // double linked list - Entry prev; - Entry next; - private final int hashcode; - Entry iterChainNext; - Entry iterChainPrev; - - Entry(WeakSet set, E referenced, ReferenceQueue q, Entry next, Entry nextInIter) { - super(referenced, q); - this.set = set; - - this.next = next; - this.prev = null; - - if (next != null) { - next.prev = this; - } - - if (referenced != null) { - hashcode = set.hashIt(referenced); - } else { - hashcode = 0; - } - - chainIntoIter(nextInIter); - } - - @SuppressWarnings("unchecked") - static final Entry[] createArray(int size) { - return new Entry[size]; - } - - void chainIntoIter(Entry nextInIter) { - iterChainNext = nextInIter; - - if (nextInIter != null) { - nextInIter.iterChainPrev = this; + private class EntryIterator extends HashIterator> { + public Map.Entry next() { + return nextEntry(); } } - /** deques itself */ - void remove() { - if (prev != null) { - prev.next = next; + // Views + + private transient Set> entrySet = null; + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + */ + @Override + public Set keySet() { + Set ks = keySet; + return (ks != null ? ks : (keySet = new KeySet())); + } + + private class KeySet extends AbstractSet { + public Iterator iterator() { + return new KeyIterator(); } - if (next != null) { - next.prev = prev; + public int size() { + return SharedKeyWeakHashMap.this.size(); } - if (iterChainNext != null) { - iterChainNext.iterChainPrev = iterChainPrev; + @Override + public boolean contains(Object o) { + return containsKey(o); } - if (iterChainPrev != null) { - iterChainPrev.iterChainNext = iterChainNext; - } else { // root - set.iterChain = iterChainNext; + @Override + public boolean remove(Object o) { + if (containsKey(o)) { + SharedKeyWeakHashMap.this.remove(o); + return true; + } else + return false; } - if (set.entries[hashcode] == this) { - set.entries[hashcode] = next; - } - - prev = null; - next = null; - iterChainNext = null; - iterChainPrev = null; - } - - public int hashCode() { - return hashcode; - } - - public boolean equals(Object o) { - Object oo = get(); - - if (oo == null) { - return false; - } else { - return oo.equals(o); + @Override + public void clear() { + SharedKeyWeakHashMap.this.clear(); } } - public Entry clone(ReferenceQueue q) { - return new Entry(set, get(), q, next != null ? next.clone(q) : null, null); + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + */ + @Override + public Collection values() { + Collection vs = values; + return (vs != null ? vs : (values = new Values())); + } + + private class Values extends AbstractCollection { + public Iterator iterator() { + return new ValueIterator(); + } + + public int size() { + return SharedKeyWeakHashMap.this.size(); + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + + @Override + public void clear() { + SharedKeyWeakHashMap.this.clear(); + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation, or through the + * setValue operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Set.remove, removeAll, retainAll and + * clear operations. It does not support the + * add or addAll operations. + */ + public Set> entrySet() { + Set> es = entrySet; + return es != null ? es : (entrySet = new EntrySet()); + } + + private class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + Entry candidate = getEntry(e.getKey()); + return candidate != null && candidate.equals(e); + } + + @Override + public boolean remove(Object o) { + return removeMapping(o) != null; + } + + public int size() { + return SharedKeyWeakHashMap.this.size(); + } + + @Override + public void clear() { + SharedKeyWeakHashMap.this.clear(); + } + + private List> deepCopy() { + List> list = new ArrayList>(size()); + for (Map.Entry e : this) + // have to use own class since AbstractMap.SimpleEntry is appears only in jdk 1.6 + list.add(new /*AbstractMap.*/SimpleEntry(e)); + return list; + } + + @Override + public Object[] toArray() { + return deepCopy().toArray(); + } + + @Override + public T[] toArray(T[] a) { + return deepCopy().toArray(a); + } + } + + //////////////////////////////////////////////////////////////////////////// + // new changes + + /** + * Applies a supplemental hash function to a given hashCode, which + * defends against poor quality hash functions. This is critical + * because HashMap uses power-of-two length hash tables, that + * otherwise encounter collisions for hashCodes that do not differ + * in lower bits. Note: Null keys always map to hash 0, thus index 0. + */ + static int hash(int h) { + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } + + // Views + + /** + * Each of these fields are initialized to contain an instance of the + * appropriate view the first time this view is requested. The views are + * stateless, so there's no reason to create more than one of each. + */ + transient volatile Set keySet = null; + transient volatile Collection values = null; + + /** + * Put specified key in this set if key is not yet in set. + * returns previous value in set if key already in set. + * + * @param key key to put in set. + * @return the previous set entry equals with key, or + * new key if there were not entry in set. + */ + private K putIfAbsent(K key) { + K k = (K) maskNull(key); + int h = hash(k.hashCode()); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + + Entry e = tab[i]; + while (e != null) { + if (e.hash == h) { + K refedKey = e.get(); + if (eq(k, refedKey)) { + return refedKey; + } + } + e = e.next; + } + + modCount++; + e = tab[i]; + tab[i] = new Entry(k, queue, h, e); + if (++size >= threshold) + resize(tab.length * 2); + return k; } } }