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