• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.collect;
18 
19 import static com.google.common.collect.CollectPreconditions.checkEntryNotNull;
20 
21 import com.google.common.annotations.GwtCompatible;
22 import com.google.common.collect.ImmutableMapEntry.TerminalEntry;
23 
24 import java.io.Serializable;
25 
26 import javax.annotation.Nullable;
27 
28 /**
29  * Bimap with two or more mappings.
30  *
31  * @author Louis Wasserman
32  */
33 @GwtCompatible(serializable = true, emulated = true)
34 @SuppressWarnings("serial") // uses writeReplace(), not default serialization
35 class RegularImmutableBiMap<K, V> extends ImmutableBiMap<K, V> {
36 
37   static final double MAX_LOAD_FACTOR = 1.2;
38 
39   private final transient ImmutableMapEntry<K, V>[] keyTable;
40   private final transient ImmutableMapEntry<K, V>[] valueTable;
41   private final transient ImmutableMapEntry<K, V>[] entries;
42   private final transient int mask;
43   private final transient int hashCode;
44 
RegularImmutableBiMap(TerminalEntry<?, ?>.... entriesToAdd)45   RegularImmutableBiMap(TerminalEntry<?, ?>... entriesToAdd) {
46     this(entriesToAdd.length, entriesToAdd);
47   }
48 
49   /**
50    * Constructor for RegularImmutableBiMap that takes as input an array of {@code TerminalEntry}
51    * entries.  Assumes that these entries have already been checked for null.
52    *
53    * <p>This allows reuse of the entry objects from the array in the actual implementation.
54    */
RegularImmutableBiMap(int n, TerminalEntry<?, ?>[] entriesToAdd)55   RegularImmutableBiMap(int n, TerminalEntry<?, ?>[] entriesToAdd) {
56     int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR);
57     this.mask = tableSize - 1;
58     ImmutableMapEntry<K, V>[] keyTable = createEntryArray(tableSize);
59     ImmutableMapEntry<K, V>[] valueTable = createEntryArray(tableSize);
60     ImmutableMapEntry<K, V>[] entries = createEntryArray(n);
61     int hashCode = 0;
62 
63     for (int i = 0; i < n; i++) {
64       @SuppressWarnings("unchecked")
65       TerminalEntry<K, V> entry = (TerminalEntry<K, V>) entriesToAdd[i];
66       K key = entry.getKey();
67       V value = entry.getValue();
68 
69       int keyHash = key.hashCode();
70       int valueHash = value.hashCode();
71       int keyBucket = Hashing.smear(keyHash) & mask;
72       int valueBucket = Hashing.smear(valueHash) & mask;
73 
74       ImmutableMapEntry<K, V> nextInKeyBucket = keyTable[keyBucket];
75       for (ImmutableMapEntry<K, V> keyEntry = nextInKeyBucket; keyEntry != null;
76            keyEntry = keyEntry.getNextInKeyBucket()) {
77         checkNoConflict(!key.equals(keyEntry.getKey()), "key", entry, keyEntry);
78       }
79       ImmutableMapEntry<K, V> nextInValueBucket = valueTable[valueBucket];
80       for (ImmutableMapEntry<K, V> valueEntry = nextInValueBucket; valueEntry != null;
81            valueEntry = valueEntry.getNextInValueBucket()) {
82         checkNoConflict(!value.equals(valueEntry.getValue()), "value", entry, valueEntry);
83       }
84       ImmutableMapEntry<K, V> newEntry =
85           (nextInKeyBucket == null && nextInValueBucket == null)
86           ? entry
87           : new NonTerminalBiMapEntry<K, V>(entry, nextInKeyBucket, nextInValueBucket);
88       keyTable[keyBucket] = newEntry;
89       valueTable[valueBucket] = newEntry;
90       entries[i] = newEntry;
91       hashCode += keyHash ^ valueHash;
92     }
93 
94     this.keyTable = keyTable;
95     this.valueTable = valueTable;
96     this.entries = entries;
97     this.hashCode = hashCode;
98   }
99 
100   /**
101    * Constructor for RegularImmutableBiMap that makes no assumptions about the input entries.
102    */
RegularImmutableBiMap(Entry<?, ?>[] entriesToAdd)103   RegularImmutableBiMap(Entry<?, ?>[] entriesToAdd) {
104     int n = entriesToAdd.length;
105     int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR);
106     this.mask = tableSize - 1;
107     ImmutableMapEntry<K, V>[] keyTable = createEntryArray(tableSize);
108     ImmutableMapEntry<K, V>[] valueTable = createEntryArray(tableSize);
109     ImmutableMapEntry<K, V>[] entries = createEntryArray(n);
110     int hashCode = 0;
111 
112     for (int i = 0; i < n; i++) {
113       @SuppressWarnings("unchecked")
114       Entry<K, V> entry = (Entry<K, V>) entriesToAdd[i];
115       K key = entry.getKey();
116       V value = entry.getValue();
117       checkEntryNotNull(key, value);
118       int keyHash = key.hashCode();
119       int valueHash = value.hashCode();
120       int keyBucket = Hashing.smear(keyHash) & mask;
121       int valueBucket = Hashing.smear(valueHash) & mask;
122 
123       ImmutableMapEntry<K, V> nextInKeyBucket = keyTable[keyBucket];
124       for (ImmutableMapEntry<K, V> keyEntry = nextInKeyBucket; keyEntry != null;
125            keyEntry = keyEntry.getNextInKeyBucket()) {
126         checkNoConflict(!key.equals(keyEntry.getKey()), "key", entry, keyEntry);
127       }
128       ImmutableMapEntry<K, V> nextInValueBucket = valueTable[valueBucket];
129       for (ImmutableMapEntry<K, V> valueEntry = nextInValueBucket; valueEntry != null;
130            valueEntry = valueEntry.getNextInValueBucket()) {
131         checkNoConflict(!value.equals(valueEntry.getValue()), "value", entry, valueEntry);
132       }
133       ImmutableMapEntry<K, V> newEntry =
134           (nextInKeyBucket == null && nextInValueBucket == null)
135           ? new TerminalEntry<K, V>(key, value)
136           : new NonTerminalBiMapEntry<K, V>(key, value, nextInKeyBucket, nextInValueBucket);
137       keyTable[keyBucket] = newEntry;
138       valueTable[valueBucket] = newEntry;
139       entries[i] = newEntry;
140       hashCode += keyHash ^ valueHash;
141     }
142 
143     this.keyTable = keyTable;
144     this.valueTable = valueTable;
145     this.entries = entries;
146     this.hashCode = hashCode;
147   }
148 
149   private static final class NonTerminalBiMapEntry<K, V> extends ImmutableMapEntry<K, V> {
150     @Nullable private final ImmutableMapEntry<K, V> nextInKeyBucket;
151     @Nullable private final ImmutableMapEntry<K, V> nextInValueBucket;
152 
NonTerminalBiMapEntry(K key, V value, @Nullable ImmutableMapEntry<K, V> nextInKeyBucket, @Nullable ImmutableMapEntry<K, V> nextInValueBucket)153     NonTerminalBiMapEntry(K key, V value, @Nullable ImmutableMapEntry<K, V> nextInKeyBucket,
154         @Nullable ImmutableMapEntry<K, V> nextInValueBucket) {
155       super(key, value);
156       this.nextInKeyBucket = nextInKeyBucket;
157       this.nextInValueBucket = nextInValueBucket;
158     }
159 
NonTerminalBiMapEntry(ImmutableMapEntry<K, V> contents, @Nullable ImmutableMapEntry<K, V> nextInKeyBucket, @Nullable ImmutableMapEntry<K, V> nextInValueBucket)160     NonTerminalBiMapEntry(ImmutableMapEntry<K, V> contents,
161         @Nullable ImmutableMapEntry<K, V> nextInKeyBucket,
162         @Nullable ImmutableMapEntry<K, V> nextInValueBucket) {
163       super(contents);
164       this.nextInKeyBucket = nextInKeyBucket;
165       this.nextInValueBucket = nextInValueBucket;
166     }
167 
168     @Override
169     @Nullable
getNextInKeyBucket()170     ImmutableMapEntry<K, V> getNextInKeyBucket() {
171       return nextInKeyBucket;
172     }
173 
174     @Override
175     @Nullable
getNextInValueBucket()176     ImmutableMapEntry<K, V> getNextInValueBucket() {
177       return nextInValueBucket;
178     }
179   }
180 
181   @SuppressWarnings("unchecked")
createEntryArray(int length)182   private static <K, V> ImmutableMapEntry<K, V>[] createEntryArray(int length) {
183     return new ImmutableMapEntry[length];
184   }
185 
186   @Override
187   @Nullable
get(@ullable Object key)188   public V get(@Nullable Object key) {
189     if (key == null) {
190       return null;
191     }
192     int bucket = Hashing.smear(key.hashCode()) & mask;
193     for (ImmutableMapEntry<K, V> entry = keyTable[bucket]; entry != null;
194          entry = entry.getNextInKeyBucket()) {
195       if (key.equals(entry.getKey())) {
196         return entry.getValue();
197       }
198     }
199     return null;
200   }
201 
202   @Override
createEntrySet()203   ImmutableSet<Entry<K, V>> createEntrySet() {
204     return new ImmutableMapEntrySet<K, V>() {
205       @Override
206       ImmutableMap<K, V> map() {
207         return RegularImmutableBiMap.this;
208       }
209 
210       @Override
211       public UnmodifiableIterator<Entry<K, V>> iterator() {
212         return asList().iterator();
213       }
214 
215       @Override
216       ImmutableList<Entry<K, V>> createAsList() {
217         return new RegularImmutableAsList<Entry<K, V>>(this, entries);
218       }
219 
220       @Override
221       boolean isHashCodeFast() {
222         return true;
223       }
224 
225       @Override
226       public int hashCode() {
227         return hashCode;
228       }
229     };
230   }
231 
232   @Override
233   boolean isPartialView() {
234     return false;
235   }
236 
237   @Override
238   public int size() {
239     return entries.length;
240   }
241 
242   private transient ImmutableBiMap<V, K> inverse;
243 
244   @Override
245   public ImmutableBiMap<V, K> inverse() {
246     ImmutableBiMap<V, K> result = inverse;
247     return (result == null) ? inverse = new Inverse() : result;
248   }
249 
250   private final class Inverse extends ImmutableBiMap<V, K> {
251 
252     @Override
253     public int size() {
254       return inverse().size();
255     }
256 
257     @Override
258     public ImmutableBiMap<K, V> inverse() {
259       return RegularImmutableBiMap.this;
260     }
261 
262     @Override
263     public K get(@Nullable Object value) {
264       if (value == null) {
265         return null;
266       }
267       int bucket = Hashing.smear(value.hashCode()) & mask;
268       for (ImmutableMapEntry<K, V> entry = valueTable[bucket]; entry != null;
269            entry = entry.getNextInValueBucket()) {
270         if (value.equals(entry.getValue())) {
271           return entry.getKey();
272         }
273       }
274       return null;
275     }
276 
277     @Override
278     ImmutableSet<Entry<V, K>> createEntrySet() {
279       return new InverseEntrySet();
280     }
281 
282     final class InverseEntrySet extends ImmutableMapEntrySet<V, K> {
283       @Override
284       ImmutableMap<V, K> map() {
285         return Inverse.this;
286       }
287 
288       @Override
289       boolean isHashCodeFast() {
290         return true;
291       }
292 
293       @Override
294       public int hashCode() {
295         return hashCode;
296       }
297 
298       @Override
299       public UnmodifiableIterator<Entry<V, K>> iterator() {
300         return asList().iterator();
301       }
302 
303       @Override
304       ImmutableList<Entry<V, K>> createAsList() {
305         return new ImmutableAsList<Entry<V, K>>() {
306           @Override
307           public Entry<V, K> get(int index) {
308             Entry<K, V> entry = entries[index];
309             return Maps.immutableEntry(entry.getValue(), entry.getKey());
310           }
311 
312           @Override
313           ImmutableCollection<Entry<V, K>> delegateCollection() {
314             return InverseEntrySet.this;
315           }
316         };
317       }
318     }
319 
320     @Override
321     boolean isPartialView() {
322       return false;
323     }
324 
325     @Override
326     Object writeReplace() {
327       return new InverseSerializedForm<K, V>(RegularImmutableBiMap.this);
328     }
329   }
330 
331   private static class InverseSerializedForm<K, V> implements Serializable {
332     private final ImmutableBiMap<K, V> forward;
333 
334     InverseSerializedForm(ImmutableBiMap<K, V> forward) {
335       this.forward = forward;
336     }
337 
338     Object readResolve() {
339       return forward.inverse();
340     }
341 
342     private static final long serialVersionUID = 1;
343   }
344 }
345