org.mozilla.javascript.UintMap Maven / Gradle / Ivy
The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Map to associate non-negative integers to objects or integers.
* The map does not synchronize any of its operation, so either use
* it from a single thread or do own synchronization or perform all mutation
* operations on one thread before passing the map to others.
*
* @author Igor Bukanov
*
*/
public class UintMap implements Serializable
{
static final long serialVersionUID = 4242698212885848444L;
// Map implementation via hashtable,
// follows "The Art of Computer Programming" by Donald E. Knuth
public UintMap() {
this(4);
}
public UintMap(int initialCapacity) {
if (initialCapacity < 0) Kit.codeBug();
// Table grow when number of stored keys >= 3/4 of max capacity
int minimalCapacity = initialCapacity * 4 / 3;
int i;
for (i = 2; (1 << i) < minimalCapacity; ++i) { }
power = i;
if (check && power < 2) Kit.codeBug();
}
public boolean isEmpty() {
return keyCount == 0;
}
public int size() {
return keyCount;
}
public boolean has(int key) {
if (key < 0) Kit.codeBug();
return 0 <= findIndex(key);
}
/**
* Get object value assigned with key.
* @return key object value or null if key is absent
*/
public Object getObject(int key) {
if (key < 0) Kit.codeBug();
if (values != null) {
int index = findIndex(key);
if (0 <= index) {
return values[index];
}
}
return null;
}
/**
* Get integer value assigned with key.
* @return key integer value or defaultValue if key is absent
*/
public int getInt(int key, int defaultValue) {
if (key < 0) Kit.codeBug();
int index = findIndex(key);
if (0 <= index) {
if (ivaluesShift != 0) {
return keys[ivaluesShift + index];
}
return 0;
}
return defaultValue;
}
/**
* Get integer value assigned with key.
* @return key integer value or defaultValue if key does not exist or does
* not have int value
* @throws RuntimeException if key does not exist
*/
public int getExistingInt(int key) {
if (key < 0) Kit.codeBug();
int index = findIndex(key);
if (0 <= index) {
if (ivaluesShift != 0) {
return keys[ivaluesShift + index];
}
return 0;
}
// Key must exist
Kit.codeBug();
return 0;
}
/**
* Set object value of the key.
* If key does not exist, also set its int value to 0.
*/
public void put(int key, Object value) {
if (key < 0) Kit.codeBug();
int index = ensureIndex(key, false);
if (values == null) {
values = new Object[1 << power];
}
values[index] = value;
}
/**
* Set int value of the key.
* If key does not exist, also set its object value to null.
*/
public void put(int key, int value) {
if (key < 0) Kit.codeBug();
int index = ensureIndex(key, true);
if (ivaluesShift == 0) {
int N = 1 << power;
// keys.length can be N * 2 after clear which set ivaluesShift to 0
if (keys.length != N * 2) {
int[] tmp = new int[N * 2];
System.arraycopy(keys, 0, tmp, 0, N);
keys = tmp;
}
ivaluesShift = N;
}
keys[ivaluesShift + index] = value;
}
public void remove(int key) {
if (key < 0) Kit.codeBug();
int index = findIndex(key);
if (0 <= index) {
keys[index] = DELETED;
--keyCount;
// Allow to GC value and make sure that new key with the deleted
// slot shall get proper default values
if (values != null) { values[index] = null; }
if (ivaluesShift != 0) { keys[ivaluesShift + index] = 0; }
}
}
public void clear() {
int N = 1 << power;
if (keys != null) {
for (int i = 0; i != N; ++i) {
keys[i] = EMPTY;
}
if (values != null) {
for (int i = 0; i != N; ++i) {
values[i] = null;
}
}
}
ivaluesShift = 0;
keyCount = 0;
occupiedCount = 0;
}
/** Return array of present keys */
public int[] getKeys() {
int[] keys = this.keys;
int n = keyCount;
int[] result = new int[n];
for (int i = 0; n != 0; ++i) {
int entry = keys[i];
if (entry != EMPTY && entry != DELETED) {
result[--n] = entry;
}
}
return result;
}
private static int tableLookupStep(int fraction, int mask, int power) {
int shift = 32 - 2 * power;
if (shift >= 0) {
return ((fraction >>> shift) & mask) | 1;
}
else {
return (fraction & (mask >>> -shift)) | 1;
}
}
private int findIndex(int key) {
int[] keys = this.keys;
if (keys != null) {
int fraction = key * A;
int index = fraction >>> (32 - power);
int entry = keys[index];
if (entry == key) { return index; }
if (entry != EMPTY) {
// Search in table after first failed attempt
int mask = (1 << power) - 1;
int step = tableLookupStep(fraction, mask, power);
int n = 0;
do {
if (check) {
if (n >= occupiedCount) Kit.codeBug();
++n;
}
index = (index + step) & mask;
entry = keys[index];
if (entry == key) { return index; }
} while (entry != EMPTY);
}
}
return -1;
}
// Insert key that is not present to table without deleted entries
// and enough free space
private int insertNewKey(int key) {
if (check && occupiedCount != keyCount) Kit.codeBug();
if (check && keyCount == 1 << power) Kit.codeBug();
int[] keys = this.keys;
int fraction = key * A;
int index = fraction >>> (32 - power);
if (keys[index] != EMPTY) {
int mask = (1 << power) - 1;
int step = tableLookupStep(fraction, mask, power);
int firstIndex = index;
do {
if (check && keys[index] == DELETED) Kit.codeBug();
index = (index + step) & mask;
if (check && firstIndex == index) Kit.codeBug();
} while (keys[index] != EMPTY);
}
keys[index] = key;
++occupiedCount;
++keyCount;
return index;
}
private void rehashTable(boolean ensureIntSpace) {
if (keys != null) {
// Check if removing deleted entries would free enough space
if (keyCount * 2 >= occupiedCount) {
// Need to grow: less then half of deleted entries
++power;
}
}
int N = 1 << power;
int[] old = keys;
int oldShift = ivaluesShift;
if (oldShift == 0 && !ensureIntSpace) {
keys = new int[N];
}
else {
ivaluesShift = N; keys = new int[N * 2];
}
for (int i = 0; i != N; ++i) { keys[i] = EMPTY; }
Object[] oldValues = values;
if (oldValues != null) { values = new Object[N]; }
int oldCount = keyCount;
occupiedCount = 0;
if (oldCount != 0) {
keyCount = 0;
for (int i = 0, remaining = oldCount; remaining != 0; ++i) {
int key = old[i];
if (key != EMPTY && key != DELETED) {
int index = insertNewKey(key);
if (oldValues != null) {
values[index] = oldValues[i];
}
if (oldShift != 0) {
keys[ivaluesShift + index] = old[oldShift + i];
}
--remaining;
}
}
}
}
// Ensure key index creating one if necessary
private int ensureIndex(int key, boolean intType) {
int index = -1;
int firstDeleted = -1;
int[] keys = this.keys;
if (keys != null) {
int fraction = key * A;
index = fraction >>> (32 - power);
int entry = keys[index];
if (entry == key) { return index; }
if (entry != EMPTY) {
if (entry == DELETED) { firstDeleted = index; }
// Search in table after first failed attempt
int mask = (1 << power) - 1;
int step = tableLookupStep(fraction, mask, power);
int n = 0;
do {
if (check) {
if (n >= occupiedCount) Kit.codeBug();
++n;
}
index = (index + step) & mask;
entry = keys[index];
if (entry == key) { return index; }
if (entry == DELETED && firstDeleted < 0) {
firstDeleted = index;
}
} while (entry != EMPTY);
}
}
// Inserting of new key
if (check && keys != null && keys[index] != EMPTY)
Kit.codeBug();
if (firstDeleted >= 0) {
index = firstDeleted;
}
else {
// Need to consume empty entry: check occupation level
if (keys == null || occupiedCount * 4 >= (1 << power) * 3) {
// Too litle unused entries: rehash
rehashTable(intType);
return insertNewKey(key);
}
++occupiedCount;
}
keys[index] = key;
++keyCount;
return index;
}
private void writeObject(ObjectOutputStream out)
throws IOException
{
out.defaultWriteObject();
int count = keyCount;
if (count != 0) {
boolean hasIntValues = (ivaluesShift != 0);
boolean hasObjectValues = (values != null);
out.writeBoolean(hasIntValues);
out.writeBoolean(hasObjectValues);
for (int i = 0; count != 0; ++i) {
int key = keys[i];
if (key != EMPTY && key != DELETED) {
--count;
out.writeInt(key);
if (hasIntValues) {
out.writeInt(keys[ivaluesShift + i]);
}
if (hasObjectValues) {
out.writeObject(values[i]);
}
}
}
}
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
int writtenKeyCount = keyCount;
if (writtenKeyCount != 0) {
keyCount = 0;
boolean hasIntValues = in.readBoolean();
boolean hasObjectValues = in.readBoolean();
int N = 1 << power;
if (hasIntValues) {
keys = new int[2 * N];
ivaluesShift = N;
}else {
keys = new int[N];
}
for (int i = 0; i != N; ++i) {
keys[i] = EMPTY;
}
if (hasObjectValues) {
values = new Object[N];
}
for (int i = 0; i != writtenKeyCount; ++i) {
int key = in.readInt();
int index = insertNewKey(key);
if (hasIntValues) {
int ivalue = in.readInt();
keys[ivaluesShift + index] = ivalue;
}
if (hasObjectValues) {
values[index] = in.readObject();
}
}
}
}
// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32)
// See Knuth etc.
private static final int A = 0x9e3779b9;
private static final int EMPTY = -1;
private static final int DELETED = -2;
// Structure of kyes and values arrays (N == 1 << power):
// keys[0 <= i < N]: key value or EMPTY or DELETED mark
// values[0 <= i < N]: value of key at keys[i]
// keys[N <= i < 2N]: int values of keys at keys[i - N]
private transient int[] keys;
private transient Object[] values;
private int power;
private int keyCount;
private transient int occupiedCount; // == keyCount + deleted_count
// If ivaluesShift != 0, keys[ivaluesShift + index] contains integer
// values associated with keys
private transient int ivaluesShift;
// If true, enables consitency checks
private static final boolean check = false;
/* TEST START
public static void main(String[] args) {
if (!check) {
System.err.println("Set check to true and re-run");
throw new RuntimeException("Set check to true and re-run");
}
UintMap map;
map = new UintMap();
testHash(map, 2);
map = new UintMap();
testHash(map, 10 * 1000);
map = new UintMap(30 * 1000);
testHash(map, 10 * 100);
map.clear();
testHash(map, 4);
map = new UintMap(0);
testHash(map, 10 * 100);
}
private static void testHash(UintMap map, int N) {
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i, i);
check(i == map.getInt(i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i, i);
check(i == map.getInt(i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i, new Integer(i));
check(-1 == map.getInt(i, -1));
Integer obj = (Integer)map.getObject(i);
check(obj != null && i == obj.intValue());
}
check(map.size() == N);
System.out.print("."); System.out.flush();
int[] keys = map.getKeys();
check(keys.length == N);
for (int i = 0; i != N; ++i) {
int key = keys[i];
check(map.has(key));
check(!map.isIntType(key));
check(map.isObjectType(key));
Integer obj = (Integer) map.getObject(key);
check(obj != null && key == obj.intValue());
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
check(-1 == map.getInt(i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i * i, i);
check(i == map.getInt(i * i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
check(i == map.getInt(i * i, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
map.put(i * i, new Integer(i));
check(-1 == map.getInt(i * i, -1));
map.remove(i * i);
check(!map.has(i * i));
map.put(i * i, i);
check(map.isIntType(i * i));
check(null == map.getObject(i * i));
map.remove(i * i);
check(!map.isObjectType(i * i));
check(!map.isIntType(i * i));
}
int old_size = map.size();
for (int i = 0; i != N; ++i) {
map.remove(i * i);
check(map.size() == old_size);
}
System.out.print("."); System.out.flush();
map.clear();
check(map.size() == 0);
for (int i = 0; i != N; ++i) {
map.put(i * i, i);
map.put(i * i + 1, new Double(i+0.5));
}
checkSameMaps(map, (UintMap)writeAndRead(map));
System.out.print("."); System.out.flush();
map = new UintMap(0);
checkSameMaps(map, (UintMap)writeAndRead(map));
map = new UintMap(1);
checkSameMaps(map, (UintMap)writeAndRead(map));
map = new UintMap(1000);
checkSameMaps(map, (UintMap)writeAndRead(map));
System.out.print("."); System.out.flush();
map = new UintMap(N / 10);
for (int i = 0; i != N; ++i) {
map.put(2*i+1, i);
}
checkSameMaps(map, (UintMap)writeAndRead(map));
System.out.print("."); System.out.flush();
map = new UintMap(N / 10);
for (int i = 0; i != N; ++i) {
map.put(2*i+1, i);
}
for (int i = 0; i != N / 2; ++i) {
map.remove(2*i+1);
}
checkSameMaps(map, (UintMap)writeAndRead(map));
System.out.print("."); System.out.flush();
map = new UintMap();
for (int i = 0; i != N; ++i) {
map.put(2*i+1, new Double(i + 10));
}
for (int i = 0; i != N / 2; ++i) {
map.remove(2*i+1);
}
checkSameMaps(map, (UintMap)writeAndRead(map));
System.out.println(); System.out.flush();
}
private static void checkSameMaps(UintMap map1, UintMap map2) {
check(map1.size() == map2.size());
int[] keys = map1.getKeys();
check(keys.length == map1.size());
for (int i = 0; i != keys.length; ++i) {
int key = keys[i];
check(map2.has(key));
check(map1.isObjectType(key) == map2.isObjectType(key));
check(map1.isIntType(key) == map2.isIntType(key));
Object o1 = map1.getObject(key);
Object o2 = map2.getObject(key);
if (map1.isObjectType(key)) {
check(o1.equals(o2));
}else {
check(map1.getObject(key) == null);
check(map2.getObject(key) == null);
}
if (map1.isIntType(key)) {
check(map1.getExistingInt(key) == map2.getExistingInt(key));
}else {
check(map1.getInt(key, -10) == -10);
check(map1.getInt(key, -11) == -11);
check(map2.getInt(key, -10) == -10);
check(map2.getInt(key, -11) == -11);
}
}
}
private static void check(boolean condition) {
if (!condition) Kit.codeBug();
}
private static Object writeAndRead(Object obj) {
try {
java.io.ByteArrayOutputStream
bos = new java.io.ByteArrayOutputStream();
java.io.ObjectOutputStream
out = new java.io.ObjectOutputStream(bos);
out.writeObject(obj);
out.close();
byte[] data = bos.toByteArray();
java.io.ByteArrayInputStream
bis = new java.io.ByteArrayInputStream(data);
java.io.ObjectInputStream
in = new java.io.ObjectInputStream(bis);
Object result = in.readObject();
in.close();
return result;
}catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException("Unexpected");
}
}
// TEST END */
}