package de.take_weiland.mods.commons.reflect;
import de.take_weiland.mods.commons.asm.MCPNames;
import de.take_weiland.mods.commons.internal.AnnotationNull;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.invoke.MethodHandles.publicLookup;
/**
* @author diesieben07
*/
final class AccessorMemberParser {
static MethodHandle getTarget(Method method) {
try {
MethodHandle target = getTarget0(method);
Class<?>[] incomingArgs = method.getParameterTypes();
checkState(target.type().parameterCount() == incomingArgs.length, "Something went wrong, got wrong method param count");
for (int i = 0; i < incomingArgs.length; i++) {
Class<?> inArg = incomingArgs[i];
Class<?> outArg = target.type().parameterType(i);
if (inArg != outArg && !outArg.isAssignableFrom(inArg)) {
throw new IllegalAccessorException("method parameters don't match up, cannot convert " + inArg.getName() + " to " + outArg.getName());
}
}
Class<?> inRet = target.type().returnType();
Class<?> outRet = method.getReturnType();
if (inRet != outRet && !outRet.isAssignableFrom(inRet)) {
throw new IllegalAccessorException("return types don't match up, cannot convert " + inRet.getName() + " to " + outRet.getName());
}
return target;
} catch (Exception e) {
String msg = String.format(
"Failed to handle method %s in accessor interface %s. Reason: %s",
method.getName(),
method.getDeclaringClass().getName(),
Objects.toString(e.getMessage(), "Unknown"));
RuntimeException newEx = new IllegalAccessorException(msg);
Throwable cause = e instanceof IllegalAccessorException ? e.getCause() : e;
if (cause != null) {
newEx.initCause(cause);
}
throw newEx;
}
}
private static MethodHandle getTarget0(Method method) {
Construct construct = method.getAnnotation(Construct.class);
if (construct != null) {
Class<?> targetClass = maybeOverrideTarget(method.getReturnType(), method.getAnnotation(OverrideTarget.class));
checkNotPrimitive(targetClass);
Class<?>[] actualParameters = transformParameters(method, 0);
Constructor<?> constructor;
try {
constructor = targetClass.getDeclaredConstructor(actualParameters);
} catch (NoSuchMethodException e) {
throw new IllegalAccessorException("No such constructor found", e);
}
constructor.setAccessible(true);
try {
return publicLookup().unreflectConstructor(constructor);
} catch (IllegalAccessException e) {
throw new RuntimeException("impossible");
}
}
Getter getter = method.getAnnotation(Getter.class);
if (getter != null) {
Class<?> targetClass;
int numParameters = method.getParameterTypes().length;
if (numParameters == 0) {
targetClass = getter.target();
} else if (numParameters == 1) {
targetClass = method.getParameterTypes()[0];
} else {
throw new IllegalAccessorException("@Getter must have 0 or 1 parameter");
}
maybeOverrideTarget(targetClass, method.getAnnotation(OverrideTarget.class));
checkNotPrimitive(targetClass);
if (targetClass == AnnotationNull.class) {
throw new IllegalAccessorException("@Getter is missing a target");
}
Field field;
try {
field = targetClass.getDeclaredField(getter.srg() ? MCPNames.field(getter.field()) : getter.field());
} catch (NoSuchFieldException e) {
throw new IllegalAccessorException("Field target for @Getter not found", e);
}
if (Modifier.isStatic(field.getModifiers())) {
if (numParameters != 0) {
throw new IllegalAccessorException("@Getter with static field target must not take any parameters");
}
} else {
if (numParameters != 1) {
throw new IllegalAccessorException("@Getter with non-static field target must take one parameter");
}
}
field.setAccessible(true);
try {
return publicLookup().unreflectGetter(field);
} catch (IllegalAccessException e) {
throw new RuntimeException("impossible");
}
}
Setter setter = method.getAnnotation(Setter.class);
if (setter != null) {
Class<?> targetClass;
int numParameters = method.getParameterTypes().length;
if (numParameters == 1) {
targetClass = setter.target();
} else if (numParameters == 2) {
targetClass = method.getParameterTypes()[0];
} else {
throw new IllegalAccessorException("@Setter must take 1 or 2 parameters");
}
maybeOverrideTarget(targetClass, method.getAnnotation(OverrideTarget.class));
checkNotPrimitive(targetClass);
if (targetClass == AnnotationNull.class) {
throw new IllegalAccessorException("@Setter is missing a target");
}
Field field;
try {
field = targetClass.getDeclaredField(setter.srg() ? MCPNames.field(setter.field()) : setter.field());
} catch (NoSuchFieldException e) {
throw new IllegalAccessorException("Field target for @Setter not found", e);
}
if (Modifier.isStatic(field.getModifiers())) {
if (numParameters != 1) {
throw new IllegalAccessorException("@Setter with static field target must take 1 parameter");
}
} else {
if (numParameters != 2) {
throw new IllegalAccessorException("@Setter with non-static field target must take 2 parameters");
}
}
field.setAccessible(true);
try {
return publicLookup().unreflectSetter(field);
} catch (IllegalAccessException e) {
throw new RuntimeException("impossible");
}
}
Invoke invoke = method.getAnnotation(Invoke.class);
if (invoke != null) {
Class<?> targetClass = invoke.target();
int numParameters = method.getParameterTypes().length;
boolean isStatic;
if (targetClass == AnnotationNull.class) {
if (numParameters == 0) {
throw new IllegalAccessorException("@Invoke is missing a target");
} else {
targetClass = method.getParameterTypes()[0];
}
isStatic = false;
} else {
isStatic = true;
}
targetClass = maybeOverrideTarget(targetClass, method.getAnnotation(OverrideTarget.class));
checkNotPrimitive(targetClass);
Class<?>[] actualParams = transformParameters(method, isStatic ? 0 : 1);
Method targetMethod;
try {
targetMethod = targetClass.getDeclaredMethod(invoke.srg() ? MCPNames.method(invoke.method()) : invoke.method(), actualParams);
} catch (NoSuchMethodException e) {
throw new IllegalAccessorException("Method target for @Invoke not found");
}
if (isStatic != Modifier.isStatic(targetMethod.getModifiers())) {
throw new IllegalAccessorException("Expected a " + (isStatic ? "" : "non-") + "static method target for @Invoke");
}
targetMethod.setAccessible(true);
try {
return publicLookup().unreflect(targetMethod);
} catch (IllegalAccessException e) {
throw new RuntimeException("impossible");
}
}
throw new IllegalAccessorException("Don't know what to do with method " + method.getName());
}
private static Class<?>[] transformParameters(Method method, int offset) {
Class<?>[] origParams = method.getParameterTypes();
Class<?>[] newParams = new Class<?>[origParams.length - offset];
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = offset; i < origParams.length; i++) {
newParams[i - offset] = maybeOverrideTarget(origParams[i], getOverrideTargetForParameter(parameterAnnotations[i]));
}
return newParams;
}
private static OverrideTarget getOverrideTargetForParameter(Annotation[] arr) {
for (Annotation annotation : arr) {
if (annotation instanceof OverrideTarget) {
return (OverrideTarget) annotation;
}
}
return null;
}
private static Class<?> maybeOverrideTarget(Class<?> target, OverrideTarget annotation) {
if (annotation != null) {
try {
target = Class.forName(annotation.value());
} catch (ClassNotFoundException e) {
throw new IllegalAccessorException("Overridden target "+ annotation.value() + " not found", e);
}
}
return target;
}
private static void checkNotPrimitive(Class<?> target) {
if (target.isPrimitive()) {
throw new IllegalAccessorException("Cannot use primitive class as target");
}
}
private AccessorMemberParser() { }
}