001package org.kohsuke.stapler.jsr269;
002
003import org.kohsuke.MetaInfServices;
004import org.kohsuke.stapler.export.Exported;
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.TypeElement;
012import javax.tools.FileObject;
013import java.io.BufferedReader;
014import java.io.FileNotFoundException;
015import java.io.IOException;
016import java.io.InputStreamReader;
017import java.io.OutputStreamWriter;
018import java.io.PrintWriter;
019import java.util.Collection;
020import java.util.Map.Entry;
021import java.util.Properties;
022import java.util.Set;
023import java.util.TreeSet;
024
025/**
026 * @author Kohsuke Kawaguchi
027 */
028@SuppressWarnings({"Since15"})
029@SupportedAnnotationTypes("org.kohsuke.stapler.export.Exported")
030@MetaInfServices(Processor.class)
031public class ExportedBeanAnnotationProcessor extends AbstractProcessorImpl {
032
033    private Set<String> exposedBeanNames;
034
035    @Override
036    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
037        try {
038            if (roundEnv.processingOver()) {
039                FileObject beans = createResource(STAPLER_BEAN_FILE);
040                PrintWriter w = new PrintWriter(new OutputStreamWriter(beans.openOutputStream(), "UTF-8"));
041                for (String beanName : exposedBeanNames) {
042                    w.println(beanName);
043                }
044                w.close();
045                return false;
046            }
047
048            // collect all exposed properties
049            PoormansMultimap<TypeElement, Element/*member decls*/> props = new PoormansMultimap<TypeElement,Element>();
050
051            for (Element exported : roundEnv.getElementsAnnotatedWith(Exported.class)) {
052                Element type = exported.getEnclosingElement();
053                if (type.getKind().isClass() || type.getKind().isInterface()) {
054                    props.put((TypeElement)type, exported);
055                }
056            }
057
058            if (exposedBeanNames == null) {
059                scanExisting();
060            }
061
062            for (Entry<TypeElement, Collection<Element>> e : props.asMap().entrySet()) {
063                exposedBeanNames.add(e.getKey().getQualifiedName().toString());
064
065                final Properties javadocs = new Properties();
066                for (Element md : e.getValue()) {
067                    switch (md.getKind()) {
068                    case FIELD:
069                    case METHOD:
070                       String javadoc = getJavadoc(md);
071                       if(javadoc!=null)
072                           javadocs.put(md.getSimpleName().toString(), javadoc);
073                        break;
074                    default:
075                        throw new AssertionError("Unexpected element type: "+md);
076                    }
077                        // TODO: possibly a proper method signature generation, but it's too tedious
078                        // way too tedious.
079                        //private String getSignature(MethodDeclaration m) {
080                        //    final StringBuilder buf = new StringBuilder(m.getSimpleName());
081                        //    buf.append('(');
082                        //    boolean first=true;
083                        //    for (ParameterDeclaration p : m.getParameters()) {
084                        //        if(first)   first = false;
085                        //        else        buf.append(',');
086                        //        p.getType().accept(new SimpleTypeVisitor() {
087                        //            public void visitPrimitiveType(PrimitiveType pt) {
088                        //                buf.append(pt.getKind().toString().toLowerCase());
089                        //            }
090                        //            public void visitDeclaredType(DeclaredType dt) {
091                        //                buf.append(dt.getDeclaration().getQualifiedName());
092                        //            }
093                        //
094                        //            public void visitArrayType(ArrayType at) {
095                        //                at.getComponentType().accept(this);
096                        //                buf.append("[]");
097                        //            }
098                        //
099                        //            public void visitTypeVariable(TypeVariable tv) {
100                        //
101                        //                // TODO
102                        //                super.visitTypeVariable(typeVariable);
103                        //            }
104                        //
105                        //            public void visitVoidType(VoidType voidType) {
106                        //                // TODO
107                        //                super.visitVoidType(voidType);
108                        //            }
109                        //        });
110                        //    }
111                        //    buf.append(')');
112                        //    // TODO
113                        //    return null;
114                        //}
115                }
116
117                String javadocFile = e.getKey().getQualifiedName().toString().replace('.', '/') + ".javadoc";
118                notice("Generating "+ javadocFile, e.getKey());
119                writePropertyFile(javadocs, javadocFile);
120            }
121
122        } catch (IOException x) {
123            error(x);
124        } catch (RuntimeException e) {
125            // javac sucks at reporting errors in annotation processors
126            e.printStackTrace();
127            throw e;
128        } catch (Error e) {
129            e.printStackTrace();
130            throw e;
131        }
132        return false;
133    }
134
135    @Override
136    public SourceVersion getSupportedSourceVersion() {
137        return SourceVersion.latest();
138    }
139
140    private void scanExisting() throws IOException {
141        exposedBeanNames = new TreeSet<String>();
142
143        try {
144            FileObject beans = getResource(STAPLER_BEAN_FILE);
145            BufferedReader in = new BufferedReader(new InputStreamReader(beans.openInputStream(),"UTF-8"));
146            String line;
147            while((line=in.readLine())!=null)
148                exposedBeanNames.add(line.trim());
149            in.close();
150        } catch (FileNotFoundException e) {
151            // no existing file, which is fine
152        } catch (java.nio.file.NoSuchFileException e) {
153            // no existing file, which is fine
154        }
155    }
156
157    static final String STAPLER_BEAN_FILE = "META-INF/exposed.stapler-beans";
158}