AnnotationsOnModules.java revision 3839:03c2338ea473
1/*
2 * Copyright (c) 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 8159602 8170549 8171255
27 * @summary Test annotations on module declaration.
28 * @library /tools/lib
29 * @modules jdk.compiler/com.sun.tools.javac.api
30 *          jdk.compiler/com.sun.tools.javac.main
31 *          jdk.jdeps/com.sun.tools.classfile
32 * @build toolbox.ToolBox toolbox.JavacTask ModuleTestBase
33 * @run main AnnotationsOnModules
34 */
35
36import java.io.File;
37import java.nio.file.Files;
38import java.nio.file.Path;
39import java.util.Arrays;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Objects;
43import java.util.Set;
44import java.util.stream.Collectors;
45
46import javax.annotation.processing.AbstractProcessor;
47import javax.annotation.processing.RoundEnvironment;
48import javax.annotation.processing.SupportedAnnotationTypes;
49import javax.annotation.processing.SupportedOptions;
50import javax.lang.model.element.AnnotationMirror;
51import javax.lang.model.element.ModuleElement;
52import javax.lang.model.element.TypeElement;
53
54import com.sun.tools.classfile.Attribute;
55import com.sun.tools.classfile.ClassFile;
56import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute;
57import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
58import toolbox.JavacTask;
59import toolbox.Task;
60import toolbox.Task.OutputKind;
61
62public class AnnotationsOnModules extends ModuleTestBase {
63
64    public static void main(String... args) throws Exception {
65        AnnotationsOnModules t = new AnnotationsOnModules();
66        t.runTests();
67    }
68
69    @Test
70    public void testSimpleAnnotation(Path base) throws Exception {
71        Path moduleSrc = base.resolve("module-src");
72        Path m1 = moduleSrc.resolve("m1x");
73
74        tb.writeJavaFiles(m1,
75                          "@Deprecated module m1x { }");
76
77        Path modulePath = base.resolve("module-path");
78
79        Files.createDirectories(modulePath);
80
81        new JavacTask(tb)
82                .options("--module-source-path", moduleSrc.toString())
83                .outdir(modulePath)
84                .files(findJavaFiles(m1))
85                .run()
86                .writeAll();
87
88        ClassFile cf = ClassFile.read(modulePath.resolve("m1x").resolve("module-info.class"));
89        RuntimeVisibleAnnotations_attribute annotations = (RuntimeVisibleAnnotations_attribute) cf.attributes.map.get(Attribute.RuntimeVisibleAnnotations);
90
91        if (annotations == null || annotations.annotations.length != 1) {
92            throw new AssertionError("Annotations not correct!");
93        }
94    }
95
96    @Test
97    public void testSimpleJavadocDeprecationTag(Path base) throws Exception {
98        Path moduleSrc = base.resolve("module-src");
99        Path m1 = moduleSrc.resolve("src1/A");
100
101        tb.writeJavaFiles(m1,
102                "/** @deprecated */ module A { }");
103
104        Path modulePath = base.resolve("module-path");
105
106        Files.createDirectories(modulePath);
107
108        List<String> warning = new JavacTask(tb)
109                .options("--module-source-path", m1.getParent().toString(),
110                        "-XDrawDiagnostics")
111                .outdir(modulePath)
112                .files(findJavaFiles(m1))
113                .run()
114                .writeAll()
115                .getOutputLines(OutputKind.DIRECT);
116
117        List<String> expected = List.of(
118                "module-info.java:1:20: compiler.warn.missing.deprecated.annotation",
119                "1 warning");
120        if (!warning.containsAll(expected)) {
121            throw new AssertionError("Expected output not found. Expected: " + expected);
122        }
123
124        Path m2 = base.resolve("src2/B");
125
126        tb.writeJavaFiles(m2,
127                "module B { requires A; }");
128        String log = new JavacTask(tb)
129                .options("--module-source-path", m2.getParent().toString(),
130                        "--module-path", modulePath.toString(),
131                        "-XDrawDiagnostics")
132                .outdir(modulePath)
133                .files(findJavaFiles(m2))
134                .run()
135                .writeAll()
136                .getOutput(OutputKind.DIRECT);
137
138        if (!log.isEmpty()) {
139            throw new AssertionError("Output is not empty. Expected no output and no warnings.");
140        }
141
142        ClassFile cf = ClassFile.read(modulePath.resolve("A").resolve("module-info.class"));
143        RuntimeVisibleAnnotations_attribute annotations = (RuntimeVisibleAnnotations_attribute) cf.attributes.map.get(Attribute.RuntimeVisibleAnnotations);
144
145        if (annotations != null && annotations.annotations.length > 0) {
146            throw new AssertionError("Found annotation attributes. Expected no annotations for javadoc @deprecated tag.");
147        }
148
149        if (cf.attributes.map.get(Attribute.Deprecated) != null) {
150            throw new AssertionError("Found Deprecated attribute. Expected no Deprecated attribute for javadoc @deprecated tag.");
151        }
152    }
153
154    @Test
155    public void testEnhancedDeprecatedAnnotation(Path base) throws Exception {
156        Path moduleSrc = base.resolve("module-src");
157        Path m1 = moduleSrc.resolve("src1/A");
158
159        tb.writeJavaFiles(m1,
160                "@Deprecated(since=\"10.X\", forRemoval=true) module A { }");
161
162        Path modulePath = base.resolve("module-path");
163
164        Files.createDirectories(modulePath);
165
166        new JavacTask(tb)
167                .options("--module-source-path", m1.getParent().toString())
168                .outdir(modulePath)
169                .files(findJavaFiles(m1))
170                .run()
171                .writeAll();
172
173        Path m2 = base.resolve("src2/B");
174
175        tb.writeJavaFiles(m2,
176                "module B { requires A; }");
177        List<String> log = new JavacTask(tb)
178                .options("--module-source-path", m2.getParent().toString(),
179                        "--module-path", modulePath.toString(),
180                        "-XDrawDiagnostics")
181                .outdir(modulePath)
182                .files(findJavaFiles(m2))
183                .run()
184                .writeAll()
185                .getOutputLines(OutputKind.DIRECT);
186
187        List<String> expected = List.of("module-info.java:1:21: compiler.warn.has.been.deprecated.for.removal.module: A",
188                "1 warning");
189        if (!log.containsAll(expected)) {
190            throw new AssertionError("Expected output not found. Expected: " + expected);
191        }
192
193        ClassFile cf = ClassFile.read(modulePath.resolve("A").resolve("module-info.class"));
194        RuntimeVisibleAnnotations_attribute annotations = (RuntimeVisibleAnnotations_attribute) cf.attributes.map.get(Attribute.RuntimeVisibleAnnotations);
195
196        if (annotations == null ) {
197            throw new AssertionError("Annotations not found!");
198        }
199        int length = annotations.annotations.length;
200        if (length != 1 ) {
201            throw new AssertionError("Incorrect number of annotations: " + length);
202        }
203        int pairsCount = annotations.annotations[0].num_element_value_pairs;
204        if (pairsCount != 2) {
205            throw new AssertionError("Incorrect number of key-value pairs in annotation: " + pairsCount + " Expected two: forRemoval and since.");
206        }
207    }
208
209    @Test
210    public void testDeprecatedModuleRequiresDeprecatedForRemovalModule(Path base) throws Exception {
211        Path moduleSrc = base.resolve("module-src");
212        Path m1 = moduleSrc.resolve("src1/A");
213
214        tb.writeJavaFiles(m1,
215                "@Deprecated(forRemoval=true) module A { }");
216
217        Path modulePath = base.resolve("module-path");
218
219        Files.createDirectories(modulePath);
220
221        new JavacTask(tb)
222                .options("--module-source-path", m1.getParent().toString())
223                .outdir(modulePath)
224                .files(findJavaFiles(m1))
225                .run()
226                .writeAll();
227
228        Path m2 = base.resolve("src2/B");
229
230        tb.writeJavaFiles(m2,
231                "@Deprecated(forRemoval=false) module B { requires A; }");
232        List<String> log = new JavacTask(tb)
233                .options("--module-source-path", m2.getParent().toString(),
234                        "--module-path", modulePath.toString(),
235                        "-XDrawDiagnostics")
236                .outdir(modulePath)
237                .files(findJavaFiles(m2))
238                .run()
239                .writeAll()
240                .getOutputLines(OutputKind.DIRECT);
241
242        List<String> expected = List.of("module-info.java:1:51: compiler.warn.has.been.deprecated.for.removal.module: A",
243                "1 warning");
244        if (!log.containsAll(expected)) {
245            throw new AssertionError("Expected output not found. Expected: " + expected);
246        }
247    }
248
249    @Test
250    public void testExportsAndOpensToDeprecatedModule(Path base) throws Exception {
251        Path moduleSrc = base.resolve("module-src");
252
253
254        tb.writeJavaFiles(moduleSrc.resolve("B"),
255                "@Deprecated module B { }");
256        tb.writeJavaFiles(moduleSrc.resolve("C"),
257                "@Deprecated(forRemoval=true) module C { }");
258
259        Path modulePath = base.resolve("module-path");
260        Files.createDirectories(modulePath);
261
262        new JavacTask(tb)
263                .options("--module-source-path", moduleSrc.toString())
264                .outdir(modulePath)
265                .files(findJavaFiles(moduleSrc))
266                .run()
267                .writeAll();
268
269        Path m1 = base.resolve("src1/A");
270
271        tb.writeJavaFiles(m1,
272                "module A { " +
273                        "exports p1 to B; opens p1 to B;" +
274                        "exports p2 to C; opens p2 to C;" +
275                        "exports p3 to B,C; opens p3 to B,C;" +
276                        "}",
277                "package p1; public class A { }",
278                "package p2; public class A { }",
279                "package p3; public class A { }");
280        String log = new JavacTask(tb)
281                .options("--module-source-path", m1.getParent().toString(),
282                        "--module-path", modulePath.toString(),
283                        "-XDrawDiagnostics")
284                .outdir(modulePath)
285                .files(findJavaFiles(m1))
286                .run()
287                .writeAll()
288                .getOutput(OutputKind.DIRECT);
289
290        if (!log.isEmpty()) {
291            throw new AssertionError("Output is not empty! " + log);
292        }
293    }
294
295    @Test
296    public void testAnnotationWithImport(Path base) throws Exception {
297        Path moduleSrc = base.resolve("module-src");
298        Path m1 = moduleSrc.resolve("m1x");
299
300        tb.writeJavaFiles(m1,
301                          "import m1x.A; @A module m1x { }",
302                          "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A {}");
303
304        Path modulePath = base.resolve("module-path");
305
306        Files.createDirectories(modulePath);
307
308        new JavacTask(tb)
309                .options("--module-source-path", moduleSrc.toString())
310                .outdir(modulePath)
311                .files(findJavaFiles(m1))
312                .run()
313                .writeAll();
314
315        ClassFile cf = ClassFile.read(modulePath.resolve("m1x").resolve("module-info.class"));
316        RuntimeInvisibleAnnotations_attribute annotations = (RuntimeInvisibleAnnotations_attribute) cf.attributes.map.get(Attribute.RuntimeInvisibleAnnotations);
317
318        if (annotations == null || annotations.annotations.length != 1) {
319            throw new AssertionError("Annotations not correct!");
320        }
321    }
322
323    @Test
324    public void testAnnotationWithImportFromAnotherModule(Path base) throws Exception {
325        Path moduleSrc = base.resolve("module-src");
326        Path m1 = moduleSrc.resolve("src1/A");
327
328        tb.writeJavaFiles(m1,
329                "module A { exports p1; exports p2; }",
330                "package p1; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A { }",
331                "package p2; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface B { }");
332
333        Path modulePath = base.resolve("module-path");
334
335        Files.createDirectories(modulePath);
336
337        new JavacTask(tb)
338                .options("--module-source-path", m1.getParent().toString())
339                .outdir(modulePath)
340                .files(findJavaFiles(m1))
341                .run()
342                .writeAll();
343
344        Path m2 = base.resolve("src2/B");
345
346        tb.writeJavaFiles(m2,
347                "import p1.A; @A @p2.B module B { requires A; }");
348        new JavacTask(tb)
349                .options("--module-source-path", m2.getParent().toString(),
350                        "--module-path", modulePath.toString()
351                )
352                .outdir(modulePath)
353                .files(findJavaFiles(m2))
354                .run()
355                .writeAll();
356
357        ClassFile cf = ClassFile.read(modulePath.resolve("B").resolve("module-info.class"));
358        RuntimeInvisibleAnnotations_attribute annotations = (RuntimeInvisibleAnnotations_attribute) cf.attributes.map.get(Attribute.RuntimeInvisibleAnnotations);
359
360        if (annotations == null ) {
361            throw new AssertionError("Annotations not found!");
362        }
363        int length = annotations.annotations.length;
364        if (length != 2 ) {
365            throw new AssertionError("Incorrect number of annotations: " + length);
366        }
367    }
368
369    @Test
370    public void testAnnotationWithImportAmbiguity(Path base) throws Exception {
371        Path moduleSrc = base.resolve("module-src");
372        Path m1 = moduleSrc.resolve("src1/A");
373
374        tb.writeJavaFiles(m1,
375                "module A { exports p1; exports p2; }",
376                "package p1; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A { }",
377                "package p2; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A { }");
378
379        Path modulePath = base.resolve("module-path");
380
381        Files.createDirectories(modulePath);
382
383        new JavacTask(tb)
384                .options("--module-source-path", m1.getParent().toString())
385                .outdir(modulePath)
386                .files(findJavaFiles(m1))
387                .run()
388                .writeAll();
389
390        Path m2 = base.resolve("src2/B");
391
392        tb.writeJavaFiles(m2,
393                "import p1.*; import p2.*; @A module B { requires A; }");
394        List<String> log = new JavacTask(tb)
395                .options("--module-source-path", m2.getParent().toString(),
396                        "--module-path", modulePath.toString(),
397                        "-XDrawDiagnostics"
398                )
399                .outdir(modulePath)
400                .files(findJavaFiles(m2))
401                .run(Task.Expect.FAIL)
402                .writeAll()
403                .getOutputLines(OutputKind.DIRECT);
404
405        List<String> expected = List.of("module-info.java:1:28: compiler.err.ref.ambiguous: A, kindname.class, p2.A, p2, kindname.class, p1.A, p1",
406                "module-info.java:1:27: compiler.err.annotation.type.not.applicable",
407                "2 errors");
408        if (!log.containsAll(expected)) {
409            throw new AssertionError("Expected output not found. Expected: " + expected);
410        }
411
412    }
413
414    @Test
415    public void testModuleInfoAnnotationsInAPI(Path base) throws Exception {
416        Path moduleSrc = base.resolve("module-src");
417        Path m1 = moduleSrc.resolve("m1x");
418
419        tb.writeJavaFiles(m1,
420                          "import m1x.*; @A @Deprecated @E @E module m1x { }",
421                          "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A {}",
422                          "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) @Repeatable(C.class) public @interface E {}",
423                          "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface C { public E[] value(); }");
424
425        Path modulePath = base.resolve("module-path");
426
427        Files.createDirectories(modulePath);
428
429        new JavacTask(tb)
430                .options("--module-source-path", moduleSrc.toString(),
431                         "-processor", AP.class.getName())
432                .outdir(modulePath)
433                .files(findJavaFiles(m1))
434                .run()
435                .writeAll();
436
437        Path src = base.resolve("src");
438
439        tb.writeJavaFiles(src,
440                          "class T {}");
441
442        Path out = base.resolve("out");
443
444        Files.createDirectories(out);
445
446        new JavacTask(tb)
447                .options("--module-path", modulePath.toString(),
448                         "--add-modules", "m1x",
449                         "-processor", AP.class.getName())
450                .outdir(out)
451                .files(findJavaFiles(src))
452                .run()
453                .writeAll();
454
455        new JavacTask(tb)
456                .options("--module-path", modulePath.toString() + File.pathSeparator + out.toString(),
457                         "--add-modules", "m1x",
458                         "-processor", AP.class.getName(),
459                         "-proc:only")
460                .classes("m1x/m1x.A")
461                .files(findJavaFiles(src))
462                .run()
463                .writeAll();
464    }
465
466    @SupportedAnnotationTypes("*")
467    public static final class AP extends AbstractProcessor {
468
469        @Override
470        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
471            ModuleElement m1 = processingEnv.getElementUtils().getModuleElement("m1x");
472            Set<String> actualAnnotations = new HashSet<>();
473            Set<String> expectedAnnotations =
474                    new HashSet<>(Arrays.asList("@m1x.A", "@java.lang.Deprecated", "@m1x.C({@m1x.E, @m1x.E})"));
475
476            for (AnnotationMirror am : m1.getAnnotationMirrors()) {
477                actualAnnotations.add(am.toString());
478            }
479
480            if (!expectedAnnotations.equals(actualAnnotations)) {
481                throw new AssertionError("Incorrect annotations: " + actualAnnotations);
482            }
483
484            return false;
485        }
486
487    }
488
489    @Test
490    public void testModuleDeprecation(Path base) throws Exception {
491        Path moduleSrc = base.resolve("module-src");
492        Path m1 = moduleSrc.resolve("m1x");
493
494        tb.writeJavaFiles(m1,
495                          "@Deprecated module m1x { }");
496
497        Path m2 = moduleSrc.resolve("m2x");
498
499        tb.writeJavaFiles(m2,
500                          "@Deprecated module m2x { }");
501
502        Path m3 = moduleSrc.resolve("m3x");
503
504        Path modulePath = base.resolve("module-path");
505
506        Files.createDirectories(modulePath);
507
508        List<String> actual;
509        List<String> expected;
510
511        String DEPRECATED_JAVADOC = "/** @deprecated */";
512        for (String suppress : new String[] {"", DEPRECATED_JAVADOC, "@Deprecated ", "@SuppressWarnings(\"deprecation\") "}) {
513            tb.writeJavaFiles(m3,
514                              suppress + "module m3x {\n" +
515                              "    requires m1x;\n" +
516                              "    exports api to m1x, m2x;\n" +
517                              "}",
518                              "package api; public class Api { }");
519            System.err.println("compile m3x");
520            actual = new JavacTask(tb)
521                    .options("--module-source-path", moduleSrc.toString(),
522                             "-XDrawDiagnostics")
523                    .outdir(modulePath)
524                    .files(findJavaFiles(moduleSrc))
525                    .run()
526                    .writeAll()
527                    .getOutputLines(OutputKind.DIRECT);
528
529            if (suppress.isEmpty()) {
530                expected = Arrays.asList(
531                        "- compiler.note.deprecated.filename: module-info.java",
532                        "- compiler.note.deprecated.recompile");
533            } else if (suppress.equals(DEPRECATED_JAVADOC)) {
534                expected = Arrays.asList(
535                        "module-info.java:1:19: compiler.warn.missing.deprecated.annotation",
536                        "- compiler.note.deprecated.filename: module-info.java",
537                        "- compiler.note.deprecated.recompile",
538                        "1 warning");
539            } else {
540                expected = Arrays.asList("");
541            }
542
543            if (!expected.equals(actual)) {
544                throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
545            }
546
547            System.err.println("compile m3x with -Xlint:-deprecation");
548            actual = new JavacTask(tb)
549                    .options("--module-source-path", moduleSrc.toString(),
550                             "-XDrawDiagnostics",
551                             "-Xlint:deprecation")
552                    .outdir(modulePath)
553                    .files(findJavaFiles(moduleSrc))
554                    .run()
555                    .writeAll()
556                    .getOutputLines(OutputKind.DIRECT);
557
558            if (suppress.isEmpty()) {
559                expected = Arrays.asList(
560                        "module-info.java:2:14: compiler.warn.has.been.deprecated.module: m1x",
561                        "1 warning");
562            } else if (suppress.equals(DEPRECATED_JAVADOC)) {
563                expected = Arrays.asList(
564                        "module-info.java:1:19: compiler.warn.missing.deprecated.annotation",
565                        "module-info.java:2:14: compiler.warn.has.been.deprecated.module: m1x",
566                        "2 warnings");
567            } else {
568                expected = Arrays.asList("");
569            }
570
571            if (!expected.equals(actual)) {
572                throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
573            }
574
575            //load the deprecated module-infos from classfile:
576            System.err.println("compile m3x with -Xlint:-deprecation, loading deprecated modules from classes");
577            actual = new JavacTask(tb)
578                    .options("--module-path", modulePath.toString(),
579                             "-XDrawDiagnostics",
580                             "-Xlint:deprecation")
581                    .outdir(modulePath.resolve("m3x"))
582                    .files(findJavaFiles(moduleSrc.resolve("m3x")))
583                    .run()
584                    .writeAll()
585                    .getOutputLines(OutputKind.DIRECT);
586
587            if (!expected.equals(actual)) {
588                throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
589            }
590        }
591    }
592
593    @Test
594    public void testAttributeValues(Path base) throws Exception {
595        class TestCase {
596            public final String extraDecl;
597            public final String decl;
598            public final String use;
599            public final String expectedAnnotations;
600
601            public TestCase(String extraDecl, String decl, String use, String expectedAnnotations) {
602                this.extraDecl = extraDecl;
603                this.decl = decl;
604                this.use = use;
605                this.expectedAnnotations = expectedAnnotations;
606            }
607        }
608
609        TestCase[] testCases = new TestCase[] {
610            new TestCase("package test; public enum E {A, B;}",
611                         "public E value();",
612                         "test.E.A",
613                         "@test.A(test.E.A)"),
614            new TestCase("package test; public enum E {A, B;}",
615                         "public E[] value();",
616                         "{test.E.A, test.E.B}",
617                         "@test.A({test.E.A, test.E.B})"),
618            new TestCase("package test; public class Extra {}",
619                         "public Class value();",
620                         "test.Extra.class",
621                         "@test.A(test.Extra.class)"),
622            new TestCase("package test; public class Extra {}",
623                         "public Class[] value();",
624                         "{test.Extra.class, String.class}",
625                         "@test.A({test.Extra.class, java.lang.String.class})"),
626            new TestCase("package test; public @interface Extra { public Class value(); }",
627                         "public test.Extra value();",
628                         "@test.Extra(String.class)",
629                         "@test.A(@test.Extra(java.lang.String.class))"),
630            new TestCase("package test; public @interface Extra { public Class value(); }",
631                         "public test.Extra[] value();",
632                         "{@test.Extra(String.class), @test.Extra(Integer.class)}",
633                         "@test.A({@test.Extra(java.lang.String.class), @test.Extra(java.lang.Integer.class)})"),
634            new TestCase("package test; public class Any { }",
635                         "public int value();",
636                         "1",
637                         "@test.A(1)"),
638            new TestCase("package test; public class Any { }",
639                         "public int[] value();",
640                         "{1, 2}",
641                         "@test.A({1, 2})"),
642            new TestCase("package test; public enum E {A;}",
643                        "int integer(); boolean flag(); double value(); String string(); E enumeration(); ",
644                        "enumeration = test.E.A, integer = 42, flag = true, value = 3.5, string = \"Text\"",
645                        "@test.A(enumeration=test.E.A, integer=42, flag=true, value=3.5, string=\"Text\")"),
646        };
647
648        Path extraSrc = base.resolve("extra-src");
649        tb.writeJavaFiles(extraSrc,
650                          "class Any {}");
651
652        int count = 0;
653
654        for (TestCase tc : testCases) {
655            Path testBase = base.resolve(String.valueOf(count));
656            Path moduleSrc = testBase.resolve("module-src");
657            Path m = moduleSrc.resolve("m");
658
659            tb.writeJavaFiles(m,
660                              "@test.A(" + tc.use + ") module m { }",
661                              "package test; @java.lang.annotation.Target(java.lang.annotation.ElementType.MODULE) public @interface A { " + tc.decl + "}",
662                              tc.extraDecl);
663
664            Path modulePath = testBase.resolve("module-path");
665
666            Files.createDirectories(modulePath);
667
668            new JavacTask(tb)
669                .options("--module-source-path", moduleSrc.toString())
670                .outdir(modulePath)
671                .files(findJavaFiles(moduleSrc))
672                .run()
673                .writeAll();
674
675            Path classes = testBase.resolve("classes");
676
677            Files.createDirectories(classes);
678
679            new JavacTask(tb)
680                .options("--module-path", modulePath.toString(),
681                         "--add-modules", "m",
682                         "-processorpath", System.getProperty("test.classes"),
683                         "-processor", ProxyTypeValidator.class.getName(),
684                         "-A" + OPT_EXPECTED_ANNOTATIONS + "=" + tc.expectedAnnotations)
685                .outdir(classes)
686                .files(findJavaFiles(extraSrc))
687                .run()
688                .writeAll();
689        }
690    }
691
692    private static final String OPT_EXPECTED_ANNOTATIONS = "expectedAnnotations";
693
694    @SupportedAnnotationTypes("*")
695    @SupportedOptions(OPT_EXPECTED_ANNOTATIONS)
696    public static final class ProxyTypeValidator extends AbstractProcessor {
697
698        @Override
699        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
700            ModuleElement m = processingEnv.getElementUtils().getModuleElement("m");
701            String actualTypes = m.getAnnotationMirrors()
702                                  .stream()
703                                  .map(am -> am.toString())
704                                  .collect(Collectors.joining(", "));
705            if (!Objects.equals(actualTypes, processingEnv.getOptions().get(OPT_EXPECTED_ANNOTATIONS))) {
706                throw new IllegalStateException("Expected annotations not found, actual: " + actualTypes);
707            }
708            return false;
709        }
710
711    }
712
713}
714