1 /*
2  * Copyright (C) 2007 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.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.collect.CollectPreconditions.checkNonnegative;
22 import static com.google.common.collect.CollectPreconditions.checkRemove;
23 
24 import com.google.common.annotations.GwtCompatible;
25 import com.google.common.annotations.GwtIncompatible;
26 import com.google.common.primitives.Ints;
27 
28 import java.io.InvalidObjectException;
29 import java.io.ObjectStreamException;
30 import java.io.Serializable;
31 import java.util.ConcurrentModificationException;
32 import java.util.Iterator;
33 import java.util.Map;
34 import java.util.Set;
35 
36 import javax.annotation.Nullable;
37 
38 /**
39  * Basic implementation of {@code Multiset<E>} backed by an instance of {@code
40  * Map<E, Count>}.
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(emulated = true)
48 abstract class AbstractMapBasedMultiset<E> extends AbstractMultiset<E>
49     implements Serializable {
50 
51   private transient Map<E, Count> backingMap;
52 
53   /*
54    * Cache the size for efficiency. Using a long lets us avoid the need for
55    * overflow checking and ensures that size() will function correctly even if
56    * the multiset had once been larger than Integer.MAX_VALUE.
57    */
58   private transient long size;
59 
60   /** Standard constructor. */
AbstractMapBasedMultiset(Map<E, Count> backingMap)61   protected AbstractMapBasedMultiset(Map<E, Count> backingMap) {
62     this.backingMap = checkNotNull(backingMap);
63     this.size = super.size();
64   }
65 
66   /** Used during deserialization only. The backing map must be empty. */
setBackingMap(Map<E, Count> backingMap)67   void setBackingMap(Map<E, Count> backingMap) {
68     this.backingMap = backingMap;
69   }
70 
71   // Required Implementations
72 
73   /**
74    * {@inheritDoc}
75    *
76    * <p>Invoking {@link Multiset.Entry#getCount} on an entry in the returned
77    * set always returns the current count of that element in the multiset, as
78    * opposed to the count at the time the entry was retrieved.
79    */
80   @Override
entrySet()81   public Set<Multiset.Entry<E>> entrySet() {
82     return super.entrySet();
83   }
84 
85   @Override
entryIterator()86   Iterator<Entry<E>> entryIterator() {
87     final Iterator<Map.Entry<E, Count>> backingEntries =
88         backingMap.entrySet().iterator();
89     return new Iterator<Multiset.Entry<E>>() {
90       Map.Entry<E, Count> toRemove;
91 
92       @Override
93       public boolean hasNext() {
94         return backingEntries.hasNext();
95       }
96 
97       @Override
98       public Multiset.Entry<E> next() {
99         final Map.Entry<E, Count> mapEntry = backingEntries.next();
100         toRemove = mapEntry;
101         return new Multisets.AbstractEntry<E>() {
102           @Override
103           public E getElement() {
104             return mapEntry.getKey();
105           }
106           @Override
107           public int getCount() {
108             Count count = mapEntry.getValue();
109             if (count == null || count.get() == 0) {
110               Count frequency = backingMap.get(getElement());
111               if (frequency != null) {
112                 return frequency.get();
113               }
114             }
115             return (count == null) ? 0 : count.get();
116           }
117         };
118       }
119 
120       @Override
121       public void remove() {
122         checkRemove(toRemove != null);
123         size -= toRemove.getValue().getAndSet(0);
124         backingEntries.remove();
125         toRemove = null;
126       }
127     };
128   }
129 
130   @Override
131   public void clear() {
132     for (Count frequency : backingMap.values()) {
133       frequency.set(0);
134     }
135     backingMap.clear();
136     size = 0L;
137   }
138 
139   @Override
140   int distinctElements() {
141     return backingMap.size();
142   }
143 
144   // Optimizations - Query Operations
145 
146   @Override public int size() {
147     return Ints.saturatedCast(size);
148   }
149 
150   @Override public Iterator<E> iterator() {
151     return new MapBasedMultisetIterator();
152   }
153 
154   /*
155    * Not subclassing AbstractMultiset$MultisetIterator because next() needs to
156    * retrieve the Map.Entry<E, Count> entry, which can then be used for
157    * a more efficient remove() call.
158    */
159   private class MapBasedMultisetIterator implements Iterator<E> {
160     final Iterator<Map.Entry<E, Count>> entryIterator;
161     Map.Entry<E, Count> currentEntry;
162     int occurrencesLeft;
163     boolean canRemove;
164 
165     MapBasedMultisetIterator() {
166       this.entryIterator = backingMap.entrySet().iterator();
167     }
168 
169     @Override
170     public boolean hasNext() {
171       return occurrencesLeft > 0 || entryIterator.hasNext();
172     }
173 
174     @Override
175     public E next() {
176       if (occurrencesLeft == 0) {
177         currentEntry = entryIterator.next();
178         occurrencesLeft = currentEntry.getValue().get();
179       }
180       occurrencesLeft--;
181       canRemove = true;
182       return currentEntry.getKey();
183     }
184 
185     @Override
186     public void remove() {
187       checkRemove(canRemove);
188       int frequency = currentEntry.getValue().get();
189       if (frequency <= 0) {
190         throw new ConcurrentModificationException();
191       }
192       if (currentEntry.getValue().addAndGet(-1) == 0) {
193         entryIterator.remove();
194       }
195       size--;
196       canRemove = false;
197     }
198   }
199 
200   @Override public int count(@Nullable Object element) {
201     Count frequency = Maps.safeGet(backingMap, element);
202     return (frequency == null) ? 0 : frequency.get();
203   }
204 
205   // Optional Operations - Modification Operations
206 
207   /**
208    * {@inheritDoc}
209    *
210    * @throws IllegalArgumentException if the call would result in more than
211    *     {@link Integer#MAX_VALUE} occurrences of {@code element} in this
212    *     multiset.
213    */
214   @Override public int add(@Nullable E element, int occurrences) {
215     if (occurrences == 0) {
216       return count(element);
217     }
218     checkArgument(
219         occurrences > 0, "occurrences cannot be negative: %s", occurrences);
220     Count frequency = backingMap.get(element);
221     int oldCount;
222     if (frequency == null) {
223       oldCount = 0;
224       backingMap.put(element, new Count(occurrences));
225     } else {
226       oldCount = frequency.get();
227       long newCount = (long) oldCount + (long) occurrences;
228       checkArgument(newCount <= Integer.MAX_VALUE,
229           "too many occurrences: %s", newCount);
230       frequency.getAndAdd(occurrences);
231     }
232     size += occurrences;
233     return oldCount;
234   }
235 
236   @Override public int remove(@Nullable Object element, int occurrences) {
237     if (occurrences == 0) {
238       return count(element);
239     }
240     checkArgument(
241         occurrences > 0, "occurrences cannot be negative: %s", occurrences);
242     Count frequency = backingMap.get(element);
243     if (frequency == null) {
244       return 0;
245     }
246 
247     int oldCount = frequency.get();
248 
249     int numberRemoved;
250     if (oldCount > occurrences) {
251       numberRemoved = occurrences;
252     } else {
253       numberRemoved = oldCount;
254       backingMap.remove(element);
255     }
256 
257     frequency.addAndGet(-numberRemoved);
258     size -= numberRemoved;
259     return oldCount;
260   }
261 
262   // Roughly a 33% performance improvement over AbstractMultiset.setCount().
263   @Override public int setCount(@Nullable E element, int count) {
264     checkNonnegative(count, "count");
265 
266     Count existingCounter;
267     int oldCount;
268     if (count == 0) {
269       existingCounter = backingMap.remove(element);
270       oldCount = getAndSet(existingCounter, count);
271     } else {
272       existingCounter = backingMap.get(element);
273       oldCount = getAndSet(existingCounter, count);
274 
275       if (existingCounter == null) {
276         backingMap.put(element, new Count(count));
277       }
278     }
279 
280     size += (count - oldCount);
281     return oldCount;
282   }
283 
284   private static int getAndSet(Count i, int count) {
285     if (i == null) {
286       return 0;
287     }
288 
289     return i.getAndSet(count);
290   }
291 
292   // Don't allow default serialization.
293   @GwtIncompatible("java.io.ObjectStreamException")
294   @SuppressWarnings("unused") // actually used during deserialization
295   private void readObjectNoData() throws ObjectStreamException {
296     throw new InvalidObjectException("Stream data required");
297   }
298 
299   @GwtIncompatible("not needed in emulated source.")
300   private static final long serialVersionUID = -2250766705698539974L;
301 }
302