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 static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Function;
import com.google.common.base.Nullable;
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.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.Map.Entry;

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

  /**
   * Creates an empty {@code HashMultimap} instance.
   *
   * @return a newly-created, initially-empty {@code HashMultimap}
   */
  public static  HashMultimap newHashMultimap() {
    return new HashMultimap();
  }

  /**
   * Creates a {@code HashMultimap} instance initialized with all elements from
   * the supplied {@code Multimap}. If the supplied multimap contains duplicate
   * key-value pairs, those duplicate pairs will only be stored once in the new
   * multimap.
   *
   * @param multimap the multimap whose contents are copied to this multimap.
   * @return a newly-created and initialized {@code HashMultimap}
   */
  public static  HashMultimap newHashMultimap(
      Multimap multimap) {
    return new HashMultimap(multimap);
  }

  /**
   * Creates an empty {@code ArrayListMultimap} instance.
   *
   * @return a newly-created, initially-empty {@code ArrayListMultimap}
   */
  public static  ArrayListMultimap newArrayListMultimap() {
    return new ArrayListMultimap();
  }

  /**
   * Creates an {@code ArrayListMultimap} instance initialized with all elements
   * from the supplied {@code Multimap}.
   *
   * @param multimap the multimap whose contents are copied to this multimap.
   * @return a newly-created and initialized {@code ArrayListMultimap}
   */
  public static  ArrayListMultimap newArrayListMultimap(
      Multimap multimap) {
    return new ArrayListMultimap(multimap);
  }

  /**
   * Creates an empty {@code LinkedHashMultimap} instance.
   *
   * @return a newly-created, initially-empty {@code LinkedHashMultimap}
   */
  public static  LinkedHashMultimap newLinkedHashMultimap() {
    return new LinkedHashMultimap();
  }

  /**
   * Creates a {@code LinkedHashMultimap} instance initialized with all elements
   * from the supplied {@code Multimap}. If the supplied multimap contains
   * duplicate key-value pairs, those duplicate pairs will only be stored once
   * in the new multimap. The new multimap has the same
   * {@link Multimap#entries()} iteration order as the input multimap, except
   * for excluding duplicate mappings.
   *
   * @param multimap the multimap whose contents are copied to this multimap.
   * @return a newly-created and initialized {@code LinkedHashMultimap}
   */
  public static  LinkedHashMultimap newLinkedHashMultimap(
      Multimap multimap) {
    return new LinkedHashMultimap(multimap);
  }

  /**
   * Creates an empty {@code LinkedListMultimap} instance.
   *
   * @return a newly-created, initially-empty {@code LinkedListMultimap}
   */
  public static  LinkedListMultimap newLinkedListMultimap() {
    return new LinkedListMultimap();
  }

  /**
   * Creates a {@code LinkedListMultimap} instance initialized with all elements
   * from the supplied {@code Multimap}. The new multimap has the same
   * {@link Multimap#entries()} iteration order as the input multimap.
   *
   * @param multimap the multimap whose contents are copied to this multimap.
   * @return a newly-created and initialized {@code LinkedListMultimap}
   */
  public static  LinkedListMultimap newLinkedListMultimap(
      Multimap multimap) {
    return new LinkedListMultimap(multimap);
  }

  /**
   * Creates an empty {@code TreeMultimap} instance using the natural ordering
   * of keys and values.
   *
   * @return a newly-created, initially-empty {@code TreeMultimap}
   */
  @SuppressWarnings("unchecked")  // allow ungenerified Comparable types
  public static 
      TreeMultimap newTreeMultimap() {
    return new TreeMultimap();
  }

  /**
   * Constructs a {@code TreeMultimap} with the same mappings as the specified
   * {@code Multimap}.
   *
   * 

If the supplied multimap is an instance of {@code TreeMultimap}, the * supplied multimap's comparators are copied to the new instance. * *

If the supplied multimap is not an instance of {@code TreeMultimap}, the * new multimap is ordered using the natural ordering of the key and value * classes. The key and value classes must satisfy the {@link Comparable} * interface. * * @param multimap the multimap whose contents are copied to this multimap. * @return a newly-created and initialized {@code TreeMultimap} */ public static TreeMultimap newTreeMultimap( Multimap multimap) { return new TreeMultimap(multimap); } /** * Creates an empty {@code TreeMultimap} instance using explicit comparators. * * @param keyComparator the comparator that determines the key ordering. If * it's {@code null}, the natural ordering of the keys is used. * @param valueComparator the comparator that determines the value ordering. * If it's {@code null}, the natural ordering of the values is used. * @return a newly-created, initially-empty {@code TreeMultimap} */ public static TreeMultimap newTreeMultimap( @Nullable Comparator keyComparator, @Nullable Comparator valueComparator) { return new TreeMultimap(keyComparator, valueComparator); } /** * Creates a {@code TreeMultimap} instance using explicit comparators, * initialized with all elements from the supplied {@code Multimap}. If the * supplied multimap contains duplicate key-value pairs, those duplicate * pairs will only be stored once in the new multimap. * * @param multimap the multimap whose contents are copied to this multimap. * @return a newly-created and initialized {@code TreeMultimap} */ public static TreeMultimap newTreeMultimap( @Nullable Comparator keyComparator, @Nullable Comparator valueComparator, Multimap multimap) { return new TreeMultimap(keyComparator, valueComparator, multimap); } /** * 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 #newArrayListMultimap()}, {@link #newHashMultimap()}, * {@link #newLinkedHashMultimap()}, {@link #newLinkedListMultimap()}, and * {@link #newTreeMultimap()} won't suffice. * * @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 StandardMultimap { 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. 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 * 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 #newArrayListMultimap()} and {@link #newLinkedListMultimap()} won't * suffice. * * @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 StandardListMultimap { 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 #newHashMultimap()}, {@link #newLinkedHashMultimap()}, and * {@link #newTreeMultimap()} won't suffice. * * @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 StandardSetMultimap { 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 method {@link #newTreeMultimap()} * won't suffice. * * @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 StandardSortedSetMultimap { 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: * *

  Multimap<K,V> m = Multimaps.synchronizedMultimap(
   *      new HashMultimap<K,V>());
   *   ...
   *  Set<K> s = m.keySet();  // Needn't be in synchronized block
   *   ...
   *  synchronized (m) {  // Synchronizing on m, not s!
   *    Iterator<K> 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 volatile Collection> entries; transient volatile Multiset keys; transient volatile Set keySet; transient volatile Collection values; transient volatile 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() { if (map == null) { final Map> unmodifiableMap = Collections.unmodifiableMap(delegate.asMap()); map = 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 map; } @Override public Collection> entries() { if (entries == null) { entries = unmodifiableEntries(delegate.entries()); } return entries; } @Override public Collection get(K key) { return unmodifiableValueCollection(delegate.get(key)); } @Override public Multiset keys() { if (keys == null) { keys = Multisets.unmodifiableMultiset(delegate.keys()); } return keys; } @Override public Set keySet() { if (keySet == null) { keySet = Collections.unmodifiableSet(delegate.keySet()); } return keySet; } @Override @SuppressWarnings("unused") public boolean put(K key, V value) { throw new UnsupportedOperationException(); } @Override @SuppressWarnings("unused") public boolean putAll(K key, @SuppressWarnings("hiding") Iterable values) { throw new UnsupportedOperationException(); } @Override @SuppressWarnings("unused") public boolean putAll(Multimap multimap) { throw new UnsupportedOperationException(); } @Override @SuppressWarnings("unused") public boolean remove(Object key, Object value) { throw new UnsupportedOperationException(); } @Override @SuppressWarnings("unused") public Collection removeAll(Object key) { throw new UnsupportedOperationException(); } @Override @SuppressWarnings("unused") public Collection replaceValues(K key, @SuppressWarnings("hiding") Iterable values) { throw new UnsupportedOperationException(); } @Override public Collection values() { if (values == null) { values = Collections.unmodifiableCollection(delegate.values()); } return values; } 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 volatile 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() { if (asMap == null) { asMap = new AsMap(); } return asMap; } @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(); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append('{'); for (Entry e : map.entrySet()) { builder.append(e.getKey()).append("=[") .append(e.getValue()).append("], "); } if (builder.length() > 1) { builder.setLength(builder.length() - 2); // delete last comma and space } builder.append('}'); return builder.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 NpeThrowingAbstractMap> { @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 ListMultimap} 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 unmodifiable snapshot, * it does not reflect subsequent changes on the input iterable. * *

For example, * *

   * List<String> badGuys =
   *   Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
   * Function<String, Integer> stringLengthFunction = ...;
   * Multimap<Integer, String> index
   *   = Multimaps.index(badGuys, stringLengthFunction);
   * System.out.println(index); 
* * prints * *
   * {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 ListMultimap} * @param keyFunction the function used to produce the key for each value * @return {@code ListMultimap} mapping the result of evaluating the function * {@code keyFunction} on each value in the input collection to that value */ public static ListMultimap index(Iterable values, Function keyFunction) { ArrayListMultimap index = newArrayListMultimap(); index(values, keyFunction, index); return unmodifiableListMultimap(index); } /** * Indexes the specified values into a {@code Multimap} by applying a * specified function to each item in an {@code Iterable} of values. Each * value will be stored as a value in the specified multimap. The key used to * store that value in the multimap will be the result of calling the function * on that value. Depending on the multimap implementation, duplicate entries * (equal keys and equal values) may be collapsed. * *

For example, * *

   * List<String> badGuys =
   *   Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
   * Function<String, Integer> stringLengthFunction = ...;
   * Multimap<Integer, String> index = Multimaps.newHashMultimap();
   * Multimaps.index(badGuys, stringLengthFunction, index);
   * System.out.println(index); 
* * prints * *
   * {4=[Inky], 5=[Pinky, Clyde], 6=[Blinky]} 
* * The {@link HashMultimap} collapses the duplicate occurrence of * {@code (5, "Pinky")}. * * @param values the values to add to the multimap * @param keyFunction the function used to produce the key for each value * @param multimap the multimap to store the key value pairs */ public static void index(Iterable values, Function keyFunction, Multimap multimap) { checkNotNull(keyFunction); checkNotNull(multimap); for (V value : values) { K key = keyFunction.apply(value); multimap.put(key, value); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy