All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.tanyaofei.guava.common.collect.Multimaps Maven / Gradle / Ivy

The newest version!
/*
 * 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 io.github.tanyaofei.guava.common.collect;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.concurrent.LazyInit;
import com.google.j2objc.annotations.Weak;
import com.google.j2objc.annotations.WeakOuter;
import io.github.tanyaofei.guava.common.annotations.Beta;
import io.github.tanyaofei.guava.common.annotations.GwtCompatible;
import io.github.tanyaofei.guava.common.annotations.GwtIncompatible;
import io.github.tanyaofei.guava.common.base.Function;
import io.github.tanyaofei.guava.common.base.Predicate;
import io.github.tanyaofei.guava.common.base.Predicates;
import io.github.tanyaofei.guava.common.base.Supplier;
import io.github.tanyaofei.guava.common.collect.Maps.EntryTransformer;
import org.checkerframework.checker.nullness.qual.Nullable;

import javax.annotation.CheckForNull;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collector;
import java.util.stream.Stream;

import static io.github.tanyaofei.guava.common.base.Preconditions.checkNotNull;
import static io.github.tanyaofei.guava.common.collect.CollectPreconditions.checkNonnegative;
import static io.github.tanyaofei.guava.common.collect.CollectPreconditions.checkRemove;
import static io.github.tanyaofei.guava.common.collect.NullnessCasts.uncheckedCastNullableTToT;
import static java.util.Objects.requireNonNull;

/**
 * Provides static methods acting on or generating a {@code Multimap}.
 *
 * 

See the Guava User Guide article on {@code * Multimaps}. * * @author Jared Levy * @author Robert Konigsberg * @author Mike Bostock * @author Louis Wasserman * @since 2.0 */ @GwtCompatible(emulated = true) @ElementTypesAreNonnullByDefault public final class Multimaps { private Multimaps() {} /** * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the * specified supplier. The keys and values of the entries are the result of applying the provided * mapping functions to the input elements, accumulated in the encounter order of the stream. * *

Example: * *

{@code
   * static final ListMultimap FIRST_LETTER_MULTIMAP =
   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
   *         .collect(
   *             toMultimap(
   *                  str -> str.charAt(0),
   *                  str -> str.substring(1),
   *                  MultimapBuilder.treeKeys().arrayListValues()::build));
   *
   * // is equivalent to
   *
   * static final ListMultimap FIRST_LETTER_MULTIMAP;
   *
   * static {
   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.treeKeys().arrayListValues().build();
   *     FIRST_LETTER_MULTIMAP.put('b', "anana");
   *     FIRST_LETTER_MULTIMAP.put('a', "pple");
   *     FIRST_LETTER_MULTIMAP.put('a', "sparagus");
   *     FIRST_LETTER_MULTIMAP.put('c', "arrot");
   *     FIRST_LETTER_MULTIMAP.put('c', "herry");
   * }
   * }
* *

To collect to an {@link ImmutableMultimap}, use either {@link * ImmutableSetMultimap#toImmutableSetMultimap} or {@link * ImmutableListMultimap#toImmutableListMultimap}. * * @since 21.0 */ public static < T extends @Nullable Object, K extends @Nullable Object, V extends @Nullable Object, M extends io.github.tanyaofei.guava.common.collect.Multimap> Collector toMultimap( java.util.function.Function keyFunction, java.util.function.Function valueFunction, java.util.function.Supplier multimapSupplier) { return CollectCollectors.toMultimap(keyFunction, valueFunction, multimapSupplier); } /** * Returns a {@code Collector} accumulating entries into a {@code Multimap} generated from the * specified supplier. Each input element is mapped to a key and a stream of values, each of which * are put into the resulting {@code Multimap}, in the encounter order of the stream and the * encounter order of the streams of values. * *

Example: * *

{@code
   * static final ListMultimap FIRST_LETTER_MULTIMAP =
   *     Stream.of("banana", "apple", "carrot", "asparagus", "cherry")
   *         .collect(
   *             flatteningToMultimap(
   *                  str -> str.charAt(0),
   *                  str -> str.substring(1).chars().mapToObj(c -> (char) c),
   *                  MultimapBuilder.linkedHashKeys().arrayListValues()::build));
   *
   * // is equivalent to
   *
   * static final ListMultimap FIRST_LETTER_MULTIMAP;
   *
   * static {
   *     FIRST_LETTER_MULTIMAP = MultimapBuilder.linkedHashKeys().arrayListValues().build();
   *     FIRST_LETTER_MULTIMAP.putAll('b', Arrays.asList('a', 'n', 'a', 'n', 'a'));
   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('p', 'p', 'l', 'e'));
   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('a', 'r', 'r', 'o', 't'));
   *     FIRST_LETTER_MULTIMAP.putAll('a', Arrays.asList('s', 'p', 'a', 'r', 'a', 'g', 'u', 's'));
   *     FIRST_LETTER_MULTIMAP.putAll('c', Arrays.asList('h', 'e', 'r', 'r', 'y'));
   * }
   * }
* * @since 21.0 */ @Beta public static < T extends @Nullable Object, K extends @Nullable Object, V extends @Nullable Object, M extends io.github.tanyaofei.guava.common.collect.Multimap> Collector flatteningToMultimap( java.util.function.Function keyFunction, java.util.function.Function> valueFunction, java.util.function.Supplier multimapSupplier) { return CollectCollectors.flatteningToMultimap(keyFunction, valueFunction, multimapSupplier); } /** * Creates a new {@code Multimap} backed by {@code map}, whose internal value collections are * generated by {@code factory}. * *

Warning: do not use this method when the collections returned by {@code factory} * implement either {@link List} or {@code Set}! Use the more specific method {@link * #newListMultimap}, {@link #newSetMultimap} or {@link #newSortedSetMultimap} instead, to avoid * very surprising behavior from {@link io.github.tanyaofei.guava.common.collect.Multimap#equals}. * *

The {@code factory}-generated and {@code map} classes determine the multimap iteration * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code * toString} methods for the multimap and its returned views. However, the multimap's {@code get} * method returns instances of a different class than {@code factory.get()} does. * *

The multimap is serializable if {@code map}, {@code factory}, the collections generated by * {@code factory}, and the multimap contents are all serializable. * *

The multimap is not threadsafe when any concurrent operations update the multimap, even if * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link * #synchronizedMultimap}. * *

Call this method only when the simpler methods {@link ArrayListMultimap#create()}, {@link * HashMultimap#create()}, {@link LinkedHashMultimap#create()}, {@link * LinkedListMultimap#create()}, {@link TreeMultimap#create()}, and {@link * TreeMultimap#create(Comparator, Comparator)} won't suffice. * *

Note: the multimap assumes complete ownership over of {@code map} and the collections * returned by {@code factory}. Those objects should not be manually updated and they should not * use soft, weak, or phantom references. * * @param map place to store the mapping from each key to its corresponding values * @param factory supplier of new, empty collections that will each hold all values for a given * key * @throws IllegalArgumentException if {@code map} is not empty */ public static io.github.tanyaofei.guava.common.collect.Multimap newMultimap( Map> map, final Supplier> factory) { return new CustomMultimap<>(map, factory); } private static class CustomMultimap extends AbstractMapBasedMultimap { transient Supplier> factory; CustomMultimap(Map> map, Supplier> factory) { super(map); this.factory = checkNotNull(factory); } @Override Set createKeySet() { return createMaybeNavigableKeySet(); } @Override Map> createAsMap() { return createMaybeNavigableAsMap(); } @Override protected Collection createCollection() { return factory.get(); } @Override Collection unmodifiableCollectionSubclass( Collection collection) { if (collection instanceof NavigableSet) { return io.github.tanyaofei.guava.common.collect.Sets.unmodifiableNavigableSet((NavigableSet) collection); } else if (collection instanceof SortedSet) { return Collections.unmodifiableSortedSet((SortedSet) collection); } else if (collection instanceof Set) { return Collections.unmodifiableSet((Set) collection); } else if (collection instanceof List) { return Collections.unmodifiableList((List) collection); } else { return Collections.unmodifiableCollection(collection); } } @Override Collection wrapCollection(@ParametricNullness K key, Collection collection) { if (collection instanceof List) { return wrapList(key, (List) collection, null); } else if (collection instanceof NavigableSet) { return new WrappedNavigableSet(key, (NavigableSet) collection, null); } else if (collection instanceof SortedSet) { return new WrappedSortedSet(key, (SortedSet) collection, null); } else if (collection instanceof Set) { return new WrappedSet(key, (Set) collection); } else { return new WrappedCollection(key, collection, null); } } // can't use Serialization writeMultimap and populateMultimap methods since // there's no way to generate the empty backing map. /** @serialData the factory and the backing map */ @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @GwtIncompatible // java.io.ObjectInputStream @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); factory = (Supplier>) stream.readObject(); Map> map = (Map>) stream.readObject(); setMap(map); } @GwtIncompatible // java serialization not supported private static final long serialVersionUID = 0; } /** * Creates a new {@code ListMultimap} that uses the provided map and factory. It can generate a * multimap based on arbitrary {@link Map} and {@link List} classes. * *

The {@code factory}-generated and {@code map} classes determine the multimap iteration * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code * toString} methods for the multimap and its returned views. The multimap's {@code get}, {@code * removeAll}, and {@code replaceValues} methods return {@code RandomAccess} lists if the factory * does. However, the multimap's {@code get} method returns instances of a different class than * does {@code factory.get()}. * *

The multimap is serializable if {@code map}, {@code factory}, the lists generated by {@code * factory}, and the multimap contents are all serializable. * *

The multimap is not threadsafe when any concurrent operations update the multimap, even if * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link * #synchronizedListMultimap}. * *

Call this method only when the simpler methods {@link ArrayListMultimap#create()} and {@link * LinkedListMultimap#create()} won't suffice. * *

Note: the multimap assumes complete ownership over of {@code map} and the lists returned by * {@code factory}. Those objects should not be manually updated, they should be empty when * provided, and they should not use soft, weak, or phantom references. * * @param map place to store the mapping from each key to its corresponding values * @param factory supplier of new, empty lists that will each hold all values for a given key * @throws IllegalArgumentException if {@code map} is not empty */ public static ListMultimap newListMultimap( Map> map, final Supplier> factory) { return new CustomListMultimap<>(map, factory); } private static class CustomListMultimap extends AbstractListMultimap { transient Supplier> factory; CustomListMultimap(Map> map, Supplier> factory) { super(map); this.factory = checkNotNull(factory); } @Override Set createKeySet() { return createMaybeNavigableKeySet(); } @Override Map> createAsMap() { return createMaybeNavigableAsMap(); } @Override protected List createCollection() { return factory.get(); } /** @serialData the factory and the backing map */ @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @GwtIncompatible // java.io.ObjectInputStream @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); factory = (Supplier>) stream.readObject(); Map> map = (Map>) stream.readObject(); setMap(map); } @GwtIncompatible // java serialization not supported private static final long serialVersionUID = 0; } /** * Creates a new {@code SetMultimap} that uses the provided map and factory. It can generate a * multimap based on arbitrary {@link Map} and {@link Set} classes. * *

The {@code factory}-generated and {@code map} classes determine the multimap iteration * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code * toString} methods for the multimap and its returned views. However, the multimap's {@code get} * method returns instances of a different class than {@code factory.get()} does. * *

The multimap is serializable if {@code map}, {@code factory}, the sets generated by {@code * factory}, and the multimap contents are all serializable. * *

The multimap is not threadsafe when any concurrent operations update the multimap, even if * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link * #synchronizedSetMultimap}. * *

Call this method only when the simpler methods {@link HashMultimap#create()}, {@link * LinkedHashMultimap#create()}, {@link TreeMultimap#create()}, and {@link * TreeMultimap#create(Comparator, Comparator)} won't suffice. * *

Note: the multimap assumes complete ownership over of {@code map} and the sets returned by * {@code factory}. Those objects should not be manually updated and they should not use soft, * weak, or phantom references. * * @param map place to store the mapping from each key to its corresponding values * @param factory supplier of new, empty sets that will each hold all values for a given key * @throws IllegalArgumentException if {@code map} is not empty */ public static SetMultimap newSetMultimap( Map> map, final Supplier> factory) { return new CustomSetMultimap<>(map, factory); } private static class CustomSetMultimap extends AbstractSetMultimap { transient Supplier> factory; CustomSetMultimap(Map> map, Supplier> factory) { super(map); this.factory = checkNotNull(factory); } @Override Set createKeySet() { return createMaybeNavigableKeySet(); } @Override Map> createAsMap() { return createMaybeNavigableAsMap(); } @Override protected Set createCollection() { return factory.get(); } @Override Collection unmodifiableCollectionSubclass( Collection collection) { if (collection instanceof NavigableSet) { return io.github.tanyaofei.guava.common.collect.Sets.unmodifiableNavigableSet((NavigableSet) collection); } else if (collection instanceof SortedSet) { return Collections.unmodifiableSortedSet((SortedSet) collection); } else { return Collections.unmodifiableSet((Set) collection); } } @Override Collection wrapCollection(@ParametricNullness K key, Collection collection) { if (collection instanceof NavigableSet) { return new WrappedNavigableSet(key, (NavigableSet) collection, null); } else if (collection instanceof SortedSet) { return new WrappedSortedSet(key, (SortedSet) collection, null); } else { return new WrappedSet(key, (Set) collection); } } /** @serialData the factory and the backing map */ @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @GwtIncompatible // java.io.ObjectInputStream @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); factory = (Supplier>) stream.readObject(); Map> map = (Map>) stream.readObject(); setMap(map); } @GwtIncompatible // not needed in emulated source private static final long serialVersionUID = 0; } /** * Creates a new {@code SortedSetMultimap} that uses the provided map and factory. It can generate * a multimap based on arbitrary {@link Map} and {@link SortedSet} classes. * *

The {@code factory}-generated and {@code map} classes determine the multimap iteration * order. They also specify the behavior of the {@code equals}, {@code hashCode}, and {@code * toString} methods for the multimap and its returned views. However, the multimap's {@code get} * method returns instances of a different class than {@code factory.get()} does. * *

The multimap is serializable if {@code map}, {@code factory}, the sets generated by {@code * factory}, and the multimap contents are all serializable. * *

The multimap is not threadsafe when any concurrent operations update the multimap, even if * {@code map} and the instances generated by {@code factory} are. Concurrent read operations will * work correctly. To allow concurrent update operations, wrap the multimap with a call to {@link * #synchronizedSortedSetMultimap}. * *

Call this method only when the simpler methods {@link TreeMultimap#create()} and {@link * TreeMultimap#create(Comparator, Comparator)} won't suffice. * *

Note: the multimap assumes complete ownership over of {@code map} and the sets returned by * {@code factory}. Those objects should not be manually updated and they should not use soft, * weak, or phantom references. * * @param map place to store the mapping from each key to its corresponding values * @param factory supplier of new, empty sorted sets that will each hold all values for a given * key * @throws IllegalArgumentException if {@code map} is not empty */ public static SortedSetMultimap newSortedSetMultimap( Map> map, final Supplier> factory) { return new CustomSortedSetMultimap<>(map, factory); } private static class CustomSortedSetMultimap< K extends @Nullable Object, V extends @Nullable Object> extends AbstractSortedSetMultimap { transient Supplier> factory; @CheckForNull transient Comparator valueComparator; CustomSortedSetMultimap(Map> map, Supplier> factory) { super(map); this.factory = checkNotNull(factory); valueComparator = factory.get().comparator(); } @Override Set createKeySet() { return createMaybeNavigableKeySet(); } @Override Map> createAsMap() { return createMaybeNavigableAsMap(); } @Override protected SortedSet createCollection() { return factory.get(); } @Override @CheckForNull public Comparator valueComparator() { return valueComparator; } /** @serialData the factory and the backing map */ @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @GwtIncompatible // java.io.ObjectInputStream @SuppressWarnings("unchecked") // reading data stored by writeObject private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); factory = (Supplier>) stream.readObject(); valueComparator = factory.get().comparator(); Map> map = (Map>) stream.readObject(); setMap(map); } @GwtIncompatible // not needed in emulated source private static final long serialVersionUID = 0; } /** * Copies each key-value mapping in {@code source} into {@code dest}, with its key and value * reversed. * *

If {@code source} is an {@link ImmutableMultimap}, consider using {@link * ImmutableMultimap#inverse} instead. * * @param source any multimap * @param dest the multimap to copy into; usually empty * @return {@code dest} */ @CanIgnoreReturnValue public static > M invertFrom(io.github.tanyaofei.guava.common.collect.Multimap source, M dest) { checkNotNull(dest); for (Entry entry : source.entries()) { dest.put(entry.getValue(), entry.getKey()); } return dest; } /** * Returns a synchronized (thread-safe) multimap backed by the specified multimap. In order to * guarantee serial access, it is critical that all access to the backing multimap is * accomplished through the returned multimap. * *

It is imperative that the user manually synchronize on the returned multimap when accessing * any of its collection views: * *

{@code
   * Multimap multimap = Multimaps.synchronizedMultimap(
   *     HashMultimap.create());
   * ...
   * Collection values = multimap.get(key);  // Needn't be in synchronized block
   * ...
   * synchronized (multimap) {  // Synchronizing on multimap, not values!
   *   Iterator i = values.iterator(); // Must be in synchronized block
   *   while (i.hasNext()) {
   *     foo(i.next());
   *   }
   * }
   * }
* *

Failure to follow this advice may result in non-deterministic behavior. * *

Note that the generated multimap's {@link io.github.tanyaofei.guava.common.collect.Multimap#removeAll} and {@link * io.github.tanyaofei.guava.common.collect.Multimap#replaceValues} methods return collections that aren't synchronized. * *

The returned multimap will be serializable if the specified multimap is serializable. * * @param multimap the multimap to be wrapped in a synchronized view * @return a synchronized view of the specified multimap */ public static io.github.tanyaofei.guava.common.collect.Multimap synchronizedMultimap(io.github.tanyaofei.guava.common.collect.Multimap multimap) { return Synchronized.multimap(multimap, null); } /** * Returns an unmodifiable view of the specified multimap. Query operations on the returned * multimap "read through" to the specified multimap, and attempts to modify the returned * multimap, either directly or through the multimap's views, result in an {@code * UnsupportedOperationException}. * *

The returned multimap will be serializable if the specified multimap is serializable. * * @param delegate the multimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified multimap */ public static io.github.tanyaofei.guava.common.collect.Multimap unmodifiableMultimap(io.github.tanyaofei.guava.common.collect.Multimap delegate) { if (delegate instanceof UnmodifiableMultimap || delegate instanceof ImmutableMultimap) { return delegate; } return new UnmodifiableMultimap<>(delegate); } /** * Simply returns its argument. * * @deprecated no need to use this * @since 10.0 */ @Deprecated public static io.github.tanyaofei.guava.common.collect.Multimap unmodifiableMultimap(ImmutableMultimap delegate) { return checkNotNull(delegate); } private static class UnmodifiableMultimap extends ForwardingMultimap implements Serializable { final io.github.tanyaofei.guava.common.collect.Multimap delegate; @LazyInit @CheckForNull transient Collection> entries; @LazyInit @CheckForNull transient io.github.tanyaofei.guava.common.collect.Multiset keys; @LazyInit @CheckForNull transient Set keySet; @LazyInit @CheckForNull transient Collection values; @LazyInit @CheckForNull transient Map> map; UnmodifiableMultimap(final io.github.tanyaofei.guava.common.collect.Multimap delegate) { this.delegate = checkNotNull(delegate); } @Override protected io.github.tanyaofei.guava.common.collect.Multimap delegate() { return delegate; } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Map> asMap() { Map> result = map; if (result == null) { result = map = Collections.unmodifiableMap( io.github.tanyaofei.guava.common.collect.Maps.transformValues( delegate.asMap(), new Function, Collection>() { @Override public Collection apply(Collection collection) { return unmodifiableValueCollection(collection); } })); } return result; } @Override public Collection> entries() { Collection> result = entries; if (result == null) { entries = result = unmodifiableEntries(delegate.entries()); } return result; } @Override public void forEach(BiConsumer consumer) { delegate.forEach(checkNotNull(consumer)); } @Override public Collection get(@ParametricNullness K key) { return unmodifiableValueCollection(delegate.get(key)); } @Override public io.github.tanyaofei.guava.common.collect.Multiset keys() { io.github.tanyaofei.guava.common.collect.Multiset result = keys; if (result == null) { keys = result = Multisets.unmodifiableMultiset(delegate.keys()); } return result; } @Override public Set keySet() { Set result = keySet; if (result == null) { keySet = result = Collections.unmodifiableSet(delegate.keySet()); } return result; } @Override public boolean put(@ParametricNullness K key, @ParametricNullness V value) { throw new UnsupportedOperationException(); } @Override public boolean putAll(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override public boolean putAll(io.github.tanyaofei.guava.common.collect.Multimap multimap) { throw new UnsupportedOperationException(); } @Override public boolean remove(@CheckForNull Object key, @CheckForNull Object value) { throw new UnsupportedOperationException(); } @Override public Collection removeAll(@CheckForNull Object key) { throw new UnsupportedOperationException(); } @Override public Collection replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override public Collection values() { Collection result = values; if (result == null) { values = result = Collections.unmodifiableCollection(delegate.values()); } return result; } private static final long serialVersionUID = 0; } private static class UnmodifiableListMultimap< K extends @Nullable Object, V extends @Nullable Object> extends UnmodifiableMultimap implements ListMultimap { UnmodifiableListMultimap(ListMultimap delegate) { super(delegate); } @Override public ListMultimap delegate() { return (ListMultimap) super.delegate(); } @Override public List get(@ParametricNullness K key) { return Collections.unmodifiableList(delegate().get(key)); } @Override public List removeAll(@CheckForNull Object key) { throw new UnsupportedOperationException(); } @Override public List replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } private static final long serialVersionUID = 0; } private static class UnmodifiableSetMultimap< K extends @Nullable Object, V extends @Nullable Object> extends UnmodifiableMultimap implements SetMultimap { UnmodifiableSetMultimap(SetMultimap delegate) { super(delegate); } @Override public SetMultimap delegate() { return (SetMultimap) super.delegate(); } @Override public Set get(@ParametricNullness K key) { /* * Note that this doesn't return a SortedSet when delegate is a * SortedSetMultiset, unlike (SortedSet) super.get(). */ return Collections.unmodifiableSet(delegate().get(key)); } @Override public Set> entries() { return io.github.tanyaofei.guava.common.collect.Maps.unmodifiableEntrySet(delegate().entries()); } @Override public Set removeAll(@CheckForNull Object key) { throw new UnsupportedOperationException(); } @Override public Set replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } private static final long serialVersionUID = 0; } private static class UnmodifiableSortedSetMultimap< K extends @Nullable Object, V extends @Nullable Object> extends UnmodifiableSetMultimap implements SortedSetMultimap { UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { super(delegate); } @Override public SortedSetMultimap delegate() { return (SortedSetMultimap) super.delegate(); } @Override public SortedSet get(@ParametricNullness K key) { return Collections.unmodifiableSortedSet(delegate().get(key)); } @Override public SortedSet removeAll(@CheckForNull Object key) { throw new UnsupportedOperationException(); } @Override public SortedSet replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override @CheckForNull public Comparator valueComparator() { return delegate().valueComparator(); } private static final long serialVersionUID = 0; } /** * Returns a synchronized (thread-safe) {@code SetMultimap} backed by the specified multimap. * *

You must follow the warnings described in {@link #synchronizedMultimap}. * *

The returned multimap will be serializable if the specified multimap is serializable. * * @param multimap the multimap to be wrapped * @return a synchronized view of the specified multimap */ public static SetMultimap synchronizedSetMultimap(SetMultimap multimap) { return Synchronized.setMultimap(multimap, null); } /** * Returns an unmodifiable view of the specified {@code SetMultimap}. Query operations on the * returned multimap "read through" to the specified multimap, and attempts to modify the returned * multimap, either directly or through the multimap's views, result in an {@code * UnsupportedOperationException}. * *

The returned multimap will be serializable if the specified multimap is serializable. * * @param delegate the multimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified multimap */ public static SetMultimap unmodifiableSetMultimap(SetMultimap delegate) { if (delegate instanceof UnmodifiableSetMultimap || delegate instanceof ImmutableSetMultimap) { return delegate; } return new UnmodifiableSetMultimap<>(delegate); } /** * Simply returns its argument. * * @deprecated no need to use this * @since 10.0 */ @Deprecated public static SetMultimap unmodifiableSetMultimap( ImmutableSetMultimap delegate) { return checkNotNull(delegate); } /** * Returns a synchronized (thread-safe) {@code SortedSetMultimap} backed by the specified * multimap. * *

You must follow the warnings described in {@link #synchronizedMultimap}. * *

The returned multimap will be serializable if the specified multimap is serializable. * * @param multimap the multimap to be wrapped * @return a synchronized view of the specified multimap */ public static SortedSetMultimap synchronizedSortedSetMultimap(SortedSetMultimap multimap) { return Synchronized.sortedSetMultimap(multimap, null); } /** * Returns an unmodifiable view of the specified {@code SortedSetMultimap}. Query operations on * the returned multimap "read through" to the specified multimap, and attempts to modify the * returned multimap, either directly or through the multimap's views, result in an {@code * UnsupportedOperationException}. * *

The returned multimap will be serializable if the specified multimap is serializable. * * @param delegate the multimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified multimap */ public static SortedSetMultimap unmodifiableSortedSetMultimap(SortedSetMultimap delegate) { if (delegate instanceof UnmodifiableSortedSetMultimap) { return delegate; } return new UnmodifiableSortedSetMultimap<>(delegate); } /** * Returns a synchronized (thread-safe) {@code ListMultimap} backed by the specified multimap. * *

You must follow the warnings described in {@link #synchronizedMultimap}. * * @param multimap the multimap to be wrapped * @return a synchronized view of the specified multimap */ public static ListMultimap synchronizedListMultimap(ListMultimap multimap) { return Synchronized.listMultimap(multimap, null); } /** * Returns an unmodifiable view of the specified {@code ListMultimap}. Query operations on the * returned multimap "read through" to the specified multimap, and attempts to modify the returned * multimap, either directly or through the multimap's views, result in an {@code * UnsupportedOperationException}. * *

The returned multimap will be serializable if the specified multimap is serializable. * * @param delegate the multimap for which an unmodifiable view is to be returned * @return an unmodifiable view of the specified multimap */ public static ListMultimap unmodifiableListMultimap(ListMultimap delegate) { if (delegate instanceof UnmodifiableListMultimap || delegate instanceof ImmutableListMultimap) { return delegate; } return new UnmodifiableListMultimap<>(delegate); } /** * Simply returns its argument. * * @deprecated no need to use this * @since 10.0 */ @Deprecated public static ListMultimap unmodifiableListMultimap( ImmutableListMultimap delegate) { return checkNotNull(delegate); } /** * Returns an unmodifiable view of the specified collection, preserving the interface for * instances of {@code SortedSet}, {@code Set}, {@code List} and {@code Collection}, in that order * of preference. * * @param collection the collection for which to return an unmodifiable view * @return an unmodifiable view of the collection */ private static Collection unmodifiableValueCollection( Collection collection) { if (collection instanceof SortedSet) { return Collections.unmodifiableSortedSet((SortedSet) collection); } else if (collection instanceof Set) { return Collections.unmodifiableSet((Set) collection); } else if (collection instanceof List) { return Collections.unmodifiableList((List) collection); } return Collections.unmodifiableCollection(collection); } /** * Returns an unmodifiable view of the specified collection of entries. The {@link Entry#setValue} * operation throws an {@link UnsupportedOperationException}. If the specified collection is a * {@code Set}, the returned collection is also a {@code Set}. * * @param entries the entries for which to return an unmodifiable view * @return an unmodifiable view of the entries */ private static Collection> unmodifiableEntries(Collection> entries) { if (entries instanceof Set) { return io.github.tanyaofei.guava.common.collect.Maps.unmodifiableEntrySet((Set>) entries); } return new io.github.tanyaofei.guava.common.collect.Maps.UnmodifiableEntries<>(Collections.unmodifiableCollection(entries)); } /** * Returns {@link ListMultimap#asMap multimap.asMap()}, with its type corrected from {@code Map>} to {@code Map>}. * * @since 15.0 */ @Beta @SuppressWarnings("unchecked") // safe by specification of ListMultimap.asMap() public static Map> asMap( ListMultimap multimap) { return (Map>) (Map) multimap.asMap(); } /** * Returns {@link SetMultimap#asMap multimap.asMap()}, with its type corrected from {@code Map>} to {@code Map>}. * * @since 15.0 */ @Beta @SuppressWarnings("unchecked") // safe by specification of SetMultimap.asMap() public static Map> asMap( SetMultimap multimap) { return (Map>) (Map) multimap.asMap(); } /** * Returns {@link SortedSetMultimap#asMap multimap.asMap()}, with its type corrected from {@code * Map>} to {@code Map>}. * * @since 15.0 */ @Beta @SuppressWarnings("unchecked") // safe by specification of SortedSetMultimap.asMap() public static Map> asMap( SortedSetMultimap multimap) { return (Map>) (Map) multimap.asMap(); } /** * Returns {@link io.github.tanyaofei.guava.common.collect.Multimap#asMap multimap.asMap()}. This is provided for parity with the other * more strongly-typed {@code asMap()} implementations. * * @since 15.0 */ @Beta public static Map> asMap(io.github.tanyaofei.guava.common.collect.Multimap multimap) { return multimap.asMap(); } /** * Returns a multimap view of the specified map. The multimap is backed by the map, so changes to * the map are reflected in the multimap, and vice versa. If the map is modified while an * iteration over one of the multimap's collection views is in progress (except through the * iterator's own {@code remove} operation, or through the {@code setValue} operation on a map * entry returned by the iterator), the results of the iteration are undefined. * *

The multimap supports mapping removal, which removes the corresponding mapping from the map. * It does not support any operations which might add mappings, such as {@code put}, {@code * putAll} or {@code replaceValues}. * *

The returned multimap will be serializable if the specified map is serializable. * * @param map the backing map for the returned multimap view */ public static SetMultimap forMap( Map map) { return new MapMultimap<>(map); } /** @see Multimaps#forMap */ private static class MapMultimap extends AbstractMultimap implements SetMultimap, Serializable { final Map map; MapMultimap(Map map) { this.map = checkNotNull(map); } @Override public int size() { return map.size(); } @Override public boolean containsKey(@CheckForNull Object key) { return map.containsKey(key); } @Override public boolean containsValue(@CheckForNull Object value) { return map.containsValue(value); } @Override public boolean containsEntry(@CheckForNull Object key, @CheckForNull Object value) { return map.entrySet().contains(io.github.tanyaofei.guava.common.collect.Maps.immutableEntry(key, value)); } @Override public Set get(@ParametricNullness final K key) { return new Sets.ImprovedAbstractSet() { @Override public Iterator iterator() { return new Iterator() { int i; @Override public boolean hasNext() { return (i == 0) && map.containsKey(key); } @Override @ParametricNullness public V next() { if (!hasNext()) { throw new NoSuchElementException(); } i++; /* * The cast is safe because of the containsKey check in hasNext(). (That means it's * unsafe under concurrent modification, but all bets are off then, anyway.) */ return uncheckedCastNullableTToT(map.get(key)); } @Override public void remove() { checkRemove(i == 1); i = -1; map.remove(key); } }; } @Override public int size() { return map.containsKey(key) ? 1 : 0; } }; } @Override public boolean put(@ParametricNullness K key, @ParametricNullness V value) { throw new UnsupportedOperationException(); } @Override public boolean putAll(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override public boolean putAll(io.github.tanyaofei.guava.common.collect.Multimap multimap) { throw new UnsupportedOperationException(); } @Override public Set replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override public boolean remove(@CheckForNull Object key, @CheckForNull Object value) { return map.entrySet().remove(io.github.tanyaofei.guava.common.collect.Maps.immutableEntry(key, value)); } @Override public Set removeAll(@CheckForNull Object key) { Set values = new HashSet(2); if (!map.containsKey(key)) { return values; } values.add(map.remove(key)); return values; } @Override public void clear() { map.clear(); } @Override Set createKeySet() { return map.keySet(); } @Override Collection createValues() { return map.values(); } @Override public Set> entries() { return map.entrySet(); } @Override Collection> createEntries() { throw new AssertionError("unreachable"); } @Override io.github.tanyaofei.guava.common.collect.Multiset createKeys() { return new Keys(this); } @Override Iterator> entryIterator() { return map.entrySet().iterator(); } @Override Map> createAsMap() { return new AsMap<>(this); } @Override public int hashCode() { return map.hashCode(); } private static final long serialVersionUID = 7845222491160860175L; } /** * Returns a view of a multimap where each value is transformed by a function. All other * properties of the multimap, such as iteration order, are left intact. For example, the code: * *

{@code
   * Multimap multimap =
   *     ImmutableSetMultimap.of("a", 2, "b", -3, "b", -3, "a", 4, "c", 6);
   * Function square = new Function() {
   *     public String apply(Integer in) {
   *       return Integer.toString(in * in);
   *     }
   * };
   * Multimap transformed =
   *     Multimaps.transformValues(multimap, square);
   *   System.out.println(transformed);
   * }
* * ... prints {@code {a=[4, 16], b=[9, 9], c=[36]}}. * *

Changes in the underlying multimap are reflected in this view. Conversely, this view * supports removal operations, and these are reflected in the underlying multimap. * *

It's acceptable for the underlying multimap to contain null keys, and even null values * provided that the function is capable of accepting null input. The transformed multimap might * contain null values, if the function sometimes gives a null result. * *

The returned multimap is not thread-safe or serializable, even if the underlying multimap * is. The {@code equals} and {@code hashCode} methods of the returned multimap are meaningless, * since there is not a definition of {@code equals} or {@code hashCode} for general collections, * and {@code get()} will return a general {@code Collection} as opposed to a {@code List} or a * {@code Set}. * *

The function is applied lazily, invoked when needed. This is necessary for the returned * multimap to be a view, but it means that the function will be applied many times for bulk * operations like {@link io.github.tanyaofei.guava.common.collect.Multimap#containsValue} and {@code Multimap.toString()}. For this to * perform well, {@code function} should be fast. To avoid lazy evaluation when the returned * multimap doesn't need to be a view, copy the returned multimap into a new multimap of your * choosing. * * @since 7.0 */ public static < K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> io.github.tanyaofei.guava.common.collect.Multimap transformValues( io.github.tanyaofei.guava.common.collect.Multimap fromMultimap, final Function function) { checkNotNull(function); EntryTransformer transformer = io.github.tanyaofei.guava.common.collect.Maps.asEntryTransformer(function); return transformEntries(fromMultimap, transformer); } /** * Returns a view of a {@code ListMultimap} where each value is transformed by a function. All * other properties of the multimap, such as iteration order, are left intact. For example, the * code: * *

{@code
   * ListMultimap multimap
   *      = ImmutableListMultimap.of("a", 4, "a", 16, "b", 9);
   * Function sqrt =
   *     new Function() {
   *       public Double apply(Integer in) {
   *         return Math.sqrt((int) in);
   *       }
   *     };
   * ListMultimap transformed = Multimaps.transformValues(map,
   *     sqrt);
   * System.out.println(transformed);
   * }
* * ... prints {@code {a=[2.0, 4.0], b=[3.0]}}. * *

Changes in the underlying multimap are reflected in this view. Conversely, this view * supports removal operations, and these are reflected in the underlying multimap. * *

It's acceptable for the underlying multimap to contain null keys, and even null values * provided that the function is capable of accepting null input. The transformed multimap might * contain null values, if the function sometimes gives a null result. * *

The returned multimap is not thread-safe or serializable, even if the underlying multimap * is. * *

The function is applied lazily, invoked when needed. This is necessary for the returned * multimap to be a view, but it means that the function will be applied many times for bulk * operations like {@link io.github.tanyaofei.guava.common.collect.Multimap#containsValue} and {@code Multimap.toString()}. For this to * perform well, {@code function} should be fast. To avoid lazy evaluation when the returned * multimap doesn't need to be a view, copy the returned multimap into a new multimap of your * choosing. * * @since 7.0 */ public static < K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> ListMultimap transformValues( ListMultimap fromMultimap, final Function function) { checkNotNull(function); EntryTransformer transformer = io.github.tanyaofei.guava.common.collect.Maps.asEntryTransformer(function); return transformEntries(fromMultimap, transformer); } /** * Returns a view of a multimap whose values are derived from the original multimap's entries. In * contrast to {@link #transformValues}, this method's entry-transformation logic may depend on * the key as well as the value. * *

All other properties of the transformed multimap, such as iteration order, are left intact. * For example, the code: * *

{@code
   * SetMultimap multimap =
   *     ImmutableSetMultimap.of("a", 1, "a", 4, "b", -6);
   * EntryTransformer transformer =
   *     new EntryTransformer() {
   *       public String transformEntry(String key, Integer value) {
   *          return (value >= 0) ? key : "no" + key;
   *       }
   *     };
   * Multimap transformed =
   *     Multimaps.transformEntries(multimap, transformer);
   * System.out.println(transformed);
   * }
* * ... prints {@code {a=[a, a], b=[nob]}}. * *

Changes in the underlying multimap are reflected in this view. Conversely, this view * supports removal operations, and these are reflected in the underlying multimap. * *

It's acceptable for the underlying multimap to contain null keys and null values provided * that the transformer is capable of accepting null inputs. The transformed multimap might * contain null values if the transformer sometimes gives a null result. * *

The returned multimap is not thread-safe or serializable, even if the underlying multimap * is. The {@code equals} and {@code hashCode} methods of the returned multimap are meaningless, * since there is not a definition of {@code equals} or {@code hashCode} for general collections, * and {@code get()} will return a general {@code Collection} as opposed to a {@code List} or a * {@code Set}. * *

The transformer is applied lazily, invoked when needed. This is necessary for the returned * multimap to be a view, but it means that the transformer will be applied many times for bulk * operations like {@link io.github.tanyaofei.guava.common.collect.Multimap#containsValue} and {@link Object#toString}. For this to perform * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned multimap * doesn't need to be a view, copy the returned multimap into a new multimap of your choosing. * *

Warning: This method assumes that for any instance {@code k} of {@code * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the * transformed multimap. * * @since 7.0 */ public static < K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> io.github.tanyaofei.guava.common.collect.Multimap transformEntries( io.github.tanyaofei.guava.common.collect.Multimap fromMap, EntryTransformer transformer) { return new TransformedEntriesMultimap<>(fromMap, transformer); } /** * Returns a view of a {@code ListMultimap} whose values are derived from the original multimap's * entries. In contrast to {@link #transformValues(ListMultimap, Function)}, this method's * entry-transformation logic may depend on the key as well as the value. * *

All other properties of the transformed multimap, such as iteration order, are left intact. * For example, the code: * *

{@code
   * Multimap multimap =
   *     ImmutableMultimap.of("a", 1, "a", 4, "b", 6);
   * EntryTransformer transformer =
   *     new EntryTransformer() {
   *       public String transformEntry(String key, Integer value) {
   *         return key + value;
   *       }
   *     };
   * Multimap transformed =
   *     Multimaps.transformEntries(multimap, transformer);
   * System.out.println(transformed);
   * }
* * ... prints {@code {"a"=["a1", "a4"], "b"=["b6"]}}. * *

Changes in the underlying multimap are reflected in this view. Conversely, this view * supports removal operations, and these are reflected in the underlying multimap. * *

It's acceptable for the underlying multimap to contain null keys and null values provided * that the transformer is capable of accepting null inputs. The transformed multimap might * contain null values if the transformer sometimes gives a null result. * *

The returned multimap is not thread-safe or serializable, even if the underlying multimap * is. * *

The transformer is applied lazily, invoked when needed. This is necessary for the returned * multimap to be a view, but it means that the transformer will be applied many times for bulk * operations like {@link io.github.tanyaofei.guava.common.collect.Multimap#containsValue} and {@link Object#toString}. For this to perform * well, {@code transformer} should be fast. To avoid lazy evaluation when the returned multimap * doesn't need to be a view, copy the returned multimap into a new multimap of your choosing. * *

Warning: This method assumes that for any instance {@code k} of {@code * EntryTransformer} key type {@code K}, {@code k.equals(k2)} implies that {@code k2} is also of * type {@code K}. Using an {@code EntryTransformer} key type for which this may not hold, such as * {@code ArrayList}, may risk a {@code ClassCastException} when calling methods on the * transformed multimap. * * @since 7.0 */ public static < K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> ListMultimap transformEntries( ListMultimap fromMap, EntryTransformer transformer) { return new TransformedEntriesListMultimap<>(fromMap, transformer); } private static class TransformedEntriesMultimap< K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> extends AbstractMultimap { final io.github.tanyaofei.guava.common.collect.Multimap fromMultimap; final EntryTransformer transformer; TransformedEntriesMultimap( io.github.tanyaofei.guava.common.collect.Multimap fromMultimap, final EntryTransformer transformer) { this.fromMultimap = checkNotNull(fromMultimap); this.transformer = checkNotNull(transformer); } Collection transform(@ParametricNullness K key, Collection values) { Function function = io.github.tanyaofei.guava.common.collect.Maps.asValueToValueFunction(transformer, key); if (values instanceof List) { return io.github.tanyaofei.guava.common.collect.Lists.transform((List) values, function); } else { return Collections2.transform(values, function); } } @Override Map> createAsMap() { return io.github.tanyaofei.guava.common.collect.Maps.transformEntries( fromMultimap.asMap(), new EntryTransformer, Collection>() { @Override public Collection transformEntry(@ParametricNullness K key, Collection value) { return transform(key, value); } }); } @Override public void clear() { fromMultimap.clear(); } @Override public boolean containsKey(@CheckForNull Object key) { return fromMultimap.containsKey(key); } @Override Collection> createEntries() { return new AbstractMultimap.Entries(); } @Override Iterator> entryIterator() { return Iterators.transform( fromMultimap.entries().iterator(), io.github.tanyaofei.guava.common.collect.Maps.asEntryToEntryFunction(transformer)); } @Override public Collection get(@ParametricNullness final K key) { return transform(key, fromMultimap.get(key)); } @Override public boolean isEmpty() { return fromMultimap.isEmpty(); } @Override Set createKeySet() { return fromMultimap.keySet(); } @Override io.github.tanyaofei.guava.common.collect.Multiset createKeys() { return fromMultimap.keys(); } @Override public boolean put(@ParametricNullness K key, @ParametricNullness V2 value) { throw new UnsupportedOperationException(); } @Override public boolean putAll(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override public boolean putAll(io.github.tanyaofei.guava.common.collect.Multimap multimap) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public boolean remove(@CheckForNull Object key, @CheckForNull Object value) { return get((K) key).remove(value); } @SuppressWarnings("unchecked") @Override public Collection removeAll(@CheckForNull Object key) { return transform((K) key, fromMultimap.removeAll(key)); } @Override public Collection replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } @Override public int size() { return fromMultimap.size(); } @Override Collection createValues() { return Collections2.transform( fromMultimap.entries(), io.github.tanyaofei.guava.common.collect.Maps.asEntryToValueFunction(transformer)); } } private static final class TransformedEntriesListMultimap< K extends @Nullable Object, V1 extends @Nullable Object, V2 extends @Nullable Object> extends TransformedEntriesMultimap implements ListMultimap { TransformedEntriesListMultimap( ListMultimap fromMultimap, EntryTransformer transformer) { super(fromMultimap, transformer); } @Override List transform(@ParametricNullness K key, Collection values) { return Lists.transform((List) values, io.github.tanyaofei.guava.common.collect.Maps.asValueToValueFunction(transformer, key)); } @Override public List get(@ParametricNullness K key) { return transform(key, fromMultimap.get(key)); } @SuppressWarnings("unchecked") @Override public List removeAll(@CheckForNull Object key) { return transform((K) key, fromMultimap.removeAll(key)); } @Override public List replaceValues(@ParametricNullness K key, Iterable values) { throw new UnsupportedOperationException(); } } /** * Creates an index {@code ImmutableListMultimap} that contains the results of applying a * specified function to each item in an {@code Iterable} of values. Each value will be stored as * a value in the resulting multimap, yielding a multimap with the same size as the input * iterable. The key used to store that value in the multimap will be the result of calling the * function on that value. The resulting multimap is created as an immutable snapshot. In the * returned multimap, keys appear in the order they are first encountered, and the values * corresponding to each key appear in the same order as they are encountered. * *

For example, * *

{@code
   * List badGuys =
   *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
   * Function stringLengthFunction = ...;
   * Multimap index =
   *     Multimaps.index(badGuys, stringLengthFunction);
   * System.out.println(index);
   * }
* *

prints * *

{@code
   * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
   * }
* *

The returned multimap is serializable if its keys and values are all serializable. * * @param values the values to use when constructing the {@code ImmutableListMultimap} * @param keyFunction the function used to produce the key for each value * @return {@code ImmutableListMultimap} mapping the result of evaluating the function {@code * keyFunction} on each value in the input collection to that value * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code * keyFunction} produces {@code null} for any key */ public static ImmutableListMultimap index( Iterable values, Function keyFunction) { return index(values.iterator(), keyFunction); } /** * Creates an index {@code ImmutableListMultimap} that contains the results of applying a * specified function to each item in an {@code Iterator} of values. Each value will be stored as * a value in the resulting multimap, yielding a multimap with the same size as the input * iterator. The key used to store that value in the multimap will be the result of calling the * function on that value. The resulting multimap is created as an immutable snapshot. In the * returned multimap, keys appear in the order they are first encountered, and the values * corresponding to each key appear in the same order as they are encountered. * *

For example, * *

{@code
   * List badGuys =
   *     Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
   * Function stringLengthFunction = ...;
   * Multimap index =
   *     Multimaps.index(badGuys.iterator(), stringLengthFunction);
   * System.out.println(index);
   * }
* *

prints * *

{@code
   * {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
   * }
* *

The returned multimap is serializable if its keys and values are all serializable. * * @param values the values to use when constructing the {@code ImmutableListMultimap} * @param keyFunction the function used to produce the key for each value * @return {@code ImmutableListMultimap} mapping the result of evaluating the function {@code * keyFunction} on each value in the input collection to that value * @throws NullPointerException if any element of {@code values} is {@code null}, or if {@code * keyFunction} produces {@code null} for any key * @since 10.0 */ public static ImmutableListMultimap index( Iterator values, Function keyFunction) { checkNotNull(keyFunction); ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); while (values.hasNext()) { V value = values.next(); checkNotNull(value, values); builder.put(keyFunction.apply(value), value); } return builder.build(); } static class Keys extends AbstractMultiset { @Weak final io.github.tanyaofei.guava.common.collect.Multimap multimap; Keys(io.github.tanyaofei.guava.common.collect.Multimap multimap) { this.multimap = multimap; } @Override Iterator> entryIterator() { return new TransformedIterator>, Entry>( multimap.asMap().entrySet().iterator()) { @Override Entry transform(final Map.Entry> backingEntry) { return new Multisets.AbstractEntry() { @Override @ParametricNullness public K getElement() { return backingEntry.getKey(); } @Override public int getCount() { return backingEntry.getValue().size(); } }; } }; } @Override public Spliterator spliterator() { return CollectSpliterators.map(multimap.entries().spliterator(), Map.Entry::getKey); } @Override public void forEach(Consumer consumer) { checkNotNull(consumer); multimap.entries().forEach(entry -> consumer.accept(entry.getKey())); } @Override int distinctElements() { return multimap.asMap().size(); } @Override public int size() { return multimap.size(); } @Override public boolean contains(@CheckForNull Object element) { return multimap.containsKey(element); } @Override public Iterator iterator() { return io.github.tanyaofei.guava.common.collect.Maps.keyIterator(multimap.entries().iterator()); } @Override public int count(@CheckForNull Object element) { Collection values = io.github.tanyaofei.guava.common.collect.Maps.safeGet(multimap.asMap(), element); return (values == null) ? 0 : values.size(); } @Override public int remove(@CheckForNull Object element, int occurrences) { checkNonnegative(occurrences, "occurrences"); if (occurrences == 0) { return count(element); } Collection values = io.github.tanyaofei.guava.common.collect.Maps.safeGet(multimap.asMap(), element); if (values == null) { return 0; } int oldCount = values.size(); if (occurrences >= oldCount) { values.clear(); } else { Iterator iterator = values.iterator(); for (int i = 0; i < occurrences; i++) { iterator.next(); iterator.remove(); } } return oldCount; } @Override public void clear() { multimap.clear(); } @Override public Set elementSet() { return multimap.keySet(); } @Override Iterator elementIterator() { throw new AssertionError("should never be called"); } } /** A skeleton implementation of {@link io.github.tanyaofei.guava.common.collect.Multimap#entries()}. */ abstract static class Entries extends AbstractCollection> { abstract io.github.tanyaofei.guava.common.collect.Multimap multimap(); @Override public int size() { return multimap().size(); } @Override public boolean contains(@CheckForNull Object o) { if (o instanceof Map.Entry) { Entry entry = (Entry) o; return multimap().containsEntry(entry.getKey(), entry.getValue()); } return false; } @Override public boolean remove(@CheckForNull Object o) { if (o instanceof Map.Entry) { Entry entry = (Entry) o; return multimap().remove(entry.getKey(), entry.getValue()); } return false; } @Override public void clear() { multimap().clear(); } } /** A skeleton implementation of {@link io.github.tanyaofei.guava.common.collect.Multimap#asMap()}. */ static final class AsMap extends io.github.tanyaofei.guava.common.collect.Maps.ViewCachingAbstractMap> { @Weak private final io.github.tanyaofei.guava.common.collect.Multimap multimap; AsMap(io.github.tanyaofei.guava.common.collect.Multimap multimap) { this.multimap = checkNotNull(multimap); } @Override public int size() { return multimap.keySet().size(); } @Override protected Set>> createEntrySet() { return new EntrySet(); } void removeValuesForKey(@CheckForNull Object key) { multimap.keySet().remove(key); } @WeakOuter class EntrySet extends io.github.tanyaofei.guava.common.collect.Maps.EntrySet> { @Override Map> map() { return AsMap.this; } @Override public Iterator>> iterator() { return io.github.tanyaofei.guava.common.collect.Maps.asMapEntryIterator( multimap.keySet(), new Function>() { @Override public Collection apply(@ParametricNullness K key) { return multimap.get(key); } }); } @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; } } @SuppressWarnings("unchecked") @Override @CheckForNull public Collection get(@CheckForNull Object key) { return containsKey(key) ? multimap.get((K) key) : null; } @Override @CheckForNull public Collection remove(@CheckForNull Object key) { return containsKey(key) ? multimap.removeAll(key) : null; } @Override public Set keySet() { return multimap.keySet(); } @Override public boolean isEmpty() { return multimap.isEmpty(); } @Override public boolean containsKey(@CheckForNull Object key) { return multimap.containsKey(key); } @Override public void clear() { multimap.clear(); } } /** * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect * the other. * *

The resulting multimap's views have iterators that don't support {@code remove()}, but all * other methods are supported by the multimap and its views. When adding a key that doesn't * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code * replaceValues()} methods throw an {@link IllegalArgumentException}. * *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered * multimap or its views, only mappings whose keys satisfy the filter will be removed from the * underlying multimap. * *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. * *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every * key/value mapping in the underlying multimap and determine which satisfy the filter. When a * live view is not needed, it may be faster to copy the filtered multimap and use the * copy. * *

Warning: {@code keyPredicate} must be consistent with equals, as documented at * {@link Predicate#apply}. Do not provide a predicate such as {@code * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. * * @since 11.0 */ public static io.github.tanyaofei.guava.common.collect.Multimap filterKeys( io.github.tanyaofei.guava.common.collect.Multimap unfiltered, final Predicate keyPredicate) { if (unfiltered instanceof SetMultimap) { return filterKeys((SetMultimap) unfiltered, keyPredicate); } else if (unfiltered instanceof ListMultimap) { return filterKeys((ListMultimap) unfiltered, keyPredicate); } else if (unfiltered instanceof FilteredKeyMultimap) { FilteredKeyMultimap prev = (FilteredKeyMultimap) unfiltered; return new FilteredKeyMultimap<>( prev.unfiltered, Predicates.and(prev.keyPredicate, keyPredicate)); } else if (unfiltered instanceof FilteredMultimap) { FilteredMultimap prev = (FilteredMultimap) unfiltered; return filterFiltered(prev, io.github.tanyaofei.guava.common.collect.Maps.keyPredicateOnEntries(keyPredicate)); } else { return new FilteredKeyMultimap<>(unfiltered, keyPredicate); } } /** * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect * the other. * *

The resulting multimap's views have iterators that don't support {@code remove()}, but all * other methods are supported by the multimap and its views. When adding a key that doesn't * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code * replaceValues()} methods throw an {@link IllegalArgumentException}. * *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered * multimap or its views, only mappings whose keys satisfy the filter will be removed from the * underlying multimap. * *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. * *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every * key/value mapping in the underlying multimap and determine which satisfy the filter. When a * live view is not needed, it may be faster to copy the filtered multimap and use the * copy. * *

Warning: {@code keyPredicate} must be consistent with equals, as documented at * {@link Predicate#apply}. Do not provide a predicate such as {@code * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. * * @since 14.0 */ public static SetMultimap filterKeys( SetMultimap unfiltered, final Predicate keyPredicate) { if (unfiltered instanceof FilteredKeySetMultimap) { FilteredKeySetMultimap prev = (FilteredKeySetMultimap) unfiltered; return new FilteredKeySetMultimap<>( prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); } else if (unfiltered instanceof FilteredSetMultimap) { FilteredSetMultimap prev = (FilteredSetMultimap) unfiltered; return filterFiltered(prev, io.github.tanyaofei.guava.common.collect.Maps.keyPredicateOnEntries(keyPredicate)); } else { return new FilteredKeySetMultimap<>(unfiltered, keyPredicate); } } /** * Returns a multimap containing the mappings in {@code unfiltered} whose keys satisfy a * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect * the other. * *

The resulting multimap's views have iterators that don't support {@code remove()}, but all * other methods are supported by the multimap and its views. When adding a key that doesn't * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code * replaceValues()} methods throw an {@link IllegalArgumentException}. * *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered * multimap or its views, only mappings whose keys satisfy the filter will be removed from the * underlying multimap. * *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. * *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every * key/value mapping in the underlying multimap and determine which satisfy the filter. When a * live view is not needed, it may be faster to copy the filtered multimap and use the * copy. * *

Warning: {@code keyPredicate} must be consistent with equals, as documented at * {@link Predicate#apply}. Do not provide a predicate such as {@code * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. * * @since 14.0 */ public static ListMultimap filterKeys( ListMultimap unfiltered, final Predicate keyPredicate) { if (unfiltered instanceof FilteredKeyListMultimap) { FilteredKeyListMultimap prev = (FilteredKeyListMultimap) unfiltered; return new FilteredKeyListMultimap<>( prev.unfiltered(), Predicates.and(prev.keyPredicate, keyPredicate)); } else { return new FilteredKeyListMultimap<>(unfiltered, keyPredicate); } } /** * Returns a multimap containing the mappings in {@code unfiltered} whose values satisfy a * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect * the other. * *

The resulting multimap's views have iterators that don't support {@code remove()}, but all * other methods are supported by the multimap and its views. When adding a value that doesn't * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code * replaceValues()} methods throw an {@link IllegalArgumentException}. * *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered * multimap or its views, only mappings whose value satisfy the filter will be removed from the * underlying multimap. * *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. * *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every * key/value mapping in the underlying multimap and determine which satisfy the filter. When a * live view is not needed, it may be faster to copy the filtered multimap and use the * copy. * *

Warning: {@code valuePredicate} must be consistent with equals, as documented * at {@link Predicate#apply}. Do not provide a predicate such as {@code * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. * * @since 11.0 */ public static io.github.tanyaofei.guava.common.collect.Multimap filterValues( io.github.tanyaofei.guava.common.collect.Multimap unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, io.github.tanyaofei.guava.common.collect.Maps.valuePredicateOnEntries(valuePredicate)); } /** * Returns a multimap containing the mappings in {@code unfiltered} whose values satisfy a * predicate. The returned multimap is a live view of {@code unfiltered}; changes to one affect * the other. * *

The resulting multimap's views have iterators that don't support {@code remove()}, but all * other methods are supported by the multimap and its views. When adding a value that doesn't * satisfy the predicate, the multimap's {@code put()}, {@code putAll()}, and {@code * replaceValues()} methods throw an {@link IllegalArgumentException}. * *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered * multimap or its views, only mappings whose value satisfy the filter will be removed from the * underlying multimap. * *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. * *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every * key/value mapping in the underlying multimap and determine which satisfy the filter. When a * live view is not needed, it may be faster to copy the filtered multimap and use the * copy. * *

Warning: {@code valuePredicate} must be consistent with equals, as documented * at {@link Predicate#apply}. Do not provide a predicate such as {@code * Predicates.instanceOf(ArrayList.class)}, which is inconsistent with equals. * * @since 14.0 */ public static SetMultimap filterValues( SetMultimap unfiltered, final Predicate valuePredicate) { return filterEntries(unfiltered, Maps.valuePredicateOnEntries(valuePredicate)); } /** * Returns a multimap containing the mappings in {@code unfiltered} that satisfy a predicate. The * returned multimap is a live view of {@code unfiltered}; changes to one affect the other. * *

The resulting multimap's views have iterators that don't support {@code remove()}, but all * other methods are supported by the multimap and its views. When adding a key/value pair that * doesn't satisfy the predicate, multimap's {@code put()}, {@code putAll()}, and {@code * replaceValues()} methods throw an {@link IllegalArgumentException}. * *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered * multimap or its views, only mappings whose keys satisfy the filter will be removed from the * underlying multimap. * *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. * *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every * key/value mapping in the underlying multimap and determine which satisfy the filter. When a * live view is not needed, it may be faster to copy the filtered multimap and use the * copy. * *

Warning: {@code entryPredicate} must be consistent with equals, as documented * at {@link Predicate#apply}. * * @since 11.0 */ public static io.github.tanyaofei.guava.common.collect.Multimap filterEntries( io.github.tanyaofei.guava.common.collect.Multimap unfiltered, Predicate> entryPredicate) { checkNotNull(entryPredicate); if (unfiltered instanceof SetMultimap) { return filterEntries((SetMultimap) unfiltered, entryPredicate); } return (unfiltered instanceof FilteredMultimap) ? filterFiltered((FilteredMultimap) unfiltered, entryPredicate) : new FilteredEntryMultimap(checkNotNull(unfiltered), entryPredicate); } /** * Returns a multimap containing the mappings in {@code unfiltered} that satisfy a predicate. The * returned multimap is a live view of {@code unfiltered}; changes to one affect the other. * *

The resulting multimap's views have iterators that don't support {@code remove()}, but all * other methods are supported by the multimap and its views. When adding a key/value pair that * doesn't satisfy the predicate, multimap's {@code put()}, {@code putAll()}, and {@code * replaceValues()} methods throw an {@link IllegalArgumentException}. * *

When methods such as {@code removeAll()} and {@code clear()} are called on the filtered * multimap or its views, only mappings whose keys satisfy the filter will be removed from the * underlying multimap. * *

The returned multimap isn't threadsafe or serializable, even if {@code unfiltered} is. * *

Many of the filtered multimap's methods, such as {@code size()}, iterate across every * key/value mapping in the underlying multimap and determine which satisfy the filter. When a * live view is not needed, it may be faster to copy the filtered multimap and use the * copy. * *

Warning: {@code entryPredicate} must be consistent with equals, as documented * at {@link Predicate#apply}. * * @since 14.0 */ public static SetMultimap filterEntries( SetMultimap unfiltered, Predicate> entryPredicate) { checkNotNull(entryPredicate); return (unfiltered instanceof FilteredSetMultimap) ? filterFiltered((FilteredSetMultimap) unfiltered, entryPredicate) : new FilteredEntrySetMultimap(checkNotNull(unfiltered), entryPredicate); } /** * Support removal operations when filtering a filtered multimap. Since a filtered multimap has * iterators that don't support remove, passing one to the FilteredEntryMultimap constructor would * lead to a multimap whose removal operations would fail. This method combines the predicates to * avoid that problem. */ private static io.github.tanyaofei.guava.common.collect.Multimap filterFiltered( FilteredMultimap multimap, Predicate> entryPredicate) { Predicate> predicate = Predicates.>and(multimap.entryPredicate(), entryPredicate); return new FilteredEntryMultimap<>(multimap.unfiltered(), predicate); } /** * Support removal operations when filtering a filtered multimap. Since a filtered multimap has * iterators that don't support remove, passing one to the FilteredEntryMultimap constructor would * lead to a multimap whose removal operations would fail. This method combines the predicates to * avoid that problem. */ private static SetMultimap filterFiltered( FilteredSetMultimap multimap, Predicate> entryPredicate) { Predicate> predicate = Predicates.>and(multimap.entryPredicate(), entryPredicate); return new FilteredEntrySetMultimap<>(multimap.unfiltered(), predicate); } static boolean equalsImpl(io.github.tanyaofei.guava.common.collect.Multimap multimap, @CheckForNull Object object) { if (object == multimap) { return true; } if (object instanceof io.github.tanyaofei.guava.common.collect.Multimap) { io.github.tanyaofei.guava.common.collect.Multimap that = (Multimap) object; return multimap.asMap().equals(that.asMap()); } return false; } // TODO(jlevy): Create methods that filter a SortedSetMultimap. }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy