/**
* Copyright (c) Rich Hickey. All rights reserved.
* The use and distribution terms for this software are covered by the
* Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
* which can be found in the file epl-v10.html at the root of this distribution.
* By using this software in any fashion, you are agreeing to be bound by
* the terms of this license.
* You must not remove this notice, or any other, from this software.
**/
/* rich Apr 19, 2006 */
package clojure.lang;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Arrays;
public class Reflector{
public static Object invokeInstanceMethod(Object target, String methodName, Object[] args) throws Exception{
try
{
Class c = target.getClass();
List methods = getMethods(c, args.length, methodName, false);
return invokeMatchingMethod(methodName, methods, target, args);
}
catch(InvocationTargetException e)
{
if(e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else if(e.getCause() instanceof Error)
throw (Error) e.getCause();
throw e;
}
}
private static String noMethodReport(String methodName, Object target){
return "No matching method found: " + methodName
+ (target==null?"":" for " + target.getClass());
}
static Object invokeMatchingMethod(String methodName, List methods, Object target, Object[] args)
throws Exception{
Method m = null;
Object[] boxedArgs = null;
if(methods.isEmpty())
{
throw new IllegalArgumentException(noMethodReport(methodName,target));
}
else if(methods.size() == 1)
{
m = (Method) methods.get(0);
boxedArgs = boxArgs(m.getParameterTypes(), args);
}
else //overloaded w/same arity
{
Method foundm = null;
for(Iterator i = methods.iterator(); i.hasNext();)
{
m = (Method) i.next();
Class[] params = m.getParameterTypes();
if(isCongruent(params, args))
{
if(foundm == null || Compiler.subsumes(params, foundm.getParameterTypes()))
{
foundm = m;
boxedArgs = boxArgs(params, args);
}
}
}
m = foundm;
}
if(m == null)
throw new IllegalArgumentException(noMethodReport(methodName,target));
if(!Modifier.isPublic(m.getDeclaringClass().getModifiers()))
{
//public method of non-public class, try to find it in hierarchy
Method oldm = m;
m = getAsMethodOfPublicBase(m.getDeclaringClass(), m);
if(m == null)
throw new IllegalArgumentException("Can't call public method of non-public class: " +
oldm.toString());
}
try
{
return prepRet(m.invoke(target, boxedArgs));
}
catch(InvocationTargetException e)
{
if(e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else if(e.getCause() instanceof Error)
throw (Error) e.getCause();
throw e;
}
}
public static Method getAsMethodOfPublicBase(Class c, Method m){
for(Class iface : c.getInterfaces())
{
for(Method im : iface.getMethods())
{
if(im.getName().equals(m.getName())
&& Arrays.equals(m.getParameterTypes(), im.getParameterTypes()))
{
return im;
}
}
}
Class sc = c.getSuperclass();
if(sc == null)
return null;
for(Method scm : sc.getMethods())
{
if(scm.getName().equals(m.getName())
&& Arrays.equals(m.getParameterTypes(), scm.getParameterTypes())
&& Modifier.isPublic(scm.getDeclaringClass().getModifiers()))
{
return scm;
}
}
return getAsMethodOfPublicBase(sc, m);
}
public static Object invokeConstructor(Class c, Object[] args) throws Exception{
try
{
Constructor[] allctors = c.getConstructors();
ArrayList ctors = new ArrayList();
for(int i = 0; i < allctors.length; i++)
{
Constructor ctor = allctors[i];
if(ctor.getParameterTypes().length == args.length)
ctors.add(ctor);
}
if(ctors.isEmpty())
{
throw new IllegalArgumentException("No matching ctor found"
+ " for " + c);
}
else if(ctors.size() == 1)
{
Constructor ctor = (Constructor) ctors.get(0);
return ctor.newInstance(boxArgs(ctor.getParameterTypes(), args));
}
else //overloaded w/same arity
{
for(Iterator iterator = ctors.iterator(); iterator.hasNext();)
{
Constructor ctor = (Constructor) iterator.next();
Class[] params = ctor.getParameterTypes();
if(isCongruent(params, args))
{
Object[] boxedArgs = boxArgs(params, args);
return ctor.newInstance(boxedArgs);
}
}
throw new IllegalArgumentException("No matching ctor found"
+ " for " + c);
}
}
catch(InvocationTargetException e)
{
if(e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else if(e.getCause() instanceof Error)
throw (Error) e.getCause();
throw e;
}
}
public static Object invokeStaticMethodVariadic(String className, String methodName, Object... args) throws Exception{
return invokeStaticMethod(className, methodName, args);
}
public static Object invokeStaticMethod(String className, String methodName, Object[] args) throws Exception{
Class c = RT.classForName(className);
try
{
return invokeStaticMethod(c, methodName, args);
}
catch(InvocationTargetException e)
{
if(e.getCause() instanceof Exception)
throw (Exception) e.getCause();
else if(e.getCause() instanceof Error)
throw (Error) e.getCause();
throw e;
}
}
public static Object invokeStaticMethod(Class c, String methodName, Object[] args) throws Exception{
if(methodName.equals("new"))
return invokeConstructor(c, args);
List methods = getMethods(c, args.length, methodName, true);
return invokeMatchingMethod(methodName, methods, null, args);
}
public static Object getStaticField(String className, String fieldName) throws Exception{
Class c = RT.classForName(className);
return getStaticField(c, fieldName);
}
public static Object getStaticField(Class c, String fieldName) throws Exception{
// if(fieldName.equals("class"))
// return c;
Field f = getField(c, fieldName, true);
if(f != null)
{
return prepRet(f.get(null));
}
throw new IllegalArgumentException("No matching field found: " + fieldName
+ " for " + c);
}
public static Object setStaticField(String className, String fieldName, Object val) throws Exception{
Class c = RT.classForName(className);
return setStaticField(c, fieldName, val);
}
public static Object setStaticField(Class c, String fieldName, Object val) throws Exception{
Field f = getField(c, fieldName, true);
if(f != null)
{
f.set(null, boxArg(f.getType(), val));
return val;
}
throw new IllegalArgumentException("No matching field found: " + fieldName
+ " for " + c);
}
public static Object getInstanceField(Object target, String fieldName) throws Exception{
Class c = target.getClass();
Field f = getField(c, fieldName, false);
if(f != null)
{
return prepRet(f.get(target));
}
throw new IllegalArgumentException("No matching field found: " + fieldName
+ " for " + target.getClass());
}
public static Object setInstanceField(Object target, String fieldName, Object val) throws Exception{
Class c = target.getClass();
Field f = getField(c, fieldName, false);
if(f != null)
{
f.set(target, boxArg(f.getType(), val));
return val;
}
throw new IllegalArgumentException("No matching field found: " + fieldName
+ " for " + target.getClass());
}
public static Object invokeNoArgInstanceMember(Object target, String name) throws Exception{
//favor method over field
List meths = getMethods(target.getClass(), 0, name, false);
if(meths.size() > 0)
return invokeMatchingMethod(name, meths, target, RT.EMPTY_ARRAY);
else
return getInstanceField(target, name);
}
public static Object invokeInstanceMember(Object target, String name) throws Exception{
//check for field first
Class c = target.getClass();
Field f = getField(c, name, false);
if(f != null) //field get
{
return prepRet(f.get(target));
}
return invokeInstanceMethod(target, name, RT.EMPTY_ARRAY);
}
public static Object invokeInstanceMember(String name, Object target, Object arg1) throws Exception{
//check for field first
Class c = target.getClass();
Field f = getField(c, name, false);
if(f != null) //field set
{
f.set(target, boxArg(f.getType(), arg1));
return arg1;
}
return invokeInstanceMethod(target, name, new Object[]{arg1});
}
public static Object invokeInstanceMember(String name, Object target, Object... args) throws Exception{
return invokeInstanceMethod(target, name, args);
}
static public Field getField(Class c, String name, boolean getStatics){
Field[] allfields = c.getFields();
for(int i = 0; i < allfields.length; i++)
{
if(name.equals(allfields[i].getName())
&& Modifier.isStatic(allfields[i].getModifiers()) == getStatics)
return allfields[i];
}
return null;
}
static public List getMethods(Class c, int arity, String name, boolean getStatics){
Method[] allmethods = c.getMethods();
ArrayList methods = new ArrayList();
ArrayList bridgeMethods = new ArrayList();
for(int i = 0; i < allmethods.length; i++)
{
Method method = allmethods[i];
if(name.equals(method.getName())
&& Modifier.isStatic(method.getModifiers()) == getStatics
&& method.getParameterTypes().length == arity)
{
try
{
if(method.isBridge()
&& c.getMethod(method.getName(), method.getParameterTypes())
.equals(method))
bridgeMethods.add(method);
else
methods.add(method);
}
catch(NoSuchMethodException e)
{
}
}
// && (!method.isBridge()
// || (c == StringBuilder.class &&
// c.getMethod(method.getName(), method.getParameterTypes())
// .equals(method))))
// {
// methods.add(allmethods[i]);
// }
}
if(methods.isEmpty())
methods.addAll(bridgeMethods);
if(!getStatics && c.isInterface())
{
allmethods = Object.class.getMethods();
for(int i = 0; i < allmethods.length; i++)
{
if(name.equals(allmethods[i].getName())
&& Modifier.isStatic(allmethods[i].getModifiers()) == getStatics
&& allmethods[i].getParameterTypes().length == arity)
{
methods.add(allmethods[i]);
}
}
}
return methods;
}
static Object boxArg(Class paramType, Object arg){
if(!paramType.isPrimitive())
return paramType.cast(arg);
else if(paramType == boolean.class)
return Boolean.class.cast(arg);
else if(paramType == char.class)
return Character.class.cast(arg);
else if(arg instanceof Number)
{
Number n = (Number) arg;
if(paramType == int.class)
return n.intValue();
else if(paramType == float.class)
return n.floatValue();
else if(paramType == double.class)
return n.doubleValue();
else if(paramType == long.class)
return n.longValue();
else if(paramType == short.class)
return n.shortValue();
else if(paramType == byte.class)
return n.byteValue();
}
throw new IllegalArgumentException("Unexpected param type");
}
static Object[] boxArgs(Class[] params, Object[] args){
if(params.length == 0)
return null;
Object[] ret = new Object[params.length];
for(int i = 0; i < params.length; i++)
{
Object arg = args[i];
Class paramType = params[i];
ret[i] = boxArg(paramType, arg);
}
return ret;
}
static public boolean paramArgTypeMatch(Class paramType, Class argType){
if(argType == null)
return !paramType.isPrimitive();
if(paramType == argType || paramType.isAssignableFrom(argType))
return true;
if(paramType == int.class)
return argType == Integer.class;// || argType == FixNum.class;
else if(paramType == float.class)
return argType == Float.class;
else if(paramType == double.class)
return argType == Double.class;// || argType == DoubleNum.class;
else if(paramType == long.class)
return argType == Long.class;// || argType == BigNum.class;
else if(paramType == char.class)
return argType == Character.class;
else if(paramType == short.class)
return argType == Short.class;
else if(paramType == byte.class)
return argType == Byte.class;
else if(paramType == boolean.class)
return argType == Boolean.class;
return false;
}
static boolean isCongruent(Class[] params, Object[] args){
boolean ret = false;
if(args == null)
return params.length == 0;
if(params.length == args.length)
{
ret = true;
for(int i = 0; ret && i < params.length; i++)
{
Object arg = args[i];
Class argType = (arg == null) ? null : arg.getClass();
Class paramType = params[i];
ret = paramArgTypeMatch(paramType, argType);
}
}
return ret;
}
public static Object prepRet(Object x){
// if(c == boolean.class)
// return ((Boolean) x).booleanValue() ? RT.T : null;
if(x instanceof Boolean)
return ((Boolean) x)?Boolean.TRUE:Boolean.FALSE;
return x;
}
}