/******************************************************************************* * Copyright (c) 2009 Luaj.org. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ package org.luaj.vm2.lib.jse; import java.lang.reflect.Array; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.luaj.vm2.Varargs; import org.luaj.vm2.compiler.LuaC; import org.luaj.vm2.lib.LibFunction; import org.luaj.vm2.lib.VarArgFunction; /** * Subclass of {@link LibFunction} which implements the features of the luajava package. * <p> * Luajava is an approach to mixing lua and java using simple functions that bind * java classes and methods to lua dynamically. The API is documented on the * <a href="http://www.keplerproject.org/luajava/">luajava</a> documentation pages. * * <p> * Typically, this library is included as part of a call to * {@link JsePlatform#standardGlobals()} * <pre> {@code * Globals globals = JsePlatform.standardGlobals(); * System.out.println( globals.get("luajava").get("bindClass").call( LuaValue.valueOf("java.lang.System") ).invokeMethod("currentTimeMillis") ); * } </pre> * <p> * To instantiate and use it directly, * link it into your globals table via {@link Globals#load} using code such as: * <pre> {@code * Globals globals = new Globals(); * globals.load(new JseBaseLib()); * globals.load(new PackageLib()); * globals.load(new LuajavaLib()); * globals.load( * "sys = luajava.bindClass('java.lang.System')\n"+ * "print ( sys:currentTimeMillis() )\n", "main.lua" ).call(); * } </pre> * <p> * * The {@code luajava} library is available * on all JSE platforms via the call to {@link JsePlatform#standardGlobals()} * and the luajava api's are simply invoked from lua. * Because it makes extensive use of Java's reflection API, it is not available * on JME, but can be used in Android applications. * <p> * This has been implemented to match as closely as possible the behavior in the corresponding library in C. * * @see LibFunction * @see org.luaj.vm2.lib.jse.JsePlatform * @see org.luaj.vm2.lib.jme.JmePlatform * @see LuaC * @see CoerceJavaToLua * @see CoerceLuaToJava * @see <a href="http://www.keplerproject.org/luajava/manual.html#luareference">http://www.keplerproject.org/luajava/manual.html#luareference</a> */ public class LuajavaLib extends VarArgFunction { static final int INIT = 0; static final int BINDCLASS = 1; static final int NEWINSTANCE = 2; static final int NEW = 3; static final int CREATEPROXY = 4; static final int LOADLIB = 5; static final String[] NAMES = { "bindClass", "newInstance", "new", "createProxy", "loadLib", }; static final int METHOD_MODIFIERS_VARARGS = 0x80; public LuajavaLib() { } public Varargs invoke(Varargs args) { try { switch ( opcode ) { case INIT: { // LuaValue modname = args.arg1(); LuaValue env = args.arg(2); LuaTable t = new LuaTable(); bind( t, this.getClass(), NAMES, BINDCLASS ); env.set("luajava", t); env.get("package").get("loaded").set("luajava", t); return t; } case BINDCLASS: { final Class clazz = classForName(args.checkjstring(1)); return JavaClass.forClass(clazz); } case NEWINSTANCE: case NEW: { // get constructor final LuaValue c = args.checkvalue(1); final Class clazz = (opcode==NEWINSTANCE? classForName(c.tojstring()): (Class) c.checkuserdata(Class.class)); final Varargs consargs = args.subargs(2); return JavaClass.forClass(clazz).getConstructor().invoke(consargs); } case CREATEPROXY: { final int niface = args.narg()-1; if ( niface <= 0 ) throw new LuaError("no interfaces"); final LuaValue lobj = args.checktable(niface+1); // get the interfaces final Class[] ifaces = new Class[niface]; for ( int i=0; i<niface; i++ ) ifaces[i] = classForName(args.checkjstring(i+1)); // create the invocation handler InvocationHandler handler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); LuaValue func = lobj.get(name); if ( func.isnil() ) return null; boolean isvarargs = ((method.getModifiers() & METHOD_MODIFIERS_VARARGS) != 0); int n = args!=null? args.length: 0; LuaValue[] v; if ( isvarargs ) { Object o = args[--n]; int m = Array.getLength( o ); v = new LuaValue[n+m]; for ( int i=0; i<n; i++ ) v[i] = CoerceJavaToLua.coerce(args[i]); for ( int i=0; i<m; i++ ) v[i+n] = CoerceJavaToLua.coerce(Array.get(o,i)); } else { v = new LuaValue[n]; for ( int i=0; i<n; i++ ) v[i] = CoerceJavaToLua.coerce(args[i]); } LuaValue result = func.invoke(v).arg1(); return CoerceLuaToJava.coerce(result, method.getReturnType()); } }; // create the proxy object Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), ifaces, handler); // return the proxy return LuaValue.userdataOf( proxy ); } case LOADLIB: { // get constructor String classname = args.checkjstring(1); String methodname = args.checkjstring(2); Class clazz = classForName(classname); Method method = clazz.getMethod(methodname, new Class[] {}); Object result = method.invoke(clazz, new Object[] {}); if ( result instanceof LuaValue ) { return (LuaValue) result; } else { return NIL; } } default: throw new LuaError("not yet supported: "+this); } } catch (LuaError e) { throw e; } catch (InvocationTargetException ite) { throw new LuaError(ite.getTargetException()); } catch (Exception e) { throw new LuaError(e); } } // load classes using app loader to allow luaj to be used as an extension protected Class classForName(String name) throws ClassNotFoundException { return Class.forName(name, true, ClassLoader.getSystemClassLoader()); } }