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