001/*
002 * Copyright (C) 2017 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect;
018
019import static com.google.common.base.Preconditions.checkElementIndex;
020import static com.google.common.collect.CollectPreconditions.checkPositive;
021import static com.google.common.collect.CollectPreconditions.checkRemove;
022
023import com.google.common.annotations.GwtCompatible;
024import com.google.common.collect.Multiset.Entry;
025import com.google.errorprone.annotations.CanIgnoreReturnValue;
026import java.util.Arrays;
027import java.util.Iterator;
028import java.util.NoSuchElementException;
029import java.util.Set;
030import javax.annotation.Nullable;
031
032/** EnumCountHashMap is an implementation of {@code AbstractObjectCountMap} with enum type keys. */
033@GwtCompatible(serializable = true, emulated = true)
034public class EnumCountHashMap<K extends Enum<K>> extends AbstractObjectCountMap<K> {
035
036  /** Creates an empty {@code EnumCountHashMap} instance. */
037  public static <K extends Enum<K>> EnumCountHashMap<K> create(Class<K> type) {
038    return new EnumCountHashMap<K>(type);
039  }
040
041  private final Class<K> keyType;
042
043  /** Constructs a new empty instance of {@code EnumCountHashMap}. */
044  EnumCountHashMap(Class<K> keyType) {
045    this.keyType = keyType;
046    this.keys = keyType.getEnumConstants();
047    if (this.keys == null) {
048      throw new IllegalStateException("Expected Enum class type, but got " + keyType.getName());
049    }
050    this.values = new int[this.keys.length];
051    Arrays.fill(values, 0, this.keys.length, UNSET);
052  }
053
054  @Override
055  int firstIndex() {
056    for (int i = 0; i < this.keys.length; i++) {
057      if (values[i] > 0) {
058        return i;
059      }
060    }
061    return -1;
062  }
063
064  @Override
065  int nextIndex(int index) {
066    for (int i = index + 1; i < this.keys.length; i++) {
067      if (values[i] > 0) {
068        return i;
069      }
070    }
071    return -1;
072  }
073
074  private abstract class EnumIterator<T> extends Itr<T> {
075    int nextIndex = UNSET;
076
077    @Override
078    public boolean hasNext() {
079      while (index < values.length && values[index] <= 0) {
080        index++;
081      }
082      return index != values.length;
083    }
084
085    @Override
086    public T next() {
087      checkForConcurrentModification();
088      if (!hasNext()) {
089        throw new NoSuchElementException();
090      }
091      nextCalled = true;
092      nextIndex = index;
093      return getOutput(index++);
094    }
095
096    @Override
097    public void remove() {
098      checkForConcurrentModification();
099      checkRemove(nextCalled);
100      expectedModCount++;
101      removeEntry(nextIndex);
102      nextCalled = false;
103      nextIndex = UNSET;
104      index--;
105    }
106  }
107
108  @Override
109  Set<K> createKeySet() {
110    return new KeySetView() {
111      private Object[] getFilteredKeyArray() {
112        Object[] filteredKeys = new Object[size];
113        for (int i = 0, j = 0; i < keys.length; i++) {
114          if (values[i] != UNSET) {
115            filteredKeys[j++] = keys[i];
116          }
117        }
118        return filteredKeys;
119      }
120
121      @Override
122      public Object[] toArray() {
123        return getFilteredKeyArray();
124      }
125
126      @Override
127      public <T> T[] toArray(T[] a) {
128        return ObjectArrays.toArrayImpl(getFilteredKeyArray(), 0, size, a);
129      }
130
131      @Override
132      public Iterator<K> iterator() {
133        return new EnumIterator<K>() {
134          @SuppressWarnings("unchecked")
135          @Override
136          K getOutput(int entry) {
137            return (K) keys[entry];
138          }
139        };
140      }
141    };
142  }
143
144  @Override
145  Multiset.Entry<K> getEntry(int index) {
146    checkElementIndex(index, size);
147    return new EnumMapEntry(index);
148  }
149
150  class EnumMapEntry extends MapEntry {
151    EnumMapEntry(int index) {
152      super(index);
153    }
154
155    @SuppressWarnings("unchecked")
156    @Override
157    public int getCount() {
158      return values[lastKnownIndex] == UNSET ? 0 : values[lastKnownIndex];
159    }
160
161    @SuppressWarnings("unchecked")
162    @Override
163    public int setCount(int count) {
164      if (values[lastKnownIndex] == UNSET) {
165        put(key, count);
166        return 0;
167      } else {
168        int old = values[lastKnownIndex];
169        values[lastKnownIndex] = count;
170        return old == UNSET ? 0 : old;
171      }
172    }
173  }
174
175  @Override
176  Set<Entry<K>> createEntrySet() {
177    return new EntrySetView() {
178      @Override
179      public Iterator<Entry<K>> iterator() {
180        return new EnumIterator<Entry<K>>() {
181          @Override
182          Entry<K> getOutput(int entry) {
183            return new EnumMapEntry(entry);
184          }
185        };
186      }
187    };
188  }
189
190  @Override
191  public void clear() {
192    modCount++;
193    if (keys != null) {
194      Arrays.fill(values, 0, values.length, UNSET);
195      this.size = 0;
196    }
197  }
198
199  /** Returns true if key is of the proper type to be a key in this enum map. */
200  private boolean isValidKey(Object key) {
201    if (key == null) return false;
202
203    // Cheaper than instanceof Enum followed by getDeclaringClass
204    Class<?> keyClass = key.getClass();
205    return keyClass == keyType || keyClass.getSuperclass() == keyType;
206  }
207
208  @Override
209  public boolean containsKey(@Nullable Object key) {
210    return isValidKey(key) && values[((Enum<?>) key).ordinal()] != UNSET;
211  }
212
213  @Override
214  public int get(@Nullable Object key) {
215    return containsKey(key) ? values[((Enum<?>) key).ordinal()] : 0;
216  }
217
218  @Override
219  int indexOf(@Nullable Object key) {
220    if (!isValidKey(key)) {
221      return -1;
222    }
223    return ((Enum<?>) key).ordinal();
224  }
225
226  @CanIgnoreReturnValue
227  @Override
228  int removeEntry(int entryIndex) {
229    return remove(keys[entryIndex]);
230  }
231
232  @CanIgnoreReturnValue
233  @Override
234  public int put(@Nullable K key, int value) {
235    checkPositive(value, "count");
236    typeCheck(key);
237    int index = key.ordinal();
238    int oldValue = values[index];
239    values[index] = value;
240    modCount++;
241    if (oldValue == UNSET) {
242      size++;
243      return 0;
244    }
245    return oldValue;
246  }
247
248  @CanIgnoreReturnValue
249  @Override
250  public int remove(@Nullable Object key) {
251    if (!isValidKey(key)) {
252      return 0;
253    }
254    int index = ((Enum<?>) key).ordinal();
255    int oldValue = values[index];
256    if (oldValue == UNSET) {
257      return 0;
258    } else {
259      values[index] = UNSET;
260      size--;
261      modCount++;
262      return oldValue;
263    }
264  }
265
266  /** Throws an exception if key is not of the correct type for this enum set. */
267  private void typeCheck(K key) {
268    Class<?> keyClass = key.getClass();
269    if (keyClass != keyType && keyClass.getSuperclass() != keyType)
270      throw new ClassCastException(keyClass + " != " + keyType);
271  }
272
273  @Override
274  public int hashCode() {
275    int h = 0;
276    for (int i = 0; i < keys.length; i++) {
277      h += keys[i].hashCode() ^ values[i];
278    }
279    return h;
280  }
281}