| /* | |
| * $Id: JavaModule.java 76 2012-01-06 01:25:52Z andre@naef.com $ | |
| * See LICENSE.txt for license terms. | |
| */ | |
| package com.naef.jnlua; | |
| import java.lang.reflect.Array; | |
| import java.util.HashMap; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| import java.util.Map; | |
| import com.naef.jnlua.JavaReflector.Metamethod; | |
| /** | |
| * Provides the Java module for Lua. The Java module contains Java functions for | |
| * using Java in Lua. | |
| */ | |
| public class JavaModule { | |
| // -- Static | |
| private static final JavaModule INSTANCE = new JavaModule(); | |
| private static final Map<String, Class<?>> PRIMITIVE_TYPES = new HashMap<String, Class<?>>(); | |
| static { | |
| PRIMITIVE_TYPES.put("boolean", Boolean.TYPE); | |
| PRIMITIVE_TYPES.put("byte", Byte.TYPE); | |
| PRIMITIVE_TYPES.put("char", Character.TYPE); | |
| PRIMITIVE_TYPES.put("double", Double.TYPE); | |
| PRIMITIVE_TYPES.put("float", Float.TYPE); | |
| PRIMITIVE_TYPES.put("int", Integer.TYPE); | |
| PRIMITIVE_TYPES.put("long", Long.TYPE); | |
| PRIMITIVE_TYPES.put("short", Short.TYPE); | |
| PRIMITIVE_TYPES.put("void", Void.TYPE); | |
| } | |
| // -- State | |
| private final NamedJavaFunction[] functions = { new Require(), new New(), | |
| new InstanceOf(), new Cast(), new Proxy(), new Pairs(), | |
| new IPairs(), new ToTable(), new Elements(), new Fields(), | |
| new Methods(), new Properties() }; | |
| // -- Static methods | |
| /** | |
| * Returns the instance of the Java module. | |
| * | |
| * @return the instance | |
| */ | |
| public static JavaModule getInstance() { | |
| return INSTANCE; | |
| } | |
| // -- Construction | |
| /** | |
| * Singleton. | |
| */ | |
| private JavaModule() { | |
| } | |
| // -- Operations | |
| /** | |
| * Opens this module in a Lua state. The method is invoked by | |
| * {@link LuaState#openLibs()} or by | |
| * {@link LuaState#openLib(com.naef.jnlua.LuaState.Library)} if | |
| * {@link LuaState.Library#JAVA} is passed. The module is pushed onto the | |
| * stack. | |
| * | |
| * @param luaState | |
| * the Lua state to open in | |
| */ | |
| public void open(LuaState luaState) { | |
| luaState.register("java", functions, true); | |
| } | |
| /** | |
| * Returns a table-like Lua value for the specified map. The returned value | |
| * corresponds to the return value of the <code>totable()</code> function | |
| * provided by this Java module. | |
| * | |
| * @param map | |
| * the map | |
| * @return the table-like Lua value | |
| */ | |
| public TypedJavaObject toTable(Map<?, ?> map) { | |
| return ToTable.toTable(map); | |
| } | |
| /** | |
| * Returns a table-like Lua value for the specified list. The returned value | |
| * corresponds to the return value of the <code>totable()</code> function | |
| * provided by this Java module. | |
| * | |
| * @param list | |
| * the list | |
| * @return the table-like Lua value | |
| */ | |
| public TypedJavaObject toTable(List<?> list) { | |
| return ToTable.toTable(list); | |
| } | |
| // -- Private methods | |
| /** | |
| * Loads a type. The named type is a primitive type or a class. | |
| */ | |
| private static Class<?> loadType(LuaState luaState, String typeName) { | |
| Class<?> clazz; | |
| if ((clazz = PRIMITIVE_TYPES.get(typeName)) != null) { | |
| return clazz; | |
| } | |
| try { | |
| clazz = luaState.getClassLoader().loadClass(typeName); | |
| return clazz; | |
| } catch (ClassNotFoundException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| // -- Nested types | |
| /** | |
| * Imports a Java class into the Lua namespace. Returns the class and a | |
| * status code. The status code indicates if the class was stored in the Lua | |
| * namespace. Primitive types and classes without a package are not stored | |
| * in the Lua namespace. | |
| */ | |
| private static class Require implements NamedJavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| // Check arguments | |
| String className = luaState.checkString(1); | |
| boolean doImport = luaState.toBoolean(2); | |
| // Load | |
| Class<?> clazz = loadType(luaState, className); | |
| luaState.pushJavaObject(clazz); | |
| // Import | |
| if (doImport) { | |
| luaState.rawGet(LuaState.REGISTRYINDEX, LuaState.RIDX_GLOBALS); | |
| String name = clazz.getName(); | |
| int index = name.indexOf('.'); | |
| while (index >= 0) { | |
| String part = name.substring(0, index); | |
| luaState.getField(-1, part); | |
| if (!luaState.isTable(-1)) { | |
| luaState.pop(1); | |
| luaState.newTable(); | |
| luaState.pushValue(-1); | |
| luaState.setField(-3, part); | |
| } | |
| luaState.remove(-2); | |
| name = name.substring(index + 1); | |
| index = name.indexOf('.'); | |
| } | |
| luaState.pushValue(-2); | |
| luaState.setField(-2, name); | |
| luaState.pop(1); | |
| } | |
| luaState.pushBoolean(doImport); | |
| // Return | |
| return 2; | |
| } | |
| @Override | |
| public String getName() { | |
| return "require"; | |
| } | |
| } | |
| /** | |
| * Creates and returns a new Java object or array thereof. The first | |
| * argument designates the type to instantiate, either as a class or a | |
| * string. The remaining arguments are the dimensions. | |
| */ | |
| private static class New implements NamedJavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| // Find class | |
| Class<?> clazz; | |
| if (luaState.isJavaObject(1, Class.class)) { | |
| clazz = luaState.checkJavaObject(1, Class.class); | |
| } else { | |
| String className = luaState.checkString(1); | |
| clazz = loadType(luaState, className); | |
| } | |
| // Instantiate | |
| Object object; | |
| int dimensionCount = luaState.getTop() - 1; | |
| switch (dimensionCount) { | |
| case 0: | |
| try { | |
| object = clazz.newInstance(); | |
| } catch (InstantiationException e) { | |
| throw new RuntimeException(e); | |
| } catch (IllegalAccessException e) { | |
| throw new RuntimeException(e); | |
| } | |
| break; | |
| case 1: | |
| object = Array.newInstance(clazz, luaState.checkInteger(2)); | |
| break; | |
| default: | |
| int[] dimensions = new int[dimensionCount]; | |
| for (int i = 0; i < dimensionCount; i++) { | |
| dimensions[i] = luaState.checkInteger(i + 2); | |
| } | |
| object = Array.newInstance(clazz, dimensions); | |
| } | |
| // Return | |
| luaState.pushJavaObject(object); | |
| return 1; | |
| } | |
| @Override | |
| public String getName() { | |
| return "new"; | |
| } | |
| } | |
| /** | |
| * Returns whether an object is an instance of a type. The object is given | |
| * as the first argument. the type is given as the second argument, either | |
| * as a class or as a type name. | |
| */ | |
| private static class InstanceOf implements NamedJavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| // Get the object | |
| Object object = luaState.checkJavaObject(1, Object.class); | |
| // Find class | |
| Class<?> clazz; | |
| if (luaState.isJavaObject(2, Class.class)) { | |
| clazz = luaState.checkJavaObject(2, Class.class); | |
| } else { | |
| String className = luaState.checkString(2); | |
| clazz = loadType(luaState, className); | |
| } | |
| // Type check | |
| luaState.pushBoolean(clazz.isInstance(object)); | |
| return 1; | |
| } | |
| @Override | |
| public String getName() { | |
| return "instanceof"; | |
| } | |
| } | |
| /** | |
| * Creates a typed Java object. | |
| */ | |
| private static class Cast implements NamedJavaFunction { | |
| // -- NamedJavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| // Find class | |
| final Class<?> clazz; | |
| if (luaState.isJavaObject(2, Class.class)) { | |
| clazz = luaState.checkJavaObject(2, Class.class); | |
| } else { | |
| String className = luaState.checkString(2); | |
| clazz = loadType(luaState, className); | |
| } | |
| // Get the object | |
| final Object object = luaState.checkJavaObject(1, clazz); | |
| // Push result | |
| luaState.pushJavaObject(new TypedJavaObject() { | |
| @Override | |
| public Object getObject() { | |
| return object; | |
| } | |
| @Override | |
| public Class<?> getType() { | |
| return clazz; | |
| } | |
| @Override | |
| public boolean isStrong() { | |
| return false; | |
| } | |
| }); | |
| return 1; | |
| } | |
| @Override | |
| public String getName() { | |
| return "cast"; | |
| } | |
| } | |
| /** | |
| * Creates a dynamic proxy object the implements a set of Java interfaces in | |
| * Lua. | |
| */ | |
| private static class Proxy implements NamedJavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| // Check table | |
| luaState.checkType(1, LuaType.TABLE); | |
| // Get interfaces | |
| int interfaceCount = luaState.getTop() - 1; | |
| luaState.checkArg(2, interfaceCount > 0, "no interface specified"); | |
| Class<?>[] interfaces = new Class<?>[interfaceCount]; | |
| for (int i = 0; i < interfaceCount; i++) { | |
| if (luaState.isJavaObject(i + 2, Class.class)) { | |
| interfaces[i] = luaState | |
| .checkJavaObject(i + 2, Class.class); | |
| } else { | |
| String interfaceName = luaState.checkString(i + 2); | |
| interfaces[i] = loadType(luaState, interfaceName); | |
| } | |
| } | |
| // Create proxy | |
| luaState.pushJavaObjectRaw(luaState.getProxy(1, interfaces)); | |
| return 1; | |
| } | |
| @Override | |
| public String getName() { | |
| return "proxy"; | |
| } | |
| } | |
| /** | |
| * Provides the pairs iterator from the Java reflector. | |
| */ | |
| private static class Pairs implements NamedJavaFunction { | |
| // -- NamedJavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| luaState.checkArg( | |
| 1, | |
| luaState.isJavaObjectRaw(1), | |
| String.format("Java object expected, got %s", | |
| luaState.typeName(1))); | |
| JavaFunction metamethod = luaState.getMetamethod( | |
| luaState.toJavaObjectRaw(1), Metamethod.PAIRS); | |
| return metamethod.invoke(luaState); | |
| } | |
| @Override | |
| public String getName() { | |
| return "pairs"; | |
| } | |
| } | |
| /** | |
| * Provides the ipairs iterator from the Java reflector. | |
| */ | |
| private static class IPairs implements NamedJavaFunction { | |
| // -- NamedJavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| luaState.checkArg( | |
| 1, | |
| luaState.isJavaObjectRaw(1), | |
| String.format("Java object expected, got %s", | |
| luaState.typeName(1))); | |
| JavaFunction metamethod = luaState.getMetamethod( | |
| luaState.toJavaObjectRaw(1), Metamethod.IPAIRS); | |
| return metamethod.invoke(luaState); | |
| } | |
| @Override | |
| public String getName() { | |
| return "ipairs"; | |
| } | |
| } | |
| /** | |
| * Provides a wrapper object for table-like map and list access from Lua. | |
| */ | |
| private static class ToTable implements NamedJavaFunction { | |
| // -- Static methods | |
| /** | |
| * Returns a table-like Lua value for the specified map. | |
| */ | |
| @SuppressWarnings("unchecked") | |
| public static TypedJavaObject toTable(Map<?, ?> map) { | |
| return new LuaMap((Map<Object, Object>) map); | |
| } | |
| /** | |
| * Returns a table-list Lua value for the specified list. | |
| */ | |
| @SuppressWarnings("unchecked") | |
| public static TypedJavaObject toTable(List<?> list) { | |
| return new LuaList((List<Object>) list); | |
| } | |
| // -- JavaFunction methods | |
| @SuppressWarnings("unchecked") | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| if (luaState.isJavaObject(1, Map.class)) { | |
| Map<Object, Object> map = luaState.toJavaObject(1, Map.class); | |
| luaState.pushJavaObject(new LuaMap(map)); | |
| } else if (luaState.isJavaObject(1, List.class)) { | |
| List<Object> list = luaState.toJavaObject(1, List.class); | |
| luaState.pushJavaObject(new LuaList(list)); | |
| } else { | |
| luaState.checkArg( | |
| 1, | |
| false, | |
| String.format("expected map or list, got %s", | |
| luaState.typeName(1))); | |
| } | |
| return 1; | |
| } | |
| @Override | |
| public String getName() { | |
| return "totable"; | |
| } | |
| // -- Member types | |
| /** | |
| * Provides table-like access in Lua to a Java map. | |
| */ | |
| private static class LuaMap implements JavaReflector, TypedJavaObject { | |
| // -- Static | |
| private static final JavaFunction INDEX = new Index(); | |
| private static final JavaFunction NEW_INDEX = new NewIndex(); | |
| // -- State | |
| private Map<Object, Object> map; | |
| // -- Construction | |
| /** | |
| * Creates a new instance. | |
| */ | |
| public LuaMap(Map<Object, Object> map) { | |
| this.map = map; | |
| } | |
| // -- Properties | |
| /** | |
| * Returns the map. | |
| */ | |
| public Map<Object, Object> getMap() { | |
| return map; | |
| } | |
| // -- JavaReflector methods | |
| @Override | |
| public JavaFunction getMetamethod(Metamethod metamethod) { | |
| switch (metamethod) { | |
| case INDEX: | |
| return INDEX; | |
| case NEWINDEX: | |
| return NEW_INDEX; | |
| default: | |
| return null; | |
| } | |
| } | |
| // -- TypedJavaObject methods | |
| @Override | |
| public Object getObject() { | |
| return map; | |
| } | |
| @Override | |
| public Class<?> getType() { | |
| return Map.class; | |
| } | |
| @Override | |
| public boolean isStrong() { | |
| return true; | |
| } | |
| // -- Member types | |
| /** | |
| * __index implementation for maps. | |
| */ | |
| private static class Index implements JavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| LuaMap luaMap = (LuaMap) luaState.toJavaObjectRaw(1); | |
| Object key = luaState.toJavaObject(2, Object.class); | |
| if (key == null) { | |
| throw new LuaRuntimeException(String.format( | |
| "attempt to read map with %s accessor", | |
| luaState.typeName(2))); | |
| } | |
| luaState.pushJavaObject(luaMap.getMap().get(key)); | |
| return 1; | |
| } | |
| } | |
| /** | |
| * __newindex implementation for maps. | |
| */ | |
| private static class NewIndex implements JavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| LuaMap luaMap = (LuaMap) luaState.toJavaObjectRaw(1); | |
| Object key = luaState.toJavaObject(2, Object.class); | |
| if (key == null) { | |
| throw new LuaRuntimeException(String.format( | |
| "attempt to write map with %s accessor", | |
| luaState.typeName(2))); | |
| } | |
| Object value = luaState.toJavaObject(3, Object.class); | |
| if (value != null) { | |
| luaMap.getMap().put(key, value); | |
| } else { | |
| luaMap.getMap().remove(key); | |
| } | |
| return 0; | |
| } | |
| } | |
| } | |
| /** | |
| * Provides table-like access in Lua to a Java list. | |
| */ | |
| private static class LuaList implements JavaReflector, TypedJavaObject { | |
| // -- Static | |
| private static final JavaFunction INDEX = new Index(); | |
| private static final JavaFunction NEW_INDEX = new NewIndex(); | |
| private static final JavaFunction LENGTH = new Length(); | |
| // -- State | |
| private List<Object> list; | |
| // -- Construction | |
| /** | |
| * Creates a new instance. | |
| */ | |
| public LuaList(List<Object> list) { | |
| this.list = list; | |
| } | |
| // -- Properties | |
| /** | |
| * Returns the map. | |
| */ | |
| public List<Object> getList() { | |
| return list; | |
| } | |
| // -- JavaReflector methods | |
| @Override | |
| public JavaFunction getMetamethod(Metamethod metamethod) { | |
| switch (metamethod) { | |
| case INDEX: | |
| return INDEX; | |
| case NEWINDEX: | |
| return NEW_INDEX; | |
| case LEN: | |
| return LENGTH; | |
| default: | |
| return null; | |
| } | |
| } | |
| // -- TypedJavaObject methods | |
| @Override | |
| public Object getObject() { | |
| return list; | |
| } | |
| @Override | |
| public Class<?> getType() { | |
| return List.class; | |
| } | |
| @Override | |
| public boolean isStrong() { | |
| return true; | |
| } | |
| // -- Member types | |
| /** | |
| * __index implementation for lists. | |
| */ | |
| private static class Index implements JavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| LuaList luaList = (LuaList) luaState.toJavaObjectRaw(1); | |
| if (!luaState.isNumber(2)) { | |
| throw new LuaRuntimeException(String.format( | |
| "attempt to read list with %s accessor", | |
| luaState.typeName(2))); | |
| } | |
| int index = luaState.toInteger(2); | |
| luaState.pushJavaObject(luaList.getList().get(index - 1)); | |
| return 1; | |
| } | |
| } | |
| /** | |
| * __newindex implementation for lists. | |
| */ | |
| private static class NewIndex implements JavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| LuaList luaList = (LuaList) luaState.toJavaObjectRaw(1); | |
| if (!luaState.isNumber(2)) { | |
| throw new LuaRuntimeException(String.format( | |
| "attempt to write list with %s accessor", | |
| luaState.typeName(2))); | |
| } | |
| int index = luaState.toInteger(2); | |
| Object value = luaState.toJavaObject(3, Object.class); | |
| if (value != null) { | |
| int size = luaList.getList().size(); | |
| if (index - 1 != size) { | |
| luaList.getList().set(index - 1, value); | |
| } else { | |
| luaList.getList().add(value); | |
| } | |
| } else { | |
| luaList.getList().remove(index - 1); | |
| } | |
| return 0; | |
| } | |
| } | |
| /** | |
| * __len implementation for lists. | |
| */ | |
| private static class Length implements JavaFunction { | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| LuaList luaList = (LuaList) luaState.toJavaObjectRaw(1); | |
| luaState.pushInteger(luaList.getList().size()); | |
| return 1; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Provides an iterator for Iterable objects. | |
| */ | |
| private static class Elements implements NamedJavaFunction { | |
| // -- NamedJavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| Iterable<?> iterable = luaState.checkJavaObject(1, Iterable.class); | |
| luaState.pushJavaObject(new ElementIterator(iterable.iterator())); | |
| luaState.pushJavaObject(iterable); | |
| luaState.pushNil(); | |
| return 3; | |
| } | |
| @Override | |
| public String getName() { | |
| return "elements"; | |
| } | |
| // -- Member types | |
| private static class ElementIterator implements JavaFunction { | |
| // -- State | |
| private Iterator<?> iterator; | |
| // -- Construction | |
| /** | |
| * Creates a new instance. | |
| */ | |
| public ElementIterator(Iterator<?> iterator) { | |
| this.iterator = iterator; | |
| } | |
| // -- JavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| if (iterator.hasNext()) { | |
| luaState.pushJavaObject(iterator.next()); | |
| } else { | |
| luaState.pushNil(); | |
| } | |
| return 1; | |
| } | |
| } | |
| } | |
| /** | |
| * Provides an iterator for Java object fields. | |
| */ | |
| private static class Fields implements NamedJavaFunction { | |
| // -- NamedJavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| luaState.checkArg( | |
| 1, | |
| luaState.isJavaObjectRaw(1), | |
| String.format("expected Java object, got %s", | |
| luaState.typeName(1))); | |
| JavaFunction metamethod = luaState.getMetamethod( | |
| luaState.toJavaObjectRaw(1), Metamethod.JAVAFIELDS); | |
| return metamethod.invoke(luaState); | |
| } | |
| @Override | |
| public String getName() { | |
| return "fields"; | |
| } | |
| } | |
| /** | |
| * Provides an iterator for Java methods. | |
| */ | |
| private static class Methods implements NamedJavaFunction { | |
| // -- NamedJavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| luaState.checkArg( | |
| 1, | |
| luaState.isJavaObjectRaw(1), | |
| String.format("expected Java object, got %s", | |
| luaState.typeName(1))); | |
| JavaFunction metamethod = luaState.getMetamethod( | |
| luaState.toJavaObjectRaw(1), Metamethod.JAVAMETHODS); | |
| return metamethod.invoke(luaState); | |
| } | |
| @Override | |
| public String getName() { | |
| return "methods"; | |
| } | |
| } | |
| /** | |
| * Provides an iterator for Java object properties. | |
| */ | |
| private static class Properties implements NamedJavaFunction { | |
| // -- NamedJavaFunction methods | |
| @Override | |
| public int invoke(LuaState luaState) { | |
| luaState.checkArg( | |
| 1, | |
| luaState.isJavaObjectRaw(1), | |
| String.format("expected Java object, got %s", | |
| luaState.typeName(1))); | |
| JavaFunction metamethod = luaState.getMetamethod( | |
| luaState.toJavaObjectRaw(1), Metamethod.JAVAPROPERTIES); | |
| return metamethod.invoke(luaState); | |
| } | |
| @Override | |
| public String getName() { | |
| return "properties"; | |
| } | |
| } | |
| } |