com.google.common.collect.LinkedListMultimap Maven / Gradle / Ivy
Show all versions of guava Show documentation
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.collect;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndex;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.CollectPreconditions.checkRemove;
import static java.util.Collections.unmodifiableList;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.j2objc.annotations.WeakOuter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractSequentialList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* An implementation of {@code ListMultimap} that supports deterministic iteration order for both
* keys and values. The iteration order is preserved across non-distinct key values. For example,
* for the following multimap definition:
*
* {@code
* Multimap multimap = LinkedListMultimap.create();
* multimap.put(key1, foo);
* multimap.put(key2, bar);
* multimap.put(key1, baz);
* }
*
* ... the iteration order for {@link #keys()} is {@code [key1, key2, key1]}, and similarly for
* {@link #entries()}. Unlike {@link LinkedHashMultimap}, the iteration order is kept consistent
* between keys, entries and values. For example, calling:
*
* {@code
* multimap.remove(key1, foo);
* }
*
* changes the entries iteration order to {@code [key2=bar, key1=baz]} and the key iteration
* order to {@code [key2, key1]}. The {@link #entries()} iterator returns mutable map entries, and
* {@link #replaceValues} attempts to preserve iteration order as much as possible.
*
*
The collections returned by {@link #keySet()} and {@link #asMap} iterate through the keys in
* the order they were first added to the multimap. Similarly, {@link #get}, {@link #removeAll}, and
* {@link #replaceValues} return collections that iterate through the values in the order they were
* added. The collections generated by {@link #entries()}, {@link #keys()}, and {@link #values}
* iterate across the key-value mappings in the order they were added to the multimap.
*
*
The {@link #values()} and {@link #entries()} methods both return a {@code List}, instead of
* the {@code Collection} specified by the {@link ListMultimap} interface.
*
*
The methods {@link #get}, {@link #keySet()}, {@link #keys()}, {@link #values}, {@link
* #entries()}, and {@link #asMap} return collections that are views of the multimap. If the
* multimap is modified while an iteration over any of those collections is in progress, except
* through the iterator's methods, the results of the iteration are undefined.
*
*
Keys and values may be null. All optional multimap methods are supported, and all returned
* views are modifiable.
*
*
This class is not threadsafe when any concurrent operations update the multimap. Concurrent
* read operations will work correctly. To allow concurrent update operations, wrap your multimap
* with a call to {@link Multimaps#synchronizedListMultimap}.
*
*
See the Guava User Guide article on {@code
* Multimap}.
*
* @author Mike Bostock
* @since 2.0
*/
@GwtCompatible(serializable = true, emulated = true)
public class LinkedListMultimap extends AbstractMultimap
implements ListMultimap, Serializable {
/*
* Order is maintained using a linked list containing all key-value pairs. In
* addition, a series of disjoint linked lists of "siblings", each containing
* the values for a specific key, is used to implement {@link
* ValueForKeyIterator} in constant time.
*/
private static final class Node extends AbstractMapEntry {
final @Nullable K key;
@Nullable V value;
@Nullable Node next; // the next node (with any key)
@Nullable Node previous; // the previous node (with any key)
@Nullable Node nextSibling; // the next node with the same key
@Nullable Node previousSibling; // the previous node with the same key
Node(@Nullable K key, @Nullable V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(@Nullable V newValue) {
V result = value;
this.value = newValue;
return result;
}
}
private static class KeyList {
Node head;
Node tail;
int count;
KeyList(Node firstNode) {
this.head = firstNode;
this.tail = firstNode;
firstNode.previousSibling = null;
firstNode.nextSibling = null;
this.count = 1;
}
}
private transient @Nullable Node head; // the head for all keys
private transient @Nullable Node tail; // the tail for all keys
private transient Map> keyToKeyList;
private transient int size;
/*
* Tracks modifications to keyToKeyList so that addition or removal of keys invalidates
* preexisting iterators. This does *not* track simple additions and removals of values
* that are not the first to be added or last to be removed for their key.
*/
private transient int modCount;
/** Creates a new, empty {@code LinkedListMultimap} with the default initial capacity. */
public static LinkedListMultimap create() {
return new LinkedListMultimap<>();
}
/**
* Constructs an empty {@code LinkedListMultimap} with enough capacity to hold the specified
* number of keys without rehashing.
*
* @param expectedKeys the expected number of distinct keys
* @throws IllegalArgumentException if {@code expectedKeys} is negative
*/
public static LinkedListMultimap create(int expectedKeys) {
return new LinkedListMultimap<>(expectedKeys);
}
/**
* Constructs a {@code LinkedListMultimap} with the same mappings as the specified {@code
* Multimap}. The new multimap has the same {@link Multimap#entries()} iteration order as the
* input multimap.
*
* @param multimap the multimap whose contents are copied to this multimap
*/
public static LinkedListMultimap create(
Multimap extends K, ? extends V> multimap) {
return new LinkedListMultimap<>(multimap);
}
LinkedListMultimap() {
this(12);
}
private LinkedListMultimap(int expectedKeys) {
keyToKeyList = Platform.newHashMapWithExpectedSize(expectedKeys);
}
private LinkedListMultimap(Multimap extends K, ? extends V> multimap) {
this(multimap.keySet().size());
putAll(multimap);
}
/**
* Adds a new node for the specified key-value pair before the specified {@code nextSibling}
* element, or at the end of the list if {@code nextSibling} is null. Note: if {@code nextSibling}
* is specified, it MUST be for an node for the same {@code key}!
*/
@CanIgnoreReturnValue
private Node addNode(@Nullable K key, @Nullable V value, @Nullable Node nextSibling) {
Node node = new Node<>(key, value);
if (head == null) { // empty list
head = tail = node;
keyToKeyList.put(key, new KeyList(node));
modCount++;
} else if (nextSibling == null) { // non-empty list, add to tail
tail.next = node;
node.previous = tail;
tail = node;
KeyList keyList = keyToKeyList.get(key);
if (keyList == null) {
keyToKeyList.put(key, keyList = new KeyList<>(node));
modCount++;
} else {
keyList.count++;
Node keyTail = keyList.tail;
keyTail.nextSibling = node;
node.previousSibling = keyTail;
keyList.tail = node;
}
} else { // non-empty list, insert before nextSibling
KeyList keyList = keyToKeyList.get(key);
keyList.count++;
node.previous = nextSibling.previous;
node.previousSibling = nextSibling.previousSibling;
node.next = nextSibling;
node.nextSibling = nextSibling;
if (nextSibling.previousSibling == null) { // nextSibling was key head
keyToKeyList.get(key).head = node;
} else {
nextSibling.previousSibling.nextSibling = node;
}
if (nextSibling.previous == null) { // nextSibling was head
head = node;
} else {
nextSibling.previous.next = node;
}
nextSibling.previous = node;
nextSibling.previousSibling = node;
}
size++;
return node;
}
/**
* Removes the specified node from the linked list. This method is only intended to be used from
* the {@code Iterator} classes. See also {@link LinkedListMultimap#removeAllNodes(Object)}.
*/
private void removeNode(Node node) {
if (node.previous != null) {
node.previous.next = node.next;
} else { // node was head
head = node.next;
}
if (node.next != null) {
node.next.previous = node.previous;
} else { // node was tail
tail = node.previous;
}
if (node.previousSibling == null && node.nextSibling == null) {
KeyList keyList = keyToKeyList.remove(node.key);
keyList.count = 0;
modCount++;
} else {
KeyList keyList = keyToKeyList.get(node.key);
keyList.count--;
if (node.previousSibling == null) {
keyList.head = node.nextSibling;
} else {
node.previousSibling.nextSibling = node.nextSibling;
}
if (node.nextSibling == null) {
keyList.tail = node.previousSibling;
} else {
node.nextSibling.previousSibling = node.previousSibling;
}
}
size--;
}
/** Removes all nodes for the specified key. */
private void removeAllNodes(@Nullable Object key) {
Iterators.clear(new ValueForKeyIterator(key));
}
/** Helper method for verifying that an iterator element is present. */
private static void checkElement(@Nullable Object node) {
if (node == null) {
throw new NoSuchElementException();
}
}
/** An {@code Iterator} over all nodes. */
private class NodeIterator implements ListIterator> {
int nextIndex;
@Nullable Node next;
@Nullable Node current;
@Nullable Node previous;
int expectedModCount = modCount;
NodeIterator(int index) {
int size = size();
checkPositionIndex(index, size);
if (index >= (size / 2)) {
previous = tail;
nextIndex = size;
while (index++ < size) {
previous();
}
} else {
next = head;
while (index-- > 0) {
next();
}
}
current = null;
}
private void checkForConcurrentModification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
@Override
public boolean hasNext() {
checkForConcurrentModification();
return next != null;
}
@CanIgnoreReturnValue
@Override
public Node next() {
checkForConcurrentModification();
checkElement(next);
previous = current = next;
next = next.next;
nextIndex++;
return current;
}
@Override
public void remove() {
checkForConcurrentModification();
checkRemove(current != null);
if (current != next) { // after call to next()
previous = current.previous;
nextIndex--;
} else { // after call to previous()
next = current.next;
}
removeNode(current);
current = null;
expectedModCount = modCount;
}
@Override
public boolean hasPrevious() {
checkForConcurrentModification();
return previous != null;
}
@CanIgnoreReturnValue
@Override
public Node previous() {
checkForConcurrentModification();
checkElement(previous);
next = current = previous;
previous = previous.previous;
nextIndex--;
return current;
}
@Override
public int nextIndex() {
return nextIndex;
}
@Override
public int previousIndex() {
return nextIndex - 1;
}
@Override
public void set(Entry e) {
throw new UnsupportedOperationException();
}
@Override
public void add(Entry e) {
throw new UnsupportedOperationException();
}
void setValue(V value) {
checkState(current != null);
current.value = value;
}
}
/** An {@code Iterator} over distinct keys in key head order. */
private class DistinctKeyIterator implements Iterator {
final Set seenKeys = Sets.newHashSetWithExpectedSize(keySet().size());
Node next = head;
@Nullable Node current;
int expectedModCount = modCount;
private void checkForConcurrentModification() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
@Override
public boolean hasNext() {
checkForConcurrentModification();
return next != null;
}
@Override
public K next() {
checkForConcurrentModification();
checkElement(next);
current = next;
seenKeys.add(current.key);
do { // skip ahead to next unseen key
next = next.next;
} while ((next != null) && !seenKeys.add(next.key));
return current.key;
}
@Override
public void remove() {
checkForConcurrentModification();
checkRemove(current != null);
removeAllNodes(current.key);
current = null;
expectedModCount = modCount;
}
}
/** A {@code ListIterator} over values for a specified key. */
private class ValueForKeyIterator implements ListIterator {
final @Nullable Object key;
int nextIndex;
@Nullable Node next;
@Nullable Node current;
@Nullable Node previous;
/** Constructs a new iterator over all values for the specified key. */
ValueForKeyIterator(@Nullable Object key) {
this.key = key;
KeyList keyList = keyToKeyList.get(key);
next = (keyList == null) ? null : keyList.head;
}
/**
* Constructs a new iterator over all values for the specified key starting at the specified
* index. This constructor is optimized so that it starts at either the head or the tail,
* depending on which is closer to the specified index. This allows adds to the tail to be done
* in constant time.
*
* @throws IndexOutOfBoundsException if index is invalid
*/
public ValueForKeyIterator(@Nullable Object key, int index) {
KeyList keyList = keyToKeyList.get(key);
int size = (keyList == null) ? 0 : keyList.count;
checkPositionIndex(index, size);
if (index >= (size / 2)) {
previous = (keyList == null) ? null : keyList.tail;
nextIndex = size;
while (index++ < size) {
previous();
}
} else {
next = (keyList == null) ? null : keyList.head;
while (index-- > 0) {
next();
}
}
this.key = key;
current = null;
}
@Override
public boolean hasNext() {
return next != null;
}
@CanIgnoreReturnValue
@Override
public V next() {
checkElement(next);
previous = current = next;
next = next.nextSibling;
nextIndex++;
return current.value;
}
@Override
public boolean hasPrevious() {
return previous != null;
}
@CanIgnoreReturnValue
@Override
public V previous() {
checkElement(previous);
next = current = previous;
previous = previous.previousSibling;
nextIndex--;
return current.value;
}
@Override
public int nextIndex() {
return nextIndex;
}
@Override
public int previousIndex() {
return nextIndex - 1;
}
@Override
public void remove() {
checkRemove(current != null);
if (current != next) { // after call to next()
previous = current.previousSibling;
nextIndex--;
} else { // after call to previous()
next = current.nextSibling;
}
removeNode(current);
current = null;
}
@Override
public void set(V value) {
checkState(current != null);
current.value = value;
}
@Override
@SuppressWarnings("unchecked")
public void add(V value) {
previous = addNode((K) key, value, next);
nextIndex++;
current = null;
}
}
// Query Operations
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return head == null;
}
@Override
public boolean containsKey(@Nullable Object key) {
return keyToKeyList.containsKey(key);
}
@Override
public boolean containsValue(@Nullable Object value) {
return values().contains(value);
}
// Modification Operations
/**
* Stores a key-value pair in the multimap.
*
* @param key key to store in the multimap
* @param value value to store in the multimap
* @return {@code true} always
*/
@CanIgnoreReturnValue
@Override
public boolean put(@Nullable K key, @Nullable V value) {
addNode(key, value, null);
return true;
}
// Bulk Operations
/**
* {@inheritDoc}
*
* If any entries for the specified {@code key} already exist in the multimap, their values are
* changed in-place without affecting the iteration order.
*
*
The returned list is immutable and implements {@link java.util.RandomAccess}.
*/
@CanIgnoreReturnValue
@Override
public List replaceValues(@Nullable K key, Iterable extends V> values) {
List oldValues = getCopy(key);
ListIterator keyValues = new ValueForKeyIterator(key);
Iterator extends V> newValues = values.iterator();
// Replace existing values, if any.
while (keyValues.hasNext() && newValues.hasNext()) {
keyValues.next();
keyValues.set(newValues.next());
}
// Remove remaining old values, if any.
while (keyValues.hasNext()) {
keyValues.next();
keyValues.remove();
}
// Add remaining new values, if any.
while (newValues.hasNext()) {
keyValues.add(newValues.next());
}
return oldValues;
}
private List getCopy(@Nullable Object key) {
return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key)));
}
/**
* {@inheritDoc}
*
* The returned list is immutable and implements {@link java.util.RandomAccess}.
*/
@CanIgnoreReturnValue
@Override
public List removeAll(@Nullable Object key) {
List oldValues = getCopy(key);
removeAllNodes(key);
return oldValues;
}
@Override
public void clear() {
head = null;
tail = null;
keyToKeyList.clear();
size = 0;
modCount++;
}
// Views
/**
* {@inheritDoc}
*
* If the multimap is modified while an iteration over the list is in progress (except through
* the iterator's own {@code add}, {@code set} or {@code remove} operations) the results of the
* iteration are undefined.
*
*
The returned list is not serializable and does not have random access.
*/
@Override
public List get(final @Nullable K key) {
return new AbstractSequentialList() {
@Override
public int size() {
KeyList keyList = keyToKeyList.get(key);
return (keyList == null) ? 0 : keyList.count;
}
@Override
public ListIterator listIterator(int index) {
return new ValueForKeyIterator(key, index);
}
};
}
@Override
Set createKeySet() {
@WeakOuter
class KeySetImpl extends Sets.ImprovedAbstractSet {
@Override
public int size() {
return keyToKeyList.size();
}
@Override
public Iterator iterator() {
return new DistinctKeyIterator();
}
@Override
public boolean contains(Object key) { // for performance
return containsKey(key);
}
@Override
public boolean remove(Object o) { // for performance
return !LinkedListMultimap.this.removeAll(o).isEmpty();
}
}
return new KeySetImpl();
}
@Override
Multiset createKeys() {
return new Multimaps.Keys(this);
}
/**
* {@inheritDoc}
*
* The iterator generated by the returned collection traverses the values in the order they
* were added to the multimap. Because the values may have duplicates and follow the insertion
* ordering, this method returns a {@link List}, instead of the {@link Collection} specified in
* the {@link ListMultimap} interface.
*/
@Override
public List values() {
return (List) super.values();
}
@Override
List createValues() {
@WeakOuter
class ValuesImpl extends AbstractSequentialList {
@Override
public int size() {
return size;
}
@Override
public ListIterator listIterator(int index) {
final NodeIterator nodeItr = new NodeIterator(index);
return new TransformedListIterator, V>(nodeItr) {
@Override
V transform(Entry entry) {
return entry.getValue();
}
@Override
public void set(V value) {
nodeItr.setValue(value);
}
};
}
}
return new ValuesImpl();
}
/**
* {@inheritDoc}
*
* The iterator generated by the returned collection traverses the entries in the order they
* were added to the multimap. Because the entries may have duplicates and follow the insertion
* ordering, this method returns a {@link List}, instead of the {@link Collection} specified in
* the {@link ListMultimap} interface.
*
*
An entry's {@link Entry#getKey} method always returns the same key, regardless of what
* happens subsequently. As long as the corresponding key-value mapping is not removed from the
* multimap, {@link Entry#getValue} returns the value from the multimap, which may change over
* time, and {@link Entry#setValue} modifies that value. Removing the mapping from the multimap
* does not alter the value returned by {@code getValue()}, though a subsequent {@code setValue()}
* call won't update the multimap but will lead to a revised value being returned by {@code
* getValue()}.
*/
@Override
public List> entries() {
return (List>) super.entries();
}
@Override
List> createEntries() {
@WeakOuter
class EntriesImpl extends AbstractSequentialList> {
@Override
public int size() {
return size;
}
@Override
public ListIterator> listIterator(int index) {
return new NodeIterator(index);
}
@Override
public void forEach(Consumer super Entry> action) {
checkNotNull(action);
for (Node node = head; node != null; node = node.next) {
action.accept(node);
}
}
}
return new EntriesImpl();
}
@Override
Iterator> entryIterator() {
throw new AssertionError("should never be called");
}
@Override
Map> createAsMap() {
return new Multimaps.AsMap<>(this);
}
/**
* @serialData the number of distinct keys, and then for each distinct key: the first key, the
* number of values for that key, and the key's values, followed by successive keys and values
* from the entries() ordering
*/
@GwtIncompatible // java.io.ObjectOutputStream
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeInt(size());
for (Entry entry : entries()) {
stream.writeObject(entry.getKey());
stream.writeObject(entry.getValue());
}
}
@GwtIncompatible // java.io.ObjectInputStream
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
keyToKeyList = Maps.newLinkedHashMap();
int size = stream.readInt();
for (int i = 0; i < size; i++) {
@SuppressWarnings("unchecked") // reading data stored by writeObject
K key = (K) stream.readObject();
@SuppressWarnings("unchecked") // reading data stored by writeObject
V value = (V) stream.readObject();
put(key, value);
}
}
@GwtIncompatible // java serialization not supported
private static final long serialVersionUID = 0;
}