• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 Google Inc.
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 com.google.common.annotations.GwtCompatible;
20 import com.google.common.base.Objects;
21 
22 import java.util.AbstractCollection;
23 import java.util.AbstractSet;
24 import java.util.Collection;
25 import java.util.Iterator;
26 import java.util.NoSuchElementException;
27 import java.util.Set;
28 
29 import javax.annotation.Nullable;
30 
31 import static com.google.common.base.Preconditions.checkNotNull;
32 import static com.google.common.base.Preconditions.checkState;
33 import static com.google.common.collect.Multisets.setCountImpl;
34 
35 
36 /**
37  * This class provides a skeletal implementation of the {@link Multiset}
38  * interface. A new multiset implementation can be created easily by extending
39  * this class and implementing the {@link Multiset#entrySet()} method, plus
40  * optionally overriding {@link #add(Object, int)} and
41  * {@link #remove(Object, int)} to enable modifications to the multiset.
42  *
43  * <p>The {@link #contains}, {@link #containsAll}, {@link #count}, and
44  * {@link #size} implementations all iterate across the set returned by
45  * {@link Multiset#entrySet()}, as do many methods acting on the set returned by
46  * {@link #elementSet()}. Override those methods for better performance.
47  *
48  * @author Kevin Bourrillion
49  */
50 @GwtCompatible
51 abstract class AbstractMultiset<E> extends AbstractCollection<E>
52     implements Multiset<E> {
entrySet()53   public abstract Set<Entry<E>> entrySet();
54 
55   // Query Operations
56 
size()57   @Override public int size() {
58     long sum = 0L;
59     for (Entry<E> entry : entrySet()) {
60       sum += entry.getCount();
61     }
62     return (int) Math.min(sum, Integer.MAX_VALUE);
63   }
64 
isEmpty()65   @Override public boolean isEmpty() {
66     return entrySet().isEmpty();
67   }
68 
contains(@ullable Object element)69   @Override public boolean contains(@Nullable Object element) {
70     return elementSet().contains(element);
71   }
72 
iterator()73   @Override public Iterator<E> iterator() {
74     return new MultisetIterator();
75   }
76 
77   private class MultisetIterator implements Iterator<E> {
78     private final Iterator<Entry<E>> entryIterator;
79     private Entry<E> currentEntry;
80     /** Count of subsequent elements equal to current element */
81     private int laterCount;
82     /** Count of all elements equal to current element */
83     private int totalCount;
84     private boolean canRemove;
85 
MultisetIterator()86     MultisetIterator() {
87       this.entryIterator = entrySet().iterator();
88     }
89 
hasNext()90     public boolean hasNext() {
91       return laterCount > 0 || entryIterator.hasNext();
92     }
93 
next()94     public E next() {
95       if (!hasNext()) {
96         throw new NoSuchElementException();
97       }
98       if (laterCount == 0) {
99         currentEntry = entryIterator.next();
100         totalCount = laterCount = currentEntry.getCount();
101       }
102       laterCount--;
103       canRemove = true;
104       return currentEntry.getElement();
105     }
106 
remove()107     public void remove() {
108       checkState(canRemove,
109           "no calls to next() since the last call to remove()");
110       if (totalCount == 1) {
111         entryIterator.remove();
112       } else {
113         AbstractMultiset.this.remove(currentEntry.getElement());
114       }
115       totalCount--;
116       canRemove = false;
117     }
118   }
119 
count(Object element)120   public int count(Object element) {
121     for (Entry<E> entry : entrySet()) {
122       if (Objects.equal(entry.getElement(), element)) {
123         return entry.getCount();
124       }
125     }
126     return 0;
127   }
128 
129   // Modification Operations
130 
add(@ullable E element)131   @Override public boolean add(@Nullable E element) {
132     add(element, 1);
133     return true;
134   }
135 
add(E element, int occurrences)136   public int add(E element, int occurrences) {
137     throw new UnsupportedOperationException();
138   }
139 
remove(Object element)140   @Override public boolean remove(Object element) {
141     return remove(element, 1) > 0;
142   }
143 
remove(Object element, int occurrences)144   public int remove(Object element, int occurrences) {
145     throw new UnsupportedOperationException();
146   }
147 
setCount(E element, int count)148   public int setCount(E element, int count) {
149     return setCountImpl(this, element, count);
150   }
151 
setCount(E element, int oldCount, int newCount)152   public boolean setCount(E element, int oldCount, int newCount) {
153     return setCountImpl(this, element, oldCount, newCount);
154   }
155 
156   // Bulk Operations
157 
containsAll(Collection<?> elements)158   @Override public boolean containsAll(Collection<?> elements) {
159     return elementSet().containsAll(elements);
160   }
161 
addAll(Collection<? extends E> elementsToAdd)162   @Override public boolean addAll(Collection<? extends E> elementsToAdd) {
163     if (elementsToAdd.isEmpty()) {
164       return false;
165     }
166     if (elementsToAdd instanceof Multiset) {
167       @SuppressWarnings("unchecked")
168       Multiset<? extends E> that = (Multiset<? extends E>) elementsToAdd;
169       for (Entry<? extends E> entry : that.entrySet()) {
170         add(entry.getElement(), entry.getCount());
171       }
172     } else {
173       super.addAll(elementsToAdd);
174     }
175     return true;
176   }
177 
removeAll(Collection<?> elementsToRemove)178   @Override public boolean removeAll(Collection<?> elementsToRemove) {
179     Collection<?> collection = (elementsToRemove instanceof Multiset)
180         ? ((Multiset<?>) elementsToRemove).elementSet() : elementsToRemove;
181 
182     return elementSet().removeAll(collection);
183     // TODO: implement retainAll similarly?
184   }
185 
retainAll(Collection<?> elementsToRetain)186   @Override public boolean retainAll(Collection<?> elementsToRetain) {
187     checkNotNull(elementsToRetain);
188     Iterator<Entry<E>> entries = entrySet().iterator();
189     boolean modified = false;
190     while (entries.hasNext()) {
191       Entry<E> entry = entries.next();
192       if (!elementsToRetain.contains(entry.getElement())) {
193         entries.remove();
194         modified = true;
195       }
196     }
197     return modified;
198   }
199 
clear()200   @Override public void clear() {
201     entrySet().clear();
202   }
203 
204   // Views
205 
206   private transient Set<E> elementSet;
207 
elementSet()208   public Set<E> elementSet() {
209     Set<E> result = elementSet;
210     if (result == null) {
211       elementSet = result = createElementSet();
212     }
213     return result;
214   }
215 
216   /**
217    * Creates a new instance of this multiset's element set, which will be
218    * returned by {@link #elementSet()}.
219    */
createElementSet()220   Set<E> createElementSet() {
221     return new ElementSet();
222   }
223 
224   private class ElementSet extends AbstractSet<E> {
iterator()225     @Override public Iterator<E> iterator() {
226       final Iterator<Entry<E>> entryIterator = entrySet().iterator();
227       return new Iterator<E>() {
228         public boolean hasNext() {
229           return entryIterator.hasNext();
230         }
231         public E next() {
232           return entryIterator.next().getElement();
233         }
234         public void remove() {
235           entryIterator.remove();
236         }
237       };
238     }
size()239     @Override public int size() {
240       return entrySet().size();
241     }
242   }
243 
244   // Object methods
245 
246   /**
247    * {@inheritDoc}
248    *
249    * <p>This implementation returns {@code true} if {@code other} is a multiset
250    * of the same size and if, for each element, the two multisets have the same
251    * count.
252    */
253   @Override public boolean equals(@Nullable Object object) {
254     if (object == this) {
255       return true;
256     }
257     if (object instanceof Multiset) {
258       Multiset<?> that = (Multiset<?>) object;
259       /*
260        * We can't simply check whether the entry sets are equal, since that
261        * approach fails when a TreeMultiset has a comparator that returns 0
262        * when passed unequal elements.
263        */
264 
265       if (this.size() != that.size()) {
266         return false;
267       }
268       for (Entry<?> entry : that.entrySet()) {
269         if (count(entry.getElement()) != entry.getCount()) {
270           return false;
271         }
272       }
273       return true;
274     }
275     return false;
276   }
277 
278   /**
279    * {@inheritDoc}
280    *
281    * <p>This implementation returns the hash code of {@link
282    * Multiset#entrySet()}.
283    */
284   @Override public int hashCode() {
285     return entrySet().hashCode();
286   }
287 
288   /**
289    * {@inheritDoc}
290    *
291    * <p>This implementation returns the result of invoking {@code toString} on
292    * {@link Multiset#entrySet()}.
293    */
294   @Override public String toString() {
295     return entrySet().toString();
296   }
297 }
298