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