// Path: src/main/java/com/bnorm/auto/weave/internal/advice/Advice.java
// public interface Advice {
// 
//     Object call(Chain chain);
// }
// 
// Path: src/main/java/com/bnorm/auto/weave/internal/advice/Chain.java
// public abstract class Chain
//         implements AfterJoinPoint, AfterReturningJoinPoint, AfterThrowingJoinPoint, AroundJoinPoint, BeforeJoinPoint {
// 
//     private final Advice[] advice;
//     private final Object target;
//     private final StaticPointcut staticPointcut;
//     private final List<?> args;
// 
//     private int index;
//     private Object result;
//     private Throwable error;
// 
//     protected Chain(Advice[] advice, Object target, StaticPointcut staticPointcut, List<?> args) {
//         this.advice = advice;
//         this.target = target;
//         this.staticPointcut = staticPointcut;
//         this.args = args;
// 
//         this.index = 0;
//         this.result = null;
//         this.error = null;
//     }
// 
//     public Object proceed() {
//         if (index < advice.length) {
//             return advice[index++].call(this);
//         } else {
//             try {
//                 this.result = call();
//                 return result;
//             } catch (Throwable error) {
//                 this.error = error;
//                 // Sneaky rethrow any method exceptions
//                 // This is horrible, don't do this in normal code, but this removes the
//                 // need for complicated exception handling in the generated code.
//                 throw Chain.<Error>sneakyThrow(error);
//             }
//         }
//     }
// 
//     protected abstract Object call() throws Throwable;
// 
//     /** Java Puzzlers #43. */
//     @SuppressWarnings("unchecked")
//     private static <T extends Throwable> Error sneakyThrow(Throwable t) throws T {
//         throw (T) t;
//     }
// 
//     @Override
//     public Object result() {
//         return result;
//     }
// 
//     @Override
//     public Throwable error() {
//         return error;
//     }
// 
//     @Override
//     public Object target() {
//         return target;
//     }
// 
//     @Override
//     public List<?> args() {
//         return args;
//     }
// 
//     @Override
//     public String method() {
//         return staticPointcut.method();
//     }
// 
//     @Override
//     public Class<?> targetType() {
//         return staticPointcut.targetType();
//     }
// 
//     @Override
//     public Class<?> returnType() {
//         return staticPointcut.returnType();
//     }
// 
//     @Override
//     public List<Class<?>> argTypes() {
//         return staticPointcut.argTypes();
//     }
// }


import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.NoType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import com.bnorm.auto.weave.AutoAdvice;
import com.bnorm.auto.weave.AutoAspect;
import com.bnorm.auto.weave.AutoWeave;
import com.bnorm.auto.weave.internal.advice.Advice;
import com.bnorm.auto.weave.internal.advice.Chain;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.NameAllocator;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import static javax.tools.Diagnostic.Kind.ERROR;
import static javax.tools.Diagnostic.Kind.WARNING;
            processingEnv.getMessager()
                         .printMessage(Diagnostic.Kind.WARNING,
                                       "Could not write generated class " + javaFile.typeSpec.name + ": " + e);
        }
    }

    private Set<AdviceDescriptor> advice(RoundEnvironment roundEnv) {
        final Map<TypeElement, Set<ExecutableElement>> adviceMap = new LinkedHashMap<>();
        for (Element advice : roundEnv.getElementsAnnotatedWith(AutoAdvice.class)) {
            advice.accept(new SimpleElementVisitor6<Object, Object>() {
                @Override
                protected Object defaultAction(Element e, Object o) {
                    throw new AssertionError("AutoAdvice cannot be applied to anything but a method!");
                }

                @Override
                public Object visitExecutable(ExecutableElement e, Object o) {
                    boolean error = false;

                    List<? extends VariableElement> parameters = e.getParameters();
                    if (parameters.size() != 1) {
                        messager.printMessage(ERROR,
                                              "AutoAdvice can only be applied to a method with a single JoinPoint parameter.",
                                              e);
                        error = true;
                    }

                    // todo(bnorm) check first (and only) parameter extends but is not JoinPoint
                    // todo(bnorm) class should have a default constructor

// Path: src/main/java/com/bnorm/auto/weave/internal/advice/Advice.java
// public interface Advice {
// 
//     Object call(Chain chain);
// }
// 
// Path: src/main/java/com/bnorm/auto/weave/internal/advice/Chain.java
// public abstract class Chain
//         implements AfterJoinPoint, AfterReturningJoinPoint, AfterThrowingJoinPoint, AroundJoinPoint, BeforeJoinPoint {
// 
//     private final Advice[] advice;
//     private final Object target;
//     private final StaticPointcut staticPointcut;
//     private final List<?> args;
// 
//     private int index;
//     private Object result;
//     private Throwable error;
// 
//     protected Chain(Advice[] advice, Object target, StaticPointcut staticPointcut, List<?> args) {
//         this.advice = advice;
//         this.target = target;
//         this.staticPointcut = staticPointcut;
//         this.args = args;
// 
//         this.index = 0;
//         this.result = null;
//         this.error = null;
//     }
// 
//     public Object proceed() {
//         if (index < advice.length) {
//             return advice[index++].call(this);
//         } else {
//             try {
//                 this.result = call();
//                 return result;
//             } catch (Throwable error) {
//                 this.error = error;
//                 // Sneaky rethrow any method exceptions
//                 // This is horrible, don't do this in normal code, but this removes the
//                 // need for complicated exception handling in the generated code.
//                 throw Chain.<Error>sneakyThrow(error);
//             }
//         }
//     }
// 
//     protected abstract Object call() throws Throwable;
// 
//     /** Java Puzzlers #43. */
//     @SuppressWarnings("unchecked")
//     private static <T extends Throwable> Error sneakyThrow(Throwable t) throws T {
//         throw (T) t;
//     }
// 
//     @Override
//     public Object result() {
//         return result;
//     }
// 
//     @Override
//     public Throwable error() {
//         return error;
//     }
// 
//     @Override
//     public Object target() {
//         return target;
//     }
// 
//     @Override
//     public List<?> args() {
//         return args;
//     }
// 
//     @Override
//     public String method() {
//         return staticPointcut.method();
//     }
// 
//     @Override
//     public Class<?> targetType() {
//         return staticPointcut.targetType();
//     }
// 
//     @Override
//     public Class<?> returnType() {
//         return staticPointcut.returnType();
//     }
// 
//     @Override
//     public List<Class<?>> argTypes() {
//         return staticPointcut.argTypes();
//     }
// }


// Path: src/main/java/com/bnorm/auto/weave/internal/AutoWeaveProcessor.java
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.NoType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import com.bnorm.auto.weave.AutoAdvice;
import com.bnorm.auto.weave.AutoAspect;
import com.bnorm.auto.weave.AutoWeave;
import com.bnorm.auto.weave.internal.advice.Advice;
import com.bnorm.auto.weave.internal.advice.Chain;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.NameAllocator;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import static javax.tools.Diagnostic.Kind.ERROR;
import static javax.tools.Diagnostic.Kind.WARNING;

            processingEnv.getMessager()
                         .printMessage(Diagnostic.Kind.WARNING,
                                       "Could not write generated class " + javaFile.typeSpec.name + ": " + e);
        }
    }

    private Set<AdviceDescriptor> advice(RoundEnvironment roundEnv) {
        final Map<TypeElement, Set<ExecutableElement>> adviceMap = new LinkedHashMap<>();
        for (Element advice : roundEnv.getElementsAnnotatedWith(AutoAdvice.class)) {
            advice.accept(new SimpleElementVisitor6<Object, Object>() {
                @Override
                protected Object defaultAction(Element e, Object o) {
                    throw new AssertionError("AutoAdvice cannot be applied to anything but a method!");
                }

                @Override
                public Object visitExecutable(ExecutableElement e, Object o) {
                    boolean error = false;

                    List<? extends VariableElement> parameters = e.getParameters();
                    if (parameters.size() != 1) {
                        messager.printMessage(ERROR,
                                              "AutoAdvice can only be applied to a method with a single JoinPoint parameter.",
                                              e);
                        error = true;
                    }

                    // todo(bnorm) check first (and only) parameter extends but is not JoinPoint
                    // todo(bnorm) class should have a default constructor

                    if (error) {