com.google.common.collect.AbstractMapBasedMultimap 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 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.NullnessCasts.uncheckedCastNullableTToT;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.Maps.ViewCachingAbstractMap;
import com.google.j2objc.annotations.WeakOuter;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.NavigableMap;
import java.util.NavigableSet;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.Spliterator;
import java.util.function.BiConsumer;
import javax.annotation.CheckForNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Basic implementation of the {@link Multimap} interface. This class represents a multimap as a map
* that associates each key with a collection of values. All methods of {@link Multimap} are
* supported, including those specified as optional in the interface.
*
* To implement a multimap, a subclass must define the method {@link #createCollection()}, which
* creates an empty collection of values for a key.
*
*
The multimap constructor takes a map that has a single entry for each distinct key. When you
* insert a key-value pair with a key that isn't already in the multimap, {@code
* AbstractMapBasedMultimap} calls {@link #createCollection()} to create the collection of values
* for that key. The subclass should not call {@link #createCollection()} directly, and a new
* instance should be created every time the method is called.
*
*
For example, the subclass could pass a {@link java.util.TreeMap} during construction, and
* {@link #createCollection()} could return a {@link java.util.TreeSet}, in which case the
* multimap's iterators would propagate through the keys and values in sorted order.
*
*
Keys and values may be null, as long as the underlying collection classes support null
* elements.
*
*
The collections created by {@link #createCollection()} may or may not allow duplicates. If the
* collection, such as a {@link Set}, does not support duplicates, an added key-value pair will
* replace an existing pair with the same key and value, if such a pair is present. With collections
* like {@link List} that allow duplicates, the collection will keep the existing key-value pairs
* while adding a new pair.
*
*
This class is not threadsafe when any concurrent operations update the multimap, even if the
* underlying map and {@link #createCollection()} method return threadsafe classes. Concurrent read
* operations will work correctly. To allow concurrent update operations, wrap your multimap with a
* call to {@link Multimaps#synchronizedMultimap}.
*
*
For serialization to work, the subclass must specify explicit {@code readObject} and {@code
* writeObject} methods.
*
* @author Jared Levy
* @author Louis Wasserman
*/
@GwtCompatible
@ElementTypesAreNonnullByDefault
abstract class AbstractMapBasedMultimap
extends AbstractMultimap implements Serializable {
/*
* Here's an outline of the overall design.
*
* The map variable contains the collection of values associated with each
* key. When a key-value pair is added to a multimap that didn't previously
* contain any values for that key, a new collection generated by
* createCollection is added to the map. That same collection instance
* remains in the map as long as the multimap has any values for the key. If
* all values for the key are removed, the key and collection are removed
* from the map.
*
* The get method returns a WrappedCollection, which decorates the collection
* in the map (if the key is present) or an empty collection (if the key is
* not present). When the collection delegate in the WrappedCollection is
* empty, the multimap may contain subsequently added values for that key. To
* handle that situation, the WrappedCollection checks whether map contains
* an entry for the provided key, and if so replaces the delegate.
*/
private transient Map> map;
private transient int totalSize;
/**
* Creates a new multimap that uses the provided map.
*
* @param map place to store the mapping from each key to its corresponding values
* @throws IllegalArgumentException if {@code map} is not empty
*/
protected AbstractMapBasedMultimap(Map> map) {
checkArgument(map.isEmpty());
this.map = map;
}
/** Used during deserialization only. */
final void setMap(Map> map) {
this.map = map;
totalSize = 0;
for (Collection values : map.values()) {
checkArgument(!values.isEmpty());
totalSize += values.size();
}
}
/**
* Creates an unmodifiable, empty collection of values.
*
* This is used in {@link #removeAll} on an empty key.
*/
Collection createUnmodifiableEmptyCollection() {
return unmodifiableCollectionSubclass(createCollection());
}
/**
* Creates the collection of values for a single key.
*
* Collections with weak, soft, or phantom references are not supported. Each call to {@code
* createCollection} should create a new instance.
*
*
The returned collection class determines whether duplicate key-value pairs are allowed.
*
* @return an empty collection of values
*/
abstract Collection createCollection();
/**
* Creates the collection of values for an explicitly provided key. By default, it simply calls
* {@link #createCollection()}, which is the correct behavior for most implementations. The {@link
* LinkedHashMultimap} class overrides it.
*
* @param key key to associate with values in the collection
* @return an empty collection of values
*/
Collection createCollection(@ParametricNullness K key) {
return createCollection();
}
Map> backingMap() {
return map;
}
// Query Operations
@Override
public int size() {
return totalSize;
}
@Override
public boolean containsKey(@CheckForNull Object key) {
return map.containsKey(key);
}
// Modification Operations
@Override
public boolean put(@ParametricNullness K key, @ParametricNullness V value) {
Collection collection = map.get(key);
if (collection == null) {
collection = createCollection(key);
if (collection.add(value)) {
totalSize++;
map.put(key, collection);
return true;
} else {
throw new AssertionError("New Collection violated the Collection spec");
}
} else if (collection.add(value)) {
totalSize++;
return true;
} else {
return false;
}
}
private Collection getOrCreateCollection(@ParametricNullness K key) {
Collection collection = map.get(key);
if (collection == null) {
collection = createCollection(key);
map.put(key, collection);
}
return collection;
}
// Bulk Operations
/**
* {@inheritDoc}
*
* The returned collection is immutable.
*/
@Override
public Collection replaceValues(@ParametricNullness K key, Iterable extends V> values) {
Iterator extends V> iterator = values.iterator();
if (!iterator.hasNext()) {
return removeAll(key);
}
// TODO(lowasser): investigate atomic failure?
Collection collection = getOrCreateCollection(key);
Collection oldValues = createCollection();
oldValues.addAll(collection);
totalSize -= collection.size();
collection.clear();
while (iterator.hasNext()) {
if (collection.add(iterator.next())) {
totalSize++;
}
}
return unmodifiableCollectionSubclass(oldValues);
}
/**
* {@inheritDoc}
*
* The returned collection is immutable.
*/
@Override
public Collection removeAll(@CheckForNull Object key) {
Collection collection = map.remove(key);
if (collection == null) {
return createUnmodifiableEmptyCollection();
}
Collection output = createCollection();
output.addAll(collection);
totalSize -= collection.size();
collection.clear();
return unmodifiableCollectionSubclass(output);
}
Collection unmodifiableCollectionSubclass(
Collection collection) {
return Collections.unmodifiableCollection(collection);
}
@Override
public void clear() {
// Clear each collection, to make previously returned collections empty.
for (Collection collection : map.values()) {
collection.clear();
}
map.clear();
totalSize = 0;
}
// Views
/**
* {@inheritDoc}
*
* The returned collection is not serializable.
*/
@Override
public Collection get(@ParametricNullness K key) {
Collection collection = map.get(key);
if (collection == null) {
collection = createCollection(key);
}
return wrapCollection(key, collection);
}
/**
* Generates a decorated collection that remains consistent with the values in the multimap for
* the provided key. Changes to the multimap may alter the returned collection, and vice versa.
*/
Collection wrapCollection(@ParametricNullness K key, Collection collection) {
return new WrappedCollection(key, collection, null);
}
final List wrapList(
@ParametricNullness K key, List list, @CheckForNull WrappedCollection ancestor) {
return (list instanceof RandomAccess)
? new RandomAccessWrappedList(key, list, ancestor)
: new WrappedList(key, list, ancestor);
}
/**
* Collection decorator that stays in sync with the multimap values for a key. There are two kinds
* of wrapped collections: full and subcollections. Both have a delegate pointing to the
* underlying collection class.
*
* Full collections, identified by a null ancestor field, contain all multimap values for a
* given key. Its delegate is a value in {@link AbstractMapBasedMultimap#map} whenever the
* delegate is non-empty. The {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap}
* methods ensure that the {@code WrappedCollection} and map remain consistent.
*
*
A subcollection, such as a sublist, contains some of the values for a given key. Its
* ancestor field points to the full wrapped collection with all values for the key. The
* subcollection {@code refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} methods call
* the corresponding methods of the full wrapped collection.
*/
@WeakOuter
class WrappedCollection extends AbstractCollection {
@ParametricNullness final K key;
Collection delegate;
@CheckForNull final WrappedCollection ancestor;
@CheckForNull final Collection ancestorDelegate;
WrappedCollection(
@ParametricNullness K key,
Collection delegate,
@CheckForNull WrappedCollection ancestor) {
this.key = key;
this.delegate = delegate;
this.ancestor = ancestor;
this.ancestorDelegate = (ancestor == null) ? null : ancestor.getDelegate();
}
/**
* If the delegate collection is empty, but the multimap has values for the key, replace the
* delegate with the new collection for the key.
*
* For a subcollection, refresh its ancestor and validate that the ancestor delegate hasn't
* changed.
*/
void refreshIfEmpty() {
if (ancestor != null) {
ancestor.refreshIfEmpty();
if (ancestor.getDelegate() != ancestorDelegate) {
throw new ConcurrentModificationException();
}
} else if (delegate.isEmpty()) {
Collection newDelegate = map.get(key);
if (newDelegate != null) {
delegate = newDelegate;
}
}
}
/**
* If collection is empty, remove it from {@code AbstractMapBasedMultimap.this.map}. For
* subcollections, check whether the ancestor collection is empty.
*/
void removeIfEmpty() {
if (ancestor != null) {
ancestor.removeIfEmpty();
} else if (delegate.isEmpty()) {
map.remove(key);
}
}
@ParametricNullness
K getKey() {
return key;
}
/**
* Add the delegate to the map. Other {@code WrappedCollection} methods should call this method
* after adding elements to a previously empty collection.
*
* Subcollection add the ancestor's delegate instead.
*/
void addToMap() {
if (ancestor != null) {
ancestor.addToMap();
} else {
map.put(key, delegate);
}
}
@Override
public int size() {
refreshIfEmpty();
return delegate.size();
}
@Override
public boolean equals(@CheckForNull Object object) {
if (object == this) {
return true;
}
refreshIfEmpty();
return delegate.equals(object);
}
@Override
public int hashCode() {
refreshIfEmpty();
return delegate.hashCode();
}
@Override
public String toString() {
refreshIfEmpty();
return delegate.toString();
}
Collection getDelegate() {
return delegate;
}
@Override
public Iterator iterator() {
refreshIfEmpty();
return new WrappedIterator();
}
@Override
public Spliterator spliterator() {
refreshIfEmpty();
return delegate.spliterator();
}
/** Collection iterator for {@code WrappedCollection}. */
class WrappedIterator implements Iterator {
final Iterator delegateIterator;
final Collection originalDelegate = delegate;
WrappedIterator() {
delegateIterator = iteratorOrListIterator(delegate);
}
WrappedIterator(Iterator delegateIterator) {
this.delegateIterator = delegateIterator;
}
/**
* If the delegate changed since the iterator was created, the iterator is no longer valid.
*/
void validateIterator() {
refreshIfEmpty();
if (delegate != originalDelegate) {
throw new ConcurrentModificationException();
}
}
@Override
public boolean hasNext() {
validateIterator();
return delegateIterator.hasNext();
}
@Override
@ParametricNullness
public V next() {
validateIterator();
return delegateIterator.next();
}
@Override
public void remove() {
delegateIterator.remove();
totalSize--;
removeIfEmpty();
}
Iterator getDelegateIterator() {
validateIterator();
return delegateIterator;
}
}
@Override
public boolean add(@ParametricNullness V value) {
refreshIfEmpty();
boolean wasEmpty = delegate.isEmpty();
boolean changed = delegate.add(value);
if (changed) {
totalSize++;
if (wasEmpty) {
addToMap();
}
}
return changed;
}
@CheckForNull
WrappedCollection getAncestor() {
return ancestor;
}
// The following methods are provided for better performance.
@Override
public boolean addAll(Collection extends V> collection) {
if (collection.isEmpty()) {
return false;
}
int oldSize = size(); // calls refreshIfEmpty
boolean changed = delegate.addAll(collection);
if (changed) {
int newSize = delegate.size();
totalSize += (newSize - oldSize);
if (oldSize == 0) {
addToMap();
}
}
return changed;
}
@Override
public boolean contains(@CheckForNull Object o) {
refreshIfEmpty();
return delegate.contains(o);
}
@Override
public boolean containsAll(Collection> c) {
refreshIfEmpty();
return delegate.containsAll(c);
}
@Override
public void clear() {
int oldSize = size(); // calls refreshIfEmpty
if (oldSize == 0) {
return;
}
delegate.clear();
totalSize -= oldSize;
removeIfEmpty(); // maybe shouldn't be removed if this is a sublist
}
@Override
public boolean remove(@CheckForNull Object o) {
refreshIfEmpty();
boolean changed = delegate.remove(o);
if (changed) {
totalSize--;
removeIfEmpty();
}
return changed;
}
@Override
public boolean removeAll(Collection> c) {
if (c.isEmpty()) {
return false;
}
int oldSize = size(); // calls refreshIfEmpty
boolean changed = delegate.removeAll(c);
if (changed) {
int newSize = delegate.size();
totalSize += (newSize - oldSize);
removeIfEmpty();
}
return changed;
}
@Override
public boolean retainAll(Collection> c) {
checkNotNull(c);
int oldSize = size(); // calls refreshIfEmpty
boolean changed = delegate.retainAll(c);
if (changed) {
int newSize = delegate.size();
totalSize += (newSize - oldSize);
removeIfEmpty();
}
return changed;
}
}
private static Iterator iteratorOrListIterator(
Collection collection) {
return (collection instanceof List)
? ((List) collection).listIterator()
: collection.iterator();
}
/** Set decorator that stays in sync with the multimap values for a key. */
@WeakOuter
class WrappedSet extends WrappedCollection implements Set {
WrappedSet(@ParametricNullness K key, Set delegate) {
super(key, delegate, null);
}
@Override
public boolean removeAll(Collection> c) {
if (c.isEmpty()) {
return false;
}
int oldSize = size(); // calls refreshIfEmpty
// Guava issue 1013: AbstractSet and most JDK set implementations are
// susceptible to quadratic removeAll performance on lists;
// use a slightly smarter implementation here
boolean changed = Sets.removeAllImpl((Set) delegate, c);
if (changed) {
int newSize = delegate.size();
totalSize += (newSize - oldSize);
removeIfEmpty();
}
return changed;
}
}
/** SortedSet decorator that stays in sync with the multimap values for a key. */
@WeakOuter
class WrappedSortedSet extends WrappedCollection implements SortedSet {
WrappedSortedSet(
@ParametricNullness K key,
SortedSet delegate,
@CheckForNull WrappedCollection ancestor) {
super(key, delegate, ancestor);
}
SortedSet getSortedSetDelegate() {
return (SortedSet) getDelegate();
}
@Override
@CheckForNull
public Comparator super V> comparator() {
return getSortedSetDelegate().comparator();
}
@Override
@ParametricNullness
public V first() {
refreshIfEmpty();
return getSortedSetDelegate().first();
}
@Override
@ParametricNullness
public V last() {
refreshIfEmpty();
return getSortedSetDelegate().last();
}
@Override
public SortedSet headSet(@ParametricNullness V toElement) {
refreshIfEmpty();
return new WrappedSortedSet(
getKey(),
getSortedSetDelegate().headSet(toElement),
(getAncestor() == null) ? this : getAncestor());
}
@Override
public SortedSet subSet(@ParametricNullness V fromElement, @ParametricNullness V toElement) {
refreshIfEmpty();
return new WrappedSortedSet(
getKey(),
getSortedSetDelegate().subSet(fromElement, toElement),
(getAncestor() == null) ? this : getAncestor());
}
@Override
public SortedSet tailSet(@ParametricNullness V fromElement) {
refreshIfEmpty();
return new WrappedSortedSet(
getKey(),
getSortedSetDelegate().tailSet(fromElement),
(getAncestor() == null) ? this : getAncestor());
}
}
@WeakOuter
class WrappedNavigableSet extends WrappedSortedSet implements NavigableSet {
WrappedNavigableSet(
@ParametricNullness K key,
NavigableSet delegate,
@CheckForNull WrappedCollection ancestor) {
super(key, delegate, ancestor);
}
@Override
NavigableSet getSortedSetDelegate() {
return (NavigableSet) super.getSortedSetDelegate();
}
@Override
@CheckForNull
public V lower(@ParametricNullness V v) {
return getSortedSetDelegate().lower(v);
}
@Override
@CheckForNull
public V floor(@ParametricNullness V v) {
return getSortedSetDelegate().floor(v);
}
@Override
@CheckForNull
public V ceiling(@ParametricNullness V v) {
return getSortedSetDelegate().ceiling(v);
}
@Override
@CheckForNull
public V higher(@ParametricNullness V v) {
return getSortedSetDelegate().higher(v);
}
@Override
@CheckForNull
public V pollFirst() {
return Iterators.pollNext(iterator());
}
@Override
@CheckForNull
public V pollLast() {
return Iterators.pollNext(descendingIterator());
}
private NavigableSet wrap(NavigableSet wrapped) {
return new WrappedNavigableSet(key, wrapped, (getAncestor() == null) ? this : getAncestor());
}
@Override
public NavigableSet descendingSet() {
return wrap(getSortedSetDelegate().descendingSet());
}
@Override
public Iterator descendingIterator() {
return new WrappedIterator(getSortedSetDelegate().descendingIterator());
}
@Override
public NavigableSet subSet(
@ParametricNullness V fromElement,
boolean fromInclusive,
@ParametricNullness V toElement,
boolean toInclusive) {
return wrap(
getSortedSetDelegate().subSet(fromElement, fromInclusive, toElement, toInclusive));
}
@Override
public NavigableSet headSet(@ParametricNullness V toElement, boolean inclusive) {
return wrap(getSortedSetDelegate().headSet(toElement, inclusive));
}
@Override
public NavigableSet tailSet(@ParametricNullness V fromElement, boolean inclusive) {
return wrap(getSortedSetDelegate().tailSet(fromElement, inclusive));
}
}
/** List decorator that stays in sync with the multimap values for a key. */
@WeakOuter
class WrappedList extends WrappedCollection implements List {
WrappedList(
@ParametricNullness K key, List delegate, @CheckForNull WrappedCollection ancestor) {
super(key, delegate, ancestor);
}
List getListDelegate() {
return (List) getDelegate();
}
@Override
public boolean addAll(int index, Collection extends V> c) {
if (c.isEmpty()) {
return false;
}
int oldSize = size(); // calls refreshIfEmpty
boolean changed = getListDelegate().addAll(index, c);
if (changed) {
int newSize = getDelegate().size();
totalSize += (newSize - oldSize);
if (oldSize == 0) {
addToMap();
}
}
return changed;
}
@Override
@ParametricNullness
public V get(int index) {
refreshIfEmpty();
return getListDelegate().get(index);
}
@Override
@ParametricNullness
public V set(int index, @ParametricNullness V element) {
refreshIfEmpty();
return getListDelegate().set(index, element);
}
@Override
public void add(int index, @ParametricNullness V element) {
refreshIfEmpty();
boolean wasEmpty = getDelegate().isEmpty();
getListDelegate().add(index, element);
totalSize++;
if (wasEmpty) {
addToMap();
}
}
@Override
@ParametricNullness
public V remove(int index) {
refreshIfEmpty();
V value = getListDelegate().remove(index);
totalSize--;
removeIfEmpty();
return value;
}
@Override
public int indexOf(@CheckForNull Object o) {
refreshIfEmpty();
return getListDelegate().indexOf(o);
}
@Override
public int lastIndexOf(@CheckForNull Object o) {
refreshIfEmpty();
return getListDelegate().lastIndexOf(o);
}
@Override
public ListIterator listIterator() {
refreshIfEmpty();
return new WrappedListIterator();
}
@Override
public ListIterator listIterator(int index) {
refreshIfEmpty();
return new WrappedListIterator(index);
}
@Override
public List subList(int fromIndex, int toIndex) {
refreshIfEmpty();
return wrapList(
getKey(),
getListDelegate().subList(fromIndex, toIndex),
(getAncestor() == null) ? this : getAncestor());
}
/** ListIterator decorator. */
private class WrappedListIterator extends WrappedIterator implements ListIterator {
WrappedListIterator() {}
public WrappedListIterator(int index) {
super(getListDelegate().listIterator(index));
}
private ListIterator getDelegateListIterator() {
return (ListIterator) getDelegateIterator();
}
@Override
public boolean hasPrevious() {
return getDelegateListIterator().hasPrevious();
}
@Override
@ParametricNullness
public V previous() {
return getDelegateListIterator().previous();
}
@Override
public int nextIndex() {
return getDelegateListIterator().nextIndex();
}
@Override
public int previousIndex() {
return getDelegateListIterator().previousIndex();
}
@Override
public void set(@ParametricNullness V value) {
getDelegateListIterator().set(value);
}
@Override
public void add(@ParametricNullness V value) {
boolean wasEmpty = isEmpty();
getDelegateListIterator().add(value);
totalSize++;
if (wasEmpty) {
addToMap();
}
}
}
}
/**
* List decorator that stays in sync with the multimap values for a key and supports rapid random
* access.
*/
private class RandomAccessWrappedList extends WrappedList implements RandomAccess {
RandomAccessWrappedList(
@ParametricNullness K key, List delegate, @CheckForNull WrappedCollection ancestor) {
super(key, delegate, ancestor);
}
}
@Override
Set createKeySet() {
return new KeySet(map);
}
final Set createMaybeNavigableKeySet() {
if (map instanceof NavigableMap) {
return new NavigableKeySet((NavigableMap>) map);
} else if (map instanceof SortedMap) {
return new SortedKeySet((SortedMap>) map);
} else {
return new KeySet(map);
}
}
@WeakOuter
private class KeySet extends Maps.KeySet> {
KeySet(final Map> subMap) {
super(subMap);
}
@Override
public Iterator iterator() {
final Iterator>> entryIterator = map().entrySet().iterator();
return new Iterator() {
@CheckForNull Entry> entry;
@Override
public boolean hasNext() {
return entryIterator.hasNext();
}
@Override
@ParametricNullness
public K next() {
entry = entryIterator.next();
return entry.getKey();
}
@Override
public void remove() {
checkState(entry != null, "no calls to next() since the last call to remove()");
Collection collection = entry.getValue();
entryIterator.remove();
totalSize -= collection.size();
collection.clear();
entry = null;
}
};
}
// The following methods are included for better performance.
@Override
public Spliterator spliterator() {
return map().keySet().spliterator();
}
@Override
public boolean remove(@CheckForNull Object key) {
int count = 0;
Collection collection = map().remove(key);
if (collection != null) {
count = collection.size();
collection.clear();
totalSize -= count;
}
return count > 0;
}
@Override
public void clear() {
Iterators.clear(iterator());
}
@Override
public boolean containsAll(Collection> c) {
return map().keySet().containsAll(c);
}
@Override
public boolean equals(@CheckForNull Object object) {
return this == object || this.map().keySet().equals(object);
}
@Override
public int hashCode() {
return map().keySet().hashCode();
}
}
@WeakOuter
private class SortedKeySet extends KeySet implements SortedSet {
SortedKeySet(SortedMap> subMap) {
super(subMap);
}
SortedMap> sortedMap() {
return (SortedMap>) super.map();
}
@Override
@CheckForNull
public Comparator super K> comparator() {
return sortedMap().comparator();
}
@Override
@ParametricNullness
public K first() {
return sortedMap().firstKey();
}
@Override
public SortedSet headSet(@ParametricNullness K toElement) {
return new SortedKeySet(sortedMap().headMap(toElement));
}
@Override
@ParametricNullness
public K last() {
return sortedMap().lastKey();
}
@Override
public SortedSet subSet(@ParametricNullness K fromElement, @ParametricNullness K toElement) {
return new SortedKeySet(sortedMap().subMap(fromElement, toElement));
}
@Override
public SortedSet tailSet(@ParametricNullness K fromElement) {
return new SortedKeySet(sortedMap().tailMap(fromElement));
}
}
@WeakOuter
private final class NavigableKeySet extends SortedKeySet implements NavigableSet {
NavigableKeySet(NavigableMap> subMap) {
super(subMap);
}
@Override
NavigableMap> sortedMap() {
return (NavigableMap>) super.sortedMap();
}
@Override
@CheckForNull
public K lower(@ParametricNullness K k) {
return sortedMap().lowerKey(k);
}
@Override
@CheckForNull
public K floor(@ParametricNullness K k) {
return sortedMap().floorKey(k);
}
@Override
@CheckForNull
public K ceiling(@ParametricNullness K k) {
return sortedMap().ceilingKey(k);
}
@Override
@CheckForNull
public K higher(@ParametricNullness K k) {
return sortedMap().higherKey(k);
}
@Override
@CheckForNull
public K pollFirst() {
return Iterators.pollNext(iterator());
}
@Override
@CheckForNull
public K pollLast() {
return Iterators.pollNext(descendingIterator());
}
@Override
public NavigableSet descendingSet() {
return new NavigableKeySet(sortedMap().descendingMap());
}
@Override
public Iterator descendingIterator() {
return descendingSet().iterator();
}
@Override
public NavigableSet headSet(@ParametricNullness K toElement) {
return headSet(toElement, false);
}
@Override
public NavigableSet headSet(@ParametricNullness K toElement, boolean inclusive) {
return new NavigableKeySet(sortedMap().headMap(toElement, inclusive));
}
@Override
public NavigableSet subSet(
@ParametricNullness K fromElement, @ParametricNullness K toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public NavigableSet subSet(
@ParametricNullness K fromElement,
boolean fromInclusive,
@ParametricNullness K toElement,
boolean toInclusive) {
return new NavigableKeySet(
sortedMap().subMap(fromElement, fromInclusive, toElement, toInclusive));
}
@Override
public NavigableSet tailSet(@ParametricNullness K fromElement) {
return tailSet(fromElement, true);
}
@Override
public NavigableSet tailSet(@ParametricNullness K fromElement, boolean inclusive) {
return new NavigableKeySet(sortedMap().tailMap(fromElement, inclusive));
}
}
/** Removes all values for the provided key. */
private void removeValuesForKey(@CheckForNull Object key) {
Collection collection = Maps.safeRemove(map, key);
if (collection != null) {
int count = collection.size();
collection.clear();
totalSize -= count;
}
}
private abstract class Itr implements Iterator {
final Iterator>> keyIterator;
@CheckForNull K key;
@CheckForNull Collection collection;
Iterator valueIterator;
Itr() {
keyIterator = map.entrySet().iterator();
key = null;
collection = null;
valueIterator = Iterators.emptyModifiableIterator();
}
abstract T output(@ParametricNullness K key, @ParametricNullness V value);
@Override
public boolean hasNext() {
return keyIterator.hasNext() || valueIterator.hasNext();
}
@Override
@ParametricNullness
public T next() {
if (!valueIterator.hasNext()) {
Entry> mapEntry = keyIterator.next();
key = mapEntry.getKey();
collection = mapEntry.getValue();
valueIterator = collection.iterator();
}
/*
* uncheckedCastNullableTToT is safe: The first call to this method always enters the !hasNext() case and
* populates key, after which it's never cleared.
*/
return output(uncheckedCastNullableTToT(key), valueIterator.next());
}
@Override
public void remove() {
valueIterator.remove();
/*
* requireNonNull is safe because we've already initialized `collection`. If we hadn't, then
* valueIterator.remove() would have failed.
*/
if (requireNonNull(collection).isEmpty()) {
keyIterator.remove();
}
totalSize--;
}
}
/**
* {@inheritDoc}
*
* The iterator generated by the returned collection traverses the values for one key, followed
* by the values of a second key, and so on.
*/
@Override
public Collection values() {
return super.values();
}
@Override
Collection createValues() {
return new Values();
}
@Override
Iterator valueIterator() {
return new Itr() {
@Override
@ParametricNullness
V output(@ParametricNullness K key, @ParametricNullness V value) {
return value;
}
};
}
@Override
Spliterator valueSpliterator() {
return CollectSpliterators.flatMap(
map.values().spliterator(), Collection::spliterator, Spliterator.SIZED, size());
}
/*
* TODO(kevinb): should we copy this javadoc to each concrete class, so that
* classes like LinkedHashMultimap that need to say something different are
* still able to {@inheritDoc} all the way from Multimap?
*/
@Override
Multiset createKeys() {
return new Multimaps.Keys(this);
}
/**
* {@inheritDoc}
*
* The iterator generated by the returned collection traverses the values for one key, followed
* by the values of a second key, and so on.
*
*
Each entry is an immutable snapshot of a key-value mapping in the multimap, taken at the
* time the entry is returned by a method call to the collection or its iterator.
*/
@Override
public Collection> entries() {
return super.entries();
}
@Override
Collection> createEntries() {
if (this instanceof SetMultimap) {
return new EntrySet();
} else {
return new Entries();
}
}
/**
* Returns an iterator across all key-value map entries, used by {@code entries().iterator()} and
* {@code values().iterator()}. The default behavior, which traverses the values for one key, the
* values for a second key, and so on, suffices for most {@code AbstractMapBasedMultimap}
* implementations.
*
* @return an iterator across map entries
*/
@Override
Iterator> entryIterator() {
return new Itr>() {
@Override
Entry output(@ParametricNullness K key, @ParametricNullness V value) {
return Maps.immutableEntry(key, value);
}
};
}
@Override
Spliterator> entrySpliterator() {
return CollectSpliterators.flatMap(
map.entrySet().spliterator(),
keyToValueCollectionEntry -> {
K key = keyToValueCollectionEntry.getKey();
Collection valueCollection = keyToValueCollectionEntry.getValue();
return CollectSpliterators.map(
valueCollection.spliterator(), (V value) -> Maps.immutableEntry(key, value));
},
Spliterator.SIZED,
size());
}
@Override
public void forEach(BiConsumer super K, ? super V> action) {
checkNotNull(action);
map.forEach(
(key, valueCollection) -> valueCollection.forEach(value -> action.accept(key, value)));
}
@Override
Map> createAsMap() {
return new AsMap(map);
}
final Map> createMaybeNavigableAsMap() {
if (map instanceof NavigableMap) {
return new NavigableAsMap((NavigableMap>) map);
} else if (map instanceof SortedMap) {
return new SortedAsMap((SortedMap>) map);
} else {
return new AsMap(map);
}
}
@WeakOuter
private class AsMap extends ViewCachingAbstractMap> {
/**
* Usually the same as map, but smaller for the headMap(), tailMap(), or subMap() of a
* SortedAsMap.
*/
final transient Map> submap;
AsMap(Map> submap) {
this.submap = submap;
}
@Override
protected Set>> createEntrySet() {
return new AsMapEntries();
}
// The following methods are included for performance.
@Override
public boolean containsKey(@CheckForNull Object key) {
return Maps.safeContainsKey(submap, key);
}
@Override
@CheckForNull
public Collection get(@CheckForNull Object key) {
Collection collection = Maps.safeGet(submap, key);
if (collection == null) {
return null;
}
@SuppressWarnings("unchecked")
K k = (K) key;
return wrapCollection(k, collection);
}
@Override
public Set keySet() {
return AbstractMapBasedMultimap.this.keySet();
}
@Override
public int size() {
return submap.size();
}
@Override
@CheckForNull
public Collection remove(@CheckForNull Object key) {
Collection collection = submap.remove(key);
if (collection == null) {
return null;
}
Collection output = createCollection();
output.addAll(collection);
totalSize -= collection.size();
collection.clear();
return output;
}
@Override
public boolean equals(@CheckForNull Object object) {
return this == object || submap.equals(object);
}
@Override
public int hashCode() {
return submap.hashCode();
}
@Override
public String toString() {
return submap.toString();
}
@Override
public void clear() {
if (submap == map) {
AbstractMapBasedMultimap.this.clear();
} else {
Iterators.clear(new AsMapIterator());
}
}
Entry> wrapEntry(Entry> entry) {
K key = entry.getKey();
return Maps.immutableEntry(key, wrapCollection(key, entry.getValue()));
}
@WeakOuter
class AsMapEntries extends Maps.EntrySet> {
@Override
Map> map() {
return AsMap.this;
}
@Override
public Iterator>> iterator() {
return new AsMapIterator();
}
@Override
public Spliterator>> spliterator() {
return CollectSpliterators.map(submap.entrySet().spliterator(), AsMap.this::wrapEntry);
}
// The following methods are included for performance.
@Override
public boolean contains(@CheckForNull Object o) {
return Collections2.safeContains(submap.entrySet(), o);
}
@Override
public boolean remove(@CheckForNull Object o) {
if (!contains(o)) {
return false;
}
// requireNonNull is safe because of the contains check.
Entry, ?> entry = requireNonNull((Entry, ?>) o);
removeValuesForKey(entry.getKey());
return true;
}
}
/** Iterator across all keys and value collections. */
class AsMapIterator implements Iterator>> {
final Iterator>> delegateIterator = submap.entrySet().iterator();
@CheckForNull Collection collection;
@Override
public boolean hasNext() {
return delegateIterator.hasNext();
}
@Override
public Entry> next() {
Entry> entry = delegateIterator.next();
collection = entry.getValue();
return wrapEntry(entry);
}
@Override
public void remove() {
checkState(collection != null, "no calls to next() since the last call to remove()");
delegateIterator.remove();
totalSize -= collection.size();
collection.clear();
collection = null;
}
}
}
@WeakOuter
private class SortedAsMap extends AsMap implements SortedMap> {
SortedAsMap(SortedMap> submap) {
super(submap);
}
SortedMap> sortedMap() {
return (SortedMap>) submap;
}
@Override
@CheckForNull
public Comparator super K> comparator() {
return sortedMap().comparator();
}
@Override
@ParametricNullness
public K firstKey() {
return sortedMap().firstKey();
}
@Override
@ParametricNullness
public K lastKey() {
return sortedMap().lastKey();
}
@Override
public SortedMap> headMap(@ParametricNullness K toKey) {
return new SortedAsMap(sortedMap().headMap(toKey));
}
@Override
public SortedMap> subMap(
@ParametricNullness K fromKey, @ParametricNullness K toKey) {
return new SortedAsMap(sortedMap().subMap(fromKey, toKey));
}
@Override
public SortedMap> tailMap(@ParametricNullness K fromKey) {
return new SortedAsMap(sortedMap().tailMap(fromKey));
}
@CheckForNull SortedSet sortedKeySet;
// returns a SortedSet, even though returning a Set would be sufficient to
// satisfy the SortedMap.keySet() interface
@Override
public SortedSet keySet() {
SortedSet result = sortedKeySet;
return (result == null) ? sortedKeySet = createKeySet() : result;
}
@Override
SortedSet createKeySet() {
return new SortedKeySet(sortedMap());
}
}
private final class NavigableAsMap extends SortedAsMap implements NavigableMap> {
NavigableAsMap(NavigableMap> submap) {
super(submap);
}
@Override
NavigableMap> sortedMap() {
return (NavigableMap>) super.sortedMap();
}
@Override
@CheckForNull
public Entry> lowerEntry(@ParametricNullness K key) {
Entry> entry = sortedMap().lowerEntry(key);
return (entry == null) ? null : wrapEntry(entry);
}
@Override
@CheckForNull
public K lowerKey(@ParametricNullness K key) {
return sortedMap().lowerKey(key);
}
@Override
@CheckForNull
public Entry> floorEntry(@ParametricNullness K key) {
Entry> entry = sortedMap().floorEntry(key);
return (entry == null) ? null : wrapEntry(entry);
}
@Override
@CheckForNull
public K floorKey(@ParametricNullness K key) {
return sortedMap().floorKey(key);
}
@Override
@CheckForNull
public Entry> ceilingEntry(@ParametricNullness K key) {
Entry> entry = sortedMap().ceilingEntry(key);
return (entry == null) ? null : wrapEntry(entry);
}
@Override
@CheckForNull
public K ceilingKey(@ParametricNullness K key) {
return sortedMap().ceilingKey(key);
}
@Override
@CheckForNull
public Entry> higherEntry(@ParametricNullness K key) {
Entry> entry = sortedMap().higherEntry(key);
return (entry == null) ? null : wrapEntry(entry);
}
@Override
@CheckForNull
public K higherKey(@ParametricNullness K key) {
return sortedMap().higherKey(key);
}
@Override
@CheckForNull
public Entry> firstEntry() {
Entry> entry = sortedMap().firstEntry();
return (entry == null) ? null : wrapEntry(entry);
}
@Override
@CheckForNull
public Entry> lastEntry() {
Entry> entry = sortedMap().lastEntry();
return (entry == null) ? null : wrapEntry(entry);
}
@Override
@CheckForNull
public Entry> pollFirstEntry() {
return pollAsMapEntry(entrySet().iterator());
}
@Override
@CheckForNull
public Entry> pollLastEntry() {
return pollAsMapEntry(descendingMap().entrySet().iterator());
}
@CheckForNull
Entry> pollAsMapEntry(Iterator>> entryIterator) {
if (!entryIterator.hasNext()) {
return null;
}
Entry> entry = entryIterator.next();
Collection output = createCollection();
output.addAll(entry.getValue());
entryIterator.remove();
return Maps.immutableEntry(entry.getKey(), unmodifiableCollectionSubclass(output));
}
@Override
public NavigableMap> descendingMap() {
return new NavigableAsMap(sortedMap().descendingMap());
}
@Override
public NavigableSet keySet() {
return (NavigableSet) super.keySet();
}
@Override
NavigableSet createKeySet() {
return new NavigableKeySet(sortedMap());
}
@Override
public NavigableSet navigableKeySet() {
return keySet();
}
@Override
public NavigableSet descendingKeySet() {
return descendingMap().navigableKeySet();
}
@Override
public NavigableMap> subMap(
@ParametricNullness K fromKey, @ParametricNullness K toKey) {
return subMap(fromKey, true, toKey, false);
}
@Override
public NavigableMap> subMap(
@ParametricNullness K fromKey,
boolean fromInclusive,
@ParametricNullness K toKey,
boolean toInclusive) {
return new NavigableAsMap(sortedMap().subMap(fromKey, fromInclusive, toKey, toInclusive));
}
@Override
public NavigableMap> headMap(@ParametricNullness K toKey) {
return headMap(toKey, false);
}
@Override
public NavigableMap> headMap(@ParametricNullness K toKey, boolean inclusive) {
return new NavigableAsMap(sortedMap().headMap(toKey, inclusive));
}
@Override
public NavigableMap> tailMap(@ParametricNullness K fromKey) {
return tailMap(fromKey, true);
}
@Override
public NavigableMap> tailMap(
@ParametricNullness K fromKey, boolean inclusive) {
return new NavigableAsMap(sortedMap().tailMap(fromKey, inclusive));
}
}
private static final long serialVersionUID = 2447537837011683357L;
}