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

com.google.common.collect.Multimaps Maven / Gradle / Ivy

Go to download

Google Collections Library is a suite of new collections and collection-related goodness for Java 5.0

There is a newer version: 1.0
Show newest version
/*
 * Copyright (C) 2007 Google Inc.
 *
 * 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 com.google.common.annotations.GwtCompatible;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Joiner.MapJoiner;
import com.google.common.base.Preconditions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Supplier;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;

import javax.annotation.Nullable;

/**
 * Provides static methods acting on or generating a {@code Multimap}.
 *
 * @author Jared Levy
 * @author Robert Konigsberg
 * @author Mike Bostock
 */
@GwtCompatible
public final class Multimaps {
  private Multimaps() {}

  /**
   * Creates a new {@code Multimap} that uses the provided map and factory. It
   * can generate a multimap based on arbitrary {@link Map} and
   * {@link Collection} 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 * 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 Multimap newMultimap(Map> map, final Supplier> factory) { return new CustomMultimap(map, factory); } private static class CustomMultimap extends AbstractMultimap { transient Supplier> factory; CustomMultimap(Map> map, Supplier> factory) { super(map); this.factory = checkNotNull(factory); } @Override protected Collection createCollection() { return factory.get(); } // 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 */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @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); } 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 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 protected List createCollection() { return factory.get(); } /** @serialData the factory and the backing map */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @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); } 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 protected Set createCollection() { return factory.get(); } /** @serialData the factory and the backing map */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @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); } 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 extends AbstractSortedSetMultimap { transient Supplier> factory; transient Comparator valueComparator; CustomSortedSetMultimap(Map> map, Supplier> factory) { super(map); this.factory = checkNotNull(factory); valueComparator = factory.get().comparator(); } @Override protected SortedSet createCollection() { return factory.get(); } /*@Override*/ public Comparator valueComparator() { return valueComparator; } /** @serialData the factory and the backing map */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(factory); stream.writeObject(backingMap()); } @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); } private static final long serialVersionUID = 0; } /** * Copies each key-value mapping in {@code source} into {@code dest}, with * its key and value reversed. * * @param source any multimap * @param dest the multimap to copy into; usually empty * @return {@code dest} */ public static > M invertFrom( Multimap source, M dest) { for (Map.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 m = Multimaps.synchronizedMultimap(
   *      HashMultimap.create());
   *  ...
   *  Set s = m.keySet();  // Needn't be in synchronized block
   *  ...
   *  synchronized (m) {  // Synchronizing on m, not s!
   *    Iterator i = s.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 Multimap#removeAll} and * {@link 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 Multimap synchronizedMultimap( 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}. * *

Note that the generated multimap's {@link Multimap#removeAll} and * {@link Multimap#replaceValues} methods return collections that are * modifiable. * *

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 Multimap unmodifiableMultimap( Multimap delegate) { return new UnmodifiableMultimap(delegate); } private static class UnmodifiableMultimap extends ForwardingMultimap implements Serializable { final Multimap delegate; transient Collection> entries; transient Multiset keys; transient Set keySet; transient Collection values; transient Map> map; UnmodifiableMultimap(final Multimap delegate) { this.delegate = delegate; } @Override protected Multimap delegate() { return delegate; } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Map> asMap() { Map> result = map; if (result == null) { final Map> unmodifiableMap = Collections.unmodifiableMap(delegate.asMap()); map = result = new ForwardingMap>() { @Override protected Map> delegate() { return unmodifiableMap; } Set>> entrySet; @Override public Set>> entrySet() { Set>> result = entrySet; return (result == null) ? entrySet = unmodifiableAsMapEntries(unmodifiableMap.entrySet()) : result; } @Override public Collection get(Object key) { Collection collection = unmodifiableMap.get(key); return (collection == null) ? null : unmodifiableValueCollection(collection); } Collection> asMapValues; @Override public Collection> values() { Collection> result = asMapValues; return (result == null) ? asMapValues = new UnmodifiableAsMapValues(unmodifiableMap.values()) : result; } @Override public boolean containsValue(Object o) { return values().contains(o); } }; } return result; } @Override public Collection> entries() { Collection> result = entries; if (result == null) { entries = result = unmodifiableEntries(delegate.entries()); } return result; } @Override public Collection get(K key) { return unmodifiableValueCollection(delegate.get(key)); } @Override public Multiset keys() { 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(K key, V value) { throw new UnsupportedOperationException(); } @Override public boolean putAll(K key, @SuppressWarnings("hiding") Iterable values) { throw new UnsupportedOperationException(); } @Override public boolean putAll(Multimap multimap) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object key, Object value) { throw new UnsupportedOperationException(); } @Override public Collection removeAll(Object key) { throw new UnsupportedOperationException(); } @Override public Collection replaceValues(K key, @SuppressWarnings("hiding") 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 UnmodifiableAsMapValues extends ForwardingCollection> { final Collection> delegate; UnmodifiableAsMapValues(Collection> delegate) { this.delegate = Collections.unmodifiableCollection(delegate); } @Override protected Collection> delegate() { return delegate; } @Override public Iterator> iterator() { final Iterator> iterator = delegate.iterator(); return new Iterator>() { public boolean hasNext() { return iterator.hasNext(); } public Collection next() { return unmodifiableValueCollection(iterator.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } @Override public Object[] toArray() { return ObjectArrays.toArrayImpl(this); } @Override public T[] toArray(T[] array) { return ObjectArrays.toArrayImpl(this, array); } @Override public boolean contains(Object o) { return Iterators.contains(iterator(), o); } @Override public boolean containsAll(Collection c) { return Collections2.containsAll(this, c); } } private static class UnmodifiableListMultimap extends UnmodifiableMultimap implements ListMultimap { UnmodifiableListMultimap(ListMultimap delegate) { super(delegate); } @Override public ListMultimap delegate() { return (ListMultimap) super.delegate(); } @Override public List get(K key) { return Collections.unmodifiableList(delegate().get(key)); } @Override public List removeAll(Object key) { throw new UnsupportedOperationException(); } @Override public List replaceValues( K key, @SuppressWarnings("hiding") Iterable values) { throw new UnsupportedOperationException(); } private static final long serialVersionUID = 0; } private static class UnmodifiableSetMultimap extends UnmodifiableMultimap implements SetMultimap { UnmodifiableSetMultimap(SetMultimap delegate) { super(delegate); } @Override public SetMultimap delegate() { return (SetMultimap) super.delegate(); } @Override public Set get(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 Maps.unmodifiableEntrySet(delegate().entries()); } @Override public Set removeAll(Object key) { throw new UnsupportedOperationException(); } @Override public Set replaceValues( K key, @SuppressWarnings("hiding") Iterable values) { throw new UnsupportedOperationException(); } private static final long serialVersionUID = 0; } private static class UnmodifiableSortedSetMultimap extends UnmodifiableSetMultimap implements SortedSetMultimap { UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { super(delegate); } @Override public SortedSetMultimap delegate() { return (SortedSetMultimap) super.delegate(); } @Override public SortedSet get(K key) { return Collections.unmodifiableSortedSet(delegate().get(key)); } @Override public SortedSet removeAll(Object key) { throw new UnsupportedOperationException(); } @Override public SortedSet replaceValues( K key, @SuppressWarnings("hiding") Iterable values) { throw new UnsupportedOperationException(); } 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}. * *

Note that the generated multimap's {@link Multimap#removeAll} and * {@link Multimap#replaceValues} methods return collections that are * modifiable. * *

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) { return new UnmodifiableSetMultimap(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}. * *

Note that the generated multimap's {@link Multimap#removeAll} and * {@link Multimap#replaceValues} methods return collections that are * modifiable. * *

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) { 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}. * *

Note that the generated multimap's {@link Multimap#removeAll} and * {@link Multimap#replaceValues} methods return collections that are * modifiable. * *

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) { return new UnmodifiableListMultimap(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 multimap {@code asMap} entry. * The {@link Entry#setValue} operation throws an {@link * UnsupportedOperationException}, and the collection returned by {@code * getValue} is also an unmodifiable (type-preserving) view. This also has the * side-effect of redefining equals to comply with the Map.Entry contract, and * to avoid a possible nefarious implementation of equals. * * @param entry the entry for which to return an unmodifiable view * @return an unmodifiable view of the entry */ private static Map.Entry> unmodifiableAsMapEntry( final Map.Entry> entry) { checkNotNull(entry); return new AbstractMapEntry>() { @Override public K getKey() { return entry.getKey(); } @Override public Collection getValue() { return unmodifiableValueCollection(entry.getValue()); } }; } /** * 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 Maps.unmodifiableEntrySet((Set>) entries); } return new Maps.UnmodifiableEntries( Collections.unmodifiableCollection(entries)); } /** * Returns an unmodifiable view of the specified set of {@code asMap} entries. * The {@link Entry#setValue} operation throws an {@link * UnsupportedOperationException}, as do any operations that attempt to modify * the returned collection. * * @param asMapEntries the {@code asMap} entries for which to return an * unmodifiable view * @return an unmodifiable view of the collection entries */ private static Set>> unmodifiableAsMapEntries( Set>> asMapEntries) { return new UnmodifiableAsMapEntries( Collections.unmodifiableSet(asMapEntries)); } /** @see Multimaps#unmodifiableAsMapEntries */ static class UnmodifiableAsMapEntries extends ForwardingSet>> { private final Set>> delegate; UnmodifiableAsMapEntries(Set>> delegate) { this.delegate = delegate; } @Override protected Set>> delegate() { return delegate; } @Override public Iterator>> iterator() { final Iterator>> iterator = delegate.iterator(); return new ForwardingIterator>>() { @Override protected Iterator>> delegate() { return iterator; } @Override public Entry> next() { return unmodifiableAsMapEntry(iterator.next()); } }; } @Override public Object[] toArray() { return ObjectArrays.toArrayImpl(this); } @Override public T[] toArray(T[] array) { return ObjectArrays.toArrayImpl(this, array); } @Override public boolean contains(Object o) { return Maps.containsEntryImpl(delegate(), o); } @Override public boolean containsAll(Collection c) { return Collections2.containsAll(this, c); } @Override public boolean equals(@Nullable Object object) { return Collections2.setEquals(this, object); } } /** * 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 implements SetMultimap, Serializable { final Map map; transient Map> asMap; MapMultimap(Map map) { this.map = checkNotNull(map); } public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean containsKey(Object key) { return map.containsKey(key); } public boolean containsValue(Object value) { return map.containsValue(value); } public boolean containsEntry(Object key, Object value) { return map.entrySet().contains(Maps.immutableEntry(key, value)); } public Set get(final K key) { return new AbstractSet() { @Override public Iterator iterator() { return new Iterator() { int i; public boolean hasNext() { return (i == 0) && map.containsKey(key); } public V next() { if (!hasNext()) { throw new NoSuchElementException(); } i++; return map.get(key); } public void remove() { checkState(i == 1); i = -1; map.remove(key); } }; } @Override public int size() { return map.containsKey(key) ? 1 : 0; } }; } public boolean put(K key, V value) { throw new UnsupportedOperationException(); } public boolean putAll(K key, Iterable values) { throw new UnsupportedOperationException(); } public boolean putAll(Multimap multimap) { throw new UnsupportedOperationException(); } public Set replaceValues(K key, Iterable values) { throw new UnsupportedOperationException(); } public boolean remove(Object key, Object value) { return map.entrySet().remove(Maps.immutableEntry(key, value)); } public Set removeAll(Object key) { Set values = new HashSet(2); if (!map.containsKey(key)) { return values; } values.add(map.remove(key)); return values; } public void clear() { map.clear(); } public Set keySet() { return map.keySet(); } public Multiset keys() { return Multisets.forSet(map.keySet()); } public Collection values() { return map.values(); } public Set> entries() { return map.entrySet(); } public Map> asMap() { Map> result = asMap; if (result == null) { asMap = result = new AsMap(); } return result; } @Override public boolean equals(@Nullable Object object) { if (object == this) { return true; } if (object instanceof Multimap) { Multimap that = (Multimap) object; return this.size() == that.size() && asMap().equals(that.asMap()); } return false; } @Override public int hashCode() { return map.hashCode(); } private static final MapJoiner joiner = Joiner.on("], ").withKeyValueSeparator("=[").useForNull("null"); @Override public String toString() { if (map.isEmpty()) { return "{}"; } StringBuilder builder = new StringBuilder(map.size() * 16).append('{'); joiner.appendTo(builder, map); return builder.append("]}").toString(); } /** @see MapMultimap#asMap */ class AsMapEntries extends AbstractSet>> { @Override public int size() { return map.size(); } @Override public Iterator>> iterator() { return new Iterator>>() { final Iterator keys = map.keySet().iterator(); public boolean hasNext() { return keys.hasNext(); } public Entry> next() { final K key = keys.next(); return new AbstractMapEntry>() { @Override public K getKey() { return key; } @Override public Collection getValue() { return get(key); } }; } public void remove() { keys.remove(); } }; } @Override public boolean contains(Object o) { if (!(o instanceof Entry)) { return false; } Entry entry = (Entry) o; if (!(entry.getValue() instanceof Set)) { return false; } Set set = (Set) entry.getValue(); return (set.size() == 1) && containsEntry(entry.getKey(), set.iterator().next()); } @Override public boolean remove(Object o) { if (!(o instanceof Entry)) { return false; } Entry entry = (Entry) o; if (!(entry.getValue() instanceof Set)) { return false; } Set set = (Set) entry.getValue(); return (set.size() == 1) && map.entrySet().remove( Maps.immutableEntry(entry.getKey(), set.iterator().next())); } } /** @see MapMultimap#asMap */ class AsMap extends Maps.ImprovedAbstractMap> { @Override protected Set>> createEntrySet() { return new AsMapEntries(); } // The following methods are included for performance. @Override public boolean containsKey(Object key) { return map.containsKey(key); } @SuppressWarnings("unchecked") @Override public Collection get(Object key) { Collection collection = MapMultimap.this.get((K) key); return collection.isEmpty() ? null : collection; } @Override public Collection remove(Object key) { Collection collection = removeAll(key); return collection.isEmpty() ? null : collection; } } private static final long serialVersionUID = 7845222491160860175L; } /** * Creates an index {@code ImmutableMultimap} 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, it does not reflect subsequent changes on the input * iterable. * *

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], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}}
* *

The returned multimap is serializable if its keys and values are all * serializable. * * @param values the values to use when constructing the {@code * ImmutableMultimap} * @param keyFunction the function used to produce the key for each value * @return {@code ImmutableMultimap} mapping the result of evaluating the * function {@code keyFunction} on each value in the input collection to * that value * @throws NullPointerException if any of the following cases is true:

    *
  • {@code values} is null *
  • {@code keyFunction} is null *
  • An element in {@code values} is null *
  • {@code keyFunction} returns null for any element of {@code values} *
*/ public static ImmutableListMultimap index( Iterable values, Function keyFunction) { checkNotNull(keyFunction); ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder(); for (V value : values) { Preconditions.checkNotNull(value, values); builder.put(keyFunction.apply(value), value); } return builder.build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy