org.mozilla.javascript.ObjToIntMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of js Show documentation
Show all versions of js Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Igor Bukanov
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.javascript;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Map to associate objects to 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 ObjToIntMap implements Serializable
{
static final long serialVersionUID = -1542220580748809402L;
// Map implementation via hashtable,
// follows "The Art of Computer Programming" by Donald E. Knuth
// ObjToIntMap is a copy cat of ObjToIntMap with API adjusted to object keys
public static class Iterator {
Iterator(ObjToIntMap master) {
this.master = master;
}
final void init(Object[] keys, int[] values, int keyCount) {
this.keys = keys;
this.values = values;
this.cursor = -1;
this.remaining = keyCount;
}
public void start() {
master.initIterator(this);
next();
}
public boolean done() {
return remaining < 0;
}
public void next() {
if (remaining == -1) Kit.codeBug();
if (remaining == 0) {
remaining = -1;
cursor = -1;
}else {
for (++cursor; ; ++cursor) {
Object key = keys[cursor];
if (key != null && key != DELETED) {
--remaining;
break;
}
}
}
}
public Object getKey() {
Object key = keys[cursor];
if (key == UniqueTag.NULL_VALUE) { key = null; }
return key;
}
public int getValue() {
return values[cursor];
}
public void setValue(int value) {
values[cursor] = value;
}
ObjToIntMap master;
private int cursor;
private int remaining;
private Object[] keys;
private int[] values;
}
public ObjToIntMap() {
this(4);
}
public ObjToIntMap(int keyCountHint) {
if (keyCountHint < 0) Kit.codeBug();
// Table grow when number of stored keys >= 3/4 of max capacity
int minimalCapacity = keyCountHint * 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(Object key) {
if (key == null) { key = UniqueTag.NULL_VALUE; }
return 0 <= findIndex(key);
}
/**
* Get integer value assigned with key.
* @return key integer value or defaultValue if key is absent
*/
public int get(Object key, int defaultValue) {
if (key == null) { key = UniqueTag.NULL_VALUE; }
int index = findIndex(key);
if (0 <= index) {
return values[index];
}
return defaultValue;
}
/**
* Get integer value assigned with key.
* @return key integer value
* @throws RuntimeException if key does not exist
*/
public int getExisting(Object key) {
if (key == null) { key = UniqueTag.NULL_VALUE; }
int index = findIndex(key);
if (0 <= index) {
return values[index];
}
// Key must exist
Kit.codeBug();
return 0;
}
public void put(Object key, int value) {
if (key == null) { key = UniqueTag.NULL_VALUE; }
int index = ensureIndex(key);
values[index] = value;
}
/**
* If table already contains a key that equals to keyArg, return that key
* while setting its value to zero, otherwise add keyArg with 0 value to
* the table and return it.
*/
public Object intern(Object keyArg) {
boolean nullKey = false;
if (keyArg == null) {
nullKey = true;
keyArg = UniqueTag.NULL_VALUE;
}
int index = ensureIndex(keyArg);
values[index] = 0;
return (nullKey) ? null : keys[index];
}
public void remove(Object key) {
if (key == null) { key = UniqueTag.NULL_VALUE; }
int index = findIndex(key);
if (0 <= index) {
keys[index] = DELETED;
--keyCount;
}
}
public void clear() {
int i = keys.length;
while (i != 0) {
keys[--i] = null;
}
keyCount = 0;
occupiedCount = 0;
}
public Iterator newIterator() {
return new Iterator(this);
}
// The sole purpose of the method is to avoid accessing private fields
// from the Iterator inner class to workaround JDK 1.1 compiler bug which
// generates code triggering VerifierError on recent JVMs
final void initIterator(Iterator i) {
i.init(keys, values, keyCount);
}
/** Return array of present keys */
public Object[] getKeys() {
Object[] array = new Object[keyCount];
getKeys(array, 0);
return array;
}
public void getKeys(Object[] array, int offset) {
int count = keyCount;
for (int i = 0; count != 0; ++i) {
Object key = keys[i];
if (key != null && key != DELETED) {
if (key == UniqueTag.NULL_VALUE) { key = null; }
array[offset] = key;
++offset;
--count;
}
}
}
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(Object key) {
if (keys != null) {
int hash = key.hashCode();
int fraction = hash * A;
int index = fraction >>> (32 - power);
Object test = keys[index];
if (test != null) {
int N = 1 << power;
if (test == key
|| (values[N + index] == hash && test.equals(key)))
{
return index;
}
// Search in table after first failed attempt
int mask = N - 1;
int step = tableLookupStep(fraction, mask, power);
int n = 0;
for (;;) {
if (check) {
if (n >= occupiedCount) Kit.codeBug();
++n;
}
index = (index + step) & mask;
test = keys[index];
if (test == null) {
break;
}
if (test == key
|| (values[N + index] == hash && test.equals(key)))
{
return index;
}
}
}
}
return -1;
}
// Insert key that is not present to table without deleted entries
// and enough free space
private int insertNewKey(Object key, int hash) {
if (check && occupiedCount != keyCount) Kit.codeBug();
if (check && keyCount == 1 << power) Kit.codeBug();
int fraction = hash * A;
int index = fraction >>> (32 - power);
int N = 1 << power;
if (keys[index] != null) {
int mask = N - 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] != null);
}
keys[index] = key;
values[N + index] = hash;
++occupiedCount;
++keyCount;
return index;
}
private void rehashTable() {
if (keys == null) {
if (check && keyCount != 0) Kit.codeBug();
if (check && occupiedCount != 0) Kit.codeBug();
int N = 1 << power;
keys = new Object[N];
values = new int[2 * N];
}
else {
// 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;
Object[] oldKeys = keys;
int[] oldValues = values;
int oldN = oldKeys.length;
keys = new Object[N];
values = new int[2 * N];
int remaining = keyCount;
occupiedCount = keyCount = 0;
for (int i = 0; remaining != 0; ++i) {
Object key = oldKeys[i];
if (key != null && key != DELETED) {
int keyHash = oldValues[oldN + i];
int index = insertNewKey(key, keyHash);
values[index] = oldValues[i];
--remaining;
}
}
}
}
// Ensure key index creating one if necessary
private int ensureIndex(Object key) {
int hash = key.hashCode();
int index = -1;
int firstDeleted = -1;
if (keys != null) {
int fraction = hash * A;
index = fraction >>> (32 - power);
Object test = keys[index];
if (test != null) {
int N = 1 << power;
if (test == key
|| (values[N + index] == hash && test.equals(key)))
{
return index;
}
if (test == DELETED) {
firstDeleted = index;
}
// Search in table after first failed attempt
int mask = N - 1;
int step = tableLookupStep(fraction, mask, power);
int n = 0;
for (;;) {
if (check) {
if (n >= occupiedCount) Kit.codeBug();
++n;
}
index = (index + step) & mask;
test = keys[index];
if (test == null) {
break;
}
if (test == key
|| (values[N + index] == hash && test.equals(key)))
{
return index;
}
if (test == DELETED && firstDeleted < 0) {
firstDeleted = index;
}
}
}
}
// Inserting of new key
if (check && keys != null && keys[index] != null)
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();
return insertNewKey(key, hash);
}
++occupiedCount;
}
keys[index] = key;
values[(1 << power) + index] = hash;
++keyCount;
return index;
}
private void writeObject(ObjectOutputStream out)
throws IOException
{
out.defaultWriteObject();
int count = keyCount;
for (int i = 0; count != 0; ++i) {
Object key = keys[i];
if (key != null && key != DELETED) {
--count;
out.writeObject(key);
out.writeInt(values[i]);
}
}
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
int writtenKeyCount = keyCount;
if (writtenKeyCount != 0) {
keyCount = 0;
int N = 1 << power;
keys = new Object[N];
values = new int[2 * N];
for (int i = 0; i != writtenKeyCount; ++i) {
Object key = in.readObject();
int hash = key.hashCode();
int index = insertNewKey(key, hash);
values[index] = in.readInt();
}
}
}
// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32)
// See Knuth etc.
private static final int A = 0x9e3779b9;
private static final Object DELETED = new Object();
// Structure of kyes and values arrays (N == 1 << power):
// keys[0 <= i < N]: key value or null or DELETED mark
// values[0 <= i < N]: value of key at keys[i]
// values[N <= i < 2*N]: hash code of key at keys[i-N]
private transient Object[] keys;
private transient int[] values;
private int power;
private int keyCount;
private transient int occupiedCount; // == keyCount + deleted_count
// 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");
}
ObjToIntMap map;
map = new ObjToIntMap(0);
testHash(map, 3);
map = new ObjToIntMap(0);
testHash(map, 10 * 1000);
map = new ObjToIntMap();
testHash(map, 10 * 1000);
map = new ObjToIntMap(30 * 1000);
testHash(map, 10 * 100);
map.clear();
testHash(map, 4);
map = new ObjToIntMap(0);
testHash(map, 10 * 100);
}
private static void testHash(ObjToIntMap map, int N) {
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i);
check(-1 == map.get(key, -1));
map.put(key, i);
check(i == map.get(key, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i);
map.put(key, i);
check(i == map.get(key, -1));
}
check(map.size() == N);
System.out.print("."); System.out.flush();
Object[] keys = map.getKeys();
check(keys.length == N);
for (int i = 0; i != N; ++i) {
Object key = keys[i];
check(map.has(key));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i);
check(i == map.get(key, -1));
}
int Nsqrt = -1;
for (int i = 0; ; ++i) {
if (i * i >= N) {
Nsqrt = i;
break;
}
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i * i);
map.put(key, i);
check(i == map.get(key, -1));
}
check(map.size() == 2 * N - Nsqrt);
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i * i);
check(i == map.get(key, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(-1 - i * i);
map.put(key, i);
check(i == map.get(key, -1));
}
check(map.size() == 3 * N - Nsqrt);
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(-1 - i * i);
map.remove(key);
check(!map.has(key));
}
check(map.size() == 2 * N - Nsqrt);
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i * i);
check(i == map.get(key, -1));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i);
int j = intSqrt(i);
if (j * j == i) {
check(j == map.get(key, -1));
}else {
check(i == map.get(key, -1));
}
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i * i);
map.remove(key);
check(-2 == map.get(key, -2));
}
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i);
map.put(key, i);
check(i == map.get(key, -2));
}
check(map.size() == N);
System.out.print("."); System.out.flush();
for (int i = 0; i != N; ++i) {
Object key = testKey(i);
check(i == map.get(key, -1));
}
System.out.print("."); System.out.flush();
ObjToIntMap copy = (ObjToIntMap)writeAndRead(map);
check(copy.size() == N);
for (int i = 0; i != N; ++i) {
Object key = testKey(i);
check(i == copy.get(key, -1));
}
System.out.print("."); System.out.flush();
checkSameMaps(copy, map);
System.out.println(); System.out.flush();
}
private static void checkSameMaps(ObjToIntMap map1, ObjToIntMap map2) {
check(map1.size() == map2.size());
Object[] keys = map1.getKeys();
check(keys.length == map1.size());
for (int i = 0; i != keys.length; ++i) {
check(map1.get(keys[i], -1) == map2.get(keys[i], -1));
}
}
private static void check(boolean condition) {
if (!condition) Kit.codeBug();
}
private static Object[] testPool;
private static Object testKey(int i) {
int MAX_POOL = 100;
if (0 <= i && i < MAX_POOL) {
if (testPool != null && testPool[i] != null) {
return testPool[i];
}
}
Object x = new Double(i + 0.5);
if (0 <= i && i < MAX_POOL) {
if (testPool == null) {
testPool = new Object[MAX_POOL];
}
testPool[i] = x;
}
return x;
}
private static int intSqrt(int i) {
int approx = (int)Math.sqrt(i) + 1;
while (approx * approx > i) {
--approx;
}
return approx;
}
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) {
throw new RuntimeException("Unexpected");
}
}
// TEST END */
}