com.fitbur.google.common.collect.LinkedListMultimap Maven / Gradle / Ivy
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in com.fitburpliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.com.fitbur/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.fitbur.google.com.fitburmon.collect;
import static com.fitbur.google.com.fitburmon.base.Preconditions.checkPositionIndex;
import static com.fitbur.google.com.fitburmon.base.Preconditions.checkState;
import static com.fitbur.google.com.fitburmon.collect.CollectPreconditions.checkRemove;
import static java.util.Collections.unmodifiableList;
import com.fitbur.google.com.fitburmon.annotations.GwtCompatible;
import com.fitbur.google.com.fitburmon.annotations.GwtIncompatible;
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.HashMap;
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 javax.annotation.Nullable;
/**
* An implementation of {@code ListMultimap} that supports com.fitburterministic
* iteration order for both keys and values. The iteration order is preserved
* across non-distinct key values. For example, for the following multimap
* com.fitburfinition: {@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
*
* map.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 (imported from Google Collections Library)
*/
@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 K key;
V value;
Node next; // the next node (with any key)
Node previous; // the previous node (with any key)
Node nextSibling; // the next node with the same key
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 Node head; // the head for all keys
private transient 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 com.fitburfault 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() {
keyToKeyList = Maps.newHashMap();
}
private LinkedListMultimap(int expectedKeys) {
keyToKeyList = new HashMap>(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}!
*/
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;
Node next;
Node current;
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;
}
@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;
}
@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;
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 Object key;
int nextIndex;
Node next;
Node current;
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, com.fitburpending 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;
}
@Override
public V next() {
checkElement(next);
previous = current = next;
next = next.nextSibling;
nextIndex++;
return current.value;
}
@Override
public boolean hasPrevious() {
return previous != null;
}
@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
*/
@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}.
*/
@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}.
*/
@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() {
return new 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();
}
};
}
/**
* {@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() {
return new 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);
}
};
}
};
}
/**
* {@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() {
return new AbstractSequentialList>() {
@Override public int size() {
return size;
}
@Override public ListIterator> listIterator(int index) {
return new NodeIterator(index);
}
};
}
@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.com.fitburfaultWriteObject();
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.com.fitburfaultReadObject();
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;
}