All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fiolino.common.analyzing.AnnotationInterestParser Maven / Gradle / Ivy

Go to download

General structure to easily create dynamic logic via MethodHandles and others.

There is a newer version: 1.0.10
Show newest version
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 annotationType;

        AnnotationInterestEnvironment(String name, Set elements,
                                      Class 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 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 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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy