| /* |
| * Copyright 2009 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.util.collect; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.util.AbstractCollection; |
| import java.util.AbstractSet; |
| import java.util.Collection; |
| import java.util.ConcurrentModificationException; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| |
| /** |
| * A memory-efficient hash map. |
| * |
| * @param <K> the key type |
| * @param <V> the value type |
| */ |
| public class HashMap<K, V> implements Map<K, V>, Serializable { |
| |
| /** |
| * In the interest of memory-savings, we start with the smallest feasible |
| * power-of-two table size that can hold three items without rehashing. If we |
| * started with a size of 2, we'd have to expand as soon as the second item |
| * was added. |
| */ |
| private static final int INITIAL_TABLE_SIZE = 4; |
| |
| private abstract class BaseIterator<E> implements Iterator<E> { |
| private Object[] coModCheckKeys = keys; |
| private int index = 0; |
| private int last = -1; |
| |
| public boolean hasNext() { |
| if (coModCheckKeys != keys) { |
| throw new ConcurrentModificationException(); |
| } |
| advanceToItem(); |
| return index < keys.length; |
| } |
| |
| public E next() { |
| if (!hasNext()) { |
| throw new NoSuchElementException(); |
| } |
| last = index; |
| return iteratorItem(index++); |
| } |
| |
| public void remove() { |
| if (last < 0) { |
| throw new IllegalStateException(); |
| } |
| if (coModCheckKeys != keys) { |
| throw new ConcurrentModificationException(); |
| } |
| internalRemove(last); |
| if (keys[last] != null) { |
| // Hole was plugged. |
| index = last; |
| } |
| last = -1; |
| } |
| |
| protected abstract E iteratorItem(int index); |
| |
| private void advanceToItem() { |
| for (; index < keys.length; ++index) { |
| if (keys[index] != null) { |
| return; |
| } |
| } |
| } |
| } |
| |
| private class EntryIterator extends BaseIterator<Entry<K, V>> { |
| @Override |
| protected Entry<K, V> iteratorItem(int index) { |
| return new HashEntry(index); |
| } |
| } |
| |
| private class EntrySet extends AbstractSet<Entry<K, V>> { |
| @Override |
| public boolean add(Entry<K, V> entry) { |
| boolean result = !HashMap.this.containsKey(entry.getKey()); |
| HashMap.this.put(entry.getKey(), entry.getValue()); |
| return result; |
| } |
| |
| @Override |
| public boolean addAll(Collection<? extends Entry<K, V>> c) { |
| HashMap.this.resizeForJoin(c.size()); |
| return super.addAll(c); |
| } |
| |
| @Override |
| public void clear() { |
| HashMap.this.clear(); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean contains(Object o) { |
| if (!(o instanceof Entry)) { |
| return false; |
| } |
| Entry<K, V> entry = (Entry<K, V>) o; |
| V value = HashMap.this.get(entry.getKey()); |
| return HashMap.this.valueEquals(value, entry.getValue()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return HashMap.this.hashCode(); |
| } |
| |
| @Override |
| public Iterator<java.util.Map.Entry<K, V>> iterator() { |
| return new EntryIterator(); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean remove(Object o) { |
| if (!(o instanceof Entry)) { |
| return false; |
| } |
| Entry<K, V> entry = (Entry<K, V>) o; |
| int index = findKey(entry.getKey()); |
| if (index >= 0 && valueEquals(values[index], entry.getValue())) { |
| internalRemove(index); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean removeAll(Collection<?> c) { |
| boolean didRemove = false; |
| for (Object o : c) { |
| didRemove |= remove(o); |
| } |
| return didRemove; |
| } |
| |
| @Override |
| public int size() { |
| return HashMap.this.size; |
| } |
| } |
| |
| private class HashEntry implements Entry<K, V> { |
| private final int index; |
| |
| public HashEntry(int index) { |
| this.index = index; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean equals(Object o) { |
| if (!(o instanceof Entry)) { |
| return false; |
| } |
| Entry<K, V> entry = (Entry<K, V>) o; |
| return keyEquals(getKey(), entry.getKey()) |
| && valueEquals(getValue(), entry.getValue()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public K getKey() { |
| return (K) unmaskNullKey(keys[index]); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public V getValue() { |
| return (V) values[index]; |
| } |
| |
| @Override |
| public int hashCode() { |
| return keyHashCode(getKey()) ^ valueHashCode(getValue()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public V setValue(V value) { |
| V previous = (V) values[index]; |
| values[index] = value; |
| return previous; |
| } |
| |
| @Override |
| public String toString() { |
| return getKey() + "=" + getValue(); |
| } |
| } |
| |
| private class KeyIterator extends BaseIterator<K> { |
| @SuppressWarnings("unchecked") |
| @Override |
| protected K iteratorItem(int index) { |
| return (K) unmaskNullKey(keys[index]); |
| } |
| } |
| |
| private class KeySet extends AbstractSet<K> { |
| @Override |
| public void clear() { |
| HashMap.this.clear(); |
| } |
| |
| @Override |
| public boolean contains(Object o) { |
| return HashMap.this.containsKey(o); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 0; |
| for (int i = 0; i < keys.length; ++i) { |
| Object key = keys[i]; |
| if (key != null) { |
| result += keyHashCode(unmaskNullKey(key)); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public Iterator<K> iterator() { |
| return new KeyIterator(); |
| } |
| |
| @Override |
| public boolean remove(Object o) { |
| int index = findKey(o); |
| if (index >= 0) { |
| internalRemove(index); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean removeAll(Collection<?> c) { |
| boolean didRemove = false; |
| for (Object o : c) { |
| didRemove |= remove(o); |
| } |
| return didRemove; |
| } |
| |
| @Override |
| public int size() { |
| return HashMap.this.size; |
| } |
| } |
| |
| private class ValueIterator extends BaseIterator<V> { |
| @SuppressWarnings("unchecked") |
| @Override |
| protected V iteratorItem(int index) { |
| return (V) values[index]; |
| } |
| } |
| |
| private class Values extends AbstractCollection<V> { |
| @Override |
| public void clear() { |
| HashMap.this.clear(); |
| } |
| |
| @Override |
| public boolean contains(Object o) { |
| return HashMap.this.containsValue(o); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 0; |
| for (int i = 0; i < keys.length; ++i) { |
| if (keys[i] != null) { |
| result += valueHashCode(values[i]); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public Iterator<V> iterator() { |
| return new ValueIterator(); |
| } |
| |
| @Override |
| public boolean remove(Object o) { |
| if (o == null) { |
| for (int i = 0; i < keys.length; ++i) { |
| if (keys[i] != null && values[i] == null) { |
| internalRemove(i); |
| return true; |
| } |
| } |
| } else { |
| for (int i = 0; i < keys.length; ++i) { |
| if (valueEquals(values[i], o)) { |
| internalRemove(i); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean removeAll(Collection<?> c) { |
| boolean didRemove = false; |
| for (Object o : c) { |
| didRemove |= remove(o); |
| } |
| return didRemove; |
| } |
| |
| @Override |
| public int size() { |
| return HashMap.this.size; |
| } |
| } |
| |
| private static final Object NULL_KEY = new Serializable() { |
| Object readResolve() { |
| return NULL_KEY; |
| } |
| }; |
| |
| static Object maskNullKey(Object k) { |
| return (k == null) ? NULL_KEY : k; |
| } |
| |
| static Object unmaskNullKey(Object k) { |
| return (k == NULL_KEY) ? null : k; |
| } |
| |
| /** |
| * Backing store for all the keys; transient due to custom serialization. |
| * Default access to avoid synthetic accessors from inner classes. |
| */ |
| transient Object[] keys; |
| |
| /** |
| * Number of pairs in this set; transient due to custom serialization. Default |
| * access to avoid synthetic accessors from inner classes. |
| */ |
| transient int size = 0; |
| |
| /** |
| * Backing store for all the values; transient due to custom serialization. |
| * Default access to avoid synthetic accessors from inner classes. |
| */ |
| transient Object[] values; |
| |
| public HashMap() { |
| initTable(INITIAL_TABLE_SIZE); |
| } |
| |
| public HashMap(Map<? extends K, ? extends V> m) { |
| int newCapacity = INITIAL_TABLE_SIZE; |
| int expectedSize = m.size(); |
| while (newCapacity * 3 < expectedSize * 4) { |
| newCapacity <<= 1; |
| } |
| |
| initTable(newCapacity); |
| putAll(m); |
| } |
| |
| public void clear() { |
| initTable(INITIAL_TABLE_SIZE); |
| size = 0; |
| } |
| |
| public boolean containsKey(Object key) { |
| return findKey(key) >= 0; |
| } |
| |
| public boolean containsValue(Object value) { |
| if (value == null) { |
| for (int i = 0; i < keys.length; ++i) { |
| if (keys[i] != null && values[i] == null) { |
| return true; |
| } |
| } |
| } else { |
| for (Object existing : values) { |
| if (valueEquals(existing, value)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public Set<Entry<K, V>> entrySet() { |
| return new EntrySet(); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public boolean equals(Object o) { |
| if (!(o instanceof Map)) { |
| return false; |
| } |
| Map<K, V> other = (Map<K, V>) o; |
| return entrySet().equals(other.entrySet()); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public V get(Object key) { |
| int index = findKey(key); |
| return (index < 0) ? null : (V) values[index]; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 0; |
| for (int i = 0; i < keys.length; ++i) { |
| Object key = keys[i]; |
| if (key != null) { |
| result += keyHashCode(unmaskNullKey(key)) ^ valueHashCode(values[i]); |
| } |
| } |
| return result; |
| } |
| |
| public boolean isEmpty() { |
| return size == 0; |
| } |
| |
| public Set<K> keySet() { |
| return new KeySet(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public V put(K key, V value) { |
| int index = findKeyOrEmpty(key); |
| if (keys[index] == null) { |
| // Not in the map, may need to grow. |
| if (ensureSizeFor(++size)) { |
| // If we had to grow the table, must recompute the index. |
| index = findKeyOrEmpty(key); |
| } |
| keys[index] = maskNullKey(key); |
| values[index] = value; |
| return null; |
| } else { |
| // In the map, set a new value; |
| Object previousValue = values[index]; |
| values[index] = value; |
| return (V) previousValue; |
| } |
| } |
| |
| public void putAll(Map<? extends K, ? extends V> m) { |
| resizeForJoin(m.size()); |
| for (Entry<? extends K, ? extends V> entry : m.entrySet()) { |
| put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| public V remove(Object key) { |
| int index = findKey(key); |
| if (index < 0) { |
| return null; |
| } |
| Object previousValue = values[index]; |
| internalRemove(index); |
| return (V) previousValue; |
| } |
| |
| public int size() { |
| return size; |
| } |
| |
| @Override |
| public String toString() { |
| if (size == 0) { |
| return "{}"; |
| } |
| StringBuilder buf = new StringBuilder(32 * size()); |
| buf.append('{'); |
| |
| boolean needComma = false; |
| for (int i = 0; i < keys.length; ++i) { |
| Object key = keys[i]; |
| if (key != null) { |
| if (needComma) { |
| buf.append(',').append(' '); |
| } |
| key = unmaskNullKey(key); |
| Object value = values[i]; |
| buf.append(key == this ? "(this Map)" : key).append('=').append( |
| value == this ? "(this Map)" : value); |
| needComma = true; |
| } |
| } |
| buf.append('}'); |
| return buf.toString(); |
| } |
| |
| public Collection<V> values() { |
| return new Values(); |
| } |
| |
| /** |
| * Adapted from {@link org.apache.commons.collections.map.AbstractHashedMap}. |
| */ |
| @SuppressWarnings("unchecked") |
| protected void doReadObject(ObjectInputStream in) throws IOException, |
| ClassNotFoundException { |
| int capacity = in.readInt(); |
| initTable(capacity); |
| int items = in.readInt(); |
| for (int i = 0; i < items; i++) { |
| Object key = in.readObject(); |
| Object value = in.readObject(); |
| put((K) key, (V) value); |
| } |
| } |
| |
| /** |
| * Adapted from {@link org.apache.commons.collections.map.AbstractHashedMap}. |
| */ |
| protected void doWriteObject(ObjectOutputStream out) throws IOException { |
| out.writeInt(keys.length); |
| out.writeInt(size); |
| for (int i = 0; i < keys.length; ++i) { |
| Object key = keys[i]; |
| if (key != null) { |
| out.writeObject(unmaskNullKey(key)); |
| out.writeObject(values[i]); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether two keys are equal for the purposes of this set. |
| */ |
| protected boolean keyEquals(Object a, Object b) { |
| return (a == null) ? (b == null) : a.equals(b); |
| } |
| |
| /** |
| * Returns the hashCode for a key. |
| */ |
| protected int keyHashCode(Object k) { |
| return (k == null) ? 0 : k.hashCode(); |
| } |
| |
| /** |
| * Returns whether two values are equal for the purposes of this set. |
| */ |
| protected boolean valueEquals(Object a, Object b) { |
| return (a == null) ? (b == null) : a.equals(b); |
| } |
| |
| /** |
| * Returns the hashCode for a value. |
| */ |
| protected int valueHashCode(Object v) { |
| return (v == null) ? 0 : v.hashCode(); |
| } |
| |
| /** |
| * Ensures the map is large enough to contain the specified number of entries. |
| * Default access to avoid synthetic accessors from inner classes. |
| */ |
| boolean ensureSizeFor(int expectedSize) { |
| if (keys.length * 3 >= expectedSize * 4) { |
| return false; |
| } |
| |
| int newCapacity = keys.length << 1; |
| while (newCapacity * 3 < expectedSize * 4) { |
| newCapacity <<= 1; |
| } |
| |
| Object[] oldKeys = keys; |
| Object[] oldValues = values; |
| initTable(newCapacity); |
| for (int i = 0; i < oldKeys.length; ++i) { |
| Object k = oldKeys[i]; |
| if (k != null) { |
| int newIndex = getKeyIndex(unmaskNullKey(k)); |
| while (keys[newIndex] != null) { |
| if (++newIndex == keys.length) { |
| newIndex = 0; |
| } |
| } |
| keys[newIndex] = k; |
| values[newIndex] = oldValues[i]; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns the index in the key table at which a particular key resides, or -1 |
| * if the key is not in the table. Default access to avoid synthetic accessors |
| * from inner classes. |
| */ |
| int findKey(Object k) { |
| int index = getKeyIndex(k); |
| while (true) { |
| Object existing = keys[index]; |
| if (existing == null) { |
| return -1; |
| } |
| if (keyEquals(k, unmaskNullKey(existing))) { |
| return index; |
| } |
| if (++index == keys.length) { |
| index = 0; |
| } |
| } |
| } |
| |
| /** |
| * Returns the index in the key table at which a particular key resides, or |
| * the index of an empty slot in the table where this key should be inserted |
| * if it is not already in the table. Default access to avoid synthetic |
| * accessors from inner classes. |
| */ |
| int findKeyOrEmpty(Object k) { |
| int index = getKeyIndex(k); |
| while (true) { |
| Object existing = keys[index]; |
| if (existing == null) { |
| return index; |
| } |
| if (keyEquals(k, unmaskNullKey(existing))) { |
| return index; |
| } |
| if (++index == keys.length) { |
| index = 0; |
| } |
| } |
| } |
| |
| /** |
| * Removes the entry at the specified index, and performs internal management |
| * to make sure we don't wind up with a hole in the table. Default access to |
| * avoid synthetic accessors from inner classes. |
| */ |
| void internalRemove(int index) { |
| keys[index] = null; |
| values[index] = null; |
| --size; |
| plugHole(index); |
| } |
| |
| /** |
| * Resizes this map to accommodate the minimum size required to join this map |
| * with another map. This is an optimization to prevent multiple resizes |
| * during the join operation. Naively, it would seem like we should resize to |
| * hold {@code (size + otherSize)}. However, the incoming map might have |
| * duplicates with this map; it might even be all duplicates. The correct |
| * behavior when the incoming map is all duplicates is NOT to resize, and |
| * therefore not to invalidate any iterators. |
| * <p> |
| * In practice, this strategy results in a worst-case of two resizes. In the |
| * worst case, where {@code size} and {@code otherSize} are roughly equal and |
| * the sets are completely disjoint, we might do 1 initial rehash and then 1 |
| * additional rehash down the road. But this is an edge case that requires |
| * getting unlucky on both boundaries. Most of the time, we do either 1 |
| * initial rehash or 1 down the road, because doubling the capacity generally |
| * allows this map to absorb an equally-sized disjoint map. |
| */ |
| boolean resizeForJoin(int sizeOther) { |
| return ensureSizeFor(Math.max(size, sizeOther)); |
| } |
| |
| private int getKeyIndex(Object k) { |
| int h = keyHashCode(k); |
| // Copied from Apache's AbstractHashedMap; prevents power-of-two collisions. |
| h += ~(h << 9); |
| h ^= (h >>> 14); |
| h += (h << 4); |
| h ^= (h >>> 10); |
| // Power of two trick. |
| return h & (keys.length - 1); |
| } |
| |
| private void initTable(int capacity) { |
| keys = new Object[capacity]; |
| values = new Object[capacity]; |
| } |
| |
| /** |
| * Tricky, we left a hole in the map, which we have to fill. The only way to |
| * do this is to search forwards through the map shuffling back values that |
| * match this index until we hit a null. |
| */ |
| private void plugHole(int hole) { |
| int index = hole + 1; |
| if (index == keys.length) { |
| index = 0; |
| } |
| while (keys[index] != null) { |
| int targetIndex = getKeyIndex(unmaskNullKey(keys[index])); |
| if (hole < index) { |
| /* |
| * "Normal" case, the index is past the hole and the "bad range" is from |
| * hole (exclusive) to index (inclusive). |
| */ |
| if (!(hole < targetIndex && targetIndex <= index)) { |
| // Plug it! |
| keys[hole] = keys[index]; |
| values[hole] = values[index]; |
| keys[index] = null; |
| values[index] = null; |
| hole = index; |
| } |
| } else { |
| /* |
| * "Wrapped" case, the index is before the hole (we've wrapped) and the |
| * "good range" is from index (exclusive) to hole (inclusive). |
| */ |
| if (index < targetIndex && targetIndex <= hole) { |
| // Plug it! |
| keys[hole] = keys[index]; |
| values[hole] = values[index]; |
| keys[index] = null; |
| values[index] = null; |
| hole = index; |
| } |
| } |
| if (++index == keys.length) { |
| index = 0; |
| } |
| } |
| } |
| |
| private void readObject(ObjectInputStream in) throws IOException, |
| ClassNotFoundException { |
| in.defaultReadObject(); |
| doReadObject(in); |
| } |
| |
| private void writeObject(ObjectOutputStream out) throws IOException { |
| out.defaultWriteObject(); |
| doWriteObject(out); |
| } |
| } |