| /* |
| * $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"; |
| } |
| } |
| } |