AnnotationProcessing.java revision 3823:cd0a8a7a0199
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.io.File;
37import java.io.IOException;
38import java.io.OutputStream;
39import java.io.Reader;
40import java.io.Writer;
41import java.nio.file.Files;
42import java.nio.file.Path;
43import java.nio.file.Paths;
44import java.util.Arrays;
45import java.util.HashMap;
46import java.util.HashSet;
47import java.util.List;
48import java.util.Map;
49import java.util.Objects;
50import java.util.Set;
51import java.util.concurrent.Callable;
52import java.util.stream.Collectors;
53
54import javax.annotation.processing.AbstractProcessor;
55import javax.annotation.processing.Filer;
56import javax.annotation.processing.FilerException;
57import javax.annotation.processing.Messager;
58import javax.annotation.processing.ProcessingEnvironment;
59import javax.annotation.processing.RoundEnvironment;
60import javax.annotation.processing.SupportedAnnotationTypes;
61import javax.annotation.processing.SupportedOptions;
62import javax.lang.model.SourceVersion;
63import javax.lang.model.element.Element;
64import javax.lang.model.element.ElementKind;
65import javax.lang.model.element.ModuleElement;
66import javax.lang.model.element.ModuleElement.ProvidesDirective;
67import javax.lang.model.element.ModuleElement.UsesDirective;
68import javax.lang.model.element.PackageElement;
69import javax.lang.model.element.TypeElement;
70import javax.lang.model.element.VariableElement;
71import javax.lang.model.type.TypeKind;
72import javax.lang.model.util.ElementFilter;
73import javax.lang.model.util.ElementScanner9;
74import javax.tools.Diagnostic.Kind;
75import javax.tools.FileObject;
76import javax.tools.JavaCompiler;
77import javax.tools.JavaCompiler.CompilationTask;
78import javax.tools.JavaFileManager;
79import javax.tools.JavaFileManager.Location;
80import javax.tools.JavaFileObject;
81import javax.tools.StandardJavaFileManager;
82import javax.tools.StandardLocation;
83import javax.tools.ToolProvider;
84
85import toolbox.JavacTask;
86import toolbox.Task;
87import toolbox.Task.Mode;
88import toolbox.Task.OutputKind;
89
90public class AnnotationProcessing extends ModuleTestBase {
91
92    public static void main(String... args) throws Exception {
93        new AnnotationProcessing().runTests();
94    }
95
96    @Test
97    public void testAPSingleModule(Path base) throws Exception {
98        Path moduleSrc = base.resolve("module-src");
99        Path m1 = moduleSrc.resolve("m1x");
100
101        Path classes = base.resolve("classes");
102
103        Files.createDirectories(classes);
104
105        tb.writeJavaFiles(m1,
106                          "module m1x { }",
107                          "package impl; public class Impl { }");
108
109        String log = new JavacTask(tb)
110                .options("--module-source-path", moduleSrc.toString(),
111                         "-processor", AP.class.getName(),
112                         "-AexpectedEnclosedElements=m1x=>impl")
113                .outdir(classes)
114                .files(findJavaFiles(moduleSrc))
115                .run()
116                .writeAll()
117                .getOutput(Task.OutputKind.DIRECT);
118
119        if (!log.isEmpty())
120            throw new AssertionError("Unexpected output: " + log);
121    }
122
123    @Test
124    public void testAPMultiModule(Path base) throws Exception {
125        Path moduleSrc = base.resolve("module-src");
126        Path m1 = moduleSrc.resolve("m1x");
127        Path m2 = moduleSrc.resolve("m2x");
128
129        Path classes = base.resolve("classes");
130
131        Files.createDirectories(classes);
132
133        tb.writeJavaFiles(m1,
134                          "module m1x { }",
135                          "package impl1; public class Impl1 { }");
136
137        tb.writeJavaFiles(m2,
138                          "module m2x { }",
139                          "package impl2; public class Impl2 { }");
140
141        String log = new JavacTask(tb)
142                .options("--module-source-path", moduleSrc.toString(),
143                         "-processor", AP.class.getName(),
144                         "-AexpectedEnclosedElements=m1x=>impl1,m2x=>impl2")
145                .outdir(classes)
146                .files(findJavaFiles(moduleSrc))
147                .run()
148                .writeAll()
149                .getOutput(Task.OutputKind.DIRECT);
150
151        if (!log.isEmpty())
152            throw new AssertionError("Unexpected output: " + log);
153    }
154
155    @SupportedAnnotationTypes("*")
156    @SupportedOptions("expectedEnclosedElements")
157    public static final class AP extends AbstractProcessor {
158
159        private Map<String, List<String>> module2ExpectedEnclosedElements;
160        private Set<String> seenModules = new HashSet<>();
161
162        @Override
163        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
164            if (module2ExpectedEnclosedElements == null) {
165                module2ExpectedEnclosedElements = new HashMap<>();
166
167                String expectedEnclosedElements =
168                        processingEnv.getOptions().get("expectedEnclosedElements");
169
170                for (String moduleDef : expectedEnclosedElements.split(",")) {
171                    String[] module2Packages = moduleDef.split("=>");
172
173                    module2ExpectedEnclosedElements.put(module2Packages[0],
174                                                        Arrays.asList(module2Packages[1].split(":")));
175                }
176            }
177
178            //verify ModuleType and ModuleSymbol behavior:
179            for (Element root : roundEnv.getRootElements()) {
180                ModuleElement module = processingEnv.getElementUtils().getModuleOf(root);
181
182                assertEquals(TypeKind.MODULE, module.asType().getKind());
183
184                boolean[] seenModule = new boolean[1];
185
186                module.accept(new ElementScanner9<Void, Void>() {
187                    @Override
188                    public Void visitModule(ModuleElement e, Void p) {
189                        seenModule[0] = true;
190                        return null;
191                    }
192                    @Override
193                    public Void scan(Element e, Void p) {
194                        throw new AssertionError("Shouldn't get here.");
195                    }
196                }, null);
197
198                assertEquals(true, seenModule[0]);
199
200                List<String> actualElements =
201                        module.getEnclosedElements()
202                              .stream()
203                              .map(s -> (PackageElement) s)
204                              .map(p -> p.getQualifiedName().toString())
205                              .collect(Collectors.toList());
206
207                String moduleName = module.getQualifiedName().toString();
208
209                assertEquals(module2ExpectedEnclosedElements.get(moduleName),
210                             actualElements);
211
212                seenModules.add(moduleName);
213            }
214
215            if (roundEnv.processingOver()) {
216                assertEquals(module2ExpectedEnclosedElements.keySet(), seenModules);
217            }
218
219            return false;
220        }
221
222        @Override
223        public SourceVersion getSupportedSourceVersion() {
224            return SourceVersion.latest();
225        }
226
227    }
228
229    @Test
230    public void testVerifyUsesProvides(Path base) throws Exception {
231        Path moduleSrc = base.resolve("module-src");
232        Path m1 = moduleSrc.resolve("m1x");
233
234        Path classes = base.resolve("classes");
235
236        Files.createDirectories(classes);
237
238        tb.writeJavaFiles(m1,
239                          "module m1x { exports api; uses api.Api; provides api.Api with impl.Impl; }",
240                          "package api; public class Api { }",
241                          "package impl; public class Impl extends api.Api { }");
242
243        String log = new JavacTask(tb)
244                .options("-doe", "-processor", VerifyUsesProvidesAP.class.getName())
245                .outdir(classes)
246                .files(findJavaFiles(moduleSrc))
247                .run()
248                .writeAll()
249                .getOutput(Task.OutputKind.DIRECT);
250
251        if (!log.isEmpty())
252            throw new AssertionError("Unexpected output: " + log);
253    }
254
255    @SupportedAnnotationTypes("*")
256    public static final class VerifyUsesProvidesAP extends AbstractProcessor {
257
258        @Override
259        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
260            TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
261
262            assertNonNull("Cannot find api.Api", api);
263
264            ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
265
266            assertNonNull("modle is null", modle);
267
268            List<? extends UsesDirective> uses = ElementFilter.usesIn(modle.getDirectives());
269            assertEquals(1, uses.size());
270            assertEquals("api.Api", uses.iterator().next().getService().getQualifiedName().toString());
271
272            List<? extends ProvidesDirective> provides = ElementFilter.providesIn(modle.getDirectives());
273            assertEquals(1, provides.size());
274            assertEquals("api.Api", provides.iterator().next().getService().getQualifiedName().toString());
275            assertEquals("impl.Impl", provides.iterator().next().getImplementations().get(0).getQualifiedName().toString());
276
277            return false;
278        }
279
280        @Override
281        public SourceVersion getSupportedSourceVersion() {
282            return SourceVersion.latest();
283        }
284
285    }
286
287    @Test
288    public void testPackageNoModule(Path base) throws Exception {
289        Path src = base.resolve("src");
290        Path classes = base.resolve("classes");
291
292        Files.createDirectories(classes);
293
294        tb.writeJavaFiles(src,
295                          "package api; public class Api { }");
296
297        String log = new JavacTask(tb)
298                .options("-processor", VerifyPackageNoModule.class.getName(),
299                         "-source", "8",
300                         "-Xlint:-options")
301                .outdir(classes)
302                .files(findJavaFiles(src))
303                .run()
304                .writeAll()
305                .getOutput(Task.OutputKind.DIRECT);
306
307        if (!log.isEmpty())
308            throw new AssertionError("Unexpected output: " + log);
309    }
310
311    @SupportedAnnotationTypes("*")
312    public static final class VerifyPackageNoModule extends AbstractProcessor {
313
314        @Override
315        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
316            TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
317
318            assertNonNull("Cannot find api.Api", api);
319
320            ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
321
322            assertNull("modle is not null", modle);
323
324            return false;
325        }
326
327        @Override
328        public SourceVersion getSupportedSourceVersion() {
329            return SourceVersion.latest();
330        }
331
332    }
333
334    @Test
335    public void testQualifiedClassForProcessing(Path base) throws Exception {
336        Path moduleSrc = base.resolve("module-src");
337        Path m1 = moduleSrc.resolve("m1x");
338        Path m2 = moduleSrc.resolve("m2x");
339
340        Path classes = base.resolve("classes");
341
342        Files.createDirectories(classes);
343
344        tb.writeJavaFiles(m1,
345                          "module m1x { }",
346                          "package impl; public class Impl { int m1x; }");
347
348        tb.writeJavaFiles(m2,
349                          "module m2x { }",
350                          "package impl; public class Impl { int m2x; }");
351
352        new JavacTask(tb)
353            .options("--module-source-path", moduleSrc.toString())
354            .outdir(classes)
355            .files(findJavaFiles(moduleSrc))
356            .run()
357            .writeAll()
358            .getOutput(Task.OutputKind.DIRECT);
359
360        List<String> expected = Arrays.asList("Note: field: m1x");
361
362        for (Mode mode : new Mode[] {Mode.API, Mode.CMDLINE}) {
363            List<String> log = new JavacTask(tb, mode)
364                    .options("-processor", QualifiedClassForProcessing.class.getName(),
365                             "--module-path", classes.toString())
366                    .classes("m1x/impl.Impl")
367                    .outdir(classes)
368                    .run()
369                    .writeAll()
370                    .getOutputLines(Task.OutputKind.DIRECT);
371
372            if (!expected.equals(log))
373                throw new AssertionError("Unexpected output: " + log);
374        }
375    }
376
377    @SupportedAnnotationTypes("*")
378    public static final class QualifiedClassForProcessing extends AbstractProcessor {
379
380        @Override
381        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
382            if (processingEnv.getElementUtils().getModuleElement("m1x") == null) {
383                throw new AssertionError("No m1x module found.");
384            }
385
386            Messager messager = processingEnv.getMessager();
387
388            for (TypeElement clazz : ElementFilter.typesIn(roundEnv.getRootElements())) {
389                for (VariableElement field : ElementFilter.fieldsIn(clazz.getEnclosedElements())) {
390                    messager.printMessage(Kind.NOTE, "field: " + field.getSimpleName());
391                }
392            }
393
394            return false;
395        }
396
397        @Override
398        public SourceVersion getSupportedSourceVersion() {
399            return SourceVersion.latest();
400        }
401
402    }
403
404    @Test
405    public void testModuleInRootElements(Path base) throws Exception {
406        Path moduleSrc = base.resolve("module-src");
407        Path m1 = moduleSrc.resolve("m1");
408
409        Path classes = base.resolve("classes");
410
411        Files.createDirectories(classes);
412
413        tb.writeJavaFiles(m1,
414                          "module m1x { exports api; }",
415                          "package api; public class Api { }");
416
417        List<String> log = new JavacTask(tb)
418                .options("-processor", ModuleInRootElementsAP.class.getName())
419                .outdir(classes)
420                .files(findJavaFiles(moduleSrc))
421                .run()
422                .writeAll()
423                .getOutputLines(Task.OutputKind.STDERR);
424
425        assertEquals(Arrays.asList("module: m1x"), log);
426    }
427
428    @SupportedAnnotationTypes("*")
429    public static final class ModuleInRootElementsAP extends AbstractProcessor {
430
431        @Override
432        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
433            roundEnv.getRootElements()
434                    .stream()
435                    .filter(el -> el.getKind() == ElementKind.MODULE)
436                    .forEach(mod -> System.err.println("module: " + mod.getSimpleName()));
437
438            return false;
439        }
440
441        @Override
442        public SourceVersion getSupportedSourceVersion() {
443            return SourceVersion.latest();
444        }
445
446    }
447
448    @Test
449    public void testAnnotationsInModuleInfo(Path base) throws Exception {
450        Path moduleSrc = base.resolve("module-src");
451        Path m1 = moduleSrc.resolve("m1");
452
453        tb.writeJavaFiles(m1,
454                          "@Deprecated module m1x { }");
455
456        Path m2 = moduleSrc.resolve("m2x");
457
458        tb.writeJavaFiles(m2,
459                          "@SuppressWarnings(\"\") module m2x { }");
460
461        Path classes = base.resolve("classes");
462
463        Files.createDirectories(classes);
464
465        List<String> log = new JavacTask(tb)
466                .options("-processor", AnnotationsInModuleInfoPrint.class.getName())
467                .outdir(classes)
468                .files(findJavaFiles(m1))
469                .run()
470                .writeAll()
471                .getOutputLines(Task.OutputKind.DIRECT);
472
473        List<String> expectedLog = Arrays.asList("Note: AP Invoked",
474                                                 "Note: AP Invoked");
475
476        assertEquals(expectedLog, log);
477
478        new JavacTask(tb)
479            .options("-processor", AnnotationsInModuleInfoFail.class.getName())
480            .outdir(classes)
481            .files(findJavaFiles(m2))
482            .run()
483            .writeAll();
484    }
485
486    @SupportedAnnotationTypes("java.lang.Deprecated")
487    public static final class AnnotationsInModuleInfoPrint extends AbstractProcessor {
488
489        @Override
490        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
491            processingEnv.getMessager().printMessage(Kind.NOTE, "AP Invoked");
492            return false;
493        }
494
495        @Override
496        public SourceVersion getSupportedSourceVersion() {
497            return SourceVersion.latest();
498        }
499
500    }
501
502    @SupportedAnnotationTypes("java.lang.Deprecated")
503    public static final class AnnotationsInModuleInfoFail extends AbstractProcessor {
504
505        @Override
506        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
507            throw new AssertionError();
508        }
509
510        @Override
511        public SourceVersion getSupportedSourceVersion() {
512            return SourceVersion.latest();
513        }
514
515    }
516
517    @Test
518    public void testGenerateInMultiModeAPI(Path base) throws Exception {
519        Path moduleSrc = base.resolve("module-src");
520        Path classes = base.resolve("classes");
521
522        Files.createDirectories(classes);
523
524        Path m1 = moduleSrc.resolve("m1x");
525
526        tb.writeJavaFiles(m1,
527                          "module m1x { exports api1; }",
528                          "package api1; public class Api { GenApi ga; impl.Impl i; }");
529
530        writeFile("1", m1, "api1", "api");
531        writeFile("1", m1, "impl", "impl");
532
533        Path m2 = moduleSrc.resolve("m2x");
534
535        tb.writeJavaFiles(m2,
536                          "module m2x { requires m1x; exports api2; }",
537                          "package api2; public class Api { api1.GenApi ga1; GenApi qa2; impl.Impl i;}");
538
539        writeFile("2", m2, "api2", "api");
540        writeFile("2", m2, "impl", "impl");
541
542        for (FileType fileType : FileType.values()) {
543            if (Files.isDirectory(classes)) {
544                tb.cleanDirectory(classes);
545            } else {
546                Files.createDirectories(classes);
547            }
548
549            new JavacTask(tb)
550              .options("-processor", MultiModeAPITestAP.class.getName(),
551                       "--module-source-path", moduleSrc.toString(),
552                       "-Afiletype=" + fileType.name())
553              .outdir(classes)
554              .files(findJavaFiles(moduleSrc))
555              .run()
556              .writeAll();
557
558            assertFileExists(classes, "m1x", "api1", "GenApi.class");
559            assertFileExists(classes, "m1x", "impl", "Impl.class");
560            assertFileExists(classes, "m1x", "api1", "gen1");
561            assertFileExists(classes, "m2x", "api2", "GenApi.class");
562            assertFileExists(classes, "m2x", "impl", "Impl.class");
563            assertFileExists(classes, "m2x", "api2", "gen1");
564        }
565    }
566
567    enum FileType {
568        SOURCE,
569        CLASS;
570    }
571
572    public static abstract class GeneratingAP extends AbstractProcessor {
573
574        void createSource(CreateFileObject file, String name, String content) {
575            try (Writer out = file.create().openWriter()) {
576                out.write(content);
577            } catch (IOException ex) {
578                throw new IllegalStateException(ex);
579            }
580        }
581
582        void createClass(CreateFileObject file, String name, String content) {
583            String fileNameStub = name.replace(".", File.separator);
584
585            try (OutputStream out = file.create().openOutputStream()) {
586                Path scratch = Files.createDirectories(Paths.get(""));
587                Path scratchSrc = scratch.resolve(fileNameStub + ".java").toAbsolutePath();
588
589                Files.createDirectories(scratchSrc.getParent());
590
591                try (Writer w = Files.newBufferedWriter(scratchSrc)) {
592                    w.write(content);
593                }
594
595                Path scratchClasses = scratch.resolve("classes");
596
597                Files.createDirectories(scratchClasses);
598
599                JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
600                try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
601                    List<String> options = Arrays.asList("-d", scratchClasses.toString());
602                    Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(scratchSrc);
603                    CompilationTask task = comp.getTask(null, fm, null, options, null, files);
604
605                    if (!task.call()) {
606                        throw new AssertionError("compilation failed");
607                    }
608                }
609
610                Path classfile = scratchClasses.resolve(fileNameStub + ".class");
611
612                Files.copy(classfile, out);
613            } catch (IOException ex) {
614                throw new IllegalStateException(ex);
615            }
616        }
617
618        void doReadResource(CreateFileObject file, String expectedContent) {
619            try {
620                StringBuilder actualContent = new StringBuilder();
621
622                try (Reader r = file.create().openReader(true)) {
623                    int read;
624
625                    while ((read = r.read()) != (-1)) {
626                        actualContent.append((char) read);
627                    }
628
629                }
630
631                assertEquals(expectedContent, actualContent.toString());
632            } catch (IOException ex) {
633                throw new IllegalStateException(ex);
634            }
635        }
636
637        public interface CreateFileObject {
638            public FileObject create() throws IOException;
639        }
640
641        void expectFilerException(Callable<Object> c) {
642            try {
643                c.call();
644                throw new AssertionError("Expected exception not thrown");
645            } catch (FilerException ex) {
646                //expected
647            } catch (Exception ex) {
648                throw new IllegalStateException(ex);
649            }
650        }
651
652        @Override
653        public SourceVersion getSupportedSourceVersion() {
654            return SourceVersion.latest();
655        }
656
657    }
658
659    @SupportedAnnotationTypes("*")
660    @SupportedOptions({"filetype", "modulename"})
661    public static final class MultiModeAPITestAP extends GeneratingAP {
662
663        int round;
664
665        @Override
666        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
667            if (round++ != 0)
668                return false;
669
670            createClass("m1x", "api1.GenApi", "package api1; public class GenApi {}");
671            createClass("m1x", "impl.Impl", "package impl; public class Impl {}");
672            createClass("m2x", "api2.GenApi", "package api2; public class GenApi {}");
673            createClass("m2x", "impl.Impl", "package impl; public class Impl {}");
674
675            createResource("m1x", "api1", "gen1");
676            createResource("m2x", "api2", "gen1");
677
678            readResource("m1x", "api1", "api", "1");
679            readResource("m1x", "impl", "impl", "1");
680            readResource("m2x", "api2", "api", "2");
681            readResource("m2x", "impl", "impl", "2");
682
683            Filer filer = processingEnv.getFiler();
684
685            expectFilerException(() -> filer.createSourceFile("fail.Fail"));
686            expectFilerException(() -> filer.createClassFile("fail.Fail"));
687            expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "fail", "fail"));
688            expectFilerException(() -> filer.getResource(StandardLocation.MODULE_SOURCE_PATH, "fail", "fail"));
689
690            //must not generate to unnamed package:
691            expectFilerException(() -> filer.createSourceFile("m1/Fail"));
692            expectFilerException(() -> filer.createClassFile("m1/Fail"));
693
694            //cannot generate resources to modules that are not root modules:
695            expectFilerException(() -> filer.createSourceFile("java.base/fail.Fail"));
696            expectFilerException(() -> filer.createClassFile("java.base/fail.Fail"));
697            expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "java.base/fail", "Fail"));
698
699            return false;
700        }
701
702        void createClass(String expectedModule, String name, String content) {
703            Filer filer = processingEnv.getFiler();
704            FileType filetype = FileType.valueOf(processingEnv.getOptions().getOrDefault("filetype", ""));
705
706            switch (filetype) {
707                case SOURCE:
708                    createSource(() -> filer.createSourceFile(expectedModule + "/" + name), name, content);
709                    break;
710                case CLASS:
711                    createClass(() -> filer.createClassFile(expectedModule + "/" + name), name, content);
712                    break;
713                default:
714                    throw new AssertionError("Unexpected filetype: " + filetype);
715            }
716        }
717
718        void createResource(String expectedModule, String pkg, String relName) {
719            try {
720                Filer filer = processingEnv.getFiler();
721
722                filer.createResource(StandardLocation.CLASS_OUTPUT, expectedModule + "/" + pkg, relName)
723                     .openOutputStream()
724                     .close();
725            } catch (IOException ex) {
726                throw new IllegalStateException(ex);
727            }
728        }
729
730        void readResource(String expectedModule, String pkg, String relName, String expectedContent) {
731            Filer filer = processingEnv.getFiler();
732
733            doReadResource(() -> filer.getResource(StandardLocation.MODULE_SOURCE_PATH, expectedModule + "/" + pkg, relName),
734                           expectedContent);
735        }
736
737    }
738
739    @Test
740    public void testGenerateInSingleNameModeAPI(Path base) throws Exception {
741        Path classes = base.resolve("classes");
742
743        Files.createDirectories(classes);
744
745        Path m1 = base.resolve("module-src");
746
747        tb.writeJavaFiles(m1,
748                          "module m1x { }");
749
750        writeFile("3", m1, "impl", "resource");
751
752        new JavacTask(tb)
753          .options("-processor", SingleNameModeAPITestAP.class.getName(),
754                   "-sourcepath", m1.toString())
755          .outdir(classes)
756          .files(findJavaFiles(m1))
757          .run()
758          .writeAll();
759
760        assertFileExists(classes, "impl", "Impl1.class");
761        assertFileExists(classes, "impl", "Impl2.class");
762        assertFileExists(classes, "impl", "Impl3");
763        assertFileExists(classes, "impl", "Impl4.class");
764        assertFileExists(classes, "impl", "Impl5.class");
765        assertFileExists(classes, "impl", "Impl6");
766        assertFileExists(classes, "impl", "Impl7.class");
767        assertFileExists(classes, "impl", "Impl8.class");
768        assertFileExists(classes, "impl", "Impl9");
769    }
770
771
772    @SupportedAnnotationTypes("*")
773    public static final class SingleNameModeAPITestAP extends GeneratingAP {
774
775        int round;
776
777        @Override
778        public synchronized void init(ProcessingEnvironment processingEnv) {
779            super.init(processingEnv);
780        }
781
782        @Override
783        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
784            if (round++ != 0)
785                return false;
786
787            Filer filer = processingEnv.getFiler();
788
789            createSource(() -> filer.createSourceFile("impl.Impl1"), "impl.Impl1", "package impl; class Impl1 {}");
790            createClass(() -> filer.createClassFile("impl.Impl2"), "impl.Impl2", "package impl; class Impl2 {}");
791            createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "impl", "Impl3"), "impl.Impl3", "");
792            doReadResource(() -> filer.getResource(StandardLocation.SOURCE_PATH, "impl", "resource"), "3");
793
794            createSource(() -> filer.createSourceFile("m1x/impl.Impl4"), "impl.Impl4", "package impl; class Impl4 {}");
795            createClass(() -> filer.createClassFile("m1x/impl.Impl5"), "impl.Impl5", "package impl; class Impl5 {}");
796            createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "m1x/impl", "Impl6"), "impl.Impl6", "");
797            doReadResource(() -> filer.getResource(StandardLocation.SOURCE_PATH, "m1x/impl", "resource"), "3");
798
799            TypeElement jlObject = processingEnv.getElementUtils().getTypeElement("java.lang.Object");
800
801            //"broken" originating element:
802            createSource(() -> filer.createSourceFile("impl.Impl7", jlObject), "impl.Impl7", "package impl; class Impl7 {}");
803            createClass(() -> filer.createClassFile("impl.Impl8", jlObject), "impl.Impl8", "package impl; class Impl8 {}");
804            createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "impl", "Impl9", jlObject), "impl.Impl9", "");
805
806            //must not generate to unnamed package:
807            expectFilerException(() -> filer.createSourceFile("Fail"));
808            expectFilerException(() -> filer.createClassFile("Fail"));
809            expectFilerException(() -> filer.createSourceFile("m1x/Fail"));
810            expectFilerException(() -> filer.createClassFile("m1x/Fail"));
811
812            //cannot generate resources to modules that are not root modules:
813            expectFilerException(() -> filer.createSourceFile("java.base/fail.Fail"));
814            expectFilerException(() -> filer.createClassFile("java.base/fail.Fail"));
815            expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "java.base/fail", "Fail"));
816
817            return false;
818        }
819
820    }
821
822    @Test
823    public void testGenerateInUnnamedModeAPI(Path base) throws Exception {
824        Path classes = base.resolve("classes");
825
826        Files.createDirectories(classes);
827
828        Path src = base.resolve("src");
829
830        tb.writeJavaFiles(src,
831                          "class T {}");
832
833        new JavacTask(tb)
834          .options("-processor", UnnamedModeAPITestAP.class.getName(),
835                   "-sourcepath", src.toString())
836          .outdir(classes)
837          .files(findJavaFiles(src))
838          .run()
839          .writeAll();
840
841        assertFileExists(classes, "Impl1.class");
842        assertFileExists(classes, "Impl2.class");
843    }
844
845    @Test
846    public void testGenerateInNoModeAPI(Path base) throws Exception {
847        Path classes = base.resolve("classes");
848
849        Files.createDirectories(classes);
850
851        Path src = base.resolve("src");
852
853        tb.writeJavaFiles(src,
854                          "class T {}");
855
856        new JavacTask(tb)
857          .options("-processor", UnnamedModeAPITestAP.class.getName(),
858                   "-source", "8", "-target", "8",
859                   "-sourcepath", src.toString())
860          .outdir(classes)
861          .files(findJavaFiles(src))
862          .run()
863          .writeAll();
864
865        assertFileExists(classes, "Impl1.class");
866        assertFileExists(classes, "Impl2.class");
867    }
868
869    @SupportedAnnotationTypes("*")
870    public static final class UnnamedModeAPITestAP extends GeneratingAP {
871
872        int round;
873
874        @Override
875        public synchronized void init(ProcessingEnvironment processingEnv) {
876            super.init(processingEnv);
877        }
878
879        @Override
880        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
881            if (round++ != 0)
882                return false;
883
884            Filer filer = processingEnv.getFiler();
885
886            //must not generate to unnamed package:
887            createSource(() -> filer.createSourceFile("Impl1"), "Impl1", "class Impl1 {}");
888            createClass(() -> filer.createClassFile("Impl2"), "Impl2", "class Impl2 {}");
889
890            return false;
891        }
892
893    }
894
895    @Test
896    public void testDisambiguateAnnotations(Path base) throws Exception {
897        Path classes = base.resolve("classes");
898
899        Files.createDirectories(classes);
900
901        Path src = base.resolve("src");
902        Path m1 = src.resolve("m1x");
903
904        tb.writeJavaFiles(m1,
905                          "module m1x { exports api; }",
906                          "package api; public @interface A {}",
907                          "package api; public @interface B {}");
908
909        Path m2 = src.resolve("m2x");
910
911        tb.writeJavaFiles(m2,
912                          "module m2x { exports api; }",
913                          "package api; public @interface A {}",
914                          "package api; public @interface B {}");
915
916        Path m3 = src.resolve("m3x");
917
918        tb.writeJavaFiles(m3,
919                          "module m3x { requires m1x; }",
920                          "package impl; import api.*; @A @B public class T {}");
921
922        Path m4 = src.resolve("m4x");
923
924        tb.writeJavaFiles(m4,
925                          "module m4x { requires m2x; }",
926                          "package impl; import api.*; @A @B public class T {}");
927
928        List<String> log;
929        List<String> expected;
930
931        log = new JavacTask(tb)
932            .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
933                     "--module-source-path", src.toString(),
934                     "-m", "m1x,m2x")
935            .outdir(classes)
936            .run()
937            .writeAll()
938            .getOutputLines(OutputKind.STDERR);
939
940        expected = Arrays.asList("");
941
942        if (!expected.equals(log)) {
943            throw new AssertionError("Output does not match; output: " + log);
944        }
945
946        log = new JavacTask(tb)
947            .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
948                     "--module-source-path", src.toString(),
949                     "-m", "m3x")
950            .outdir(classes)
951            .run()
952            .writeAll()
953            .getOutputLines(OutputKind.STDERR);
954
955        expected = Arrays.asList("SelectAnnotationBTestAP",
956                                 "SelectAnnotationBTestAP");
957
958        if (!expected.equals(log)) {
959            throw new AssertionError("Output does not match; output: " + log);
960        }
961
962        log = new JavacTask(tb)
963            .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
964                     "--module-source-path", src.toString(),
965                     "-m", "m4x")
966            .outdir(classes)
967            .run()
968            .writeAll()
969            .getOutputLines(OutputKind.STDERR);
970
971        expected = Arrays.asList("SelectAnnotationATestAP",
972                                 "SelectAnnotationBTestAP",
973                                 "SelectAnnotationATestAP",
974                                 "SelectAnnotationBTestAP");
975
976        if (!expected.equals(log)) {
977            throw new AssertionError("Output does not match; output: " + log);
978        }
979    }
980
981    @SupportedAnnotationTypes("m2x/api.A")
982    public static final class SelectAnnotationATestAP extends AbstractProcessor {
983
984        @Override
985        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
986            System.err.println("SelectAnnotationATestAP");
987
988            return false;
989        }
990
991    }
992
993    @SupportedAnnotationTypes("api.B")
994    public static final class SelectAnnotationBTestAP extends AbstractProcessor {
995
996        @Override
997        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
998            System.err.println("SelectAnnotationBTestAP");
999
1000            return false;
1001        }
1002
1003    }
1004
1005    private static void writeFile(String content, Path base, String... pathElements) throws IOException {
1006        Path file = resolveFile(base, pathElements);
1007
1008        Files.createDirectories(file.getParent());
1009
1010        try (Writer out = Files.newBufferedWriter(file)) {
1011            out.append(content);
1012        }
1013    }
1014
1015    private static void assertNonNull(String msg, Object val) {
1016        if (val == null) {
1017            throw new AssertionError(msg);
1018        }
1019    }
1020
1021    private static void assertNull(String msg, Object val) {
1022        if (val != null) {
1023            throw new AssertionError(msg);
1024        }
1025    }
1026
1027    private static void assertEquals(Object expected, Object actual) {
1028        if (!Objects.equals(expected, actual)) {
1029            throw new AssertionError("expected: " + expected + "; actual=" + actual);
1030        }
1031    }
1032
1033    private static void assertFileExists(Path base, String... pathElements) {
1034        Path file = resolveFile(base, pathElements);
1035
1036        if (!Files.exists(file)) {
1037            throw new AssertionError("Expected file: " + file + " exist, but it does not.");
1038        }
1039    }
1040
1041    static Path resolveFile(Path base, String... pathElements) {
1042        Path file = base;
1043
1044        for (String el : pathElements) {
1045            file = file.resolve(el);
1046        }
1047
1048        return file;
1049    }
1050
1051}
1052