|  | /* | 
|  | * $Id: DefaultJavaReflector.java 173 2013-07-28 20:46:07Z 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) { | 
|  | // Process public superclasses only | 
|  | if (Modifier.isPublic(superclass.getModifiers())) { | 
|  | // 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(), | 
|  | 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) + ")"; | 
|  | } | 
|  | } | 
|  | } |