001package org.kohsuke.stapler.jsr269;
002
003import org.kohsuke.MetaInfServices;
004import org.kohsuke.stapler.DataBoundConstructor;
005
006import javax.annotation.processing.Processor;
007import javax.annotation.processing.RoundEnvironment;
008import javax.annotation.processing.SupportedAnnotationTypes;
009import javax.lang.model.SourceVersion;
010import javax.lang.model.element.Element;
011import javax.lang.model.element.ElementKind;
012import javax.lang.model.element.ExecutableElement;
013import javax.lang.model.element.TypeElement;
014import javax.lang.model.element.VariableElement;
015import javax.lang.model.util.ElementScanner6;
016import java.io.IOException;
017import java.util.Properties;
018import java.util.Set;
019import javax.lang.model.element.Modifier;
020import javax.tools.Diagnostic;
021
022/**
023 * @author Kohsuke Kawaguchi
024 */
025@SuppressWarnings({"Since15"})
026@SupportedAnnotationTypes("*")
027@MetaInfServices(Processor.class)
028public class ConstructorProcessor extends AbstractProcessorImpl {
029    @Override
030    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
031        try {
032            ElementScanner6<Void, Void> scanner = new ElementScanner6<Void, Void>() {
033                @Override
034                public Void visitExecutable(ExecutableElement e, Void aVoid) {
035                    if(e.getAnnotation(DataBoundConstructor.class)!=null) {
036                        write(e);
037                    } else {
038                        String javadoc = getJavadoc(e);
039                        if(javadoc!=null && javadoc.contains("@stapler-constructor")) {
040                            write(e);
041                        }
042                    }
043
044                    return super.visitExecutable(e, aVoid);
045                }
046
047                @Override
048                public Void visitUnknown(Element e, Void aVoid) {
049                    return DEFAULT_VALUE;
050                }
051            };
052
053            for (Element e : roundEnv.getRootElements()) {
054                if (e.getKind() == ElementKind.PACKAGE) { // JENKINS-11739
055                    continue;
056                }
057                scanner.scan(e, null);
058            }
059
060            return false;
061        } catch (RuntimeException e) {
062            // javac sucks at reporting errors in annotation processors
063            e.printStackTrace();
064            throw e;
065        } catch (Error e) {
066            e.printStackTrace();
067            throw e;
068        }
069    }
070
071    @Override
072    public SourceVersion getSupportedSourceVersion() {
073        return SourceVersion.latest();
074    }
075
076    private void write(ExecutableElement c) {
077        if (!c.getModifiers().contains(Modifier.PUBLIC)) {
078            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@DataBoundConstructor must be applied to a public constructor", c);
079            return;
080        }
081        if (c.getEnclosingElement().getModifiers().contains(Modifier.ABSTRACT)) {
082            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@DataBoundConstructor may not be used on an abstract class (only on concrete subclasses)", c);
083            return;
084        }
085        try {
086            StringBuilder buf = new StringBuilder();
087            for( VariableElement p : c.getParameters() ) {
088                if(buf.length()>0)  buf.append(',');
089                buf.append(p.getSimpleName());
090            }
091
092            TypeElement t = (TypeElement) c.getEnclosingElement();
093            String name = t.getQualifiedName().toString().replace('.', '/') + ".stapler";
094            notice("Generating " + name, c);
095
096            Properties p = new Properties();
097            p.put("constructor",buf.toString());
098            writePropertyFile(p, name);
099        } catch (IOException x) {
100            error(x);
101        }
102    }
103}