/* * Copyright (C) 2007 The Guava Authors * * 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.common.collect; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.CollectPreconditions.checkNonnegative; import static com.google.common.collect.CollectPreconditions.checkRemove; import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; import com.google.common.base.Objects; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.AbstractMap; import java.util.Arrays; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import javax.annotation.Nullable; /** * A {@link BiMap} backed by two hash tables. This implementation allows null keys and values. A * {@code HashBiMap} and its inverse are both serializable. * * <p>See the Guava User Guide article on <a href= * "http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#BiMap"> {@code BiMap} * </a>. * * @author Louis Wasserman * @author Mike Bostock * @since 2.0 (imported from Google Collections Library) */ @GwtCompatible(emulated = true) public final class HashBiMap<K, V> extends AbstractMap<K, V> implements BiMap<K, V>, Serializable { /** * Returns a new, empty {@code HashBiMap} with the default initial capacity (16). */ public static <K, V> HashBiMap<K, V> create() { return create(16); } /** * Constructs a new, empty bimap with the specified expected size. * * @param expectedSize the expected number of entries * @throws IllegalArgumentException if the specified expected size is negative */ public static <K, V> HashBiMap<K, V> create(int expectedSize) { return new HashBiMap<K, V>(expectedSize); } /** * Constructs a new bimap containing initial values from {@code map}. The bimap is created with an * initial capacity sufficient to hold the mappings in the specified map. */ public static <K, V> HashBiMap<K, V> create(Map<? extends K, ? extends V> map) { HashBiMap<K, V> bimap = create(map.size()); bimap.putAll(map); return bimap; } private static final class BiEntry<K, V> extends ImmutableEntry<K, V> { final int keyHash; final int valueHash; @Nullable BiEntry<K, V> nextInKToVBucket; @Nullable BiEntry<K, V> nextInVToKBucket; BiEntry(K key, int keyHash, V value, int valueHash) { super(key, value); this.keyHash = keyHash; this.valueHash = valueHash; } } private static final double LOAD_FACTOR = 1.0; private transient BiEntry<K, V>[] hashTableKToV; private transient BiEntry<K, V>[] hashTableVToK; private transient int size; private transient int mask; private transient int modCount; private HashBiMap(int expectedSize) { init(expectedSize); } private void init(int expectedSize) { checkNonnegative(expectedSize, "expectedSize"); int tableSize = Hashing.closedTableSize(expectedSize, LOAD_FACTOR); this.hashTableKToV = createTable(tableSize); this.hashTableVToK = createTable(tableSize); this.mask = tableSize - 1; this.modCount = 0; this.size = 0; } /** * Finds and removes {@code entry} from the bucket linked lists in both the * key-to-value direction and the value-to-key direction. */ private void delete(BiEntry<K, V> entry) { int keyBucket = entry.keyHash & mask; BiEntry<K, V> prevBucketEntry = null; for (BiEntry<K, V> bucketEntry = hashTableKToV[keyBucket]; true; bucketEntry = bucketEntry.nextInKToVBucket) { if (bucketEntry == entry) { if (prevBucketEntry == null) { hashTableKToV[keyBucket] = entry.nextInKToVBucket; } else { prevBucketEntry.nextInKToVBucket = entry.nextInKToVBucket; } break; } prevBucketEntry = bucketEntry; } int valueBucket = entry.valueHash & mask; prevBucketEntry = null; for (BiEntry<K, V> bucketEntry = hashTableVToK[valueBucket];; bucketEntry = bucketEntry.nextInVToKBucket) { if (bucketEntry == entry) { if (prevBucketEntry == null) { hashTableVToK[valueBucket] = entry.nextInVToKBucket; } else { prevBucketEntry.nextInVToKBucket = entry.nextInVToKBucket; } break; } prevBucketEntry = bucketEntry; } size--; modCount++; } private void insert(BiEntry<K, V> entry) { int keyBucket = entry.keyHash & mask; entry.nextInKToVBucket = hashTableKToV[keyBucket]; hashTableKToV[keyBucket] = entry; int valueBucket = entry.valueHash & mask; entry.nextInVToKBucket = hashTableVToK[valueBucket]; hashTableVToK[valueBucket] = entry; size++; modCount++; } private static int hash(@Nullable Object o) { return Hashing.smear((o == null) ? 0 : o.hashCode()); } private BiEntry<K, V> seekByKey(@Nullable Object key, int keyHash) { for (BiEntry<K, V> entry = hashTableKToV[keyHash & mask]; entry != null; entry = entry.nextInKToVBucket) { if (keyHash == entry.keyHash && Objects.equal(key, entry.key)) { return entry; } } return null; } private BiEntry<K, V> seekByValue(@Nullable Object value, int valueHash) { for (BiEntry<K, V> entry = hashTableVToK[valueHash & mask]; entry != null; entry = entry.nextInVToKBucket) { if (valueHash == entry.valueHash && Objects.equal(value, entry.value)) { return entry; } } return null; } @Override public boolean containsKey(@Nullable Object key) { return seekByKey(key, hash(key)) != null; } @Override public boolean containsValue(@Nullable Object value) { return seekByValue(value, hash(value)) != null; } @Nullable @Override public V get(@Nullable Object key) { BiEntry<K, V> entry = seekByKey(key, hash(key)); return (entry == null) ? null : entry.value; } @Override public V put(@Nullable K key, @Nullable V value) { return put(key, value, false); } @Override public V forcePut(@Nullable K key, @Nullable V value) { return put(key, value, true); } private V put(@Nullable K key, @Nullable V value, boolean force) { int keyHash = hash(key); int valueHash = hash(value); BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash); if (oldEntryForKey != null && valueHash == oldEntryForKey.valueHash && Objects.equal(value, oldEntryForKey.value)) { return value; } BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash); if (oldEntryForValue != null) { if (force) { delete(oldEntryForValue); } else { throw new IllegalArgumentException("value already present: " + value); } } if (oldEntryForKey != null) { delete(oldEntryForKey); } BiEntry<K, V> newEntry = new BiEntry<K, V>(key, keyHash, value, valueHash); insert(newEntry); rehashIfNecessary(); return (oldEntryForKey == null) ? null : oldEntryForKey.value; } @Nullable private K putInverse(@Nullable V value, @Nullable K key, boolean force) { int valueHash = hash(value); int keyHash = hash(key); BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash); if (oldEntryForValue != null && keyHash == oldEntryForValue.keyHash && Objects.equal(key, oldEntryForValue.key)) { return key; } BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash); if (oldEntryForKey != null) { if (force) { delete(oldEntryForKey); } else { throw new IllegalArgumentException("value already present: " + key); } } if (oldEntryForValue != null) { delete(oldEntryForValue); } BiEntry<K, V> newEntry = new BiEntry<K, V>(key, keyHash, value, valueHash); insert(newEntry); rehashIfNecessary(); return (oldEntryForValue == null) ? null : oldEntryForValue.key; } private void rehashIfNecessary() { BiEntry<K, V>[] oldKToV = hashTableKToV; if (Hashing.needsResizing(size, oldKToV.length, LOAD_FACTOR)) { int newTableSize = oldKToV.length * 2; this.hashTableKToV = createTable(newTableSize); this.hashTableVToK = createTable(newTableSize); this.mask = newTableSize - 1; this.size = 0; for (int bucket = 0; bucket < oldKToV.length; bucket++) { BiEntry<K, V> entry = oldKToV[bucket]; while (entry != null) { BiEntry<K, V> nextEntry = entry.nextInKToVBucket; insert(entry); entry = nextEntry; } } this.modCount++; } } @SuppressWarnings("unchecked") private BiEntry<K, V>[] createTable(int length) { return new BiEntry[length]; } @Override public V remove(@Nullable Object key) { BiEntry<K, V> entry = seekByKey(key, hash(key)); if (entry == null) { return null; } else { delete(entry); return entry.value; } } @Override public void clear() { size = 0; Arrays.fill(hashTableKToV, null); Arrays.fill(hashTableVToK, null); modCount++; } @Override public int size() { return size; } abstract class Itr<T> implements Iterator<T> { int nextBucket = 0; BiEntry<K, V> next = null; BiEntry<K, V> toRemove = null; int expectedModCount = modCount; private void checkForConcurrentModification() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } @Override public boolean hasNext() { checkForConcurrentModification(); if (next != null) { return true; } while (nextBucket < hashTableKToV.length) { if (hashTableKToV[nextBucket] != null) { next = hashTableKToV[nextBucket++]; return true; } nextBucket++; } return false; } @Override public T next() { checkForConcurrentModification(); if (!hasNext()) { throw new NoSuchElementException(); } BiEntry<K, V> entry = next; next = entry.nextInKToVBucket; toRemove = entry; return output(entry); } @Override public void remove() { checkForConcurrentModification(); checkRemove(toRemove != null); delete(toRemove); expectedModCount = modCount; toRemove = null; } abstract T output(BiEntry<K, V> entry); } @Override public Set<K> keySet() { return new KeySet(); } private final class KeySet extends Maps.KeySet<K, V> { KeySet() { super(HashBiMap.this); } @Override public Iterator<K> iterator() { return new Itr<K>() { @Override K output(BiEntry<K, V> entry) { return entry.key; } }; } @Override public boolean remove(@Nullable Object o) { BiEntry<K, V> entry = seekByKey(o, hash(o)); if (entry == null) { return false; } else { delete(entry); return true; } } } @Override public Set<V> values() { return inverse().keySet(); } @Override public Set<Entry<K, V>> entrySet() { return new EntrySet(); } private final class EntrySet extends Maps.EntrySet<K, V> { @Override Map<K, V> map() { return HashBiMap.this; } @Override public Iterator<Entry<K, V>> iterator() { return new Itr<Entry<K, V>>() { @Override Entry<K, V> output(BiEntry<K, V> entry) { return new MapEntry(entry); } class MapEntry extends AbstractMapEntry<K, V> { BiEntry<K, V> delegate; MapEntry(BiEntry<K, V> entry) { this.delegate = entry; } @Override public K getKey() { return delegate.key; } @Override public V getValue() { return delegate.value; } @Override public V setValue(V value) { V oldValue = delegate.value; int valueHash = hash(value); if (valueHash == delegate.valueHash && Objects.equal(value, oldValue)) { return value; } checkArgument( seekByValue(value, valueHash) == null, "value already present: %s", value); delete(delegate); BiEntry<K, V> newEntry = new BiEntry<K, V>(delegate.key, delegate.keyHash, value, valueHash); insert(newEntry); expectedModCount = modCount; if (toRemove == delegate) { toRemove = newEntry; } delegate = newEntry; return oldValue; } } }; } } private transient BiMap<V, K> inverse; @Override public BiMap<V, K> inverse() { return (inverse == null) ? inverse = new Inverse() : inverse; } private final class Inverse extends AbstractMap<V, K> implements BiMap<V, K>, Serializable { BiMap<K, V> forward() { return HashBiMap.this; } @Override public int size() { return size; } @Override public void clear() { forward().clear(); } @Override public boolean containsKey(@Nullable Object value) { return forward().containsValue(value); } @Override public K get(@Nullable Object value) { BiEntry<K, V> entry = seekByValue(value, hash(value)); return (entry == null) ? null : entry.key; } @Override public K put(@Nullable V value, @Nullable K key) { return putInverse(value, key, false); } @Override public K forcePut(@Nullable V value, @Nullable K key) { return putInverse(value, key, true); } @Override public K remove(@Nullable Object value) { BiEntry<K, V> entry = seekByValue(value, hash(value)); if (entry == null) { return null; } else { delete(entry); return entry.key; } } @Override public BiMap<K, V> inverse() { return forward(); } @Override public Set<V> keySet() { return new InverseKeySet(); } private final class InverseKeySet extends Maps.KeySet<V, K> { InverseKeySet() { super(Inverse.this); } @Override public boolean remove(@Nullable Object o) { BiEntry<K, V> entry = seekByValue(o, hash(o)); if (entry == null) { return false; } else { delete(entry); return true; } } @Override public Iterator<V> iterator() { return new Itr<V>() { @Override V output(BiEntry<K, V> entry) { return entry.value; } }; } } @Override public Set<K> values() { return forward().keySet(); } @Override public Set<Entry<V, K>> entrySet() { return new Maps.EntrySet<V, K>() { @Override Map<V, K> map() { return Inverse.this; } @Override public Iterator<Entry<V, K>> iterator() { return new Itr<Entry<V, K>>() { @Override Entry<V, K> output(BiEntry<K, V> entry) { return new InverseEntry(entry); } class InverseEntry extends AbstractMapEntry<V, K> { BiEntry<K, V> delegate; InverseEntry(BiEntry<K, V> entry) { this.delegate = entry; } @Override public V getKey() { return delegate.value; } @Override public K getValue() { return delegate.key; } @Override public K setValue(K key) { K oldKey = delegate.key; int keyHash = hash(key); if (keyHash == delegate.keyHash && Objects.equal(key, oldKey)) { return key; } checkArgument(seekByKey(key, keyHash) == null, "value already present: %s", key); delete(delegate); BiEntry<K, V> newEntry = new BiEntry<K, V>(key, keyHash, delegate.value, delegate.valueHash); insert(newEntry); expectedModCount = modCount; // This is safe because entries can only get bumped up to earlier in the iteration, // so they can't get revisited. return oldKey; } } }; } }; } Object writeReplace() { return new InverseSerializedForm<K, V>(HashBiMap.this); } } private static final class InverseSerializedForm<K, V> implements Serializable { private final HashBiMap<K, V> bimap; InverseSerializedForm(HashBiMap<K, V> bimap) { this.bimap = bimap; } Object readResolve() { return bimap.inverse(); } } /** * @serialData the number of entries, first key, first value, second key, second value, and so on. */ @GwtIncompatible("java.io.ObjectOutputStream") private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMap(this, stream); } @GwtIncompatible("java.io.ObjectInputStream") private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int size = Serialization.readCount(stream); init(size); Serialization.populateMap(this, stream, size); } @GwtIncompatible("Not needed in emulated source") private static final long serialVersionUID = 0; }