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 8171322
  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 
  36 import java.io.File;
  37 import java.nio.file.Files;
  38 import java.nio.file.Path;
  39 import java.util.Arrays;
  40 import java.util.HashSet;
  41 import java.util.List;
  42 import java.util.Objects;
  43 import java.util.Set;
  44 import java.util.stream.Collectors;
  45 
  46 import javax.annotation.processing.AbstractProcessor;
  47 import javax.annotation.processing.RoundEnvironment;
  48 import javax.annotation.processing.SupportedAnnotationTypes;
  49 import javax.annotation.processing.SupportedOptions;
  50 import javax.lang.model.element.AnnotationMirror;
  51 import javax.lang.model.element.ModuleElement;
  52 import javax.lang.model.element.TypeElement;
  53 
  54 import com.sun.tools.classfile.Attribute;
  55 import com.sun.tools.classfile.ClassFile;
  56 import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute;
  57 import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
  58 import toolbox.JavacTask;
  59 import toolbox.Task;
  60 import toolbox.Task.OutputKind;
  61 
  62 public 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 AAA { }",
 377                 "package p2; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface AAA { }");
 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.*; @AAA 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: AAA, kindname.class, p2.AAA, p2, kindname.class, p1.AAA, p1",
 406                 "1 error");
 407         if (!log.containsAll(expected)) {
 408             throw new AssertionError("Expected output not found. Expected: " + expected);
 409         }
 410 
 411     }
 412 
 413     @Test
 414     public void testModuleInfoAnnotationsInAPI(Path base) throws Exception {
 415         Path moduleSrc = base.resolve("module-src");
 416         Path m1 = moduleSrc.resolve("m1x");
 417 
 418         tb.writeJavaFiles(m1,
 419                           "import m1x.*; @A @Deprecated @E @E module m1x { }",
 420                           "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A {}",
 421                           "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) @Repeatable(C.class) public @interface E {}",
 422                           "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface C { public E[] value(); }");
 423 
 424         Path modulePath = base.resolve("module-path");
 425 
 426         Files.createDirectories(modulePath);
 427 
 428         new JavacTask(tb)
 429                 .options("--module-source-path", moduleSrc.toString(),
 430                          "-processor", AP.class.getName())
 431                 .outdir(modulePath)
 432                 .files(findJavaFiles(m1))
 433                 .run()
 434                 .writeAll();
 435 
 436         Path src = base.resolve("src");
 437 
 438         tb.writeJavaFiles(src,
 439                           "class T {}");
 440 
 441         Path out = base.resolve("out");
 442 
 443         Files.createDirectories(out);
 444 
 445         new JavacTask(tb)
 446                 .options("--module-path", modulePath.toString(),
 447                          "--add-modules", "m1x",
 448                          "-processor", AP.class.getName())
 449                 .outdir(out)
 450                 .files(findJavaFiles(src))
 451                 .run()
 452                 .writeAll();
 453 
 454         new JavacTask(tb)
 455                 .options("--module-path", modulePath.toString() + File.pathSeparator + out.toString(),
 456                          "--add-modules", "m1x",
 457                          "-processor", AP.class.getName(),
 458                          "-proc:only")
 459                 .classes("m1x/m1x.A")
 460                 .files(findJavaFiles(src))
 461                 .run()
 462                 .writeAll();
 463     }
 464 
 465     @SupportedAnnotationTypes("*")
 466     public static final class AP extends AbstractProcessor {
 467 
 468         @Override
 469         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 470             ModuleElement m1 = processingEnv.getElementUtils().getModuleElement("m1x");
 471             Set<String> actualAnnotations = new HashSet<>();
 472             Set<String> expectedAnnotations =
 473                     new HashSet<>(Arrays.asList("@m1x.A", "@java.lang.Deprecated", "@m1x.C({@m1x.E, @m1x.E})"));
 474 
 475             for (AnnotationMirror am : m1.getAnnotationMirrors()) {
 476                 actualAnnotations.add(am.toString());
 477             }
 478 
 479             if (!expectedAnnotations.equals(actualAnnotations)) {
 480                 throw new AssertionError("Incorrect annotations: " + actualAnnotations);
 481             }
 482 
 483             return false;
 484         }
 485 
 486     }
 487 
 488     @Test
 489     public void testModuleDeprecation(Path base) throws Exception {
 490         Path moduleSrc = base.resolve("module-src");
 491         Path m1 = moduleSrc.resolve("m1x");
 492 
 493         tb.writeJavaFiles(m1,
 494                           "@Deprecated module m1x { }");
 495 
 496         Path m2 = moduleSrc.resolve("m2x");
 497 
 498         tb.writeJavaFiles(m2,
 499                           "@Deprecated module m2x { }");
 500 
 501         Path m3 = moduleSrc.resolve("m3x");
 502 
 503         Path modulePath = base.resolve("module-path");
 504 
 505         Files.createDirectories(modulePath);
 506 
 507         List<String> actual;
 508         List<String> expected;
 509 
 510         String DEPRECATED_JAVADOC = "/** @deprecated */";
 511         for (String suppress : new String[] {"", DEPRECATED_JAVADOC, "@Deprecated ", "@SuppressWarnings(\"deprecation\") "}) {
 512             tb.writeJavaFiles(m3,
 513                               suppress + "module m3x {\n" +
 514                               "    requires m1x;\n" +
 515                               "    exports api to m1x, m2x;\n" +
 516                               "}",
 517                               "package api; public class Api { }");
 518             System.err.println("compile m3x");
 519             actual = new JavacTask(tb)
 520                     .options("--module-source-path", moduleSrc.toString(),
 521                              "-XDrawDiagnostics")
 522                     .outdir(modulePath)
 523                     .files(findJavaFiles(moduleSrc))
 524                     .run()
 525                     .writeAll()
 526                     .getOutputLines(OutputKind.DIRECT);
 527 
 528             if (suppress.isEmpty()) {
 529                 expected = Arrays.asList(
 530                         "- compiler.note.deprecated.filename: module-info.java",
 531                         "- compiler.note.deprecated.recompile");
 532             } else if (suppress.equals(DEPRECATED_JAVADOC)) {
 533                 expected = Arrays.asList(
 534                         "module-info.java:1:19: compiler.warn.missing.deprecated.annotation",
 535                         "- compiler.note.deprecated.filename: module-info.java",
 536                         "- compiler.note.deprecated.recompile",
 537                         "1 warning");
 538             } else {
 539                 expected = Arrays.asList("");
 540             }
 541 
 542             if (!expected.equals(actual)) {
 543                 throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
 544             }
 545 
 546             System.err.println("compile m3x with -Xlint:-deprecation");
 547             actual = new JavacTask(tb)
 548                     .options("--module-source-path", moduleSrc.toString(),
 549                              "-XDrawDiagnostics",
 550                              "-Xlint:deprecation")
 551                     .outdir(modulePath)
 552                     .files(findJavaFiles(moduleSrc))
 553                     .run()
 554                     .writeAll()
 555                     .getOutputLines(OutputKind.DIRECT);
 556 
 557             if (suppress.isEmpty()) {
 558                 expected = Arrays.asList(
 559                         "module-info.java:2:14: compiler.warn.has.been.deprecated.module: m1x",
 560                         "1 warning");
 561             } else if (suppress.equals(DEPRECATED_JAVADOC)) {
 562                 expected = Arrays.asList(
 563                         "module-info.java:1:19: compiler.warn.missing.deprecated.annotation",
 564                         "module-info.java:2:14: compiler.warn.has.been.deprecated.module: m1x",
 565                         "2 warnings");
 566             } else {
 567                 expected = Arrays.asList("");
 568             }
 569 
 570             if (!expected.equals(actual)) {
 571                 throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
 572             }
 573 
 574             //load the deprecated module-infos from classfile:
 575             System.err.println("compile m3x with -Xlint:-deprecation, loading deprecated modules from classes");
 576             actual = new JavacTask(tb)
 577                     .options("--module-path", modulePath.toString(),
 578                              "-XDrawDiagnostics",
 579                              "-Xlint:deprecation")
 580                     .outdir(modulePath.resolve("m3x"))
 581                     .files(findJavaFiles(moduleSrc.resolve("m3x")))
 582                     .run()
 583                     .writeAll()
 584                     .getOutputLines(OutputKind.DIRECT);
 585 
 586             if (!expected.equals(actual)) {
 587                 throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
 588             }
 589         }
 590     }
 591 
 592     @Test
 593     public void testAttributeValues(Path base) throws Exception {
 594         class TestCase {
 595             public final String extraDecl;
 596             public final String decl;
 597             public final String use;
 598             public final String expectedAnnotations;
 599 
 600             public TestCase(String extraDecl, String decl, String use, String expectedAnnotations) {
 601                 this.extraDecl = extraDecl;
 602                 this.decl = decl;
 603                 this.use = use;
 604                 this.expectedAnnotations = expectedAnnotations;
 605             }
 606         }
 607 
 608         TestCase[] testCases = new TestCase[] {
 609             new TestCase("package test; public enum E {A, B;}",
 610                          "public E value();",
 611                          "test.E.A",
 612                          "@test.A(A)"),
 613             new TestCase("package test; public enum E {A, B;}",
 614                          "public E[] value();",
 615                          "{test.E.A, test.E.B}",
 616                          "@test.A({A, B})"),
 617             new TestCase("package test; public class Extra {}",
 618                          "public Class value();",
 619                          "test.Extra.class",
 620                          "@test.A(test.Extra.class)"),
 621             new TestCase("package test; public class Extra {}",
 622                          "public Class[] value();",
 623                          "{test.Extra.class, String.class}",
 624                          "@test.A({test.Extra.class, java.lang.String.class})"),
 625             new TestCase("package test; public @interface Extra { public Class value(); }",
 626                          "public test.Extra value();",
 627                          "@test.Extra(String.class)",
 628                          "@test.A(@test.Extra(java.lang.String.class))"),
 629             new TestCase("package test; public @interface Extra { public Class value(); }",
 630                          "public test.Extra[] value();",
 631                          "{@test.Extra(String.class), @test.Extra(Integer.class)}",
 632                          "@test.A({@test.Extra(java.lang.String.class), @test.Extra(java.lang.Integer.class)})"),
 633             new TestCase("package test; public class Any { }",
 634                          "public int value();",
 635                          "1",
 636                          "@test.A(1)"),
 637             new TestCase("package test; public class Any { }",
 638                          "public int[] value();",
 639                          "{1, 2}",
 640                          "@test.A({1, 2})"),
 641             new TestCase("package test; public enum E {A;}",
 642                         "int integer(); boolean flag(); double value(); String string(); E enumeration(); ",
 643                         "enumeration = test.E.A, integer = 42, flag = true, value = 3.5, string = \"Text\"",
 644                         "@test.A(enumeration=A, integer=42, flag=true, value=3.5, string=\"Text\")"),
 645         };
 646 
 647         Path extraSrc = base.resolve("extra-src");
 648         tb.writeJavaFiles(extraSrc,
 649                           "class Any {}");
 650 
 651         int count = 0;
 652 
 653         for (TestCase tc : testCases) {
 654             Path testBase = base.resolve(String.valueOf(count));
 655             Path moduleSrc = testBase.resolve("module-src");
 656             Path m = moduleSrc.resolve("m");
 657 
 658             tb.writeJavaFiles(m,
 659                               "@test.A(" + tc.use + ") module m { }",
 660                               "package test; @java.lang.annotation.Target(java.lang.annotation.ElementType.MODULE) public @interface A { " + tc.decl + "}",
 661                               tc.extraDecl);
 662 
 663             Path modulePath = testBase.resolve("module-path");
 664 
 665             Files.createDirectories(modulePath);
 666 
 667             new JavacTask(tb)
 668                 .options("--module-source-path", moduleSrc.toString())
 669                 .outdir(modulePath)
 670                 .files(findJavaFiles(moduleSrc))
 671                 .run()
 672                 .writeAll();
 673 
 674             Path classes = testBase.resolve("classes");
 675 
 676             Files.createDirectories(classes);
 677 
 678             new JavacTask(tb)
 679                 .options("--module-path", modulePath.toString(),
 680                          "--add-modules", "m",
 681                          "-processorpath", System.getProperty("test.classes"),
 682                          "-processor", ProxyTypeValidator.class.getName(),
 683                          "-A" + OPT_EXPECTED_ANNOTATIONS + "=" + tc.expectedAnnotations)
 684                 .outdir(classes)
 685                 .files(findJavaFiles(extraSrc))
 686                 .run()
 687                 .writeAll();
 688         }
 689     }
 690 
 691     private static final String OPT_EXPECTED_ANNOTATIONS = "expectedAnnotations";
 692 
 693     @SupportedAnnotationTypes("*")
 694     @SupportedOptions(OPT_EXPECTED_ANNOTATIONS)
 695     public static final class ProxyTypeValidator extends AbstractProcessor {
 696 
 697         @Override
 698         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 699             ModuleElement m = processingEnv.getElementUtils().getModuleElement("m");
 700             String actualTypes = m.getAnnotationMirrors()
 701                                   .stream()
 702                                   .map(am -> am.toString())
 703                                   .collect(Collectors.joining(", "));
 704             if (!Objects.equals(actualTypes, processingEnv.getOptions().get(OPT_EXPECTED_ANNOTATIONS))) {
 705                 throw new IllegalStateException("Expected annotations not found, actual: " + actualTypes);
 706             }
 707             return false;
 708         }
 709 
 710     }
 711 
 712 }