AnnotationProcessing.java revision 3822:d8766c39123a
1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/**
25 * @test
26 * @bug 8133884 8162711
27 * @summary Verify that annotation processing works.
28 * @library /tools/lib
29 * @modules
30 *      jdk.compiler/com.sun.tools.javac.api
31 *      jdk.compiler/com.sun.tools.javac.main
32 * @build toolbox.ToolBox toolbox.JavacTask ModuleTestBase
33 * @run main AnnotationProcessing
34 */
35
36import java.nio.file.Files;
37import java.nio.file.Path;
38import java.util.Arrays;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42import java.util.Objects;
43import java.util.Set;
44import java.util.stream.Collectors;
45
46import javax.annotation.processing.AbstractProcessor;
47import javax.annotation.processing.Messager;
48import javax.annotation.processing.RoundEnvironment;
49import javax.annotation.processing.SupportedAnnotationTypes;
50import javax.annotation.processing.SupportedOptions;
51import javax.lang.model.SourceVersion;
52import javax.lang.model.element.Element;
53import javax.lang.model.element.ModuleElement;
54import javax.lang.model.element.ModuleElement.ProvidesDirective;
55import javax.lang.model.element.ModuleElement.UsesDirective;
56import javax.lang.model.element.PackageElement;
57import javax.lang.model.element.TypeElement;
58import javax.lang.model.element.VariableElement;
59import javax.lang.model.type.TypeKind;
60import javax.lang.model.util.ElementFilter;
61import javax.lang.model.util.ElementScanner9;
62import javax.tools.Diagnostic.Kind;
63
64import toolbox.JavacTask;
65import toolbox.Task;
66import toolbox.Task.Mode;
67
68public class AnnotationProcessing extends ModuleTestBase {
69
70    public static void main(String... args) throws Exception {
71        new AnnotationProcessing().runTests();
72    }
73
74    @Test
75    public void testAPSingleModule(Path base) throws Exception {
76        Path moduleSrc = base.resolve("module-src");
77        Path m1 = moduleSrc.resolve("m1x");
78
79        Path classes = base.resolve("classes");
80
81        Files.createDirectories(classes);
82
83        tb.writeJavaFiles(m1,
84                          "module m1x { }",
85                          "package impl; public class Impl { }");
86
87        String log = new JavacTask(tb)
88                .options("--module-source-path", moduleSrc.toString(),
89                         "-processor", AP.class.getName(),
90                         "-AexpectedEnclosedElements=m1x=>impl")
91                .outdir(classes)
92                .files(findJavaFiles(moduleSrc))
93                .run()
94                .writeAll()
95                .getOutput(Task.OutputKind.DIRECT);
96
97        if (!log.isEmpty())
98            throw new AssertionError("Unexpected output: " + log);
99    }
100
101    @Test
102    public void testAPMultiModule(Path base) throws Exception {
103        Path moduleSrc = base.resolve("module-src");
104        Path m1 = moduleSrc.resolve("m1x");
105        Path m2 = moduleSrc.resolve("m2x");
106
107        Path classes = base.resolve("classes");
108
109        Files.createDirectories(classes);
110
111        tb.writeJavaFiles(m1,
112                          "module m1x { }",
113                          "package impl1; public class Impl1 { }");
114
115        tb.writeJavaFiles(m2,
116                          "module m2x { }",
117                          "package impl2; public class Impl2 { }");
118
119        String log = new JavacTask(tb)
120                .options("--module-source-path", moduleSrc.toString(),
121                         "-processor", AP.class.getName(),
122                         "-AexpectedEnclosedElements=m1x=>impl1,m2x=>impl2")
123                .outdir(classes)
124                .files(findJavaFiles(moduleSrc))
125                .run()
126                .writeAll()
127                .getOutput(Task.OutputKind.DIRECT);
128
129        if (!log.isEmpty())
130            throw new AssertionError("Unexpected output: " + log);
131    }
132
133    @SupportedAnnotationTypes("*")
134    @SupportedOptions("expectedEnclosedElements")
135    public static final class AP extends AbstractProcessor {
136
137        private Map<String, List<String>> module2ExpectedEnclosedElements;
138
139        @Override
140        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
141            if (module2ExpectedEnclosedElements == null) {
142                module2ExpectedEnclosedElements = new HashMap<>();
143
144                String expectedEnclosedElements =
145                        processingEnv.getOptions().get("expectedEnclosedElements");
146
147                for (String moduleDef : expectedEnclosedElements.split(",")) {
148                    String[] module2Packages = moduleDef.split("=>");
149
150                    module2ExpectedEnclosedElements.put(module2Packages[0],
151                                                        Arrays.asList(module2Packages[1].split(":")));
152                }
153            }
154
155            //verify ModuleType and ModuleSymbol behavior:
156            for (Element root : roundEnv.getRootElements()) {
157                ModuleElement module = processingEnv.getElementUtils().getModuleOf(root);
158
159                assertEquals(TypeKind.MODULE, module.asType().getKind());
160
161                boolean[] seenModule = new boolean[1];
162
163                module.accept(new ElementScanner9<Void, Void>() {
164                    @Override
165                    public Void visitModule(ModuleElement e, Void p) {
166                        seenModule[0] = true;
167                        return null;
168                    }
169                    @Override
170                    public Void scan(Element e, Void p) {
171                        throw new AssertionError("Shouldn't get here.");
172                    }
173                }, null);
174
175                assertEquals(true, seenModule[0]);
176
177                List<String> actualElements =
178                        module.getEnclosedElements()
179                              .stream()
180                              .map(s -> (PackageElement) s)
181                              .map(p -> p.getQualifiedName().toString())
182                              .collect(Collectors.toList());
183
184                assertEquals(module2ExpectedEnclosedElements.remove(module.getQualifiedName().toString()),
185                             actualElements);
186            }
187
188            if (roundEnv.processingOver()) {
189                assertEquals(true, module2ExpectedEnclosedElements.isEmpty());
190            }
191
192            return false;
193        }
194
195        @Override
196        public SourceVersion getSupportedSourceVersion() {
197            return SourceVersion.latest();
198        }
199
200    }
201
202    @Test
203    public void testVerifyUsesProvides(Path base) throws Exception {
204        Path moduleSrc = base.resolve("module-src");
205        Path m1 = moduleSrc.resolve("m1x");
206
207        Path classes = base.resolve("classes");
208
209        Files.createDirectories(classes);
210
211        tb.writeJavaFiles(m1,
212                          "module m1x { exports api; uses api.Api; provides api.Api with impl.Impl; }",
213                          "package api; public class Api { }",
214                          "package impl; public class Impl extends api.Api { }");
215
216        String log = new JavacTask(tb)
217                .options("-doe", "-processor", VerifyUsesProvidesAP.class.getName())
218                .outdir(classes)
219                .files(findJavaFiles(moduleSrc))
220                .run()
221                .writeAll()
222                .getOutput(Task.OutputKind.DIRECT);
223
224        if (!log.isEmpty())
225            throw new AssertionError("Unexpected output: " + log);
226    }
227
228    @SupportedAnnotationTypes("*")
229    public static final class VerifyUsesProvidesAP extends AbstractProcessor {
230
231        @Override
232        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
233            TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
234
235            assertNonNull("Cannot find api.Api", api);
236
237            ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
238
239            assertNonNull("modle is null", modle);
240
241            List<? extends UsesDirective> uses = ElementFilter.usesIn(modle.getDirectives());
242            assertEquals(1, uses.size());
243            assertEquals("api.Api", uses.iterator().next().getService().getQualifiedName().toString());
244
245            List<? extends ProvidesDirective> provides = ElementFilter.providesIn(modle.getDirectives());
246            assertEquals(1, provides.size());
247            assertEquals("api.Api", provides.iterator().next().getService().getQualifiedName().toString());
248            assertEquals("impl.Impl", provides.iterator().next().getImplementations().get(0).getQualifiedName().toString());
249
250            return false;
251        }
252
253        @Override
254        public SourceVersion getSupportedSourceVersion() {
255            return SourceVersion.latest();
256        }
257
258    }
259
260    @Test
261    public void testPackageNoModule(Path base) throws Exception {
262        Path src = base.resolve("src");
263        Path classes = base.resolve("classes");
264
265        Files.createDirectories(classes);
266
267        tb.writeJavaFiles(src,
268                          "package api; public class Api { }");
269
270        String log = new JavacTask(tb)
271                .options("-processor", VerifyPackageNoModule.class.getName(),
272                         "-source", "8",
273                         "-Xlint:-options")
274                .outdir(classes)
275                .files(findJavaFiles(src))
276                .run()
277                .writeAll()
278                .getOutput(Task.OutputKind.DIRECT);
279
280        if (!log.isEmpty())
281            throw new AssertionError("Unexpected output: " + log);
282    }
283
284    @SupportedAnnotationTypes("*")
285    public static final class VerifyPackageNoModule extends AbstractProcessor {
286
287        @Override
288        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
289            TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
290
291            assertNonNull("Cannot find api.Api", api);
292
293            ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
294
295            assertNull("modle is not null", modle);
296
297            return false;
298        }
299
300        @Override
301        public SourceVersion getSupportedSourceVersion() {
302            return SourceVersion.latest();
303        }
304
305    }
306
307    @Test
308    public void testQualifiedClassForProcessing(Path base) throws Exception {
309        Path moduleSrc = base.resolve("module-src");
310        Path m1 = moduleSrc.resolve("m1x");
311        Path m2 = moduleSrc.resolve("m2x");
312
313        Path classes = base.resolve("classes");
314
315        Files.createDirectories(classes);
316
317        tb.writeJavaFiles(m1,
318                          "module m1x { }",
319                          "package impl; public class Impl { int m1x; }");
320
321        tb.writeJavaFiles(m2,
322                          "module m2x { }",
323                          "package impl; public class Impl { int m2x; }");
324
325        new JavacTask(tb)
326            .options("--module-source-path", moduleSrc.toString())
327            .outdir(classes)
328            .files(findJavaFiles(moduleSrc))
329            .run()
330            .writeAll()
331            .getOutput(Task.OutputKind.DIRECT);
332
333        List<String> expected = Arrays.asList("Note: field: m1x");
334
335        for (Mode mode : new Mode[] {Mode.API, Mode.CMDLINE}) {
336            List<String> log = new JavacTask(tb, mode)
337                    .options("-processor", QualifiedClassForProcessing.class.getName(),
338                             "--module-path", classes.toString())
339                    .classes("m1x/impl.Impl")
340                    .outdir(classes)
341                    .run()
342                    .writeAll()
343                    .getOutputLines(Task.OutputKind.DIRECT);
344
345            if (!expected.equals(log))
346                throw new AssertionError("Unexpected output: " + log);
347        }
348    }
349
350    @SupportedAnnotationTypes("*")
351    public static final class QualifiedClassForProcessing extends AbstractProcessor {
352
353        @Override
354        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
355            if (processingEnv.getElementUtils().getModuleElement("m1x") == null) {
356                throw new AssertionError("No m1x module found.");
357            }
358
359            Messager messager = processingEnv.getMessager();
360
361            for (TypeElement clazz : ElementFilter.typesIn(roundEnv.getRootElements())) {
362                for (VariableElement field : ElementFilter.fieldsIn(clazz.getEnclosedElements())) {
363                    messager.printMessage(Kind.NOTE, "field: " + field.getSimpleName());
364                }
365            }
366
367            return false;
368        }
369
370        @Override
371        public SourceVersion getSupportedSourceVersion() {
372            return SourceVersion.latest();
373        }
374
375    }
376
377    private static void assertNonNull(String msg, Object val) {
378        if (val == null) {
379            throw new AssertionError(msg);
380        }
381    }
382
383    private static void assertNull(String msg, Object val) {
384        if (val != null) {
385            throw new AssertionError(msg);
386        }
387    }
388
389    private static void assertEquals(Object expected, Object actual) {
390        if (!Objects.equals(expected, actual)) {
391            throw new AssertionError("expected: " + expected + "; actual=" + actual);
392        }
393    }
394
395}
396