/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* This hash map uses weak references, so that elements that are no longer
* referenced elsewhere can be garbage collected. It also uses object identity
* to compare keys. The garbage collection happens when trying to add new data,
* or when resizing.
*
* @param <K> the keys
* @param <V> the value
*/
public class WeakIdentityHashMap<K, V> implements Map<K, V> {
private static final int MAX_LOAD = 90;
private static final WeakReference<Object> DELETED_KEY =
new WeakReference<Object>(null);
private int mask, len, size, deletedCount, level;
private int maxSize, minSize, maxDeleted;
private WeakReference<K>[] keys;
private V[] values;
public WeakIdentityHashMap() {
reset(2);
}
public int size() {
return size;
}
private void checkSizePut() {
if (deletedCount > size) {
rehash(level);
}
if (size + deletedCount >= maxSize) {
rehash(level + 1);
}
}
private void checkSizeRemove() {
if (size < minSize && level > 0) {
rehash(level - 1);
} else if (deletedCount > maxDeleted) {
rehash(level);
}
}
private int getIndex(Object key) {
return System.identityHashCode(key) & mask;
}
@SuppressWarnings("unchecked")
private void reset(int newLevel) {
minSize = size * 3 / 4;
size = 0;
level = newLevel;
len = 2 << level;
mask = len - 1;
maxSize = (int) (len * MAX_LOAD / 100L);
deletedCount = 0;
maxDeleted = 20 + len / 2;
keys = new WeakReference[len];
values = (V[]) new Object[len];
}
public V put(K key, V value) {
checkSizePut();
int index = getIndex(key);
int plus = 1;
int deleted = -1;
do {
WeakReference<K> k = keys[index];
if (k == null) {
// found an empty record
if (deleted >= 0) {
index = deleted;
deletedCount--;
}
size++;
keys[index] = new WeakReference<K>(key);
values[index] = value;
return null;
} else if (k == DELETED_KEY) {
if (deleted < 0) {
// found the first deleted record
deleted = index;
}
} else {
Object r = k.get();
if (r == null) {
delete(index);
} else if (r == key) {
// update existing
V old = values[index];
values[index] = value;
return old;
}
}
index = (index + plus++) & mask;
} while(plus <= len);
throw new RuntimeException("Hashmap is full");
}
public V remove(Object key) {
checkSizeRemove();
int index = getIndex(key);
int plus = 1;
do {
WeakReference<K> k = keys[index];
if (k == null) {
// found an empty record
return null;
} else if (k == DELETED_KEY) {
// continue
} else {
Object r = k.get();
if (r == null) {
delete(index);
} else if (r == key) {
// found the record
V old = values[index];
delete(index);
return old;
}
}
index = (index + plus++) & mask;
k = keys[index];
} while(plus <= len);
// not found
return null;
}
@SuppressWarnings("unchecked")
private void delete(int index) {
keys[index] = (WeakReference<K>) DELETED_KEY;
values[index] = null;
deletedCount++;
size--;
}
private void rehash(int newLevel) {
WeakReference<K>[] oldKeys = keys;
V[] oldValues = values;
reset(newLevel);
for (int i = 0; i < oldKeys.length; i++) {
WeakReference<K> k = oldKeys[i];
if (k != null && k != DELETED_KEY) {
K key = k.get();
if (key != null) {
put(key, oldValues[i]);
}
}
}
}
public V get(Object key) {
int index = getIndex(key);
int plus = 1;
do {
WeakReference<K> k = keys[index];
if (k == null) {
return null;
} else if (k == DELETED_KEY) {
// continue
} else {
Object r = k.get();
if (r == null) {
delete(index);
} else if (r == key) {
return values[index];
}
}
index = (index + plus++) & mask;
} while(plus <= len);
return null;
}
public void clear() {
reset(2);
}
public boolean containsKey(Object key) {
return get(key) != null;
}
public boolean containsValue(Object value) {
if (value == null) {
return false;
}
for (V item: values) {
if (value.equals(item)) {
return true;
}
}
return false;
}
public Set<java.util.Map.Entry<K, V>> entrySet() {
throw new UnsupportedOperationException();
}
public boolean isEmpty() {
return size == 0;
}
public Set<K> keySet() {
throw new UnsupportedOperationException();
}
public void putAll(Map<? extends K, ? extends V> m) {
throw new UnsupportedOperationException();
}
public Collection<V> values() {
throw new UnsupportedOperationException();
}
}