/** * Copyright 2012 Jason Sorensen (sorensenj@smert.net) * * 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 net.smert.frameworkgl.utils; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; /** * * @author Jason Sorensen <sorensenj@smert.net> * @param <V> */ public class HashMapIntGeneric<V> { private static int DEFAULT_INITIAL_CAPACITY = 16; private static int MAXIMUM_CAPACITY = 1 << 30; private static int NULL_BUCKET = 0; private static float DEFAULT_LOAD_FACTOR = .75f; // Not a true "not found" setting, just an unlikely value. Since we don't allow // for NULL there is no value which can be used. Means you can't have this value // under normal conditions. public static int NULL_KEY = -Integer.MAX_VALUE + 1; private float loadFactor; private int modCount; private int size; private int threshold; private Collection<V> values; private Entry<V>[] table; private Set<Entry<V>> entrySet; private Set<Integer> keySet; public HashMapIntGeneric() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } public HashMapIntGeneric(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMapIntGeneric(int initialCapacity, float loadFactor) { if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); } if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Illegal load factor: " + loadFactor); } if (initialCapacity > MAXIMUM_CAPACITY) { initialCapacity = MAXIMUM_CAPACITY; } int capacity = 1; while (capacity < initialCapacity) { capacity <<= 1; } this.loadFactor = loadFactor; modCount = 0; size = 0; threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; entrySet = null; keySet = null; } private void addEntry(int hash, int key, V value, int bucketIndex) { if ((size >= threshold) && (table[bucketIndex] != null)) { resize(2 * table.length); bucketIndex = indexFor(hash, table.length); } Entry<V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } private void copyTable(Entry[] newTable) { int newCapacity = newTable.length; for (Entry<V> e : table) { while (e != null) { Entry<V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } } private Entry<V> getEntry(int key) { if (size == 0) { return null; } int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<V> e = table[i]; e != null; e = e.next) { if ((e.hash == hash) && (e.key == key)) { return e; } } return null; } private V getForNullKey() { if (size == 0) { return null; } for (Entry<V> e = table[NULL_BUCKET]; e != null; e = e.next) { if (e.key == NULL_KEY) { return e.value; } } return null; } private int hash(int k) { int h = k; h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } private int indexFor(int hash, int length) { return hash & (length - 1); } private Iterator<Entry<V>> newEntryIterator() { return new EntryIterator(); } private Iterator<Integer> newKeyIterator() { return new KeyIterator(); } private Iterator<V> newValueIterator() { return new ValueIterator(); } private V putForNullKey(V value) { for (Entry<V> e = table[NULL_BUCKET]; e != null; e = e.next) { if (e.key == NULL_KEY) { V oldValue = e.value; e.value = value; return oldValue; } } modCount++; addEntry(0, NULL_KEY, value, 0); return null; } private Entry<V> removeEntryForKey(int key) { if (size == 0) { return null; } int hash = (key == NULL_KEY) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry<V> prev = table[i]; Entry<V> e = prev; while (e != null) { Entry<V> next = e.next; if ((e.hash == hash) && (e.key == key)) { modCount++; size--; if (prev == e) { table[i] = next; } else { prev.next = next; } return e; } prev = e; e = next; } return null; } private Entry<V> removeEntry(Entry<V> entry) { if (size == 0) { return null; } int key = entry.getKey(); int hash = (key == NULL_KEY) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry<V> prev = table[i]; Entry<V> e = prev; while (e != null) { Entry<V> next = e.next; if ((e.hash == hash) && e.equals(entry)) { modCount++; size--; if (prev == e) { table[i] = next; } else { prev.next = next; } return e; } prev = e; e = next; } return null; } private void resize(int newCapacity) { if (table.length == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; copyTable(newTable); table = newTable; threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } public void clear() { modCount++; Arrays.fill(table, null); size = 0; } public boolean containsKey(int key) { return getEntry(key) != null; } public boolean containsValue(Object value) { for (Entry<V> e : table) { for (Entry<V> ie = e; ie != null; ie = ie.next) { if (ie.value.equals(value)) { return true; } } } return false; } public Set<Entry<V>> entrySet() { Set<Entry<V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } public V get(int key) { if (key == NULL_KEY) { return getForNullKey(); } Entry<V> e = getEntry(key); return (e == null) ? null : e.getValue(); } public boolean isEmpty() { return size == 0; } public Set<Integer> keySet() { Set<Integer> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); } public V put(int key, V value) { if (key == NULL_KEY) { return putForNullKey(value); } int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<V> e = table[i]; e != null; e = e.next) { if ((e.hash == hash) && (e.key == key)) { V oldValue = e.value; e.value = value; return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } public V remove(int key) { Entry<V> e = removeEntryForKey(key); return (e == null) ? null : e.value; } public int size() { return size; } public Collection<V> values() { Collection<V> vs = values; return (vs != null ? vs : (values = new Values())); } public static class Entry<V> { private int hash; private int key; private Entry<V> next; private V value; public Entry(int h, int k, V v, Entry<V> n) { hash = h; key = k; next = n; value = v; } public V getValue() { return value; } public int getKey() { return key; } public V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } @Override public int hashCode() { int hash = 7; hash = 53 * hash + this.key; hash = 53 * hash + Objects.hashCode(this.value); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Entry<?> other = (Entry<?>) obj; if (this.key != other.key) { return false; } return Objects.equals(this.value, other.value); } @Override public String toString() { return getKey() + "=" + getValue(); } } private class EntryIterator extends HashIterator<Entry<V>> { @Override public Entry<V> next() { return nextEntry(); } } private class EntrySet extends AbstractSet<Entry<V>> { @Override public void clear() { HashMapIntGeneric.this.clear(); } @Override public boolean contains(Object o) { if (!(o instanceof Entry)) { return false; } Entry<V> e = (Entry<V>) o; Entry<V> candidate = getEntry(e.getKey()); return ((candidate != null) && candidate.equals(e)); } @Override public Iterator<Entry<V>> iterator() { return newEntryIterator(); } @Override public boolean remove(Object o) { if (!(o instanceof Entry)) { return false; } return removeEntry((Entry<V>) o) != null; } @Override public int size() { return size; } } private abstract class HashIterator<E> implements Iterator<E> { private int expectedModCount; private int index; private Entry<V> current; private Entry<V> next; public HashIterator() { expectedModCount = modCount; if (size > 0) { while ((index < table.length) && ((next = table[index++]) == null)); } } @Override public boolean hasNext() { return next != null; } public Entry<V> nextEntry() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } Entry<V> e = next; if (e == null) { throw new NoSuchElementException(); } if ((next = e.next) == null) { while ((index < table.length) && ((next = table[index++]) == null)); } current = e; return e; } @Override public void remove() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } if (current == null) { throw new IllegalStateException(); } int k = current.key; current = null; HashMapIntGeneric.this.removeEntryForKey(k); expectedModCount = modCount; } } private class KeyIterator extends HashIterator<Integer> { @Override public Integer next() { return nextEntry().getKey(); } } private class KeySet extends AbstractSet<Integer> { @Override public void clear() { HashMapIntGeneric.this.clear(); } @Override public boolean contains(Object o) { if (!(o instanceof Integer)) { return false; } return containsKey((Integer) o); } @Override public Iterator<Integer> iterator() { return newKeyIterator(); } @Override public boolean remove(Object o) { if (!(o instanceof Integer)) { return false; } return HashMapIntGeneric.this.removeEntryForKey((Integer) o) != null; } @Override public int size() { return size; } } private class Values extends AbstractCollection<V> { @Override public void clear() { HashMapIntGeneric.this.clear(); } @Override public boolean contains(Object o) { return containsValue(o); } @Override public Iterator<V> iterator() { return newValueIterator(); } @Override public int size() { return size; } } private class ValueIterator extends HashIterator<V> { @Override public V next() { return nextEntry().value; } } }