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