blob: 7a4f2edda11e09cd1a28995f96ccc5d7d1dfc4ae [file] [log] [blame]
/* Copyright (c) 2001-2010, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.store;
import java.util.NoSuchElementException;
import org.hsqldb.lib.ArrayCounter;
import org.hsqldb.lib.Iterator;
/**
* Base class for hash tables or sets. The exact type of the structure is
* defined by the constructor. Each instance has at least a keyTable array
* and a HashIndex instance for looking up the keys into this table. Instances
* that are maps also have a valueTable the same size as the keyTable.
*
* Special getOrAddXXX() methods are used for object maps in some subclasses.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.9.0
* @since 1.7.2
*/
public class BaseHashMap {
/*
data store:
keys: {array of primitive | array of object}
values: {none | array of primitive | array of object} same size as keys
objects support : hashCode(), equals()
implemented types of keyTable:
{objectKeyTable: variable size Object[] array for keys |
intKeyTable: variable size int[] for keys |
longKeyTable: variable size long[] for keys }
implemented types of valueTable:
{objectValueTable: variable size Object[] array for values |
intValueTable: variable size int[] for values |
longValueTable: variable size long[] for values}
valueTable does not exist for sets or for object pools
hash index:
hashTable: fixed size int[] array for hash lookup into keyTable
linkTable: pointer to the next key ; size equal or larger than hashTable
but equal to the valueTable
access count table:
{none |
variable size int[] array for access count} same size as xxxKeyTable
*/
//
boolean isIntKey;
boolean isLongKey;
boolean isObjectKey;
boolean isNoValue;
boolean isIntValue;
boolean isLongValue;
boolean isObjectValue;
protected boolean isTwoObjectValue;
protected boolean isList;
//
private ValuesIterator valuesIterator;
//
protected HashIndex hashIndex;
//
protected int[] intKeyTable;
protected Object[] objectKeyTable;
protected long[] longKeyTable;
//
protected int[] intValueTable;
protected Object[] objectValueTable;
protected long[] longValueTable;
//
protected int accessMin;
protected int accessCount;
protected int[] accessTable;
protected boolean[] multiValueTable;
protected Object[] objectValueTable2;
//
final float loadFactor;
final int initialCapacity;
int threshold;
protected int maxCapacity;
protected int purgePolicy = NO_PURGE;
protected boolean minimizeOnEmpty;
//
boolean hasZeroKey;
int zeroKeyIndex = -1;
// keyOrValueTypes
protected static final int noKeyOrValue = 0;
protected static final int intKeyOrValue = 1;
protected static final int longKeyOrValue = 2;
protected static final int objectKeyOrValue = 3;
// purgePolicy
protected static final int NO_PURGE = 0;
protected static final int PURGE_ALL = 1;
protected static final int PURGE_HALF = 2;
protected static final int PURGE_QUARTER = 3;
//
public final static int ACCESS_MAX = Integer.MAX_VALUE - (1 << 20);
protected BaseHashMap(int initialCapacity, int keyType, int valueType,
boolean hasAccessCount)
throws IllegalArgumentException {
if (initialCapacity <= 0) {
throw new IllegalArgumentException();
}
if (initialCapacity < 3) {
initialCapacity = 3;
}
this.loadFactor = 1; // can use any value if necessary
this.initialCapacity = initialCapacity;
threshold = initialCapacity;
int hashtablesize = (int) (initialCapacity * loadFactor);
if (hashtablesize < 3) {
hashtablesize = 3;
}
hashIndex = new HashIndex(hashtablesize, initialCapacity, true);
int arraySize = threshold;
if (keyType == BaseHashMap.intKeyOrValue) {
isIntKey = true;
intKeyTable = new int[arraySize];
} else if (keyType == BaseHashMap.objectKeyOrValue) {
isObjectKey = true;
objectKeyTable = new Object[arraySize];
} else {
isLongKey = true;
longKeyTable = new long[arraySize];
}
if (valueType == BaseHashMap.intKeyOrValue) {
isIntValue = true;
intValueTable = new int[arraySize];
} else if (valueType == BaseHashMap.objectKeyOrValue) {
isObjectValue = true;
objectValueTable = new Object[arraySize];
} else if (valueType == BaseHashMap.longKeyOrValue) {
isLongValue = true;
longValueTable = new long[arraySize];
} else {
isNoValue = true;
}
if (hasAccessCount) {
accessTable = new int[arraySize];
}
}
protected int getLookup(Object key, int hash) {
int lookup = hashIndex.getLookup(hash);
Object tempKey;
for (; lookup >= 0; lookup = hashIndex.getNextLookup(lookup)) {
tempKey = objectKeyTable[lookup];
if (key.equals(tempKey)) {
return lookup;
}
}
return lookup;
}
protected int getLookup(int key) {
int lookup = hashIndex.getLookup(key);
int tempKey;
for (; lookup >= 0; lookup = hashIndex.linkTable[lookup]) {
tempKey = intKeyTable[lookup];
if (key == tempKey) {
return lookup;
}
}
return lookup;
}
protected int getLookup(long key) {
int lookup = hashIndex.getLookup((int) key);
long tempKey;
for (; lookup >= 0; lookup = hashIndex.getNextLookup(lookup)) {
tempKey = longKeyTable[lookup];
if (key == tempKey) {
return lookup;
}
}
return lookup;
}
protected Iterator getValuesIterator(Object key, int hash) {
int lookup = getLookup(key, hash);
if (valuesIterator == null) {
valuesIterator = new ValuesIterator();
}
valuesIterator.reset(key, lookup);
return valuesIterator;
}
/**
* generic method for adding or removing keys
*/
protected Object addOrRemove(long longKey, long longValue,
Object objectKey, Object objectValue,
boolean remove) {
int hash = (int) longKey;
if (isObjectKey) {
if (objectKey == null) {
return null;
}
hash = objectKey.hashCode();
}
int index = hashIndex.getHashIndex(hash);
int lookup = hashIndex.hashTable[index];
int lastLookup = -1;
Object returnValue = null;
for (; lookup >= 0;
lastLookup = lookup,
lookup = hashIndex.getNextLookup(lookup)) {
if (isObjectKey) {
if (objectKeyTable[lookup].equals(objectKey)) {
break;
}
} else if (isIntKey) {
if (longKey == intKeyTable[lookup]) {
break;
}
} else if (isLongKey) {
if (longKey == longKeyTable[lookup]) {
break;
}
}
}
if (lookup >= 0) {
if (remove) {
if (isObjectKey) {
objectKeyTable[lookup] = null;
} else {
if (longKey == 0) {
hasZeroKey = false;
zeroKeyIndex = -1;
}
if (isIntKey) {
intKeyTable[lookup] = 0;
} else {
longKeyTable[lookup] = 0;
}
}
if (isObjectValue) {
returnValue = objectValueTable[lookup];
objectValueTable[lookup] = null;
} else if (isIntValue) {
intValueTable[lookup] = 0;
} else if (isLongValue) {
longValueTable[lookup] = 0;
}
hashIndex.unlinkNode(index, lastLookup, lookup);
if (accessTable != null) {
accessTable[lookup] = 0;
}
if (minimizeOnEmpty && hashIndex.elementCount == 0) {
rehash(initialCapacity);
}
return returnValue;
}
if (isObjectValue) {
returnValue = objectValueTable[lookup];
objectValueTable[lookup] = objectValue;
} else if (isIntValue) {
intValueTable[lookup] = (int) longValue;
} else if (isLongValue) {
longValueTable[lookup] = longValue;
}
if (accessTable != null) {
accessTable[lookup] = ++accessCount;
}
return returnValue;
}
// not found
if (remove) {
return null;
}
if (hashIndex.elementCount >= threshold) {
// should throw maybe, if reset returns false?
if (reset()) {
return addOrRemove(longKey, longValue, objectKey, objectValue,
remove);
} else {
return null;
}
}
lookup = hashIndex.linkNode(index, lastLookup);
// type dependent block
if (isObjectKey) {
objectKeyTable[lookup] = objectKey;
} else if (isIntKey) {
intKeyTable[lookup] = (int) longKey;
if (longKey == 0) {
hasZeroKey = true;
zeroKeyIndex = lookup;
}
} else if (isLongKey) {
longKeyTable[lookup] = longKey;
if (longKey == 0) {
hasZeroKey = true;
zeroKeyIndex = lookup;
}
}
if (isObjectValue) {
objectValueTable[lookup] = objectValue;
} else if (isIntValue) {
intValueTable[lookup] = (int) longValue;
} else if (isLongValue) {
longValueTable[lookup] = longValue;
}
//
if (accessTable != null) {
accessTable[lookup] = ++accessCount;
}
return returnValue;
}
/**
* generic method for adding or removing key / values in multi-value
* maps
*/
protected Object addOrRemoveMultiVal(long longKey, long longValue,
Object objectKey, Object objectValue,
boolean removeKey,
boolean removeValue) {
int hash = (int) longKey;
if (isObjectKey) {
if (objectKey == null) {
return null;
}
hash = objectKey.hashCode();
}
int index = hashIndex.getHashIndex(hash);
int lookup = hashIndex.hashTable[index];
int lastLookup = -1;
Object returnValue = null;
boolean multiValue = false;
for (; lookup >= 0;
lastLookup = lookup,
lookup = hashIndex.getNextLookup(lookup)) {
if (isObjectKey) {
if (objectKeyTable[lookup].equals(objectKey)) {
if (removeKey) {
while (true) {
objectKeyTable[lookup] = null;
returnValue = objectValueTable[lookup];
objectValueTable[lookup] = null;
hashIndex.unlinkNode(index, lastLookup, lookup);
multiValueTable[lookup] = false;
lookup = hashIndex.hashTable[index];
if (lookup < 0
|| !objectKeyTable[lookup].equals(
objectKey)) {
return returnValue;
}
}
} else {
if (objectValueTable[lookup].equals(objectValue)) {
if (removeValue) {
objectKeyTable[lookup] = null;
returnValue = objectValueTable[lookup];
objectValueTable[lookup] = null;
hashIndex.unlinkNode(index, lastLookup,
lookup);
multiValueTable[lookup] = false;
lookup = lastLookup;
return returnValue;
} else {
return objectValueTable[lookup];
}
}
}
multiValue = true;
}
} else if (isIntKey) {
if (longKey == intKeyTable[lookup]) {
if (removeKey) {
while (true) {
if (longKey == 0) {
hasZeroKey = false;
zeroKeyIndex = -1;
}
intKeyTable[lookup] = 0;
intValueTable[lookup] = 0;
hashIndex.unlinkNode(index, lastLookup, lookup);
multiValueTable[lookup] = false;
lookup = hashIndex.hashTable[index];
if (lookup < 0 || longKey != intKeyTable[lookup]) {
return null;
}
}
} else {
if (intValueTable[lookup] == longValue) {
return null;
}
}
multiValue = true;
}
} else if (isLongKey) {
if (longKey == longKeyTable[lookup]) {
if (removeKey) {
while (true) {
if (longKey == 0) {
hasZeroKey = false;
zeroKeyIndex = -1;
}
longKeyTable[lookup] = 0;
longValueTable[lookup] = 0;
hashIndex.unlinkNode(index, lastLookup, lookup);
multiValueTable[lookup] = false;
lookup = hashIndex.hashTable[index];
if (lookup < 0
|| longKey != longKeyTable[lookup]) {
return null;
}
}
} else {
if (intValueTable[lookup] == longValue) {
return null;
}
}
multiValue = true;
}
}
}
if (removeKey || removeValue) {
return returnValue;
}
if (hashIndex.elementCount >= threshold) {
// should throw maybe, if reset returns false?
if (reset()) {
return addOrRemoveMultiVal(longKey, longValue, objectKey,
objectValue, removeKey,
removeValue);
} else {
return null;
}
}
lookup = hashIndex.linkNode(index, lastLookup);
// type dependent block
if (isObjectKey) {
objectKeyTable[lookup] = objectKey;
} else if (isIntKey) {
intKeyTable[lookup] = (int) longKey;
if (longKey == 0) {
hasZeroKey = true;
zeroKeyIndex = lookup;
}
} else if (isLongKey) {
longKeyTable[lookup] = longKey;
if (longKey == 0) {
hasZeroKey = true;
zeroKeyIndex = lookup;
}
}
if (isObjectValue) {
objectValueTable[lookup] = objectValue;
} else if (isIntValue) {
intValueTable[lookup] = (int) longValue;
} else if (isLongValue) {
longValueTable[lookup] = longValue;
}
if (multiValue) {
multiValueTable[lookup] = true;
}
//
if (accessTable != null) {
accessTable[lookup] = ++accessCount;
}
return returnValue;
}
/**
* type-specific method for adding or removing keys in long or int->Object maps
*/
protected Object addOrRemove(long longKey, Object objectValue,
Object objectValueTwo, boolean remove) {
int hash = (int) longKey;
int index = hashIndex.getHashIndex(hash);
int lookup = hashIndex.hashTable[index];
int lastLookup = -1;
Object returnValue = null;
for (; lookup >= 0;
lastLookup = lookup,
lookup = hashIndex.getNextLookup(lookup)) {
if (isIntKey) {
if (longKey == intKeyTable[lookup]) {
break;
}
} else {
if (longKey == longKeyTable[lookup]) {
break;
}
}
}
if (lookup >= 0) {
if (remove) {
if (longKey == 0) {
hasZeroKey = false;
zeroKeyIndex = -1;
}
if (isIntKey) {
intKeyTable[lookup] = 0;
} else {
longKeyTable[lookup] = 0;
}
returnValue = objectValueTable[lookup];
objectValueTable[lookup] = null;
hashIndex.unlinkNode(index, lastLookup, lookup);
if (isTwoObjectValue) {
objectKeyTable[lookup] = null;
}
if (accessTable != null) {
accessTable[lookup] = 0;
}
return returnValue;
}
if (isObjectValue) {
returnValue = objectValueTable[lookup];
objectValueTable[lookup] = objectValue;
}
if (isTwoObjectValue) {
objectKeyTable[lookup] = objectValueTwo;
}
if (accessTable != null) {
accessTable[lookup] = ++accessCount;
}
return returnValue;
}
// not found
if (remove) {
return returnValue;
}
if (hashIndex.elementCount >= threshold) {
if (reset()) {
return addOrRemove(longKey, objectValue, objectValueTwo,
remove);
} else {
return null;
}
}
lookup = hashIndex.linkNode(index, lastLookup);
if (isIntKey) {
intKeyTable[lookup] = (int) longKey;
} else {
longKeyTable[lookup] = longKey;
}
if (longKey == 0) {
hasZeroKey = true;
zeroKeyIndex = lookup;
}
objectValueTable[lookup] = objectValue;
if (isTwoObjectValue) {
objectKeyTable[lookup] = objectValueTwo;
}
if (accessTable != null) {
accessTable[lookup] = ++accessCount;
}
return returnValue;
}
/**
* type specific method for Object sets or Object->Object maps
*/
protected Object removeObject(Object objectKey, boolean removeRow) {
if (objectKey == null) {
return null;
}
int hash = objectKey.hashCode();
int index = hashIndex.getHashIndex(hash);
int lookup = hashIndex.hashTable[index];
int lastLookup = -1;
Object returnValue = null;
for (; lookup >= 0;
lastLookup = lookup,
lookup = hashIndex.getNextLookup(lookup)) {
if (objectKeyTable[lookup].equals(objectKey)) {
objectKeyTable[lookup] = null;
hashIndex.unlinkNode(index, lastLookup, lookup);
if (isObjectValue) {
returnValue = objectValueTable[lookup];
objectValueTable[lookup] = null;
}
if (removeRow) {
removeRow(lookup);
}
return returnValue;
}
}
// not found
return returnValue;
}
protected boolean reset() {
if (maxCapacity == 0 || maxCapacity > threshold) {
rehash(hashIndex.linkTable.length * 2);
return true;
} else if (purgePolicy == PURGE_ALL) {
clear();
return true;
} else if (purgePolicy == PURGE_QUARTER) {
clear(threshold / 4, threshold >> 8);
return true;
} else if (purgePolicy == PURGE_HALF) {
clear(threshold / 2, threshold >> 8);
return true;
} else if (purgePolicy == NO_PURGE) {
return false;
}
return false;
}
/**
* rehash uses existing key and element arrays. key / value pairs are
* put back into the arrays from the top, removing any gaps. any redundant
* key / value pairs duplicated at the end of the array are then cleared.
*
* newCapacity must be larger or equal to existing number of elements.
*/
protected void rehash(int newCapacity) {
int limitLookup = hashIndex.newNodePointer;
boolean oldZeroKey = hasZeroKey;
int oldZeroKeyIndex = zeroKeyIndex;
if (newCapacity < hashIndex.elementCount) {
return;
}
hashIndex.reset((int) (newCapacity * loadFactor), newCapacity);
if (multiValueTable != null) {
int counter = multiValueTable.length;
while (--counter >= 0) {
multiValueTable[counter] = false;
}
}
hasZeroKey = false;
zeroKeyIndex = -1;
threshold = newCapacity;
for (int lookup = -1;
(lookup = nextLookup(lookup, limitLookup, oldZeroKey, oldZeroKeyIndex))
< limitLookup; ) {
long longKey = 0;
long longValue = 0;
Object objectKey = null;
Object objectValue = null;
if (isObjectKey) {
objectKey = objectKeyTable[lookup];
} else if (isIntKey) {
longKey = intKeyTable[lookup];
} else {
longKey = longKeyTable[lookup];
}
if (isObjectValue) {
objectValue = objectValueTable[lookup];
} else if (isIntValue) {
longValue = intValueTable[lookup];
} else if (isLongValue) {
longValue = longValueTable[lookup];
}
if (multiValueTable == null) {
addOrRemove(longKey, longValue, objectKey, objectValue, false);
} else {
addOrRemoveMultiVal(longKey, longValue, objectKey,
objectValue, false, false);
}
if (accessTable != null) {
accessTable[hashIndex.elementCount - 1] = accessTable[lookup];
}
}
resizeElementArrays(hashIndex.newNodePointer, newCapacity);
}
/**
* resize the arrays contianing the key / value data
*/
private void resizeElementArrays(int dataLength, int newLength) {
Object temp;
int usedLength = newLength > dataLength ? dataLength
: newLength;
if (isIntKey) {
temp = intKeyTable;
intKeyTable = new int[newLength];
System.arraycopy(temp, 0, intKeyTable, 0, usedLength);
}
if (isIntValue) {
temp = intValueTable;
intValueTable = new int[newLength];
System.arraycopy(temp, 0, intValueTable, 0, usedLength);
}
if (isLongKey) {
temp = longKeyTable;
longKeyTable = new long[newLength];
System.arraycopy(temp, 0, longKeyTable, 0, usedLength);
}
if (isLongValue) {
temp = longValueTable;
longValueTable = new long[newLength];
System.arraycopy(temp, 0, longValueTable, 0, usedLength);
}
if (objectKeyTable != null) {
temp = objectKeyTable;
objectKeyTable = new Object[newLength];
System.arraycopy(temp, 0, objectKeyTable, 0, usedLength);
}
if (isObjectValue) {
temp = objectValueTable;
objectValueTable = new Object[newLength];
System.arraycopy(temp, 0, objectValueTable, 0, usedLength);
}
if (objectValueTable2 != null) {
temp = objectValueTable2;
objectValueTable2 = new Object[newLength];
System.arraycopy(temp, 0, objectValueTable2, 0, usedLength);
}
if (accessTable != null) {
temp = accessTable;
accessTable = new int[newLength];
System.arraycopy(temp, 0, accessTable, 0, usedLength);
}
if (multiValueTable != null) {
temp = multiValueTable;
multiValueTable = new boolean[newLength];
System.arraycopy(temp, 0, multiValueTable, 0, usedLength);
}
}
/**
* clear all the key / value data in a range.
*/
private void clearElementArrays(final int from, final int to) {
if (isIntKey) {
int counter = to;
while (--counter >= from) {
intKeyTable[counter] = 0;
}
} else if (isLongKey) {
int counter = to;
while (--counter >= from) {
longKeyTable[counter] = 0;
}
} else if (isObjectKey || objectKeyTable != null) {
int counter = to;
while (--counter >= from) {
objectKeyTable[counter] = null;
}
}
if (isIntValue) {
int counter = to;
while (--counter >= from) {
intValueTable[counter] = 0;
}
} else if (isLongValue) {
int counter = to;
while (--counter >= from) {
longValueTable[counter] = 0;
}
} else if (isObjectValue) {
int counter = to;
while (--counter >= from) {
objectValueTable[counter] = null;
}
}
if (accessTable != null) {
int counter = to;
while (--counter >= from) {
accessTable[counter] = 0;
}
}
if (multiValueTable != null) {
int counter = to;
while (--counter >= from) {
multiValueTable[counter] = false;
}
}
}
/**
* move the elements after a removed key / value pair to fill the gap
*/
void removeFromElementArrays(int lookup) {
int arrayLength = hashIndex.linkTable.length;
if (isIntKey) {
Object array = intKeyTable;
System.arraycopy(array, lookup + 1, array, lookup,
arrayLength - lookup - 1);
intKeyTable[arrayLength - 1] = 0;
}
if (isLongKey) {
Object array = longKeyTable;
System.arraycopy(array, lookup + 1, array, lookup,
arrayLength - lookup - 1);
longKeyTable[arrayLength - 1] = 0;
}
if (isObjectKey || objectKeyTable != null) {
Object array = objectKeyTable;
System.arraycopy(array, lookup + 1, array, lookup,
arrayLength - lookup - 1);
objectKeyTable[arrayLength - 1] = null;
}
if (isIntValue) {
Object array = intValueTable;
System.arraycopy(array, lookup + 1, array, lookup,
arrayLength - lookup - 1);
intValueTable[arrayLength - 1] = 0;
}
if (isLongValue) {
Object array = longValueTable;
System.arraycopy(array, lookup + 1, array, lookup,
arrayLength - lookup - 1);
longValueTable[arrayLength - 1] = 0;
}
if (isObjectValue) {
Object array = objectValueTable;
System.arraycopy(array, lookup + 1, array, lookup,
arrayLength - lookup - 1);
objectValueTable[arrayLength - 1] = null;
}
}
/**
* find the next lookup in the key/value tables with an entry
* allows the use of old limit and zero int key attributes
*/
int nextLookup(int lookup, int limitLookup, boolean hasZeroKey,
int zeroKeyIndex) {
for (++lookup; lookup < limitLookup; lookup++) {
if (isObjectKey) {
if (objectKeyTable[lookup] != null) {
return lookup;
}
} else if (isIntKey) {
if (intKeyTable[lookup] != 0) {
return lookup;
} else if (hasZeroKey && lookup == zeroKeyIndex) {
return lookup;
}
} else {
if (longKeyTable[lookup] != 0) {
return lookup;
} else if (hasZeroKey && lookup == zeroKeyIndex) {
return lookup;
}
}
}
return lookup;
}
/**
* find the next lookup in the key/value tables with an entry
* uses current limits and zero integer key state
*/
protected int nextLookup(int lookup) {
for (++lookup; lookup < hashIndex.newNodePointer; lookup++) {
if (isObjectKey) {
if (objectKeyTable[lookup] != null) {
return lookup;
}
} else if (isIntKey) {
if (intKeyTable[lookup] != 0) {
return lookup;
} else if (hasZeroKey && lookup == zeroKeyIndex) {
return lookup;
}
} else {
if (longKeyTable[lookup] != 0) {
return lookup;
} else if (hasZeroKey && lookup == zeroKeyIndex) {
return lookup;
}
}
}
return -1;
}
/**
* row must already been freed of key / element
*/
protected void removeRow(int lookup) {
hashIndex.removeEmptyNode(lookup);
removeFromElementArrays(lookup);
}
/**
* Clear the map completely.
*/
public void clear() {
if (hashIndex.modified) {
accessCount = 0;
accessMin = accessCount;
hasZeroKey = false;
zeroKeyIndex = -1;
clearElementArrays(0, hashIndex.linkTable.length);
hashIndex.clear();
if (minimizeOnEmpty) {
rehash(initialCapacity);
}
}
}
/**
* Return the max accessCount value for count elements with the lowest
* access count. Always return at least accessMin + 1
*/
public int getAccessCountCeiling(int count, int margin) {
return ArrayCounter.rank(accessTable, hashIndex.newNodePointer, count,
accessMin + 1, accessCount, margin);
}
/**
* This is called after all elements below count accessCount have been
* removed
*/
public void setAccessCountFloor(int count) {
accessMin = count;
}
public int incrementAccessCount() {
return ++accessCount;
}
/**
* Clear approximately count elements from the map, starting with
* those with low accessTable ranking.
*
* Only for maps with Object key table
*/
protected void clear(int count, int margin) {
if (margin < 64) {
margin = 64;
}
int maxlookup = hashIndex.newNodePointer;
int accessBase = getAccessCountCeiling(count, margin);
for (int lookup = 0; lookup < maxlookup; lookup++) {
Object o = objectKeyTable[lookup];
if (o != null && accessTable[lookup] < accessBase) {
removeObject(o, false);
}
}
accessMin = accessBase;
}
protected void resetAccessCount() {
if (accessCount < ACCESS_MAX) {
return;
}
if (accessMin < Integer.MAX_VALUE - (1 << 24)) {
accessMin = Integer.MAX_VALUE - (1 << 24);
}
int i = accessTable.length;
while (--i >= 0) {
if (accessTable[i] <= accessMin) {
accessTable[i] = 0;
} else {
accessTable[i] -= accessMin;
}
}
accessCount -= accessMin;
accessMin = 0;
}
public int capacity() {
return hashIndex.linkTable.length;
}
public int size() {
return hashIndex.elementCount;
}
public boolean isEmpty() {
return hashIndex.elementCount == 0;
}
protected boolean containsKey(Object key) {
if (key == null) {
return false;
}
if (hashIndex.elementCount == 0) {
return false;
}
int lookup = getLookup(key, key.hashCode());
return lookup == -1 ? false
: true;
}
protected boolean containsKey(int key) {
if (hashIndex.elementCount == 0) {
return false;
}
int lookup = getLookup(key);
return lookup == -1 ? false
: true;
}
protected boolean containsKey(long key) {
if (hashIndex.elementCount == 0) {
return false;
}
int lookup = getLookup(key);
return lookup == -1 ? false
: true;
}
protected boolean containsValue(Object value) {
int lookup = 0;
if (hashIndex.elementCount == 0) {
return false;
}
if (value == null) {
for (; lookup < hashIndex.newNodePointer; lookup++) {
if (objectValueTable[lookup] == null) {
if (isObjectKey) {
if (objectKeyTable[lookup] != null) {
return true;
}
} else if (isIntKey) {
if (intKeyTable[lookup] != 0) {
return true;
} else if (hasZeroKey && lookup == zeroKeyIndex) {
return true;
}
} else {
if (longKeyTable[lookup] != 0) {
return true;
} else if (hasZeroKey && lookup == zeroKeyIndex) {
return true;
}
}
}
}
} else {
for (; lookup < hashIndex.newNodePointer; lookup++) {
if (value.equals(objectValueTable[lookup])) {
return true;
}
}
}
return false;
}
/**
* Currently only for object maps
*/
protected class ValuesIterator implements org.hsqldb.lib.Iterator {
int lookup = -1;
Object key;
private void reset(Object key, int lookup) {
this.key = key;
this.lookup = lookup;
}
public boolean hasNext() {
return lookup != -1;
}
public Object next() throws NoSuchElementException {
if (lookup == -1) {
return null;
}
Object value = BaseHashMap.this.objectValueTable[lookup];
while (true) {
lookup = BaseHashMap.this.hashIndex.getNextLookup(lookup);
if (lookup == -1
|| BaseHashMap.this.objectKeyTable[lookup].equals(
key)) {
break;
}
}
return value;
}
public int nextInt() throws NoSuchElementException {
throw new NoSuchElementException("Hash Iterator");
}
public long nextLong() throws NoSuchElementException {
throw new NoSuchElementException("Hash Iterator");
}
public void remove() throws NoSuchElementException {
throw new NoSuchElementException("Hash Iterator");
}
public void setValue(Object value) {
throw new NoSuchElementException("Hash Iterator");
}
}
protected class MultiValueKeyIterator implements Iterator {
boolean keys;
int lookup = -1;
int counter;
boolean removed;
public MultiValueKeyIterator() {
toNextLookup();
}
private void toNextLookup() {
while (true) {
lookup = nextLookup(lookup);
if (lookup == -1 || !multiValueTable[lookup]) {
break;
}
}
}
public boolean hasNext() {
return lookup != -1;
}
public Object next() throws NoSuchElementException {
Object value = objectKeyTable[lookup];
toNextLookup();
return value;
}
public int nextInt() throws NoSuchElementException {
throw new NoSuchElementException("Hash Iterator");
}
public long nextLong() throws NoSuchElementException {
throw new NoSuchElementException("Hash Iterator");
}
public void remove() throws NoSuchElementException {
throw new NoSuchElementException("Hash Iterator");
}
public void setValue(Object value) {
throw new NoSuchElementException("Hash Iterator");
}
}
/**
* Iterator returns Object, int or long and is used both for keys and
* values
*/
protected class BaseHashIterator implements Iterator {
boolean keys;
int lookup = -1;
int counter;
boolean removed;
/**
* default is iterator for values
*/
public BaseHashIterator() {}
public BaseHashIterator(boolean keys) {
this.keys = keys;
}
public boolean hasNext() {
return counter < hashIndex.elementCount;
}
public Object next() throws NoSuchElementException {
if ((keys && !isObjectKey) || (!keys && !isObjectValue)) {
throw new NoSuchElementException("Hash Iterator");
}
removed = false;
if (hasNext()) {
counter++;
lookup = nextLookup(lookup);
if (keys) {
return objectKeyTable[lookup];
} else {
return objectValueTable[lookup];
}
}
throw new NoSuchElementException("Hash Iterator");
}
public int nextInt() throws NoSuchElementException {
if ((keys && !isIntKey) || (!keys && !isIntValue)) {
throw new NoSuchElementException("Hash Iterator");
}
removed = false;
if (hasNext()) {
counter++;
lookup = nextLookup(lookup);
if (keys) {
return intKeyTable[lookup];
} else {
return intValueTable[lookup];
}
}
throw new NoSuchElementException("Hash Iterator");
}
public long nextLong() throws NoSuchElementException {
if ((!isLongKey || !keys)) {
throw new NoSuchElementException("Hash Iterator");
}
removed = false;
if (hasNext()) {
counter++;
lookup = nextLookup(lookup);
if (keys) {
return longKeyTable[lookup];
} else {
return longValueTable[lookup];
}
}
throw new NoSuchElementException("Hash Iterator");
}
public void remove() throws NoSuchElementException {
if (removed) {
throw new NoSuchElementException("Hash Iterator");
}
counter--;
removed = true;
if (BaseHashMap.this.isObjectKey) {
if (multiValueTable == null) {
addOrRemove(0, 0, objectKeyTable[lookup], null, true);
} else {
if (keys) {
addOrRemoveMultiVal(0, 0, objectKeyTable[lookup],
null, true, false);
} else {
addOrRemoveMultiVal(0, 0, objectKeyTable[lookup],
objectValueTable[lookup], false,
true);
}
}
} else if (isIntKey) {
addOrRemove(intKeyTable[lookup], 0, null, null, true);
} else {
addOrRemove(longKeyTable[lookup], 0, null, null, true);
}
if (isList) {
removeRow(lookup);
lookup--;
}
}
public void setValue(Object value) {
if (keys) {
throw new NoSuchElementException();
}
objectValueTable[lookup] = value;
}
public int getAccessCount() {
if (removed || accessTable == null) {
throw new NoSuchElementException();
}
return accessTable[lookup];
}
public void setAccessCount(int count) {
if (removed || accessTable == null) {
throw new NoSuchElementException();
}
accessTable[lookup] = count;
}
public int getLookup() {
return lookup;
}
}
}