Refactor weak tables including proper weak key semantics and improved userdata handling.

This commit is contained in:
James Roseborough
2010-06-21 01:31:40 +00:00
parent 03cebfbf82
commit dc84bc9e8d
8 changed files with 523 additions and 414 deletions

View File

@@ -21,6 +21,8 @@
******************************************************************************/ ******************************************************************************/
package org.luaj.vm2; package org.luaj.vm2;
import java.util.Vector;
public class LuaTable extends LuaValue { public class LuaTable extends LuaValue {
private static final int MIN_HASH_CAPACITY = 2; private static final int MIN_HASH_CAPACITY = 2;
private static final LuaString N = valueOf("n"); private static final LuaString N = valueOf("n");
@@ -29,28 +31,28 @@ public class LuaTable extends LuaValue {
protected LuaValue[] hashKeys; protected LuaValue[] hashKeys;
protected LuaValue[] hashValues; protected LuaValue[] hashValues;
private int hashEntries; private int hashEntries;
private LuaValue m_metatable; protected LuaValue m_metatable;
public LuaTable() { public LuaTable() {
array = NOVALS; array = NOVALS;
hashKeys = NOVALS; hashKeys = NOVALS;
hashValues = NOVALS; hashValues = NOVALS;
} }
public LuaTable(int narray, int nhash) { public LuaTable(int narray, int nhash) {
presize(narray, nhash); presize(narray, nhash);
} }
public LuaTable(LuaValue[] named, LuaValue[] unnamed, Varargs lastarg) { public LuaTable(LuaValue[] named, LuaValue[] unnamed, Varargs lastarg) {
int nn = (named!=null? named.length: 0); int nn = (named!=null? named.length: 0);
int nu = (unnamed!=null? unnamed.length: 0); int nu = (unnamed!=null? unnamed.length: 0);
int nl = (lastarg!=null? lastarg.narg(): 0); int nl = (lastarg!=null? lastarg.narg(): 0);
presize(nu+nl, nn-(nn>>1)); presize(nu+nl, nn-(nn>>1));
for ( int i=0; i<nu; i++ ) for ( int i=0; i<nu; i++ )
array[i] = unnamed[i].optvalue(null); rawset(i+1,unnamed[i].optvalue(null));
if ( lastarg != null ) if ( lastarg != null )
for ( int i=0,n=lastarg.narg(); i<n; ++i ) for ( int i=1,n=lastarg.narg(); i<=n; ++i )
array[nu+i] = lastarg.arg(i+1).optvalue(null); rawset(nu+i,lastarg.arg(i).optvalue(null));
for ( int i=0; i<nn; i+=2 ) for ( int i=0; i<nn; i+=2 )
if (!named[i+1].isnil()) if (!named[i+1].isnil())
rawset(named[i], named[i+1]); rawset(named[i], named[i+1]);
@@ -68,15 +70,6 @@ public class LuaTable extends LuaValue {
for ( int i=1; i<=n; i++ ) for ( int i=1; i<=n; i++ )
set(i, varargs.arg(i+nskip)); set(i, varargs.arg(i+nskip));
} }
private void presize(int narray, int nhash) {
if ( nhash > 0 && nhash < MIN_HASH_CAPACITY )
nhash = MIN_HASH_CAPACITY;
array = (narray>0? new LuaValue[narray]: NOVALS);
hashKeys = (nhash>0? new LuaValue[nhash]: NOVALS);
hashValues = (nhash>0? new LuaValue[nhash]: NOVALS);
hashEntries = 0;
}
public int type() { public int type() {
return LuaValue.TTABLE; return LuaValue.TTABLE;
@@ -98,9 +91,18 @@ public class LuaTable extends LuaValue {
return this; return this;
} }
public void presize( int i ) { public void presize( int narray ) {
if ( i > array.length ) if ( narray > array.length )
array = resize( array, i ); array = resize( array, narray );
}
public void presize(int narray, int nhash) {
if ( nhash > 0 && nhash < MIN_HASH_CAPACITY )
nhash = MIN_HASH_CAPACITY;
array = (narray>0? new LuaValue[narray]: NOVALS);
hashKeys = (nhash>0? new LuaValue[nhash]: NOVALS);
hashValues = (nhash>0? new LuaValue[nhash]: NOVALS);
hashEntries = 0;
} }
private static LuaValue[] resize( LuaValue[] old, int n ) { private static LuaValue[] resize( LuaValue[] old, int n ) {
@@ -109,6 +111,14 @@ public class LuaTable extends LuaValue {
return v; return v;
} }
protected int getArrayLength() {
return array.length;
}
protected int getHashLength() {
return hashValues.length;
}
public LuaValue getmetatable() { public LuaValue getmetatable() {
if ( m_metatable!=null ) if ( m_metatable!=null )
return m_metatable.rawget(METATABLE).optvalue(m_metatable); return m_metatable.rawget(METATABLE).optvalue(m_metatable);
@@ -130,25 +140,11 @@ public class LuaTable extends LuaValue {
} }
protected LuaTable changemode(boolean weakkeys, boolean weakvalues) { protected LuaTable changemode(boolean weakkeys, boolean weakvalues) {
if ( weakkeys || weakvalues ) { if ( weakkeys || weakvalues )
return recreateas(weakkeys, weakvalues); return new WeakTable(weakkeys, weakvalues, this);
}
return this; return this;
} }
protected LuaTable recreateas(boolean weakkeys, boolean weakvalues) {
LuaTable t = weakkeys||weakvalues?
new WeakTable(weakkeys, weakvalues):
new LuaTable();
t.presize(array.length,hashKeys.length);
Varargs n;
LuaValue k = NIL;
while ( !(k = ((n = next(k)).arg1())).isnil() )
t.rawset(k, n.arg(2));
t.m_metatable = m_metatable;
return t;
}
public LuaValue get( int key ) { public LuaValue get( int key ) {
LuaValue v = rawget(key); LuaValue v = rawget(key);
return v.isnil() && m_metatable!=null? gettable(this,valueOf(key)): v; return v.isnil() && m_metatable!=null? gettable(this,valueOf(key)): v;
@@ -173,7 +169,7 @@ public class LuaTable extends LuaValue {
} }
return hashget( key ); return hashget( key );
} }
private LuaValue hashget(LuaValue key) { private LuaValue hashget(LuaValue key) {
if ( hashEntries > 0 ) { if ( hashEntries > 0 ) {
LuaValue v = hashValues[hashFindSlot(key)]; LuaValue v = hashValues[hashFindSlot(key)];
@@ -181,7 +177,7 @@ public class LuaTable extends LuaValue {
} }
return NIL; return NIL;
} }
public void set( int key, LuaValue value ) { public void set( int key, LuaValue value ) {
if ( m_metatable==null || ! rawget(key).isnil() || ! settable(this,LuaInteger.valueOf(key),value) ) if ( m_metatable==null || ! rawget(key).isnil() || ! settable(this,LuaInteger.valueOf(key),value) )
rawset(key, value); rawset(key, value);
@@ -234,8 +230,6 @@ public class LuaTable extends LuaValue {
public LuaValue remove(int pos) { public LuaValue remove(int pos) {
if ( pos == 0 ) if ( pos == 0 )
pos = length(); pos = length();
if ( pos < 1 || pos > array.length )
return NONE;
LuaValue v = rawget(pos); LuaValue v = rawget(pos);
for ( LuaValue r=v; !r.isnil(); ) { for ( LuaValue r=v; !r.isnil(); ) {
r = rawget(pos+1); r = rawget(pos+1);
@@ -267,9 +261,9 @@ public class LuaTable extends LuaValue {
} }
public LuaValue getn() { public LuaValue getn() {
for ( int n=array.length; --n>0; ) for ( int n=getArrayLength(); n>0; --n )
if ( array[n]!=null ) if ( !rawget(n).isnil() )
return LuaInteger.valueOf(n+1); return LuaInteger.valueOf(n);
return ZERO; return ZERO;
} }
@@ -277,10 +271,11 @@ public class LuaTable extends LuaValue {
* Get the length of this table, as lua defines it. * Get the length of this table, as lua defines it.
*/ */
public int length() { public int length() {
int n=array.length+1,m=0; int a = getArrayLength();
int n = a+1,m=0;
while ( !rawget(n).isnil() ) { while ( !rawget(n).isnil() ) {
m = n; m = n;
n += array.length+hashEntries+1; n += a+getHashLength()+1;
} }
while ( n > m+1 ) { while ( n > m+1 ) {
int k = (n+m) / 2; int k = (n+m) / 2;
@@ -312,7 +307,10 @@ public class LuaTable extends LuaValue {
return n; return n;
} }
/**
* Get the next element after a particular key in the table
* @return key,value or nil
*/
/** /**
* Get the next element after a particular key in the table * Get the next element after a particular key in the table
* @return key,value or nil * @return key,value or nil
@@ -352,7 +350,7 @@ public class LuaTable extends LuaValue {
// nothing found, push nil, return nil. // nothing found, push nil, return nil.
return NIL; return NIL;
} }
/** /**
* Get the next element after a particular key in the * Get the next element after a particular key in the
* contiguous array part of a table * contiguous array part of a table
@@ -371,16 +369,13 @@ public class LuaTable extends LuaValue {
* @param func function to call * @param func function to call
*/ */
public LuaValue foreach(LuaValue func) { public LuaValue foreach(LuaValue func) {
LuaValue v = NIL; Varargs n;
for ( int i=0; i<array.length; i++ ) LuaValue k = NIL;
if ( array[i] != null ) LuaValue v;
if ( !(v = func.call(LuaInteger.valueOf(i+1), array[i])).isnil() ) while ( !(k = ((n = next(k)).arg1())).isnil() )
return v; if ( ! (v = func.call(k, n.arg(2))).isnil() )
for ( int i=0; i<hashKeys.length; i++ ) return v;
if ( hashKeys[i] != null ) return NIL;
if ( !(v = func.call(hashKeys[i], hashValues[i])).isnil() )
return v;
return v;
} }
/** /**
@@ -390,46 +385,15 @@ public class LuaTable extends LuaValue {
* @param func * @param func
*/ */
public LuaValue foreachi(LuaValue func) { public LuaValue foreachi(LuaValue func) {
LuaValue v = NIL; Varargs n;
for ( int i=0; i<array.length && array[i]!=null; i++ ) LuaValue k = NIL;
if ( !(v = func.call(LuaInteger.valueOf(i+1), array[i])).isnil() ) LuaValue v;
while ( !(k = ((n = inext(k)).arg1())).isnil() )
if ( ! (v = func.call(k, n.arg(2))).isnil() )
return v; return v;
return v; return NIL;
} }
// ======================= test hooks =================
/** Value used in testing to provide the capacity of the array part */
int arrayCapacity() {
return array.length;
}
/** Value used in testing to provide the capacity of the hash part */
int hashCapacity() {
return hashKeys.length;
}
/** Value used in testing to provide the total count of elements */
int keyCount() {
int n = 0;
for ( int i=0; i<array.length; i++ )
if ( array[i] != null )
++n;
return n + hashEntries;
}
/** Value used in testing to enumerate the keys */
public LuaValue[] keys() {
LuaValue[] vals = new LuaValue[keyCount()];
int n = 0;
for ( int i=0; i<array.length; i++ )
if ( array[i] != null )
vals[n++] = LuaInteger.valueOf(i+1);
for ( int i=0; i<hashKeys.length; i++ )
if ( hashKeys[i] != null )
vals[n++] = hashKeys[i];
return vals;
}
// ======================= hashset ================= // ======================= hashset =================
@@ -592,4 +556,21 @@ public class LuaTable extends LuaValue {
array[j] = a; array[j] = a;
} }
/** @deprecate - count via iteration instead */
public int keyCount() {
return keys().length;
}
/** @deprecate - use next() instead */
public LuaValue[] keys() {
Vector l = new Vector();
LuaValue k = LuaValue.NIL;
while ( true ) {
Varargs n = next(k);
if ( (k = n.arg1()).isnil() )
break;
l.add( k );
}
return (LuaValue[]) l.toArray(new LuaValue[l.size()]);
}
} }

View File

@@ -167,6 +167,7 @@ public class LuaValue extends Varargs {
public static LuaValue argerror(int iarg,String msg) { throw new LuaError("bad argument #"+iarg+": "+msg); } public static LuaValue argerror(int iarg,String msg) { throw new LuaError("bad argument #"+iarg+": "+msg); }
protected LuaValue typerror(String expected) { throw new LuaError(expected+" expected, got "+typename()); } protected LuaValue typerror(String expected) { throw new LuaError(expected+" expected, got "+typename()); }
protected LuaValue unimplemented(String fun) { throw new LuaError("'"+fun+"' not implemented for "+typename()); } protected LuaValue unimplemented(String fun) { throw new LuaError("'"+fun+"' not implemented for "+typename()); }
protected LuaValue illegal(String op,String typename) { throw new LuaError("illegal operation '"+op+"' for "+typename); }
protected LuaValue callerror() { throw new LuaError("attempt to call "+typename()); } protected LuaValue callerror() { throw new LuaError("attempt to call "+typename()); }
protected LuaValue lenerror() { throw new LuaError("attempt to get length of "+typename()); } protected LuaValue lenerror() { throw new LuaError("attempt to get length of "+typename()); }
protected LuaValue aritherror() { throw new LuaError("attempt to perform arithmetic on "+typename()); } protected LuaValue aritherror() { throw new LuaError("attempt to perform arithmetic on "+typename()); }
@@ -316,6 +317,7 @@ public class LuaValue extends Varargs {
// lua number/string conversion // lua number/string conversion
public LuaString strvalue() { typerror("strValue"); return null; } public LuaString strvalue() { typerror("strValue"); return null; }
public LuaValue strongkey() { return this; }
public LuaValue strongvalue() { return this; } public LuaValue strongvalue() { return this; }
// conversion from java values // conversion from java values

View File

@@ -23,170 +23,269 @@ package org.luaj.vm2;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
public class WeakTable extends LuaTable { import org.luaj.vm2.lib.TwoArgFunction;
private final boolean weakKeys,weakValues; public class WeakTable extends LuaTable {
private LuaTable backing;
private boolean weakkeys,weakvalues;
WeakTable( boolean weakKeys, boolean weakValues ) { public WeakTable(boolean weakkeys, boolean weakvalues) {
this.weakKeys = weakKeys; this(weakkeys, weakvalues, 0, 0);
this.weakValues = weakValues; }
protected WeakTable(boolean weakkeys, boolean weakvalues, int narray, int nhash) {
this.backing = new LuaTable(narray, nhash);
this.weakkeys = weakkeys;
this.weakvalues = weakvalues;
}
protected WeakTable(boolean weakkeys, boolean weakvalues, LuaTable source) {
this(weakkeys, weakvalues, source.getArrayLength(), source.getHashLength());
Varargs n;
LuaValue k = NIL;
while ( !(k = ((n = source.next(k)).arg1())).isnil() )
rawset(k, n.arg(2));
m_metatable = source.m_metatable;
}
public void presize( int narray ) {
backing.presize(narray);
} }
private static class WeakValue extends LuaValue { public void presize(int narray, int nhash) {
private final WeakReference ref; backing.presize(narray, nhash);
public WeakValue(LuaValue val) { }
ref = new WeakReference(val);
protected int getArrayLength() {
return backing.getArrayLength();
}
protected int getHashLength() {
return backing.getHashLength();
}
protected WeakTable changemode(boolean weakkeys, boolean weakvalues) {
this.weakkeys = weakkeys;
this.weakvalues = weakvalues;
return this;
}
LuaValue weaken( LuaValue value ) {
switch ( value.type() ) {
case LuaValue.TFUNCTION:
case LuaValue.TTHREAD:
case LuaValue.TTABLE:
return new WeakValue(value);
case LuaValue.TUSERDATA:
return new WeakUserdata(value);
default:
return value;
} }
}
public void rawset( int key, LuaValue value ) {
if ( weakvalues )
value = weaken( value );
backing.set(key, value);
}
/** caller must ensure key is not nil */
public void rawset( LuaValue key, LuaValue value ) {
if ( weakvalues )
value = weaken( value );
if ( weakkeys ) {
switch ( key.type() ) {
case LuaValue.TFUNCTION:
case LuaValue.TTHREAD:
case LuaValue.TTABLE:
case LuaValue.TUSERDATA:
key = value = new WeakEntry(this, key, value);
break;
default:
break;
}
}
backing.set(key, value);
}
public LuaValue rawget( int key ) {
return rawget(valueOf(key));
}
public LuaValue rawget( LuaValue key ) {
LuaValue v = backing.rawget(key);
if ( v.isnil() )
return NIL;
v = v.strongvalue();
if ( v.isnil() )
backing.rawset(key, NIL);
return v;
}
public int maxn() {
return backing.maxn();
}
/**
* Get the next element after a particular key in the table
* @return key,value or nil
*/
public Varargs next( LuaValue key ) {
while ( true ) {
Varargs n = backing.next(key);
LuaValue k = n.arg1();
if ( k.isnil() )
return NIL;
LuaValue ks = k.strongkey();
LuaValue vs = n.arg(2).strongvalue();
if ( ks.isnil() || vs.isnil() ) {
backing.rawset(ks, NIL);
} else {
return varargsOf(ks,vs);
}
}
}
/**
* Get the next element after a particular key in the
* contiguous array part of a table
* @return key,value or nil
*/
public Varargs inext(LuaValue key) {
int k = key.optint(0)+1;
LuaValue v = this.rawget(k);
return v.isnil()? NIL: varargsOf(valueOf(k),v);
}
// ----------------- sort support -----------------------------
public void sort(final LuaValue comparator) {
backing.sort( new TwoArgFunction() {
public LuaValue call(LuaValue arg1, LuaValue arg2) {
return comparator.call( arg1.strongvalue(), arg2.strongvalue() );
}
} );
}
static class WeakValue extends LuaValue {
final WeakReference ref;
protected WeakValue(LuaValue value) {
ref = new WeakReference(value);
}
public int type() { public int type() {
return strongvalue().type(); illegal("type","weak value");
return 0;
} }
public String typename() { public String typename() {
return "weakvalue"; illegal("typename","weak value");
return null;
} }
public String toString() {
return "weak<"+ref.get()+">";
}
public LuaValue strongkey() {
Object o = ref.get();
return o!=null? (LuaValue)o: NIL;
}
public LuaValue strongvalue() { public LuaValue strongvalue() {
Object o = ref.get(); Object o = ref.get();
return o!=null? (LuaValue)o: NIL; return o!=null? (LuaValue)o: NIL;
} }
public String tojstring() {
return strongvalue().tojstring();
}
}
private static class WeakUserdata extends LuaValue {
private WeakReference ref;
private WeakReference mt;
public WeakUserdata(Object val, LuaValue metatable) {
this.ref = new WeakReference(val);
this.mt = new WeakReference(metatable);
}
public int type() {
return TVALUE;
}
public String typename() {
return "weakuserdata";
}
public LuaValue strongvalue() {
if ( ref != null ) {
Object o = ref.get();
if ( o != null )
return userdataOf( o, (LuaValue) mt.get() );
}
ref = mt = null;
return NIL;
}
}
private static class WeakEntry extends LuaValue {
private LuaValue key;
private LuaValue val;
private WeakEntry(LuaValue key, LuaValue val) {
this.key = key;
this.val = val;
}
public int type() {
return LuaValue.TNIL;
}
public String typename() {
return "weakentry";
}
public LuaValue strongkey() {
LuaValue k = key.strongvalue();
LuaValue v = val.strongvalue();
if ( k.isnil() || v.isnil() )
return key = val = NIL;
return k;
}
public LuaValue strongvalue() {
LuaValue k = key.strongvalue();
LuaValue v = val.strongvalue();
if ( k.isnil() || v.isnil() )
return key = val = NIL;
return v;
}
public boolean eq_b(LuaValue rhs) { public boolean eq_b(LuaValue rhs) {
return strongkey().eq_b(rhs); Object o = ref.get();
} return o!=null && rhs.eq_b((LuaValue)o);
public int hashCode() {
return strongkey().hashCode();
} }
} }
static final class WeakUserdata extends WeakValue {
private boolean shouldWeaken( LuaValue value ) { private final WeakReference ob;
switch ( value.type() ) { private final WeakReference mt;
case LuaValue.TFUNCTION:
case LuaValue.TTHREAD: private WeakUserdata(LuaValue value) {
case LuaValue.TTABLE: super(value);
case LuaValue.TUSERDATA: ob = new WeakReference(value.touserdata());
LuaValue udmt = value.getmetatable();
mt = udmt!=null? new WeakReference(udmt): null;
}
public LuaValue strongvalue() {
Object u = ref.get();
if ( u != null )
return (LuaValue) u;
Object o = ob.get();
Object m = mt!=null? mt.get(): null;
return o!=null? m!=null? userdataOf(o,(LuaValue)m): userdataOf(o): NIL;
}
public boolean eq_b(LuaValue rhs) {
return rhs.isuserdata() && (rhs.touserdata() == ob.get());
}
public boolean isuserdata() {
return true; return true;
} }
return false;
} public Object touserdata() {
return ob.get();
private LuaValue toWeak( LuaValue value ) {
switch ( value.type() ) {
case LuaValue.TFUNCTION:
case LuaValue.TTHREAD:
case LuaValue.TTABLE: return new WeakValue( value );
case LuaValue.TUSERDATA: return new WeakUserdata( value.checkuserdata(), value.getmetatable() );
default: return value;
} }
} }
public LuaValue rawget(int key) { static final class WeakEntry extends LuaValue {
LuaValue v = super.rawget(key); final WeakTable table;
if ( v.isnil() ) final LuaValue weakkey;
return NIL; final int keyhash;
v = v.strongvalue();
if ( v.isnil() ) {
// TODO: mark table for culling?
super.rawset(key, NIL);
}
return v;
}
public LuaValue rawget(LuaValue key) { private WeakEntry(WeakTable table, LuaValue key, LuaValue weakvalue) {
LuaValue v = super.rawget(key); this.table = table;
if ( v.isnil() ) this.weakkey = table.weaken(key);
return NIL; this.keyhash = key.hashCode();
v = v.strongvalue();
if ( v.isnil() ) {
// TODO: mark table for culling?
super.rawset(key, NIL);
}
return v;
}
public void rawset(int key, LuaValue val) {
if ( val.isnil() || !weakValues || !shouldWeaken(val) ) {
super.rawset(key, val);
} else {
super.rawset(key, toWeak(val));
}
}
public void rawset(LuaValue key, LuaValue val) { // store an association from table to value in the key's metatable
if ( val.isnil() ) { LuaValue mt = key.getmetatable();
super.rawset(key, val); if ( mt == null )
} else { key.setmetatable(mt=new LuaTable(0,1));
boolean weakenKey = weakKeys && shouldWeaken(key); mt.set(table, weakvalue);
boolean weakenVal = weakValues && shouldWeaken(val); }
if ( weakenKey ) {
WeakEntry e = new WeakEntry( toWeak(key), weakenVal? toWeak(val): val); // when looking up the value, look in the keys metatable
super.rawset(e, e); public LuaValue strongvalue() {
} else if ( weakenVal ) { LuaValue key = weakkey.strongkey();
super.rawset(key, toWeak(val)); if ( key.isnil() )
} else { return NIL;
super.rawset(key, val); LuaValue mt = key.getmetatable();
} if ( mt == null )
return NIL;
LuaValue weakvalue = mt.get(table);
return weakvalue.strongvalue();
} }
}
protected LuaTable changemode(boolean k, boolean v) {
if ( k!=this.weakKeys || v!=weakValues )
return recreateas(k,v);
return this;
}
public int type() {
illegal("type","weak entry");
return 0;
}
public String typename() {
illegal("typename","weak entry");
return null;
}
public String toString() {
return "weak<"+strongkey()+","+strongvalue()+">";
}
public int hashCode() {
return keyhash;
}
public boolean eq_b(LuaValue rhs) {
return rhs.eq_b(weakkey.strongkey());
}
}
} }

View File

@@ -26,6 +26,7 @@ import junit.framework.TestSuite;
import org.luaj.vm2.WeakTableTest.WeakKeyTableTest; import org.luaj.vm2.WeakTableTest.WeakKeyTableTest;
import org.luaj.vm2.WeakTableTest.WeakKeyValueTableTest; import org.luaj.vm2.WeakTableTest.WeakKeyValueTableTest;
import org.luaj.vm2.WeakTableTest.WeakValueTableTest;
import org.luaj.vm2.compiler.CompilerUnitTests; import org.luaj.vm2.compiler.CompilerUnitTests;
import org.luaj.vm2.compiler.DumpLoadEndianIntTest; import org.luaj.vm2.compiler.DumpLoadEndianIntTest;
import org.luaj.vm2.compiler.RegressionTests; import org.luaj.vm2.compiler.RegressionTests;
@@ -52,7 +53,7 @@ public class AllTests {
table.addTestSuite(TableTest.class); table.addTestSuite(TableTest.class);
table.addTestSuite(TableArrayTest.class); table.addTestSuite(TableArrayTest.class);
table.addTestSuite(TableHashTest.class); table.addTestSuite(TableHashTest.class);
table.addTestSuite(WeakTableTest.class); table.addTestSuite(WeakValueTableTest.class);
table.addTestSuite(WeakKeyTableTest.class); table.addTestSuite(WeakKeyTableTest.class);
table.addTestSuite(WeakKeyValueTableTest.class); table.addTestSuite(WeakKeyValueTableTest.class);
suite.addTest(table); suite.addTest(table);

View File

@@ -57,10 +57,10 @@ public class TableArrayTest extends TestCase {
} }
// Ensure capacities make sense // Ensure capacities make sense
assertEquals( 0, t.hashCapacity() ); assertEquals( 0, t.getHashLength() );
assertTrue( t.arrayCapacity() >= 32 ); assertTrue( t.getArrayLength() >= 32 );
assertTrue( t.arrayCapacity() <= 64 ); assertTrue( t.getArrayLength() <= 64 );
} }
@@ -79,8 +79,8 @@ public class TableArrayTest extends TestCase {
assertEquals(LuaInteger.valueOf(i), t.get(i)); assertEquals(LuaInteger.valueOf(i), t.get(i));
} }
assertTrue( t.arrayCapacity() >= 0 && t.arrayCapacity() <= 2 ); assertTrue( t.getArrayLength() >= 0 && t.getArrayLength() <= 2 );
assertTrue( t.hashCapacity() >= 4 ); assertTrue( t.getHashLength() >= 4 );
} }
public void testOutOfOrderIntegerKeyInsertion() { public void testOutOfOrderIntegerKeyInsertion() {
@@ -96,11 +96,11 @@ public class TableArrayTest extends TestCase {
} }
// Ensure capacities make sense // Ensure capacities make sense
assertTrue( t.arrayCapacity() >= 0 ); assertTrue( t.getArrayLength() >= 0 );
assertTrue( t.arrayCapacity() <= 6 ); assertTrue( t.getArrayLength() <= 6 );
assertTrue( t.hashCapacity() >= 16 ); assertTrue( t.getHashLength() >= 16 );
assertTrue( t.hashCapacity() <= 64 ); assertTrue( t.getHashLength() <= 64 );
} }
@@ -113,10 +113,10 @@ public class TableArrayTest extends TestCase {
t.set( str, LuaInteger.valueOf( i ) ); t.set( str, LuaInteger.valueOf( i ) );
} }
assertTrue( t.arrayCapacity() >= 9 ); // 1, 2, ..., 9 assertTrue( t.getArrayLength() >= 9 ); // 1, 2, ..., 9
assertTrue( t.arrayCapacity() <= 18 ); assertTrue( t.getArrayLength() <= 18 );
assertTrue( t.hashCapacity() >= 11 ); // 0, "0", "1", ..., "9" assertTrue( t.getHashLength() >= 11 ); // 0, "0", "1", ..., "9"
assertTrue( t.hashCapacity() <= 33 ); assertTrue( t.getHashLength() <= 33 );
LuaValue[] keys = t.keys(); LuaValue[] keys = t.keys();

View File

@@ -44,7 +44,7 @@ public class TableHashTest extends TestCase {
public void testSetRemove() { public void testSetRemove() {
LuaTable t = new_Table(); LuaTable t = new_Table();
assertEquals( 0, t.hashCapacity() ); assertEquals( 0, t.getHashLength() );
assertEquals( 0, t.length() ); assertEquals( 0, t.length() );
assertEquals( 0, t.keyCount() ); assertEquals( 0, t.keyCount() );
@@ -52,13 +52,13 @@ public class TableHashTest extends TestCase {
"cd", "ef", "g", "hi", "jk", "lm", "no", "pq", "rs", }; "cd", "ef", "g", "hi", "jk", "lm", "no", "pq", "rs", };
int[] capacities = { 0, 2, 4, 4, 7, 7, 7, 10, 10, 14, 14, 14, 14, 19, 19, 19, 19, 25, 25, 25 }; int[] capacities = { 0, 2, 4, 4, 7, 7, 7, 10, 10, 14, 14, 14, 14, 19, 19, 19, 19, 25, 25, 25 };
for ( int i = 0; i < keys.length; ++i ) { for ( int i = 0; i < keys.length; ++i ) {
assertEquals( capacities[i], t.hashCapacity() ); assertEquals( capacities[i], t.getHashLength() );
String si = "Test Value! "+i; String si = "Test Value! "+i;
t.set( keys[i], si ); t.set( keys[i], si );
assertEquals( 0, t.length() ); assertEquals( 0, t.length() );
assertEquals( i+1, t.keyCount() ); assertEquals( i+1, t.keyCount() );
} }
assertEquals( capacities[keys.length], t.hashCapacity() ); assertEquals( capacities[keys.length], t.getHashLength() );
for ( int i = 0; i < keys.length; ++i ) { for ( int i = 0; i < keys.length; ++i ) {
LuaValue vi = LuaString.valueOf( "Test Value! "+i ); LuaValue vi = LuaString.valueOf( "Test Value! "+i );
assertEquals( vi, t.get( keys[i] ) ); assertEquals( vi, t.get( keys[i] ) );
@@ -72,7 +72,7 @@ public class TableHashTest extends TestCase {
t.set( keys[i], LuaString.valueOf( "Replacement Value! "+i ) ); t.set( keys[i], LuaString.valueOf( "Replacement Value! "+i ) );
assertEquals( 0, t.length() ); assertEquals( 0, t.length() );
assertEquals( keys.length, t.keyCount() ); assertEquals( keys.length, t.keyCount() );
assertEquals( capacities[keys.length], t.hashCapacity() ); assertEquals( capacities[keys.length], t.getHashLength() );
} }
for ( int i = 0; i < keys.length; ++i ) { for ( int i = 0; i < keys.length; ++i ) {
LuaValue vi = LuaString.valueOf( "Replacement Value! "+i ); LuaValue vi = LuaString.valueOf( "Replacement Value! "+i );
@@ -85,9 +85,9 @@ public class TableHashTest extends TestCase {
assertEquals( 0, t.length() ); assertEquals( 0, t.length() );
assertEquals( keys.length-i-1, t.keyCount() ); assertEquals( keys.length-i-1, t.keyCount() );
if ( i<keys.length-1 ) if ( i<keys.length-1 )
assertEquals( capacities[keys.length], t.hashCapacity() ); assertEquals( capacities[keys.length], t.getHashLength() );
else else
assertTrue( 0<=t.hashCapacity() ); assertTrue( 0<=t.getHashLength() );
} }
for ( int i = 0; i < keys.length; ++i ) { for ( int i = 0; i < keys.length; ++i ) {
assertEquals( LuaValue.NIL, t.get( keys[i] ) ); assertEquals( LuaValue.NIL, t.get( keys[i] ) );

View File

@@ -21,6 +21,7 @@
******************************************************************************/ ******************************************************************************/
package org.luaj.vm2; package org.luaj.vm2;
import java.util.ArrayList;
import java.util.Vector; import java.util.Vector;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -35,6 +36,23 @@ public class TableTest extends TestCase {
return new LuaTable(n,m); return new LuaTable(n,m);
} }
private int keyCount(LuaTable t) {
return keys(t).length;
}
private LuaValue[] keys(LuaTable t) {
ArrayList<LuaValue> l = new ArrayList<LuaValue>();
LuaValue k = LuaValue.NIL;
while ( true ) {
Varargs n = t.next(k);
if ( (k = n.arg1()).isnil() )
break;
l.add( k );
}
return l.toArray(new LuaValue[t.length()]);
}
public void testInOrderIntegerKeyInsertion() { public void testInOrderIntegerKeyInsertion() {
LuaTable t = new_Table(); LuaTable t = new_Table();
@@ -48,10 +66,10 @@ public class TableTest extends TestCase {
} }
// Ensure capacities make sense // Ensure capacities make sense
assertEquals( 0, t.hashCapacity() ); assertEquals( 0, t.getHashLength() );
assertTrue( t.arrayCapacity() >= 32 ); assertTrue( t.getArrayLength() >= 32 );
assertTrue( t.arrayCapacity() <= 64 ); assertTrue( t.getArrayLength() <= 64 );
} }
@@ -70,8 +88,8 @@ public class TableTest extends TestCase {
assertEquals(LuaInteger.valueOf(i), t.get(i)); assertEquals(LuaInteger.valueOf(i), t.get(i));
} }
assertTrue( t.arrayCapacity() >= 0 && t.arrayCapacity() <= 2 ); assertTrue( t.getArrayLength() >= 0 && t.getArrayLength() <= 2 );
assertTrue( t.hashCapacity() >= 4 ); assertTrue( t.getHashLength() >= 4 );
} }
public void testOutOfOrderIntegerKeyInsertion() { public void testOutOfOrderIntegerKeyInsertion() {
@@ -87,11 +105,11 @@ public class TableTest extends TestCase {
} }
// Ensure capacities make sense // Ensure capacities make sense
assertTrue( t.arrayCapacity() >= 0 ); assertTrue( t.getArrayLength() >= 0 );
assertTrue( t.arrayCapacity() <= 6 ); assertTrue( t.getArrayLength() <= 6 );
assertTrue( t.hashCapacity() >= 16 ); assertTrue( t.getHashLength() >= 16 );
assertTrue( t.hashCapacity() <= 64 ); assertTrue( t.getHashLength() <= 64 );
} }
@@ -104,12 +122,12 @@ public class TableTest extends TestCase {
t.set( str, LuaInteger.valueOf( i ) ); t.set( str, LuaInteger.valueOf( i ) );
} }
assertTrue( t.arrayCapacity() >= 9 ); // 1, 2, ..., 9 assertTrue( t.getArrayLength() >= 9 ); // 1, 2, ..., 9
assertTrue( t.arrayCapacity() <= 18 ); assertTrue( t.getArrayLength() <= 18 );
assertTrue( t.hashCapacity() >= 11 ); // 0, "0", "1", ..., "9" assertTrue( t.getHashLength() >= 11 ); // 0, "0", "1", ..., "9"
assertTrue( t.hashCapacity() <= 33 ); assertTrue( t.getHashLength() <= 33 );
LuaValue[] keys = t.keys(); LuaValue[] keys = keys(t);
int intKeys = 0; int intKeys = 0;
int stringKeys = 0; int stringKeys = 0;
@@ -145,7 +163,7 @@ public class TableTest extends TestCase {
t.set( "test", LuaValue.valueOf("foo") ); t.set( "test", LuaValue.valueOf("foo") );
t.set( "explode", LuaValue.valueOf("explode") ); t.set( "explode", LuaValue.valueOf("explode") );
assertEquals( 2, t.keyCount() ); assertEquals( 2, keyCount(t) );
} }
public void testRemove0() { public void testRemove0() {
@@ -173,11 +191,11 @@ public class TableTest extends TestCase {
t.set( 42, LuaValue.NIL ); t.set( 42, LuaValue.NIL );
t.set( new_Table(), LuaValue.NIL ); t.set( new_Table(), LuaValue.NIL );
t.set( "test", LuaValue.NIL ); t.set( "test", LuaValue.NIL );
assertEquals( 0, t.keyCount() ); assertEquals( 0, keyCount(t) );
t.set( 10, LuaInteger.valueOf( 5 ) ); t.set( 10, LuaInteger.valueOf( 5 ) );
t.set( 10, LuaValue.NIL ); t.set( 10, LuaValue.NIL );
assertEquals( 0, t.keyCount() ); assertEquals( 0, keyCount(t) );
} }
public void testRemove2() { public void testRemove2() {
@@ -185,23 +203,23 @@ public class TableTest extends TestCase {
t.set( "test", LuaValue.valueOf("foo") ); t.set( "test", LuaValue.valueOf("foo") );
t.set( "string", LuaInteger.valueOf( 10 ) ); t.set( "string", LuaInteger.valueOf( 10 ) );
assertEquals( 2, t.keyCount() ); assertEquals( 2, keyCount(t) );
t.set( "string", LuaValue.NIL ); t.set( "string", LuaValue.NIL );
t.set( "three", LuaValue.valueOf( 3.14 ) ); t.set( "three", LuaValue.valueOf( 3.14 ) );
assertEquals( 2, t.keyCount() ); assertEquals( 2, keyCount(t) );
t.set( "test", LuaValue.NIL ); t.set( "test", LuaValue.NIL );
assertEquals( 1, t.keyCount() ); assertEquals( 1, keyCount(t) );
t.set( 10, LuaInteger.valueOf( 5 ) ); t.set( 10, LuaInteger.valueOf( 5 ) );
assertEquals( 2, t.keyCount() ); assertEquals( 2, keyCount(t) );
t.set( 10, LuaValue.NIL ); t.set( 10, LuaValue.NIL );
assertEquals( 1, t.keyCount() ); assertEquals( 1, keyCount(t) );
t.set( "three", LuaValue.NIL ); t.set( "three", LuaValue.NIL );
assertEquals( 0, t.keyCount() ); assertEquals( 0, keyCount(t) );
} }
public void testInOrderLuaLength() { public void testInOrderLuaLength() {

View File

@@ -22,64 +22,9 @@
package org.luaj.vm2; package org.luaj.vm2;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Random;
public class WeakTableTest extends TableTest { abstract public class WeakTableTest extends TableTest {
protected LuaTable new_Table() { return new WeakTable(false, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(false, true); }
public static class WeakKeyTableTest extends TableTest {
protected LuaTable new_Table() { return new WeakTable(true, false); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, false); }
}
public static class WeakKeyValueTableTest extends TableTest {
protected LuaTable new_Table() { return new WeakTable(true, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, true); }
}
public void testWeakValuesTable() {
LuaTable t = new_Table();
Object obj = new Object();
LuaTable tableValue = new LuaTable();
LuaString stringValue = LuaString.valueOf("this is a test");
t.set("table", tableValue);
t.set("userdata", LuaValue.userdataOf(obj, null));
t.set("string", stringValue);
t.set("string2", LuaString.valueOf("another string"));
assertTrue("table must have at least 4 elements", t.hashKeys.length > 4);
// check that table can be used to get elements
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// nothing should be collected, since we have strong references here
System.gc();
// check that elements are still there
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// drop our strong references
obj = null;
tableValue = null;
stringValue = null;
// Garbage collection should cause weak entries to be dropped.
System.gc();
// check that they are dropped
assertEquals(LuaValue.NIL, t.get("table"));
assertEquals(LuaValue.NIL, t.get("userdata"));
assertFalse("strings should not be in weak references", t.get("string").isnil());
}
public static class MyData { public static class MyData {
public final int value; public final int value;
public MyData( int value ) { public MyData( int value ) {
@@ -91,98 +36,161 @@ public class WeakTableTest extends TableTest {
public boolean equals( Object o ) { public boolean equals( Object o ) {
return (o instanceof MyData) && ((MyData)o).value == value; return (o instanceof MyData) && ((MyData)o).value == value;
} }
public String toSting() { public String toString() {
return "mydata-"+value; return "mydata-"+value;
} }
} }
public void testWeakKeysTable() {
LuaTable t = new WeakTable(true, false);
LuaValue key = LuaValue.userdataOf(new MyData(111));
LuaValue val = LuaValue.userdataOf(new MyData(222));
// set up the table
t.set( key, val );
assertEquals( val, t.get(key) );
System.gc();
assertEquals( val, t.get(key) );
// drop key and value references, replace them with new ones static void collectGarbage() {
WeakReference origkey = new WeakReference(key); Runtime rt = Runtime.getRuntime();
WeakReference origval = new WeakReference(val); rt.gc();
key = LuaValue.userdataOf(new MyData(111)); try {
val = LuaValue.userdataOf(new MyData(222)); Thread.sleep(20);
rt.gc();
// new key and value should be interchangeable (feature of this test class Thread.sleep(20);
assertEquals( key, origkey.get() ); } catch ( Exception e ) {
assertEquals( val, origval.get() ); e.printStackTrace();
assertEquals( val, t.get(key) ); }
assertEquals( val, t.get((LuaValue) origkey.get()) ); rt.gc();
assertEquals( origval.get(), t.get(key) );
// value should not be reachable after gc
System.gc();
assertEquals( null, origkey.get() );
assertEquals( LuaValue.NIL, t.get(key) );
// value should also be gone after gc after access!
System.gc();
assertEquals( null, origkey.get() );
assertEquals( null, origval.get() );
} }
public void testWeakKeysValuesTable() { public static class WeakValueTableTest extends WeakTableTest {
LuaTable t = new WeakTable(true, true); protected LuaTable new_Table() { return new WeakTable(false, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(false, true); }
public void testWeakValuesTable() {
LuaTable t = new_Table();
Object obj = new Object();
LuaTable tableValue = new LuaTable();
LuaString stringValue = LuaString.valueOf("this is a test");
t.set("table", tableValue);
t.set("userdata", LuaValue.userdataOf(obj, null));
t.set("string", stringValue);
t.set("string2", LuaString.valueOf("another string"));
assertTrue("table must have at least 4 elements", t.getHashLength() > 4);
// check that table can be used to get elements
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// nothing should be collected, since we have strong references here
collectGarbage();
// check that elements are still there
assertEquals(tableValue, t.get("table"));
assertEquals(stringValue, t.get("string"));
assertEquals(obj, t.get("userdata").checkuserdata());
// drop our strong references
obj = null;
tableValue = null;
stringValue = null;
// Garbage collection should cause weak entries to be dropped.
collectGarbage();
// check that they are dropped
assertEquals(LuaValue.NIL, t.get("table"));
assertEquals(LuaValue.NIL, t.get("userdata"));
assertFalse("strings should not be in weak references", t.get("string").isnil());
}
}
public static class WeakKeyTableTest extends WeakTableTest {
protected LuaTable new_Table() { return new WeakTable(true, false); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, false); }
LuaValue key = LuaValue.userdataOf(new MyData(111)); public void testWeakKeysTable() {
LuaValue val = LuaValue.userdataOf(new MyData(222)); LuaTable t = new WeakTable(true, false);
LuaValue key2 = LuaValue.userdataOf(new MyData(333));
LuaValue val2 = LuaValue.userdataOf(new MyData(444)); LuaValue key = LuaValue.userdataOf(new MyData(111));
LuaValue key3 = LuaValue.userdataOf(new MyData(555)); LuaValue val = LuaValue.userdataOf(new MyData(222));
LuaValue val3 = LuaValue.userdataOf(new MyData(666));
// set up the table
t.set( key, val );
assertEquals( val, t.get(key) );
System.gc();
assertEquals( val, t.get(key) );
// drop key and value references, replace them with new ones
WeakReference origkey = new WeakReference(key);
WeakReference origval = new WeakReference(val);
key = LuaValue.userdataOf(new MyData(111));
val = LuaValue.userdataOf(new MyData(222));
// new key and value should be interchangeable (feature of this test class
assertEquals( key, origkey.get() );
assertEquals( val, origval.get() );
assertEquals( val, t.get(key) );
assertEquals( val, t.get((LuaValue) origkey.get()) );
assertEquals( origval.get(), t.get(key) );
// value should not be reachable after gc
collectGarbage();
assertEquals( null, origkey.get() );
assertEquals( null, origval.get() );
assertEquals( LuaValue.NIL, t.get(key) );
}
}
public static class WeakKeyValueTableTest extends WeakTableTest {
protected LuaTable new_Table() { return new WeakTable(true, true); }
protected LuaTable new_Table(int n,int m) { return new WeakTable(true, true); }
// set up the table public void testWeakKeysValuesTable() {
t.set( key, val ); LuaTable t = new WeakTable(true, true);
t.set( key2, val2 );
t.set( key3, val3 ); LuaValue key = LuaValue.userdataOf(new MyData(111));
assertEquals( val, t.get(key) ); LuaValue val = LuaValue.userdataOf(new MyData(222));
assertEquals( val2, t.get(key2) ); LuaValue key2 = LuaValue.userdataOf(new MyData(333));
assertEquals( val3, t.get(key3) ); LuaValue val2 = LuaValue.userdataOf(new MyData(444));
System.gc(); LuaValue key3 = LuaValue.userdataOf(new MyData(555));
assertEquals( val, t.get(key) ); LuaValue val3 = LuaValue.userdataOf(new MyData(666));
assertEquals( val2, t.get(key2) );
assertEquals( val3, t.get(key3) ); // set up the table
t.set( key, val );
t.set( key2, val2 );
t.set( key3, val3 );
assertEquals( val, t.get(key) );
assertEquals( val2, t.get(key2) );
assertEquals( val3, t.get(key3) );
System.gc();
assertEquals( val, t.get(key) );
assertEquals( val2, t.get(key2) );
assertEquals( val3, t.get(key3) );
// drop key and value references, replace them with new ones // drop key and value references, replace them with new ones
WeakReference origkey = new WeakReference(key); WeakReference origkey = new WeakReference(key);
WeakReference origval = new WeakReference(val); WeakReference origval = new WeakReference(val);
WeakReference origkey2 = new WeakReference(key2); WeakReference origkey2 = new WeakReference(key2);
WeakReference origval2 = new WeakReference(val2); WeakReference origval2 = new WeakReference(val2);
WeakReference origkey3 = new WeakReference(key3); WeakReference origkey3 = new WeakReference(key3);
WeakReference origval3 = new WeakReference(val3); WeakReference origval3 = new WeakReference(val3);
key = LuaValue.userdataOf(new MyData(111)); key = LuaValue.userdataOf(new MyData(111));
val = LuaValue.userdataOf(new MyData(222)); val = LuaValue.userdataOf(new MyData(222));
key2 = LuaValue.userdataOf(new MyData(333)); key2 = LuaValue.userdataOf(new MyData(333));
// don't drop val2, or key3 // don't drop val2, or key3
val3 = LuaValue.userdataOf(new MyData(666)); val3 = LuaValue.userdataOf(new MyData(666));
// no values should be reachable after gc // no values should be reachable after gc
System.gc(); collectGarbage();
assertEquals( null, origkey.get() ); assertEquals( null, origkey.get() );
assertEquals( null, origval.get() ); assertEquals( null, origval.get() );
assertEquals( null, origkey2.get() ); assertEquals( null, origkey2.get() );
assertEquals( null, origval3.get() ); assertEquals( null, origval3.get() );
assertEquals( LuaValue.NIL, t.get(key) ); assertEquals( LuaValue.NIL, t.get(key) );
assertEquals( LuaValue.NIL, t.get(key2) ); assertEquals( LuaValue.NIL, t.get(key2) );
assertEquals( LuaValue.NIL, t.get(key3) ); assertEquals( LuaValue.NIL, t.get(key3) );
// all originals should be gone after gc, then access // all originals should be gone after gc, then access
val2 = null; val2 = null;
key3 = null; key3 = null;
System.gc(); collectGarbage();
assertEquals( null, origval2.get() ); assertEquals( null, origval2.get() );
assertEquals( null, origkey3.get() ); assertEquals( null, origkey3.get() );
} }
}
} }