org.armedbear.lisp.WeakHashTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of abcl Show documentation
Show all versions of abcl Show documentation
Common Lisp implementation running on the JVM
/*
* HashTable.java
*
* Copyright (C) 2002-2007 Peter Graves
* Copyright (C) 2010 Erik Huelsmann
* Copyright (C) 2011 Mark Evenson
* $Id$
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under
* terms of your choice, provided that you also meet, for each linked
* independent module, the terms and conditions of the license of that
* module. An independent module is a module which is not derived from
* or based on this library. If you modify this library, you may extend
* this exception to your version of the library, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package org.armedbear.lisp;
import static org.armedbear.lisp.Lisp.*;
import java.lang.ref.WeakReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
// ??? Replace standard Hashtable when this code is working; maybe not
// because we have additional places for locking here.
//
// We can't simply extend HashTable as the methods returning HashEntry
// are referring to different types as HashEntry is internal to this
// class.
//
// XXX individuals are invited to figure out how to use Java generics
// to simplify/beautify things here, but I couldn't get the
// WeakHashTable type to be parameterized on an enclosed type.
public class WeakHashTable
extends LispObject
implements org.armedbear.lisp.protocol.Hashtable
{
protected static final float loadFactor = 0.75f;
protected final LispObject rehashSize;
protected final LispObject rehashThreshold;
/**
* The rounded product of the capacity and the load factor. When the number
* of elements exceeds the threshold, the implementation calls rehash().
*/
protected int threshold;
/** Array containing the actual key-value mappings. */
@SuppressWarnings("VolatileArrayField")
protected volatile HashEntry[] buckets;
/** The actual current number of key-value pairs. */
protected volatile int count;
final Comparator comparator;
final private ReentrantLock lock = new ReentrantLock();
HashEntry bucketType;
final LispObject weakness;
private WeakHashTable(Comparator c, int size, LispObject rehashSize,
LispObject rehashThreshold, LispObject weakness)
{
this.rehashSize = rehashSize;
this.rehashThreshold = rehashThreshold;
bucketType = null;
this.weakness = weakness;
if (weakness.equals(Keyword.KEY)) {
bucketType = this.new HashEntryWeakKey();
} else if (weakness.equals(Keyword.VALUE)) {
bucketType = this.new HashEntryWeakValue();
} else if (weakness.equals(Keyword.KEY_AND_VALUE)) {
bucketType = this.new HashEntryWeakKeyAndValue();
} else if (weakness.equals(Keyword.KEY_OR_VALUE)) {
bucketType = this.new HashEntryWeakKeyOrValue();
} else {
// We handle this check in the wrapping Lisp code.
assert false
: "Bad weakness argument to WeakHashTable type constructor.";
}
buckets = bucketType.makeArray(size);
threshold = (int) (size * loadFactor);
comparator = c;
}
protected static int calculateInitialCapacity(int size) {
int capacity = 1;
while (capacity < size) {
capacity <<= 1;
}
return capacity;
}
// XXX only WEAK references types are implemented for WeakHashTable.
// XXX This enum is currently unused in this code
enum ReferenceType {
NORMAL,
WEAK,
SOFT
}
// XXX This enum is currently unused in this code
enum WeaknessType {
/** KEY means that the key of an entry must be live to
guarantee that the entry is preserved. */
KEY,
/** VALUE means that the value of an entry must be live to
guarantee that the entry is preserved. */
VALUE,
/** KEY-AND-VALUE means that both the key and the value
must be live to guarantee that the entry is preserved. */
KEY_AND_VALUE,
/** KEY-OR-VALUE means that either the key or the value
must be live to guarantee that the entry is preserved. */
KEY_OR_VALUE
}
public static WeakHashTable newEqHashTable(int size, LispObject rehashSize,
LispObject rehashThreshold,
LispObject weakness)
{
return new WeakHashTable(new Comparator(), size,
rehashSize, rehashThreshold, weakness);
}
public static WeakHashTable newEqlHashTable(int size, LispObject rehashSize,
LispObject rehashThreshold,
LispObject weakness)
{
return new WeakHashTable(new EqlComparator(), size,
rehashSize, rehashThreshold, weakness);
}
public static WeakHashTable newEqualHashTable(int size, LispObject rehashSize,
LispObject rehashThreshold,
LispObject weakness)
{
return new WeakHashTable(new EqualComparator(), size,
rehashSize, rehashThreshold, weakness);
}
public static WeakHashTable newEqualpHashTable(int size, LispObject rehashSize,
LispObject rehashThreshold,
LispObject weakness)
{
return new WeakHashTable(new EqualpComparator(), size,
rehashSize, rehashThreshold, weakness);
}
public final LispObject getRehashSize() {
return rehashSize;
}
public final LispObject getRehashThreshold() {
return rehashThreshold;
}
/** How many hash buckets exist in the underlying data structure. */
public int getSize() {
HashEntry[] b = getTable();
return b.length;
}
/** Number of entries stored in the hash buckets. */
public int getCount() {
getTable(); // To force gc on entries
return count;
}
@Override
public LispObject typeOf() {
return Symbol.HASH_TABLE;
}
@Override
public LispObject classOf() {
return BuiltInClass.HASH_TABLE;
}
@Override
public LispObject typep(LispObject type) {
if (type == Symbol.HASH_TABLE) {
return T;
}
if (type == BuiltInClass.HASH_TABLE) {
return T;
}
return super.typep(type);
}
// XXX Not thread-safe as hash entries can be GCd "out from under"
// the invoking thread. But the HashTable implementation
// seemingly suffers from the same problem if entries are
// removed/added while this method executes.
@Override
public boolean equalp(LispObject obj) {
if (this == obj) {
return true;
}
if (obj instanceof WeakHashTable) {
WeakHashTable ht = (WeakHashTable) obj;
if (count != ht.count) {
return false;
}
if (getTest() != ht.getTest()) {
return false;
}
LispObject entries = ENTRIES();
while (entries != NIL) {
LispObject entry = entries.car();
LispObject key = entry.car();
LispObject value = entry.cdr();
if (!value.equalp(ht.get(key))) {
return false;
}
entries = entries.cdr();
}
return true;
}
return false;
}
@Override
public LispObject getParts() {
HashEntry[] b = getTable();;
LispObject parts = NIL;
for (int i = 0; i < b.length; i++) {
HashEntry e = b[i];
while (e != null) {
LispObject key = e.getKey();
LispObject value = e.getValue();
if (key != null && value != null) {
parts = parts.push(new Cons("KEY [bucket " + i + "]", key));
parts = parts.push(new Cons("VALUE", value));
} else {
assert false
: "Dangling hash entries encountered.";
}
e = e.getNext();
}
}
return parts.nreverse();
}
public void clear() {
lock.lock();
try {
buckets = bucketType.makeArray(buckets.length);
count = 0;
while (queue.poll() != null)
;
} finally {
lock.unlock();
}
}
// gethash key hash-table &optional default => value, present-p
public LispObject gethash(LispObject key) {
LispObject value = get(key);
final LispObject presentp;
if (value == null) {
value = presentp = NIL;
} else {
presentp = T;
}
return LispThread.currentThread().setValues(value, presentp);
}
// gethash key hash-table &optional default => value, present-p
public LispObject gethash(LispObject key, LispObject defaultValue) {
LispObject value = get(key);
final LispObject presentp;
if (value == null) {
value = defaultValue;
presentp = NIL;
} else {
presentp = T;
}
return LispThread.currentThread().setValues(value, presentp);
}
public LispObject gethash1(LispObject key) {
final LispObject value = get(key);
return value != null ? value : NIL;
}
public LispObject puthash(LispObject key, LispObject newValue) {
put(key, newValue);
return newValue;
}
// remhash key hash-table => generalized-boolean
public LispObject remhash(LispObject key) {
// A value in a Lisp hash table can never be null, so...
return remove(key) != null ? T : NIL;
}
@Override
public String printObject() {
if (Symbol.PRINT_READABLY.symbolValue(LispThread.currentThread()) != NIL) {
error(new PrintNotReadable(list(Keyword.OBJECT, this)));
return null; // Not reached.
}
StringBuilder sb = new StringBuilder(getTest().princToString());
sb.append(' ');
sb.append(Symbol.HASH_TABLE.princToString());
sb.append(' ');
if (bucketType instanceof HashEntryWeakKey) {
sb.append("WEAKNESS :KEY");
} else if (bucketType instanceof HashEntryWeakValue) {
sb.append("WEAKNESS :VALUE");
} else if (bucketType instanceof HashEntryWeakKeyAndValue) {
sb.append("WEAKNESS :KEY-AND-VALUE");
} else if (bucketType instanceof HashEntryWeakKeyOrValue) {
sb.append("WEAKNESS :KEY-OR-VALUE");
}
sb.append(' ');
sb.append(count);
if (count == 1) {
sb.append(" entry");
} else {
sb.append(" entries");
}
sb.append(", ");
sb.append(buckets.length);
sb.append(" buckets");
return unreadableString(sb.toString());
}
public Symbol getTest() {
return comparator.getTest();
}
public LispObject getWeakness() {
return weakness;
}
HashEntry[] getTable() {
lock.lock();
try {
bucketType.expungeQueue();
return buckets;
} finally {
lock.unlock();
}
}
protected HashEntry getEntry(LispObject key) {
HashEntry[] b = getTable();
int hash = comparator.hash(key);
HashEntry e = b[hash & (b.length - 1)];
while (e != null) {
if (hash == e.getHash()
&& (key == e.getKey()
|| comparator.keysEqual(key, e.getKey()))) {
return e;
}
e = e.getNext();
}
return null;
}
public LispObject get(LispObject key) {
HashEntry e = getEntry(key);
LispObject v = (e == null) ? null : e.getValue();
if (e == null || v != null) {
return v;
}
return e.getValue();
}
public void put(LispObject key, LispObject value) {
HashEntry e = getEntry(key);
if (e != null) {
e.setValue(value);
} else {
// Not found. We need to add a new entry.
if (++count > threshold) {
rehash();
}
int hash = comparator.hash(key);
int index = hash & (buckets.length - 1);
buckets[index] = bucketType.makeInstance(key, hash,
value, buckets[index],
index);
}
}
public LispObject remove(LispObject key) {
lock.lock();
try {
bucketType.expungeQueue();
int index = comparator.hash(key) & (buckets.length - 1);
HashEntry e = buckets[index];
HashEntry last = null;
while (e != null) {
LispObject entryKey = e.getKey();
if (entryKey == null) {
e.clear();
if (last == null) {
buckets[index] = e.getNext();
} else {
last.setNext(e.getNext());
}
--count;
} else if (comparator.keysEqual(key, entryKey)) {
e.clear();
if (last == null) {
buckets[index] = e.getNext();
} else {
last.setNext(e.getNext());
}
--count;
return e.getValue();
}
last = e;
e = e.getNext();
}
return null;
} finally {
lock.unlock();
}
}
/**
* Internal removal of the HashEntry associated with the
* Reference used for a hashtables with soft/weak references.
*/
private void remove(Reference ref) {
assert lock.isHeldByCurrentThread();
HashEntry entry = entryLookup.get(ref);
// assert entry != null
// : "Failed to find hash entry for reference.";
if (entry == null) {
return; // XXX how does this happen?
}
int index = entry.getSlot();
HashEntry e = this.buckets[index];
HashEntry last = null;
while (e != null) {
if (e.equals(entry)) {
if (last == null) {
this.buckets[index] = e.getNext();
} else {
last.setNext(e.getNext());
}
--count;
break;
}
last = e;
e = e.getNext();
}
}
protected void rehash() {
lock.lock();
try {
final int newCapacity = buckets.length * 2;
threshold = (int) (newCapacity * loadFactor);
int mask = newCapacity - 1;
HashEntry[] newBuckets = bucketType.makeArray(newCapacity);
for (int i = buckets.length; i-- > 0;) {
HashEntry e = buckets[i];
while (e != null) {
LispObject key = e.getKey();
LispObject value = e.getValue();
if (key == null || value == null) {
e.clear();
e = e.getNext();
continue;
}
final int index = comparator.hash(key) & mask;
e.clear();
newBuckets[index]
= bucketType.makeInstance(key,
e.getHash(),
value,
newBuckets[index],
index);
e = e.getNext();
}
}
buckets = newBuckets;
} finally {
lock.unlock();
}
}
@Deprecated
public LispObject ENTRIES() {
return getEntries();
}
/** @returns A list of (key . value) pairs. */
public LispObject getEntries() {
HashEntry[] b = getTable();
LispObject list = NIL;
for (int i = b.length; i-- > 0;) {
HashEntry e = b[i];
while (e != null) {
LispObject key = e.getKey();
LispObject value = e.getValue();
if (key != null && value != null) {
list = new Cons(new Cons(key, value), list);
} else {
assert false
: "ENTRIES encounted dangling entries.";
}
e = e.getNext();
}
}
return list;
}
public LispObject MAPHASH(LispObject function) {
HashEntry[] b = getTable();
for (int i = b.length; i-- > 0;) {
HashEntry e = b[i];
while (e != null) {
LispObject key = e.getKey();
LispObject value = e.getValue();
if (key != null && value != null) {
function.execute(key, value);
} else {
assert false
: "MAPHASH encountered dangling entries.";
}
e = e.getNext();
}
}
return NIL;
}
protected static class Comparator {
Symbol getTest() {
return Symbol.EQ;
}
boolean keysEqual(LispObject key1, LispObject key2) {
return key1 == key2;
}
int hash(LispObject key) {
return key.sxhash();
}
}
protected static class EqlComparator extends Comparator {
@Override
Symbol getTest() {
return Symbol.EQL;
}
@Override
boolean keysEqual(LispObject key1, LispObject key2) {
return key1.eql(key2);
}
}
protected static class EqualComparator extends Comparator {
@Override
Symbol getTest() {
return Symbol.EQUAL;
}
@Override
boolean keysEqual(LispObject key1, LispObject key2) {
return key1.equal(key2);
}
}
protected static class EqualpComparator extends Comparator {
@Override
Symbol getTest() {
return Symbol.EQUALP;
}
@Override
boolean keysEqual(LispObject key1, LispObject key2) {
return key1.equalp(key2);
}
@Override
int hash(LispObject key) {
return key.psxhash();
}
}
abstract class HashEntry
{
LispObject key;
int hash;
volatile LispObject value;
HashEntry next;
int slot;
public HashEntry() {};
public HashEntry(LispObject key, int hash, LispObject value,
HashEntry next, int slot)
{
this.key = key;
this.hash = hash;
this.value = value;
this.next = next;
this.slot = slot;
}
public LispObject getKey() {
return key;
}
public void setKey(LispObject key) {
this.key = key;
}
public int getHash() {
return hash;
}
public void setHash(int hash) {
this.hash = hash;
}
public LispObject getValue() {
return value;
}
public void setValue(LispObject value) {
this.value = value;
}
public HashEntry getNext() {
return next;
}
public void setNext(HashEntry next) {
this.next = next;
}
public int getSlot() {
return slot;
}
public void setSlot(int slot) {
this.slot = slot;
}
abstract HashEntry[] makeArray(int length);
abstract HashEntry makeInstance(LispObject key, int hash,
LispObject value,
HashEntry next, int slot);
abstract void expungeQueue();
abstract void clear();
}
ReferenceQueue queue
= new ReferenceQueue();
Map entryLookup
= Collections.synchronizedMap(new HashMap());
class HashEntryWeakKey
extends HashEntry
{
private WeakReference key;
public HashEntryWeakKey() {};
public HashEntryWeakKey(LispObject key, int hash, LispObject value,
HashEntry next, int slot)
{
this.hash = hash;
this.value = value;
this.next = next;
this.slot = slot;
this.key = new WeakReference(key, queue);
entryLookup.put(this.key, this);
}
public LispObject getKey() {
return key.get();
}
public void setKey(LispObject key) {
java.lang.ref.WeakReference old = this.key;
old.clear();
this.key = new WeakReference(key, queue);
entryLookup.put(this.key, this);
}
HashEntryWeakKey[] makeArray(int length) {
return new HashEntryWeakKey[length];
}
HashEntry makeInstance(LispObject key, int hash, LispObject value,
HashEntry next, int slot)
{
return new HashEntryWeakKey(key, hash, value, next, slot);
}
void expungeQueue() {
Reference ref = queue.poll();
while (ref != null) {
WeakHashTable.this.remove(ref);
entryLookup.remove(ref);
ref = queue.poll();
}
}
/** Remove referenced objects from GC queue structures. */
void clear() {
key.clear();
assert entryLookup.containsKey(key)
: "Key was not in lookup table";
entryLookup.remove(key);
}
}
class HashEntryWeakValue
extends HashEntry
{
private WeakReference value;
public HashEntryWeakValue() {};
public HashEntryWeakValue(LispObject key, int hash, LispObject value,
HashEntry next, int slot)
{
this.hash = hash;
this.key = key;
this.next = next;
this.slot = slot;
this.value = new WeakReference(value, queue);
entryLookup.put(this.value, this);
}
public LispObject getValue() {
return value.get();
}
public void setValue(LispObject value) {
java.lang.ref.WeakReference old = this.value;
old.clear();
this.value = new WeakReference(value, queue);
entryLookup.put(this.value, this);
}
HashEntryWeakValue[] makeArray(int length) {
return new HashEntryWeakValue[length];
}
HashEntryWeakValue makeInstance(LispObject key, int hash, LispObject value,
HashEntry next, int slot)
{
return new HashEntryWeakValue(key, hash, value, next, slot);
}
void expungeQueue() {
Reference ref = queue.poll();
while (ref != null) {
WeakHashTable.this.remove(ref);
entryLookup.remove(ref);
ref = queue.poll();
}
}
/** Remove referenced objects from GC queue structures. */
void clear() {
value.clear();
assert entryLookup.containsKey(value)
: "Value was not in lookup table.";
entryLookup.remove(value);
}
}
class HashEntryWeakKeyAndValue
extends HashEntry
{
private WeakReference key;
private WeakReference value;
public HashEntryWeakKeyAndValue() {};
public HashEntryWeakKeyAndValue(LispObject key, int hash,
LispObject value,
HashEntry next, int slot)
{
this.hash = hash;
this.next = next;
this.slot = slot;
this.key = new WeakReference(key, queue);
entryLookup.put(this.key, this);
this.value = new WeakReference(value, queue);
entryLookup.put(this.value, this);
}
public LispObject getKey() {
return key.get();
}
public void setKey(LispObject key) {
java.lang.ref.WeakReference old = this.key;
entryLookup.remove(old);
old.clear();
this.key = new WeakReference(key, queue);
entryLookup.put(this.key, this);
}
public LispObject getValue() {
return value.get();
}
public void setValue(LispObject value) {
java.lang.ref.WeakReference old = this.value;
entryLookup.remove(old);
old.clear();
this.value = new WeakReference(value, queue);
entryLookup.put(this.value, this);
}
HashEntryWeakKeyAndValue[] makeArray(int length) {
return new HashEntryWeakKeyAndValue[length];
}
HashEntryWeakKeyAndValue makeInstance(LispObject key, int hash,
LispObject value,
HashEntry next, int slot)
{
return new HashEntryWeakKeyAndValue(key, hash, value, next, slot);
}
void expungeQueue() {
Reference ref = queue.poll();
while (ref != null) {
HashEntry entry = entryLookup.get(ref);
if (entry == null) {
ref = queue.poll();
continue;
}
if (entry.getKey() == null
&& entry.getValue() == null) {
WeakHashTable.this.remove(ref);
entryLookup.remove(ref);
} else {
entryLookup.remove(ref);
}
ref = queue.poll();
}
}
/** Remove referenced objects from GC queue structures. */
void clear() {
key.clear();
value.clear();
entryLookup.remove(key);
entryLookup.remove(value);
}
}
class HashEntryWeakKeyOrValue
extends HashEntryWeakKeyAndValue
{
public HashEntryWeakKeyOrValue() {};
public HashEntryWeakKeyOrValue(LispObject key, int hash,
LispObject value,
HashEntry next, int slot)
{
super(key, hash, value, next, slot);
}
HashEntryWeakKeyOrValue[] makeArray(int length) {
return new HashEntryWeakKeyOrValue[length];
}
HashEntryWeakKeyOrValue makeInstance(LispObject key, int hash,
LispObject value,
HashEntry next, int slot)
{
return new HashEntryWeakKeyOrValue(key, hash, value, next, slot);
}
void expungeQueue() {
Reference ref = queue.poll();
while (ref != null) {
HashEntry entry = entryLookup.get(ref);
if (entry == null) {
ref = queue.poll();
continue;
}
WeakHashTable.this.remove(ref);
entryLookup.remove(entry.key);
entryLookup.remove(entry.value);
ref = queue.poll();
}
}
}
// For EQUALP hash tables.
@Override
public int psxhash() {
long result = 2062775257; // Chosen at random.
result = mix(result, count);
result = mix(result, getTest().sxhash());
return (int) (result & 0x7fffffff);
}
}