com.google.common.collect.FilteredEntryMultimap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of guava Show documentation
Show all versions of guava Show documentation
Guava library, with additional type annotations for verification
The newest version!
/*
* Copyright (C) 2012 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.collect;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.CollectPreconditions.checkNonnegative;
import com.google.common.annotations.GwtCompatible;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps.ViewCachingAbstractMap;
import com.google.j2objc.annotations.WeakOuter;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signedness.qual.UnknownSignedness;
/**
* Implementation of {@link Multimaps#filterEntries(Multimap, Predicate)}.
*
* @author Jared Levy
* @author Louis Wasserman
*/
@GwtCompatible
@ElementTypesAreNonnullByDefault
class FilteredEntryMultimap
extends AbstractMultimap implements FilteredMultimap {
final Multimap unfiltered;
final Predicate> predicate;
FilteredEntryMultimap(Multimap unfiltered, Predicate> predicate) {
this.unfiltered = checkNotNull(unfiltered);
this.predicate = checkNotNull(predicate);
}
@Override
public Multimap unfiltered() {
return unfiltered;
}
@Override
public Predicate> entryPredicate() {
return predicate;
}
@Override
public int size() {
return entries().size();
}
private boolean satisfies(@ParametricNullness K key, @ParametricNullness V value) {
return predicate.apply(Maps.immutableEntry(key, value));
}
final class ValuePredicate implements Predicate {
@ParametricNullness private final K key;
ValuePredicate(@ParametricNullness K key) {
this.key = key;
}
@Override
public boolean apply(@ParametricNullness V value) {
return satisfies(key, value);
}
}
static Collection filterCollection(
Collection collection, Predicate predicate) {
if (collection instanceof Set) {
return Sets.filter((Set) collection, predicate);
} else {
return Collections2.filter(collection, predicate);
}
}
@Override
public boolean containsKey(@CheckForNull @UnknownSignedness Object key) {
return asMap().get(key) != null;
}
@Override
public Collection removeAll(@CheckForNull Object key) {
return MoreObjects.firstNonNull(asMap().remove(key), unmodifiableEmptyCollection());
}
Collection unmodifiableEmptyCollection() {
// These return false, rather than throwing a UOE, on remove calls.
return (unfiltered instanceof SetMultimap)
? Collections.emptySet()
: Collections.emptyList();
}
@Override
public void clear() {
entries().clear();
}
@Override
public Collection get(@ParametricNullness K key) {
return filterCollection(unfiltered.get(key), new ValuePredicate(key));
}
@Override
Collection> createEntries() {
return filterCollection(unfiltered.entries(), predicate);
}
@Override
Collection createValues() {
return new FilteredMultimapValues<>(this);
}
@Override
Iterator> entryIterator() {
throw new AssertionError("should never be called");
}
@Override
Map> createAsMap() {
return new AsMap();
}
@Override
Set createKeySet() {
return asMap().keySet();
}
boolean removeEntriesIf(Predicate>> predicate) {
Iterator>> entryIterator = unfiltered.asMap().entrySet().iterator();
boolean changed = false;
while (entryIterator.hasNext()) {
Entry> entry = entryIterator.next();
K key = entry.getKey();
Collection collection = filterCollection(entry.getValue(), new ValuePredicate(key));
if (!collection.isEmpty() && predicate.apply(Maps.immutableEntry(key, collection))) {
if (collection.size() == entry.getValue().size()) {
entryIterator.remove();
} else {
collection.clear();
}
changed = true;
}
}
return changed;
}
@WeakOuter
class AsMap extends ViewCachingAbstractMap> {
@Override
public boolean containsKey(@CheckForNull @UnknownSignedness Object key) {
return get(key) != null;
}
@Override
public void clear() {
FilteredEntryMultimap.this.clear();
}
@Override
@CheckForNull
public Collection get(@CheckForNull @UnknownSignedness Object key) {
Collection result = unfiltered.asMap().get(key);
if (result == null) {
return null;
}
@SuppressWarnings("unchecked") // key is equal to a K, if not a K itself
K k = (K) key;
result = filterCollection(result, new ValuePredicate(k));
return result.isEmpty() ? null : result;
}
@Override
@CheckForNull
public Collection remove(@CheckForNull @UnknownSignedness Object key) {
Collection collection = unfiltered.asMap().get(key);
if (collection == null) {
return null;
}
@SuppressWarnings("unchecked") // it's definitely equal to a K
K k = (K) key;
List result = Lists.newArrayList();
Iterator itr = collection.iterator();
while (itr.hasNext()) {
V v = itr.next();
if (satisfies(k, v)) {
itr.remove();
result.add(v);
}
}
if (result.isEmpty()) {
return null;
} else if (unfiltered instanceof SetMultimap) {
return Collections.unmodifiableSet(Sets.newLinkedHashSet(result));
} else {
return Collections.unmodifiableList(result);
}
}
@Override
Set createKeySet() {
@WeakOuter
class KeySetImpl extends Maps.KeySet> {
KeySetImpl() {
super(AsMap.this);
}
@Override
public boolean removeAll(Collection c) {
return removeEntriesIf(Maps.keyPredicateOnEntries(in(c)));
}
@Override
public boolean retainAll(Collection c) {
return removeEntriesIf(Maps.keyPredicateOnEntries(not(in(c))));
}
@Override
public boolean remove(@CheckForNull @UnknownSignedness Object o) {
return AsMap.this.remove(o) != null;
}
}
return new KeySetImpl();
}
@Override
Set>> createEntrySet() {
@WeakOuter
class EntrySetImpl extends Maps.EntrySet> {
@Override
Map> map() {
return AsMap.this;
}
@Override
public Iterator>> iterator() {
return new AbstractIterator>>() {
final Iterator>> backingIterator =
unfiltered.asMap().entrySet().iterator();
@Override
@CheckForNull
protected Entry> computeNext() {
while (backingIterator.hasNext()) {
Entry> entry = backingIterator.next();
K key = entry.getKey();
Collection collection =
filterCollection(entry.getValue(), new ValuePredicate(key));
if (!collection.isEmpty()) {
return Maps.immutableEntry(key, collection);
}
}
return endOfData();
}
};
}
@Override
public boolean removeAll(Collection c) {
return removeEntriesIf(in(c));
}
@Override
public boolean retainAll(Collection c) {
return removeEntriesIf(not(in(c)));
}
@Override
public @NonNegative int size() {
return Iterators.size(iterator());
}
}
return new EntrySetImpl();
}
@Override
Collection> createValues() {
@WeakOuter
class ValuesImpl extends Maps.Values> {
ValuesImpl() {
super(AsMap.this);
}
@Override
public boolean remove(@CheckForNull @UnknownSignedness Object o) {
if (o instanceof Collection) {
Collection c = (Collection) o;
Iterator>> entryIterator =
unfiltered.asMap().entrySet().iterator();
while (entryIterator.hasNext()) {
Entry> entry = entryIterator.next();
K key = entry.getKey();
Collection collection =
filterCollection(entry.getValue(), new ValuePredicate(key));
if (!collection.isEmpty() && c.equals(collection)) {
if (collection.size() == entry.getValue().size()) {
entryIterator.remove();
} else {
collection.clear();
}
return true;
}
}
}
return false;
}
@Override
public boolean removeAll(Collection c) {
return removeEntriesIf(Maps.>valuePredicateOnEntries(in(c)));
}
@Override
public boolean retainAll(Collection c) {
return removeEntriesIf(Maps.>valuePredicateOnEntries(not(in(c))));
}
}
return new ValuesImpl();
}
}
@Override
Multiset createKeys() {
return new Keys();
}
@WeakOuter
class Keys extends Multimaps.Keys {
Keys() {
super(FilteredEntryMultimap.this);
}
@Override
public int remove(@CheckForNull Object key, int occurrences) {
checkNonnegative(occurrences, "occurrences");
if (occurrences == 0) {
return count(key);
}
Collection collection = unfiltered.asMap().get(key);
if (collection == null) {
return 0;
}
@SuppressWarnings("unchecked") // key is equal to a K, if not a K itself
K k = (K) key;
int oldCount = 0;
Iterator itr = collection.iterator();
while (itr.hasNext()) {
V v = itr.next();
if (satisfies(k, v)) {
oldCount++;
if (oldCount <= occurrences) {
itr.remove();
}
}
}
return oldCount;
}
@Override
public Set> entrySet() {
return new Multisets.EntrySet() {
@Override
Multiset multiset() {
return Keys.this;
}
@Override
public Iterator> iterator() {
return Keys.this.entryIterator();
}
@Override
public @NonNegative int size() {
return FilteredEntryMultimap.this.keySet().size();
}
private boolean removeEntriesIf(Predicate> predicate) {
return FilteredEntryMultimap.this.removeEntriesIf(
(Map.Entry> entry) ->
predicate.apply(
Multisets.immutableEntry(entry.getKey(), entry.getValue().size())));
}
@Override
public boolean removeAll(Collection c) {
return removeEntriesIf(in(c));
}
@Override
public boolean retainAll(Collection c) {
return removeEntriesIf(not(in(c)));
}
};
}
}
}