/** * Copyright 2001-2004 The Apache Software Foundation * * 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.scratchdisk.util; import java.util.AbstractCollection; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * Hashtable-based map with integer keys that allows values to be removed * by the garbage collector.<P> * * The algorithms used are basically the same as those * in {@link java.util.HashMap}. In particular, you * can specify a load factor and capacity to suit your * needs. * * This map does <I>not</I> allow null values. Attempting to add a null * value to the map will raise a <Code>NullPointerException</Code>.<P> * * This data structure is not synchronized. * * @see java.lang.ref.Reference */ public class IntMap<V> extends AbstractMap<Integer, V> { protected static class Entry<V> implements Map.Entry<Integer, V>{ protected int key; protected Entry<V> next; /** * Reference value. Note this can never be null. */ protected V value; public Entry(int key, V value, Entry<V> next) { this.key = key; this.value = value; this.next = next; } public V getValue() { return value; } public Integer getKey() { return key; } public V setValue(V value) { V prev = this.value; this.value = value; return prev; } } int entryCount; /** * The threshold variable is calculated by multiplying * table.length and loadFactor. * Note: I originally marked this field as final, but then this class * didn't compile under JDK1.2.2. * @serial */ float loadFactor; /** * Number of mappings in this map. */ transient int size; /** * The hash table. Its length is always a power of two. */ transient Entry<V>[] table; /** * When size reaches threshold, the map is resized. * @see #resize() */ transient int threshold; /** * Number of times this map has been modified. */ transient volatile int modCount; /** * Cached key set. May be null if key set is never accessed. */ transient Set<Integer> keySet; /** * Cached entry set. May be null if entry set is never accessed. */ transient Set<Map.Entry<Integer, V>> entrySet; /** * Cached values. May be null if values() is never accessed. */ transient Collection<V> values; /** * Constructs a new <Code>IntMap</Code> */ public IntMap() { this(16, 0.75f); } /** * Constructs a new <Code>IntMap</Code> with the * specified load factor and initial capacity. * * @param capacity the initial capacity for the map * @param loadFactor the load factor for the map */ @SuppressWarnings("unchecked") public IntMap(int capacity, float loadFactor) { super(); if (capacity <= 0) throw new IllegalArgumentException("capacity must be positive"); if ((loadFactor <= 0.0f) || (loadFactor >= 1.0f)) throw new IllegalArgumentException("Load factor must be greater than 0 and less than 1."); int initialSize = 1; while (initialSize < capacity) initialSize *= 2; this.table = new Entry[initialSize]; this.loadFactor = loadFactor; this.threshold = (int) (initialSize * loadFactor); } /** * @param key */ protected V doRemove(int key) { int index = indexFor(key); Entry<V> previous = null; Entry<V> entry = table[index]; while (entry != null) { if (key == entry.key) { if (previous == null) table[index] = entry.next; else previous.next = entry.next; this.size--; modCount++; return entry.getValue(); } previous = entry; entry = entry.next; } return null; } /** * Converts the given hash code into an index into the * hash table. */ private int indexFor(int hash) { // mix the bits to avoid bucket collisions... hash += ~(hash << 15); hash ^= (hash >>> 10); hash += (hash << 3); hash ^= (hash >>> 6); hash += ~(hash << 11); hash ^= (hash >>> 16); return hash & (table.length - 1); } /** * Returns the entry associated with the given key. * * @param key the key of the entry to look up * @return the entry associated with that key, or null * if the key is not in this map */ public Entry<V> getEntry(int key) { for (Entry<V> entry = table[indexFor(key)]; entry != null; entry = entry.next) if (entry.key == key) return entry; return null; } protected void purge() { } /** * Returns the size of this map. * * @return the size of this map */ public int size() { purge(); return size; } /** * Returns <Code>true</Code> if this map is empty. * * @return <Code>true</Code> if this map is empty */ public boolean isEmpty() { purge(); return size == 0; } /** * Returns <Code>true</Code> if this map contains the given key. * * @return true if the given key is in this map */ public boolean containsKey(int key) { purge(); Entry entry = getEntry(key); if (entry == null) return false; return entry.getValue() != null; } public boolean containsKey(Integer key) { return containsKey(key.intValue()); } /** * java.util.Map compatible version of containsKey */ public boolean containsKey(Object key) { return key instanceof Number && containsKey(((Number) key).intValue()); } /** * Returns the value associated with the given key, if any. * * @return the value associated with the given key, or <Code>null</Code> * if the key maps to no value */ public V get(int key) { purge(); Entry<V> entry = getEntry(key); if (entry == null) return null; return entry.getValue(); } public V get(Integer key) { return this.get(key.intValue()); } /** * java.util.Map compatible version of get */ public V get(Object key) { if (key instanceof Number) return get(((Number) key).intValue()); return null; } /** * Constructs a new table entry for the given data * * @param key The entry key * @param value The entry value * @param next The next value in the entry's collision chain * @return The new table entry */ protected Entry<V> createEntry(int key, V value, Entry<V> next) { return new Entry<V>(key, value, next); } /** * Associates the given key with the given value. * <P> * Neither the key nor the value may be null. * * @param key the key of the mapping * @param value the value of the mapping * @throws NullPointerException if either the key or value is null */ public V put(int key, V value) { if (value == null) throw new NullPointerException("null values not allowed"); purge(); if (size + 1 > threshold) resize(); int index = indexFor(key); Entry<V> previous = null; Entry<V> entry = table[index]; while (entry != null) { if (key == entry.key) { V result = entry.getValue(); if (previous == null) table[index] = createEntry(key, value, entry.next); else previous.next = createEntry(key, value, entry.next); return result; } previous = entry; entry = entry.next; } this.size++; modCount++; table[index] = createEntry(key, value, table[index]); entryCount++; return null; } public V put(Integer key, V value) { return put(key.intValue(), value); } /** * Removes the key and its associated value from this map. * * @param key the key to remove * @return the value associated with that key, or null if the key was not in * the map */ public V remove(int key) { purge(); return doRemove(key); } public V remove(Integer key) { return remove(key.intValue()); } /** * java.util.Map compatible version of remove */ public V remove(Object key) { if (key instanceof Number) return remove(((Number) key).intValue()); return null; } /** * Clears this map. */ public void clear() { Arrays.fill(table, null); size = 0; } /** * Resizes this hash table by doubling its capacity. * This is an expensive operation, as entries must * be copied from the old smaller table to the new * bigger table. */ @SuppressWarnings("unchecked") private void resize() { Entry<V>[] old = table; table = new Entry[old.length * 2]; for (int i = 0; i < old.length; i++) { Entry<V> next = old[i]; while (next != null) { Entry<V> entry = next; next = next.next; int index = indexFor(entry.key); entry.next = table[index]; table[index] = entry; } old[i] = null; } threshold = (int) (table.length * loadFactor); } public Integer keyOf(Object value) { purge(); Iterator<Map.Entry<Integer, V>> i = entrySet().iterator(); while (i.hasNext()) { Map.Entry<Integer, V> e = i.next(); if (value.equals(e.getValue())) return e.getKey(); } return null; } /** * Returns a set view of this map's entries. * * @return a set view of this map's entries */ public Set<Map.Entry<Integer, V>> entrySet() { if (entrySet != null) return entrySet; entrySet = new AbstractSet<Map.Entry<Integer, V>>() { public int size() { return IntMap.this.size(); } public void clear() { IntMap.this.clear(); } public boolean contains(Object o) { if (o != null && o instanceof Map.Entry) { Map.Entry e = (Map.Entry) o; Object k = e.getKey(); if (k instanceof Number) { Entry e2 = getEntry(((Number) k).intValue()); return (e2 != null) && e.equals(e2); } } return false; } public boolean remove(Object o) { boolean r = contains(o); if (r) { Map.Entry e = (Map.Entry) o; Object k = e.getKey(); if (k instanceof Number) IntMap.this.remove(((Number) k).intValue()); else r = false; } return r; } public Iterator<Map.Entry<Integer, V>> iterator() { return new EntryIterator(); } public Object[] toArray() { return toArray(new Object[0]); } }; return entrySet; } /** * Returns a set view of this map's keys. * * @return a set view of this map's keys */ public Set<Integer> keySet() { if (keySet != null) return keySet; keySet = new AbstractSet<Integer>() { public int size() { return size; } public Iterator<Integer> iterator() { return new KeyIterator(); } public boolean contains(Object o) { return o instanceof Number ? containsKey(((Number) o).intValue()) : false; } public boolean remove(Object o) { return o instanceof Number ? IntMap.this.remove(((Number) o).intValue()) != null : false; } public void clear() { IntMap.this.clear(); } }; return keySet; } /** * Returns a collection view of this map's values. * * @return a collection view of this map's values. */ public Collection<V> values() { if (values != null) return values; values = new AbstractCollection<V>() { public int size() { return size; } public void clear() { IntMap.this.clear(); } public Iterator<V> iterator() { return new ValueIterator(); } }; return values; } private abstract class AbstractIterator<E> implements Iterator<E> { // These fields keep track of where we are in the table. int index; Entry<V> entry; Entry<V> previous; // These Object fields provide hard references to the // current and next entry; this assures that if hasNext() // returns true, next() will actually return a valid element. int currentKey, nextKey; Object nextValue; int expectedModCount; public AbstractIterator() { index = (size() != 0 ? table.length : 0); // have to do this here! size() invocation above // may have altered the modCount. expectedModCount = modCount; } public boolean hasNext() { checkMod(); while (nextNull()) { Entry<V> e = entry; int i = index; while ((e == null) && (i > 0)) { i--; e = table[i]; } entry = e; index = i; if (e == null) { currentKey = -1; return false; } nextKey = e.key; nextValue = e.getValue(); if (nextNull()) entry = entry.next; } return true; } private void checkMod() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } private boolean nextNull() { return (nextKey == -1) || (nextValue == null); } protected Entry<V> nextEntry() { checkMod(); if (nextNull() && !hasNext()) throw new NoSuchElementException(); previous = entry; entry = entry.next; currentKey = nextKey; nextKey = -1; nextValue = null; return previous; } public void remove() { checkMod(); if (previous == null) throw new IllegalStateException(); IntMap.this.remove(currentKey); previous = null; currentKey = -1; expectedModCount = modCount; } } private class EntryIterator extends AbstractIterator<Map.Entry<Integer, V>> { public Map.Entry<Integer, V> next() { return nextEntry(); } } private class ValueIterator extends AbstractIterator<V> { public V next() { return nextEntry().getValue(); } } private class KeyIterator extends AbstractIterator<Integer> { public Integer next() { return new Integer(nextEntry().key); } } }