package com.mixpanel.android.viewcrawler;
import android.view.View;
import com.mixpanel.android.util.MPLog;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/* package */ class Caller {
public Caller(Class<?> targetClass, String methodName, Object[] methodArgs, Class<?> resultType)
throws NoSuchMethodException {
mMethodName = methodName;
// TODO if this is a bitmap, we might be hogging a lot of memory here.
// We likely need a caching/loading to disk layer for bitmap-valued edits
// I'm going to kick this down the road for now.
mMethodArgs = methodArgs;
mMethodResultType = resultType;
mTargetMethod = pickMethod(targetClass);
if (null == mTargetMethod) {
throw new NoSuchMethodException("Method " + targetClass.getName() + "." + mMethodName + " doesn't exit");
}
mTargetClass = mTargetMethod.getDeclaringClass();
}
@Override
public String toString() {
return "[Caller " + mMethodName + "(" + mMethodArgs + ")" + "]";
}
public Object[] getArgs() {
return mMethodArgs;
}
public Object applyMethod(View target) {
return applyMethodWithArguments(target, mMethodArgs);
}
public Object applyMethodWithArguments(View target, Object[] arguments) {
final Class<?> klass = target.getClass();
if (mTargetClass.isAssignableFrom(klass)) {
try {
return mTargetMethod.invoke(target, arguments);
} catch (final IllegalAccessException e) {
MPLog.e(LOGTAG, "Method " + mTargetMethod.getName() + " appears not to be public", e);
} catch (final IllegalArgumentException e) {
MPLog.e(LOGTAG, "Method " + mTargetMethod.getName() + " called with arguments of the wrong type", e);
} catch (final InvocationTargetException e) {
MPLog.e(LOGTAG, "Method " + mTargetMethod.getName() + " threw an exception", e);
}
}
return null;
}
public boolean argsAreApplicable(Object[] proposedArgs) {
final Class<?>[] paramTypes = mTargetMethod.getParameterTypes();
if (proposedArgs.length != paramTypes.length) {
return false;
}
for (int i = 0; i < proposedArgs.length; i++) {
final Class<?> paramType = assignableArgType(paramTypes[i]);
if (null == proposedArgs[i]) {
if (paramType == byte.class ||
paramType == short.class ||
paramType == int.class ||
paramType == long.class ||
paramType == float.class ||
paramType == double.class ||
paramType == boolean.class ||
paramType == char.class) {
return false;
}
} else {
final Class<?> argumentType = assignableArgType(proposedArgs[i].getClass());
if (!paramType.isAssignableFrom(argumentType)) {
return false;
}
}
}
return true;
}
private static Class<?> assignableArgType(Class<?> type) {
// a.isAssignableFrom(b) only tests if b is a
// subclass of a. It does not handle the autoboxing case,
// i.e. when a is an int and b is an Integer, so we have
// to make the Object types primitive types. When the
// function is finally invoked, autoboxing will take
// care of the the cast.
if (type == Byte.class) {
type = byte.class;
} else if (type == Short.class) {
type = short.class;
} else if (type == Integer.class) {
type = int.class;
} else if (type == Long.class) {
type = long.class;
} else if (type == Float.class) {
type = float.class;
} else if (type == Double.class) {
type = double.class;
} else if (type == Boolean.class) {
type = boolean.class;
} else if (type == Character.class) {
type = char.class;
}
return type;
}
private Method pickMethod(Class<?> klass) {
final Class<?>[] argumentTypes = new Class[mMethodArgs.length];
for (int i = 0; i < mMethodArgs.length; i++) {
argumentTypes[i] = mMethodArgs[i].getClass();
}
for (final Method method : klass.getMethods()) {
final String foundName = method.getName();
final Class<?>[] params = method.getParameterTypes();
if (!foundName.equals(mMethodName) || params.length != mMethodArgs.length) {
continue;
}
final Class<?> assignType = assignableArgType(mMethodResultType);
final Class<?> resultType = assignableArgType(method.getReturnType());
if (! assignType.isAssignableFrom(resultType)) {
continue;
}
boolean assignable = true;
for (int i = 0; i < params.length && assignable; i++) {
final Class<?> argumentType = assignableArgType(argumentTypes[i]);
final Class<?> paramType = assignableArgType(params[i]);
assignable = paramType.isAssignableFrom(argumentType);
}
if (! assignable) {
continue;
}
return method;
}
return null;
}
private final String mMethodName;
private final Object[] mMethodArgs;
private final Class<?> mMethodResultType;
private final Class<?> mTargetClass;
private final Method mTargetMethod;
@SuppressWarnings("unused")
private static final String LOGTAG = "MixpanelABTest.Caller";
}