View Javadoc
1   /*
2    * Copyright (C) 2012 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  import static com.google.common.base.Predicates.in;
21  import static com.google.common.base.Predicates.not;
22  import static com.google.common.collect.CollectPreconditions.checkNonnegative;
23  
24  import com.google.common.annotations.GwtCompatible;
25  import com.google.common.base.Objects;
26  import com.google.common.base.Predicate;
27  import com.google.common.collect.Maps.ImprovedAbstractMap;
28  
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Map.Entry;
35  import java.util.Set;
36  
37  import javax.annotation.Nullable;
38  
39  /**
40   * Implementation of {@link Multimaps#filterEntries(Multimap, Predicate)}.
41   * 
42   * @author Jared Levy
43   * @author Louis Wasserman
44   */
45  @GwtCompatible
46  class FilteredEntryMultimap<K, V> extends AbstractMultimap<K, V> implements FilteredMultimap<K, V> {
47    final Multimap<K, V> unfiltered;
48    final Predicate<? super Entry<K, V>> predicate;
49  
50    FilteredEntryMultimap(Multimap<K, V> unfiltered, Predicate<? super Entry<K, V>> predicate) {
51      this.unfiltered = checkNotNull(unfiltered);
52      this.predicate = checkNotNull(predicate);
53    }
54    
55    @Override
56    public Multimap<K, V> unfiltered() {
57      return unfiltered;
58    }
59  
60    @Override
61    public Predicate<? super Entry<K, V>> entryPredicate() {
62      return predicate;
63    }
64  
65    @Override
66    public int size() {
67      return entries().size();
68    }
69  
70    private boolean satisfies(K key, V value) {
71      return predicate.apply(Maps.immutableEntry(key, value));
72    }
73    
74  
75    final class ValuePredicate implements Predicate<V> {
76      private final K key;
77  
78      ValuePredicate(K key) {
79        this.key = key;
80      }
81  
82      @Override
83      public boolean apply(@Nullable V value) {
84        return satisfies(key, value);
85      }
86    }
87  
88    static <E> Collection<E> filterCollection(
89        Collection<E> collection, Predicate<? super E> predicate) {
90      if (collection instanceof Set) {
91        return Sets.filter((Set<E>) collection, predicate);
92      } else {
93        return Collections2.filter(collection, predicate);
94      }
95    }
96  
97    @Override
98    public boolean containsKey(@Nullable Object key) {
99      return asMap().get(key) != null;
100   }
101 
102   @Override
103   public Collection<V> removeAll(@Nullable Object key) {
104     return Objects.firstNonNull(asMap().remove(key), unmodifiableEmptyCollection());
105   }
106 
107   Collection<V> unmodifiableEmptyCollection() {
108     // These return false, rather than throwing a UOE, on remove calls.
109     return (unfiltered instanceof SetMultimap) 
110         ? Collections.<V>emptySet() 
111         : Collections.<V>emptyList();
112   }
113 
114   @Override
115   public void clear() {
116     entries().clear();
117   }
118 
119   @Override
120   public Collection<V> get(final K key) {
121     return filterCollection(unfiltered.get(key), new ValuePredicate(key));
122   }
123 
124   @Override
125   Collection<Entry<K, V>> createEntries() {
126     return filterCollection(unfiltered.entries(), predicate);
127   }
128   
129   @Override
130   Collection<V> createValues() {
131     return new FilteredMultimapValues<K, V>(this);
132   }
133 
134   @Override
135   Iterator<Entry<K, V>> entryIterator() {
136     throw new AssertionError("should never be called");
137   }
138 
139   @Override
140   Map<K, Collection<V>> createAsMap() {
141     return new AsMap();
142   }
143   
144   @Override
145   public Set<K> keySet() {
146     return asMap().keySet();
147   }
148   
149   boolean removeEntriesIf(Predicate<? super Entry<K, Collection<V>>> predicate) {
150     Iterator<Entry<K, Collection<V>>> entryIterator = unfiltered.asMap().entrySet().iterator();
151     boolean changed = false;
152     while (entryIterator.hasNext()) {
153       Entry<K, Collection<V>> entry = entryIterator.next();
154       K key = entry.getKey();
155       Collection<V> collection = filterCollection(entry.getValue(), new ValuePredicate(key));
156       if (!collection.isEmpty() && predicate.apply(Maps.immutableEntry(key, collection))) {
157         if (collection.size() == entry.getValue().size()) {
158           entryIterator.remove();
159         } else {
160           collection.clear();
161         }
162         changed = true;
163       }
164     }
165     return changed;
166   }
167   
168   class AsMap extends ImprovedAbstractMap<K, Collection<V>> {
169     @Override
170     public boolean containsKey(@Nullable Object key) {
171       return get(key) != null;
172     }
173 
174     @Override
175     public void clear() {
176       FilteredEntryMultimap.this.clear();
177     }
178 
179     @Override
180     public Collection<V> get(@Nullable Object key) {
181       Collection<V> result = unfiltered.asMap().get(key);
182       if (result == null) {
183         return null;
184       }
185       @SuppressWarnings("unchecked") // key is equal to a K, if not a K itself
186       K k = (K) key;
187       result = filterCollection(result, new ValuePredicate(k));
188       return result.isEmpty() ? null : result;
189     }
190     
191     @Override
192     public Collection<V> remove(@Nullable Object key) {
193       Collection<V> collection = unfiltered.asMap().get(key);
194       if (collection == null) {
195         return null;
196       }
197       @SuppressWarnings("unchecked") // it's definitely equal to a K
198       K k = (K) key;
199       List<V> result = Lists.newArrayList();
200       Iterator<V> itr = collection.iterator();
201       while (itr.hasNext()) {
202         V v = itr.next();
203         if (satisfies(k, v)) {
204           itr.remove();
205           result.add(v);
206         }
207       }
208       if (result.isEmpty()) {
209         return null;
210       } else if (unfiltered instanceof SetMultimap) {
211         return Collections.unmodifiableSet(Sets.newLinkedHashSet(result));
212       } else {
213         return Collections.unmodifiableList(result);
214       }
215     }
216     
217     @Override
218     Set<K> createKeySet() {
219       return new Maps.KeySet<K, Collection<V>>(this) {
220         @Override
221         public boolean removeAll(Collection<?> c) {
222           return removeEntriesIf(Maps.<K>keyPredicateOnEntries(in(c)));
223         }
224 
225         @Override
226         public boolean retainAll(Collection<?> c) {
227           return removeEntriesIf(Maps.<K>keyPredicateOnEntries(not(in(c))));
228         }
229 
230         @Override
231         public boolean remove(@Nullable Object o) {
232           return AsMap.this.remove(o) != null;
233         }
234       };
235     }
236 
237     @Override
238     Set<Entry<K, Collection<V>>> createEntrySet() {
239       return new Maps.EntrySet<K, Collection<V>>() {
240         @Override
241         Map<K, Collection<V>> map() {
242           return AsMap.this;
243         }
244 
245         @Override
246         public Iterator<Entry<K, Collection<V>>> iterator() {
247           return new AbstractIterator<Entry<K, Collection<V>>>() {
248             final Iterator<Entry<K, Collection<V>>> backingIterator 
249                 = unfiltered.asMap().entrySet().iterator();
250 
251             @Override
252             protected Entry<K, Collection<V>> computeNext() {
253               while (backingIterator.hasNext()) {
254                 Entry<K, Collection<V>> entry = backingIterator.next();
255                 K key = entry.getKey();
256                 Collection<V> collection 
257                     = filterCollection(entry.getValue(), new ValuePredicate(key));
258                 if (!collection.isEmpty()) {
259                   return Maps.immutableEntry(key, collection);
260                 }
261               }
262               return endOfData();
263             }
264           };
265         }
266 
267         @Override
268         public boolean removeAll(Collection<?> c) {
269           return removeEntriesIf(in(c));
270         }
271 
272         @Override
273         public boolean retainAll(Collection<?> c) {
274           return removeEntriesIf(not(in(c)));
275         }
276         
277         @Override
278         public int size() {
279           return Iterators.size(iterator());
280         }
281       };
282     }
283     
284     @Override
285     Collection<Collection<V>> createValues() {
286       return new Maps.Values<K, Collection<V>>(AsMap.this) {
287         @Override
288         public boolean remove(@Nullable Object o) {
289           if (o instanceof Collection) {
290             Collection<?> c = (Collection<?>) o;
291             Iterator<Entry<K, Collection<V>>> entryIterator 
292                 = unfiltered.asMap().entrySet().iterator();
293             while (entryIterator.hasNext()) {
294               Entry<K, Collection<V>> entry = entryIterator.next();
295               K key = entry.getKey();
296               Collection<V> collection 
297                   = filterCollection(entry.getValue(), new ValuePredicate(key));
298               if (!collection.isEmpty() && c.equals(collection)) {
299                 if (collection.size() == entry.getValue().size()) {
300                   entryIterator.remove();
301                 } else {
302                   collection.clear();
303                 }
304                 return true;
305               }
306             }
307           }
308           return false;
309         }
310 
311         @Override
312         public boolean removeAll(Collection<?> c) {
313           return removeEntriesIf(Maps.<Collection<V>>valuePredicateOnEntries(in(c)));
314         }
315 
316         @Override
317         public boolean retainAll(Collection<?> c) {
318           return removeEntriesIf(Maps.<Collection<V>>valuePredicateOnEntries(not(in(c))));
319         }
320       };
321     }
322   }
323   
324   @Override
325   Multiset<K> createKeys() {
326     return new Keys();
327   }
328   
329   class Keys extends Multimaps.Keys<K, V> {
330     Keys() {
331       super(FilteredEntryMultimap.this);
332     }
333 
334     @Override
335     public int remove(@Nullable Object key, int occurrences) {
336       checkNonnegative(occurrences, "occurrences");
337       if (occurrences == 0) {
338         return count(key);
339       }
340       Collection<V> collection = unfiltered.asMap().get(key);
341       if (collection == null) {
342         return 0;
343       }
344       @SuppressWarnings("unchecked") // key is equal to a K, if not a K itself
345       K k = (K) key;
346       int oldCount = 0;
347       Iterator<V> itr = collection.iterator();
348       while (itr.hasNext()) {
349         V v = itr.next();
350         if (satisfies(k, v)) {
351           oldCount++;
352           if (oldCount <= occurrences) {
353             itr.remove();
354           }
355         }
356       }
357       return oldCount;
358     }
359 
360     @Override
361     public Set<Multiset.Entry<K>> entrySet() {
362       return new Multisets.EntrySet<K>() {
363 
364         @Override
365         Multiset<K> multiset() {
366           return Keys.this;
367         }
368 
369         @Override
370         public Iterator<Multiset.Entry<K>> iterator() {
371           return Keys.this.entryIterator();
372         }
373 
374         @Override
375         public int size() {
376           return FilteredEntryMultimap.this.keySet().size();
377         }
378         
379         private boolean removeEntriesIf(final Predicate<? super Multiset.Entry<K>> predicate) {
380           return FilteredEntryMultimap.this.removeEntriesIf(
381               new Predicate<Map.Entry<K, Collection<V>>>() {
382                 @Override
383                 public boolean apply(Map.Entry<K, Collection<V>> entry) {
384                   return predicate.apply(
385                       Multisets.immutableEntry(entry.getKey(), entry.getValue().size()));
386                 }
387               });
388         }
389         
390         @Override
391         public boolean removeAll(Collection<?> c) {
392           return removeEntriesIf(in(c));
393         }
394         
395         @Override
396         public boolean retainAll(Collection<?> c) {
397           return removeEntriesIf(not(in(c)));
398         }
399       };
400     }
401   }
402 }