diff --git a/guava/src/com/google/common/collect/ImmutableBiMap.java b/guava/src/com/google/common/collect/ImmutableBiMap.java index ae431c3c0818..7dfd4b6fe44e 100644 --- a/guava/src/com/google/common/collect/ImmutableBiMap.java +++ b/guava/src/com/google/common/collect/ImmutableBiMap.java @@ -18,10 +18,8 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.ImmutableMap.Builder; import java.util.Map; -import java.util.Map.Entry; /** * An immutable {@link BiMap} with reliable user-specified iteration order. Does @@ -249,7 +247,7 @@ public static ImmutableBiMap copyOf( Entry entry = (Entry) entryArray[0]; return of(entry.getKey(), entry.getValue()); default: - return new RegularImmutableBiMap(entryArray); + return new RegularImmutableBiMap(entryArray.length, entryArray); } } diff --git a/guava/src/com/google/common/collect/ImmutableMap.java b/guava/src/com/google/common/collect/ImmutableMap.java index 666ce8088d73..5ce877e01431 100644 --- a/guava/src/com/google/common/collect/ImmutableMap.java +++ b/guava/src/com/google/common/collect/ImmutableMap.java @@ -20,7 +20,6 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.ImmutableMapEntry.TerminalEntry; import java.io.Serializable; import java.util.Collections; @@ -116,8 +115,8 @@ public static ImmutableMap of( *

A call to {@link Map.Entry#setValue} on the returned entry will always * throw {@link UnsupportedOperationException}. */ - static TerminalEntry entryOf(K key, V value) { - return new TerminalEntry(key, value); + static ImmutableMapEntry entryOf(K key, V value) { + return new ImmutableMapEntry(key, value); } /** @@ -157,7 +156,7 @@ static void checkNoConflict(boolean safe, String conflictDescription, * @since 2.0 (imported from Google Collections Library) */ public static class Builder { - TerminalEntry[] entries; + ImmutableMapEntry[] entries; int size; /** @@ -170,7 +169,7 @@ public Builder() { @SuppressWarnings("unchecked") Builder(int initialCapacity) { - this.entries = new TerminalEntry[initialCapacity]; + this.entries = new ImmutableMapEntry[initialCapacity]; this.size = 0; } @@ -187,7 +186,7 @@ private void ensureCapacity(int minCapacity) { */ public Builder put(K key, V value) { ensureCapacity(size + 1); - TerminalEntry entry = entryOf(key, value); + ImmutableMapEntry entry = entryOf(key, value); // don't inline this: we want to fail atomically if key or value is null entries[size++] = entry; return this; @@ -302,7 +301,7 @@ public static ImmutableMap copyOf( Entry onlyEntry = (Entry) entryArray[0]; return of(onlyEntry.getKey(), onlyEntry.getValue()); default: - return new RegularImmutableMap(entryArray); + return new RegularImmutableMap(entryArray.length, entryArray); } } diff --git a/guava/src/com/google/common/collect/ImmutableMapEntry.java b/guava/src/com/google/common/collect/ImmutableMapEntry.java index 1a75e8f7c5fd..7bed3cd064a4 100644 --- a/guava/src/com/google/common/collect/ImmutableMapEntry.java +++ b/guava/src/com/google/common/collect/ImmutableMapEntry.java @@ -27,11 +27,14 @@ * hash buckets for the key and the value. This allows reuse in {@link RegularImmutableMap} and * {@link RegularImmutableBiMap}, which don't have to recopy the entries created by their * {@code Builder} implementations. + * + * This base implementation has no key or value pointers, so instances of ImmutableMapEntry + * (but not its subclasses) can be reused when copied from one ImmutableMap to another. * * @author Louis Wasserman */ @GwtIncompatible("unnecessary") -abstract class ImmutableMapEntry extends ImmutableEntry { +class ImmutableMapEntry extends ImmutableEntry { /** * Creates an {@code ImmutableMapEntry} array to hold parameterized entries. The * result must never be upcast back to ImmutableMapEntry[] (or Object[], etc.), or @@ -53,30 +56,57 @@ static ImmutableMapEntry[] createEntryArray(int size) { } @Nullable - abstract ImmutableMapEntry getNextInKeyBucket(); + ImmutableMapEntry getNextInKeyBucket() { + return null; + } @Nullable - abstract ImmutableMapEntry getNextInValueBucket(); - - static final class TerminalEntry extends ImmutableMapEntry { - TerminalEntry(ImmutableMapEntry contents) { - super(contents); - } + ImmutableMapEntry getNextInValueBucket() { + return null; + } + + /** + * Returns true if this entry has no bucket links and can safely be reused as a terminal + * entry in a bucket in another map. + */ + boolean isReusable() { + return true; + } + + static class NonTerminalImmutableMapEntry extends ImmutableMapEntry { + private final transient ImmutableMapEntry nextInKeyBucket; - TerminalEntry(K key, V value) { + NonTerminalImmutableMapEntry(K key, V value, ImmutableMapEntry nextInKeyBucket) { super(key, value); + this.nextInKeyBucket = nextInKeyBucket; } @Override @Nullable - ImmutableMapEntry getNextInKeyBucket() { - return null; + final ImmutableMapEntry getNextInKeyBucket() { + return nextInKeyBucket; + } + + @Override + final boolean isReusable() { + return false; + } + } + + static final class NonTerminalImmutableBiMapEntry + extends NonTerminalImmutableMapEntry { + private final transient ImmutableMapEntry nextInValueBucket; + + NonTerminalImmutableBiMapEntry(K key, V value, ImmutableMapEntry nextInKeyBucket, + ImmutableMapEntry nextInValueBucket) { + super(key, value, nextInKeyBucket); + this.nextInValueBucket = nextInValueBucket; } @Override @Nullable ImmutableMapEntry getNextInValueBucket() { - return null; + return nextInValueBucket; } } } diff --git a/guava/src/com/google/common/collect/ImmutableSortedMap.java b/guava/src/com/google/common/collect/ImmutableSortedMap.java index 5ee52eb39d6a..8081ae800c55 100644 --- a/guava/src/com/google/common/collect/ImmutableSortedMap.java +++ b/guava/src/com/google/common/collect/ImmutableSortedMap.java @@ -23,14 +23,11 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.ImmutableMap.Builder; -import com.google.common.collect.ImmutableMapEntry.TerminalEntry; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Map; -import java.util.Map.Entry; import java.util.NavigableMap; import java.util.SortedMap; import java.util.TreeMap; @@ -108,7 +105,7 @@ private static ImmutableSortedMap of(Comparator comparat } private static , V> ImmutableSortedMap - ofEntries(TerminalEntry... entries) { + ofEntries(ImmutableMapEntry... entries) { return fromEntries(Ordering.natural(), false, entries, entries.length); } diff --git a/guava/src/com/google/common/collect/RegularImmutableBiMap.java b/guava/src/com/google/common/collect/RegularImmutableBiMap.java index 0aed45ba7c16..7f47b9c668a1 100644 --- a/guava/src/com/google/common/collect/RegularImmutableBiMap.java +++ b/guava/src/com/google/common/collect/RegularImmutableBiMap.java @@ -16,12 +16,13 @@ package com.google.common.collect; +import static com.google.common.base.Preconditions.checkPositionIndex; import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; import static com.google.common.collect.ImmutableMapEntry.createEntryArray; import static com.google.common.collect.RegularImmutableMap.checkNoConflictInKeyBucket; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.ImmutableMapEntry.TerminalEntry; +import com.google.common.collect.ImmutableMapEntry.NonTerminalImmutableBiMapEntry; import java.io.Serializable; @@ -35,76 +36,31 @@ @GwtCompatible(serializable = true, emulated = true) @SuppressWarnings("serial") // uses writeReplace(), not default serialization class RegularImmutableBiMap extends ImmutableBiMap { - + static final double MAX_LOAD_FACTOR = 1.2; - + private final transient ImmutableMapEntry[] keyTable; private final transient ImmutableMapEntry[] valueTable; private final transient ImmutableMapEntry[] entries; private final transient int mask; private final transient int hashCode; - - RegularImmutableBiMap(TerminalEntry... entriesToAdd) { + + RegularImmutableBiMap(ImmutableMapEntry... entriesToAdd) { this(entriesToAdd.length, entriesToAdd); } - - /** - * Constructor for RegularImmutableBiMap that takes as input an array of {@code TerminalEntry} - * entries. Assumes that these entries have already been checked for null. - * - *

This allows reuse of the entry objects from the array in the actual implementation. - */ - RegularImmutableBiMap(int n, TerminalEntry[] entriesToAdd) { - int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR); - this.mask = tableSize - 1; - ImmutableMapEntry[] keyTable = createEntryArray(tableSize); - ImmutableMapEntry[] valueTable = createEntryArray(tableSize); - ImmutableMapEntry[] entries = createEntryArray(n); - int hashCode = 0; - - for (int i = 0; i < n; i++) { - @SuppressWarnings("unchecked") - TerminalEntry entry = (TerminalEntry) entriesToAdd[i]; - K key = entry.getKey(); - V value = entry.getValue(); - - int keyHash = key.hashCode(); - int valueHash = value.hashCode(); - int keyBucket = Hashing.smear(keyHash) & mask; - int valueBucket = Hashing.smear(valueHash) & mask; - - ImmutableMapEntry nextInKeyBucket = keyTable[keyBucket]; - checkNoConflictInKeyBucket(key, entry, nextInKeyBucket); - ImmutableMapEntry nextInValueBucket = valueTable[valueBucket]; - checkNoConflictInValueBucket(value, entry, nextInValueBucket); - ImmutableMapEntry newEntry = - (nextInKeyBucket == null && nextInValueBucket == null) - ? entry - : new NonTerminalBiMapEntry(entry, nextInKeyBucket, nextInValueBucket); - keyTable[keyBucket] = newEntry; - valueTable[valueBucket] = newEntry; - entries[i] = newEntry; - hashCode += keyHash ^ valueHash; - } - - this.keyTable = keyTable; - this.valueTable = valueTable; - this.entries = entries; - this.hashCode = hashCode; - } - + /** * Constructor for RegularImmutableBiMap that makes no assumptions about the input entries. */ - RegularImmutableBiMap(Entry[] entriesToAdd) { - int n = entriesToAdd.length; + RegularImmutableBiMap(int n, Entry[] entriesToAdd) { + checkPositionIndex(n, entriesToAdd.length); int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR); this.mask = tableSize - 1; ImmutableMapEntry[] keyTable = createEntryArray(tableSize); ImmutableMapEntry[] valueTable = createEntryArray(tableSize); ImmutableMapEntry[] entries = createEntryArray(n); int hashCode = 0; - + for (int i = 0; i < n; i++) { @SuppressWarnings("unchecked") Entry entry = (Entry) entriesToAdd[i]; @@ -115,27 +71,39 @@ class RegularImmutableBiMap extends ImmutableBiMap { int valueHash = value.hashCode(); int keyBucket = Hashing.smear(keyHash) & mask; int valueBucket = Hashing.smear(valueHash) & mask; - + ImmutableMapEntry nextInKeyBucket = keyTable[keyBucket]; checkNoConflictInKeyBucket(key, entry, nextInKeyBucket); ImmutableMapEntry nextInValueBucket = valueTable[valueBucket]; checkNoConflictInValueBucket(value, entry, nextInValueBucket); - ImmutableMapEntry newEntry = - (nextInKeyBucket == null && nextInValueBucket == null) - ? new TerminalEntry(key, value) - : new NonTerminalBiMapEntry(key, value, nextInKeyBucket, nextInValueBucket); + ImmutableMapEntry newEntry; + if (nextInValueBucket == null && nextInKeyBucket == null) { + /* + * TODO(user): consider using a NonTerminalImmutableMapEntry when nextInKeyBucket is + * nonnull but nextInValueBucket is null. This may save a few bytes on some platforms, but + * 2-morphic call sites are often optimized much better than 3-morphic, so it'd require + * benchmarking. + */ + boolean reusable = entry instanceof ImmutableMapEntry + && ((ImmutableMapEntry) entry).isReusable(); + newEntry = + reusable ? (ImmutableMapEntry) entry : new ImmutableMapEntry(key, value); + } else { + newEntry = new NonTerminalImmutableBiMapEntry( + key, value, nextInKeyBucket, nextInValueBucket); + } keyTable[keyBucket] = newEntry; valueTable[valueBucket] = newEntry; entries[i] = newEntry; hashCode += keyHash ^ valueHash; } - + this.keyTable = keyTable; this.valueTable = valueTable; this.entries = entries; this.hashCode = hashCode; } - + // checkNoConflictInKeyBucket is static imported from RegularImmutableMap private static void checkNoConflictInValueBucket(Object value, Entry entry, @@ -144,38 +112,6 @@ private static void checkNoConflictInValueBucket(Object value, Entry entry checkNoConflict(!value.equals(valueBucketHead.getValue()), "value", entry, valueBucketHead); } } - - private static final class NonTerminalBiMapEntry extends ImmutableMapEntry { - @Nullable private final ImmutableMapEntry nextInKeyBucket; - @Nullable private final ImmutableMapEntry nextInValueBucket; - - NonTerminalBiMapEntry(K key, V value, @Nullable ImmutableMapEntry nextInKeyBucket, - @Nullable ImmutableMapEntry nextInValueBucket) { - super(key, value); - this.nextInKeyBucket = nextInKeyBucket; - this.nextInValueBucket = nextInValueBucket; - } - - NonTerminalBiMapEntry(ImmutableMapEntry contents, - @Nullable ImmutableMapEntry nextInKeyBucket, - @Nullable ImmutableMapEntry nextInValueBucket) { - super(contents); - this.nextInKeyBucket = nextInKeyBucket; - this.nextInValueBucket = nextInValueBucket; - } - - @Override - @Nullable - ImmutableMapEntry getNextInKeyBucket() { - return nextInKeyBucket; - } - - @Override - @Nullable - ImmutableMapEntry getNextInValueBucket() { - return nextInValueBucket; - } - } @Override @Nullable @@ -207,7 +143,7 @@ boolean isPartialView() { public int size() { return entries.length; } - + private transient ImmutableBiMap inverse; @Override @@ -215,7 +151,7 @@ public ImmutableBiMap inverse() { ImmutableBiMap result = inverse; return (result == null) ? inverse = new Inverse() : result; } - + private final class Inverse extends ImmutableBiMap { @Override @@ -247,7 +183,7 @@ public K get(@Nullable Object value) { ImmutableSet> createEntrySet() { return new InverseEntrySet(); } - + final class InverseEntrySet extends ImmutableMapEntrySet { @Override ImmutableMap map() { @@ -290,24 +226,24 @@ ImmutableCollection> delegateCollection() { boolean isPartialView() { return false; } - + @Override Object writeReplace() { return new InverseSerializedForm(RegularImmutableBiMap.this); } } - + private static class InverseSerializedForm implements Serializable { private final ImmutableBiMap forward; - + InverseSerializedForm(ImmutableBiMap forward) { this.forward = forward; } - + Object readResolve() { return forward.inverse(); } - + private static final long serialVersionUID = 1; } } diff --git a/guava/src/com/google/common/collect/RegularImmutableMap.java b/guava/src/com/google/common/collect/RegularImmutableMap.java index 6786e4267115..53803a1a599d 100644 --- a/guava/src/com/google/common/collect/RegularImmutableMap.java +++ b/guava/src/com/google/common/collect/RegularImmutableMap.java @@ -16,11 +16,12 @@ package com.google.common.collect; +import static com.google.common.base.Preconditions.checkPositionIndex; import static com.google.common.collect.CollectPreconditions.checkEntryNotNull; import static com.google.common.collect.ImmutableMapEntry.createEntryArray; import com.google.common.annotations.GwtCompatible; -import com.google.common.collect.ImmutableMapEntry.TerminalEntry; +import com.google.common.collect.ImmutableMapEntry.NonTerminalImmutableMapEntry; import javax.annotation.Nullable; @@ -41,42 +42,15 @@ final class RegularImmutableMap extends ImmutableMap { // 'and' with an int to get a table index private final transient int mask; - RegularImmutableMap(TerminalEntry... theEntries) { + RegularImmutableMap(ImmutableMapEntry... theEntries) { this(theEntries.length, theEntries); } - /** - * Constructor for RegularImmutableMap that takes as input an array of {@code TerminalEntry} - * entries. Assumes that these entries have already been checked for null. - * - *

This allows reuse of the entry objects from the array in the actual implementation. - */ - RegularImmutableMap(int size, TerminalEntry[] theEntries) { - entries = createEntryArray(size); - int tableSize = Hashing.closedTableSize(size, MAX_LOAD_FACTOR); - table = createEntryArray(tableSize); - mask = tableSize - 1; - for (int entryIndex = 0; entryIndex < size; entryIndex++) { - @SuppressWarnings("unchecked") - TerminalEntry entry = (TerminalEntry) theEntries[entryIndex]; - K key = entry.getKey(); - int tableIndex = Hashing.smear(key.hashCode()) & mask; - @Nullable ImmutableMapEntry existing = table[tableIndex]; - // prepend, not append, so the entries can be immutable - ImmutableMapEntry newEntry = (existing == null) - ? entry - : new NonTerminalMapEntry(entry, existing); - table[tableIndex] = newEntry; - entries[entryIndex] = newEntry; - checkNoConflictInKeyBucket(key, newEntry, existing); - } - } - /** * Constructor for RegularImmutableMap that makes no assumptions about the input entries. */ - RegularImmutableMap(Entry[] theEntries) { - int size = theEntries.length; + RegularImmutableMap(int size, Entry[] theEntries) { + checkPositionIndex(size, theEntries.length); entries = createEntryArray(size); int tableSize = Hashing.closedTableSize(size, MAX_LOAD_FACTOR); table = createEntryArray(tableSize); @@ -90,9 +64,15 @@ final class RegularImmutableMap extends ImmutableMap { int tableIndex = Hashing.smear(key.hashCode()) & mask; @Nullable ImmutableMapEntry existing = table[tableIndex]; // prepend, not append, so the entries can be immutable - ImmutableMapEntry newEntry = (existing == null) - ? new TerminalEntry(key, value) - : new NonTerminalMapEntry(key, value, existing); + ImmutableMapEntry newEntry; + if (existing == null) { + boolean reusable = entry instanceof ImmutableMapEntry + && ((ImmutableMapEntry) entry).isReusable(); + newEntry = + reusable ? (ImmutableMapEntry) entry : new ImmutableMapEntry(key, value); + } else { + newEntry = new NonTerminalImmutableMapEntry(key, value, existing); + } table[tableIndex] = newEntry; entries[entryIndex] = newEntry; checkNoConflictInKeyBucket(key, newEntry, existing); @@ -105,32 +85,6 @@ static void checkNoConflictInKeyBucket( checkNoConflict(!key.equals(keyBucketHead.getKey()), "key", entry, keyBucketHead); } } - - private static final class NonTerminalMapEntry extends ImmutableMapEntry { - private final ImmutableMapEntry nextInKeyBucket; - - NonTerminalMapEntry(K key, V value, ImmutableMapEntry nextInKeyBucket) { - super(key, value); - this.nextInKeyBucket = nextInKeyBucket; - } - - NonTerminalMapEntry(ImmutableMapEntry contents, ImmutableMapEntry nextInKeyBucket) { - super(contents); - this.nextInKeyBucket = nextInKeyBucket; - } - - @Override - ImmutableMapEntry getNextInKeyBucket() { - return nextInKeyBucket; - } - - @Override - @Nullable - ImmutableMapEntry getNextInValueBucket() { - return null; - } - - } /** * Closed addressing tends to perform well even with high load factors.