/* | |
* $Id: DefaultJavaReflector.java 161 2012-10-06 13:53:02Z andre@naef.com $ | |
* See LICENSE.txt for license terms. | |
*/ | |
package com.naef.jnlua; | |
import java.beans.BeanInfo; | |
import java.beans.IntrospectionException; | |
import java.beans.Introspector; | |
import java.beans.PropertyDescriptor; | |
import java.lang.reflect.Array; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.NavigableMap; | |
import java.util.Set; | |
import java.util.Map.Entry; | |
import java.util.concurrent.locks.ReadWriteLock; | |
import java.util.concurrent.locks.ReentrantReadWriteLock; | |
/** | |
* Default implementation of the <code>JavaReflector</code> interface. | |
*/ | |
public class DefaultJavaReflector implements JavaReflector { | |
// -- Static | |
private static final DefaultJavaReflector INSTANCE = new DefaultJavaReflector(); | |
private static final Object JAVA_FUNCTION_TYPE = new Object(); | |
private static final Object[] EMPTY_ARGUMENTS = new Object[0]; | |
// -- State | |
private Map<Class<?>, Map<String, Accessor>> accessors = new HashMap<Class<?>, Map<String, Accessor>>(); | |
private ReadWriteLock accessorLock = new ReentrantReadWriteLock(); | |
private Map<LuaCallSignature, Invocable> invocableDispatches = new HashMap<LuaCallSignature, Invocable>(); | |
private ReadWriteLock invocableDispatchLock = new ReentrantReadWriteLock(); | |
private JavaFunction index = new Index(); | |
private JavaFunction newIndex = new NewIndex(); | |
private JavaFunction equal = new Equal(); | |
private JavaFunction length = new Length(); | |
private JavaFunction lessThan = new LessThan(); | |
private JavaFunction lessThanOrEqual = new LessThanOrEqual(); | |
private JavaFunction toString = new ToString(); | |
private JavaFunction pairs = new Pairs(); | |
private JavaFunction ipairs = new IPairs(); | |
private JavaFunction javaFields = new AccessorPairs(FieldAccessor.class); | |
private JavaFunction javaMethods = new AccessorPairs( | |
InvocableAccessor.class); | |
private JavaFunction javaProperties = new AccessorPairs( | |
PropertyAccessor.class); | |
// -- Static methods | |
/** | |
* Returns the instance of this class. | |
* | |
* @return the instance | |
*/ | |
public static DefaultJavaReflector getInstance() { | |
return INSTANCE; | |
} | |
// -- Construction | |
/** | |
* Creates a new instances; | |
*/ | |
private DefaultJavaReflector() { | |
} | |
// -- JavaReflector methods | |
@Override | |
public JavaFunction getMetamethod(Metamethod metamethod) { | |
switch (metamethod) { | |
case INDEX: | |
return index; | |
case NEWINDEX: | |
return newIndex; | |
case LEN: | |
return length; | |
case EQ: | |
return equal; | |
case LT: | |
return lessThan; | |
case LE: | |
return lessThanOrEqual; | |
case TOSTRING: | |
return toString; | |
case PAIRS: | |
return pairs; | |
case IPAIRS: | |
return ipairs; | |
case JAVAFIELDS: | |
return javaFields; | |
case JAVAMETHODS: | |
return javaMethods; | |
case JAVAPROPERTIES: | |
return javaProperties; | |
default: | |
return null; | |
} | |
} | |
// -- Private methods | |
/** | |
* Returns the accessors of an object. | |
*/ | |
private Map<String, Accessor> getObjectAccessors(Object object) { | |
// Check cache | |
Class<?> clazz = getObjectClass(object); | |
accessorLock.readLock().lock(); | |
try { | |
Map<String, Accessor> result = accessors.get(clazz); | |
if (result != null) { | |
return result; | |
} | |
} finally { | |
accessorLock.readLock().unlock(); | |
} | |
// Fill in | |
Map<String, Accessor> result = createClassAccessors(clazz); | |
accessorLock.writeLock().lock(); | |
try { | |
if (!accessors.containsKey(clazz)) { | |
accessors.put(clazz, result); | |
} else { | |
result = accessors.get(clazz); | |
} | |
} finally { | |
accessorLock.writeLock().unlock(); | |
} | |
return result; | |
} | |
/** | |
* Creates the accessors of a class. | |
*/ | |
private Map<String, Accessor> createClassAccessors(Class<?> clazz) { | |
Map<String, Accessor> result = new HashMap<String, Accessor>(); | |
// Fields | |
Field[] fields = clazz.getFields(); | |
for (int i = 0; i < fields.length; i++) { | |
result.put(fields[i].getName(), new FieldAccessor(fields[i])); | |
} | |
// Methods | |
Map<String, Map<List<Class<?>>, Invocable>> accessibleMethods = new HashMap<String, Map<List<Class<?>>, Invocable>>(); | |
Method[] methods = clazz.getMethods(); | |
for (int i = 0; i < methods.length; i++) { | |
// Do not overwrite fields | |
Method method = methods[i]; | |
if (result.containsKey(method.getName())) { | |
continue; | |
} | |
// Attempt to find the method in a public class if the declaring | |
// class is not public | |
if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) { | |
method = getPublicClassMethod(clazz, method.getName(), | |
method.getParameterTypes()); | |
if (method == null) { | |
continue; | |
} | |
} | |
// For each method name and parameter type list, keep | |
// only the method declared by the most specific class | |
Map<List<Class<?>>, Invocable> overloaded = accessibleMethods | |
.get(method.getName()); | |
if (overloaded == null) { | |
overloaded = new HashMap<List<Class<?>>, Invocable>(); | |
accessibleMethods.put(method.getName(), overloaded); | |
} | |
List<Class<?>> parameterTypes = Arrays.asList(method | |
.getParameterTypes()); | |
Invocable currentInvocable = overloaded.get(parameterTypes); | |
if (currentInvocable != null | |
&& method.getDeclaringClass().isAssignableFrom( | |
currentInvocable.getDeclaringClass())) { | |
continue; | |
} | |
overloaded.put(parameterTypes, new InvocableMethod(method)); | |
} | |
for (Map.Entry<String, Map<List<Class<?>>, Invocable>> entry : accessibleMethods | |
.entrySet()) { | |
result.put(entry.getKey(), new InvocableAccessor(clazz, entry | |
.getValue().values())); | |
} | |
// Constructors | |
Constructor<?>[] constructors = clazz.getConstructors(); | |
List<Invocable> accessibleConstructors = new ArrayList<Invocable>( | |
constructors.length); | |
for (int i = 0; i < constructors.length; i++) { | |
// Ignore constructor if the declaring class is not public | |
if (!Modifier.isPublic(constructors[i].getDeclaringClass() | |
.getModifiers())) { | |
continue; | |
} | |
accessibleConstructors | |
.add(new InvocableConstructor(constructors[i])); | |
} | |
if (clazz.isInterface()) { | |
accessibleConstructors.add(new InvocableProxy(clazz)); | |
} | |
if (!accessibleConstructors.isEmpty()) { | |
result.put("new", new InvocableAccessor(clazz, | |
accessibleConstructors)); | |
} | |
// Properties | |
BeanInfo beanInfo; | |
try { | |
beanInfo = Introspector.getBeanInfo(clazz); | |
} catch (IntrospectionException e) { | |
throw new RuntimeException(e); | |
} | |
PropertyDescriptor[] propertyDescriptors = beanInfo | |
.getPropertyDescriptors(); | |
for (int i = 0; i < propertyDescriptors.length; i++) { | |
// Do not overwrite fields or methods | |
if (result.containsKey(propertyDescriptors[i].getName())) { | |
continue; | |
} | |
// Attempt to find the read/write methods in a public class if the | |
// declaring class is not public | |
Method method = propertyDescriptors[i].getReadMethod(); | |
if (method != null | |
&& !Modifier.isPublic(method.getDeclaringClass() | |
.getModifiers())) { | |
method = getPublicClassMethod(clazz, method.getName(), | |
method.getParameterTypes()); | |
try { | |
propertyDescriptors[i].setReadMethod(method); | |
} catch (IntrospectionException e) { | |
} | |
} | |
method = propertyDescriptors[i].getWriteMethod(); | |
if (method != null | |
&& !Modifier.isPublic(method.getDeclaringClass() | |
.getModifiers())) { | |
method = getPublicClassMethod(clazz, method.getName(), | |
method.getParameterTypes()); | |
try { | |
propertyDescriptors[i].setWriteMethod(method); | |
} catch (IntrospectionException e) { | |
} | |
} | |
// Do not process properties without a read and a write method | |
if (propertyDescriptors[i].getReadMethod() == null | |
&& propertyDescriptors[i].getWriteMethod() == null) { | |
continue; | |
} | |
result.put(propertyDescriptors[i].getName(), new PropertyAccessor( | |
clazz, propertyDescriptors[i])); | |
} | |
return result; | |
} | |
/** | |
* Returns a public class method matching a method name and parameter list. | |
* The public class can be a superclass or interface. | |
*/ | |
private Method getPublicClassMethod(Class<?> clazz, String methodName, | |
Class<?>[] parameterTypes) { | |
Method method = getPublicSuperclassMethod(clazz, methodName, | |
parameterTypes); | |
if (method != null) { | |
return method; | |
} | |
return getInterfaceMethod(clazz, methodName, parameterTypes); | |
} | |
/** | |
* Returns a public superclass method matching a method name and parameter | |
* list. | |
*/ | |
private Method getPublicSuperclassMethod(Class<?> clazz, String methodName, | |
Class<?>[] parameterTypes) { | |
Class<?> superclass = clazz.getSuperclass(); | |
while (superclass != null) { | |
// Ignore non-public superclasses | |
if (!Modifier.isPublic(superclass.getModifiers())) { | |
continue; | |
} | |
// Find method in superclass | |
try { | |
Method method = superclass.getDeclaredMethod(methodName, | |
parameterTypes); | |
if (Modifier.isPublic(method.getModifiers())) { | |
return method; | |
} | |
} catch (NoSuchMethodException e) { | |
// Not found | |
} | |
// Check superclass | |
superclass = superclass.getSuperclass(); | |
} | |
// Not found | |
return null; | |
} | |
/** | |
* Returns an interface method matching a method name and parameter list. | |
*/ | |
private Method getInterfaceMethod(Class<?> clazz, String methodName, | |
Class<?>[] parameterTypes) { | |
do { | |
// Get interfaces | |
Class<?>[] interfaces = clazz.getInterfaces(); | |
for (int i = 0; i < interfaces.length; i++) { | |
// Ignore non-public interfaces | |
if (!Modifier.isPublic(interfaces[i].getModifiers())) { | |
continue; | |
} | |
// Find method in the current interface | |
try { | |
return interfaces[i].getDeclaredMethod(methodName, | |
parameterTypes); | |
} catch (NoSuchMethodException e) { | |
// Not found | |
} | |
// Check superinterfaces | |
Method method = getInterfaceMethod(interfaces[i], methodName, | |
parameterTypes); | |
if (method != null) { | |
return method; | |
} | |
} | |
// Check superclass | |
clazz = clazz.getSuperclass(); | |
} while (clazz != null); | |
// Not found | |
return null; | |
} | |
/** | |
* Returns the class of an object, or the class itself if the object is | |
* already a class. | |
*/ | |
private Class<?> getObjectClass(Object object) { | |
return object instanceof Class<?> ? (Class<?>) object : object | |
.getClass(); | |
} | |
// -- Nested types | |
/** | |
* <code>__index</code> metamethod implementation. | |
*/ | |
private class Index implements JavaFunction { | |
public int invoke(LuaState luaState) { | |
// Get object and class | |
Object object = luaState.toJavaObject(1, Object.class); | |
Class<?> objectClass = getObjectClass(object); | |
// Handle arrays | |
if (objectClass.isArray()) { | |
if (!luaState.isNumber(2)) { | |
throw new LuaRuntimeException(String.format( | |
"attempt to read array with %s accessor", | |
luaState.typeName(2))); | |
} | |
int index = luaState.toInteger(2); | |
int length = Array.getLength(object); | |
if (index < 1 || index > length) { | |
throw new LuaRuntimeException(String.format( | |
"attempt to read array of length %d at index %d", | |
length, index)); | |
} | |
luaState.pushJavaObject(Array.get(object, index - 1)); | |
return 1; | |
} | |
// Handle objects | |
Map<String, Accessor> objectAccessors = getObjectAccessors(object); | |
String key = luaState.toString(-1); | |
if (key == null) { | |
throw new LuaRuntimeException(String.format( | |
"attempt to read class %s with %s accessor", object | |
.getClass().getCanonicalName(), luaState | |
.typeName(-1))); | |
} | |
Accessor accessor = objectAccessors.get(key); | |
if (accessor == null) { | |
throw new LuaRuntimeException( | |
String.format( | |
"attempt to read class %s with accessor '%s' (undefined)", | |
objectClass.getCanonicalName(), key)); | |
} | |
accessor.read(luaState, object); | |
return 1; | |
} | |
} | |
/** | |
* <code>__newindex</code> metamethod implementation. | |
*/ | |
private class NewIndex implements JavaFunction { | |
public int invoke(LuaState luaState) { | |
// Get object and class | |
Object object = luaState.toJavaObject(1, Object.class); | |
Class<?> objectClass = getObjectClass(object); | |
// Handle arrays | |
if (objectClass.isArray()) { | |
if (!luaState.isNumber(2)) { | |
throw new LuaRuntimeException(String.format( | |
"attempt to write array with %s accessor", | |
luaState.typeName(2))); | |
} | |
int index = luaState.toInteger(2); | |
int length = Array.getLength(object); | |
if (index < 1 || index > length) { | |
throw new LuaRuntimeException(String.format( | |
"attempt to write array of length %d at index %d", | |
length, index)); | |
} | |
Class<?> componentType = objectClass.getComponentType(); | |
if (!luaState.isJavaObject(3, componentType)) { | |
throw new LuaRuntimeException( | |
String.format( | |
"attempt to write array of %s at index %d with %s value", | |
componentType.getCanonicalName(), 3, | |
luaState.typeName(3))); | |
} | |
Object value = luaState.toJavaObject(3, componentType); | |
Array.set(object, index - 1, value); | |
return 0; | |
} | |
// Handle objects | |
Map<String, Accessor> objectAccessors = getObjectAccessors(object); | |
String key = luaState.toString(2); | |
if (key == null) { | |
throw new LuaRuntimeException(String.format( | |
"attempt to write class %s with %s accessor", object | |
.getClass().getCanonicalName(), luaState | |
.typeName(2))); | |
} | |
Accessor accessor = objectAccessors.get(key); | |
if (accessor == null) { | |
throw new LuaRuntimeException( | |
String.format( | |
"attempt to write class %s with accessor '%s' (undefined)", | |
objectClass.getCanonicalName(), key)); | |
} | |
accessor.write(luaState, object); | |
return 0; | |
} | |
} | |
/** | |
* <code>__len</code> metamethod implementation. | |
*/ | |
private class Length implements JavaFunction { | |
@Override | |
public int invoke(LuaState luaState) { | |
Object object = luaState.toJavaObject(1, Object.class); | |
if (object.getClass().isArray()) { | |
luaState.pushInteger(Array.getLength(object)); | |
return 1; | |
} | |
luaState.pushInteger(0); | |
return 1; | |
} | |
} | |
/** | |
* <code>__eq</code> metamethod implementation. | |
*/ | |
private class Equal implements JavaFunction { | |
@Override | |
public int invoke(LuaState luaState) { | |
Object object1 = luaState.toJavaObject(1, Object.class); | |
Object object2 = luaState.toJavaObject(2, Object.class); | |
luaState.pushBoolean(object1 == object2 || object1 != null | |
&& object1.equals(object2)); | |
return 1; | |
} | |
} | |
/** | |
* <code>__lt</code> metamethod implementation. | |
*/ | |
private class LessThan implements JavaFunction { | |
@SuppressWarnings("unchecked") | |
@Override | |
public int invoke(LuaState luaState) { | |
if (!luaState.isJavaObject(1, Comparable.class)) { | |
throw new LuaRuntimeException(String.format( | |
"class %s does not implement Comparable", | |
luaState.typeName(1))); | |
} | |
Comparable<Object> comparable = luaState.toJavaObject(1, | |
Comparable.class); | |
Object object = luaState.toJavaObject(2, Object.class); | |
luaState.pushBoolean(comparable.compareTo(object) < 0); | |
return 1; | |
} | |
} | |
/** | |
* <code>__le</code> metamethod implementation. | |
*/ | |
private class LessThanOrEqual implements JavaFunction { | |
@SuppressWarnings("unchecked") | |
@Override | |
public int invoke(LuaState luaState) { | |
if (!luaState.isJavaObject(1, Comparable.class)) { | |
throw new LuaRuntimeException(String.format( | |
"class %s does not implement Comparable", | |
luaState.typeName(1))); | |
} | |
Comparable<Object> comparable = luaState.toJavaObject(1, | |
Comparable.class); | |
Object object = luaState.toJavaObject(2, Object.class); | |
luaState.pushBoolean(comparable.compareTo(object) <= 0); | |
return 1; | |
} | |
} | |
/** | |
* <code>__tostring</code> metamethod implementation. | |
*/ | |
private class ToString implements JavaFunction { | |
@Override | |
public int invoke(LuaState luaState) { | |
Object object = luaState.toJavaObject(1, Object.class); | |
luaState.pushString(object != null ? object.toString() : "null"); | |
return 1; | |
} | |
} | |
/** | |
* 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 an iterator for accessors. | |
*/ | |
private class AccessorPairs implements JavaFunction { | |
// -- State | |
private Class<?> accessorClass; | |
// -- Construction | |
/** | |
* Creates a new instance. | |
*/ | |
public AccessorPairs(Class<?> accessorClass) { | |
this.accessorClass = accessorClass; | |
} | |
// -- JavaFunction methods | |
@Override | |
public int invoke(LuaState luaState) { | |
// Get object | |
Object object = luaState.toJavaObject(1, Object.class); | |
Class<?> objectClass = getObjectClass(object); | |
// Create iterator | |
Map<String, Accessor> objectAccessors = getObjectAccessors(object); | |
Iterator<Entry<String, Accessor>> iterator = objectAccessors | |
.entrySet().iterator(); | |
luaState.pushJavaObject(new AccessorNext(iterator, | |
objectClass == object)); | |
luaState.pushJavaObject(object); | |
luaState.pushNil(); | |
return 3; | |
} | |
// -- Member types | |
/** | |
* Provides the next function for iterating accessors. | |
*/ | |
private class AccessorNext implements JavaFunction { | |
// -- State | |
private Iterator<Entry<String, Accessor>> iterator; | |
private boolean isStatic; | |
// -- Construction | |
/** | |
* Creates a new instance. | |
*/ | |
public AccessorNext(Iterator<Entry<String, Accessor>> iterator, | |
boolean isStatic) { | |
this.iterator = iterator; | |
this.isStatic = isStatic; | |
} | |
// -- JavaFunction methods | |
@Override | |
public int invoke(LuaState luaState) { | |
while (iterator.hasNext()) { | |
Entry<String, Accessor> entry = iterator.next(); | |
Accessor accessor = entry.getValue(); | |
// Filter by accessor class | |
if (accessor.getClass() != accessorClass) { | |
continue; | |
} | |
// Filter by non-static, static | |
if (isStatic) { | |
if (!accessor.isStatic()) { | |
continue; | |
} | |
} else { | |
if (!accessor.isNotStatic()) { | |
continue; | |
} | |
} | |
// Push match | |
luaState.pushString(entry.getKey()); | |
Object object = luaState.toJavaObject(1, Object.class); | |
accessor.read(luaState, object); | |
return 2; | |
} | |
// End iteration | |
return 0; | |
} | |
} | |
} | |
/** | |
* Provides access to class or object members. | |
*/ | |
private interface Accessor { | |
/** | |
* Reads the object member. | |
*/ | |
void read(LuaState luaState, Object object); | |
/** | |
* Writes the object member. | |
*/ | |
void write(LuaState luaState, Object object); | |
/** | |
* Returns whether this accessor is applicable in a non-static context. | |
*/ | |
boolean isNotStatic(); | |
/** | |
* Returns whether this accessor is applicable in a static context. | |
*/ | |
boolean isStatic(); | |
} | |
/** | |
* Provides field access. | |
*/ | |
private class FieldAccessor implements Accessor { | |
// -- State | |
private Field field; | |
// -- Construction | |
/** | |
* Creates a new instance. | |
*/ | |
public FieldAccessor(Field field) { | |
this.field = field; | |
} | |
// -- Accessor methods | |
@Override | |
public void read(LuaState luaState, Object object) { | |
try { | |
Class<?> objectClass = getObjectClass(object); | |
if (objectClass == object) { | |
object = null; | |
} | |
luaState.pushJavaObject(field.get(object)); | |
} catch (IllegalArgumentException e) { | |
throw new RuntimeException(e); | |
} catch (IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
@Override | |
public void write(LuaState luaState, Object object) { | |
try { | |
Class<?> objectClass = getObjectClass(object); | |
if (objectClass == object) { | |
object = null; | |
} | |
Object value = luaState.checkJavaObject(-1, field.getType()); | |
field.set(object, value); | |
} catch (IllegalArgumentException e) { | |
throw new RuntimeException(e); | |
} catch (IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
@Override | |
public boolean isNotStatic() { | |
return !Modifier.isStatic(field.getModifiers()); | |
} | |
@Override | |
public boolean isStatic() { | |
return Modifier.isStatic(field.getModifiers()); | |
} | |
} | |
/** | |
* Provides invocable access. | |
*/ | |
private class InvocableAccessor implements Accessor, JavaFunction { | |
// -- State | |
private Class<?> clazz; | |
private List<Invocable> invocables; | |
// -- Construction | |
/** | |
* Creates a new instance. | |
*/ | |
public InvocableAccessor(Class<?> clazz, | |
Collection<Invocable> invocables) { | |
this.clazz = clazz; | |
this.invocables = new ArrayList<Invocable>(invocables); | |
} | |
// -- Properties | |
/** | |
* Returns the name of the invocable. | |
*/ | |
public String getName() { | |
return invocables.get(0).getName(); | |
} | |
/** | |
* Returns what this invocable accessor is for. | |
*/ | |
public String getWhat() { | |
return invocables.get(0).getWhat(); | |
} | |
// -- Accessor methods | |
@Override | |
public void read(LuaState luaState, Object object) { | |
Class<?> objectClass = getObjectClass(object); | |
if (objectClass == object) { | |
object = null; | |
} | |
luaState.pushJavaFunction(this); | |
} | |
@Override | |
public void write(LuaState luaState, Object object) { | |
Class<?> objectClass = getObjectClass(object); | |
throw new LuaRuntimeException(String.format( | |
"attempt to write class %s with accessor '%s' (a %s)", | |
objectClass.getCanonicalName(), getName(), getWhat())); | |
} | |
@Override | |
public boolean isNotStatic() { | |
for (Invocable invocable : invocables) { | |
if (!Modifier.isStatic(invocable.getModifiers())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
@Override | |
public boolean isStatic() { | |
for (Invocable invocable : invocables) { | |
if (Modifier.isStatic(invocable.getModifiers())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
// -- JavaFunction methods | |
@Override | |
public int invoke(LuaState luaState) { | |
// Argument sanity checks | |
Object object = luaState.checkJavaObject(1, Object.class); | |
Class<?> objectClass = getObjectClass(object); | |
luaState.checkArg(1, clazz.isAssignableFrom(objectClass), String | |
.format("class %s is not a subclass of %s", | |
objectClass.getCanonicalName(), | |
clazz.getCanonicalName())); | |
if (objectClass == object) { | |
object = null; | |
} | |
// Invocable dispatch | |
LuaCallSignature luaCallSignature = getLuaCallSignature(luaState); | |
Invocable invocable; | |
invocableDispatchLock.readLock().lock(); | |
try { | |
invocable = invocableDispatches.get(luaCallSignature); | |
} finally { | |
invocableDispatchLock.readLock().unlock(); | |
} | |
if (invocable == null) { | |
invocable = dispatchInvocable(luaState, object == null); | |
invocableDispatchLock.writeLock().lock(); | |
try { | |
if (!invocableDispatches.containsKey(luaCallSignature)) { | |
invocableDispatches.put(luaCallSignature, invocable); | |
} else { | |
invocable = invocableDispatches.get(luaCallSignature); | |
} | |
} finally { | |
invocableDispatchLock.writeLock().unlock(); | |
} | |
} | |
// Prepare arguments | |
int argCount = luaState.getTop() - 1; | |
int parameterCount = invocable.getParameterCount(); | |
Object[] arguments = new Object[parameterCount]; | |
if (invocable.isVarArgs()) { | |
for (int i = 0; i < parameterCount - 1; i++) { | |
arguments[i] = luaState.toJavaObject(i + 2, | |
invocable.getParameterType(i)); | |
} | |
arguments[parameterCount - 1] = Array.newInstance( | |
invocable.getParameterType(parameterCount - 1), | |
argCount - (parameterCount - 1)); | |
for (int i = parameterCount - 1; i < argCount; i++) { | |
Array.set( | |
arguments[parameterCount - 1], | |
i - (parameterCount - 1), | |
luaState.toJavaObject(i + 2, | |
invocable.getParameterType(i))); | |
} | |
} else { | |
for (int i = 0; i < parameterCount; i++) { | |
arguments[i] = luaState.toJavaObject(i + 2, | |
invocable.getParameterType(i)); | |
} | |
} | |
// Invoke | |
Object result; | |
try { | |
result = invocable.invoke(object, arguments); | |
} catch (InstantiationException e) { | |
throw new RuntimeException(e); | |
} catch (IllegalArgumentException e) { | |
throw new RuntimeException(e); | |
} catch (IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} catch (InvocationTargetException e) { | |
throw new RuntimeException(e.getTargetException()); | |
} | |
// Return | |
if (invocable.getReturnType() != Void.TYPE) { | |
if (invocable.isRawReturn()) { | |
luaState.pushJavaObjectRaw(result); | |
} else { | |
luaState.pushJavaObject(result); | |
} | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
// -- Private methods | |
/** | |
* Creates a Lua call signature. | |
*/ | |
private LuaCallSignature getLuaCallSignature(LuaState luaState) { | |
int argCount = luaState.getTop() - 1; | |
Object[] types = new Object[argCount]; | |
for (int i = 0; i < argCount; i++) { | |
LuaType type = luaState.type(i + 2); | |
switch (type) { | |
case FUNCTION: | |
types[i] = luaState.isJavaFunction(i + 2) ? JAVA_FUNCTION_TYPE | |
: LuaType.FUNCTION; | |
break; | |
case USERDATA: | |
if (luaState.isJavaObjectRaw(i + 2)) { | |
Object object = luaState.toJavaObjectRaw(i + 2); | |
if (object instanceof TypedJavaObject) { | |
types[i] = ((TypedJavaObject) object).getType(); | |
} else { | |
types[i] = object.getClass(); | |
} | |
} else { | |
types[i] = LuaType.USERDATA; | |
} | |
break; | |
default: | |
types[i] = type; | |
} | |
} | |
return new LuaCallSignature(clazz, getName(), types); | |
} | |
/** | |
* Dispatches an invocable. | |
*/ | |
private Invocable dispatchInvocable(LuaState luaState, | |
boolean staticDispatch) { | |
// Begin with all candidates | |
Set<Invocable> candidates = new HashSet<Invocable>(invocables); | |
// Eliminate methods with an invalid static modifier | |
for (Iterator<Invocable> i = candidates.iterator(); i.hasNext();) { | |
Invocable invocable = i.next(); | |
if (Modifier.isStatic(invocable.getModifiers()) != staticDispatch) { | |
i.remove(); | |
} | |
} | |
// Eliminate methods with an invalid parameter count | |
int argCount = luaState.getTop() - 1; | |
for (Iterator<Invocable> i = candidates.iterator(); i.hasNext();) { | |
Invocable invocable = i.next(); | |
if (invocable.isVarArgs()) { | |
if (argCount < invocable.getParameterCount() - 1) { | |
i.remove(); | |
} | |
} else { | |
if (argCount != invocable.getParameterCount()) { | |
i.remove(); | |
} | |
} | |
} | |
// Eliminate methods that are not applicable | |
Converter converter = luaState.getConverter(); | |
outer: for (Iterator<Invocable> i = candidates.iterator(); i | |
.hasNext();) { | |
Invocable invocable = i.next(); | |
for (int j = 0; j < argCount; j++) { | |
int distance = converter.getTypeDistance(luaState, j + 2, | |
invocable.getParameterType(j)); | |
if (distance == Integer.MAX_VALUE) { | |
i.remove(); | |
continue outer; | |
} | |
} | |
} | |
// Eliminate variable arguments methods in the presence of fix | |
// arguments methods | |
boolean haveFixArgs = false; | |
boolean haveVarArgs = false; | |
for (Invocable invocable : candidates) { | |
haveFixArgs = haveFixArgs || !invocable.isVarArgs(); | |
haveVarArgs = haveVarArgs || invocable.isVarArgs(); | |
} | |
if (haveVarArgs && haveFixArgs) { | |
for (Iterator<Invocable> i = candidates.iterator(); i.hasNext();) { | |
Invocable invocable = i.next(); | |
if (invocable.isVarArgs()) { | |
i.remove(); | |
} | |
} | |
} | |
// Eliminate methods that are not closest | |
outer: for (Iterator<Invocable> i = candidates.iterator(); i | |
.hasNext();) { | |
Invocable invocable = i.next(); | |
inner: for (Invocable other : candidates) { | |
if (other == invocable) { | |
continue; | |
} | |
int parameterCount = Math.min( | |
argCount, | |
Math.max(invocable.getParameterCount(), | |
other.getParameterCount())); | |
boolean delta = false; | |
for (int j = 0; j < parameterCount; j++) { | |
int distance = converter.getTypeDistance(luaState, | |
j + 2, invocable.getParameterType(j)); | |
int otherDistance = converter.getTypeDistance(luaState, | |
j + 2, other.getParameterType(j)); | |
if (otherDistance > distance) { | |
// Other is not closer | |
continue inner; | |
} | |
delta = delta || distance != otherDistance; | |
} | |
// If there is no delta, other is not closer | |
if (!delta) { | |
continue; | |
} | |
// Other is closer | |
i.remove(); | |
continue outer; | |
} | |
} | |
// Eliminate methods that are not most precise | |
outer: for (Iterator<Invocable> i = candidates.iterator(); i | |
.hasNext();) { | |
Invocable invocable = i.next(); | |
inner: for (Invocable other : candidates) { | |
if (other == invocable) { | |
continue; | |
} | |
int parameterCount = Math.min( | |
argCount, | |
Math.max(invocable.getParameterCount(), | |
other.getParameterCount())); | |
boolean delta = false; | |
for (int j = 0; j < parameterCount; j++) { | |
Class<?> type = invocable.getParameterType(j); | |
Class<?> otherType = other.getParameterType(j); | |
if (!type.isAssignableFrom(otherType)) { | |
// Other is not more specific | |
continue inner; | |
} | |
delta = delta || type != otherType; | |
} | |
// If there is no delta, other is not more specific | |
if (!delta) { | |
continue; | |
} | |
// Other is more specific | |
i.remove(); | |
continue outer; | |
} | |
} | |
// Handle outcomes | |
if (candidates.isEmpty()) { | |
throw getSignatureMismatchException(luaState); | |
} | |
if (candidates.size() > 1) { | |
throw getSignatureAmbivalenceException(luaState, candidates); | |
} | |
// Return | |
return candidates.iterator().next(); | |
} | |
/** | |
* Returns a Lua runtime exception indicating that no matching invocable | |
* has been found. | |
*/ | |
private LuaRuntimeException getSignatureMismatchException( | |
LuaState luaState) { | |
return new LuaRuntimeException(String.format( | |
"no %s of class %s matches '%s(%s)'", getWhat(), | |
clazz.getCanonicalName(), getName(), | |
getLuaSignatureString(luaState))); | |
} | |
/** | |
* Returns a Lua runtime exception indicating that an invocable is | |
* ambivalent. | |
*/ | |
private LuaRuntimeException getSignatureAmbivalenceException( | |
LuaState luaState, Set<Invocable> candidates) { | |
StringBuffer sb = new StringBuffer(); | |
sb.append(String.format( | |
"%s '%s(%s)' on class %s is ambivalent among ", getWhat(), | |
getName(), getLuaSignatureString(luaState), | |
clazz.getCanonicalName())); | |
boolean first = true; | |
for (Invocable invocable : candidates) { | |
if (first) { | |
first = false; | |
} else { | |
sb.append(", "); | |
} | |
sb.append(String.format("'%s(%s)'", getName(), | |
getJavaSignatureString(invocable.getParameterTypes()))); | |
} | |
return new LuaRuntimeException(sb.toString()); | |
} | |
/** | |
* Returns a Lua value signature string for diagnostic messages. | |
*/ | |
private String getLuaSignatureString(LuaState luaState) { | |
int argCount = luaState.getTop() - 1; | |
StringBuffer sb = new StringBuffer(); | |
for (int i = 0; i < argCount; i++) { | |
if (i > 0) { | |
sb.append(", "); | |
} | |
sb.append(luaState.typeName(i + 2)); | |
} | |
return sb.toString(); | |
} | |
/** | |
* Returns a Java type signature string for diagnostic messages. | |
*/ | |
private String getJavaSignatureString(Class<?>[] types) { | |
StringBuffer sb = new StringBuffer(); | |
for (int i = 0; i < types.length; i++) { | |
if (i > 0) { | |
sb.append(", "); | |
} | |
sb.append(types[i].getCanonicalName()); | |
} | |
return sb.toString(); | |
} | |
} | |
/** | |
* Provides property access. | |
*/ | |
private class PropertyAccessor implements Accessor { | |
// -- State | |
private Class<?> clazz; | |
private PropertyDescriptor propertyDescriptor; | |
// -- Construction | |
/** | |
* Creates a new instance. | |
*/ | |
public PropertyAccessor(Class<?> clazz, | |
PropertyDescriptor propertyDescriptor) { | |
this.clazz = clazz; | |
this.propertyDescriptor = propertyDescriptor; | |
} | |
// -- Accessor methods | |
@Override | |
public void read(LuaState luaState, Object object) { | |
if (propertyDescriptor.getReadMethod() == null) { | |
throw new LuaRuntimeException( | |
String.format( | |
"attempt to read class %s with accessor '%s' (a write-only property)", | |
clazz.getCanonicalName(), | |
propertyDescriptor.getName())); | |
} | |
try { | |
luaState.pushJavaObject(propertyDescriptor.getReadMethod() | |
.invoke(object, EMPTY_ARGUMENTS)); | |
} catch (IllegalArgumentException e) { | |
throw new RuntimeException(e); | |
} catch (IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} catch (InvocationTargetException e) { | |
throw new RuntimeException(e.getTargetException()); | |
} | |
} | |
@Override | |
public void write(LuaState luaState, Object object) { | |
if (propertyDescriptor.getWriteMethod() == null) { | |
throw new LuaRuntimeException( | |
String.format( | |
"attempt to write class %s with acessor '%s' (a read-only property)", | |
clazz.getCanonicalName(), | |
propertyDescriptor.getName())); | |
} | |
try { | |
Object value = luaState.checkJavaObject(-1, | |
propertyDescriptor.getPropertyType()); | |
propertyDescriptor.getWriteMethod().invoke(object, value); | |
} catch (IllegalArgumentException e) { | |
throw new RuntimeException(e); | |
} catch (IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} catch (InvocationTargetException e) { | |
throw new RuntimeException(e.getTargetException()); | |
} | |
luaState.pop(1); | |
} | |
@Override | |
public boolean isNotStatic() { | |
return true; | |
} | |
@Override | |
public boolean isStatic() { | |
return false; | |
} | |
} | |
/** | |
* Virtual superinterface for methods and constructors. | |
*/ | |
private interface Invocable { | |
/** | |
* Returns what this invocable is, for use in diagnostic messages. | |
*/ | |
public String getWhat(); | |
/** | |
* Returns the declaring class of this invocable. | |
*/ | |
public Class<?> getDeclaringClass(); | |
/** | |
* Returns the modifiers of this invocable. | |
*/ | |
public int getModifiers(); | |
/** | |
* Returns the name of this invocable. | |
*/ | |
public String getName(); | |
/** | |
* Returns the return type of this invocable. | |
*/ | |
public Class<?> getReturnType(); | |
/** | |
* Returns whether this invocable has a return value that must be pushed | |
* raw. | |
*/ | |
public boolean isRawReturn(); | |
/** | |
* Returns the number of parameters. | |
*/ | |
public int getParameterCount(); | |
/** | |
* Returns the parameter types of this invocable. | |
*/ | |
public Class<?>[] getParameterTypes(); | |
/** | |
* Returns a parameter type, flattening variable arguments. | |
*/ | |
public Class<?> getParameterType(int index); | |
/** | |
* Returns whether this invocable has a variable number of arguments. | |
*/ | |
public boolean isVarArgs(); | |
/** | |
* Invokes this invocable. | |
*/ | |
public Object invoke(Object obj, Object... args) | |
throws InstantiationException, IllegalAccessException, | |
IllegalArgumentException, InvocationTargetException; | |
} | |
/** | |
* Invocable method. | |
*/ | |
private static class InvocableMethod implements Invocable { | |
private Method method; | |
private Class<?>[] parameterTypes; | |
/** | |
* Creates a new instance. | |
*/ | |
public InvocableMethod(Method method) { | |
this.method = method; | |
this.parameterTypes = method.getParameterTypes(); | |
} | |
@Override | |
public String getWhat() { | |
return "method"; | |
} | |
@Override | |
public Class<?> getDeclaringClass() { | |
return method.getDeclaringClass(); | |
} | |
@Override | |
public int getModifiers() { | |
return method.getModifiers(); | |
} | |
@Override | |
public String getName() { | |
return method.getName(); | |
} | |
@Override | |
public Class<?> getReturnType() { | |
return method.getReturnType(); | |
} | |
@Override | |
public boolean isRawReturn() { | |
return false; | |
} | |
@Override | |
public int getParameterCount() { | |
return parameterTypes.length; | |
} | |
@Override | |
public Class<?>[] getParameterTypes() { | |
return parameterTypes; | |
} | |
@Override | |
public Class<?> getParameterType(int index) { | |
if (method.isVarArgs() && index >= parameterTypes.length - 1) { | |
return parameterTypes[parameterTypes.length - 1] | |
.getComponentType(); | |
} else { | |
return parameterTypes[index]; | |
} | |
} | |
@Override | |
public boolean isVarArgs() { | |
return method.isVarArgs(); | |
} | |
@Override | |
public Object invoke(Object obj, Object... args) | |
throws IllegalAccessException, IllegalArgumentException, | |
InvocationTargetException { | |
return method.invoke(obj, args); | |
} | |
@Override | |
public String toString() { | |
return method.toString(); | |
} | |
} | |
/** | |
* Invocable constructor. | |
*/ | |
private static class InvocableConstructor implements Invocable { | |
// -- State | |
private Constructor<?> constructor; | |
private Class<?>[] parameterTypes; | |
/** | |
* Creates a new instance. | |
*/ | |
public InvocableConstructor(Constructor<?> constructor) { | |
this.constructor = constructor; | |
this.parameterTypes = constructor.getParameterTypes(); | |
} | |
@Override | |
public String getWhat() { | |
return "constructor"; | |
} | |
@Override | |
public Class<?> getDeclaringClass() { | |
return constructor.getDeclaringClass(); | |
} | |
@Override | |
public int getModifiers() { | |
return constructor.getModifiers() | Modifier.STATIC; | |
} | |
@Override | |
public String getName() { | |
return "new"; | |
} | |
@Override | |
public Class<?> getReturnType() { | |
return constructor.getDeclaringClass(); | |
} | |
@Override | |
public boolean isRawReturn() { | |
return false; | |
} | |
@Override | |
public int getParameterCount() { | |
return parameterTypes.length; | |
} | |
@Override | |
public Class<?>[] getParameterTypes() { | |
return parameterTypes; | |
} | |
@Override | |
public Class<?> getParameterType(int index) { | |
if (constructor.isVarArgs() && index >= parameterTypes.length - 1) { | |
return parameterTypes[parameterTypes.length - 1] | |
.getComponentType(); | |
} else { | |
return parameterTypes[index]; | |
} | |
} | |
@Override | |
public boolean isVarArgs() { | |
return constructor.isVarArgs(); | |
} | |
@Override | |
public Object invoke(Object obj, Object... args) | |
throws InstantiationException, IllegalAccessException, | |
IllegalArgumentException, InvocationTargetException { | |
return constructor.newInstance(args); | |
} | |
@Override | |
public String toString() { | |
return constructor.toString(); | |
} | |
} | |
/** | |
* Invocable proxy. | |
*/ | |
private static class InvocableProxy implements Invocable { | |
// -- Static | |
private static final Class<?>[] PARAMETER_TYPES = new Class<?>[] { LuaValueProxy.class }; | |
// -- State | |
private Class<?> interfaze; | |
/** | |
* Creates a new instance. | |
*/ | |
public InvocableProxy(Class<?> interfaze) { | |
this.interfaze = interfaze; | |
} | |
@Override | |
public String getWhat() { | |
return "proxy"; | |
} | |
@Override | |
public Class<?> getDeclaringClass() { | |
return interfaze; | |
} | |
@Override | |
public int getModifiers() { | |
return interfaze.getModifiers() | Modifier.STATIC; | |
} | |
@Override | |
public String getName() { | |
return "new"; | |
} | |
@Override | |
public Class<?> getReturnType() { | |
return interfaze; | |
} | |
@Override | |
public boolean isRawReturn() { | |
return true; | |
} | |
@Override | |
public int getParameterCount() { | |
return 1; | |
} | |
@Override | |
public Class<?>[] getParameterTypes() { | |
return PARAMETER_TYPES; | |
} | |
@Override | |
public Class<?> getParameterType(int index) { | |
return PARAMETER_TYPES[0]; | |
} | |
@Override | |
public boolean isVarArgs() { | |
return false; | |
} | |
@Override | |
public Object invoke(Object obj, Object... args) | |
throws InstantiationException, IllegalAccessException, | |
IllegalArgumentException, InvocationTargetException { | |
LuaValueProxy luaValueProxy = (LuaValueProxy) args[0]; | |
luaValueProxy.pushValue(); | |
Object proxy = luaValueProxy.getLuaState().getProxy(-1, interfaze); | |
luaValueProxy.getLuaState().pop(1); | |
return proxy; | |
} | |
@Override | |
public String toString() { | |
return interfaze.toString(); | |
} | |
} | |
/** | |
* Lua call signature. | |
*/ | |
private static class LuaCallSignature { | |
// -- State | |
private Class<?> clazz; | |
private String invocableName; | |
private Object[] types; | |
private int hashCode; | |
// -- Construction | |
/** | |
* Creates a new instance. | |
*/ | |
public LuaCallSignature(Class<?> clazz, String invocableName, | |
Object[] types) { | |
this.clazz = clazz; | |
this.invocableName = invocableName; | |
this.types = types; | |
hashCode = clazz.hashCode(); | |
hashCode = hashCode * 65599 + invocableName.hashCode(); | |
for (int i = 0; i < types.length; i++) { | |
hashCode = hashCode * 65599 + types[i].hashCode(); | |
} | |
} | |
@Override | |
public int hashCode() { | |
return hashCode; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (obj == this) { | |
return true; | |
} | |
if (!(obj instanceof LuaCallSignature)) { | |
return false; | |
} | |
LuaCallSignature other = (LuaCallSignature) obj; | |
if (clazz != other.clazz | |
|| !invocableName.equals(other.invocableName) | |
|| types.length != other.types.length) { | |
return false; | |
} | |
for (int i = 0; i < types.length; i++) { | |
if (types[i] != other.types[i]) { | |
return false; | |
} | |
} | |
return true; | |
} | |
@Override | |
public String toString() { | |
return clazz.getCanonicalName() + ": " + invocableName + "(" | |
+ Arrays.asList(types) + ")"; | |
} | |
} | |
} |