• 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 static com.google.common.base.Preconditions.checkArgument;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.base.Preconditions.checkState;
23 import static com.google.common.collect.Multisets.checkNonnegative;
24 
25 import java.io.InvalidObjectException;
26 import java.io.ObjectStreamException;
27 import java.io.Serializable;
28 import java.util.AbstractSet;
29 import java.util.Collection;
30 import java.util.ConcurrentModificationException;
31 import java.util.Iterator;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.concurrent.atomic.AtomicInteger;
35 
36 import javax.annotation.Nullable;
37 
38 /**
39  * Basic implementation of {@code Multiset<E>} backed by an instance of {@code
40  * Map<E, AtomicInteger>}.
41  *
42  * <p>For serialization to work, the subclass must specify explicit {@code
43  * readObject} and {@code writeObject} methods.
44  *
45  * @author Kevin Bourrillion
46  */
47 @GwtCompatible
48 abstract class AbstractMapBasedMultiset<E> extends AbstractMultiset<E>
49     implements Serializable {
50 
51   // TODO: Replace AtomicInteger with a to-be-written IntegerHolder class for
52   // better performance.
53   private transient Map<E, AtomicInteger> backingMap;
54 
55   /*
56    * Cache the size for efficiency. Using a long lets us avoid the need for
57    * overflow checking and ensures that size() will function correctly even if
58    * the multiset had once been larger than Integer.MAX_VALUE.
59    */
60   private transient long size;
61 
62   /** Standard constructor. */
AbstractMapBasedMultiset(Map<E, AtomicInteger> backingMap)63   protected AbstractMapBasedMultiset(Map<E, AtomicInteger> backingMap) {
64     this.backingMap = checkNotNull(backingMap);
65     this.size = super.size();
66   }
67 
backingMap()68   Map<E, AtomicInteger> backingMap() {
69     return backingMap;
70   }
71 
72   /** Used during deserialization only. The backing map must be empty. */
setBackingMap(Map<E, AtomicInteger> backingMap)73   void setBackingMap(Map<E, AtomicInteger> backingMap) {
74     this.backingMap = backingMap;
75   }
76 
77   // Required Implementations
78 
79   private transient EntrySet entrySet;
80 
81   /**
82    * {@inheritDoc}
83    *
84    * <p>Invoking {@link Multiset.Entry#getCount} on an entry in the returned
85    * set always returns the current count of that element in the multiset, as
86    * opposed to the count at the time the entry was retrieved.
87    */
entrySet()88   @Override public Set<Multiset.Entry<E>> entrySet() {
89     EntrySet result = entrySet;
90     if (result == null) {
91       entrySet = result = new EntrySet();
92     }
93     return result;
94   }
95 
96   private class EntrySet extends AbstractSet<Multiset.Entry<E>> {
iterator()97     @Override public Iterator<Multiset.Entry<E>> iterator() {
98       final Iterator<Map.Entry<E, AtomicInteger>> backingEntries
99           = backingMap.entrySet().iterator();
100       return new Iterator<Multiset.Entry<E>>() {
101         Map.Entry<E, AtomicInteger> toRemove;
102 
103         public boolean hasNext() {
104           return backingEntries.hasNext();
105         }
106 
107         public Multiset.Entry<E> next() {
108           final Map.Entry<E, AtomicInteger> mapEntry = backingEntries.next();
109           toRemove = mapEntry;
110           return new Multisets.AbstractEntry<E>() {
111             public E getElement() {
112               return mapEntry.getKey();
113             }
114             public int getCount() {
115               int count = mapEntry.getValue().get();
116               if (count == 0) {
117                 AtomicInteger frequency = backingMap.get(getElement());
118                 if (frequency != null) {
119                   count = frequency.get();
120                 }
121               }
122               return count;
123             }
124           };
125         }
126 
127         public void remove() {
128           checkState(toRemove != null,
129               "no calls to next() since the last call to remove()");
130           size -= toRemove.getValue().getAndSet(0);
131           backingEntries.remove();
132           toRemove = null;
133         }
134       };
135     }
136 
137     @Override public int size() {
138       return backingMap.size();
139     }
140 
141     // The following overrides are for better performance.
142 
143     @Override public void clear() {
144       for (AtomicInteger frequency : backingMap.values()) {
145         frequency.set(0);
146       }
147       backingMap.clear();
148       size = 0L;
149     }
150 
151     @Override public boolean contains(Object o) {
152       if (o instanceof Entry) {
153         Entry<?> entry = (Entry<?>) o;
154         int count = count(entry.getElement());
155         return (count == entry.getCount()) && (count > 0);
156       }
157       return false;
158     }
159 
160     @Override public boolean remove(Object o) {
161       if (contains(o)) {
162         Entry<?> entry = (Entry<?>) o;
163         AtomicInteger frequency = backingMap.remove(entry.getElement());
164         int numberRemoved = frequency.getAndSet(0);
165         size -= numberRemoved;
166         return true;
167       }
168       return false;
169     }
170   }
171 
172   // Optimizations - Query Operations
173 
174   @Override public int size() {
175     return (int) Math.min(this.size, Integer.MAX_VALUE);
176   }
177 
178   @Override public Iterator<E> iterator() {
179     return new MapBasedMultisetIterator();
180   }
181 
182   /*
183    * Not subclassing AbstractMultiset$MultisetIterator because next() needs to
184    * retrieve the Map.Entry<E, AtomicInteger> entry, which can then be used for
185    * a more efficient remove() call.
186    */
187   private class MapBasedMultisetIterator implements Iterator<E> {
188     final Iterator<Map.Entry<E, AtomicInteger>> entryIterator;
189     Map.Entry<E, AtomicInteger> currentEntry;
190     int occurrencesLeft;
191     boolean canRemove;
192 
193     MapBasedMultisetIterator() {
194       this.entryIterator = backingMap.entrySet().iterator();
195     }
196 
197     public boolean hasNext() {
198       return occurrencesLeft > 0 || entryIterator.hasNext();
199     }
200 
201     public E next() {
202       if (occurrencesLeft == 0) {
203         currentEntry = entryIterator.next();
204         occurrencesLeft = currentEntry.getValue().get();
205       }
206       occurrencesLeft--;
207       canRemove = true;
208       return currentEntry.getKey();
209     }
210 
211     public void remove() {
212       checkState(canRemove,
213           "no calls to next() since the last call to remove()");
214       int frequency = currentEntry.getValue().get();
215       if (frequency <= 0) {
216         throw new ConcurrentModificationException();
217       }
218       if (currentEntry.getValue().addAndGet(-1) == 0) {
219         entryIterator.remove();
220       }
221       size--;
222       canRemove = false;
223     }
224   }
225 
226   @Override public int count(@Nullable Object element) {
227     AtomicInteger frequency = backingMap.get(element);
228     return (frequency == null) ? 0 : frequency.get();
229   }
230 
231   // Optional Operations - Modification Operations
232 
233   /**
234    * {@inheritDoc}
235    *
236    * @throws IllegalArgumentException if the call would result in more than
237    *     {@link Integer#MAX_VALUE} occurrences of {@code element} in this
238    *     multiset.
239    */
240   @Override public int add(@Nullable E element, int occurrences) {
241     if (occurrences == 0) {
242       return count(element);
243     }
244     checkArgument(
245         occurrences > 0, "occurrences cannot be negative: %s", occurrences);
246     AtomicInteger frequency = backingMap.get(element);
247     int oldCount;
248     if (frequency == null) {
249       oldCount = 0;
250       backingMap.put(element, new AtomicInteger(occurrences));
251     } else {
252       oldCount = frequency.get();
253       long newCount = (long) oldCount + (long) occurrences;
254       checkArgument(newCount <= Integer.MAX_VALUE,
255           "too many occurrences: %s", newCount);
256       frequency.getAndAdd(occurrences);
257     }
258     size += occurrences;
259     return oldCount;
260   }
261 
262   @Override public int remove(@Nullable Object element, int occurrences) {
263     if (occurrences == 0) {
264       return count(element);
265     }
266     checkArgument(
267         occurrences > 0, "occurrences cannot be negative: %s", occurrences);
268     AtomicInteger frequency = backingMap.get(element);
269     if (frequency == null) {
270       return 0;
271     }
272 
273     int oldCount = frequency.get();
274 
275     int numberRemoved;
276     if (oldCount > occurrences) {
277       numberRemoved = occurrences;
278     } else {
279       numberRemoved = oldCount;
280       backingMap.remove(element);
281     }
282 
283     frequency.addAndGet(-numberRemoved);
284     size -= numberRemoved;
285     return oldCount;
286   }
287 
288   // Roughly a 33% performance improvement over AbstractMultiset.setCount().
289   @Override public int setCount(E element, int count) {
290     checkNonnegative(count, "count");
291 
292     AtomicInteger existingCounter;
293     int oldCount;
294     if (count == 0) {
295       existingCounter = backingMap.remove(element);
296       oldCount = getAndSet(existingCounter, count);
297     } else {
298       existingCounter = backingMap.get(element);
299       oldCount = getAndSet(existingCounter, count);
300 
301       if (existingCounter == null) {
302         backingMap.put(element, new AtomicInteger(count));
303       }
304     }
305 
306     size += (count - oldCount);
307     return oldCount;
308   }
309 
310   private static int getAndSet(AtomicInteger i, int count) {
311     if (i == null) {
312       return 0;
313     }
314 
315     return i.getAndSet(count);
316   }
317 
318   private int removeAllOccurrences(@Nullable Object element,
319       Map<E, AtomicInteger> map) {
320     AtomicInteger frequency = map.remove(element);
321     if (frequency == null) {
322       return 0;
323     }
324     int numberRemoved = frequency.getAndSet(0);
325     size -= numberRemoved;
326     return numberRemoved;
327   }
328 
329   // Views
330 
331   @Override Set<E> createElementSet() {
332     return new MapBasedElementSet(backingMap);
333   }
334 
335   class MapBasedElementSet extends ForwardingSet<E> {
336 
337     // This mapping is the usually the same as {@code backingMap}, but can be a
338     // submap in some implementations.
339     private final Map<E, AtomicInteger> map;
340     private final Set<E> delegate;
341 
342     MapBasedElementSet(Map<E, AtomicInteger> map) {
343       this.map = map;
344       delegate = map.keySet();
345     }
346 
347     @Override protected Set<E> delegate() {
348       return delegate;
349     }
350 
351     // TODO: a way to not have to write this much code?
352 
353     @Override public Iterator<E> iterator() {
354       final Iterator<Map.Entry<E, AtomicInteger>> entries
355           = map.entrySet().iterator();
356       return new Iterator<E>() {
357         Map.Entry<E, AtomicInteger> toRemove;
358 
359         public boolean hasNext() {
360           return entries.hasNext();
361         }
362 
363         public E next() {
364           toRemove = entries.next();
365           return toRemove.getKey();
366         }
367 
368         public void remove() {
369           checkState(toRemove != null,
370               "no calls to next() since the last call to remove()");
371           size -= toRemove.getValue().getAndSet(0);
372           entries.remove();
373           toRemove = null;
374         }
375       };
376     }
377 
378     @Override public boolean remove(Object element) {
379       return removeAllOccurrences(element, map) != 0;
380     }
381 
382     @Override public boolean removeAll(Collection<?> elementsToRemove) {
383       return Iterators.removeAll(iterator(), elementsToRemove);
384     }
385 
386     @Override public boolean retainAll(Collection<?> elementsToRetain) {
387       return Iterators.retainAll(iterator(), elementsToRetain);
388     }
389 
390     @Override public void clear() {
391       if (map == backingMap) {
392         AbstractMapBasedMultiset.this.clear();
393       } else {
394         Iterator<E> i = iterator();
395         while (i.hasNext()) {
396           i.next();
397           i.remove();
398         }
399       }
400     }
401 
402     public Map<E, AtomicInteger> getMap() {
403       return map;
404     }
405   }
406 
407   // Don't allow default serialization.
408   @SuppressWarnings("unused") // actually used during deserialization
409   private void readObjectNoData() throws ObjectStreamException {
410     throw new InvalidObjectException("Stream data required");
411   }
412 
413   private static final long serialVersionUID = -2250766705698539974L;
414 }
415