jaroslav@1258: /*
jaroslav@1258: * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
jaroslav@1258: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
jaroslav@1258: *
jaroslav@1258: * This code is free software; you can redistribute it and/or modify it
jaroslav@1258: * under the terms of the GNU General Public License version 2 only, as
jaroslav@1258: * published by the Free Software Foundation. Oracle designates this
jaroslav@1258: * particular file as subject to the "Classpath" exception as provided
jaroslav@1258: * by Oracle in the LICENSE file that accompanied this code.
jaroslav@1258: *
jaroslav@1258: * This code is distributed in the hope that it will be useful, but WITHOUT
jaroslav@1258: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
jaroslav@1258: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
jaroslav@1258: * version 2 for more details (a copy is included in the LICENSE file that
jaroslav@1258: * accompanied this code).
jaroslav@1258: *
jaroslav@1258: * You should have received a copy of the GNU General Public License version
jaroslav@1258: * 2 along with this work; if not, write to the Free Software Foundation,
jaroslav@1258: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
jaroslav@1258: *
jaroslav@1258: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
jaroslav@1258: * or visit www.oracle.com if you need additional information or have any
jaroslav@1258: * questions.
jaroslav@1258: */
jaroslav@1258:
jaroslav@1258: package java.util;
jaroslav@1258: import java.lang.ref.WeakReference;
jaroslav@1258: import java.lang.ref.ReferenceQueue;
jaroslav@1258:
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Hash table based implementation of the Map interface, with
jaroslav@1258: * weak keys .
jaroslav@1258: * An entry in a WeakHashMap will automatically be removed when
jaroslav@1258: * its key is no longer in ordinary use. More precisely, the presence of a
jaroslav@1258: * mapping for a given key will not prevent the key from being discarded by the
jaroslav@1258: * garbage collector, that is, made finalizable, finalized, and then reclaimed.
jaroslav@1258: * When a key has been discarded its entry is effectively removed from the map,
jaroslav@1258: * so this class behaves somewhat differently from other Map
jaroslav@1258: * implementations.
jaroslav@1258: *
jaroslav@1258: *
Both null values and the null key are supported. This class has
jaroslav@1258: * performance characteristics similar to those of the HashMap
jaroslav@1258: * class, and has the same efficiency parameters of initial capacity
jaroslav@1258: * and load factor .
jaroslav@1258: *
jaroslav@1258: *
Like most collection classes, this class is not synchronized.
jaroslav@1258: * A synchronized WeakHashMap may be constructed using the
jaroslav@1258: * {@link Collections#synchronizedMap Collections.synchronizedMap}
jaroslav@1258: * method.
jaroslav@1258: *
jaroslav@1258: *
This class is intended primarily for use with key objects whose
jaroslav@1258: * equals methods test for object identity using the
jaroslav@1258: * == operator. Once such a key is discarded it can never be
jaroslav@1258: * recreated, so it is impossible to do a lookup of that key in a
jaroslav@1258: * WeakHashMap at some later time and be surprised that its entry
jaroslav@1258: * has been removed. This class will work perfectly well with key objects
jaroslav@1258: * whose equals methods are not based upon object identity, such
jaroslav@1258: * as String instances. With such recreatable key objects,
jaroslav@1258: * however, the automatic removal of WeakHashMap entries whose
jaroslav@1258: * keys have been discarded may prove to be confusing.
jaroslav@1258: *
jaroslav@1258: *
The behavior of the WeakHashMap class depends in part upon
jaroslav@1258: * the actions of the garbage collector, so several familiar (though not
jaroslav@1258: * required) Map invariants do not hold for this class. Because
jaroslav@1258: * the garbage collector may discard keys at any time, a
jaroslav@1258: * WeakHashMap may behave as though an unknown thread is silently
jaroslav@1258: * removing entries. In particular, even if you synchronize on a
jaroslav@1258: * WeakHashMap instance and invoke none of its mutator methods, it
jaroslav@1258: * is possible for the size method to return smaller values over
jaroslav@1258: * time, for the isEmpty method to return false and
jaroslav@1258: * then true , for the containsKey method to return
jaroslav@1258: * true and later false for a given key, for the
jaroslav@1258: * get method to return a value for a given key but later return
jaroslav@1258: * null , for the put method to return
jaroslav@1258: * null and the remove method to return
jaroslav@1258: * false for a key that previously appeared to be in the map, and
jaroslav@1258: * for successive examinations of the key set, the value collection, and
jaroslav@1258: * the entry set to yield successively smaller numbers of elements.
jaroslav@1258: *
jaroslav@1258: *
Each key object in a WeakHashMap is stored indirectly as
jaroslav@1258: * the referent of a weak reference. Therefore a key will automatically be
jaroslav@1258: * removed only after the weak references to it, both inside and outside of the
jaroslav@1258: * map, have been cleared by the garbage collector.
jaroslav@1258: *
jaroslav@1258: *
Implementation note: The value objects in a
jaroslav@1258: * WeakHashMap are held by ordinary strong references. Thus care
jaroslav@1258: * should be taken to ensure that value objects do not strongly refer to their
jaroslav@1258: * own keys, either directly or indirectly, since that will prevent the keys
jaroslav@1258: * from being discarded. Note that a value object may refer indirectly to its
jaroslav@1258: * key via the WeakHashMap itself; that is, a value object may
jaroslav@1258: * strongly refer to some other key object whose associated value object, in
jaroslav@1258: * turn, strongly refers to the key of the first value object. One way
jaroslav@1258: * to deal with this is to wrap values themselves within
jaroslav@1258: * WeakReferences before
jaroslav@1258: * inserting, as in: m.put(key, new WeakReference(value)) ,
jaroslav@1258: * and then unwrapping upon each get .
jaroslav@1258: *
jaroslav@1258: *
The iterators returned by the iterator method of the collections
jaroslav@1258: * returned by all of this class's "collection view methods" are
jaroslav@1258: * fail-fast : if the map is structurally modified at any time after the
jaroslav@1258: * iterator is created, in any way except through the iterator's own
jaroslav@1258: * remove method, the iterator will throw a {@link
jaroslav@1258: * ConcurrentModificationException}. Thus, in the face of concurrent
jaroslav@1258: * modification, the iterator fails quickly and cleanly, rather than risking
jaroslav@1258: * arbitrary, non-deterministic behavior at an undetermined time in the future.
jaroslav@1258: *
jaroslav@1258: *
Note that the fail-fast behavior of an iterator cannot be guaranteed
jaroslav@1258: * as it is, generally speaking, impossible to make any hard guarantees in the
jaroslav@1258: * presence of unsynchronized concurrent modification. Fail-fast iterators
jaroslav@1258: * throw ConcurrentModificationException on a best-effort basis.
jaroslav@1258: * Therefore, it would be wrong to write a program that depended on this
jaroslav@1258: * exception for its correctness: the fail-fast behavior of iterators
jaroslav@1258: * should be used only to detect bugs.
jaroslav@1258: *
jaroslav@1258: *
This class is a member of the
jaroslav@1258: *
jaroslav@1258: * Java Collections Framework .
jaroslav@1258: *
jaroslav@1258: * @param the type of keys maintained by this map
jaroslav@1258: * @param the type of mapped values
jaroslav@1258: *
jaroslav@1258: * @author Doug Lea
jaroslav@1258: * @author Josh Bloch
jaroslav@1258: * @author Mark Reinhold
jaroslav@1258: * @since 1.2
jaroslav@1258: * @see java.util.HashMap
jaroslav@1258: * @see java.lang.ref.WeakReference
jaroslav@1258: */
jaroslav@1258: public class WeakHashMap
jaroslav@1258: extends AbstractMap
jaroslav@1258: implements Map {
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The default initial capacity -- MUST be a power of two.
jaroslav@1258: */
jaroslav@1258: private static final int DEFAULT_INITIAL_CAPACITY = 16;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The maximum capacity, used if a higher value is implicitly specified
jaroslav@1258: * by either of the constructors with arguments.
jaroslav@1258: * MUST be a power of two <= 1<<30.
jaroslav@1258: */
jaroslav@1258: private static final int MAXIMUM_CAPACITY = 1 << 30;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The load factor used when none specified in constructor.
jaroslav@1258: */
jaroslav@1258: private static final float DEFAULT_LOAD_FACTOR = 0.75f;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The table, resized as necessary. Length MUST Always be a power of two.
jaroslav@1258: */
jaroslav@1258: Entry[] table;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The number of key-value mappings contained in this weak hash map.
jaroslav@1258: */
jaroslav@1258: private int size;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The next size value at which to resize (capacity * load factor).
jaroslav@1258: */
jaroslav@1258: private int threshold;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The load factor for the hash table.
jaroslav@1258: */
jaroslav@1258: private final float loadFactor;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Reference queue for cleared WeakEntries
jaroslav@1258: */
jaroslav@1258: private final ReferenceQueue queue = new ReferenceQueue<>();
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The number of times this WeakHashMap has been structurally modified.
jaroslav@1258: * Structural modifications are those that change the number of
jaroslav@1258: * mappings in the map or otherwise modify its internal structure
jaroslav@1258: * (e.g., rehash). This field is used to make iterators on
jaroslav@1258: * Collection-views of the map fail-fast.
jaroslav@1258: *
jaroslav@1258: * @see ConcurrentModificationException
jaroslav@1258: */
jaroslav@1258: int modCount;
jaroslav@1258:
jaroslav@1258: @SuppressWarnings("unchecked")
jaroslav@1258: private Entry[] newTable(int n) {
jaroslav@1258: return (Entry[]) new Entry[n];
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Constructs a new, empty WeakHashMap with the given initial
jaroslav@1258: * capacity and the given load factor.
jaroslav@1258: *
jaroslav@1258: * @param initialCapacity The initial capacity of the WeakHashMap
jaroslav@1258: * @param loadFactor The load factor of the WeakHashMap
jaroslav@1258: * @throws IllegalArgumentException if the initial capacity is negative,
jaroslav@1258: * or if the load factor is nonpositive.
jaroslav@1258: */
jaroslav@1258: public WeakHashMap(int initialCapacity, float loadFactor) {
jaroslav@1258: if (initialCapacity < 0)
jaroslav@1258: throw new IllegalArgumentException("Illegal Initial Capacity: "+
jaroslav@1258: initialCapacity);
jaroslav@1258: if (initialCapacity > MAXIMUM_CAPACITY)
jaroslav@1258: initialCapacity = MAXIMUM_CAPACITY;
jaroslav@1258:
jaroslav@1258: if (loadFactor <= 0 || Float.isNaN(loadFactor))
jaroslav@1258: throw new IllegalArgumentException("Illegal Load factor: "+
jaroslav@1258: loadFactor);
jaroslav@1258: int capacity = 1;
jaroslav@1258: while (capacity < initialCapacity)
jaroslav@1258: capacity <<= 1;
jaroslav@1258: table = newTable(capacity);
jaroslav@1258: this.loadFactor = loadFactor;
jaroslav@1258: threshold = (int)(capacity * loadFactor);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Constructs a new, empty WeakHashMap with the given initial
jaroslav@1258: * capacity and the default load factor (0.75).
jaroslav@1258: *
jaroslav@1258: * @param initialCapacity The initial capacity of the WeakHashMap
jaroslav@1258: * @throws IllegalArgumentException if the initial capacity is negative
jaroslav@1258: */
jaroslav@1258: public WeakHashMap(int initialCapacity) {
jaroslav@1258: this(initialCapacity, DEFAULT_LOAD_FACTOR);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Constructs a new, empty WeakHashMap with the default initial
jaroslav@1258: * capacity (16) and load factor (0.75).
jaroslav@1258: */
jaroslav@1258: public WeakHashMap() {
jaroslav@1258: this.loadFactor = DEFAULT_LOAD_FACTOR;
jaroslav@1258: threshold = DEFAULT_INITIAL_CAPACITY;
jaroslav@1258: table = newTable(DEFAULT_INITIAL_CAPACITY);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Constructs a new WeakHashMap with the same mappings as the
jaroslav@1258: * specified map. The WeakHashMap is created with the default
jaroslav@1258: * load factor (0.75) and an initial capacity sufficient to hold the
jaroslav@1258: * mappings in the specified map.
jaroslav@1258: *
jaroslav@1258: * @param m the map whose mappings are to be placed in this map
jaroslav@1258: * @throws NullPointerException if the specified map is null
jaroslav@1258: * @since 1.3
jaroslav@1258: */
jaroslav@1258: public WeakHashMap(Map extends K, ? extends V> m) {
jaroslav@1258: this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 16),
jaroslav@1258: DEFAULT_LOAD_FACTOR);
jaroslav@1258: putAll(m);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: // internal utilities
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Value representing null keys inside tables.
jaroslav@1258: */
jaroslav@1258: private static final Object NULL_KEY = new Object();
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Use NULL_KEY for key if it is null.
jaroslav@1258: */
jaroslav@1258: private static Object maskNull(Object key) {
jaroslav@1258: return (key == null) ? NULL_KEY : key;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns internal representation of null key back to caller as null.
jaroslav@1258: */
jaroslav@1258: static Object unmaskNull(Object key) {
jaroslav@1258: return (key == NULL_KEY) ? null : key;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Checks for equality of non-null reference x and possibly-null y. By
jaroslav@1258: * default uses Object.equals.
jaroslav@1258: */
jaroslav@1258: private static boolean eq(Object x, Object y) {
jaroslav@1258: return x == y || x.equals(y);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns index for hash code h.
jaroslav@1258: */
jaroslav@1258: private static int indexFor(int h, int length) {
jaroslav@1258: return h & (length-1);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Expunges stale entries from the table.
jaroslav@1258: */
jaroslav@1258: private void expungeStaleEntries() {
jaroslav@1258: for (Object x; (x = queue.poll()) != null; ) {
jaroslav@1258: synchronized (queue) {
jaroslav@1258: @SuppressWarnings("unchecked")
jaroslav@1258: Entry e = (Entry) x;
jaroslav@1258: int i = indexFor(e.hash, table.length);
jaroslav@1258:
jaroslav@1258: Entry prev = table[i];
jaroslav@1258: Entry p = prev;
jaroslav@1258: while (p != null) {
jaroslav@1258: Entry next = p.next;
jaroslav@1258: if (p == e) {
jaroslav@1258: if (prev == e)
jaroslav@1258: table[i] = next;
jaroslav@1258: else
jaroslav@1258: prev.next = next;
jaroslav@1258: // Must not null out e.next;
jaroslav@1258: // stale entries may be in use by a HashIterator
jaroslav@1258: e.value = null; // Help GC
jaroslav@1258: size--;
jaroslav@1258: break;
jaroslav@1258: }
jaroslav@1258: prev = p;
jaroslav@1258: p = next;
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns the table after first expunging stale entries.
jaroslav@1258: */
jaroslav@1258: private Entry[] getTable() {
jaroslav@1258: expungeStaleEntries();
jaroslav@1258: return table;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns the number of key-value mappings in this map.
jaroslav@1258: * This result is a snapshot, and may not reflect unprocessed
jaroslav@1258: * entries that will be removed before next attempted access
jaroslav@1258: * because they are no longer referenced.
jaroslav@1258: */
jaroslav@1258: public int size() {
jaroslav@1258: if (size == 0)
jaroslav@1258: return 0;
jaroslav@1258: expungeStaleEntries();
jaroslav@1258: return size;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns true if this map contains no key-value mappings.
jaroslav@1258: * This result is a snapshot, and may not reflect unprocessed
jaroslav@1258: * entries that will be removed before next attempted access
jaroslav@1258: * because they are no longer referenced.
jaroslav@1258: */
jaroslav@1258: public boolean isEmpty() {
jaroslav@1258: return size() == 0;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns the value to which the specified key is mapped,
jaroslav@1258: * or {@code null} if this map contains no mapping for the key.
jaroslav@1258: *
jaroslav@1258: * More formally, if this map contains a mapping from a key
jaroslav@1258: * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
jaroslav@1258: * key.equals(k))}, then this method returns {@code v}; otherwise
jaroslav@1258: * it returns {@code null}. (There can be at most one such mapping.)
jaroslav@1258: *
jaroslav@1258: *
A return value of {@code null} does not necessarily
jaroslav@1258: * indicate that the map contains no mapping for the key; it's also
jaroslav@1258: * possible that the map explicitly maps the key to {@code null}.
jaroslav@1258: * The {@link #containsKey containsKey} operation may be used to
jaroslav@1258: * distinguish these two cases.
jaroslav@1258: *
jaroslav@1258: * @see #put(Object, Object)
jaroslav@1258: */
jaroslav@1258: public V get(Object key) {
jaroslav@1258: Object k = maskNull(key);
jaroslav@1258: int h = HashMap.hash(k.hashCode());
jaroslav@1258: Entry[] tab = getTable();
jaroslav@1258: int index = indexFor(h, tab.length);
jaroslav@1258: Entry e = tab[index];
jaroslav@1258: while (e != null) {
jaroslav@1258: if (e.hash == h && eq(k, e.get()))
jaroslav@1258: return e.value;
jaroslav@1258: e = e.next;
jaroslav@1258: }
jaroslav@1258: return null;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns true if this map contains a mapping for the
jaroslav@1258: * specified key.
jaroslav@1258: *
jaroslav@1258: * @param key The key whose presence in this map is to be tested
jaroslav@1258: * @return true if there is a mapping for key ;
jaroslav@1258: * false otherwise
jaroslav@1258: */
jaroslav@1258: public boolean containsKey(Object key) {
jaroslav@1258: return getEntry(key) != null;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns the entry associated with the specified key in this map.
jaroslav@1258: * Returns null if the map contains no mapping for this key.
jaroslav@1258: */
jaroslav@1258: Entry getEntry(Object key) {
jaroslav@1258: Object k = maskNull(key);
jaroslav@1258: int h = HashMap.hash(k.hashCode());
jaroslav@1258: Entry[] tab = getTable();
jaroslav@1258: int index = indexFor(h, tab.length);
jaroslav@1258: Entry e = tab[index];
jaroslav@1258: while (e != null && !(e.hash == h && eq(k, e.get())))
jaroslav@1258: e = e.next;
jaroslav@1258: return e;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Associates the specified value with the specified key in this map.
jaroslav@1258: * If the map previously contained a mapping for this key, the old
jaroslav@1258: * value is replaced.
jaroslav@1258: *
jaroslav@1258: * @param key key with which the specified value is to be associated.
jaroslav@1258: * @param value value to be associated with the specified key.
jaroslav@1258: * @return the previous value associated with key , or
jaroslav@1258: * null if there was no mapping for key .
jaroslav@1258: * (A null return can also indicate that the map
jaroslav@1258: * previously associated null with key .)
jaroslav@1258: */
jaroslav@1258: public V put(K key, V value) {
jaroslav@1258: Object k = maskNull(key);
jaroslav@1258: int h = HashMap.hash(k.hashCode());
jaroslav@1258: Entry[] tab = getTable();
jaroslav@1258: int i = indexFor(h, tab.length);
jaroslav@1258:
jaroslav@1258: for (Entry e = tab[i]; e != null; e = e.next) {
jaroslav@1258: if (h == e.hash && eq(k, e.get())) {
jaroslav@1258: V oldValue = e.value;
jaroslav@1258: if (value != oldValue)
jaroslav@1258: e.value = value;
jaroslav@1258: return oldValue;
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: modCount++;
jaroslav@1258: Entry e = tab[i];
jaroslav@1258: tab[i] = new Entry<>(k, value, queue, h, e);
jaroslav@1258: if (++size >= threshold)
jaroslav@1258: resize(tab.length * 2);
jaroslav@1258: return null;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Rehashes the contents of this map into a new array with a
jaroslav@1258: * larger capacity. This method is called automatically when the
jaroslav@1258: * number of keys in this map reaches its threshold.
jaroslav@1258: *
jaroslav@1258: * If current capacity is MAXIMUM_CAPACITY, this method does not
jaroslav@1258: * resize the map, but sets threshold to Integer.MAX_VALUE.
jaroslav@1258: * This has the effect of preventing future calls.
jaroslav@1258: *
jaroslav@1258: * @param newCapacity the new capacity, MUST be a power of two;
jaroslav@1258: * must be greater than current capacity unless current
jaroslav@1258: * capacity is MAXIMUM_CAPACITY (in which case value
jaroslav@1258: * is irrelevant).
jaroslav@1258: */
jaroslav@1258: void resize(int newCapacity) {
jaroslav@1258: Entry[] oldTable = getTable();
jaroslav@1258: int oldCapacity = oldTable.length;
jaroslav@1258: if (oldCapacity == MAXIMUM_CAPACITY) {
jaroslav@1258: threshold = Integer.MAX_VALUE;
jaroslav@1258: return;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: Entry[] newTable = newTable(newCapacity);
jaroslav@1258: transfer(oldTable, newTable);
jaroslav@1258: table = newTable;
jaroslav@1258:
jaroslav@1258: /*
jaroslav@1258: * If ignoring null elements and processing ref queue caused massive
jaroslav@1258: * shrinkage, then restore old table. This should be rare, but avoids
jaroslav@1258: * unbounded expansion of garbage-filled tables.
jaroslav@1258: */
jaroslav@1258: if (size >= threshold / 2) {
jaroslav@1258: threshold = (int)(newCapacity * loadFactor);
jaroslav@1258: } else {
jaroslav@1258: expungeStaleEntries();
jaroslav@1258: transfer(newTable, oldTable);
jaroslav@1258: table = oldTable;
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /** Transfers all entries from src to dest tables */
jaroslav@1258: private void transfer(Entry[] src, Entry[] dest) {
jaroslav@1258: for (int j = 0; j < src.length; ++j) {
jaroslav@1258: Entry e = src[j];
jaroslav@1258: src[j] = null;
jaroslav@1258: while (e != null) {
jaroslav@1258: Entry next = e.next;
jaroslav@1258: Object key = e.get();
jaroslav@1258: if (key == null) {
jaroslav@1258: e.next = null; // Help GC
jaroslav@1258: e.value = null; // " "
jaroslav@1258: size--;
jaroslav@1258: } else {
jaroslav@1258: int i = indexFor(e.hash, dest.length);
jaroslav@1258: e.next = dest[i];
jaroslav@1258: dest[i] = e;
jaroslav@1258: }
jaroslav@1258: e = next;
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Copies all of the mappings from the specified map to this map.
jaroslav@1258: * These mappings will replace any mappings that this map had for any
jaroslav@1258: * of the keys currently in the specified map.
jaroslav@1258: *
jaroslav@1258: * @param m mappings to be stored in this map.
jaroslav@1258: * @throws NullPointerException if the specified map is null.
jaroslav@1258: */
jaroslav@1258: public void putAll(Map extends K, ? extends V> m) {
jaroslav@1258: int numKeysToBeAdded = m.size();
jaroslav@1258: if (numKeysToBeAdded == 0)
jaroslav@1258: return;
jaroslav@1258:
jaroslav@1258: /*
jaroslav@1258: * Expand the map if the map if the number of mappings to be added
jaroslav@1258: * is greater than or equal to threshold. This is conservative; the
jaroslav@1258: * obvious condition is (m.size() + size) >= threshold, but this
jaroslav@1258: * condition could result in a map with twice the appropriate capacity,
jaroslav@1258: * if the keys to be added overlap with the keys already in this map.
jaroslav@1258: * By using the conservative calculation, we subject ourself
jaroslav@1258: * to at most one extra resize.
jaroslav@1258: */
jaroslav@1258: if (numKeysToBeAdded > threshold) {
jaroslav@1258: int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
jaroslav@1258: if (targetCapacity > MAXIMUM_CAPACITY)
jaroslav@1258: targetCapacity = MAXIMUM_CAPACITY;
jaroslav@1258: int newCapacity = table.length;
jaroslav@1258: while (newCapacity < targetCapacity)
jaroslav@1258: newCapacity <<= 1;
jaroslav@1258: if (newCapacity > table.length)
jaroslav@1258: resize(newCapacity);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: for (Map.Entry extends K, ? extends V> e : m.entrySet())
jaroslav@1258: put(e.getKey(), e.getValue());
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Removes the mapping for a key from this weak hash map if it is present.
jaroslav@1258: * More formally, if this map contains a mapping from key k to
jaroslav@1258: * value v such that (key==null ? k==null :
jaroslav@1258: * key.equals(k))
, that mapping is removed. (The map can contain
jaroslav@1258: * at most one such mapping.)
jaroslav@1258: *
jaroslav@1258: * Returns the value to which this map previously associated the key,
jaroslav@1258: * or null if the map contained no mapping for the key. A
jaroslav@1258: * return value of null does not necessarily indicate
jaroslav@1258: * that the map contained no mapping for the key; it's also possible
jaroslav@1258: * that the map explicitly mapped the key to null .
jaroslav@1258: *
jaroslav@1258: *
The map will not contain a mapping for the specified key once the
jaroslav@1258: * call returns.
jaroslav@1258: *
jaroslav@1258: * @param key key whose mapping is to be removed from the map
jaroslav@1258: * @return the previous value associated with key , or
jaroslav@1258: * null if there was no mapping for key
jaroslav@1258: */
jaroslav@1258: public V remove(Object key) {
jaroslav@1258: Object k = maskNull(key);
jaroslav@1258: int h = HashMap.hash(k.hashCode());
jaroslav@1258: Entry[] tab = getTable();
jaroslav@1258: int i = indexFor(h, tab.length);
jaroslav@1258: Entry prev = tab[i];
jaroslav@1258: Entry e = prev;
jaroslav@1258:
jaroslav@1258: while (e != null) {
jaroslav@1258: Entry next = e.next;
jaroslav@1258: if (h == e.hash && eq(k, e.get())) {
jaroslav@1258: modCount++;
jaroslav@1258: size--;
jaroslav@1258: if (prev == e)
jaroslav@1258: tab[i] = next;
jaroslav@1258: else
jaroslav@1258: prev.next = next;
jaroslav@1258: return e.value;
jaroslav@1258: }
jaroslav@1258: prev = e;
jaroslav@1258: e = next;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: return null;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /** Special version of remove needed by Entry set */
jaroslav@1258: boolean removeMapping(Object o) {
jaroslav@1258: if (!(o instanceof Map.Entry))
jaroslav@1258: return false;
jaroslav@1258: Entry[] tab = getTable();
jaroslav@1258: Map.Entry,?> entry = (Map.Entry,?>)o;
jaroslav@1258: Object k = maskNull(entry.getKey());
jaroslav@1258: int h = HashMap.hash(k.hashCode());
jaroslav@1258: int i = indexFor(h, tab.length);
jaroslav@1258: Entry prev = tab[i];
jaroslav@1258: Entry e = prev;
jaroslav@1258:
jaroslav@1258: while (e != null) {
jaroslav@1258: Entry next = e.next;
jaroslav@1258: if (h == e.hash && e.equals(entry)) {
jaroslav@1258: modCount++;
jaroslav@1258: size--;
jaroslav@1258: if (prev == e)
jaroslav@1258: tab[i] = next;
jaroslav@1258: else
jaroslav@1258: prev.next = next;
jaroslav@1258: return true;
jaroslav@1258: }
jaroslav@1258: prev = e;
jaroslav@1258: e = next;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: return false;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Removes all of the mappings from this map.
jaroslav@1258: * The map will be empty after this call returns.
jaroslav@1258: */
jaroslav@1258: public void clear() {
jaroslav@1258: // clear out ref queue. We don't need to expunge entries
jaroslav@1258: // since table is getting cleared.
jaroslav@1258: while (queue.poll() != null)
jaroslav@1258: ;
jaroslav@1258:
jaroslav@1258: modCount++;
jaroslav@1258: Arrays.fill(table, null);
jaroslav@1258: size = 0;
jaroslav@1258:
jaroslav@1258: // Allocation of array may have caused GC, which may have caused
jaroslav@1258: // additional entries to go stale. Removing these entries from the
jaroslav@1258: // reference queue will make them eligible for reclamation.
jaroslav@1258: while (queue.poll() != null)
jaroslav@1258: ;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns true if this map maps one or more keys to the
jaroslav@1258: * specified value.
jaroslav@1258: *
jaroslav@1258: * @param value value whose presence in this map is to be tested
jaroslav@1258: * @return true if this map maps one or more keys to the
jaroslav@1258: * specified value
jaroslav@1258: */
jaroslav@1258: public boolean containsValue(Object value) {
jaroslav@1258: if (value==null)
jaroslav@1258: return containsNullValue();
jaroslav@1258:
jaroslav@1258: Entry[] tab = getTable();
jaroslav@1258: for (int i = tab.length; i-- > 0;)
jaroslav@1258: for (Entry e = tab[i]; e != null; e = e.next)
jaroslav@1258: if (value.equals(e.value))
jaroslav@1258: return true;
jaroslav@1258: return false;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Special-case code for containsValue with null argument
jaroslav@1258: */
jaroslav@1258: private boolean containsNullValue() {
jaroslav@1258: Entry[] tab = getTable();
jaroslav@1258: for (int i = tab.length; i-- > 0;)
jaroslav@1258: for (Entry e = tab[i]; e != null; e = e.next)
jaroslav@1258: if (e.value==null)
jaroslav@1258: return true;
jaroslav@1258: return false;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * The entries in this hash table extend WeakReference, using its main ref
jaroslav@1258: * field as the key.
jaroslav@1258: */
jaroslav@1258: private static class Entry extends WeakReference implements Map.Entry {
jaroslav@1258: V value;
jaroslav@1258: final int hash;
jaroslav@1258: Entry next;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Creates new entry.
jaroslav@1258: */
jaroslav@1258: Entry(Object key, V value,
jaroslav@1258: ReferenceQueue queue,
jaroslav@1258: int hash, Entry next) {
jaroslav@1258: super(key, queue);
jaroslav@1258: this.value = value;
jaroslav@1258: this.hash = hash;
jaroslav@1258: this.next = next;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: @SuppressWarnings("unchecked")
jaroslav@1258: public K getKey() {
jaroslav@1258: return (K) WeakHashMap.unmaskNull(get());
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public V getValue() {
jaroslav@1258: return value;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public V setValue(V newValue) {
jaroslav@1258: V oldValue = value;
jaroslav@1258: value = newValue;
jaroslav@1258: return oldValue;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public boolean equals(Object o) {
jaroslav@1258: if (!(o instanceof Map.Entry))
jaroslav@1258: return false;
jaroslav@1258: Map.Entry,?> e = (Map.Entry,?>)o;
jaroslav@1258: K k1 = getKey();
jaroslav@1258: Object k2 = e.getKey();
jaroslav@1258: if (k1 == k2 || (k1 != null && k1.equals(k2))) {
jaroslav@1258: V v1 = getValue();
jaroslav@1258: Object v2 = e.getValue();
jaroslav@1258: if (v1 == v2 || (v1 != null && v1.equals(v2)))
jaroslav@1258: return true;
jaroslav@1258: }
jaroslav@1258: return false;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public int hashCode() {
jaroslav@1258: K k = getKey();
jaroslav@1258: V v = getValue();
jaroslav@1258: return ((k==null ? 0 : k.hashCode()) ^
jaroslav@1258: (v==null ? 0 : v.hashCode()));
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public String toString() {
jaroslav@1258: return getKey() + "=" + getValue();
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private abstract class HashIterator implements Iterator {
jaroslav@1258: private int index;
jaroslav@1258: private Entry entry = null;
jaroslav@1258: private Entry lastReturned = null;
jaroslav@1258: private int expectedModCount = modCount;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Strong reference needed to avoid disappearance of key
jaroslav@1258: * between hasNext and next
jaroslav@1258: */
jaroslav@1258: private Object nextKey = null;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Strong reference needed to avoid disappearance of key
jaroslav@1258: * between nextEntry() and any use of the entry
jaroslav@1258: */
jaroslav@1258: private Object currentKey = null;
jaroslav@1258:
jaroslav@1258: HashIterator() {
jaroslav@1258: index = isEmpty() ? 0 : table.length;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public boolean hasNext() {
jaroslav@1258: Entry[] t = table;
jaroslav@1258:
jaroslav@1258: while (nextKey == null) {
jaroslav@1258: Entry e = entry;
jaroslav@1258: int i = index;
jaroslav@1258: while (e == null && i > 0)
jaroslav@1258: e = t[--i];
jaroslav@1258: entry = e;
jaroslav@1258: index = i;
jaroslav@1258: if (e == null) {
jaroslav@1258: currentKey = null;
jaroslav@1258: return false;
jaroslav@1258: }
jaroslav@1258: nextKey = e.get(); // hold on to key in strong ref
jaroslav@1258: if (nextKey == null)
jaroslav@1258: entry = entry.next;
jaroslav@1258: }
jaroslav@1258: return true;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /** The common parts of next() across different types of iterators */
jaroslav@1258: protected Entry nextEntry() {
jaroslav@1258: if (modCount != expectedModCount)
jaroslav@1258: throw new ConcurrentModificationException();
jaroslav@1258: if (nextKey == null && !hasNext())
jaroslav@1258: throw new NoSuchElementException();
jaroslav@1258:
jaroslav@1258: lastReturned = entry;
jaroslav@1258: entry = entry.next;
jaroslav@1258: currentKey = nextKey;
jaroslav@1258: nextKey = null;
jaroslav@1258: return lastReturned;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public void remove() {
jaroslav@1258: if (lastReturned == null)
jaroslav@1258: throw new IllegalStateException();
jaroslav@1258: if (modCount != expectedModCount)
jaroslav@1258: throw new ConcurrentModificationException();
jaroslav@1258:
jaroslav@1258: WeakHashMap.this.remove(currentKey);
jaroslav@1258: expectedModCount = modCount;
jaroslav@1258: lastReturned = null;
jaroslav@1258: currentKey = null;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private class ValueIterator extends HashIterator {
jaroslav@1258: public V next() {
jaroslav@1258: return nextEntry().value;
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private class KeyIterator extends HashIterator {
jaroslav@1258: public K next() {
jaroslav@1258: return nextEntry().getKey();
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private class EntryIterator extends HashIterator> {
jaroslav@1258: public Map.Entry next() {
jaroslav@1258: return nextEntry();
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: // Views
jaroslav@1258:
jaroslav@1258: private transient Set> entrySet = null;
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns a {@link Set} view of the keys contained in this map.
jaroslav@1258: * The set is backed by the map, so changes to the map are
jaroslav@1258: * reflected in the set, and vice-versa. If the map is modified
jaroslav@1258: * while an iteration over the set is in progress (except through
jaroslav@1258: * the iterator's own remove operation), the results of
jaroslav@1258: * the iteration are undefined. The set supports element removal,
jaroslav@1258: * which removes the corresponding mapping from the map, via the
jaroslav@1258: * Iterator.remove , Set.remove ,
jaroslav@1258: * removeAll , retainAll , and clear
jaroslav@1258: * operations. It does not support the add or addAll
jaroslav@1258: * operations.
jaroslav@1258: */
jaroslav@1258: public Set keySet() {
jaroslav@1258: Set ks = keySet;
jaroslav@1258: return (ks != null ? ks : (keySet = new KeySet()));
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private class KeySet extends AbstractSet {
jaroslav@1258: public Iterator iterator() {
jaroslav@1258: return new KeyIterator();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public int size() {
jaroslav@1258: return WeakHashMap.this.size();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public boolean contains(Object o) {
jaroslav@1258: return containsKey(o);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public boolean remove(Object o) {
jaroslav@1258: if (containsKey(o)) {
jaroslav@1258: WeakHashMap.this.remove(o);
jaroslav@1258: return true;
jaroslav@1258: }
jaroslav@1258: else
jaroslav@1258: return false;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public void clear() {
jaroslav@1258: WeakHashMap.this.clear();
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns a {@link Collection} view of the values contained in this map.
jaroslav@1258: * The collection is backed by the map, so changes to the map are
jaroslav@1258: * reflected in the collection, and vice-versa. If the map is
jaroslav@1258: * modified while an iteration over the collection is in progress
jaroslav@1258: * (except through the iterator's own remove operation),
jaroslav@1258: * the results of the iteration are undefined. The collection
jaroslav@1258: * supports element removal, which removes the corresponding
jaroslav@1258: * mapping from the map, via the Iterator.remove ,
jaroslav@1258: * Collection.remove , removeAll ,
jaroslav@1258: * retainAll and clear operations. It does not
jaroslav@1258: * support the add or addAll operations.
jaroslav@1258: */
jaroslav@1258: public Collection values() {
jaroslav@1258: Collection vs = values;
jaroslav@1258: return (vs != null) ? vs : (values = new Values());
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private class Values extends AbstractCollection {
jaroslav@1258: public Iterator iterator() {
jaroslav@1258: return new ValueIterator();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public int size() {
jaroslav@1258: return WeakHashMap.this.size();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public boolean contains(Object o) {
jaroslav@1258: return containsValue(o);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public void clear() {
jaroslav@1258: WeakHashMap.this.clear();
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: /**
jaroslav@1258: * Returns a {@link Set} view of the mappings contained in this map.
jaroslav@1258: * The set is backed by the map, so changes to the map are
jaroslav@1258: * reflected in the set, and vice-versa. If the map is modified
jaroslav@1258: * while an iteration over the set is in progress (except through
jaroslav@1258: * the iterator's own remove operation, or through the
jaroslav@1258: * setValue operation on a map entry returned by the
jaroslav@1258: * iterator) the results of the iteration are undefined. The set
jaroslav@1258: * supports element removal, which removes the corresponding
jaroslav@1258: * mapping from the map, via the Iterator.remove ,
jaroslav@1258: * Set.remove , removeAll , retainAll and
jaroslav@1258: * clear operations. It does not support the
jaroslav@1258: * add or addAll operations.
jaroslav@1258: */
jaroslav@1258: public Set> entrySet() {
jaroslav@1258: Set> es = entrySet;
jaroslav@1258: return es != null ? es : (entrySet = new EntrySet());
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private class EntrySet extends AbstractSet> {
jaroslav@1258: public Iterator> iterator() {
jaroslav@1258: return new EntryIterator();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public boolean contains(Object o) {
jaroslav@1258: if (!(o instanceof Map.Entry))
jaroslav@1258: return false;
jaroslav@1258: Map.Entry,?> e = (Map.Entry,?>)o;
jaroslav@1258: Entry candidate = getEntry(e.getKey());
jaroslav@1258: return candidate != null && candidate.equals(e);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public boolean remove(Object o) {
jaroslav@1258: return removeMapping(o);
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public int size() {
jaroslav@1258: return WeakHashMap.this.size();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public void clear() {
jaroslav@1258: WeakHashMap.this.clear();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: private List> deepCopy() {
jaroslav@1258: List> list = new ArrayList<>(size());
jaroslav@1258: for (Map.Entry e : this)
jaroslav@1258: list.add(new AbstractMap.SimpleEntry<>(e));
jaroslav@1258: return list;
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public Object[] toArray() {
jaroslav@1258: return deepCopy().toArray();
jaroslav@1258: }
jaroslav@1258:
jaroslav@1258: public T[] toArray(T[] a) {
jaroslav@1258: return deepCopy().toArray(a);
jaroslav@1258: }
jaroslav@1258: }
jaroslav@1258: }