/* * Copyright (C) 2008 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.common.collect; import com.google.common.annotations.GwtCompatible; import com.google.common.collect.ImmutableSet.ArrayImmutableSet; import com.google.common.collect.ImmutableSet.TransformedImmutableSet; import static com.google.common.base.Preconditions.checkNotNull; /** * Implementation of {@link ImmutableMap} with two or more entries. * * @author Jesse Wilson * @author Kevin Bourrillion */ @GwtCompatible(serializable = true) final class RegularImmutableMap<K, V> extends ImmutableMap<K, V> { private final transient Entry<K, V>[] entries; // entries in insertion order private final transient Object[] table; // alternating keys and values // 'and' with an int then shift to get a table index private final transient int mask; private final transient int keySetHashCode; RegularImmutableMap(Entry<?, ?>... immutableEntries) { // each of our 6 callers carefully put only Entry<K, V>s into the array! // checkNotNull for GWT. @SuppressWarnings("unchecked") Entry<K, V>[] tmp = (Entry<K, V>[]) checkNotNull(immutableEntries); this.entries = tmp; int tableSize = Hashing.chooseTableSize(immutableEntries.length); table = new Object[tableSize * 2]; mask = tableSize - 1; int keySetHashCodeMutable = 0; for (Entry<K, V> entry : this.entries) { checkNotNull(entry); // checkNotNull for GWT. K key = checkNotNull(entry.getKey()); // checkNotNull for GWT. int keyHashCode = key.hashCode(); for (int i = Hashing.smear(keyHashCode); true; i++) { int index = (i & mask) * 2; Object existing = table[index]; if (existing == null) { V value = checkNotNull(entry.getValue()); // checkNotNull for GWT. table[index] = key; table[index + 1] = value; keySetHashCodeMutable += keyHashCode; break; } else if (existing.equals(key)) { throw new IllegalArgumentException("duplicate key: " + key); } } } keySetHashCode = keySetHashCodeMutable; } @Override public V get(Object key) { if (key == null) { return null; } for (int i = Hashing.smear(key.hashCode()); true; i++) { int index = (i & mask) * 2; Object candidate = table[index]; if (candidate == null) { return null; } if (candidate.equals(key)) { // we're careful to store only V's at odd indices @SuppressWarnings("unchecked") V value = (V) table[index + 1]; return value; } } } public int size() { return entries.length; } @Override public boolean isEmpty() { return false; } @Override public boolean containsValue(Object value) { if (value == null) { return false; } for (Entry<K, V> entry : entries) { if (entry.getValue().equals(value)) { return true; } } return false; } // TODO: Serialization of the map views should serialize the map, and // deserialization should call entrySet(), keySet(), or values() on the // deserialized map. The views are serializable since the Immutable* classes // are. private transient ImmutableSet<Entry<K, V>> entrySet; @Override public ImmutableSet<Entry<K, V>> entrySet() { ImmutableSet<Entry<K, V>> es = entrySet; return (es == null) ? (entrySet = new EntrySet<K, V>(this)) : es; } @SuppressWarnings("serial") // uses writeReplace(), not default serialization private static class EntrySet<K, V> extends ArrayImmutableSet<Entry<K, V>> { final transient RegularImmutableMap<K, V> map; EntrySet(RegularImmutableMap<K, V> map) { super(map.entries); this.map = map; } @Override public boolean contains(Object target) { if (target instanceof Entry) { Entry<?, ?> entry = (Entry<?, ?>) target; V mappedValue = map.get(entry.getKey()); return mappedValue != null && mappedValue.equals(entry.getValue()); } return false; } } private transient ImmutableSet<K> keySet; @Override public ImmutableSet<K> keySet() { ImmutableSet<K> ks = keySet; return (ks == null) ? (keySet = new KeySet<K, V>(this)) : ks; } @SuppressWarnings("serial") // uses writeReplace(), not default serialization private static class KeySet<K, V> extends TransformedImmutableSet<Entry<K, V>, K> { final RegularImmutableMap<K, V> map; KeySet(RegularImmutableMap<K, V> map) { super(map.entries, map.keySetHashCode); this.map = map; } @Override K transform(Entry<K, V> element) { return element.getKey(); } @Override public boolean contains(Object target) { return map.containsKey(target); } } private transient ImmutableCollection<V> values; @Override public ImmutableCollection<V> values() { ImmutableCollection<V> v = values; return (v == null) ? (values = new Values<V>(this)) : v; } @SuppressWarnings("serial") // uses writeReplace(), not default serialization private static class Values<V> extends ImmutableCollection<V> { final RegularImmutableMap<?, V> map; Values(RegularImmutableMap<?, V> map) { this.map = map; } public int size() { return map.entries.length; } @Override public UnmodifiableIterator<V> iterator() { return new AbstractIterator<V>() { int index = 0; @Override protected V computeNext() { return (index < map.entries.length) ? map.entries[index++].getValue() : endOfData(); } }; } @Override public boolean contains(Object target) { return map.containsValue(target); } } @Override public String toString() { StringBuilder result = new StringBuilder(size() * 16).append('{'); Collections2.standardJoiner.appendTo(result, entries); return result.append('}').toString(); } // This class is never actually serialized directly, but we have to make the // warning go away (and suppressing would suppress for all nested classes too) private static final long serialVersionUID = 0; }