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}