/*
* Copyright (C) 2017 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.collect;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.collect.CollectPreconditions.checkPositive;
import static com.google.common.collect.CollectPreconditions.checkRemove;
import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.Multiset.Entry;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.Nullable;
/** EnumCountHashMap is an implementation of {@code AbstractObjectCountMap} with enum type keys. */
@GwtCompatible(serializable = true, emulated = true)
class EnumCountHashMap<K extends Enum<K>> extends AbstractObjectCountMap<K> {
/** Creates an empty {@code EnumCountHashMap} instance. */
public static <K extends Enum<K>> EnumCountHashMap<K> create(Class<K> type) {
return new EnumCountHashMap<K>(type);
}
private final Class<K> keyType;
/** Constructs a new empty instance of {@code EnumCountHashMap}. */
EnumCountHashMap(Class<K> keyType) {
this.keyType = keyType;
this.keys = keyType.getEnumConstants();
if (this.keys == null) {
throw new IllegalStateException("Expected Enum class type, but got " + keyType.getName());
}
this.values = new int[this.keys.length];
Arrays.fill(values, 0, this.keys.length, UNSET);
}
@Override
int firstIndex() {
for (int i = 0; i < this.keys.length; i++) {
if (values[i] > 0) {
return i;
}
}
return -1;
}
@Override
int nextIndex(int index) {
for (int i = index + 1; i < this.keys.length; i++) {
if (values[i] > 0) {
return i;
}
}
return -1;
}
private abstract class EnumIterator<T> extends Itr<T> {
int nextIndex = UNSET;
@Override
public boolean hasNext() {
while (index < values.length && values[index] <= 0) {
index++;
}
return index != values.length;
}
@Override
public T next() {
checkForConcurrentModification();
if (!hasNext()) {
throw new NoSuchElementException();
}
nextCalled = true;
nextIndex = index;
return getOutput(index++);
}
@Override
public void remove() {
checkForConcurrentModification();
checkRemove(nextCalled);
expectedModCount++;
removeEntry(nextIndex);
nextCalled = false;
nextIndex = UNSET;
index--;
}
}
@Override
Set<K> createKeySet() {
return new KeySetView() {
private Object[] getFilteredKeyArray() {
Object[] filteredKeys = new Object[size];
for (int i = 0, j = 0; i < keys.length; i++) {
if (values[i] != UNSET) {
filteredKeys[j++] = keys[i];
}
}
return filteredKeys;
}
@Override
public Object[] toArray() {
return getFilteredKeyArray();
}
@Override
public <T> T[] toArray(T[] a) {
return ObjectArrays.toArrayImpl(getFilteredKeyArray(), 0, size, a);
}
@Override
public Iterator<K> iterator() {
return new EnumIterator<K>() {
@SuppressWarnings("unchecked")
@Override
K getOutput(int entry) {
return (K) keys[entry];
}
};
}
};
}
@Override
Multiset.Entry<K> getEntry(int index) {
checkElementIndex(index, size);
return new EnumMapEntry(index);
}
class EnumMapEntry extends MapEntry {
EnumMapEntry(int index) {
super(index);
}
@SuppressWarnings("unchecked")
@Override
public int getCount() {
return values[lastKnownIndex] == UNSET ? 0 : values[lastKnownIndex];
}
@SuppressWarnings("unchecked")
@Override
public int setCount(int count) {
if (values[lastKnownIndex] == UNSET) {
put(key, count);
return 0;
} else {
int old = values[lastKnownIndex];
values[lastKnownIndex] = count;
return old == UNSET ? 0 : old;
}
}
}
@Override
Set<Entry<K>> createEntrySet() {
return new EntrySetView() {
@Override
public Iterator<Entry<K>> iterator() {
return new EnumIterator<Entry<K>>() {
@Override
Entry<K> getOutput(int entry) {
return new EnumMapEntry(entry);
}
};
}
};
}
@Override
public void clear() {
modCount++;
if (keys != null) {
Arrays.fill(values, 0, values.length, UNSET);
this.size = 0;
}
}
/** Returns true if key is of the proper type to be a key in this enum map. */
private boolean isValidKey(Object key) {
if (key == null) return false;
// Cheaper than instanceof Enum followed by getDeclaringClass
Class<?> keyClass = key.getClass();
return keyClass == keyType || keyClass.getSuperclass() == keyType;
}
@Override
public boolean containsKey(@Nullable Object key) {
return isValidKey(key) && values[((Enum<?>) key).ordinal()] != UNSET;
}
@Override
public int get(@Nullable Object key) {
return containsKey(key) ? values[((Enum<?>) key).ordinal()] : 0;
}
@Override
int indexOf(@Nullable Object key) {
if (!isValidKey(key)) {
return -1;
}
return ((Enum<?>) key).ordinal();
}
@CanIgnoreReturnValue
@Override
int removeEntry(int entryIndex) {
return remove(keys[entryIndex]);
}
@CanIgnoreReturnValue
@Override
public int put(@Nullable K key, int value) {
checkPositive(value, "count");
typeCheck(key);
int index = key.ordinal();
int oldValue = values[index];
values[index] = value;
modCount++;
if (oldValue == UNSET) {
size++;
return 0;
}
return oldValue;
}
@CanIgnoreReturnValue
@Override
public int remove(@Nullable Object key) {
if (!isValidKey(key)) {
return 0;
}
int index = ((Enum<?>) key).ordinal();
int oldValue = values[index];
if (oldValue == UNSET) {
return 0;
} else {
values[index] = UNSET;
size--;
modCount++;
return oldValue;
}
}
/** Throws an exception if key is not of the correct type for this enum set. */
private void typeCheck(K key) {
Class<?> keyClass = key.getClass();
if (keyClass != keyType && keyClass.getSuperclass() != keyType)
throw new ClassCastException(keyClass + " != " + keyType);
}
@Override
public int hashCode() {
int h = 0;
for (int i = 0; i < keys.length; i++) {
h += keys[i].hashCode() ^ values[i];
}
return h;
}
}