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}