/* * eXist Open Source Native XML Database * Copyright (C) 2000-2010 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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 net.jcip.annotations.NotThreadSafe; import java.util.Iterator; @NotThreadSafe public class SequencedLongHashMap<V> extends AbstractHashtable<Long, V> { /** * 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<V> { final long key; V value; /** points to the next entry in insertion order. */ Entry<V> next = null; /** points to the previous entry in insertion order. */ Entry<V> prev = null; /** points to the prev entry if more than one key maps * to the same bucket in the table. */ Entry<V> prevDup = null; /** points to the next entry if more than one key maps * to the same bucket in the table. */ Entry<V> nextDup = null; public Entry(final long key, final V value) { this.key = key; this.value = value; } public Entry<V> getNext() { return next; } public long getKey() { return key; } public V getValue() { return value; } public String toString() { return Long.toString(key); } } private long[] keys; private Entry<V>[] values; /** points to the first entry inserted. */ private Entry<V> first = null; /** points to the last inserted entry. */ private Entry<V> last = null; @SuppressWarnings("unchecked") SequencedLongHashMap() { super(); keys = new long[tabSize]; values = (Entry<V>[]) new Entry[tabSize]; } @SuppressWarnings("unchecked") public SequencedLongHashMap(final int iSize) { super(iSize); keys = new long[tabSize]; values = (Entry<V>[]) new Entry[tabSize]; } /** * Add a new entry for the key. * * @param key The key * @param value The value */ @SuppressWarnings("unchecked") public void put(final long key, final V value) { final Entry<V> 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, V 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<V> 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 The key to retrieve the value for */ public V get(final long key) { int idx = hash(key) % tabSize; if (idx < 0) { idx *= -1; } if (values[idx] == null) { return null; } // key does not exist Entry<V> 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<V> getFirstEntry() { return first; } /** * Remove the entry specified by key from the map. * * @param key The key */ public V remove(final long key) { final Entry<V> entry = removeFromHashtable(key); if (entry != null) { removeEntry(entry); return entry.value; } else { return null; } } private Entry<V> removeFromHashtable(final long key) { int idx = hash(key) % tabSize; if (idx < 0) { idx *= -1; } if (values[idx] == null) { return null; // key does not exist } Entry<V> 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 Entry<V> removeFirst() { if (first == null) { return null; } final Entry<V> head = first; removeFromHashtable(first.key); removeEntry(first); return head; } /** * Remove an entry. * * @param entry The entry to remove */ private void removeEntry(final Entry<V> 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 static int hash(final long l) { return (int) (l ^ (l >>> 32)); } /** * Returns an iterator over all keys in the * order in which they were inserted. */ @Override public Iterator<Long> iterator() { return new SequencedLongIterator<>(IteratorType.KEYS); } /** * Returns an iterator over all values in the order * in which they were inserted. */ @Override public Iterator<V> valueIterator() { return new SequencedLongIterator<>(IteratorType.VALUES); } public class SequencedLongIterator<T> extends AbstractHashSetIterator<T> { private Entry<V> current; public SequencedLongIterator(final IteratorType type) { super(type); current = first; } @Override public boolean hasNext() { return current != null; } @SuppressWarnings("unchecked") @Override public T next() { if (current == null) { return null; } final Entry next = current; current = current.next; switch (returnType) { case KEYS: return (T) Long.valueOf(next.key); case VALUES: return (T) next.value; } throw new IllegalStateException("This never happens"); } } }