net.amygdalum.xrayinterface.InvocationResolver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xrayinterface Show documentation
Show all versions of xrayinterface Show documentation
An application to get controlled access to private fields and methods of Java classes
package net.amygdalum.xrayinterface;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Arrays.asList;
import static net.amygdalum.xrayinterface.BindingQualifier.AUTO;
import static net.amygdalum.xrayinterface.BindingQualifier.CONSTRUCTOR;
import static net.amygdalum.xrayinterface.BindingQualifier.GET;
import static net.amygdalum.xrayinterface.BindingQualifier.METHOD;
import static net.amygdalum.xrayinterface.BindingQualifier.SET;
import static net.amygdalum.xrayinterface.FinalUtil.ensureNonFinal;
import static net.amygdalum.xrayinterface.FixedType.fixed;
import static net.amygdalum.xrayinterface.SignatureUtil.computeFieldNames;
import static net.amygdalum.xrayinterface.SignatureUtil.fieldSignature;
import static net.amygdalum.xrayinterface.SignatureUtil.isBooleanGetter;
import static net.amygdalum.xrayinterface.SignatureUtil.isGetter;
import static net.amygdalum.xrayinterface.SignatureUtil.isSetter;
import static net.amygdalum.xrayinterface.SignatureUtil.methodSignature;
import static net.amygdalum.xrayinterface.SignatureUtil.propertyOf;
import static net.amygdalum.xrayinterface.Type.convertedTypes;
import static net.amygdalum.xrayinterface.Type.isWeakMatching;
import static net.amygdalum.xrayinterface.Type.matchedTypes;
import static net.amygdalum.xrayinterface.Type.matches;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
public class InvocationResolver {
private static Map, Function> signatures = createSignatureMapping();
private MethodHandles.Lookup lookup;
private Map fieldCache;
private Class> type;
public InvocationResolver(Class> type) {
this.type = type;
this.lookup = MethodHandles.lookup();
this.fieldCache = new HashMap();
}
public Class> getType() {
return type;
}
private static Map, Function> createSignatureMapping() {
Map, Function> signatures = new IdentityHashMap<>();
signatures.put(Construct.class, annotation -> new BindingSignature("", CONSTRUCTOR));
signatures.put(Delegate.class, annotation -> new BindingSignature(((Delegate) annotation).value(), METHOD));
signatures.put(SetProperty.class, annotation -> new BindingSignature(((SetProperty) annotation).value(), SET));
signatures.put(GetProperty.class, annotation -> new BindingSignature(((GetProperty) annotation).value(), GET));
return signatures;
}
protected MethodInvocationHandler findInvocationHandler(Method method) throws NoSuchMethodException, NoSuchFieldException {
BindingSignature signature = resolveSignature(method);
switch (signature.qualifier) {
case SET:
return createSetterInvocator(signature.name, signature.params[0]);
case GET:
return createGetterInvocator(signature.name, signature.result);
case METHOD:
return createMethodInvocator(signature.name, signature.result, signature.params, signature.exceptions);
case CONSTRUCTOR:
return createConstructorInvocator(signature.result, signature.params, signature.exceptions);
default:
throw new NoSuchMethodException("invocation resolver failed resolving: " + method.toGenericString());
}
}
protected BindingSignature resolveSignature(Method method) throws NoSuchMethodException, NoSuchFieldException {
BindingSignature signature = initSignature(method.getAnnotations());
signature = completeSignature(signature, method);
return signature;
}
private BindingSignature initSignature(Annotation[] annotations) {
if (annotations == null) {
return new BindingSignature();
} else {
return Stream.of(annotations)
.filter(annotation -> signatures.containsKey(annotation.annotationType()))
.map(annotation -> signatures.get(annotation.annotationType()).apply(annotation))
.findFirst()
.orElse(new BindingSignature());
}
}
private BindingSignature completeSignature(BindingSignature signature, Method method) throws NoSuchMethodException, NoSuchFieldException {
Exception exception = null;
for (BindingQualifier type : signature.types()) {
try {
return completeSignature(type, signature, method);
} catch (NoSuchFieldException | NoSuchMethodException e) {
if (exception == null) {
exception = e;
}
}
}
if (exception instanceof NoSuchFieldException) {
throw (NoSuchFieldException) exception;
} else if (exception instanceof NoSuchMethodException) {
throw (NoSuchMethodException) exception;
} else {
return new BindingSignature(method.getName());
}
}
private BindingSignature completeSignature(BindingQualifier type, BindingSignature signature, Method method) throws NoSuchMethodException, NoSuchFieldException {
switch (type) {
case SET:
return completeSetter(signature, method);
case GET:
return completeGetter(signature, method);
case METHOD:
return completeMethod(signature, method);
case CONSTRUCTOR:
return completeConstructor(signature, method);
default:
throw new NoSuchMethodException();
}
}
private BindingSignature completeSetter(BindingSignature signature, Method method) throws NoSuchFieldException {
if (!signature.hasName() && !isSetter(method)) {
throw new NoSuchFieldException();
}
Type type = setterType(method);
List names = signature.hasName() ? asList(signature.name) : computeFieldNames(propertyOf(method));
for (String name : names) {
try {
Field field = findField(name, type);
signature.params = new Type[] { type.matching(field.getType()) };
signature.name = name;
signature.qualifier = SET;
return signature;
} catch (NoSuchFieldException e) {
}
}
throw new NoSuchFieldException(fieldSignature(names, type.matchedType()));
}
private Type setterType(Method method) {
Parameter[] parameters = method.getParameters();
if (parameters.length == 1) {
Parameter value = parameters[0];
Class> valueClass = value.getType();
Convert converted = value.getAnnotation(Convert.class);
if (converted != null) {
return new MatchType(name(converted, valueClass), valueClass);
} else {
return fixed(valueClass);
}
} else {
return null;
}
}
private BindingSignature completeGetter(BindingSignature signature, Method method) throws NoSuchFieldException {
if (!signature.hasName() && !(isGetter(method) || isBooleanGetter(method))) {
throw new NoSuchFieldException();
}
Type type = getterType(method);
List names = signature.hasName() ? asList(signature.name) : computeFieldNames(propertyOf(method));
for (String name : names) {
try {
Field field = findField(name, type);
signature.result = type.matching(field.getType());
signature.name = name;
signature.qualifier = GET;
return signature;
} catch (NoSuchFieldException e) {
}
}
throw new NoSuchFieldException(fieldSignature(names, type.matchedType()));
}
private Type getterType(Method method) {
Class> valueClass = method.getReturnType();
Convert converted = method.getAnnotation(Convert.class);
if (converted != null) {
return new MatchType(name(converted, valueClass), valueClass);
} else {
return fixed(valueClass);
}
}
private String name(Convert converted, Class> valueClass) {
String name = converted.value();
if ("".equals(name)) {
name = valueClass.getSimpleName();
}
return name;
}
private BindingSignature completeMethod(BindingSignature signature, Method method) throws NoSuchMethodException {
String name = signature.hasName() ? signature.name : method.getName();
Type result = resultType(method);
Type[] params = paramTypes(method);
Type[] exceptions = exceptionTypes(method);
Method targetMethod = findMethod(name, result, params, exceptions);
signature.result = result.matching(targetMethod.getReturnType());
signature.params = matching(params, targetMethod.getParameterTypes());
signature.exceptions = matching(exceptions, targetMethod.getExceptionTypes());
signature.name = name;
signature.qualifier = METHOD;
return signature;
}
private Type resultType(Method method) {
Class> valueClass = method.getReturnType();
Convert converted = method.getAnnotation(Convert.class);
if (converted != null) {
return new MatchType(name(converted, valueClass), valueClass);
} else {
return fixed(valueClass);
}
}
private Type[] paramTypes(Method method) {
Parameter[] parameters = method.getParameters();
Type[] types = new Type[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter value = parameters[i];
Class> valueClass = value.getType();
Convert converted = value.getAnnotation(Convert.class);
if (converted != null) {
types[i] = new MatchType(name(converted, valueClass), valueClass);
} else {
types[i] = fixed(valueClass);
}
}
return types;
}
private Type[] exceptionTypes(Method method) {
Class>[] exceptions = method.getExceptionTypes();
Type[] types = new Type[exceptions.length];
for (int i = 0; i < exceptions.length; i++) {
Class> valueClass = exceptions[i];
types[i] = fixed(valueClass);
}
return types;
}
private BindingSignature completeConstructor(BindingSignature signature, Method method) throws NoSuchMethodException {
if (signature.qualifier == AUTO && !SignatureUtil.isConstructor(method)) {
throw new NoSuchMethodException();
}
Type result = resultType(method);
Type[] params = paramTypes(method);
Type[] exceptions = exceptionTypes(method);
Constructor> targetMethod = findConstructor(result, params, exceptions);
signature.result = result.matching(targetMethod.getDeclaringClass());
signature.params = matching(params, targetMethod.getParameterTypes());
signature.exceptions = matching(exceptions, targetMethod.getExceptionTypes());
signature.name = "";
signature.qualifier = CONSTRUCTOR;
return signature;
}
private Type[] matching(Type[] types, Class>[] matchingTypes) {
for (int i = 0; i < types.length; i++) {
types[i] = types[i].matching(matchingTypes[i]);
}
return types;
}
protected MethodInvocationHandler createSetterInvocator(String name, Type param) throws NoSuchFieldException {
Field field = findField(name, param);
ensureNonFinal(field);
return createSetterInvocator(field, param.convertedType());
}
private MethodInvocationHandler createSetterInvocator(Field field, Class> convertedPropertyType) throws NoSuchFieldException {
try {
MethodHandle getter = lookup.unreflectSetter(field);
if (isStatic(field.getModifiers())) {
return new StaticSetter(field.getName(), getter, convertedPropertyType);
} else {
return new FieldSetter(field.getName(), getter, convertedPropertyType);
}
} catch (IllegalAccessException e) {
throw new ReflectionFailedException(e);
}
}
protected MethodInvocationHandler createGetterInvocator(String name, Type result) throws NoSuchFieldException {
Field field = findField(name, result);
return createGetterInvocator(field, result.convertedType());
}
private MethodInvocationHandler createGetterInvocator(Field field, Class> convertedPropertyType) throws NoSuchFieldException {
try {
MethodHandle getter = lookup.unreflectGetter(field);
if (isStatic(field.getModifiers())) {
return new StaticGetter(field.getName(), getter, convertedPropertyType);
} else {
return new FieldGetter(field.getName(), getter, convertedPropertyType);
}
} catch (IllegalAccessException e) {
throw new ReflectionFailedException(e);
}
}
protected Field findField(String name, Type type) throws NoSuchFieldException {
Class> currentClass = this.type;
while (currentClass != Object.class) {
try {
Field field = fieldCache.get(name);
if (field == null) {
field = currentClass.getDeclaredField(name);
field.setAccessible(true);
fieldCache.put(name, field);
}
if (type.matches(field.getType())) {
return field;
}
} catch (NoSuchFieldException e) {
}
currentClass = currentClass.getSuperclass();
}
Class> fieldType = type.matchedType();
throw new NoSuchFieldException(fieldSignature(name, fieldType));
}
protected MethodInvocationHandler createMethodInvocator(String name, Type result, Type[] params, Type[] exceptions) throws NoSuchMethodException {
Method method = findMethod(name, result, params, exceptions);
return createMethodInvocator(method, result, params);
}
private MethodInvocationHandler createMethodInvocator(Method method, Type result, Type[] params) throws NoSuchMethodException {
try {
MethodHandle methodHandle = lookup.unreflect(method);
if (isStatic(method.getModifiers())) {
return new StaticMethodInvoker(method.getName(), methodHandle, result.convertedType(), convertedTypes(params));
} else {
return new MethodInvoker(method.getName(), methodHandle, result.convertedType(), convertedTypes(params));
}
} catch (IllegalAccessException e) {
throw new ReflectionFailedException(e);
}
}
protected Method findMethod(String name, Type result, Type[] params, Type[] exceptions) throws NoSuchMethodException {
boolean weakMatching = isWeakMatching(params);
Class>[] paramTypes = matchedTypes(params);
Class>[] exceptionTypes = matchedTypes(exceptions);
Class> currentClass = this.type;
while (currentClass != Object.class) {
try {
Method candidate = weakMatching ? matchMethod(currentClass, name, params) : matchStrong(currentClass, name, paramTypes);
if (result.matches(candidate.getReturnType())
&& Arrays.equals(exceptionTypes, candidate.getExceptionTypes())) {
candidate.setAccessible(true);
return candidate;
}
} catch (NoSuchMethodException e) {
}
currentClass = currentClass.getSuperclass();
}
throw new NoSuchMethodException(methodSignature(name, result.matchedType(), paramTypes, exceptionTypes));
}
private Method matchMethod(Class> currentClass, String name, Type[] params) throws NoSuchMethodException {
return Stream.of(currentClass.getDeclaredMethods())
.filter(method -> name.equals(method.getName()))
.filter(method -> matches(params, method.getParameterTypes()))
.findFirst()
.orElseThrow(() -> new NoSuchMethodException());
}
private Method matchStrong(Class> currentClass, String name, Class>[] paramTypes) throws NoSuchMethodException {
return currentClass.getDeclaredMethod(name, paramTypes);
}
protected MethodInvocationHandler createConstructorInvocator(Type result, Type[] params, Type[] exceptions) throws NoSuchMethodException {
Constructor> constructor = findConstructor(result, params, exceptions);
return createConstructorInvocator(constructor, result, params);
}
private MethodInvocationHandler createConstructorInvocator(Constructor> constructor, Type result, Type[] params) throws NoSuchMethodException {
try {
return new ConstructorInvoker(lookup.unreflectConstructor(constructor), result.convertedType(), convertedTypes(params));
} catch (IllegalAccessException e) {
throw new ReflectionFailedException(e);
}
}
protected Constructor> findConstructor(Type result, Type[] params, Type[] exceptions) throws NoSuchMethodException {
Class>[] paramTypes = matchedTypes(params);
Class>[] exceptionTypes = matchedTypes(exceptions);
Class> currentClass = this.type;
while (currentClass != Object.class) {
try {
Constructor> candidate = currentClass.getDeclaredConstructor(paramTypes);
if (result.matches(type)
&& Arrays.equals(exceptionTypes, candidate.getExceptionTypes())) {
candidate.setAccessible(true);
return candidate;
}
} catch (NoSuchMethodException e) {
}
currentClass = currentClass.getSuperclass();
}
throw new NoSuchMethodException(methodSignature("", type, paramTypes, exceptionTypes));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy