diff options
Diffstat (limited to 'guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Multimaps.java')
-rw-r--r-- | guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Multimaps.java | 834 |
1 files changed, 702 insertions, 132 deletions
diff --git a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Multimaps.java b/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Multimaps.java index c2d630f..7e8e2f7 100644 --- a/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Multimaps.java +++ b/guava-gwt/src-super/com/google/common/collect/super/com/google/common/collect/Multimaps.java @@ -20,17 +20,21 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import com.google.common.annotations.Beta; 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.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; +import com.google.common.collect.Collections2.TransformedCollection; import com.google.common.collect.Maps.EntryTransformer; import java.io.Serializable; import java.util.AbstractCollection; +import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -48,10 +52,6 @@ import javax.annotation.Nullable; /** * Provides static methods acting on or generating a {@code Multimap}. * - * <p>See the Guava User Guide article on <a href= - * "http://code.google.com/p/guava-libraries/wiki/CollectionUtilitiesExplained#Multimaps"> - * {@code Multimaps}</a>. - * * @author Jared Levy * @author Robert Konigsberg * @author Mike Bostock @@ -63,14 +63,9 @@ public final class Multimaps { private Multimaps() {} /** - * Creates a new {@code Multimap} backed by {@code map}, whose internal value - * collections are generated by {@code factory}. - * - * <b>Warning: do not use</b> 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 Multimap#equals}. + * 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. * * <p>The {@code factory}-generated and {@code map} classes determine the * multimap iteration order. They also specify the behavior of the @@ -110,7 +105,7 @@ public final class Multimaps { return new CustomMultimap<K, V>(map, factory); } - private static class CustomMultimap<K, V> extends AbstractMapBasedMultimap<K, V> { + private static class CustomMultimap<K, V> extends AbstractMultimap<K, V> { transient Supplier<? extends Collection<V>> factory; CustomMultimap(Map<K, Collection<V>> map, @@ -334,13 +329,13 @@ public final class Multimaps { * <p>It is imperative that the user manually synchronize on the returned * multimap when accessing any of its collection views: <pre> {@code * - * Multimap<K, V> multimap = Multimaps.synchronizedMultimap( + * Multimap<K, V> m = Multimaps.synchronizedMultimap( * HashMultimap.<K, V>create()); * ... - * Collection<V> values = multimap.get(key); // Needn't be in synchronized block + * Set<K> s = m.keySet(); // Needn't be in synchronized block * ... - * synchronized (multimap) { // Synchronizing on multimap, not values! - * Iterator<V> i = values.iterator(); // Must 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()); * } @@ -541,7 +536,7 @@ public final class Multimaps { } @Override public Iterator<Collection<V>> iterator() { final Iterator<Collection<V>> iterator = delegate.iterator(); - return new UnmodifiableIterator<Collection<V>>() { + return new Iterator<Collection<V>>() { @Override public boolean hasNext() { return iterator.hasNext(); @@ -550,6 +545,10 @@ public final class Multimaps { public Collection<V> next() { return unmodifiableValueCollection(iterator.next()); } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } }; } @Override public Object[] toArray() { @@ -972,7 +971,7 @@ public final class Multimaps { @Override public Set<V> get(final K key) { - return new Sets.ImprovedAbstractSet<V>() { + return new AbstractSet<V>() { @Override public Iterator<V> iterator() { return new Iterator<V>() { int i; @@ -1053,7 +1052,7 @@ public final class Multimaps { @Override public Multiset<K> keys() { - return new Multimaps.Keys<K, V>(this); + return Multisets.forSet(map.keySet()); } @Override @@ -1104,27 +1103,35 @@ public final class Multimaps { } /** @see MapMultimap#asMap */ - class AsMapEntries extends Sets.ImprovedAbstractSet<Entry<K, Collection<V>>> { + class AsMapEntries extends AbstractSet<Entry<K, Collection<V>>> { @Override public int size() { return map.size(); } @Override public Iterator<Entry<K, Collection<V>>> iterator() { - return new TransformedIterator<K, Entry<K, Collection<V>>>(map.keySet().iterator()) { + return new Iterator<Entry<K, Collection<V>>>() { + final Iterator<K> keys = map.keySet().iterator(); + @Override - Entry<K, Collection<V>> transform(final K key) { + public boolean hasNext() { + return keys.hasNext(); + } + @Override + public Entry<K, Collection<V>> next() { + final K key = keys.next(); return new AbstractMapEntry<K, Collection<V>>() { - @Override - public K getKey() { + @Override public K getKey() { return key; } - - @Override - public Collection<V> getValue() { + @Override public Collection<V> getValue() { return get(key); } }; } + @Override + public void remove() { + keys.remove(); + } }; } @@ -1226,6 +1233,7 @@ public final class Multimaps { * * @since 7.0 */ + @Beta public static <K, V1, V2> Multimap<K, V2> transformValues( Multimap<K, V1> fromMultimap, final Function<? super V1, V2> function) { checkNotNull(function); @@ -1294,29 +1302,15 @@ public final class Multimaps { * * @since 7.0 */ + @Beta public static <K, V1, V2> Multimap<K, V2> transformEntries( Multimap<K, V1> fromMap, EntryTransformer<? super K, ? super V1, V2> transformer) { return new TransformedEntriesMultimap<K, V1, V2>(fromMap, transformer); } - - static final class ValueFunction<K, V1, V2> implements Function<V1, V2> { - private final K key; - private final EntryTransformer<? super K, ? super V1, V2> transformer; - - ValueFunction(K key, EntryTransformer<? super K, ? super V1, V2> transformer) { - this.key = key; - this.transformer = transformer; - } - - @Override - public V2 apply(@Nullable V1 value) { - return transformer.transformEntry(key, value); - } - } private static class TransformedEntriesMultimap<K, V1, V2> - extends AbstractMultimap<K, V2> { + implements Multimap<K, V2> { final Multimap<K, V1> fromMultimap; final EntryTransformer<? super K, ? super V1, V2> transformer; @@ -1326,25 +1320,30 @@ public final class Multimaps { this.transformer = checkNotNull(transformer); } - Collection<V2> transform(K key, Collection<V1> values) { - Function<V1, V2> function = new ValueFunction<K, V1, V2>(key, transformer); - if (values instanceof List) { - return Lists.transform((List<V1>) values, function); - } else { - return Collections2.transform(values, function); - } + Collection<V2> transform(final K key, Collection<V1> values) { + return Collections2.transform(values, new Function<V1, V2>() { + @Override public V2 apply(V1 value) { + return transformer.transformEntry(key, value); + } + }); } - @Override - Map<K, Collection<V2>> createAsMap() { - return Maps.transformEntries(fromMultimap.asMap(), - new EntryTransformer<K, Collection<V1>, Collection<V2>>() { + private transient Map<K, Collection<V2>> asMap; - @Override public Collection<V2> transformEntry( - K key, Collection<V1> value) { - return transform(key, value); - } - }); + @Override public Map<K, Collection<V2>> asMap() { + if (asMap == null) { + Map<K, Collection<V2>> aM = Maps.transformEntries(fromMultimap.asMap(), + new EntryTransformer<K, Collection<V1>, Collection<V2>>() { + + @Override public Collection<V2> transformEntry( + K key, Collection<V1> value) { + return transform(key, value); + } + }); + asMap = aM; + return aM; + } + return asMap; } @Override public void clear() { @@ -1365,25 +1364,58 @@ public final class Multimaps { return values().contains(value); } - @Override - Iterator<Entry<K, V2>> entryIterator() { - return Iterators.transform( - fromMultimap.entries().iterator(), new Function<Entry<K, V1>, Entry<K, V2>>() { - @Override - public Entry<K, V2> apply(final Entry<K, V1> entry) { - return new AbstractMapEntry<K, V2>() { - @Override - public K getKey() { - return entry.getKey(); - } + private transient Collection<Entry<K, V2>> entries; + + @Override public Collection<Entry<K, V2>> entries() { + if (entries == null) { + Collection<Entry<K, V2>> es = new TransformedEntries(transformer); + entries = es; + return es; + } + return entries; + } + + private class TransformedEntries + extends TransformedCollection<Entry<K, V1>, Entry<K, V2>> { + + TransformedEntries( + final EntryTransformer<? super K, ? super V1, V2> transformer) { + super(fromMultimap.entries(), + new Function<Entry<K, V1>, Entry<K, V2>>() { + @Override public Entry<K, V2> apply(final Entry<K, V1> entry) { + return new AbstractMapEntry<K, V2>() { + + @Override public K getKey() { + return entry.getKey(); + } + + @Override public V2 getValue() { + return transformer.transformEntry( + entry.getKey(), entry.getValue()); + } + }; + } + }); + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry<?, ?> entry = (Entry<?, ?>) o; + return containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + @SuppressWarnings("unchecked") + @Override public boolean remove(Object o) { + if (o instanceof Entry) { + Entry<?, ?> entry = (Entry<?, ?>) o; + Collection<V2> values = get((K) entry.getKey()); + return values.remove(entry.getValue()); + } + return false; + } - @Override - public V2 getValue() { - return transformer.transformEntry(entry.getKey(), entry.getValue()); - } - }; - } - }); } @Override public Collection<V2> get(final K key) { @@ -1433,16 +1465,39 @@ public final class Multimaps { @Override public int size() { return fromMultimap.size(); } - - @Override - Collection<V2> createValues() { - return Collections2.transform( - fromMultimap.entries(), new Function<Entry<K, V1>, V2>() { - @Override public V2 apply(Entry<K, V1> entry) { - return transformer.transformEntry( - entry.getKey(), entry.getValue()); - } - }); + + private transient Collection<V2> values; + + @Override public Collection<V2> values() { + if (values == null) { + Collection<V2> vs = Collections2.transform( + fromMultimap.entries(), new Function<Entry<K, V1>, V2>() { + + @Override public V2 apply(Entry<K, V1> entry) { + return transformer.transformEntry( + entry.getKey(), entry.getValue()); + } + }); + values = vs; + return vs; + } + return values; + } + + @Override public boolean equals(Object obj) { + if (obj instanceof Multimap) { + Multimap<?, ?> other = (Multimap<?, ?>) obj; + return asMap().equals(other.asMap()); + } + return false; + } + + @Override public int hashCode() { + return asMap().hashCode(); + } + + @Override public String toString() { + return asMap().toString(); } } @@ -1487,6 +1542,7 @@ public final class Multimaps { * * @since 7.0 */ + @Beta public static <K, V1, V2> ListMultimap<K, V2> transformValues( ListMultimap<K, V1> fromMultimap, final Function<? super V1, V2> function) { @@ -1553,6 +1609,7 @@ public final class Multimaps { * * @since 7.0 */ + @Beta public static <K, V1, V2> ListMultimap<K, V2> transformEntries( ListMultimap<K, V1> fromMap, EntryTransformer<? super K, ? super V1, V2> transformer) { @@ -1639,6 +1696,24 @@ public final class Multimaps { } /** + * <b>Deprecated.</b> + * + * @since 10.0 + * @deprecated use {@link #index(Iterator, Function)} by casting {@code + * values} to {@code Iterator<V>}, or better yet, by implementing only + * {@code Iterator} and not {@code Iterable}. <b>This method is scheduled + * for deletion in March 2012.</b> + */ + @Beta + @Deprecated + public static <K, V, I extends Object & Iterable<V> & Iterator<V>> + ImmutableListMultimap<K, V> index( + I values, Function<? super V, K> keyFunction) { + Iterable<V> valuesIterable = checkNotNull(values); + return index(valuesIterable, 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, @@ -1694,36 +1769,39 @@ public final class Multimaps { return builder.build(); } - static class Keys<K, V> extends AbstractMultiset<K> { - final Multimap<K, V> multimap; - - Keys(Multimap<K, V> multimap) { - this.multimap = multimap; - } + static abstract class Keys<K, V> extends AbstractMultiset<K> { + abstract Multimap<K, V> multimap(); @Override Iterator<Multiset.Entry<K>> entryIterator() { - return new TransformedIterator<Map.Entry<K, Collection<V>>, Multiset.Entry<K>>( - multimap.asMap().entrySet().iterator()) { - @Override - Multiset.Entry<K> transform( - final Map.Entry<K, Collection<V>> backingEntry) { + final Iterator<Map.Entry<K, Collection<V>>> backingIterator = + multimap().asMap().entrySet().iterator(); + return new Iterator<Multiset.Entry<K>>() { + @Override public boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override public Multiset.Entry<K> next() { + final Map.Entry<K, Collection<V>> backingEntry = + backingIterator.next(); return new Multisets.AbstractEntry<K>() { - @Override - public K getElement() { + @Override public K getElement() { return backingEntry.getKey(); } - @Override - public int getCount() { + @Override public int getCount() { return backingEntry.getValue().size(); } }; } + + @Override public void remove() { + backingIterator.remove(); + } }; } @Override int distinctElements() { - return multimap.asMap().size(); + return multimap().asMap().size(); } @Override Set<Multiset.Entry<K>> createEntrySet() { @@ -1744,22 +1822,22 @@ public final class Multimaps { } @Override public boolean isEmpty() { - return multimap.isEmpty(); + return multimap().isEmpty(); } @Override public boolean contains(@Nullable Object o) { - if (o instanceof Multiset.Entry) { + if (o instanceof Multiset.Entry<?>) { Multiset.Entry<?> entry = (Multiset.Entry<?>) o; - Collection<V> collection = multimap.asMap().get(entry.getElement()); + Collection<V> collection = multimap().asMap().get(entry.getElement()); return collection != null && collection.size() == entry.getCount(); } return false; } @Override public boolean remove(@Nullable Object o) { - if (o instanceof Multiset.Entry) { + if (o instanceof Multiset.Entry<?>) { Multiset.Entry<?> entry = (Multiset.Entry<?>) o; - Collection<V> collection = multimap.asMap().get(entry.getElement()); + Collection<V> collection = multimap().asMap().get(entry.getElement()); if (collection != null && collection.size() == entry.getCount()) { collection.clear(); return true; @@ -1770,16 +1848,30 @@ public final class Multimaps { } @Override public boolean contains(@Nullable Object element) { - return multimap.containsKey(element); + return multimap().containsKey(element); } @Override public Iterator<K> iterator() { - return Maps.keyIterator(multimap.entries().iterator()); + return Iterators.transform(multimap().entries().iterator(), + new Function<Map.Entry<K, V>, K>() { + @Override public K apply(Map.Entry<K, V> entry) { + return entry.getKey(); + } + }); } @Override public int count(@Nullable Object element) { - Collection<V> values = Maps.safeGet(multimap.asMap(), element); - return (values == null) ? 0 : values.size(); + try { + if (multimap().containsKey(element)) { + Collection<V> values = multimap().asMap().get(element); + return (values == null) ? 0 : values.size(); + } + return 0; + } catch (ClassCastException e) { + return 0; + } catch (NullPointerException e) { + return 0; + } } @Override public int remove(@Nullable Object element, int occurrences) { @@ -1788,7 +1880,14 @@ public final class Multimaps { return count(element); } - Collection<V> values = Maps.safeGet(multimap.asMap(), element); + Collection<V> values; + try { + values = multimap().asMap().get(element); + } catch (ClassCastException e) { + return 0; + } catch (NullPointerException e) { + return 0; + } if (values == null) { return 0; @@ -1808,35 +1907,45 @@ public final class Multimaps { } @Override public void clear() { - multimap.clear(); + multimap().clear(); } @Override public Set<K> elementSet() { - return multimap.keySet(); + return multimap().keySet(); } } - static class Values<K, V> extends AbstractCollection<V> { - final Multimap<K, V> multimap; - - Values(Multimap<K, V> multimap) { - this.multimap = multimap; - } + static abstract class Values<K, V> extends AbstractCollection<V> { + abstract Multimap<K, V> multimap(); @Override public Iterator<V> iterator() { - return Maps.valueIterator(multimap.entries().iterator()); + final Iterator<Map.Entry<K, V>> backingIterator = + multimap().entries().iterator(); + return new Iterator<V>() { + @Override public boolean hasNext() { + return backingIterator.hasNext(); + } + + @Override public V next() { + return backingIterator.next().getValue(); + } + + @Override public void remove() { + backingIterator.remove(); + } + }; } @Override public int size() { - return multimap.size(); + return multimap().size(); } @Override public boolean contains(@Nullable Object o) { - return multimap.containsValue(o); + return multimap().containsValue(o); } @Override public void clear() { - multimap.clear(); + multimap().clear(); } } @@ -1852,7 +1961,7 @@ public final class Multimaps { } @Override public boolean contains(@Nullable Object o) { - if (o instanceof Map.Entry) { + if (o instanceof Map.Entry<?, ?>) { Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; return multimap().containsEntry(entry.getKey(), entry.getValue()); } @@ -1860,7 +1969,7 @@ public final class Multimaps { } @Override public boolean remove(@Nullable Object o) { - if (o instanceof Map.Entry) { + if (o instanceof Map.Entry<?, ?>) { Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; return multimap().remove(entry.getKey(), entry.getValue()); } @@ -1953,15 +2062,476 @@ public final class Multimaps { /** * 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 + * the FilteredMultimap constructor would lead to a multimap whose removal * operations would fail. This method combines the predicates to avoid that * problem. */ - private static <K, V> Multimap<K, V> filterFiltered(FilteredMultimap<K, V> multimap, + private static <K, V> Multimap<K, V> filterFiltered(FilteredMultimap<K, V> map, Predicate<? super Entry<K, V>> entryPredicate) { Predicate<Entry<K, V>> predicate - = Predicates.and(multimap.entryPredicate(), entryPredicate); - return new FilteredEntryMultimap<K, V>(multimap.unfiltered, predicate); + = Predicates.and(map.predicate, entryPredicate); + return new FilteredMultimap<K, V>(map.unfiltered, predicate); + } + + private static class FilteredMultimap<K, V> implements Multimap<K, V> { + final Multimap<K, V> unfiltered; + final Predicate<? super Entry<K, V>> predicate; + + FilteredMultimap(Multimap<K, V> unfiltered, Predicate<? super Entry<K, V>> predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + @Override public int size() { + return entries().size(); + } + + @Override public boolean isEmpty() { + return entries().isEmpty(); + } + + @Override public boolean containsKey(Object key) { + return asMap().containsKey(key); + } + + @Override public boolean containsValue(Object value) { + return values().contains(value); + } + + // This method should be called only when key is a K and value is a V. + @SuppressWarnings("unchecked") + boolean satisfiesPredicate(Object key, Object value) { + return predicate.apply(Maps.immutableEntry((K) key, (V) value)); + } + + @Override public boolean containsEntry(Object key, Object value) { + return unfiltered.containsEntry(key, value) && satisfiesPredicate(key, value); + } + + @Override public boolean put(K key, V value) { + checkArgument(satisfiesPredicate(key, value)); + return unfiltered.put(key, value); + } + + @Override public boolean remove(Object key, Object value) { + return containsEntry(key, value) ? unfiltered.remove(key, value) : false; + } + + @Override public boolean putAll(K key, Iterable<? extends V> values) { + for (V value : values) { + checkArgument(satisfiesPredicate(key, value)); + } + return unfiltered.putAll(key, values); + } + + @Override public boolean putAll(Multimap<? extends K, ? extends V> multimap) { + for (Entry<? extends K, ? extends V> entry : multimap.entries()) { + checkArgument(satisfiesPredicate(entry.getKey(), entry.getValue())); + } + return unfiltered.putAll(multimap); + } + + @Override public Collection<V> replaceValues(K key, Iterable<? extends V> values) { + for (V value : values) { + checkArgument(satisfiesPredicate(key, value)); + } + // Not calling unfiltered.replaceValues() since values that don't satisify + // the filter should remain in the multimap. + Collection<V> oldValues = removeAll(key); + unfiltered.putAll(key, values); + return oldValues; + } + + @Override public Collection<V> removeAll(Object key) { + List<V> removed = Lists.newArrayList(); + Collection<V> values = unfiltered.asMap().get(key); + if (values != null) { + Iterator<V> iterator = values.iterator(); + while (iterator.hasNext()) { + V value = iterator.next(); + if (satisfiesPredicate(key, value)) { + removed.add(value); + iterator.remove(); + } + } + } + if (unfiltered instanceof SetMultimap) { + return Collections.unmodifiableSet(Sets.newLinkedHashSet(removed)); + } else { + return Collections.unmodifiableList(removed); + } + } + + @Override public void clear() { + entries().clear(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multimap) { + Multimap<?, ?> that = (Multimap<?, ?>) object; + return asMap().equals(that.asMap()); + } + return false; + } + + @Override public int hashCode() { + return asMap().hashCode(); + } + + @Override public String toString() { + return asMap().toString(); + } + + class ValuePredicate implements Predicate<V> { + final K key; + ValuePredicate(K key) { + this.key = key; + } + @Override public boolean apply(V value) { + return satisfiesPredicate(key, value); + } + } + + Collection<V> filterCollection(Collection<V> collection, Predicate<V> predicate) { + if (collection instanceof Set) { + return Sets.filter((Set<V>) collection, predicate); + } else { + return Collections2.filter(collection, predicate); + } + } + + @Override public Collection<V> get(K key) { + return filterCollection(unfiltered.get(key), new ValuePredicate(key)); + } + + @Override public Set<K> keySet() { + return asMap().keySet(); + } + + Collection<V> values; + + @Override public Collection<V> values() { + return (values == null) ? values = new Values() : values; + } + + class Values extends Multimaps.Values<K, V> { + @Override Multimap<K, V> multimap() { + return FilteredMultimap.this; + } + + @Override public boolean contains(@Nullable Object o) { + return Iterators.contains(iterator(), o); + } + + // Override remove methods since iterator doesn't support remove. + + @Override public boolean remove(Object o) { + Iterator<Entry<K, V>> iterator = unfiltered.entries().iterator(); + while (iterator.hasNext()) { + Entry<K, V> entry = iterator.next(); + if (Objects.equal(o, entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection<?> c) { + boolean changed = false; + Iterator<Entry<K, V>> iterator = unfiltered.entries().iterator(); + while (iterator.hasNext()) { + Entry<K, V> entry = iterator.next(); + if (c.contains(entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public boolean retainAll(Collection<?> c) { + boolean changed = false; + Iterator<Entry<K, V>> iterator = unfiltered.entries().iterator(); + while (iterator.hasNext()) { + Entry<K, V> entry = iterator.next(); + if (!c.contains(entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + } + + Collection<Entry<K, V>> entries; + + @Override public Collection<Entry<K, V>> entries() { + return (entries == null) + ? entries = Collections2.filter(unfiltered.entries(), predicate) + : entries; + } + + /** + * Remove all filtered asMap() entries that satisfy the predicate. + */ + boolean removeEntriesIf(Predicate<Map.Entry<K, Collection<V>>> removalPredicate) { + Iterator<Map.Entry<K, Collection<V>>> iterator = unfiltered.asMap().entrySet().iterator(); + boolean changed = false; + while (iterator.hasNext()) { + // Determine whether to remove the filtered values with this key. + Map.Entry<K, Collection<V>> entry = iterator.next(); + K key = entry.getKey(); + Collection<V> collection = entry.getValue(); + Predicate<V> valuePredicate = new ValuePredicate(key); + Collection<V> filteredCollection = filterCollection(collection, valuePredicate); + Map.Entry<K, Collection<V>> filteredEntry = Maps.immutableEntry(key, filteredCollection); + if (removalPredicate.apply(filteredEntry) && !filteredCollection.isEmpty()) { + changed = true; + if (Iterables.all(collection, valuePredicate)) { + iterator.remove(); // Remove all values for the key. + } else { + filteredCollection.clear(); // Remove the filtered values only. + } + } + } + return changed; + } + + Map<K, Collection<V>> asMap; + + @Override public Map<K, Collection<V>> asMap() { + return (asMap == null) ? asMap = createAsMap() : asMap; + } + + static final Predicate<Collection<?>> NOT_EMPTY = new Predicate<Collection<?>>() { + @Override public boolean apply(Collection<?> input) { + return !input.isEmpty(); + } + }; + + Map<K, Collection<V>> createAsMap() { + // Select the values that satisify the predicate. + EntryTransformer<K, Collection<V>, Collection<V>> transformer + = new EntryTransformer<K, Collection<V>, Collection<V>>() { + @Override public Collection<V> transformEntry(K key, Collection<V> collection) { + return filterCollection(collection, new ValuePredicate(key)); + } + }; + Map<K, Collection<V>> transformed + = Maps.transformEntries(unfiltered.asMap(), transformer); + + // Select the keys that have at least one value remaining. + Map<K, Collection<V>> filtered = Maps.filterValues(transformed, NOT_EMPTY); + + // Override the removal methods, since removing a map entry should not + // affect values that don't satisfy the filter. + return new AsMap(filtered); + } + + class AsMap extends ForwardingMap<K, Collection<V>> { + final Map<K, Collection<V>> delegate; + + AsMap(Map<K, Collection<V>> delegate) { + this.delegate = delegate; + } + + @Override protected Map<K, Collection<V>> delegate() { + return delegate; + } + + @Override public Collection<V> remove(Object o) { + Collection<V> output = FilteredMultimap.this.removeAll(o); + return output.isEmpty() ? null : output; + } + + @Override public void clear() { + FilteredMultimap.this.clear(); + } + + Set<K> keySet; + + @Override public Set<K> keySet() { + return (keySet == null) ? keySet = new KeySet() : keySet; + } + + class KeySet extends Maps.KeySet<K, Collection<V>> { + @Override Map<K, Collection<V>> map() { + return AsMap.this; + } + + @Override public boolean remove(Object o) { + Collection<V> collection = delegate.get(o); + if (collection == null) { + return false; + } + collection.clear(); + return true; + } + + @Override public boolean removeAll(Collection<?> c) { + return Sets.removeAllImpl(this, c); + } + + @Override public boolean retainAll(final Collection<?> c) { + Predicate<Map.Entry<K, Collection<V>>> removalPredicate + = new Predicate<Map.Entry<K, Collection<V>>>() { + @Override public boolean apply(Map.Entry<K, Collection<V>> entry) { + return !c.contains(entry.getKey()); + } + }; + return removeEntriesIf(removalPredicate); + } + } + + Values asMapValues; + + @Override public Collection<Collection<V>> values() { + return (asMapValues == null) ? asMapValues = new Values() : asMapValues; + } + + class Values extends Maps.Values<K, Collection<V>> { + @Override Map<K, Collection<V>> map() { + return AsMap.this; + } + + @Override public boolean remove(Object o) { + for (Collection<V> collection : this) { + if (collection.equals(o)) { + collection.clear(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(final Collection<?> c) { + Predicate<Map.Entry<K, Collection<V>>> removalPredicate + = new Predicate<Map.Entry<K, Collection<V>>>() { + @Override public boolean apply(Map.Entry<K, Collection<V>> entry) { + return c.contains(entry.getValue()); + } + }; + return removeEntriesIf(removalPredicate); + } + + @Override public boolean retainAll(final Collection<?> c) { + Predicate<Map.Entry<K, Collection<V>>> removalPredicate + = new Predicate<Map.Entry<K, Collection<V>>>() { + @Override public boolean apply(Map.Entry<K, Collection<V>> entry) { + return !c.contains(entry.getValue()); + } + }; + return removeEntriesIf(removalPredicate); + } + } + + EntrySet entrySet; + + @Override public Set<Map.Entry<K, Collection<V>>> entrySet() { + return (entrySet == null) ? entrySet = new EntrySet(super.entrySet()) : entrySet; + } + + class EntrySet extends Maps.EntrySet<K, Collection<V>> { + Set<Map.Entry<K, Collection<V>>> delegateEntries; + + public EntrySet(Set<Map.Entry<K, Collection<V>>> delegateEntries) { + this.delegateEntries = delegateEntries; + } + + @Override Map<K, Collection<V>> map() { + return AsMap.this; + } + + @Override public Iterator<Map.Entry<K, Collection<V>>> iterator() { + return delegateEntries.iterator(); + } + + @Override public boolean remove(Object o) { + if (o instanceof Entry<?, ?>) { + Entry<?, ?> entry = (Entry<?, ?>) o; + Collection<V> collection = delegate.get(entry.getKey()); + if (collection != null && collection.equals(entry.getValue())) { + collection.clear(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection<?> c) { + return Sets.removeAllImpl(this, c); + } + + @Override public boolean retainAll(final Collection<?> c) { + Predicate<Map.Entry<K, Collection<V>>> removalPredicate + = new Predicate<Map.Entry<K, Collection<V>>>() { + @Override public boolean apply(Map.Entry<K, Collection<V>> entry) { + return !c.contains(entry); + } + }; + return removeEntriesIf(removalPredicate); + } + } + } + + AbstractMultiset<K> keys; + + @Override public Multiset<K> keys() { + return (keys == null) ? keys = new Keys() : keys; + } + + class Keys extends Multimaps.Keys<K, V> { + @Override Multimap<K, V> multimap() { + return FilteredMultimap.this; + } + + @Override public int remove(Object o, int occurrences) { + checkArgument(occurrences >= 0); + Collection<V> values = unfiltered.asMap().get(o); + if (values == null) { + return 0; + } + int priorCount = 0; + int removed = 0; + Iterator<V> iterator = values.iterator(); + while (iterator.hasNext()) { + if (satisfiesPredicate(o, iterator.next())) { + priorCount++; + if (removed < occurrences) { + iterator.remove(); + removed++; + } + } + } + return priorCount; + } + + @Override Set<Multiset.Entry<K>> createEntrySet() { + return new EntrySet(); + } + + class EntrySet extends Multimaps.Keys<K, V>.KeysEntrySet { + @Override public boolean removeAll(Collection<?> c) { + return Sets.removeAllImpl(this, c); + } + + @Override public boolean retainAll(final Collection<?> c) { + Predicate<Map.Entry<K, Collection<V>>> removalPredicate + = new Predicate<Map.Entry<K, Collection<V>>>() { + @Override public boolean apply(Map.Entry<K, Collection<V>> entry) { + Multiset.Entry<K> multisetEntry + = Multisets.immutableEntry(entry.getKey(), entry.getValue().size()); + return !c.contains(multisetEntry); + } + }; + return removeEntriesIf(removalPredicate); + } + } + } } // TODO(jlevy): Create methods that filter a SetMultimap or SortedSetMultimap. |