diff --git a/src/core/org/luaj/vm2/LuaError.java b/src/core/org/luaj/vm2/LuaError.java index cf1bad9b..049fbafc 100644 --- a/src/core/org/luaj/vm2/LuaError.java +++ b/src/core/org/luaj/vm2/LuaError.java @@ -49,6 +49,8 @@ public class LuaError extends RuntimeException { return msg; } + private Throwable cause; + /** * Construct a LuaErrorException in response to a Throwable that was caught * indicating a problem with the VM rather than the lua code. @@ -57,6 +59,7 @@ public class LuaError extends RuntimeException { */ public LuaError(Throwable cause) { this( errorHook( "vm error: "+cause ) ); + this.cause = cause; } /** @@ -94,5 +97,12 @@ public class LuaError extends RuntimeException { } } + /** + * Get the cause, if any. + */ + public Throwable getCause() { + return cause; + } + } diff --git a/src/jse/org/luaj/vm2/lib/jse/CoerceLuaToJava.java b/src/jse/org/luaj/vm2/lib/jse/CoerceLuaToJava.java index ecf34b24..5955638d 100644 --- a/src/jse/org/luaj/vm2/lib/jse/CoerceLuaToJava.java +++ b/src/jse/org/luaj/vm2/lib/jse/CoerceLuaToJava.java @@ -21,9 +21,11 @@ ******************************************************************************/ package org.luaj.vm2.lib.jse; +import java.lang.reflect.Array; import java.util.HashMap; import java.util.Map; +import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaValue; @@ -213,14 +215,50 @@ public class CoerceLuaToJava { COERCIONS.put( Object.class, objectCoercion ); } - static Object coerceArg(LuaValue v, Class type) { - Coercion co = (Coercion) COERCIONS.get( type ); - if ( co != null ) - return co.coerce( v ); - Object o = v.optuserdata(type, null); - if ( o != null ) - return o; - return v; + + /** Score a single parameter, including array handling */ + private static int scoreParam(LuaValue a, Class c) { + if ( a.isuserdata(c) ) + return 0; + Coercion co = (Coercion) COERCIONS.get( c ); + if ( co != null ) { + return co.score( a ); + } + if ( c.isArray() ) { + Class typ = c.getComponentType(); + switch ( a.type() ) { + case LuaValue.TTABLE: + return scoreParam( a.checktable().get(1), typ ); + default: + return 0x10 + (scoreParam(a, typ) << 8); + } + } + return 0x1000; + } + + /** Do a conversion */ + static Object coerceArg(LuaValue a, Class c) { + if ( a.isuserdata(c) ) + return a.touserdata(c); + Coercion co = (Coercion) COERCIONS.get( c ); + if ( co != null ) { + return co.coerce( a ); + } + if ( c.isArray() ) { + boolean istable = a.istable(); + int n = istable? a.length(): 1; + Class typ = c.getComponentType(); + Object arr = Array.newInstance(typ, n); + for ( int i=0; i= 0 && index < Array.getLength(instance) ) + return CoerceJavaToLua.coerce( Array.get(instance, index) ); + return NIL; + } + } final String s = key.toString(); try { Field f = clazz.getField(s); - Object o = f.get(table.checkuserdata(Object.class)); + Object o = f.get(instance); return CoerceJavaToLua.coerce( o ); } catch (NoSuchFieldException nsfe) { + if ( clazz.isArray() && key.equals(LENGTH) ) + return LuaValue.valueOf( Array.getLength(instance) ); return new LMethod(clazz,s); } catch (Exception e) { throw new LuaError(e); @@ -202,6 +232,18 @@ public class LuajavaLib extends OneArgFunction { }); mt.set( LuaValue.NEWINDEX, new ThreeArgFunction() { public LuaValue call(LuaValue table, LuaValue key, LuaValue val) { + Object instance = table.touserdata(); + if ( key.isinttype() ) { + if ( clazz.isArray() ) { + Object v = CoerceLuaToJava.coerceArg(val, clazz.getComponentType()); + int index = key.toint() - 1; + if ( index >= 0 && index < Array.getLength(instance) ) + Array.set(instance, index, v); + else + throw new LuaError("array bounds exceeded "+index); + return NIL; + } + } String s = key.toString(); try { Field f = clazz.getField(s); @@ -241,6 +283,8 @@ public class LuajavaLib extends OneArgFunction { // coerce the result return CoerceJavaToLua.coerce(result); + } catch (InvocationTargetException ite) { + throw new LuaError(ite.getTargetException()); } catch (Exception e) { throw new LuaError(e); } diff --git a/test/junit/org/luaj/vm2/AllTests.java b/test/junit/org/luaj/vm2/AllTests.java index 418561c2..f76f4283 100644 --- a/test/junit/org/luaj/vm2/AllTests.java +++ b/test/junit/org/luaj/vm2/AllTests.java @@ -26,13 +26,14 @@ import junit.framework.TestSuite; import org.luaj.vm2.WeakTableTest.WeakKeyTableTest; import org.luaj.vm2.WeakTableTest.WeakKeyValueTableTest; +import org.luaj.vm2.lib.jse.LuaJavaCoercionTest; public class AllTests { public static Test suite() { TestSuite suite = new TestSuite("All Tests for Luaj-vm2"); - // table tests + // vm tests TestSuite vm = new TestSuite("VM Tests"); vm.addTestSuite(TypeTest.class); vm.addTestSuite(UnaryBinaryOperatorsTest.class); @@ -50,6 +51,11 @@ public class AllTests { table.addTestSuite(WeakKeyValueTableTest.class); suite.addTest(table); + // library tests + TestSuite lib = new TestSuite("Library Tests"); + lib.addTestSuite(LuaJavaCoercionTest.class); + suite.addTest(lib); + // compatiblity tests suite.addTest(CompatibiltyTest.suite()); diff --git a/test/junit/org/luaj/vm2/lib/jse/LuaJavaCoercionTest.java b/test/junit/org/luaj/vm2/lib/jse/LuaJavaCoercionTest.java new file mode 100644 index 00000000..38cab053 --- /dev/null +++ b/test/junit/org/luaj/vm2/lib/jse/LuaJavaCoercionTest.java @@ -0,0 +1,254 @@ +package org.luaj.vm2.lib.jse; + +import junit.framework.TestCase; + +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaInteger; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaUserdata; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.compiler.LuaC; +import org.luaj.vm2.lib.JsePlatform; + +public class LuaJavaCoercionTest extends TestCase { + + private static LuaValue _G; + private static LuaValue ZERO = LuaValue.ZERO; + private static LuaValue ONE = LuaValue.ONE; + private static LuaValue TWO = LuaValue.valueOf(2); + private static LuaValue THREE = LuaValue.valueOf(3); + private static LuaString LENGTH = LuaString.valueOf("length"); + + protected void setUp() throws Exception { + super.setUp(); + _G = JsePlatform.standardGlobals(); + LuaC.install(); + } + + public void testJavaIntToLuaInt() { + Integer i = Integer.valueOf(777); + LuaValue v = CoerceJavaToLua.coerce(i); + assertEquals( LuaInteger.class, v.getClass() ); + assertEquals( 777, v.toint() ); + } + + public void testLuaIntToJavaInt() { + LuaInteger i = LuaInteger.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"); + LuaValue v = CoerceJavaToLua.coerce(s); + assertEquals( LuaString.class, v.getClass() ); + assertEquals( "777", v.toString() ); + } + + public void testLuaStringToJavaString() { + LuaString s = LuaValue.valueOf("777"); + Object o = CoerceLuaToJava.coerceArg(s, String.class); + assertEquals( String.class, o.getClass() ); + assertEquals( "777", o ); + } + + public void testJavaIntArrayToLuaTable() { + int[] i = { 222, 333 }; + LuaValue v = CoerceJavaToLua.coerce(i); + assertEquals( LuaUserdata.class, v.getClass() ); + assertNotNull( v.getmetatable() ); + assertEquals( LuaInteger.valueOf(222), v.get(ONE) ); + assertEquals( LuaInteger.valueOf(333), v.get(TWO) ); + assertEquals( TWO, v.get(LENGTH)); + assertEquals( LuaValue.NIL, v.get(THREE) ); + assertEquals( LuaValue.NIL, v.get(ZERO) ); + v.set(ONE, LuaInteger.valueOf(444)); + v.set(TWO, LuaInteger.valueOf(555)); + assertEquals( 444, i[0] ); + assertEquals( 555, i[1] ); + assertEquals( LuaInteger.valueOf(444), v.get(ONE) ); + assertEquals( LuaInteger.valueOf(555), v.get(TWO) ); + try { + v.set(ZERO, LuaInteger.valueOf(777)); + fail( "array bound exception not thrown" ); + } catch ( LuaError lee ) { + // expected + } + try { + v.set(THREE, LuaInteger.valueOf(777)); + fail( "array bound exception not thrown" ); + } catch ( LuaError lee ) { + // expected + } + } + + public void testLuaTableToJavaIntArray() { + LuaTable t = new LuaTable(); + t.set(1, LuaInteger.valueOf(222) ); + t.set(2, LuaInteger.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 } }; + LuaValue la = LuaInteger.valueOf(a); + LuaTable tb = new LuaTable(); + LuaTable tc = new LuaTable(); + LuaValue va = CoerceJavaToLua.coerce(a); + LuaValue vb = CoerceJavaToLua.coerce(b); + LuaValue vc = CoerceJavaToLua.coerce(c); + tc.set( ONE, new LuaTable() ); + + int saa = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { la }, new Class[] { int.class } ); + int sab = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { la }, new Class[] { int[].class } ); + int sac = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { la }, new Class[] { int[][].class } ); + assertTrue( saa < sab ); + assertTrue( saa < sac ); + int sba = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { tb }, new Class[] { int.class } ); + int sbb = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { tb }, new Class[] { int[].class } ); + int sbc = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { tb }, new Class[] { int[][].class } ); + assertTrue( sbb < sba ); + assertTrue( sbb < sbc ); + int sca = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { tc }, new Class[] { int.class } ); + int scb = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { tc }, new Class[] { int[].class } ); + int scc = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { tc }, new Class[] { int[][].class } ); + assertTrue( scc < sca ); + assertTrue( scc < scb ); + + int vaa = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { va }, new Class[] { int.class } ); + int vab = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { va }, new Class[] { int[].class } ); + int vac = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { va }, new Class[] { int[][].class } ); + assertTrue( vaa < vab ); + assertTrue( vaa < vac ); + int vba = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { vb }, new Class[] { int.class } ); + int vbb = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { vb }, new Class[] { int[].class } ); + int vbc = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { vb }, new Class[] { int[][].class } ); + assertTrue( vbb < vba ); + assertTrue( vbb < vbc ); + int vca = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { vc }, new Class[] { int.class } ); + int vcb = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { vc }, new Class[] { int[].class } ); + int vcc = CoerceLuaToJava.scoreParamTypes( new LuaValue[] { 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 LuaString SAMPLE = LuaString.valueOf("sample"); + + public void testIntArrayParameterMatching() { + LuaValue v = CoerceJavaToLua.coerce(new SampleClass()); + + // get sample field, call with no arguments + LuaValue method = v.get(SAMPLE); + LuaValue result = method.call(v); + assertEquals( "void-args", result.toString() ); + + // get sample field, call with one arguments + LuaValue arg = CoerceJavaToLua.coerce(new Integer(123)); + method.call(v,arg); + result = method.call(v); + assertEquals( "int-args 123", result.toString() ); + + // get sample field, call with array argument + arg = CoerceJavaToLua.coerce(new int[]{345,678}); + method.call(v,arg); + result = method.call(v); + assertEquals( "int-array-args 345,678", result.toString() ); + + // get sample field, call with two-d array argument + arg = CoerceJavaToLua.coerce(new int[][]{{22,33},{44,55}}); + method.call(v,arg); + result = method.call(v); + assertEquals( "int-array-array-args 22,33,44,55", result.toString() ); + } + + public static final class SomeException extends RuntimeException { + public SomeException(String message) { + super(message); + } + } + + public static final class SomeClass { + public static void someMethod() { + throw new SomeException( "this is some message" ); + } + } + + public void testExceptionMessage() { + String script = "return pcall( luajava.bindClass( \""+SomeClass.class.getName()+"\").someMethod )"; + Varargs vresult = _G.get("loadstring").call(LuaValue.valueOf(script)).invoke(LuaValue.NONE); + LuaValue message = vresult.arg1(); + LuaValue status = vresult.arg(2); + assertEquals( LuaValue.FALSE, status ); + assertEquals( "lua error: "+SomeException.class.getName()+": this is some message", message.toString() ); + } + + public void testLuaErrorCause() { + String script = "luajava.bindClass( \""+SomeClass.class.getName()+"\").someMethod()"; + LuaValue chunk = _G.get("loadstring").call(LuaValue.valueOf(script)); + try { + chunk.invoke(LuaValue.NONE); + fail( "call should not have succeeded" ); + } catch ( LuaError lee ) { + Throwable c = lee.getCause(); + assertEquals( SomeException.class, c.getClass() ); + } + } + + public interface VarArgsInterface { + public String varargsMethod( String a, String ... v ); + public String arrayargsMethod( String a, String[] v ); + } + + public void testVarArgsProxy() { + String script = "return luajava.createProxy( \""+VarArgsInterface.class.getName()+"\", \n"+ + "{\n" + + " varargsMethod = function(a,...)\n" + + " return table.concat({a,...},'-')\n" + + " end,\n" + + " arrayargsMethod = function(a,array)\n" + + " return tostring(a)..(array and \n" + + " ('-'..tostring(array.length)\n" + + " ..'-'..tostring(array[1])\n" + + " ..'-'..tostring(array[2])\n" + + " ) or '-nil')\n" + + " end,\n" + + "} )\n"; + Varargs chunk = _G.get("loadstring").call(LuaValue.valueOf(script)); + if ( ! chunk.arg1().toboolean() ) + fail( chunk.arg(2).toString() ); + LuaValue result = chunk.arg1().call(); + Object u = result.touserdata(); + VarArgsInterface v = (VarArgsInterface) u; + assertEquals( "foo", v.varargsMethod("foo") ); + assertEquals( "foo-bar", v.varargsMethod("foo", "bar") ); + assertEquals( "foo-bar-etc", v.varargsMethod("foo", "bar", "etc") ); + assertEquals( "foo-0-nil-nil", v.arrayargsMethod("foo", new String[0]) ); + assertEquals( "foo-1-bar-nil", v.arrayargsMethod("foo", new String[] {"bar"}) ); + assertEquals( "foo-2-bar-etc", v.arrayargsMethod("foo", new String[] {"bar","etc"}) ); + assertEquals( "foo-3-bar-etc", v.arrayargsMethod("foo", new String[] {"bar","etc","etc"}) ); + assertEquals( "foo-nil", v.arrayargsMethod("foo", null) ); + } + +}