/* * $Id: JavaModule.java,v 1.2 2008/11/07 21:00:52 anaef Exp $ * 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 java.util.NavigableMap; 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); } private static final NamedJavaFunction[] EMPTY_MODULE = new NamedJavaFunction[0]; // -- 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. * * @param luaState * the Lua state to open in */ public void open(LuaState luaState) { synchronized (luaState) { luaState.register("java", functions); luaState.pop(1); } } /** * 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.checkBoolean(2, false); // Load Class<?> clazz = loadType(luaState, className); luaState.pushJavaObject(clazz); // Import if (doImport) { className = clazz.getName(); int lastDotIndex = className.lastIndexOf('.'); if (lastDotIndex >= 0) { String packageName = className.substring(0, lastDotIndex); className = className.substring(lastDotIndex + 1); luaState.register(packageName, EMPTY_MODULE); luaState.pushJavaObject(clazz); luaState.setField(-2, className); luaState.pop(1); } else { luaState.pushJavaObject(clazz); luaState.setGlobal(className); } } 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 an iterator for maps. For <code>NavigableMap</code> objects, the * function returns a stateless iterator which allows concurrent * modifications to the map. For other maps, the function returns an * iterator based on <code>Iterator</code> which does not support concurrent * modifications. */ private static class Pairs implements NamedJavaFunction { // -- Static private final JavaFunction navigableMapNext = new NavigableMapNext(); // -- JavaFunction methods @SuppressWarnings("unchecked") @Override public int invoke(LuaState luaState) { Map<Object, Object> map = luaState.checkJavaObject(1, Map.class); luaState.checkArg(1, map != null, String.format( "expected map, got %s", luaState.typeName(1))); if (map instanceof NavigableMap) { luaState.pushJavaFunction(navigableMapNext); } else { luaState .pushJavaFunction(new MapNext(map.entrySet().iterator())); } luaState.pushJavaObject(map); luaState.pushNil(); return 3; } @Override public String getName() { return "pairs"; } /** * Provides a stateful iterator function for maps. */ private static class MapNext implements JavaFunction { // -- State private Iterator<Map.Entry<Object, Object>> iterator; // -- Construction /** * Creates a new instance. */ public MapNext(Iterator<Map.Entry<Object, Object>> iterator) { this.iterator = iterator; } // -- JavaFunction methods public int invoke(LuaState luaState) { if (iterator.hasNext()) { Map.Entry<Object, Object> entry = iterator.next(); luaState.pushJavaObject(entry.getKey()); luaState.pushJavaObject(entry.getValue()); return 2; } else { luaState.pushNil(); return 1; } } } /** * Provides a stateless iterator function for navigable maps. */ private static class NavigableMapNext implements JavaFunction { // -- JavaFunction methods @SuppressWarnings("unchecked") public int invoke(LuaState luaState) { NavigableMap<Object, Object> navigableMap = luaState .checkJavaObject(1, NavigableMap.class); Object key = luaState.checkJavaObject(2, Object.class); Map.Entry<Object, Object> entry; if (key != null) { entry = navigableMap.higherEntry(key); } else { entry = navigableMap.firstEntry(); } if (entry != null) { luaState.pushJavaObject(entry.getKey()); luaState.pushJavaObject(entry.getValue()); return 2; } else { luaState.pushNil(); return 1; } } } } /** * Provides an iterator for lists and arrays. */ private static class IPairs implements NamedJavaFunction { // -- Static private final JavaFunction listNext = new ListNext(); private final JavaFunction arrayNext = new ArrayNext(); // -- JavaFunction methods @Override public int invoke(LuaState luaState) { Object object; if (luaState.isJavaObject(1, List.class)) { object = luaState.toJavaObject(1, List.class); luaState.pushJavaFunction(listNext); } else { object = luaState.checkJavaObject(1, Object.class); luaState.checkArg(1, object.getClass().isArray(), String .format("expected list or array, got %s", luaState .typeName(1))); luaState.pushJavaFunction(arrayNext); } luaState.pushJavaObject(object); luaState.pushInteger(0); return 3; } @Override public String getName() { return "ipairs"; } /** * Provides a stateless iterator function for lists. */ private static class ListNext implements JavaFunction { public int invoke(LuaState luaState) { List<?> list = luaState.checkJavaObject(1, List.class); int size = list.size(); int index = luaState.checkInteger(2); index++; if (index >= 1 && index <= size) { luaState.pushInteger(index); luaState.pushJavaObject(list.get(index - 1)); return 2; } else { luaState.pushNil(); return 1; } } } /** * Provides a stateless iterator function for arrays. */ private static class ArrayNext implements JavaFunction { public int invoke(LuaState luaState) { Object array = luaState.checkJavaObject(1, Object.class); int length = java.lang.reflect.Array.getLength(array); int index = luaState.checkInteger(2); index++; if (index >= 1 && index <= length) { luaState.pushInteger(index); luaState.pushJavaObject(Array.get(array, index - 1)); return 2; } else { luaState.pushNil(); return 1; } } } } /** * 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"; } } }