001/*
002 * Copyright (C) 2007 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect;
018
019import static com.google.common.base.Preconditions.checkNotNull;
020import static com.google.common.base.Preconditions.checkState;
021import static com.google.common.collect.CollectPreconditions.checkNonnegative;
022import static com.google.common.collect.Hashing.closedTableSize;
023import static com.google.common.collect.Hashing.smearedHash;
024import static com.google.common.collect.Maps.capacity;
025import static java.util.Objects.requireNonNull;
026
027import com.google.common.annotations.GwtCompatible;
028import com.google.common.annotations.GwtIncompatible;
029import com.google.common.annotations.J2ktIncompatible;
030import com.google.common.annotations.VisibleForTesting;
031import com.google.errorprone.annotations.CanIgnoreReturnValue;
032import com.google.j2objc.annotations.WeakOuter;
033import java.io.IOException;
034import java.io.ObjectInputStream;
035import java.io.ObjectOutputStream;
036import java.util.AbstractMap.SimpleImmutableEntry;
037import java.util.Arrays;
038import java.util.Collection;
039import java.util.ConcurrentModificationException;
040import java.util.Iterator;
041import java.util.Map;
042import java.util.Map.Entry;
043import java.util.NoSuchElementException;
044import java.util.Objects;
045import java.util.Set;
046import java.util.Spliterator;
047import java.util.Spliterators;
048import java.util.function.Consumer;
049import org.jspecify.annotations.Nullable;
050
051/**
052 * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that
053 * returns collections whose iterators follow the ordering in which the data was added to the
054 * multimap.
055 *
056 * <p>The collections returned by {@code keySet}, {@code keys}, and {@code asMap} iterate through
057 * the keys in the order they were first added to the multimap. Similarly, {@code get}, {@code
058 * removeAll}, and {@code replaceValues} return collections that iterate through the values in the
059 * order they were added. The collections generated by {@code entries} and {@code values} iterate
060 * across the key-value mappings in the order they were added to the multimap.
061 *
062 * <p>The iteration ordering of the collections generated by {@code keySet}, {@code keys}, and
063 * {@code asMap} has a few subtleties. As long as the set of keys remains unchanged, adding or
064 * removing mappings does not affect the key iteration order. However, if you remove all values
065 * associated with a key and then add the key back to the multimap, that key will come last in the
066 * key iteration order.
067 *
068 * <p>The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an
069 * existing key-value pair has no effect.
070 *
071 * <p>Keys and values may be null. All optional multimap methods are supported, and all returned
072 * views are modifiable.
073 *
074 * <p>This class is not threadsafe when any concurrent operations update the multimap. Concurrent
075 * read operations will work correctly. To allow concurrent update operations, wrap your multimap
076 * with a call to {@link Multimaps#synchronizedSetMultimap}.
077 *
078 * <p><b>Warning:</b> Do not modify either a key <i>or a value</i> of a {@code LinkedHashMultimap}
079 * in a way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will
080 * result.
081 *
082 * <p>See the Guava User Guide article on <a href=
083 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap">{@code Multimap}</a>.
084 *
085 * @author Jared Levy
086 * @author Louis Wasserman
087 * @since 2.0
088 */
089@GwtCompatible
090public final class LinkedHashMultimap<K extends @Nullable Object, V extends @Nullable Object>
091    extends AbstractSetMultimap<K, V> {
092
093  /** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */
094  public static <K extends @Nullable Object, V extends @Nullable Object>
095      LinkedHashMultimap<K, V> create() {
096    return new LinkedHashMultimap<>(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY);
097  }
098
099  /**
100   * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold the specified
101   * numbers of keys and values without rehashing.
102   *
103   * @param expectedKeys the expected number of distinct keys
104   * @param expectedValuesPerKey the expected average number of values per key
105   * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is
106   *     negative
107   */
108  public static <K extends @Nullable Object, V extends @Nullable Object>
109      LinkedHashMultimap<K, V> create(int expectedKeys, int expectedValuesPerKey) {
110    return new LinkedHashMultimap<>(capacity(expectedKeys), capacity(expectedValuesPerKey));
111  }
112
113  /**
114   * Constructs a {@code LinkedHashMultimap} with the same mappings as the specified multimap. If a
115   * key-value mapping appears multiple times in the input multimap, it only appears once in the
116   * constructed multimap. The new multimap has the same {@link Multimap#entries()} iteration order
117   * as the input multimap, except for excluding duplicate mappings.
118   *
119   * @param multimap the multimap whose contents are copied to this multimap
120   */
121  public static <K extends @Nullable Object, V extends @Nullable Object>
122      LinkedHashMultimap<K, V> create(Multimap<? extends K, ? extends V> multimap) {
123    LinkedHashMultimap<K, V> result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY);
124    result.putAll(multimap);
125    return result;
126  }
127
128  /**
129   * LinkedHashMultimap entries are in no less than three coexisting linked lists: a bucket in the
130   * hash table for a {@code Set<V>} associated with a key, the linked list of insertion-ordered
131   * entries in that {@code Set<V>}, and the linked list of entries in the LinkedHashMultimap as a
132   * whole.
133   */
134  @VisibleForTesting
135  static final class ValueEntry<K extends @Nullable Object, V extends @Nullable Object>
136      extends SimpleImmutableEntry<K, V> {
137    final int smearedValueHash;
138
139    @Nullable ValueEntry<K, V> nextInValueBucket;
140
141    private @Nullable ValueEntry<K, V> predecessorInValueSet;
142    private @Nullable ValueEntry<K, V> successorInValueSet;
143
144    private @Nullable ValueEntry<K, V> predecessorInMultimap;
145    private @Nullable ValueEntry<K, V> successorInMultimap;
146
147    ValueEntry(
148        @ParametricNullness K key,
149        @ParametricNullness V value,
150        int smearedValueHash,
151        @Nullable ValueEntry<K, V> nextInValueBucket) {
152      super(key, value);
153      this.smearedValueHash = smearedValueHash;
154      this.nextInValueBucket = nextInValueBucket;
155    }
156
157    boolean matchesValue(@Nullable Object v, int smearedVHash) {
158      return smearedValueHash == smearedVHash && Objects.equals(getValue(), v);
159    }
160  }
161
162  private static final int DEFAULT_KEY_CAPACITY = 16;
163  private static final int DEFAULT_VALUE_SET_CAPACITY = 2;
164  @VisibleForTesting static final double VALUE_SET_LOAD_FACTOR = 1.0;
165
166  @VisibleForTesting transient int valueSetCapacity;
167  private transient MultimapIterationChain<K, V> multimapIterationChain =
168      new MultimapIterationChain<>();
169
170  private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) {
171    super(Platform.newLinkedHashMapWithExpectedSize(keyCapacity));
172    checkNonnegative(valueSetCapacity, "expectedValuesPerKey");
173
174    this.valueSetCapacity = valueSetCapacity;
175  }
176
177  /**
178   * {@inheritDoc}
179   *
180   * <p>Creates an empty {@code LinkedHashSet} for a collection of values for one key.
181   *
182   * @return a new {@code LinkedHashSet} containing a collection of values for one key
183   */
184  @Override
185  Set<V> createCollection() {
186    return Platform.newLinkedHashSetWithExpectedSize(valueSetCapacity);
187  }
188
189  /**
190   * {@inheritDoc}
191   *
192   * <p>Creates a decorated insertion-ordered set that also keeps track of the order in which
193   * key-value pairs are added to the multimap.
194   *
195   * @param key key to associate with values in the collection
196   * @return a new decorated set containing a collection of values for one key
197   */
198  @Override
199  Collection<V> createCollection(@ParametricNullness K key) {
200    return new ValueSet(key, valueSetCapacity);
201  }
202
203  /**
204   * {@inheritDoc}
205   *
206   * <p>If {@code values} is not empty and the multimap already contains a mapping for {@code key},
207   * the {@code keySet()} ordering is unchanged. However, the provided values always come last in
208   * the {@link #entries()} and {@link #values()} iteration orderings.
209   */
210  @CanIgnoreReturnValue
211  @Override
212  public Set<V> replaceValues(@ParametricNullness K key, Iterable<? extends V> values) {
213    return super.replaceValues(key, values);
214  }
215
216  /**
217   * Returns a set of all key-value pairs. Changes to the returned set will update the underlying
218   * multimap, and vice versa. The entries set does not support the {@code add} or {@code addAll}
219   * operations.
220   *
221   * <p>The iterator generated by the returned set traverses the entries in the order they were
222   * added to the multimap.
223   *
224   * <p>Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the
225   * time the entry is returned by a method call to the collection or its iterator.
226   */
227  @Override
228  public Set<Entry<K, V>> entries() {
229    return super.entries();
230  }
231
232  /**
233   * Returns a view collection of all <i>distinct</i> keys contained in this multimap. Note that the
234   * key set contains a key if and only if this multimap maps that key to at least one value.
235   *
236   * <p>The iterator generated by the returned set traverses the keys in the order they were first
237   * added to the multimap.
238   *
239   * <p>Changes to the returned set will update the underlying multimap, and vice versa. However,
240   * <i>adding</i> to the returned set is not possible.
241   */
242  @Override
243  public Set<K> keySet() {
244    return super.keySet();
245  }
246
247  /**
248   * Returns a collection of all values in the multimap. Changes to the returned collection will
249   * update the underlying multimap, and vice versa.
250   *
251   * <p>The iterator generated by the returned collection traverses the values in the order they
252   * were added to the multimap.
253   */
254  @Override
255  public Collection<V> values() {
256    return super.values();
257  }
258
259  @VisibleForTesting
260  @WeakOuter
261  final class ValueSet extends Sets.ImprovedAbstractSet<V> {
262    /*
263     * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory
264     * consumption.
265     */
266
267    @ParametricNullness private final K key;
268    @VisibleForTesting @Nullable ValueEntry<K, V>[] hashTable;
269    private int size = 0;
270    private int modCount = 0;
271
272    private @Nullable ValueEntry<K, V> firstEntry;
273    private @Nullable ValueEntry<K, V> lastEntry;
274
275    ValueSet(@ParametricNullness K key, int expectedValues) {
276      this.key = key;
277      // Round expected values up to a power of 2 to get the table size.
278      int tableSize = closedTableSize(expectedValues, VALUE_SET_LOAD_FACTOR);
279
280      @SuppressWarnings({"rawtypes", "unchecked"})
281      @Nullable ValueEntry<K, V>[] hashTable = new @Nullable ValueEntry[tableSize];
282      this.hashTable = hashTable;
283    }
284
285    private void succeedsInValueSet(
286        @Nullable ValueEntry<K, V> pred, @Nullable ValueEntry<K, V> succ) {
287      if (pred == null) {
288        firstEntry = succ;
289      } else {
290        pred.successorInValueSet = succ;
291      }
292      if (succ == null) {
293        lastEntry = pred;
294      } else {
295        succ.predecessorInValueSet = pred;
296      }
297    }
298
299    private void deleteFromValueSet(ValueEntry<K, V> entry) {
300      succeedsInValueSet(entry.predecessorInValueSet, entry.successorInValueSet);
301    }
302
303    private void appendToValueSet(ValueEntry<K, V> newEntry) {
304      succeedsInValueSet(lastEntry, newEntry);
305      lastEntry = newEntry;
306    }
307
308    private int mask() {
309      return hashTable.length - 1;
310    }
311
312    @Override
313    public Iterator<V> iterator() {
314      return new Iterator<V>() {
315        @Nullable ValueEntry<K, V> nextEntry = firstEntry;
316        @Nullable ValueEntry<K, V> toRemove;
317        int expectedModCount = modCount;
318
319        private void checkForComodification() {
320          if (modCount != expectedModCount) {
321            throw new ConcurrentModificationException();
322          }
323        }
324
325        @Override
326        public boolean hasNext() {
327          checkForComodification();
328          return nextEntry != null;
329        }
330
331        @Override
332        @ParametricNullness
333        public V next() {
334          checkForComodification();
335          ValueEntry<K, V> entry = nextEntry;
336          if (entry == null) {
337            throw new NoSuchElementException();
338          }
339          V result = entry.getValue();
340          toRemove = entry;
341          nextEntry = entry.successorInValueSet;
342          return result;
343        }
344
345        @Override
346        public void remove() {
347          checkForComodification();
348          checkState(toRemove != null, "no calls to next() since the last call to remove()");
349          ValueSet.this.remove(toRemove.getValue());
350          expectedModCount = modCount;
351          toRemove = null;
352        }
353      };
354    }
355
356    @Override
357    public void forEach(Consumer<? super V> action) {
358      checkNotNull(action);
359      for (ValueEntry<K, V> entry = firstEntry; entry != null; entry = entry.successorInValueSet) {
360        action.accept(entry.getValue());
361      }
362    }
363
364    @Override
365    public int size() {
366      return size;
367    }
368
369    @Override
370    public boolean contains(@Nullable Object o) {
371      int smearedHash = smearedHash(o);
372      for (ValueEntry<K, V> entry = hashTable[smearedHash & mask()];
373          entry != null;
374          entry = entry.nextInValueBucket) {
375        if (entry.matchesValue(o, smearedHash)) {
376          return true;
377        }
378      }
379      return false;
380    }
381
382    @Override
383    public boolean add(@ParametricNullness V value) {
384      int smearedHash = smearedHash(value);
385      int bucket = smearedHash & mask();
386      ValueEntry<K, V> rowHead = hashTable[bucket];
387      for (ValueEntry<K, V> entry = rowHead; entry != null; entry = entry.nextInValueBucket) {
388        if (entry.matchesValue(value, smearedHash)) {
389          return false;
390        }
391      }
392
393      ValueEntry<K, V> newEntry = new ValueEntry<>(key, value, smearedHash, rowHead);
394      appendToValueSet(newEntry);
395      multimapIterationChain.append(newEntry);
396      hashTable[bucket] = newEntry;
397      size++;
398      modCount++;
399      rehashIfNecessary();
400      return true;
401    }
402
403    private void rehashIfNecessary() {
404      if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) {
405        @SuppressWarnings("unchecked")
406        ValueEntry<K, V>[] hashTable =
407            (ValueEntry<K, V>[]) new ValueEntry<?, ?>[this.hashTable.length * 2];
408        this.hashTable = hashTable;
409        int mask = hashTable.length - 1;
410        for (ValueEntry<K, V> entry = firstEntry;
411            entry != null;
412            entry = entry.successorInValueSet) {
413          int bucket = entry.smearedValueHash & mask;
414          entry.nextInValueBucket = hashTable[bucket];
415          hashTable[bucket] = entry;
416        }
417      }
418    }
419
420    @CanIgnoreReturnValue
421    @Override
422    public boolean remove(@Nullable Object o) {
423      int smearedHash = smearedHash(o);
424      int bucket = smearedHash & mask();
425      ValueEntry<K, V> prev = null;
426      for (ValueEntry<K, V> entry = hashTable[bucket];
427          entry != null;
428          prev = entry, entry = entry.nextInValueBucket) {
429        if (entry.matchesValue(o, smearedHash)) {
430          if (prev == null) {
431            // first entry in the bucket
432            hashTable[bucket] = entry.nextInValueBucket;
433          } else {
434            prev.nextInValueBucket = entry.nextInValueBucket;
435          }
436          deleteFromValueSet(entry);
437          multimapIterationChain.delete(entry);
438          size--;
439          modCount++;
440          return true;
441        }
442      }
443      return false;
444    }
445
446    @Override
447    public void clear() {
448      Arrays.fill(hashTable, null);
449      size = 0;
450      for (ValueEntry<K, V> entry = firstEntry; entry != null; entry = entry.successorInValueSet) {
451        multimapIterationChain.delete(entry);
452        // TODO(cpovirk): Also clear *InValueSet (after reading next) and nextInValueBucket?
453      }
454      firstEntry = null;
455      lastEntry = null;
456      modCount++;
457    }
458  }
459
460  @Override
461  Iterator<Entry<K, V>> entryIterator() {
462    return new Iterator<Entry<K, V>>() {
463      @Nullable ValueEntry<K, V> nextEntry = multimapIterationChain.firstEntry;
464      @Nullable ValueEntry<K, V> toRemove;
465
466      @Override
467      public boolean hasNext() {
468        return nextEntry != null;
469      }
470
471      @Override
472      public Entry<K, V> next() {
473        ValueEntry<K, V> entry = nextEntry;
474        if (entry == null) {
475          throw new NoSuchElementException();
476        }
477        toRemove = entry;
478        nextEntry = entry.successorInMultimap;
479        return entry;
480      }
481
482      @Override
483      public void remove() {
484        checkState(toRemove != null, "no calls to next() since the last call to remove()");
485        LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue());
486        toRemove = null;
487      }
488    };
489  }
490
491  @Override
492  Spliterator<Entry<K, V>> entrySpliterator() {
493    return Spliterators.spliterator(
494        entries(), Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.NONNULL);
495  }
496
497  @Override
498  Iterator<V> valueIterator() {
499    return Maps.valueIterator(entryIterator());
500  }
501
502  @Override
503  Spliterator<V> valueSpliterator() {
504    return CollectSpliterators.map(entrySpliterator(), Spliterator.ORDERED, Entry::getValue);
505  }
506
507  /**
508   * @serialData the expected values per key, the number of distinct keys, the number of entries,
509   *     and the entries in order
510   */
511  @GwtIncompatible
512  @J2ktIncompatible
513    private void writeObject(ObjectOutputStream stream) throws IOException {
514    stream.defaultWriteObject();
515    stream.writeInt(keySet().size());
516    for (K key : keySet()) {
517      stream.writeObject(key);
518    }
519    stream.writeInt(size());
520    for (Entry<K, V> entry : entries()) {
521      stream.writeObject(entry.getKey());
522      stream.writeObject(entry.getValue());
523    }
524  }
525
526  @GwtIncompatible
527  @J2ktIncompatible
528    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
529    stream.defaultReadObject();
530    multimapIterationChain = new MultimapIterationChain<>();
531    valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY;
532    int distinctKeys = stream.readInt();
533    Map<K, Collection<V>> map = Platform.newLinkedHashMapWithExpectedSize(12);
534    for (int i = 0; i < distinctKeys; i++) {
535      @SuppressWarnings("unchecked")
536      K key = (K) stream.readObject();
537      map.put(key, createCollection(key));
538    }
539    int entries = stream.readInt();
540    for (int i = 0; i < entries; i++) {
541      @SuppressWarnings("unchecked")
542      K key = (K) stream.readObject();
543      @SuppressWarnings("unchecked")
544      V value = (V) stream.readObject();
545      /*
546       * requireNonNull is safe for a properly serialized multimap: We've already inserted a
547       * collection for each key that we expect.
548       */
549      requireNonNull(map.get(key)).add(value);
550    }
551    setMap(map);
552  }
553
554  private static final class MultimapIterationChain<
555      K extends @Nullable Object, V extends @Nullable Object> {
556    @Nullable ValueEntry<K, V> firstEntry;
557    @Nullable ValueEntry<K, V> lastEntry;
558
559    void succeeds(@Nullable ValueEntry<K, V> pred, @Nullable ValueEntry<K, V> succ) {
560      if (pred == null) {
561        firstEntry = succ;
562      } else {
563        pred.successorInMultimap = succ;
564      }
565      if (succ == null) {
566        lastEntry = pred;
567      } else {
568        succ.predecessorInMultimap = pred;
569      }
570    }
571
572    void delete(ValueEntry<K, V> entry) {
573      succeeds(entry.predecessorInMultimap, entry.successorInMultimap);
574    }
575
576    void append(ValueEntry<K, V> newEntry) {
577      succeeds(lastEntry, newEntry);
578      lastEntry = newEntry;
579    }
580  }
581
582  @GwtIncompatible @J2ktIncompatible private static final long serialVersionUID = 1;
583}