org.fiolino.common.analyzing.AnnotationInterestParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons Show documentation
Show all versions of commons Show documentation
General structure to easily create dynamic logic via MethodHandles and others.
package org.fiolino.common.analyzing;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.fiolino.common.container.Container;
import org.fiolino.common.processing.Analyzer;
import org.fiolino.common.processing.ModelDescription;
import org.fiolino.common.processing.FieldDescription;
import static java.lang.invoke.MethodHandles.publicLookup;
import static java.lang.invoke.MethodType.methodType;
/**
* Created by kuli on 01.12.15.
*/
public class AnnotationInterestParser {
private static final Logger logger = Logger.getLogger(AnnotationInterestParser.class.getName());
private static class InterestEnvironment {
private final String name;
private final Set elements;
final MethodHandle action;
InterestEnvironment(String name, Set elements, MethodHandle action) {
this.name = name;
this.elements = elements;
this.action = action;
}
Consumer toFieldAction(final Object analyzeable, final Analyzer analyzer) {
if (!elements.contains(AnalyzedElement.FIELD)) {
return f -> {
};
}
return f -> {
logger.finer(() -> "Calling " + name + " for field " + f);
Object child = execute(analyzeable, analyzer, () -> analyzer.getModelDescription().getValueDescription(f), f, null);
if (child == null || child == analyzeable) {
return;
}
analyzer.analyzeAgain(child);
};
}
Consumer toMethodAction(final Object analyzeable, final Analyzer analyzer) {
if (!elements.contains(AnalyzedElement.METHOD)) {
return m -> {
};
}
return m -> {
logger.finer(() -> "Calling " + name + " for method " + m);
Object child = execute(analyzeable, analyzer, () -> analyzer.getModelDescription().getValueDescription(m), null, m);
if (child == null || child == analyzeable) {
return;
}
analyzer.analyzeAgain(child);
};
}
protected Object execute(Object analyzeable, Analyzer analyzer,
Supplier valueDescriptionSupplier,
Field f, Method m)
throws ModelInconsistencyException {
FieldDescription fieldDescription = valueDescriptionSupplier.get();
try {
return action.invokeExact(analyzeable, analyzer, fieldDescription, fieldDescription.getConfiguration(), f, m);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException("Error executing " + action + " for " + (f == null ? m : f), t);
}
}
}
private static class AnnotationInterestEnvironment extends InterestEnvironment {
private final Class extends Annotation> annotationType;
AnnotationInterestEnvironment(String name, Set elements,
Class extends Annotation> annotationType, MethodHandle action) {
super(name, elements, action);
this.annotationType = annotationType;
}
@Override
protected Object execute(Object analyzeable, Analyzer analyzer,
Supplier valueDescriptionSupplier,
Field f, Method m) {
AccessibleObject accessible = f == null ? m : f;
Annotation anno = accessible.getAnnotation(annotationType);
if (anno == null) {
return null;
}
FieldDescription fieldDescription = valueDescriptionSupplier.get();
try {
return action.invokeExact(analyzeable, analyzer, fieldDescription, fieldDescription.getConfiguration(),
f, m, anno);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException("Error executing " + action + " for " + accessible, t);
}
}
}
private final Class> parsedType;
private final List handlers = new ArrayList<>();
private class MethodVisitor implements Consumer {
private final Object toParse;
private final Priority priority;
MethodVisitor(Object toParse, Priority priority) {
this.toParse = toParse;
this.priority = priority;
}
@Override
public void accept(Method method) {
visitMethod(toParse, method, priority);
}
}
public AnnotationInterestParser(Object toParse) {
parsedType = toParse.getClass();
ClassWalker walker = new ClassWalker<>();
walker.onMethod(new MethodVisitor(toParse, Priority.INITIALIZING));
walker.onMethod(new MethodVisitor(toParse, Priority.PREPROCESSING));
walker.onMethod(new MethodVisitor(toParse, Priority.PROCESSING));
walker.onMethod(new MethodVisitor(toParse, Priority.POSTPROCESSING));
walker.analyze(parsedType);
}
private void visitMethod(Object toParse, Method method, Priority priority) {
AnnotationInterest annotation = method.getAnnotation(AnnotationInterest.class);
if (annotation == null || annotation.value() != priority) {
return;
}
MethodHandles.Lookup lookup;
if (toParse instanceof Analyzeable) {
lookup = ((Analyzeable) toParse).getLookup();
} else {
lookup = publicLookup();
}
lookup = lookup.in(toParse.getClass());
MethodHandle handle;
try {
handle = lookup.unreflect(method);
} catch (IllegalAccessException ex) {
logger.log(Level.WARNING, () -> "Method " + method + " is not accessible!");
return;
}
if (Modifier.isStatic(method.getModifiers())) {
handle = MethodHandles.dropArguments(handle, 0, Object.class);
} else {
handle = handle.asType(handle.type().changeParameterType(0, Object.class));
}
handle = ensureCorrectReturnType(handle);
Set elements = EnumSet.noneOf(AnalyzedElement.class);
Collections.addAll(elements, annotation.elements());
Class extends Annotation> annotationType = annotation.annotation();
Class>[] parameterTypes = method.getParameterTypes();
int n = parameterTypes.length;
int[] parameterIndexes = new int[n + 1];
for (int i = 0; i < n; ) {
Class> pClass = parameterTypes[i++];
if (Analyzer.class.equals(pClass)) {
parameterIndexes[i] = 1;
} else if (FieldDescription.class.equals(pClass)) {
parameterIndexes[i] = 2;
} else if (Container.class.equals(pClass)) {
parameterIndexes[i] = 3;
} else if (pClass.isAnnotation()) {
parameterIndexes[i] = 6;
if (Annotation.class.equals(pClass)) {
if (annotationType.equals(Annotation.class)) {
throw new AssertionError("Method " + method + " has abstract Annotation type and must therefore specify which to use.");
}
} else {
Class extends Annotation> concreteType = pClass.asSubclass(Annotation.class);
if (annotationType.equals(Annotation.class) || annotationType.equals(concreteType)) {
annotationType = concreteType;
} else {
throw new AssertionError("Cannot show interest for " + annotationType.getName() + " and " + concreteType.getName() + " together.");
}
handle = handle.asType(handle.type().changeParameterType(i, Annotation.class));
}
} else if (Field.class.equals(pClass)) {
elements.add(AnalyzedElement.FIELD);
parameterIndexes[i] = 4;
} else if (Method.class.equals(pClass)) {
elements.add(AnalyzedElement.METHOD);
parameterIndexes[i] = 5;
} else {
throw new AssertionError("Method " + method + "'s parameter #" + i + " is of unhandled type " + pClass.getName());
}
}
if (elements.isEmpty()) {
elements = EnumSet.allOf(AnalyzedElement.class);
}
InterestEnvironment env;
if (annotationType.equals(Annotation.class)) {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Method " + method + " will be called for all " + logInfo(elements));
}
handle = MethodHandles.permuteArguments(handle, methodType(Object.class, Object.class, Analyzer.class,
FieldDescription.class, Container.class, Field.class, Method.class), parameterIndexes);
env = new InterestEnvironment(method.toGenericString(), elements, handle);
} else {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Method " + method + " will be called for all " + logInfo(elements) + " annotated with "
+ annotationType.getName());
}
handle = MethodHandles.permuteArguments(handle, methodType(Object.class, Object.class, Analyzer.class,
FieldDescription.class, Container.class, Field.class, Method.class, Annotation.class), parameterIndexes);
env = new AnnotationInterestEnvironment(method.toGenericString(), elements, annotationType, handle);
}
handlers.add(env);
}
private String logInfo(Set elementTypes) {
if (elementTypes.size() > 1) {
return "accessible objects";
} else {
return elementTypes.iterator().next().name().toLowerCase() + "s";
}
}
private MethodHandle ensureCorrectReturnType(MethodHandle handle) {
Class> returnType = handle.type().returnType();
if (returnType == void.class) {
// Then return the same instance
// Class>[] parameterTypes = handle.type().parameterArray();
// Class>[] dropAllExceptFirst = new Class>[parameterTypes.length - 1];
// System.arraycopy(parameterTypes, 1, dropAllExceptFirst, 0, dropAllExceptFirst.length);
// return MethodHandles.foldArguments(MethodHandles.dropArguments(MethodHandles.identity(Object.class), 1, dropAllExceptFirst), handle);
return MethodHandles.filterReturnValue(handle, MethodHandles.constant(Object.class, null));
} else {
return handle.asType(handle.type().changeReturnType(Object.class));
}
}
public void analyze(Object analyzeable, Analyzer analyzer) throws ModelInconsistencyException {
if (!parsedType.isInstance(analyzeable)) {
throw new IllegalArgumentException("Parser had analyzed " + parsedType.getName()
+ ", but argument is " + analyzeable);
}
ModelDescription modelDescription = analyzer.getModelDescription();
if (analyzeable instanceof Analyzeable) {
((Analyzeable) analyzeable).preAnalyze(modelDescription);
}
ClassWalker classWalker = new ClassWalker<>();
for (InterestEnvironment env : handlers) {
// classAnalyzer.forClasses()
classWalker.onField(env.toFieldAction(analyzeable, analyzer));
classWalker.onMethod(env.toMethodAction(analyzeable, analyzer));
}
classWalker.analyze(analyzer.getModelDescription().getModelType());
if (analyzeable instanceof Analyzeable) {
((Analyzeable) analyzeable).postAnalyze(modelDescription);
}
}
}