Coverage Summary for Class: LinkedHashMultimap (com.google.common.collect)

Class Method, % Line, %
LinkedHashMultimap 27.3% (6/22) 25% (16/64)
LinkedHashMultimap$1 0% (0/4) 0% (0/12)
LinkedHashMultimap$ValueEntry 70% (7/10) 82.4% (14/17)
LinkedHashMultimap$ValueSet 53.3% (8/15) 37.9% (33/87)
LinkedHashMultimap$ValueSet$1 80% (4/5) 61.9% (13/21)
LinkedHashMultimap$ValueSetLink
Total 44.6% (25/56) 37.8% (76/201)


1 /* 2  * Copyright (C) 2007 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.base.Preconditions.checkNotNull; 20 import static com.google.common.collect.CollectPreconditions.checkNonnegative; 21 import static com.google.common.collect.CollectPreconditions.checkRemove; 22  23 import com.google.common.annotations.GwtCompatible; 24 import com.google.common.annotations.GwtIncompatible; 25 import com.google.common.annotations.VisibleForTesting; 26 import com.google.common.base.Objects; 27 import com.google.errorprone.annotations.CanIgnoreReturnValue; 28 import com.google.j2objc.annotations.WeakOuter; 29 import java.io.IOException; 30 import java.io.ObjectInputStream; 31 import java.io.ObjectOutputStream; 32 import java.util.Arrays; 33 import java.util.Collection; 34 import java.util.ConcurrentModificationException; 35 import java.util.Iterator; 36 import java.util.Map; 37 import java.util.Map.Entry; 38 import java.util.NoSuchElementException; 39 import java.util.Set; 40 import java.util.Spliterator; 41 import java.util.Spliterators; 42 import java.util.function.Consumer; 43 import org.checkerframework.checker.nullness.qual.Nullable; 44  45 /** 46  * Implementation of {@code Multimap} that does not allow duplicate key-value entries and that 47  * returns collections whose iterators follow the ordering in which the data was added to the 48  * multimap. 49  * 50  * <p>The collections returned by {@code keySet}, {@code keys}, and {@code asMap} iterate through 51  * the keys in the order they were first added to the multimap. Similarly, {@code get}, {@code 52  * removeAll}, and {@code replaceValues} return collections that iterate through the values in the 53  * order they were added. The collections generated by {@code entries} and {@code values} iterate 54  * across the key-value mappings in the order they were added to the multimap. 55  * 56  * <p>The iteration ordering of the collections generated by {@code keySet}, {@code keys}, and 57  * {@code asMap} has a few subtleties. As long as the set of keys remains unchanged, adding or 58  * removing mappings does not affect the key iteration order. However, if you remove all values 59  * associated with a key and then add the key back to the multimap, that key will come last in the 60  * key iteration order. 61  * 62  * <p>The multimap does not store duplicate key-value pairs. Adding a new key-value pair equal to an 63  * existing key-value pair has no effect. 64  * 65  * <p>Keys and values may be null. All optional multimap methods are supported, and all returned 66  * views are modifiable. 67  * 68  * <p>This class is not threadsafe when any concurrent operations update the multimap. Concurrent 69  * read operations will work correctly. To allow concurrent update operations, wrap your multimap 70  * with a call to {@link Multimaps#synchronizedSetMultimap}. 71  * 72  * <p><b>Warning:</b> Do not modify either a key <i>or a value</i> of a {@code LinkedHashMultimap} 73  * in a way that affects its {@link Object#equals} behavior. Undefined behavior and bugs will 74  * result. 75  * 76  * <p>See the Guava User Guide article on <a href= 77  * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multimap"> {@code 78  * Multimap}</a>. 79  * 80  * @author Jared Levy 81  * @author Louis Wasserman 82  * @since 2.0 83  */ 84 @GwtCompatible(serializable = true, emulated = true) 85 public final class LinkedHashMultimap<K, V> 86  extends LinkedHashMultimapGwtSerializationDependencies<K, V> { 87  88  /** Creates a new, empty {@code LinkedHashMultimap} with the default initial capacities. */ 89  public static <K, V> LinkedHashMultimap<K, V> create() { 90  return new LinkedHashMultimap<>(DEFAULT_KEY_CAPACITY, DEFAULT_VALUE_SET_CAPACITY); 91  } 92  93  /** 94  * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold the specified 95  * numbers of keys and values without rehashing. 96  * 97  * @param expectedKeys the expected number of distinct keys 98  * @param expectedValuesPerKey the expected average number of values per key 99  * @throws IllegalArgumentException if {@code expectedKeys} or {@code expectedValuesPerKey} is 100  * negative 101  */ 102  public static <K, V> LinkedHashMultimap<K, V> create(int expectedKeys, int expectedValuesPerKey) { 103  return new LinkedHashMultimap<>( 104  Maps.capacity(expectedKeys), Maps.capacity(expectedValuesPerKey)); 105  } 106  107  /** 108  * Constructs a {@code LinkedHashMultimap} with the same mappings as the specified multimap. If a 109  * key-value mapping appears multiple times in the input multimap, it only appears once in the 110  * constructed multimap. The new multimap has the same {@link Multimap#entries()} iteration order 111  * as the input multimap, except for excluding duplicate mappings. 112  * 113  * @param multimap the multimap whose contents are copied to this multimap 114  */ 115  public static <K, V> LinkedHashMultimap<K, V> create( 116  Multimap<? extends K, ? extends V> multimap) { 117  LinkedHashMultimap<K, V> result = create(multimap.keySet().size(), DEFAULT_VALUE_SET_CAPACITY); 118  result.putAll(multimap); 119  return result; 120  } 121  122  private interface ValueSetLink<K, V> { 123  ValueSetLink<K, V> getPredecessorInValueSet(); 124  125  ValueSetLink<K, V> getSuccessorInValueSet(); 126  127  void setPredecessorInValueSet(ValueSetLink<K, V> entry); 128  129  void setSuccessorInValueSet(ValueSetLink<K, V> entry); 130  } 131  132  private static <K, V> void succeedsInValueSet(ValueSetLink<K, V> pred, ValueSetLink<K, V> succ) { 133  pred.setSuccessorInValueSet(succ); 134  succ.setPredecessorInValueSet(pred); 135  } 136  137  private static <K, V> void succeedsInMultimap(ValueEntry<K, V> pred, ValueEntry<K, V> succ) { 138  pred.setSuccessorInMultimap(succ); 139  succ.setPredecessorInMultimap(pred); 140  } 141  142  private static <K, V> void deleteFromValueSet(ValueSetLink<K, V> entry) { 143  succeedsInValueSet(entry.getPredecessorInValueSet(), entry.getSuccessorInValueSet()); 144  } 145  146  private static <K, V> void deleteFromMultimap(ValueEntry<K, V> entry) { 147  succeedsInMultimap(entry.getPredecessorInMultimap(), entry.getSuccessorInMultimap()); 148  } 149  150  /** 151  * LinkedHashMultimap entries are in no less than three coexisting linked lists: a bucket in the 152  * hash table for a {@code Set<V>} associated with a key, the linked list of insertion-ordered 153  * entries in that {@code Set<V>}, and the linked list of entries in the LinkedHashMultimap as a 154  * whole. 155  */ 156  @VisibleForTesting 157  static final class ValueEntry<K, V> extends ImmutableEntry<K, V> implements ValueSetLink<K, V> { 158  final int smearedValueHash; 159  160  @Nullable ValueEntry<K, V> nextInValueBucket; 161  162  @Nullable ValueSetLink<K, V> predecessorInValueSet; 163  @Nullable ValueSetLink<K, V> successorInValueSet; 164  165  @Nullable ValueEntry<K, V> predecessorInMultimap; 166  @Nullable ValueEntry<K, V> successorInMultimap; 167  168  ValueEntry( 169  @Nullable K key, 170  @Nullable V value, 171  int smearedValueHash, 172  @Nullable ValueEntry<K, V> nextInValueBucket) { 173  super(key, value); 174  this.smearedValueHash = smearedValueHash; 175  this.nextInValueBucket = nextInValueBucket; 176  } 177  178  boolean matchesValue(@Nullable Object v, int smearedVHash) { 179  return smearedValueHash == smearedVHash && Objects.equal(getValue(), v); 180  } 181  182  @Override 183  public ValueSetLink<K, V> getPredecessorInValueSet() { 184  return predecessorInValueSet; 185  } 186  187  @Override 188  public ValueSetLink<K, V> getSuccessorInValueSet() { 189  return successorInValueSet; 190  } 191  192  @Override 193  public void setPredecessorInValueSet(ValueSetLink<K, V> entry) { 194  predecessorInValueSet = entry; 195  } 196  197  @Override 198  public void setSuccessorInValueSet(ValueSetLink<K, V> entry) { 199  successorInValueSet = entry; 200  } 201  202  public ValueEntry<K, V> getPredecessorInMultimap() { 203  return predecessorInMultimap; 204  } 205  206  public ValueEntry<K, V> getSuccessorInMultimap() { 207  return successorInMultimap; 208  } 209  210  public void setSuccessorInMultimap(ValueEntry<K, V> multimapSuccessor) { 211  this.successorInMultimap = multimapSuccessor; 212  } 213  214  public void setPredecessorInMultimap(ValueEntry<K, V> multimapPredecessor) { 215  this.predecessorInMultimap = multimapPredecessor; 216  } 217  } 218  219  private static final int DEFAULT_KEY_CAPACITY = 16; 220  private static final int DEFAULT_VALUE_SET_CAPACITY = 2; 221  @VisibleForTesting static final double VALUE_SET_LOAD_FACTOR = 1.0; 222  223  @VisibleForTesting transient int valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; 224  private transient ValueEntry<K, V> multimapHeaderEntry; 225  226  private LinkedHashMultimap(int keyCapacity, int valueSetCapacity) { 227  super(Platform.<K, Collection<V>>newLinkedHashMapWithExpectedSize(keyCapacity)); 228  checkNonnegative(valueSetCapacity, "expectedValuesPerKey"); 229  230  this.valueSetCapacity = valueSetCapacity; 231  this.multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); 232  succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 233  } 234  235  /** 236  * {@inheritDoc} 237  * 238  * <p>Creates an empty {@code LinkedHashSet} for a collection of values for one key. 239  * 240  * @return a new {@code LinkedHashSet} containing a collection of values for one key 241  */ 242  @Override 243  Set<V> createCollection() { 244  return Platform.newLinkedHashSetWithExpectedSize(valueSetCapacity); 245  } 246  247  /** 248  * {@inheritDoc} 249  * 250  * <p>Creates a decorated insertion-ordered set that also keeps track of the order in which 251  * key-value pairs are added to the multimap. 252  * 253  * @param key key to associate with values in the collection 254  * @return a new decorated set containing a collection of values for one key 255  */ 256  @Override 257  Collection<V> createCollection(K key) { 258  return new ValueSet(key, valueSetCapacity); 259  } 260  261  /** 262  * {@inheritDoc} 263  * 264  * <p>If {@code values} is not empty and the multimap already contains a mapping for {@code key}, 265  * the {@code keySet()} ordering is unchanged. However, the provided values always come last in 266  * the {@link #entries()} and {@link #values()} iteration orderings. 267  */ 268  @CanIgnoreReturnValue 269  @Override 270  public Set<V> replaceValues(@Nullable K key, Iterable<? extends V> values) { 271  return super.replaceValues(key, values); 272  } 273  274  /** 275  * Returns a set of all key-value pairs. Changes to the returned set will update the underlying 276  * multimap, and vice versa. The entries set does not support the {@code add} or {@code addAll} 277  * operations. 278  * 279  * <p>The iterator generated by the returned set traverses the entries in the order they were 280  * added to the multimap. 281  * 282  * <p>Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the 283  * time the entry is returned by a method call to the collection or its iterator. 284  */ 285  @Override 286  public Set<Entry<K, V>> entries() { 287  return super.entries(); 288  } 289  290  /** 291  * Returns a view collection of all <i>distinct</i> keys contained in this multimap. Note that the 292  * key set contains a key if and only if this multimap maps that key to at least one value. 293  * 294  * <p>The iterator generated by the returned set traverses the keys in the order they were first 295  * added to the multimap. 296  * 297  * <p>Changes to the returned set will update the underlying multimap, and vice versa. However, 298  * <i>adding</i> to the returned set is not possible. 299  */ 300  @Override 301  public Set<K> keySet() { 302  return super.keySet(); 303  } 304  305  /** 306  * Returns a collection of all values in the multimap. Changes to the returned collection will 307  * update the underlying multimap, and vice versa. 308  * 309  * <p>The iterator generated by the returned collection traverses the values in the order they 310  * were added to the multimap. 311  */ 312  @Override 313  public Collection<V> values() { 314  return super.values(); 315  } 316  317  @VisibleForTesting 318  @WeakOuter 319  final class ValueSet extends Sets.ImprovedAbstractSet<V> implements ValueSetLink<K, V> { 320  /* 321  * We currently use a fixed load factor of 1.0, a bit higher than normal to reduce memory 322  * consumption. 323  */ 324  325  private final K key; 326  @VisibleForTesting ValueEntry<K, V>[] hashTable; 327  private int size = 0; 328  private int modCount = 0; 329  330  // We use the set object itself as the end of the linked list, avoiding an unnecessary 331  // entry object per key. 332  private ValueSetLink<K, V> firstEntry; 333  private ValueSetLink<K, V> lastEntry; 334  335  ValueSet(K key, int expectedValues) { 336  this.key = key; 337  this.firstEntry = this; 338  this.lastEntry = this; 339  // Round expected values up to a power of 2 to get the table size. 340  int tableSize = Hashing.closedTableSize(expectedValues, VALUE_SET_LOAD_FACTOR); 341  342  @SuppressWarnings("unchecked") 343  ValueEntry<K, V>[] hashTable = new ValueEntry[tableSize]; 344  this.hashTable = hashTable; 345  } 346  347  private int mask() { 348  return hashTable.length - 1; 349  } 350  351  @Override 352  public ValueSetLink<K, V> getPredecessorInValueSet() { 353  return lastEntry; 354  } 355  356  @Override 357  public ValueSetLink<K, V> getSuccessorInValueSet() { 358  return firstEntry; 359  } 360  361  @Override 362  public void setPredecessorInValueSet(ValueSetLink<K, V> entry) { 363  lastEntry = entry; 364  } 365  366  @Override 367  public void setSuccessorInValueSet(ValueSetLink<K, V> entry) { 368  firstEntry = entry; 369  } 370  371  @Override 372  public Iterator<V> iterator() { 373  return new Iterator<V>() { 374  ValueSetLink<K, V> nextEntry = firstEntry; 375  @Nullable ValueEntry<K, V> toRemove; 376  int expectedModCount = modCount; 377  378  private void checkForComodification() { 379  if (modCount != expectedModCount) { 380  throw new ConcurrentModificationException(); 381  } 382  } 383  384  @Override 385  public boolean hasNext() { 386  checkForComodification(); 387  return nextEntry != ValueSet.this; 388  } 389  390  @Override 391  public V next() { 392  if (!hasNext()) { 393  throw new NoSuchElementException(); 394  } 395  ValueEntry<K, V> entry = (ValueEntry<K, V>) nextEntry; 396  V result = entry.getValue(); 397  toRemove = entry; 398  nextEntry = entry.getSuccessorInValueSet(); 399  return result; 400  } 401  402  @Override 403  public void remove() { 404  checkForComodification(); 405  checkRemove(toRemove != null); 406  ValueSet.this.remove(toRemove.getValue()); 407  expectedModCount = modCount; 408  toRemove = null; 409  } 410  }; 411  } 412  413  @Override 414  public void forEach(Consumer<? super V> action) { 415  checkNotNull(action); 416  for (ValueSetLink<K, V> entry = firstEntry; 417  entry != ValueSet.this; 418  entry = entry.getSuccessorInValueSet()) { 419  action.accept(((ValueEntry<K, V>) entry).getValue()); 420  } 421  } 422  423  @Override 424  public int size() { 425  return size; 426  } 427  428  @Override 429  public boolean contains(@Nullable Object o) { 430  int smearedHash = Hashing.smearedHash(o); 431  for (ValueEntry<K, V> entry = hashTable[smearedHash & mask()]; 432  entry != null; 433  entry = entry.nextInValueBucket) { 434  if (entry.matchesValue(o, smearedHash)) { 435  return true; 436  } 437  } 438  return false; 439  } 440  441  @Override 442  public boolean add(@Nullable V value) { 443  int smearedHash = Hashing.smearedHash(value); 444  int bucket = smearedHash & mask(); 445  ValueEntry<K, V> rowHead = hashTable[bucket]; 446  for (ValueEntry<K, V> entry = rowHead; entry != null; entry = entry.nextInValueBucket) { 447  if (entry.matchesValue(value, smearedHash)) { 448  return false; 449  } 450  } 451  452  ValueEntry<K, V> newEntry = new ValueEntry<>(key, value, smearedHash, rowHead); 453  succeedsInValueSet(lastEntry, newEntry); 454  succeedsInValueSet(newEntry, this); 455  succeedsInMultimap(multimapHeaderEntry.getPredecessorInMultimap(), newEntry); 456  succeedsInMultimap(newEntry, multimapHeaderEntry); 457  hashTable[bucket] = newEntry; 458  size++; 459  modCount++; 460  rehashIfNecessary(); 461  return true; 462  } 463  464  private void rehashIfNecessary() { 465  if (Hashing.needsResizing(size, hashTable.length, VALUE_SET_LOAD_FACTOR)) { 466  @SuppressWarnings("unchecked") 467  ValueEntry<K, V>[] hashTable = new ValueEntry[this.hashTable.length * 2]; 468  this.hashTable = hashTable; 469  int mask = hashTable.length - 1; 470  for (ValueSetLink<K, V> entry = firstEntry; 471  entry != this; 472  entry = entry.getSuccessorInValueSet()) { 473  ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry; 474  int bucket = valueEntry.smearedValueHash & mask; 475  valueEntry.nextInValueBucket = hashTable[bucket]; 476  hashTable[bucket] = valueEntry; 477  } 478  } 479  } 480  481  @CanIgnoreReturnValue 482  @Override 483  public boolean remove(@Nullable Object o) { 484  int smearedHash = Hashing.smearedHash(o); 485  int bucket = smearedHash & mask(); 486  ValueEntry<K, V> prev = null; 487  for (ValueEntry<K, V> entry = hashTable[bucket]; 488  entry != null; 489  prev = entry, entry = entry.nextInValueBucket) { 490  if (entry.matchesValue(o, smearedHash)) { 491  if (prev == null) { 492  // first entry in the bucket 493  hashTable[bucket] = entry.nextInValueBucket; 494  } else { 495  prev.nextInValueBucket = entry.nextInValueBucket; 496  } 497  deleteFromValueSet(entry); 498  deleteFromMultimap(entry); 499  size--; 500  modCount++; 501  return true; 502  } 503  } 504  return false; 505  } 506  507  @Override 508  public void clear() { 509  Arrays.fill(hashTable, null); 510  size = 0; 511  for (ValueSetLink<K, V> entry = firstEntry; 512  entry != this; 513  entry = entry.getSuccessorInValueSet()) { 514  ValueEntry<K, V> valueEntry = (ValueEntry<K, V>) entry; 515  deleteFromMultimap(valueEntry); 516  } 517  succeedsInValueSet(this, this); 518  modCount++; 519  } 520  } 521  522  @Override 523  Iterator<Entry<K, V>> entryIterator() { 524  return new Iterator<Entry<K, V>>() { 525  ValueEntry<K, V> nextEntry = multimapHeaderEntry.successorInMultimap; 526  @Nullable ValueEntry<K, V> toRemove; 527  528  @Override 529  public boolean hasNext() { 530  return nextEntry != multimapHeaderEntry; 531  } 532  533  @Override 534  public Entry<K, V> next() { 535  if (!hasNext()) { 536  throw new NoSuchElementException(); 537  } 538  ValueEntry<K, V> result = nextEntry; 539  toRemove = result; 540  nextEntry = nextEntry.successorInMultimap; 541  return result; 542  } 543  544  @Override 545  public void remove() { 546  checkRemove(toRemove != null); 547  LinkedHashMultimap.this.remove(toRemove.getKey(), toRemove.getValue()); 548  toRemove = null; 549  } 550  }; 551  } 552  553  @Override 554  Spliterator<Entry<K, V>> entrySpliterator() { 555  return Spliterators.spliterator(entries(), Spliterator.DISTINCT | Spliterator.ORDERED); 556  } 557  558  @Override 559  Iterator<V> valueIterator() { 560  return Maps.valueIterator(entryIterator()); 561  } 562  563  @Override 564  Spliterator<V> valueSpliterator() { 565  return CollectSpliterators.map(entrySpliterator(), Entry::getValue); 566  } 567  568  @Override 569  public void clear() { 570  super.clear(); 571  succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 572  } 573  574  /** 575  * @serialData the expected values per key, the number of distinct keys, the number of entries, 576  * and the entries in order 577  */ 578  @GwtIncompatible // java.io.ObjectOutputStream 579  private void writeObject(ObjectOutputStream stream) throws IOException { 580  stream.defaultWriteObject(); 581  stream.writeInt(keySet().size()); 582  for (K key : keySet()) { 583  stream.writeObject(key); 584  } 585  stream.writeInt(size()); 586  for (Entry<K, V> entry : entries()) { 587  stream.writeObject(entry.getKey()); 588  stream.writeObject(entry.getValue()); 589  } 590  } 591  592  @GwtIncompatible // java.io.ObjectInputStream 593  private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 594  stream.defaultReadObject(); 595  multimapHeaderEntry = new ValueEntry<>(null, null, 0, null); 596  succeedsInMultimap(multimapHeaderEntry, multimapHeaderEntry); 597  valueSetCapacity = DEFAULT_VALUE_SET_CAPACITY; 598  int distinctKeys = stream.readInt(); 599  Map<K, Collection<V>> map = Platform.newLinkedHashMapWithExpectedSize(12); 600  for (int i = 0; i < distinctKeys; i++) { 601  @SuppressWarnings("unchecked") 602  K key = (K) stream.readObject(); 603  map.put(key, createCollection(key)); 604  } 605  int entries = stream.readInt(); 606  for (int i = 0; i < entries; i++) { 607  @SuppressWarnings("unchecked") 608  K key = (K) stream.readObject(); 609  @SuppressWarnings("unchecked") 610  V value = (V) stream.readObject(); 611  map.get(key).add(value); 612  } 613  setMap(map); 614  } 615  616  @GwtIncompatible // java serialization not supported 617  private static final long serialVersionUID = 1; 618 }