summaryrefslogtreecommitdiffstats
path: root/guava/src/com/google/common/collect/AbstractBiMap.java
diff options
context:
space:
mode:
Diffstat (limited to 'guava/src/com/google/common/collect/AbstractBiMap.java')
-rw-r--r--guava/src/com/google/common/collect/AbstractBiMap.java404
1 files changed, 404 insertions, 0 deletions
diff --git a/guava/src/com/google/common/collect/AbstractBiMap.java b/guava/src/com/google/common/collect/AbstractBiMap.java
new file mode 100644
index 0000000..dcf2db4
--- /dev/null
+++ b/guava/src/com/google/common/collect/AbstractBiMap.java
@@ -0,0 +1,404 @@
+/*
+ * 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.base.Preconditions.checkState;
+
+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.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * A general-purpose bimap implementation using any two backing {@code Map}
+ * instances.
+ *
+ * <p>Note that this class contains {@code equals()} calls that keep it from
+ * supporting {@code IdentityHashMap} backing maps.
+ *
+ * @author Kevin Bourrillion
+ * @author Mike Bostock
+ */
+@GwtCompatible(emulated = true)
+abstract class AbstractBiMap<K, V> extends ForwardingMap<K, V>
+ implements BiMap<K, V>, Serializable {
+
+ private transient Map<K, V> delegate;
+ transient AbstractBiMap<V, K> inverse;
+
+ /** Package-private constructor for creating a map-backed bimap. */
+ AbstractBiMap(Map<K, V> forward, Map<V, K> backward) {
+ setDelegates(forward, backward);
+ }
+
+ /** Private constructor for inverse bimap. */
+ private AbstractBiMap(Map<K, V> backward, AbstractBiMap<V, K> forward) {
+ delegate = backward;
+ inverse = forward;
+ }
+
+ @Override protected Map<K, V> delegate() {
+ return delegate;
+ }
+
+ /**
+ * Returns its input, or throws an exception if this is not a valid key.
+ */
+ K checkKey(@Nullable K key) {
+ return key;
+ }
+
+ /**
+ * Returns its input, or throws an exception if this is not a valid value.
+ */
+ V checkValue(@Nullable V value) {
+ return value;
+ }
+
+ /**
+ * Specifies the delegate maps going in each direction. Called by the
+ * constructor and by subclasses during deserialization.
+ */
+ void setDelegates(Map<K, V> forward, Map<V, K> backward) {
+ checkState(delegate == null);
+ checkState(inverse == null);
+ checkArgument(forward.isEmpty());
+ checkArgument(backward.isEmpty());
+ checkArgument(forward != backward);
+ delegate = forward;
+ inverse = new Inverse<V, K>(backward, this);
+ }
+
+ void setInverse(AbstractBiMap<V, K> inverse) {
+ this.inverse = inverse;
+ }
+
+ // Query Operations (optimizations)
+
+ @Override public boolean containsValue(Object value) {
+ return inverse.containsKey(value);
+ }
+
+ // Modification Operations
+
+ @Override public V put(K key, V value) {
+ return putInBothMaps(key, value, false);
+ }
+
+ @Override
+ public V forcePut(K key, V value) {
+ return putInBothMaps(key, value, true);
+ }
+
+ private V putInBothMaps(@Nullable K key, @Nullable V value, boolean force) {
+ checkKey(key);
+ checkValue(value);
+ boolean containedKey = containsKey(key);
+ if (containedKey && Objects.equal(value, get(key))) {
+ return value;
+ }
+ if (force) {
+ inverse().remove(value);
+ } else {
+ checkArgument(!containsValue(value), "value already present: %s", value);
+ }
+ V oldValue = delegate.put(key, value);
+ updateInverseMap(key, containedKey, oldValue, value);
+ return oldValue;
+ }
+
+ private void updateInverseMap(
+ K key, boolean containedKey, V oldValue, V newValue) {
+ if (containedKey) {
+ removeFromInverseMap(oldValue);
+ }
+ inverse.delegate.put(newValue, key);
+ }
+
+ @Override public V remove(Object key) {
+ return containsKey(key) ? removeFromBothMaps(key) : null;
+ }
+
+ private V removeFromBothMaps(Object key) {
+ V oldValue = delegate.remove(key);
+ removeFromInverseMap(oldValue);
+ return oldValue;
+ }
+
+ private void removeFromInverseMap(V oldValue) {
+ inverse.delegate.remove(oldValue);
+ }
+
+ // Bulk Operations
+
+ @Override public void putAll(Map<? extends K, ? extends V> map) {
+ for (Entry<? extends K, ? extends V> entry : map.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override public void clear() {
+ delegate.clear();
+ inverse.delegate.clear();
+ }
+
+ // Views
+
+ @Override
+ public BiMap<V, K> inverse() {
+ return inverse;
+ }
+
+ private transient Set<K> keySet;
+
+ @Override public Set<K> keySet() {
+ Set<K> result = keySet;
+ return (result == null) ? keySet = new KeySet() : result;
+ }
+
+ private class KeySet extends ForwardingSet<K> {
+ @Override protected Set<K> delegate() {
+ return delegate.keySet();
+ }
+
+ @Override public void clear() {
+ AbstractBiMap.this.clear();
+ }
+
+ @Override public boolean remove(Object key) {
+ if (!contains(key)) {
+ return false;
+ }
+ removeFromBothMaps(key);
+ return true;
+ }
+
+ @Override public boolean removeAll(Collection<?> keysToRemove) {
+ return standardRemoveAll(keysToRemove);
+ }
+
+ @Override public boolean retainAll(Collection<?> keysToRetain) {
+ return standardRetainAll(keysToRetain);
+ }
+
+ @Override public Iterator<K> iterator() {
+ return Maps.keyIterator(entrySet().iterator());
+ }
+ }
+
+ private transient Set<V> valueSet;
+
+ @Override public Set<V> values() {
+ /*
+ * We can almost reuse the inverse's keySet, except we have to fix the
+ * iteration order so that it is consistent with the forward map.
+ */
+ Set<V> result = valueSet;
+ return (result == null) ? valueSet = new ValueSet() : result;
+ }
+
+ private class ValueSet extends ForwardingSet<V> {
+ final Set<V> valuesDelegate = inverse.keySet();
+
+ @Override protected Set<V> delegate() {
+ return valuesDelegate;
+ }
+
+ @Override public Iterator<V> iterator() {
+ return Maps.valueIterator(entrySet().iterator());
+ }
+
+ @Override public Object[] toArray() {
+ return standardToArray();
+ }
+
+ @Override public <T> T[] toArray(T[] array) {
+ return standardToArray(array);
+ }
+
+ @Override public String toString() {
+ return standardToString();
+ }
+ }
+
+ private transient Set<Entry<K, V>> entrySet;
+
+ @Override public Set<Entry<K, V>> entrySet() {
+ Set<Entry<K, V>> result = entrySet;
+ return (result == null) ? entrySet = new EntrySet() : result;
+ }
+
+ private class EntrySet extends ForwardingSet<Entry<K, V>> {
+ final Set<Entry<K, V>> esDelegate = delegate.entrySet();
+
+ @Override protected Set<Entry<K, V>> delegate() {
+ return esDelegate;
+ }
+
+ @Override public void clear() {
+ AbstractBiMap.this.clear();
+ }
+
+ @Override public boolean remove(Object object) {
+ if (!esDelegate.contains(object)) {
+ return false;
+ }
+
+ // safe because esDelgate.contains(object).
+ Entry<?, ?> entry = (Entry<?, ?>) object;
+ inverse.delegate.remove(entry.getValue());
+ /*
+ * Remove the mapping in inverse before removing from esDelegate because
+ * if entry is part of esDelegate, entry might be invalidated after the
+ * mapping is removed from esDelegate.
+ */
+ esDelegate.remove(entry);
+ return true;
+ }
+
+ @Override public Iterator<Entry<K, V>> iterator() {
+ final Iterator<Entry<K, V>> iterator = esDelegate.iterator();
+ return new Iterator<Entry<K, V>>() {
+ Entry<K, V> entry;
+
+ @Override public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override public Entry<K, V> next() {
+ entry = iterator.next();
+ final Entry<K, V> finalEntry = entry;
+
+ return new ForwardingMapEntry<K, V>() {
+ @Override protected Entry<K, V> delegate() {
+ return finalEntry;
+ }
+
+ @Override public V setValue(V value) {
+ // Preconditions keep the map and inverse consistent.
+ checkState(contains(this), "entry no longer in map");
+ // similar to putInBothMaps, but set via entry
+ if (Objects.equal(value, getValue())) {
+ return value;
+ }
+ checkArgument(!containsValue(value),
+ "value already present: %s", value);
+ V oldValue = finalEntry.setValue(value);
+ checkState(Objects.equal(value, get(getKey())),
+ "entry no longer in map");
+ updateInverseMap(getKey(), true, oldValue, value);
+ return oldValue;
+ }
+ };
+ }
+
+ @Override public void remove() {
+ checkState(entry != null);
+ V value = entry.getValue();
+ iterator.remove();
+ removeFromInverseMap(value);
+ }
+ };
+ }
+
+ // See java.util.Collections.CheckedEntrySet for details on attacks.
+
+ @Override public Object[] toArray() {
+ return standardToArray();
+ }
+ @Override public <T> T[] toArray(T[] array) {
+ return standardToArray(array);
+ }
+ @Override public boolean contains(Object o) {
+ return Maps.containsEntryImpl(delegate(), o);
+ }
+ @Override public boolean containsAll(Collection<?> c) {
+ return standardContainsAll(c);
+ }
+ @Override public boolean removeAll(Collection<?> c) {
+ return standardRemoveAll(c);
+ }
+ @Override public boolean retainAll(Collection<?> c) {
+ return standardRetainAll(c);
+ }
+ }
+
+ /** The inverse of any other {@code AbstractBiMap} subclass. */
+ private static class Inverse<K, V> extends AbstractBiMap<K, V> {
+ private Inverse(Map<K, V> backward, AbstractBiMap<V, K> forward) {
+ super(backward, forward);
+ }
+
+ /*
+ * Serialization stores the forward bimap, the inverse of this inverse.
+ * Deserialization calls inverse() on the forward bimap and returns that
+ * inverse.
+ *
+ * If a bimap and its inverse are serialized together, the deserialized
+ * instances have inverse() methods that return the other.
+ */
+
+ @Override
+ K checkKey(K key) {
+ return inverse.checkValue(key);
+ }
+
+ @Override
+ V checkValue(V value) {
+ return inverse.checkKey(value);
+ }
+
+ /**
+ * @serialData the forward bimap
+ */
+ @GwtIncompatible("java.io.ObjectOuputStream")
+ private void writeObject(ObjectOutputStream stream) throws IOException {
+ stream.defaultWriteObject();
+ stream.writeObject(inverse());
+ }
+
+ @GwtIncompatible("java.io.ObjectInputStream")
+ @SuppressWarnings("unchecked") // reading data stored by writeObject
+ private void readObject(ObjectInputStream stream)
+ throws IOException, ClassNotFoundException {
+ stream.defaultReadObject();
+ setInverse((AbstractBiMap<V, K>) stream.readObject());
+ }
+
+ @GwtIncompatible("Not needed in the emulated source.")
+ Object readResolve() {
+ return inverse().inverse();
+ }
+
+ @GwtIncompatible("Not needed in emulated source.")
+ private static final long serialVersionUID = 0;
+ }
+
+ @GwtIncompatible("Not needed in emulated source.")
+ private static final long serialVersionUID = 0;
+}