Mercurial Hosting > luan
view src/luan/LuanJavaFunction.java @ 1830:d72a52232f79
mail - minor
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Tue, 24 Sep 2024 17:17:57 -0600 |
parents | b82767112d8e |
children |
line wrap: on
line source
package luan; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.Set; import java.util.Arrays; import java.util.Collection; public final class LuanJavaFunction extends LuanFunction { private final JavaMethod method; private Object obj; private final RtnConverter rtnConverter; private final boolean takesLuan; private final ArgConverter[] argConverters; private final Class varArgCls; public LuanJavaFunction(Method method,Object obj) { this( JavaMethod.of(method), obj ); } public LuanJavaFunction(Constructor constr,Object obj) { this( JavaMethod.of(constr), obj ); } private LuanJavaFunction(JavaMethod method,Object obj) { this.method = method; this.rtnConverter = getRtnConverter(method); this.takesLuan = takesLuan(method); this.argConverters = getArgConverters(takesLuan,method); if( method.isVarArgs() ) { Class[] paramTypes = method.getParameterTypes(); this.varArgCls = paramTypes[paramTypes.length-1].getComponentType(); } else { this.varArgCls = null; } this.obj = obj; } public LuanJavaFunction(LuanJavaFunction fn,Object obj) { this.method = fn.method; this.rtnConverter = fn.rtnConverter; this.takesLuan = fn.takesLuan; this.argConverters = fn.argConverters; this.varArgCls = fn.varArgCls; this.obj = obj; } @Override public String toString() { return "java-function: " + method; } public int getParameterCount() { return argConverters.length; } public boolean isVarArgs() { return method.isVarArgs(); } @Override public Object call(Luan luan,Object[] args) throws LuanException { try { args = fixArgs(luan,args); return doCall(args); } catch(IllegalArgumentException e) { checkArgs(args); throw e; } } public Object rawCall(Luan luan,Object[] args) throws LuanException { args = fixArgs(luan,args); return doCall(args); } private Object doCall(Object[] args) throws LuanException { Object rtn; try { rtn = method.invoke(obj,args); } catch(IllegalAccessException e) { throw new RuntimeException("method = "+method,e); } catch(InvocationTargetException e) { Throwable cause = e.getCause(); if( cause instanceof Error ) throw (Error)cause; if( cause instanceof LuanException ) throw (LuanException)cause; throw new LuanException(cause); } catch(InstantiationException e) { throw new RuntimeException(e); } return rtnConverter.convert(rtn); } private static final Map primitiveMap = new HashMap(); static { primitiveMap.put(Boolean.TYPE,Boolean.class); primitiveMap.put(Character.TYPE,Character.class); primitiveMap.put(Byte.TYPE,Byte.class); primitiveMap.put(Short.TYPE,Short.class); primitiveMap.put(Integer.TYPE,Integer.class); primitiveMap.put(Long.TYPE,Long.class); primitiveMap.put(Float.TYPE,Float.class); primitiveMap.put(Double.TYPE,Double.class); primitiveMap.put(Void.TYPE,Void.class); } private void checkArgs(Object[] args) throws LuanException { Class[] a = method.getParameterTypes(); int start = takesLuan ? 1 : 0; for( int i=start; i<a.length; i++ ) { Class paramType = a[i]; Class type = paramType; if( type.isPrimitive() ) type = (Class)primitiveMap.get(type); Object arg = args[i]; if( !type.isInstance(arg) ) { String expected; if( i==a.length-1 && method.isVarArgs() ) expected = fixType(paramType.getComponentType().getSimpleName())+"..."; else expected = fixType(paramType.getSimpleName()); if( arg==null ) { if( paramType.isPrimitive() ) throw new LuanException("bad argument #"+(i+1-start)+" ("+expected+" expected, got nil)"); } else { String got; if( arg instanceof LuanFunction ) { got = "function"; } else { got = fixType(arg.getClass().getSimpleName()); if( got.equals("") ) got = arg.getClass().toString(); } throw new LuanException("bad argument #"+(i+1-start)+" ("+expected+" expected, got "+got+")"); } } } } private static String fixType(String type) { if( type.equals("byte[]") ) return "binary"; if( type.equals("Double") ) return "number"; if( type.equals("LuanTable") ) return "table"; if( type.equals("Boolean") ) return "boolean"; if( type.equals("String") ) return "string"; if( type.equals("LuanClosure") ) return "function"; if( type.equals("LuanJavaFunction") ) return "function"; return type; } private Object[] fixArgs(Luan luan,Object[] args) throws LuanException { int n = argConverters.length; Object[] rtn; int start = 0; if( !takesLuan && varArgCls==null && args.length == n ) { rtn = args; } else { if( takesLuan ) n++; rtn = new Object[n]; if( takesLuan ) { rtn[start++] = luan; } n = argConverters.length; if( varArgCls == null ) { for( int i=n; i<args.length; i++ ) { if( args[i] != null ) throw new LuanException("too many arguments"); } } else { n--; if( args.length < argConverters.length ) { rtn[rtn.length-1] = Array.newInstance(varArgCls,0); } else { int len = args.length - n; Object varArgs = Array.newInstance(varArgCls,len); ArgConverter ac = argConverters[n]; for( int i=0; i<len; i++ ) { Array.set( varArgs, i, ac.convert(args[n+i]) ); } rtn[rtn.length-1] = varArgs; } } System.arraycopy(args,0,rtn,start,Math.min(args.length,n)); } for( int i=0; i<n; i++ ) { rtn[start+i] = argConverters[i].convert(rtn[start+i]); } return rtn; } private interface RtnConverter { public Object convert(Object obj); } private static final RtnConverter RTN_NOTHING = new RtnConverter() { @Override public Object[] convert(Object obj) { return NOTHING; } }; private static final RtnConverter RTN_SAME = new RtnConverter() { @Override public Object convert(Object obj) { return obj; } }; /* private static final RtnConverter RTN_ARRAY = new RtnConverter() { @Override public Object convert(Luan luan,Object obj) { if( obj == null ) return null; Object[] a = new Object[Array.getLength(obj)]; for( int i=0; i<a.length; i++ ) { a[i] = Array.get(obj,i); } return new LuanTable(luan,new ArrayList<Object>(Arrays.asList(a))); } }; */ private static RtnConverter getRtnConverter(JavaMethod m) { Class rtnType = m.getReturnType(); if( rtnType == Void.TYPE ) return RTN_NOTHING; /* if( !m.isLuan() && rtnType.isArray() && !rtnType.getComponentType().isPrimitive() ) { return RTN_ARRAY; } */ return RTN_SAME; } public interface ArgConverter { public Object convert(Object obj) throws LuanException; } private static final ArgConverter ARG_SAME = new ArgConverter() { @Override public Object convert(Object obj) { return obj; } @Override public String toString() { return "ARG_SAME"; } }; private static final ArgConverter ARG_DOUBLE = new ArgConverter() { @Override public Object convert(Object obj) { if( obj instanceof Double ) return obj; if( obj instanceof Number ) { Number n = (Number)obj; return n.doubleValue(); } return obj; } @Override public String toString() { return "ARG_DOUBLE"; } }; private static final ArgConverter ARG_FLOAT = new ArgConverter() { @Override public Object convert(Object obj) { if( obj instanceof Float ) return obj; if( obj instanceof Number ) { Number n = (Number)obj; return n.floatValue(); } return obj; } @Override public String toString() { return "ARG_FLOAT"; } }; private static final ArgConverter ARG_LONG = new ArgConverter() { @Override public Object convert(Object obj) { if( obj instanceof Long ) return obj; if( obj instanceof Number ) { Number n = (Number)obj; long r = n.longValue(); if( r==n.doubleValue() ) return r; } return obj; } @Override public String toString() { return "ARG_LONG"; } }; private static final ArgConverter ARG_INTEGER = new ArgConverter() { @Override public Object convert(Object obj) { if( obj instanceof Integer ) return obj; if( obj instanceof Number ) { Number n = (Number)obj; int r = n.intValue(); if( r==n.doubleValue() ) return r; } return obj; } @Override public String toString() { return "ARG_INTEGER"; } }; private static final ArgConverter ARG_SHORT = new ArgConverter() { @Override public Object convert(Object obj) { if( obj instanceof Short ) return obj; if( obj instanceof Number ) { Number n = (Number)obj; short r = n.shortValue(); if( r==n.doubleValue() ) return r; } return obj; } @Override public String toString() { return "ARG_SHORT"; } }; private static final ArgConverter ARG_BYTE = new ArgConverter() { @Override public Object convert(Object obj) { if( obj instanceof Byte ) return obj; if( obj instanceof Number ) { Number n = (Number)obj; byte r = n.byteValue(); if( r==n.doubleValue() ) return r; } return obj; } @Override public String toString() { return "ARG_BYTE"; } }; /* private static final ArgConverter ARG_TABLE = new ArgConverter() { @Override public Object convert(Luan luan,Object obj) { LuanTable tbl = luan.toTable(obj); return tbl!=null ? tbl : obj; } @Override public String toString() { return "ARG_TABLE"; } }; */ private static final ArgConverter ARG_MAP = new ArgConverter() { @Override public Object convert(Object obj) throws LuanException { if( obj instanceof LuanTable ) { LuanTable t = (LuanTable)obj; return t.asMap(); } return obj; } @Override public String toString() { return "ARG_MAP"; } }; private static final ArgConverter ARG_LIST = new ArgConverter() { @Override public Object convert(Object obj) { if( obj instanceof LuanTable ) { LuanTable t = (LuanTable)obj; if( t.isList() ) return t.asList(); } return obj; } @Override public String toString() { return "ARG_LIST"; } }; private static final ArgConverter ARG_SET = new ArgConverter() { @Override public Object convert(Object obj) throws LuanException { if( obj instanceof LuanTable ) { LuanTable t = (LuanTable)obj; if( t.isSet() ) return t.asSet(); } return obj; } @Override public String toString() { return "ARG_SET"; } }; private static final ArgConverter ARG_COLLECTION = new ArgConverter() { @Override public Object convert(Object obj) throws LuanException { if( obj instanceof LuanTable ) { LuanTable t = (LuanTable)obj; if( t.isList() ) return t.asList(); if( t.isSet() ) return t.asSet(); } return obj; } @Override public String toString() { return "ARG_COLLECTION"; } }; private static class ArgArray implements ArgConverter { private final Object[] a; ArgArray(Class cls) { a = (Object[])Array.newInstance(cls.getComponentType(),0); } @Override public Object convert(Object obj) { if( obj instanceof LuanTable ) { LuanTable t = (LuanTable)obj; if( t.isList() ) { try { return t.asList().toArray(a); } catch(ArrayStoreException e) {} } } return obj; } } private static boolean takesLuan(JavaMethod m) { Class[] paramTypes = m.getParameterTypes(); return paramTypes.length > 0 && paramTypes[0].equals(Luan.class); } private static ArgConverter[] getArgConverters(boolean takesLuan,JavaMethod m) { final boolean isVarArgs = m.isVarArgs(); Class[] paramTypes = m.getParameterTypes(); if( takesLuan ) { Class[] t = new Class[paramTypes.length-1]; System.arraycopy(paramTypes,1,t,0,t.length); paramTypes = t; } ArgConverter[] a = new ArgConverter[paramTypes.length]; for( int i=0; i<a.length; i++ ) { Class paramType = paramTypes[i]; if( isVarArgs && i == a.length-1 ) paramType = paramType.getComponentType(); a[i] = getArgConverter(paramType); } return a; } public static ArgConverter getArgConverter(Class cls) { if( cls == Double.TYPE || cls.equals(Double.class) ) return ARG_DOUBLE; if( cls == Float.TYPE || cls.equals(Float.class) ) return ARG_FLOAT; if( cls == Long.TYPE || cls.equals(Long.class) ) return ARG_LONG; if( cls == Integer.TYPE || cls.equals(Integer.class) ) return ARG_INTEGER; if( cls == Short.TYPE || cls.equals(Short.class) ) return ARG_SHORT; if( cls == Byte.TYPE || cls.equals(Byte.class) ) return ARG_BYTE; // if( cls.equals(LuanTable.class) ) // return ARG_TABLE; if( cls.equals(Map.class) ) return ARG_MAP; if( cls.equals(List.class) ) return ARG_LIST; if( cls.equals(Set.class) ) return ARG_SET; if( cls.equals(Collection.class) ) return ARG_COLLECTION; if( cls.isArray() && !cls.getComponentType().isPrimitive() ) return new ArgArray(cls); return ARG_SAME; } private static abstract class JavaMethod { abstract boolean isVarArgs(); abstract Class[] getParameterTypes(); abstract Object invoke(Object obj,Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException; abstract Class getReturnType(); abstract String getName(); static JavaMethod of(final Method m) { return new JavaMethod() { @Override boolean isVarArgs() { return m.isVarArgs(); } @Override Class[] getParameterTypes() { return m.getParameterTypes(); } @Override Object invoke(Object obj,Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { return m.invoke(obj,args); } @Override Class getReturnType() { return m.getReturnType(); } @Override public String getName() { return m.getName(); } @Override public String toString() { return m.toString(); } }; } static JavaMethod of(final Constructor c) { return new JavaMethod() { @Override boolean isVarArgs() { return c.isVarArgs(); } @Override Class[] getParameterTypes() { return c.getParameterTypes(); } @Override Object invoke(Object obj,Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { return c.newInstance(args); } @Override Class getReturnType() { return c.getDeclaringClass(); } @Override public String getName() { return c.getName(); } @Override public String toString() { return c.toString(); } }; } } }