/*
* eXist Open Source Native XML Database
* Copyright (C) 2000-04, Wolfgang M. Meier (wolfgang@exist-db.org)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This library 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Id$
*/
package org.exist.util.hashtable;
/**
* A double-linked hash map additionally providing access to entries in the order in which
* they were added.
*
* If a duplicate entry is added, the old entry is removed from the list and appended to the end. The
* map thus implements a "Last Recently Used" (LRU) behaviour.
*/
import java.util.Iterator;
import org.exist.util.hashtable.Long2ObjectHashMap.Long2ObjectIterator;
public class SequencedLongHashMap extends AbstractHashtable {
/**
* Represents an entry in the map. Each entry
* has a link to the next and previous entries in
* the order in which they were inserted.
*
* @author wolf
*/
public final static class Entry {
long key;
Object value;
/** points to the next entry in insertion order. */
Entry next = null;
/** points to the previous entry in insertion order. */
Entry prev = null;
/** points to the prev entry if more than one key maps
* to the same bucket in the table.
*/
Entry prevDup = null;
/** points to the next entry if more than one key maps
* to the same bucket in the table.
*/
Entry nextDup = null;
public Entry(long key, Object value) {
this.key = key;
this.value = value;
}
public Entry getNext() {
return next;
}
public long getKey() {
return key;
}
public Object getValue() {
return value;
}
public String toString() {
return Long.toString(key);
}
}
protected long[] keys;
protected Entry[] values;
/** points to the first entry inserted. */
private Entry first = null;
/** points to the last inserted entry. */
private Entry last = null;
public SequencedLongHashMap() {
super();
keys = new long[tabSize];
values = new Entry[tabSize];
}
public SequencedLongHashMap(int iSize) {
super(iSize);
keys = new long[tabSize];
values = new Entry[tabSize];
}
/**
* Add a new entry for the key.
*
* @param key
* @param value
*/
public void put(long key, Object value) {
Entry entry = insert(key, value);
if(first == null) {
first = entry;
last = first;
} else {
last.next = entry;
entry.prev = last;
last = entry;
}
}
protected Entry insert(long key, Object value) {
if (value == null)
throw new IllegalArgumentException("Illegal value: null");
int idx = hash(key) % tabSize;
if(idx < 0)
idx *= -1;
// look for an empty bucket
if (values[idx] == null) {
keys[idx] = key;
values[idx] = new Entry(key, value);
++items;
return values[idx];
}
Entry next = values[idx];
while (next != null) {
if (next.key == key) {
// duplicate value
next.value = value;
removeEntry(next);
return next;
}
next = next.nextDup;
}
// add a new entry to the chain
next = new Entry(key, value);
next.nextDup = values[idx];
values[idx].prevDup = next;
values[idx] = next;
++items;
return next;
}
/**
* Returns the value for key or null if the key
* is not in the map.
*
* @param key
*/
public Object get(long key) {
int idx = hash(key) % tabSize;
if(idx < 0)
idx *= -1;
if (values[idx] == null)
return null; // key does not exist
Entry next = values[idx];
while (next != null) {
if (next.key == key)
return next.value;
next = next.nextDup;
}
return null;
}
/**
* Returns the first entry added to the map.
*/
public Entry getFirstEntry() {
return first;
}
/**
* Remove the entry specified by key from the map.
*
* @param key
*/
public Object remove(long key) {
Entry entry = removeFromHashtable(key);
if(entry != null) {
removeEntry(entry);
return entry.value;
} else
return null;
}
private Entry removeFromHashtable(long key) {
int idx = hash(key) % tabSize;
if(idx < 0)
idx *= -1;
if (values[idx] == null) {
return null; // key does not exist
}
Entry next = values[idx];
while (next != null) {
if (next.key == key) {
if (next.prevDup == null) {
values[idx] = next.nextDup;
if (values[idx] != null)
values[idx].prevDup = null;
} else {
next.prevDup.nextDup = next.nextDup;
if (next.nextDup != null)
next.nextDup.prevDup = next.prevDup;
}
--items;
return next;
}
next = next.nextDup;
}
return null;
}
/**
* Remove the first entry added to the map.
*/
public Object removeFirst() {
if(first == null)
return null;
final Entry head = first;
removeFromHashtable(first.key);
removeEntry(first);
return head;
}
/**
* Remove an entry.
*
* @param entry
*/
public void removeEntry(Entry entry) {
if(entry.prev == null) {
if(entry.next == null) {
first = null;
last = null;
} else {
entry.next.prev = null;
first = entry.next;
}
} else {
entry.prev.next = entry.next;
if(entry.next == null)
last = entry.prev;
else
entry.next.prev = entry.prev;
}
entry.prev = null;
entry.next = null;
}
/**
* Clear the map.
*/
public void clear() {
for (int i = 0; i < tabSize; i++)
values[i] = null;
items = 0;
first = null;
last = null;
}
protected final static int hash(long l) {
return (int) (l ^ (l >>> 32));
}
/**
* Returns an iterator over all keys in the
* order in which they were inserted.
*/
public Iterator iterator() {
return new SequencedLongIterator(Long2ObjectIterator.KEYS);
}
/**
* Returns an iterator over all values in the order
* in which they were inserted.
*/
public Iterator valueIterator() {
return new SequencedLongIterator(Long2ObjectIterator.VALUES);
}
protected class SequencedLongIterator extends HashtableIterator {
private Entry current;
public SequencedLongIterator(int type) {
super(type);
current = first;
}
/* (non-Javadoc)
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return current != null;
}
/* (non-Javadoc)
* @see org.exist.util.hashtable.Long2ObjectHashMap.Long2ObjectIterator#next()
*/
public Object next() {
if(current == null)
return null;
Entry next = current;
current = current.next;
if(returnType == VALUES) {
return next.value;
} else
return new Long(next.key);
}
}
}