• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 Google Inc.
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 com.google.common.annotations.GwtCompatible;
20 import com.google.common.annotations.VisibleForTesting;
21 import com.google.common.base.Preconditions;
22 
23 import java.io.IOException;
24 import java.io.ObjectInputStream;
25 import java.io.ObjectOutputStream;
26 import java.util.Collection;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.LinkedHashSet;
30 import java.util.Map;
31 import java.util.Set;
32 
33 import javax.annotation.Nullable;
34 
35 /**
36  * Implementation of {@code Multimap} that does not allow duplicate key-value
37  * entries and that returns collections whose iterators follow the ordering in
38  * which the data was added to the multimap.
39  *
40  * <p>The collections returned by {@code keySet}, {@code keys}, and {@code
41  * asMap} iterate through the keys in the order they were first added to the
42  * multimap. Similarly, {@code get}, {@code removeAll}, and {@code
43  * replaceValues} return collections that iterate through the values in the
44  * order they were added. The collections generated by {@code entries} and
45  * {@code values} iterate across the key-value mappings in the order they were
46  * added to the multimap.
47  *
48  * <p>The iteration ordering of the collections generated by {@code keySet},
49  * {@code keys}, and {@code asMap} has a few subtleties. As long as the set of
50  * keys remains unchanged, adding or removing mappings does not affect the key
51  * iteration order. However, if you remove all values associated with a key and
52  * then add the key back to the multimap, that key will come last in the key
53  * iteration order.
54  *
55  * <p>The multimap does not store duplicate key-value pairs. Adding a new
56  * key-value pair equal to an existing key-value pair has no effect.
57  *
58  * <p>Keys and values may be null. All optional multimap methods are supported,
59  * and all returned views are modifiable.
60  *
61  * <p>This class is not threadsafe when any concurrent operations update the
62  * multimap. Concurrent read operations will work correctly. To allow concurrent
63  * update operations, wrap your multimap with a call to {@link
64  * Multimaps#synchronizedSetMultimap}.
65  *
66  * @author Jared Levy
67  * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
68  */
69 @GwtCompatible(serializable = true)
70 public final class LinkedHashMultimap<K, V> extends AbstractSetMultimap<K, V> {
71   private static final int DEFAULT_VALUES_PER_KEY = 8;
72 
73   @VisibleForTesting
74   transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY;
75 
76   /**
77    * Map entries with an iteration order corresponding to the order in which the
78    * key-value pairs were added to the multimap.
79    */
80   // package-private for GWT deserialization
81   transient Collection<Map.Entry<K, V>> linkedEntries;
82 
83   /**
84    * Creates a new, empty {@code LinkedHashMultimap} with the default initial
85    * capacities.
86    */
create()87   public static <K, V> LinkedHashMultimap<K, V> create() {
88     return new LinkedHashMultimap<K, V>();
89   }
90 
91   /**
92    * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold
93    * the specified numbers of keys and values without rehashing.
94    *
95    * @param expectedKeys the expected number of distinct keys
96    * @param expectedValuesPerKey the expected average number of values per key
97    * @throws IllegalArgumentException if {@code expectedKeys} or {@code
98    *      expectedValuesPerKey} is negative
99    */
create( int expectedKeys, int expectedValuesPerKey)100   public static <K, V> LinkedHashMultimap<K, V> create(
101       int expectedKeys, int expectedValuesPerKey) {
102     return new LinkedHashMultimap<K, V>(expectedKeys, expectedValuesPerKey);
103   }
104 
105   /**
106    * Constructs a {@code LinkedHashMultimap} with the same mappings as the
107    * specified multimap. If a key-value mapping appears multiple times in the
108    * input multimap, it only appears once in the constructed multimap. The new
109    * multimap has the same {@link Multimap#entries()} iteration order as the
110    * input multimap, except for excluding duplicate mappings.
111    *
112    * @param multimap the multimap whose contents are copied to this multimap
113    */
create( Multimap<? extends K, ? extends V> multimap)114   public static <K, V> LinkedHashMultimap<K, V> create(
115       Multimap<? extends K, ? extends V> multimap) {
116     return new LinkedHashMultimap<K, V>(multimap);
117   }
118 
LinkedHashMultimap()119   private LinkedHashMultimap() {
120     super(new LinkedHashMap<K, Collection<V>>());
121     linkedEntries = Sets.newLinkedHashSet();
122   }
123 
LinkedHashMultimap(int expectedKeys, int expectedValuesPerKey)124   private LinkedHashMultimap(int expectedKeys, int expectedValuesPerKey) {
125     super(new LinkedHashMap<K, Collection<V>>(expectedKeys));
126     Preconditions.checkArgument(expectedValuesPerKey >= 0);
127     this.expectedValuesPerKey = expectedValuesPerKey;
128     linkedEntries = new LinkedHashSet<Map.Entry<K, V>>(
129         expectedKeys * expectedValuesPerKey);
130   }
131 
LinkedHashMultimap(Multimap<? extends K, ? extends V> multimap)132   private LinkedHashMultimap(Multimap<? extends K, ? extends V> multimap) {
133     super(new LinkedHashMap<K, Collection<V>>(
134         Maps.capacity(multimap.keySet().size())));
135     linkedEntries
136         = new LinkedHashSet<Map.Entry<K, V>>(Maps.capacity(multimap.size()));
137     putAll(multimap);
138   }
139 
140   /**
141    * {@inheritDoc}
142    *
143    * <p>Creates an empty {@code LinkedHashSet} for a collection of values for
144    * one key.
145    *
146    * @return a new {@code LinkedHashSet} containing a collection of values for
147    *     one key
148    */
createCollection()149   @Override Set<V> createCollection() {
150     return new LinkedHashSet<V>(Maps.capacity(expectedValuesPerKey));
151   }
152 
153   /**
154    * {@inheritDoc}
155    *
156    * <p>Creates a decorated {@code LinkedHashSet} that also keeps track of the
157    * order in which key-value pairs are added to the multimap.
158    *
159    * @param key key to associate with values in the collection
160    * @return a new decorated {@code LinkedHashSet} containing a collection of
161    *     values for one key
162    */
createCollection(@ullable K key)163   @Override Collection<V> createCollection(@Nullable K key) {
164     return new SetDecorator(key, createCollection());
165   }
166 
167   private class SetDecorator extends ForwardingSet<V> {
168     final Set<V> delegate;
169     final K key;
170 
SetDecorator(@ullable K key, Set<V> delegate)171     SetDecorator(@Nullable K key, Set<V> delegate) {
172       this.delegate = delegate;
173       this.key = key;
174     }
175 
delegate()176     @Override protected Set<V> delegate() {
177       return delegate;
178     }
179 
createEntry(@ullable E value)180     <E> Map.Entry<K, E> createEntry(@Nullable E value) {
181       return Maps.immutableEntry(key, value);
182     }
183 
createEntries(Collection<E> values)184     <E> Collection<Map.Entry<K, E>> createEntries(Collection<E> values) {
185       // converts a collection of values into a list of key/value map entries
186       Collection<Map.Entry<K, E>> entries
187           = Lists.newArrayListWithExpectedSize(values.size());
188       for (E value : values) {
189         entries.add(createEntry(value));
190       }
191       return entries;
192     }
193 
add(@ullable V value)194     @Override public boolean add(@Nullable V value) {
195       boolean changed = delegate.add(value);
196       if (changed) {
197         linkedEntries.add(createEntry(value));
198       }
199       return changed;
200     }
201 
addAll(Collection<? extends V> values)202     @Override public boolean addAll(Collection<? extends V> values) {
203       boolean changed = delegate.addAll(values);
204       if (changed) {
205         linkedEntries.addAll(createEntries(delegate()));
206       }
207       return changed;
208     }
209 
clear()210     @Override public void clear() {
211       linkedEntries.removeAll(createEntries(delegate()));
212       delegate.clear();
213     }
214 
iterator()215     @Override public Iterator<V> iterator() {
216       final Iterator<V> delegateIterator = delegate.iterator();
217       return new Iterator<V>() {
218         V value;
219 
220         public boolean hasNext() {
221           return delegateIterator.hasNext();
222         }
223         public V next() {
224           value = delegateIterator.next();
225           return value;
226         }
227         public void remove() {
228           delegateIterator.remove();
229           linkedEntries.remove(createEntry(value));
230         }
231       };
232     }
233 
remove(@ullable Object value)234     @Override public boolean remove(@Nullable Object value) {
235       boolean changed = delegate.remove(value);
236       if (changed) {
237         /*
238          * linkedEntries.remove() will return false when this method is called
239          * by entries().iterator().remove()
240          */
241         linkedEntries.remove(createEntry(value));
242       }
243       return changed;
244     }
245 
removeAll(Collection<?> values)246     @Override public boolean removeAll(Collection<?> values) {
247       boolean changed = delegate.removeAll(values);
248       if (changed) {
249         linkedEntries.removeAll(createEntries(values));
250       }
251       return changed;
252     }
253 
retainAll(Collection<?> values)254     @Override public boolean retainAll(Collection<?> values) {
255       /*
256        * Calling linkedEntries.retainAll() would incorrectly remove values
257        * with other keys.
258        */
259       boolean changed = false;
260       Iterator<V> iterator = delegate.iterator();
261       while (iterator.hasNext()) {
262         V value = iterator.next();
263         if (!values.contains(value)) {
264           iterator.remove();
265           linkedEntries.remove(Maps.immutableEntry(key, value));
266           changed = true;
267         }
268       }
269       return changed;
270     }
271   }
272 
273   /**
274    * {@inheritDoc}
275    *
276    * <p>Generates an iterator across map entries that follows the ordering in
277    * which the key-value pairs were added to the multimap.
278    *
279    * @return a key-value iterator with the correct ordering
280    */
createEntryIterator()281   @Override Iterator<Map.Entry<K, V>> createEntryIterator() {
282     final Iterator<Map.Entry<K, V>> delegateIterator = linkedEntries.iterator();
283 
284     return new Iterator<Map.Entry<K, V>>() {
285       Map.Entry<K, V> entry;
286 
287       public boolean hasNext() {
288         return delegateIterator.hasNext();
289       }
290 
291       public Map.Entry<K, V> next() {
292         entry = delegateIterator.next();
293         return entry;
294       }
295 
296       public void remove() {
297         // Remove from iterator first to keep iterator valid.
298         delegateIterator.remove();
299         LinkedHashMultimap.this.remove(entry.getKey(), entry.getValue());
300       }
301     };
302   }
303 
304   /**
305    * {@inheritDoc}
306    *
307    * <p>If {@code values} is not empty and the multimap already contains a
308    * mapping for {@code key}, the {@code keySet()} ordering is unchanged.
309    * However, the provided values always come last in the {@link #entries()} and
310    * {@link #values()} iteration orderings.
311    */
312   @Override public Set<V> replaceValues(
313       @Nullable K key, Iterable<? extends V> values) {
314     return super.replaceValues(key, values);
315   }
316 
317   /**
318    * Returns a set of all key-value pairs. Changes to the returned set will
319    * update the underlying multimap, and vice versa. The entries set does not
320    * support the {@code add} or {@code addAll} operations.
321    *
322    * <p>The iterator generated by the returned set traverses the entries in the
323    * order they were added to the multimap.
324    *
325    * <p>Each entry is an immutable snapshot of a key-value mapping in the
326    * multimap, taken at the time the entry is returned by a method call to the
327    * collection or its iterator.
328    */
329   @Override public Set<Map.Entry<K, V>> entries() {
330     return super.entries();
331   }
332 
333   /**
334    * Returns a collection of all values in the multimap. Changes to the returned
335    * collection will update the underlying multimap, and vice versa.
336    *
337    * <p>The iterator generated by the returned collection traverses the values
338    * in the order they were added to the multimap.
339    */
340   @Override public Collection<V> values() {
341     return super.values();
342   }
343 
344   // Unfortunately, the entries() ordering does not determine the key ordering;
345   // see the example in the LinkedListMultimap class Javadoc.
346 
347   /**
348    * @serialData the number of distinct keys, and then for each distinct key:
349    *     the first key, the number of values for that key, and the key's values,
350    *     followed by successive keys and values from the entries() ordering
351    */
352   private void writeObject(ObjectOutputStream stream) throws IOException {
353     stream.defaultWriteObject();
354     stream.writeInt(expectedValuesPerKey);
355     Serialization.writeMultimap(this, stream);
356     for (Map.Entry<K, V> entry : linkedEntries) {
357       stream.writeObject(entry.getKey());
358       stream.writeObject(entry.getValue());
359     }
360   }
361 
362   private void readObject(ObjectInputStream stream)
363       throws IOException, ClassNotFoundException {
364     stream.defaultReadObject();
365     expectedValuesPerKey = stream.readInt();
366     int distinctKeys = Serialization.readCount(stream);
367     setMap(new LinkedHashMap<K, Collection<V>>(Maps.capacity(distinctKeys)));
368     linkedEntries = new LinkedHashSet<Map.Entry<K, V>>(
369         distinctKeys * expectedValuesPerKey);
370     Serialization.populateMultimap(this, stream, distinctKeys);
371     linkedEntries.clear(); // will clear and repopulate entries
372     for (int i = 0; i < size(); i++) {
373       @SuppressWarnings("unchecked") // reading data stored by writeObject
374       K key = (K) stream.readObject();
375       @SuppressWarnings("unchecked") // reading data stored by writeObject
376       V value = (V) stream.readObject();
377       linkedEntries.add(Maps.immutableEntry(key, value));
378     }
379   }
380 
381   private static final long serialVersionUID = 0;
382 }
383