001/* 002 * Copyright (C) 2007 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.collect; 016 017import static com.google.common.base.Preconditions.checkArgument; 018import static com.google.common.base.Preconditions.checkNotNull; 019import static com.google.common.collect.CollectPreconditions.checkNonnegative; 020import static com.google.common.collect.CollectPreconditions.checkRemove; 021import static java.util.Objects.requireNonNull; 022 023import com.google.common.annotations.GwtCompatible; 024import com.google.common.annotations.GwtIncompatible; 025import com.google.common.annotations.J2ktIncompatible; 026import com.google.common.primitives.Ints; 027import com.google.errorprone.annotations.CanIgnoreReturnValue; 028import java.io.IOException; 029import java.io.ObjectInputStream; 030import java.io.ObjectOutputStream; 031import java.io.Serializable; 032import java.util.Arrays; 033import java.util.Iterator; 034import java.util.NoSuchElementException; 035import java.util.function.ObjIntConsumer; 036import org.jspecify.annotations.Nullable; 037 038/** 039 * Multiset implementation specialized for enum elements, supporting all single-element operations 040 * in O(1). 041 * 042 * <p>See the Guava User Guide article on <a href= 043 * "https://github.com/google/guava/wiki/NewCollectionTypesExplained#multiset">{@code Multiset}</a>. 044 * 045 * @author Jared Levy 046 * @since 2.0 047 */ 048@GwtCompatible(emulated = true) 049@J2ktIncompatible 050public final class EnumMultiset<E extends Enum<E>> extends AbstractMultiset<E> 051 implements Serializable { 052 /** Creates an empty {@code EnumMultiset}. */ 053 public static <E extends Enum<E>> EnumMultiset<E> create(Class<E> type) { 054 return new EnumMultiset<>(type); 055 } 056 057 /** 058 * Creates a new {@code EnumMultiset} containing the specified elements. 059 * 060 * <p>This implementation is highly efficient when {@code elements} is itself a {@link Multiset}. 061 * 062 * @param elements the elements that the multiset should contain 063 * @throws IllegalArgumentException if {@code elements} is empty 064 */ 065 public static <E extends Enum<E>> EnumMultiset<E> create(Iterable<E> elements) { 066 Iterator<E> iterator = elements.iterator(); 067 checkArgument(iterator.hasNext(), "EnumMultiset constructor passed empty Iterable"); 068 EnumMultiset<E> multiset = new EnumMultiset<>(iterator.next().getDeclaringClass()); 069 Iterables.addAll(multiset, elements); 070 return multiset; 071 } 072 073 /** 074 * Returns a new {@code EnumMultiset} instance containing the given elements. Unlike {@link 075 * EnumMultiset#create(Iterable)}, this method does not produce an exception on an empty iterable. 076 * 077 * @since 14.0 078 */ 079 public static <E extends Enum<E>> EnumMultiset<E> create(Iterable<E> elements, Class<E> type) { 080 EnumMultiset<E> result = create(type); 081 Iterables.addAll(result, elements); 082 return result; 083 } 084 085 private transient Class<E> type; 086 private transient E[] enumConstants; 087 private transient int[] counts; 088 private transient int distinctElements; 089 private transient long size; 090 091 /** Creates an empty {@code EnumMultiset}. */ 092 private EnumMultiset(Class<E> type) { 093 this.type = type; 094 checkArgument(type.isEnum()); 095 this.enumConstants = type.getEnumConstants(); 096 this.counts = new int[enumConstants.length]; 097 } 098 099 private boolean isActuallyE(@Nullable 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 */ 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 120 int distinctElements() { 121 return distinctElements; 122 } 123 124 @Override 125 public int size() { 126 return Ints.saturatedCast(size); 127 } 128 129 @Override 130 public int count(@Nullable 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 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 163 public int remove(@Nullable 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 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 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 217 abstract T output(int index); 218 219 @Override 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 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 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 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 void forEachEntry(ObjIntConsumer<? super E> action) { 284 checkNotNull(action); 285 for (int i = 0; i < enumConstants.length; i++) { 286 if (counts[i] > 0) { 287 action.accept(enumConstants[i], counts[i]); 288 } 289 } 290 } 291 292 @Override 293 public Iterator<E> iterator() { 294 return Multisets.iteratorImpl(this); 295 } 296 297 @GwtIncompatible // java.io.ObjectOutputStream 298 private void writeObject(ObjectOutputStream stream) throws IOException { 299 stream.defaultWriteObject(); 300 stream.writeObject(type); 301 Serialization.writeMultiset(this, stream); 302 } 303 304 /** 305 * @serialData the {@code Class<E>} for the enum type, the number of distinct elements, the first 306 * element, its count, the second element, its count, and so on 307 */ 308 @GwtIncompatible // java.io.ObjectInputStream 309 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 310 stream.defaultReadObject(); 311 @SuppressWarnings("unchecked") // reading data stored by writeObject 312 Class<E> localType = (Class<E>) requireNonNull(stream.readObject()); 313 type = localType; 314 enumConstants = type.getEnumConstants(); 315 counts = new int[enumConstants.length]; 316 Serialization.populateMultiset(this, stream); 317 } 318 319 @GwtIncompatible // Not needed in emulated source 320 private static final long serialVersionUID = 0; 321}