/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.structures;
import java.util.NoSuchElementException;
import org.ws4d.java.util.WS4DIllegalStateException;
/**
* Class implements a map of keys associated with values. The speed of the hash
* map is linked to the quality of the hashCode() method of the key objects.
*/
public class HashMap {
private static final int MINIMAL_CAPACITY = 1;
private static final int INITIAL_CAPACITY = 16;
transient Entry[] buckets;
transient int size;
private transient int threshold;
private transient final int loadFactor = 1;
transient int mask;
transient int changes = 0;
// Views
transient Set entrySet;
transient Set keySet;
transient DataStructure values;
// -------------------------------------------
public HashMap(int initialCapacity) {
if (initialCapacity < 1) {
initialCapacity = MINIMAL_CAPACITY;
}
init(initialCapacity);
}
public HashMap() {
init(INITIAL_CAPACITY);
}
public HashMap(HashMap map) {
int capacity = map.buckets.length;
init(capacity);
putAll(map);
}
// ---------------------------- STATIC METHODS -----------------------------
private static int nextPowerOfTwo(final int number) {
int powerOfTwo = 1;
while (powerOfTwo < number) {
powerOfTwo <<= 1;
}
return powerOfTwo;
}
// -------------------------------------------------------
/**
* Init HashMap. May be overriden by extending class to do initialization.
*
* @param tableLength
*/
void init(final int tableLength) {
buckets = new Entry[tableLength];
size = 0;
threshold = tableLength * loadFactor;
mask = tableLength - 1;
}
protected Entry addEntry(final int bucketIndex, final int hash, final Object key, final Object value) {
Entry next = buckets[bucketIndex];
Entry newEntry = createEntry(hash, key, value, next);
buckets[bucketIndex] = newEntry;
if (next != null) {
next.previous = newEntry;
}
changes++;
size++;
return newEntry;
}
protected Entry createEntry(int hash, Object key, Object value, Entry next) {
return new Entry(hash, key, value, null, next);
}
public void clear() {
changes++;
for (int i = 0; i < buckets.length; i++) {
buckets[i] = null;
}
size = 0;
}
public boolean containsKey(Object obj) {
return (getEntry(obj) != null);
}
public boolean containsValue(Object obj) {
for (int i = 0; i < buckets.length; i++) {
for (Entry entry = buckets[i]; entry != null; entry = entry.next) {
if ((entry.value == obj) || ((entry.value != null) && (entry.value.equals(obj)))) {
return true;
}
}
}
return false;
}
/**
* Get associated value to given key. A key == <code>null</code> will always
* return <code>null</code>;
*
* @param key the key to lookup
* @return the value for the given key or <code>null</code>
*/
public Object get(Object key) {
Entry entry = getEntry(key);
if (entry != null) {
return entry.value;
}
return null;
}
Entry getEntry(final Object key) {
int hash = (key == null) ? 0 : key.hashCode();
return getEntry(hash & mask, hash, key);
}
Entry getEntry(final int bucketIndex, final int hash, final Object key) {
for (Entry entry = buckets[bucketIndex]; entry != null; entry = entry.next) {
if ((entry.hash == hash) && ((entry.key == key) || entry.key.equals(key))) {
return entry;
}
}
return null;
}
public boolean isEmpty() {
return size() == 0;
}
public Object put(Object key, Object value) {
int hash = 0;
int bucketIndex = 0;
if (key != null) {
hash = key.hashCode();
bucketIndex = hash & mask;
}
Entry entry = getEntry(bucketIndex, hash, key);
if (entry != null) {
Object oldValue = entry.value;
entry.value = value;
return oldValue;
}
if (size >= threshold) {
resize(buckets.length << 1 + 1);
return put(key, value);
}
addEntry(bucketIndex, hash, key, value);
return null;
}
public void putAll(HashMap map) {
int keysToAdd = map.size();
if (keysToAdd == 0) return;
int targetSize = size + keysToAdd;
if (targetSize > threshold) {
resize(nextPowerOfTwo(targetSize / loadFactor + 1));
}
HashMap.Entry entry = null;
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
entry = (HashMap.Entry) iterator.next();
put(entry.getKey(), entry.getValue());
}
}
private void putAll0(final Entry[] entries) {
for (int i = 0; i < entries.length; i++) {
for (Entry entry = entries[i]; entry != null;) {
Entry next = entry.next;
int index = entry.hash & mask;
entry.previous = null;
Entry existingEntry = buckets[index];
if (existingEntry != null) {
existingEntry.previous = entry;
}
entry.next = existingEntry;
buckets[index] = entry;
entry = next;
}
}
}
private void resize(final int newTableLength) {
Entry[] oldBuckets = buckets;
buckets = new Entry[newTableLength];
threshold = newTableLength * loadFactor;
mask = newTableLength - 1;
putAll0(oldBuckets);
changes++;
}
public Object remove(Object key) {
int hash = 0;
int bucketIndex = 0;
if (key != null) {
hash = key.hashCode();
bucketIndex = hash & mask;
}
for (Entry entry = buckets[bucketIndex]; entry != null; entry = entry.next) {
if ((entry.hash == hash) && ((entry.key == key) || (entry.key.equals(key)))) {
removeEntry(bucketIndex, entry);
Object value = entry.value;
entry.value = null;
return value;
}
}
return null;
}
protected void removeEntry(final int bucketIndex, final Entry entry) {
Entry previous = entry.previous;
Entry next = entry.next;
if (previous != null)
previous.next = next;
else
buckets[bucketIndex] = next;
if (next != null) next.previous = previous;
entry.previous = null;
entry.next = null;
changes++;
size--;
}
public int size() {
return size;
}
// ---------------------------- CREATE VIEWS -------------------------------
public Set entrySet() {
return (entrySet != null) ? entrySet : (entrySet = new EntrySet());
}
public Set keySet() {
return (keySet != null) ? keySet : (keySet = new KeySet());
}
public DataStructure values() {
return (values != null) ? values : (values = new Values());
}
// -------------------- OVERRIDDEN OBJECT METHODS --------------------
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if ((obj == null) || !(obj instanceof HashMap)) {
return false;
}
HashMap other = (HashMap) obj;
if (other.size() != size()) {
return false;
}
try {
Iterator it = entrySet().iterator();
while (it.hasNext()) {
HashMap.Entry entry = (HashMap.Entry) it.next();
Object key = entry.getKey();
Object value = entry.getValue();
if (!other.containsKey(key)) {
return false;
} else {
Object otherValue = other.get(key);
if (value == null) {
if (otherValue != null) {
return false;
}
} else if (!value.equals(otherValue)) {
return false;
}
}
}
} catch (ClassCastException e) {
return false;
} catch (NullPointerException e) {
return false;
}
return true;
}
public int hashCode() {
int hashCode = 0;
Iterator it = entrySet().iterator();
while (it.hasNext()) {
hashCode += it.next().hashCode();
}
return hashCode;
}
public String toString() {
int size = size();
if (size == 0) {
return "{}";
}
StringBuffer sb = new StringBuffer(32 * size);
sb.append("{");
Iterator it = entrySet().iterator();
boolean hasNext = it.hasNext();
while (hasNext) {
HashMap.Entry entry = (HashMap.Entry) it.next();
Object key = entry.getKey();
Object value = entry.getValue();
sb.append(key == this ? "<Map>" : key).append("=").append(value == this ? "<Map>" : value);
hasNext = it.hasNext();
if (hasNext) {
sb.append(", ");
}
}
sb.append('}');
return sb.toString();
}
// ========================= INNER CLASSES ===============================
protected abstract class AbstractMapIterator implements Iterator {
int currentBucketIndex = -1;
private Entry current;
int nextBucketIndex = -1;
Entry next;
int changesIt;
AbstractMapIterator() {
changesIt = changes;
nextBucket();
}
private final void nextBucket() {
if (size > 0) {
while (++nextBucketIndex < buckets.length && (next = buckets[nextBucketIndex]) == null) {
// void
}
}
}
public void remove() {
checkChanges();
if (current == null) {
throw new WS4DIllegalStateException();
}
removeEntry(currentBucketIndex, current);
current = null;
changesIt = changes;
}
public boolean hasNext() {
return (next != null);
}
Entry nextEntry() {
checkChanges();
if (next == null) {
throw new NoSuchElementException();
}
current = next;
next = current.next;
currentBucketIndex = nextBucketIndex;
if (next == null) {
nextBucket();
}
return current;
}
final void checkChanges() {
if (changes != changesIt) {
throw new ConcurrentChangeException();
}
}
}
// --------------------------------- CLASS ENTRY
// ---------------------------------
public static class Entry {
/** The hash code of the key */
int hash;
/** The key */
Object key;
/** The value */
Object value;
/** The previous entry in the hash chain */
Entry previous;
/** The next entry in the hash chain */
Entry next;
Entry(int hash, Object key, Object value, Entry previous, Entry next) {
this.hash = hash;
this.key = key;
this.value = value;
this.previous = previous;
this.next = next;
}
public final Object getKey() {
return key;
}
public final Object getValue() {
return value;
}
public final boolean equals(Object obj) {
if (!(obj instanceof HashMap.Entry)) {
return false;
}
HashMap.Entry entry = (HashMap.Entry) obj;
Object key = getKey();
Object keyOther = entry.getKey();
if (key == keyOther || (key != null && key.equals(keyOther))) {
Object value = getValue();
Object valueOther = entry.getValue();
if (value == valueOther || (value != null && value.equals(valueOther))) {
return true;
}
}
return false;
}
public final int hashCode() {
return key.hashCode() ^ ((value != null) ? value.hashCode() : 0);
}
public String toString() {
return new StringBuffer().append(getKey()).append('=').append(getValue()).toString();
}
}
// ------------------- CLASS ENTRY SET ----------------------
protected class EntrySet extends Set {
public Iterator iterator() {
return new AbstractMapIterator() {
public Object next() {
return nextEntry();
}
};
}
public void clear() {
HashMap.this.clear();
}
public boolean contains(Object obj) {
if (obj instanceof HashMap.Entry) {
HashMap.Entry entry = (HashMap.Entry) obj;
Object key = entry.getKey();
int hash = (key == null) ? 0 : key.hashCode();
Entry foundEntry = getEntry(hash & mask, hash, key);
return entry.equals(foundEntry);
}
return false;
}
public boolean remove(Object obj) {
if (obj instanceof HashMap.Entry) {
HashMap.Entry entry = (HashMap.Entry) obj;
Object key = entry.getKey();
int hash = 0;
int bucketIndex = 0;
if (key != null) {
hash = key.hashCode();
bucketIndex = hash & mask;
}
Entry foundEntry = getEntry(bucketIndex, hash, key);
if (foundEntry == null) {
return false;
}
if ((foundEntry.value == entry.getValue()) || ((foundEntry.value != null) && (foundEntry.value.equals(entry.getValue())))) {
removeEntry(bucketIndex, foundEntry);
return true;
}
}
return false;
}
public int size() {
return size;
}
}
// ------------------- CLASS KEY SET ----------------------
protected class KeySet extends Set {
public Iterator iterator() {
return new AbstractMapIterator() {
public Object next() {
return nextEntry().key;
}
};
}
public void clear() {
HashMap.this.clear();
}
public boolean contains(Object obj) {
return containsKey(obj);
}
public boolean remove(Object obj) {
return (HashMap.this.remove(obj) != null);
}
public int size() {
return size;
}
}
// ------------------- CLASS VALUES ----------------------
protected class Values extends DataStructure {
public Iterator iterator() {
return new AbstractMapIterator() {
public Object next() {
return nextEntry().value;
}
};
}
public void clear() {
HashMap.this.clear();
}
public boolean contains(Object obj) {
return containsValue(obj);
}
public int size() {
return size;
}
}
}