Additional support for arrays in luajava library.

This commit is contained in:
James Roseborough
2009-10-22 06:08:33 +00:00
parent 66873ff268
commit 9d22ef7855
4 changed files with 363 additions and 25 deletions

View File

@@ -21,17 +21,19 @@
******************************************************************************/
package org.luaj.lib.j2se;
import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Map;
import org.luaj.vm.LBoolean;
import org.luaj.vm.LDouble;
import org.luaj.vm.LInteger;
import org.luaj.vm.LNil;
import org.luaj.vm.LNumber;
import org.luaj.vm.LString;
import org.luaj.vm.LTable;
import org.luaj.vm.LUserData;
import org.luaj.vm.LValue;
import org.luaj.vm.LuaErrorException;
public class CoerceLuaToJava {
@@ -197,13 +199,55 @@ public class CoerceLuaToJava {
COERCIONS.put( Object.class, objectCoercion );
}
static Object coerceArg(LValue v, Class type) {
Coercion co = (Coercion) COERCIONS.get( type );
if ( co != null )
return co.coerce( v );
if ( v instanceof LUserData )
return ((LUserData) v).m_instance;
return v;
/** Score a single parameter, including array handling */
private static int scoreParam(LValue a, Class c) {
if ( a instanceof LUserData ) {
Object o = ((LUserData) a).m_instance;
if ( c.isAssignableFrom(o.getClass()) )
return 0;
}
Coercion co = (Coercion) COERCIONS.get( c );
if ( co != null ) {
return co.score( a );
}
if ( c.isArray() ) {
Class typ = c.getComponentType();
if ( a instanceof LTable ) {
return scoreParam( ((LTable)a).get(1), typ );
} else {
return 0x10 + (scoreParam(a, typ) << 8);
}
}
return 0x1000;
}
/** Do a conversion */
public static Object coerceArg(LValue a, Class c) {
if ( a instanceof LUserData ) {
Object o = ((LUserData) a).m_instance;
if ( c.isAssignableFrom(o.getClass()) )
return o;
}
Coercion co = (Coercion) COERCIONS.get( c );
if ( co != null ) {
return co.coerce( a );
}
if ( c.isArray() ) {
boolean istable = (a instanceof LTable);
int n = istable? a.luaLength(): 1;
Class typ = c.getComponentType();
Object o = Array.newInstance(typ, n);
for ( int i=0; i<n; i++ ) {
LValue ele = (istable? ((LTable)a).get(i+1): a);
if ( ele != null )
Array.set(o, i, coerceArg(ele, typ));
}
return o;
}
if ( a.isNil() )
return null;
throw new LuaErrorException("no coercion found for "+a.getClass()+" to "+c);
}
static Object[] coerceArgs(LValue[] suppliedArgs, Class[] parameterTypes) {
@@ -223,23 +267,15 @@ public class CoerceLuaToJava {
* 3) java has less args
* 4) types coerce well
*/
static int scoreParamTypes(LValue[] suppliedArgs, Class[] paramTypes) {
public static int scoreParamTypes(LValue[] suppliedArgs, Class[] paramTypes) {
int nargs = suppliedArgs.length;
int njava = paramTypes.length;
int score = (njava == nargs? 0: njava > nargs? 0x4000: 0x8000);
for ( int i=0; i<nargs && i<njava; i++ ) {
LValue a = suppliedArgs[i];
Class c = paramTypes[i];
Coercion co = (Coercion) COERCIONS.get( c );
if ( co != null ) {
score += co.score( a );
} else if ( a instanceof LUserData ) {
Object o = ((LUserData) a).m_instance;
if ( ! c.isAssignableFrom(o.getClass()) )
score += 0x10000;
} else {
score += 0x100;
}
int s = scoreParam( a, c );
score += s;
}
return score;
}

View File

@@ -26,6 +26,7 @@ package org.luaj.lib.j2se;
*
* TODO: coerce types on way in and out, pick method base on arg count ant types.
*/
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
@@ -38,7 +39,7 @@ import java.util.List;
import java.util.Map;
import org.luaj.vm.LFunction;
import org.luaj.vm.LNil;
import org.luaj.vm.LInteger;
import org.luaj.vm.LString;
import org.luaj.vm.LTable;
import org.luaj.vm.LUserData;
@@ -194,6 +195,7 @@ public final class LuajavaLib extends LFunction {
public final LValue[] values;
public final Class[] classes;
public int hash;
public boolean cantcache;
ParamsList( LuaState vm ) {
int n = Math.max(vm.gettop()-2,0);
values = new LValue[n];
@@ -202,6 +204,9 @@ public final class LuajavaLib extends LFunction {
values[i] = vm.topointer(i-n);
classes[i] = values[i].getClass();
hash += classes[i].hashCode();
Class c = classes[i];
if ( values[i] instanceof LUserData || values[i] instanceof LTable )
cantcache = true;
}
}
public int hashCode() {
@@ -214,7 +219,9 @@ public final class LuajavaLib extends LFunction {
}
}
static LUserData toUserdata(Object instance, final Class clazz) {
private static LString LENGTH = LString.valueOf("length");
static LUserData toUserdata(final Object instance, final Class clazz) {
LTable mt = (LTable) classMetatables.get(clazz);
if ( mt == null ) {
mt = new LTable();
@@ -222,6 +229,17 @@ public final class LuajavaLib extends LFunction {
public boolean luaStackCall(LuaState vm) {
LValue table = vm.topointer(2);
LValue key = vm.topointer(3);
if ( key instanceof LInteger ) {
if ( clazz.isArray() ) {
vm.resettop();
int index = key.toJavaInt()-1;
if ( index >= 0 && index < Array.getLength(instance) )
vm.pushlvalue( CoerceJavaToLua.coerce( Array.get(instance, index) ) );
else
vm.pushnil();
return false;
}
}
final String s = key.toJavaString();
vm.resettop();
try {
@@ -229,7 +247,11 @@ public final class LuajavaLib extends LFunction {
Object o = f.get(table.toJavaInstance());
vm.pushlvalue( CoerceJavaToLua.coerce( o ) );
} catch (NoSuchFieldException nsfe) {
if ( clazz.isArray() && key.equals(LENGTH) ) {
vm.pushinteger( Array.getLength(instance) );
} else {
vm.pushlvalue( new LMethod(clazz,s) );
}
} catch (Exception e) {
throw new LuaErrorException(e);
}
@@ -241,6 +263,19 @@ public final class LuajavaLib extends LFunction {
LValue table = vm.topointer(2);
LValue key = vm.topointer(3);
LValue val = vm.topointer(4);
if ( key instanceof LInteger ) {
if ( clazz.isArray() ) {
vm.resettop();
Object v = CoerceLuaToJava.coerceArg(val, clazz.getComponentType());
int index = key.toJavaInt()-1;
if ( index >= 0 && index < Array.getLength(instance) )
Array.set(instance, key.toJavaInt()-1, v);
else
throw new LuaErrorException("array bounds exceeded "+index);
vm.resettop();
return false;
}
}
String s = key.toJavaString();
try {
Field f = clazz.getField(s);
@@ -342,6 +377,7 @@ public final class LuajavaLib extends LFunction {
// put into cache
c = (Constructor) list.get(besti);
if ( ! params.cantcache )
cache.put( params, c );
return c;
}
@@ -396,6 +432,15 @@ public final class LuajavaLib extends LFunction {
if ( list == null )
throw new IllegalArgumentException("no method named '"+methodName+"' with "+n+" args");
// trivial lists match
if ( list.size() == 1 ) {
m = (Method) list.get(0);
if ( ! params.cantcache )
cache.put( params, m );
return m;
}
// find constructor with best score
int bests = Integer.MAX_VALUE;
int besti = 0;
@@ -410,6 +455,7 @@ public final class LuajavaLib extends LFunction {
// put into cache
m = (Method) list.get(besti);
if ( ! params.cantcache )
cache.put( params, m );
return m;
}

View File

@@ -0,0 +1,69 @@
package org.luaj.sample;
import org.luaj.lib.j2se.CoerceJavaToLua;
import org.luaj.platform.*;
import org.luaj.vm.*;
/**
* Program that illustrates how userdata is mapped into lua using
* LuaJava's automated coercion
*/
public class SampleUserdataMain {
public static class MyData {
public int x = 7;
public String y = "seven";
public int xx[] = new int[] { 11, 22, 33, };
public String yy[] = new String[] { "aa", "bb" };
public int xxx[][] = new int[][] { {444, 555}, {666, 777} } ;
public String yyy[][] = new String[][] { { "ccc", "ddd" }, { "eee", "fff" } };
public void initScalars( int newx, String newy ) {
x = newx;
y = newy;
}
public void initArrays( int[] newxx, String[] newyy ) {
xx = newxx;
yy = newyy;
}
public void initMatrices( int[][] newxxx, String[][] newyyy ) {
xxx = newxxx;
yyy = newyyy;
}
public int getx() { return x; }
public String gety() { return y; }
public int[] getxx() { return xx; }
public String[] getyy() { return yy; }
public int[][] getxxx() { return xxx; }
public String[][] getyyy() { return yyy; }
}
public static void main(String[] args) {
Platform.setInstance( new J2sePlatform() );
LuaState vm = Platform.newLuaState();
org.luaj.compiler.LuaC.install();
// test script
vm.getglobal( "loadstring" );
vm.pushstring( "local mydata = ...\n" +
"print( 'mydata', mydata )\n" +
"print( 'mydata.x, mydata.y', mydata.x, mydata.y )\n" +
"print( 'mydata:getx()', mydata:getx() )\n" +
"print( 'mydata:getxx()', mydata:getxx()[1], mydata:getxx()[2] )\n" +
"print( 'mydata:getxxx()', mydata:getxxx()[1][1], mydata:getxxx()[1][2] )\n" +
"print( 'mydata:getyyy()', mydata:getyyy()[1][1], mydata:getyyy()[1][2] )\n" +
"mydata:initScalars(3,'pqr')\n" +
"mydata:initArrays({55,66},{'abc','def'})\n" +
"mydata:initMatrices({{44,55},{66}},{{'qq','rr'},{'ss','tt'}})\n" +
"print( 'mydata:getx()', mydata:getx() )\n" +
"print( 'mydata:getxx()', mydata:getxx()[1], mydata:getxx()[2] )\n" +
"print( 'mydata:getxxx()', mydata:getxxx()[1][1], mydata:getxxx()[1][2] )\n" +
"print( 'mydata:getyyy()', mydata:getyyy()[1][1], mydata:getyyy()[1][2] )\n" +
"");
vm.call( 1, 2 );
System.out.println("load result: "+vm.tostring(-2)+", "+vm.tostring(-1));
vm.settop(1);
// load argument to test script
vm.pushlvalue( CoerceJavaToLua.coerce(new MyData()) );
vm.call( 1, 0 );
}
}

View File

@@ -0,0 +1,187 @@
package org.luaj.vm;
import junit.framework.TestCase;
import org.luaj.lib.j2se.CoerceJavaToLua;
import org.luaj.lib.j2se.CoerceLuaToJava;
import org.luaj.platform.J2sePlatform;
public class LuaJavaCoercionTest extends TestCase {
private LuaState vm;
private static LInteger ZERO = LInteger.valueOf(0);
private static LInteger ONE = LInteger.valueOf(1);
private static LInteger TWO = LInteger.valueOf(2);
private static LInteger THREE = LInteger.valueOf(3);
private static LString LENGTH = LString.valueOf("length");
protected void setUp() throws Exception {
super.setUp();
Platform.setInstance( new J2sePlatform() );
org.luaj.compiler.LuaC.install();
vm = Platform.newLuaState();
}
public void testJavaIntToLuaInt() {
Integer i = Integer.valueOf(777);
LValue v = CoerceJavaToLua.coerce(i);
assertEquals( LInteger.class, v.getClass() );
assertEquals( 777, v.toJavaInt() );
}
public void testLuaIntToJavaInt() {
LInteger i = LInteger.valueOf(777);
Object o = CoerceLuaToJava.coerceArg(i, int.class);
assertEquals( Integer.class, o.getClass() );
assertEquals( 777, ((Number)o).intValue() );
o = CoerceLuaToJava.coerceArg(i, Integer.class);
assertEquals( Integer.class, o.getClass() );
assertEquals( new Integer(777), o );
}
public void testJavaStringToLuaString() {
String s = new String("777");
LValue v = CoerceJavaToLua.coerce(s);
assertEquals( LString.class, v.getClass() );
assertEquals( "777", v.toJavaString() );
}
public void testLuaStringToJavaString() {
LString s = new LString("777");
Object o = CoerceLuaToJava.coerceArg(s, String.class);
assertEquals( String.class, o.getClass() );
assertEquals( "777", o );
}
public void testJavaIntArrayToLuaTable() {
int[] i = { 222, 333 };
LValue v = CoerceJavaToLua.coerce(i);
assertEquals( LUserData.class, v.getClass() );
assertNotNull( v.luaGetMetatable() );
assertEquals( LInteger.valueOf(222), v.luaGetTable(vm, ONE) );
assertEquals( LInteger.valueOf(333), v.luaGetTable(vm, TWO) );
assertEquals( TWO, v.luaGetTable(vm, LENGTH));
assertEquals( LNil.NIL, v.luaGetTable(vm, THREE) );
assertEquals( LNil.NIL, v.luaGetTable(vm, ZERO) );
v.luaSetTable(vm, ONE, LInteger.valueOf(444));
v.luaSetTable(vm, TWO, LInteger.valueOf(555));
assertEquals( 444, i[0] );
assertEquals( 555, i[1] );
assertEquals( LInteger.valueOf(444), v.luaGetTable(vm, ONE) );
assertEquals( LInteger.valueOf(555), v.luaGetTable(vm, TWO) );
try {
v.luaSetTable(vm, ZERO, LInteger.valueOf(777));
fail( "array bound exception not thrown" );
} catch ( LuaErrorException lee ) {
// expected
}
try {
v.luaSetTable(vm, THREE, LInteger.valueOf(777));
fail( "array bound exception not thrown" );
} catch ( LuaErrorException lee ) {
// expected
}
}
public void testLuaTableToJavaIntArray() {
LTable t = new LTable();
t.put(1, LInteger.valueOf(222) );
t.put(2, LInteger.valueOf(333) );
int[] i = null;
Object o = CoerceLuaToJava.coerceArg(t, int[].class);
assertEquals( int[].class, o.getClass() );
i = (int[]) o;
assertEquals( 2, i.length );
assertEquals( 222, i[0] );
assertEquals( 333, i[1] );
}
public void testArrayParamScoring() {
int a = 5;
int[] b = { 44, 66 };
int[][] c = { { 11, 22 }, { 33, 44 } };
LValue la = LInteger.valueOf(a);
LTable tb = new LTable();
LTable tc = new LTable();
LValue va = CoerceJavaToLua.coerce(a);
LValue vb = CoerceJavaToLua.coerce(b);
LValue vc = CoerceJavaToLua.coerce(c);
tc.put( ONE, new LTable() );
int saa = CoerceLuaToJava.scoreParamTypes( new LValue[] { la }, new Class[] { int.class } );
int sab = CoerceLuaToJava.scoreParamTypes( new LValue[] { la }, new Class[] { int[].class } );
int sac = CoerceLuaToJava.scoreParamTypes( new LValue[] { la }, new Class[] { int[][].class } );
assertTrue( saa < sab );
assertTrue( saa < sac );
int sba = CoerceLuaToJava.scoreParamTypes( new LValue[] { tb }, new Class[] { int.class } );
int sbb = CoerceLuaToJava.scoreParamTypes( new LValue[] { tb }, new Class[] { int[].class } );
int sbc = CoerceLuaToJava.scoreParamTypes( new LValue[] { tb }, new Class[] { int[][].class } );
assertTrue( sbb < sba );
assertTrue( sbb < sbc );
int sca = CoerceLuaToJava.scoreParamTypes( new LValue[] { tc }, new Class[] { int.class } );
int scb = CoerceLuaToJava.scoreParamTypes( new LValue[] { tc }, new Class[] { int[].class } );
int scc = CoerceLuaToJava.scoreParamTypes( new LValue[] { tc }, new Class[] { int[][].class } );
assertTrue( scc < sca );
assertTrue( scc < scb );
int vaa = CoerceLuaToJava.scoreParamTypes( new LValue[] { va }, new Class[] { int.class } );
int vab = CoerceLuaToJava.scoreParamTypes( new LValue[] { va }, new Class[] { int[].class } );
int vac = CoerceLuaToJava.scoreParamTypes( new LValue[] { va }, new Class[] { int[][].class } );
assertTrue( vaa < vab );
assertTrue( vaa < vac );
int vba = CoerceLuaToJava.scoreParamTypes( new LValue[] { vb }, new Class[] { int.class } );
int vbb = CoerceLuaToJava.scoreParamTypes( new LValue[] { vb }, new Class[] { int[].class } );
int vbc = CoerceLuaToJava.scoreParamTypes( new LValue[] { vb }, new Class[] { int[][].class } );
assertTrue( vbb < vba );
assertTrue( vbb < vbc );
int vca = CoerceLuaToJava.scoreParamTypes( new LValue[] { vc }, new Class[] { int.class } );
int vcb = CoerceLuaToJava.scoreParamTypes( new LValue[] { vc }, new Class[] { int[].class } );
int vcc = CoerceLuaToJava.scoreParamTypes( new LValue[] { vc }, new Class[] { int[][].class } );
assertTrue( vcc < vca );
assertTrue( vcc < vcb );
}
public static class SampleClass {
public String sample() { return "void-args"; }
public String sample(int a) { return "int-args "+a; }
public String sample(int[] a) { return "int-array-args "+a[0]+","+a[1]; }
public String sample(int[][] a) { return "int-array-array-args "+a[0][0]+","+a[0][1]+","+a[1][0]+","+a[1][1]; }
}
private static final LString SAMPLE = LString.valueOf("sample");
public void testIntArrayParameterMatching() {
LValue v = CoerceJavaToLua.coerce(new SampleClass());
// get sample field, call with no arguments
LValue method = v.luaGetTable(vm, SAMPLE);
vm.pushlvalue(method);
vm.pushlvalue(v);
vm.call(1,1);
assertEquals( "void-args", vm.tostring(-1) );
// get sample field, call with no arguments
vm.pushlvalue(method);
vm.pushlvalue(v);
vm.pushlvalue( CoerceJavaToLua.coerce(new Integer(123)));
vm.call(2,1);
assertEquals( "int-args 123", vm.tostring(-1) );
// get sample field, call with no arguments
vm.pushlvalue(method);
vm.pushlvalue(v);
vm.pushlvalue( CoerceJavaToLua.coerce(new int[]{345,678}) );
vm.call(2,1);
assertEquals( "int-array-args 345,678", vm.tostring(-1) );
// get sample field, call with no arguments
vm.pushlvalue(method);
vm.pushlvalue(v);
vm.pushlvalue( CoerceJavaToLua.coerce(new int[][]{{22,33},{44,55}}) );
vm.call(2,1);
assertEquals( "int-array-array-args 22,33,44,55", vm.tostring(-1) );
}
}