summaryrefslogtreecommitdiffstats
path: root/parent/org.jvnet.hudson/src/main/java/org/jvnet/hudson/annotation_indexer/AnnotationProcessorImpl.java
blob: 3a7b6c2de627de738342845b38dd26a5c13fa851 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.sun.com/cddl/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 *
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is SezPoz. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 2008 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.jvnet.hudson.annotation_indexer;

import org.kohsuke.MetaInfServices;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import static javax.lang.model.SourceVersion.RELEASE_6;
import static javax.lang.model.SourceVersion.RELEASE_7;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import static javax.tools.StandardLocation.CLASS_OUTPUT;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * Creates indices of {@link Indexed} annotations.
 *
 * @author Kohsuke Kawaguchi
 */
@SupportedSourceVersion(RELEASE_7)
@SupportedAnnotationTypes("*")
@SuppressWarnings({"Since15"})
@MetaInfServices(Processor.class)
public class AnnotationProcessorImpl extends AbstractProcessor {
    /**
     * Use of an annotation.
     */
    private final class Use {
        /**
         * FQCN of the annotation.
         */
        final String annotationName;
        /**
         * Strings that designate FQCNs where annotations are used, either on a class or its members.
         */
        final Set<String> classes = new TreeSet<String>();
        /**
         * Keeps track of elements that has the annotation.
         */
        final Set<Element> originatingElements = new HashSet<Element>();

        private Use(String annotationName) {
            this.annotationName = annotationName;
        }

        void add(Element elt) {
            originatingElements.add(elt);

            TypeElement t;
            switch (elt.getKind()) {
            case CLASS:
                t = (TypeElement) elt;
                break;
            case METHOD:
            case FIELD:
                t = (TypeElement) elt.getEnclosingElement();
                break;
            default:
                throw new AssertionError(elt.getKind());
            }
            classes.add(getElementUtils().getBinaryName(t).toString());
        }

        String getIndexFileName() {
            return "META-INF/annotations/" + annotationName;
        }

        /**
         * Loads existing index, if it exists.
         */
        List<String> loadExisting() throws IOException {
            List<String> elements = new ArrayList<String>();
            try {
                FileObject in = processingEnv.getFiler().getResource(CLASS_OUTPUT, "", getIndexFileName());
                // Read existing annotations, for incremental compilation.
                BufferedReader is = new BufferedReader(new InputStreamReader(in.openInputStream(),"UTF-8"));
                try {
                    String line;
                    while ((line=is.readLine())!=null)
                        elements.add(line);
                } finally {
                    is.close();
                }
            } catch (FileNotFoundException x) {
                // OK, created for the first time
            }
            return elements;
        }

        void write() {
            try {
                FileObject out = processingEnv.getFiler().createResource(CLASS_OUTPUT,
                        "", getIndexFileName(),
                        originatingElements.toArray(new Element[originatingElements.size()]));

                PrintWriter w = new PrintWriter(new OutputStreamWriter(out.openOutputStream(),"UTF-8"));
                try {
                    for (String el : classes)
                        w.println(el);
                } finally {
                    w.close();
                }
            } catch (IOException x) {
                processingEnv.getMessager().printMessage(Kind.ERROR, x.toString());
            }
        }
    }

    private Elements getElementUtils() {
        return processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver())
            return false;
        
        // map from indexable annotation names, to actual uses
        Map<String,Use> output = new HashMap<String,Use>();
        scan(annotations, roundEnv, output);
        for (Use u : output.values())
            u.write();
        return false;
    }

    private AnnotationMirror findAnnotationOn(Element e, String name) {
        for (AnnotationMirror a : getElementUtils().getAllAnnotationMirrors(e))
            if (getElementUtils().getBinaryName((TypeElement) a.getAnnotationType().asElement()).contentEquals(name))
                return a;
        return null;
    }

    private void scan(Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv, Map<String,Use> output) {
        for (TypeElement ann : annotations) {
            AnnotationMirror indexed = findAnnotationOn(ann,Indexed.class.getName());
            if (indexed == null)
                continue;   // not indexed

            String annName = getElementUtils().getBinaryName(ann).toString();
            Use o = output.get(annName);
            if (o==null)
                output.put(annName,o=new Use(annName));

            for (Element elt : roundEnv.getElementsAnnotatedWith(ann)) {
                AnnotationMirror marked = findAnnotationOn(elt,annName);
                assert marked != null;

                // TODO: validator support

                o.add(elt);
            }
        }
    }


}