• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 
15 package com.google.common.collect;
16 
17 import static com.google.common.base.Preconditions.checkArgument;
18 import static com.google.common.base.Preconditions.checkNotNull;
19 import static com.google.common.collect.CollectPreconditions.checkNonnegative;
20 import static com.google.common.collect.CollectPreconditions.checkRemove;
21 import static java.util.Objects.requireNonNull;
22 
23 import com.google.common.annotations.GwtCompatible;
24 import com.google.common.annotations.GwtIncompatible;
25 import com.google.common.annotations.J2ktIncompatible;
26 import com.google.common.primitives.Ints;
27 import com.google.errorprone.annotations.CanIgnoreReturnValue;
28 import java.io.IOException;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.io.Serializable;
32 import java.util.Arrays;
33 import java.util.Iterator;
34 import java.util.NoSuchElementException;
35 import javax.annotation.CheckForNull;
36 
37 /**
38  * Multiset implementation specialized for enum elements, supporting all single-element operations
39  * in O(1).
40  *
41  * <p>See the Guava User Guide article on <a href=
42  * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}</a>.
43  *
44  * @author Jared Levy
45  * @since 2.0
46  */
47 @GwtCompatible(emulated = true)
48 @J2ktIncompatible
49 @ElementTypesAreNonnullByDefault
50 public final class EnumMultiset<E extends Enum<E>> extends AbstractMultiset<E>
51     implements Serializable {
52   /** Creates an empty {@code EnumMultiset}. */
create(Class<E> type)53   public static <E extends Enum<E>> EnumMultiset<E> create(Class<E> type) {
54     return new EnumMultiset<E>(type);
55   }
56 
57   /**
58    * Creates a new {@code EnumMultiset} containing the specified elements.
59    *
60    * <p>This implementation is highly efficient when {@code elements} is itself a {@link Multiset}.
61    *
62    * @param elements the elements that the multiset should contain
63    * @throws IllegalArgumentException if {@code elements} is empty
64    */
create(Iterable<E> elements)65   public static <E extends Enum<E>> EnumMultiset<E> create(Iterable<E> elements) {
66     Iterator<E> iterator = elements.iterator();
67     checkArgument(iterator.hasNext(), "EnumMultiset constructor passed empty Iterable");
68     EnumMultiset<E> multiset = new EnumMultiset<>(iterator.next().getDeclaringClass());
69     Iterables.addAll(multiset, elements);
70     return multiset;
71   }
72 
73   /**
74    * Returns a new {@code EnumMultiset} instance containing the given elements. Unlike {@link
75    * EnumMultiset#create(Iterable)}, this method does not produce an exception on an empty iterable.
76    *
77    * @since 14.0
78    */
create(Iterable<E> elements, Class<E> type)79   public static <E extends Enum<E>> EnumMultiset<E> create(Iterable<E> elements, Class<E> type) {
80     EnumMultiset<E> result = create(type);
81     Iterables.addAll(result, elements);
82     return result;
83   }
84 
85   private transient Class<E> type;
86   private transient E[] enumConstants;
87   private transient int[] counts;
88   private transient int distinctElements;
89   private transient long size;
90 
91   /** Creates an empty {@code EnumMultiset}. */
EnumMultiset(Class<E> type)92   private EnumMultiset(Class<E> type) {
93     this.type = type;
94     checkArgument(type.isEnum());
95     this.enumConstants = type.getEnumConstants();
96     this.counts = new int[enumConstants.length];
97   }
98 
isActuallyE(@heckForNull Object o)99   private boolean isActuallyE(@CheckForNull Object o) {
100     if (o instanceof Enum) {
101       Enum<?> e = (Enum<?>) o;
102       int index = e.ordinal();
103       return index < enumConstants.length && enumConstants[index] == e;
104     }
105     return false;
106   }
107 
108   /**
109    * Returns {@code element} cast to {@code E}, if it actually is a nonnull E. Otherwise, throws
110    * either a NullPointerException or a ClassCastException as appropriate.
111    */
checkIsE(Object element)112   private void checkIsE(Object element) {
113     checkNotNull(element);
114     if (!isActuallyE(element)) {
115       throw new ClassCastException("Expected an " + type + " but got " + element);
116     }
117   }
118 
119   @Override
distinctElements()120   int distinctElements() {
121     return distinctElements;
122   }
123 
124   @Override
size()125   public int size() {
126     return Ints.saturatedCast(size);
127   }
128 
129   @Override
count(@heckForNull Object element)130   public int count(@CheckForNull Object element) {
131     // isActuallyE checks for null, but we check explicitly to help nullness checkers.
132     if (element == null || !isActuallyE(element)) {
133       return 0;
134     }
135     Enum<?> e = (Enum<?>) element;
136     return counts[e.ordinal()];
137   }
138 
139   // Modification Operations
140   @CanIgnoreReturnValue
141   @Override
add(E element, int occurrences)142   public int add(E element, int occurrences) {
143     checkIsE(element);
144     checkNonnegative(occurrences, "occurrences");
145     if (occurrences == 0) {
146       return count(element);
147     }
148     int index = element.ordinal();
149     int oldCount = counts[index];
150     long newCount = (long) oldCount + occurrences;
151     checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount);
152     counts[index] = (int) newCount;
153     if (oldCount == 0) {
154       distinctElements++;
155     }
156     size += occurrences;
157     return oldCount;
158   }
159 
160   // Modification Operations
161   @CanIgnoreReturnValue
162   @Override
remove(@heckForNull Object element, int occurrences)163   public int remove(@CheckForNull Object element, int occurrences) {
164     // isActuallyE checks for null, but we check explicitly to help nullness checkers.
165     if (element == null || !isActuallyE(element)) {
166       return 0;
167     }
168     Enum<?> e = (Enum<?>) element;
169     checkNonnegative(occurrences, "occurrences");
170     if (occurrences == 0) {
171       return count(element);
172     }
173     int index = e.ordinal();
174     int oldCount = counts[index];
175     if (oldCount == 0) {
176       return 0;
177     } else if (oldCount <= occurrences) {
178       counts[index] = 0;
179       distinctElements--;
180       size -= oldCount;
181     } else {
182       counts[index] = oldCount - occurrences;
183       size -= occurrences;
184     }
185     return oldCount;
186   }
187 
188   // Modification Operations
189   @CanIgnoreReturnValue
190   @Override
setCount(E element, int count)191   public int setCount(E element, int count) {
192     checkIsE(element);
193     checkNonnegative(count, "count");
194     int index = element.ordinal();
195     int oldCount = counts[index];
196     counts[index] = count;
197     size += count - oldCount;
198     if (oldCount == 0 && count > 0) {
199       distinctElements++;
200     } else if (oldCount > 0 && count == 0) {
201       distinctElements--;
202     }
203     return oldCount;
204   }
205 
206   @Override
clear()207   public void clear() {
208     Arrays.fill(counts, 0);
209     size = 0;
210     distinctElements = 0;
211   }
212 
213   abstract class Itr<T> implements Iterator<T> {
214     int index = 0;
215     int toRemove = -1;
216 
output(int index)217     abstract T output(int index);
218 
219     @Override
hasNext()220     public boolean hasNext() {
221       for (; index < enumConstants.length; index++) {
222         if (counts[index] > 0) {
223           return true;
224         }
225       }
226       return false;
227     }
228 
229     @Override
next()230     public T next() {
231       if (!hasNext()) {
232         throw new NoSuchElementException();
233       }
234       T result = output(index);
235       toRemove = index;
236       index++;
237       return result;
238     }
239 
240     @Override
remove()241     public void remove() {
242       checkRemove(toRemove >= 0);
243       if (counts[toRemove] > 0) {
244         distinctElements--;
245         size -= counts[toRemove];
246         counts[toRemove] = 0;
247       }
248       toRemove = -1;
249     }
250   }
251 
252   @Override
elementIterator()253   Iterator<E> elementIterator() {
254     return new Itr<E>() {
255       @Override
256       E output(int index) {
257         return enumConstants[index];
258       }
259     };
260   }
261 
262   @Override
263   Iterator<Entry<E>> entryIterator() {
264     return new Itr<Entry<E>>() {
265       @Override
266       Entry<E> output(final int index) {
267         return new Multisets.AbstractEntry<E>() {
268           @Override
269           public E getElement() {
270             return enumConstants[index];
271           }
272 
273           @Override
274           public int getCount() {
275             return counts[index];
276           }
277         };
278       }
279     };
280   }
281 
282   @Override
283   public Iterator<E> iterator() {
284     return Multisets.iteratorImpl(this);
285   }
286 
287   @GwtIncompatible // java.io.ObjectOutputStream
288   private void writeObject(ObjectOutputStream stream) throws IOException {
289     stream.defaultWriteObject();
290     stream.writeObject(type);
291     Serialization.writeMultiset(this, stream);
292   }
293 
294   /**
295    * @serialData the {@code Class<E>} for the enum type, the number of distinct elements, the first
296    *     element, its count, the second element, its count, and so on
297    */
298   @GwtIncompatible // java.io.ObjectInputStream
299   private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
300     stream.defaultReadObject();
301     @SuppressWarnings("unchecked") // reading data stored by writeObject
302     Class<E> localType = (Class<E>) requireNonNull(stream.readObject());
303     type = localType;
304     enumConstants = type.getEnumConstants();
305     counts = new int[enumConstants.length];
306     Serialization.populateMultiset(this, stream);
307   }
308 
309   @GwtIncompatible // Not needed in emulated source
310   private static final long serialVersionUID = 0;
311 }
312