1/*
2 * Copyright (C) 2012 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package com.google.common.collect;
16
17import static com.google.common.base.Preconditions.checkNotNull;
18import static com.google.common.base.Preconditions.checkPositionIndex;
19
20import com.google.common.annotations.GwtCompatible;
21import com.google.common.base.Predicate;
22
23import java.util.Collection;
24import java.util.Collections;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Map;
28import java.util.Map.Entry;
29import java.util.Set;
30
31import javax.annotation.Nullable;
32
33/**
34 * Implementation of {@link Multimaps#filterKeys(Multimap, Predicate)}.
35 *
36 * @author Louis Wasserman
37 */
38@GwtCompatible
39class FilteredKeyMultimap<K, V> extends AbstractMultimap<K, V> implements FilteredMultimap<K, V> {
40  final Multimap<K, V> unfiltered;
41  final Predicate<? super K> keyPredicate;
42
43  FilteredKeyMultimap(Multimap<K, V> unfiltered, Predicate<? super K> keyPredicate) {
44    this.unfiltered = checkNotNull(unfiltered);
45    this.keyPredicate = checkNotNull(keyPredicate);
46  }
47
48  @Override
49  public Multimap<K, V> unfiltered() {
50    return unfiltered;
51  }
52
53  @Override
54  public Predicate<? super Entry<K, V>> entryPredicate() {
55    return Maps.keyPredicateOnEntries(keyPredicate);
56  }
57
58  @Override
59  public int size() {
60    int size = 0;
61    for (Collection<V> collection : asMap().values()) {
62      size += collection.size();
63    }
64    return size;
65  }
66
67  @Override
68  public boolean containsKey(@Nullable Object key) {
69    if (unfiltered.containsKey(key)) {
70      @SuppressWarnings("unchecked") // k is equal to a K, if not one itself
71      K k = (K) key;
72      return keyPredicate.apply(k);
73    }
74    return false;
75  }
76
77  @Override
78  public Collection<V> removeAll(Object key) {
79    return containsKey(key) ? unfiltered.removeAll(key) : unmodifiableEmptyCollection();
80  }
81
82  Collection<V> unmodifiableEmptyCollection() {
83    if (unfiltered instanceof SetMultimap) {
84      return ImmutableSet.of();
85    } else {
86      return ImmutableList.of();
87    }
88  }
89
90  @Override
91  public void clear() {
92    keySet().clear();
93  }
94
95  @Override
96  Set<K> createKeySet() {
97    return Sets.filter(unfiltered.keySet(), keyPredicate);
98  }
99
100  @Override
101  public Collection<V> get(K key) {
102    if (keyPredicate.apply(key)) {
103      return unfiltered.get(key);
104    } else if (unfiltered instanceof SetMultimap) {
105      return new AddRejectingSet<K, V>(key);
106    } else {
107      return new AddRejectingList<K, V>(key);
108    }
109  }
110
111  static class AddRejectingSet<K, V> extends ForwardingSet<V> {
112    final K key;
113
114    AddRejectingSet(K key) {
115      this.key = key;
116    }
117
118    @Override
119    public boolean add(V element) {
120      throw new IllegalArgumentException("Key does not satisfy predicate: " + key);
121    }
122
123    @Override
124    public boolean addAll(Collection<? extends V> collection) {
125      checkNotNull(collection);
126      throw new IllegalArgumentException("Key does not satisfy predicate: " + key);
127    }
128
129    @Override
130    protected Set<V> delegate() {
131      return Collections.emptySet();
132    }
133  }
134
135  static class AddRejectingList<K, V> extends ForwardingList<V> {
136    final K key;
137
138    AddRejectingList(K key) {
139      this.key = key;
140    }
141
142    @Override
143    public boolean add(V v) {
144      add(0, v);
145      return true;
146    }
147
148    @Override
149    public boolean addAll(Collection<? extends V> collection) {
150      addAll(0, collection);
151      return true;
152    }
153
154    @Override
155    public void add(int index, V element) {
156      checkPositionIndex(index, 0);
157      throw new IllegalArgumentException("Key does not satisfy predicate: " + key);
158    }
159
160    @Override
161    public boolean addAll(int index, Collection<? extends V> elements) {
162      checkNotNull(elements);
163      checkPositionIndex(index, 0);
164      throw new IllegalArgumentException("Key does not satisfy predicate: " + key);
165    }
166
167    @Override
168    protected List<V> delegate() {
169      return Collections.emptyList();
170    }
171  }
172
173  @Override
174  Iterator<Entry<K, V>> entryIterator() {
175    throw new AssertionError("should never be called");
176  }
177
178  @Override
179  Collection<Entry<K, V>> createEntries() {
180    return new Entries();
181  }
182
183  class Entries extends ForwardingCollection<Entry<K, V>> {
184    @Override
185    protected Collection<Entry<K, V>> delegate() {
186      return Collections2.filter(unfiltered.entries(), entryPredicate());
187    }
188
189    @Override
190    @SuppressWarnings("unchecked")
191    public boolean remove(@Nullable Object o) {
192      if (o instanceof Entry) {
193        Entry<?, ?> entry = (Entry<?, ?>) o;
194        if (unfiltered.containsKey(entry.getKey())
195            // if this holds, then we know entry.getKey() is a K
196            && keyPredicate.apply((K) entry.getKey())) {
197          return unfiltered.remove(entry.getKey(), entry.getValue());
198        }
199      }
200      return false;
201    }
202  }
203
204  @Override
205  Collection<V> createValues() {
206    return new FilteredMultimapValues<K, V>(this);
207  }
208
209  @Override
210  Map<K, Collection<V>> createAsMap() {
211    return Maps.filterKeys(unfiltered.asMap(), keyPredicate);
212  }
213
214  @Override
215  Multiset<K> createKeys() {
216    return Multisets.filter(unfiltered.keys(), keyPredicate);
217  }
218}
219