From 9d22ef78559064e491ff07bd898ce8ed659db808 Mon Sep 17 00:00:00 2001 From: James Roseborough Date: Thu, 22 Oct 2009 06:08:33 +0000 Subject: [PATCH] Additional support for arrays in luajava library. --- .../org/luaj/lib/j2se/CoerceLuaToJava.java | 76 +++++-- src/j2se/org/luaj/lib/j2se/LuajavaLib.java | 56 +++++- .../org/luaj/sample/SampleUserdataMain.java | 69 +++++++ .../java/org/luaj/vm/LuaJavaCoercionTest.java | 187 ++++++++++++++++++ 4 files changed, 363 insertions(+), 25 deletions(-) create mode 100644 src/sample/org/luaj/sample/SampleUserdataMain.java create mode 100644 src/test/java/org/luaj/vm/LuaJavaCoercionTest.java diff --git a/src/j2se/org/luaj/lib/j2se/CoerceLuaToJava.java b/src/j2se/org/luaj/lib/j2se/CoerceLuaToJava.java index 1610dddd..9cae61d3 100644 --- a/src/j2se/org/luaj/lib/j2se/CoerceLuaToJava.java +++ b/src/j2se/org/luaj/lib/j2se/CoerceLuaToJava.java @@ -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 { @@ -196,14 +198,56 @@ public class CoerceLuaToJava { COERCIONS.put( String.class, stringCoercion ); 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 nargs? 0x4000: 0x8000); for ( int i=0; i= 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) { - vm.pushlvalue( new LMethod(clazz,s) ); + 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,7 +377,8 @@ public final class LuajavaLib extends LFunction { // put into cache c = (Constructor) list.get(besti); - cache.put( params, c ); + 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,7 +455,8 @@ public final class LuajavaLib extends LFunction { // put into cache m = (Method) list.get(besti); - cache.put( params, m ); + if ( ! params.cantcache ) + cache.put( params, m ); return m; } diff --git a/src/sample/org/luaj/sample/SampleUserdataMain.java b/src/sample/org/luaj/sample/SampleUserdataMain.java new file mode 100644 index 00000000..992b9b11 --- /dev/null +++ b/src/sample/org/luaj/sample/SampleUserdataMain.java @@ -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 ); + } +} diff --git a/src/test/java/org/luaj/vm/LuaJavaCoercionTest.java b/src/test/java/org/luaj/vm/LuaJavaCoercionTest.java new file mode 100644 index 00000000..371d0149 --- /dev/null +++ b/src/test/java/org/luaj/vm/LuaJavaCoercionTest.java @@ -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) ); + } + + +}