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  * @summary Test --add-modules and --limit-modules; also test the "enabled" modules.
  27  * @library /tools/lib
  28  * @modules jdk.compiler/com.sun.tools.javac.api
  29  *          jdk.compiler/com.sun.tools.javac.main
  30  *          jdk.jdeps/com.sun.tools.classfile
  31  * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask toolbox.JavaTask ModuleTestBase
  32  * @run main AnnotationsOnModules
  33  */
  34 
  35 import java.io.File;
  36 import java.nio.file.Files;
  37 import java.nio.file.Path;
  38 import java.util.Arrays;
  39 import java.util.HashSet;
  40 import java.util.List;
  41 import java.util.Objects;
  42 import java.util.Set;
  43 import java.util.stream.Collectors;
  44 
  45 import javax.annotation.processing.AbstractProcessor;
  46 import javax.annotation.processing.RoundEnvironment;
  47 import javax.annotation.processing.SupportedAnnotationTypes;
  48 import javax.annotation.processing.SupportedOptions;
  49 import javax.lang.model.element.AnnotationMirror;
  50 import javax.lang.model.element.ModuleElement;
  51 import javax.lang.model.element.TypeElement;
  52 
  53 import com.sun.tools.classfile.Attribute;
  54 import com.sun.tools.classfile.ClassFile;
  55 import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute;
  56 import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
  57 import toolbox.JavacTask;
  58 import toolbox.Task.OutputKind;
  59 
  60 public class AnnotationsOnModules extends ModuleTestBase {
  61 
  62     public static void main(String... args) throws Exception {
  63         AnnotationsOnModules t = new AnnotationsOnModules();
  64         t.runTests();
  65     }
  66 
  67     @Test
  68     public void testSimpleAnnotation(Path base) throws Exception {
  69         Path moduleSrc = base.resolve("module-src");
  70         Path m1 = moduleSrc.resolve("m1x");
  71 
  72         tb.writeJavaFiles(m1,
  73                           "@Deprecated module m1x { }");
  74 
  75         Path modulePath = base.resolve("module-path");
  76 
  77         Files.createDirectories(modulePath);
  78 
  79         new JavacTask(tb)
  80                 .options("--module-source-path", moduleSrc.toString())
  81                 .outdir(modulePath)
  82                 .files(findJavaFiles(m1))
  83                 .run()
  84                 .writeAll();
  85 
  86         ClassFile cf = ClassFile.read(modulePath.resolve("m1x").resolve("module-info.class"));
  87         RuntimeVisibleAnnotations_attribute annotations = (RuntimeVisibleAnnotations_attribute) cf.attributes.map.get(Attribute.RuntimeVisibleAnnotations);
  88 
  89         if (annotations == null || annotations.annotations.length != 1) {
  90             throw new AssertionError("Annotations not correct!");
  91         }
  92     }
  93 
  94     @Test
  95     public void testAnnotationWithImport(Path base) throws Exception {
  96         Path moduleSrc = base.resolve("module-src");
  97         Path m1 = moduleSrc.resolve("m1x");
  98 
  99         tb.writeJavaFiles(m1,
 100                           "import m1x.A; @A module m1x { }",
 101                           "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A {}");
 102 
 103         Path modulePath = base.resolve("module-path");
 104 
 105         Files.createDirectories(modulePath);
 106 
 107         new JavacTask(tb)
 108                 .options("--module-source-path", moduleSrc.toString())
 109                 .outdir(modulePath)
 110                 .files(findJavaFiles(m1))
 111                 .run()
 112                 .writeAll();
 113 
 114         ClassFile cf = ClassFile.read(modulePath.resolve("m1x").resolve("module-info.class"));
 115         RuntimeInvisibleAnnotations_attribute annotations = (RuntimeInvisibleAnnotations_attribute) cf.attributes.map.get(Attribute.RuntimeInvisibleAnnotations);
 116 
 117         if (annotations == null || annotations.annotations.length != 1) {
 118             throw new AssertionError("Annotations not correct!");
 119         }
 120     }
 121 
 122     @Test
 123     public void testModuleInfoAnnotationsInAPI(Path base) throws Exception {
 124         Path moduleSrc = base.resolve("module-src");
 125         Path m1 = moduleSrc.resolve("m1x");
 126 
 127         tb.writeJavaFiles(m1,
 128                           "import m1x.*; @A @Deprecated @E @E module m1x { }",
 129                           "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface A {}",
 130                           "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) @Repeatable(C.class) public @interface E {}",
 131                           "package m1x; import java.lang.annotation.*; @Target(ElementType.MODULE) public @interface C { public E[] value(); }");
 132 
 133         Path modulePath = base.resolve("module-path");
 134 
 135         Files.createDirectories(modulePath);
 136 
 137         new JavacTask(tb)
 138                 .options("--module-source-path", moduleSrc.toString(),
 139                          "-processor", AP.class.getName())
 140                 .outdir(modulePath)
 141                 .files(findJavaFiles(m1))
 142                 .run()
 143                 .writeAll();
 144 
 145         Path src = base.resolve("src");
 146 
 147         tb.writeJavaFiles(src,
 148                           "class T {}");
 149 
 150         Path out = base.resolve("out");
 151 
 152         Files.createDirectories(out);
 153 
 154         new JavacTask(tb)
 155                 .options("--module-path", modulePath.toString(),
 156                          "--add-modules", "m1x",
 157                          "-processor", AP.class.getName())
 158                 .outdir(out)
 159                 .files(findJavaFiles(src))
 160                 .run()
 161                 .writeAll();
 162 
 163         new JavacTask(tb)
 164                 .options("--module-path", modulePath.toString() + File.pathSeparator + out.toString(),
 165                          "--add-modules", "m1x",
 166                          "-processor", AP.class.getName(),
 167                          "-proc:only")
 168                 .classes("m1x/m1x.A")
 169                 .files(findJavaFiles(src))
 170                 .run()
 171                 .writeAll();
 172     }
 173 
 174     @SupportedAnnotationTypes("*")
 175     public static final class AP extends AbstractProcessor {
 176 
 177         @Override
 178         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 179             ModuleElement m1 = processingEnv.getElementUtils().getModuleElement("m1x");
 180             Set<String> actualAnnotations = new HashSet<>();
 181             Set<String> expectedAnnotations =
 182                     new HashSet<>(Arrays.asList("@m1x.A", "@java.lang.Deprecated", "@m1x.C({@m1x.E, @m1x.E})"));
 183 
 184             for (AnnotationMirror am : m1.getAnnotationMirrors()) {
 185                 actualAnnotations.add(am.toString());
 186             }
 187 
 188             if (!expectedAnnotations.equals(actualAnnotations)) {
 189                 throw new AssertionError("Incorrect annotations: " + actualAnnotations);
 190             }
 191 
 192             return false;
 193         }
 194 
 195     }
 196 
 197     @Test
 198     public void testModuleDeprecation(Path base) throws Exception {
 199         Path moduleSrc = base.resolve("module-src");
 200         Path m1 = moduleSrc.resolve("m1x");
 201 
 202         tb.writeJavaFiles(m1,
 203                           "@Deprecated module m1x { }");
 204 
 205         Path m2 = moduleSrc.resolve("m2x");
 206 
 207         tb.writeJavaFiles(m2,
 208                           "@Deprecated module m2x { }");
 209 
 210         Path m3 = moduleSrc.resolve("m3x");
 211 
 212         Path modulePath = base.resolve("module-path");
 213 
 214         Files.createDirectories(modulePath);
 215 
 216         List<String> actual;
 217         List<String> expected;
 218 
 219         for (String suppress : new String[] {"", "@Deprecated ", "@SuppressWarnings(\"deprecation\") "}) {
 220             tb.writeJavaFiles(m3,
 221                               suppress + "module m3x {\n" +
 222                               "    requires m1x;\n" +
 223                               "    exports api to m1x, m2x;\n" +
 224                               "}",
 225                               "package api; public class Api { }");
 226             System.err.println("compile m3x");
 227             actual = new JavacTask(tb)
 228                     .options("--module-source-path", moduleSrc.toString(),
 229                              "-XDrawDiagnostics")
 230                     .outdir(modulePath)
 231                     .files(findJavaFiles(moduleSrc))
 232                     .run()
 233                     .writeAll()
 234                     .getOutputLines(OutputKind.DIRECT);
 235 
 236             if (suppress.isEmpty()) {
 237                 expected = Arrays.asList(
 238                         "- compiler.note.deprecated.filename: module-info.java",
 239                         "- compiler.note.deprecated.recompile");
 240             } else {
 241                 expected = Arrays.asList("");
 242             }
 243 
 244             if (!expected.equals(actual)) {
 245                 throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
 246             }
 247 
 248             System.err.println("compile m3x with -Xlint:-deprecation");
 249             actual = new JavacTask(tb)
 250                     .options("--module-source-path", moduleSrc.toString(),
 251                              "-XDrawDiagnostics",
 252                              "-Xlint:deprecation")
 253                     .outdir(modulePath)
 254                     .files(findJavaFiles(moduleSrc))
 255                     .run()
 256                     .writeAll()
 257                     .getOutputLines(OutputKind.DIRECT);
 258 
 259             if (suppress.isEmpty()) {
 260                 expected = Arrays.asList(
 261                         "module-info.java:2:14: compiler.warn.has.been.deprecated.module: m1x",
 262                         "1 warning");
 263             } else {
 264                 expected = Arrays.asList("");
 265             }
 266 
 267             if (!expected.equals(actual)) {
 268                 throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
 269             }
 270 
 271             //load the deprecated module-infos from classfile:
 272             System.err.println("compile m3x with -Xlint:-deprecation, loading deprecated modules from classes");
 273             actual = new JavacTask(tb)
 274                     .options("--module-path", modulePath.toString(),
 275                              "-XDrawDiagnostics",
 276                              "-Xlint:deprecation")
 277                     .outdir(modulePath.resolve("m3x"))
 278                     .files(findJavaFiles(moduleSrc.resolve("m3x")))
 279                     .run()
 280                     .writeAll()
 281                     .getOutputLines(OutputKind.DIRECT);
 282 
 283             if (!expected.equals(actual)) {
 284                 throw new AssertionError("Unexpected output: " + actual + "; suppress: " + suppress);
 285             }
 286         }
 287     }
 288 
 289     @Test
 290     public void testAttributeValues(Path base) throws Exception {
 291         class TestCase {
 292             public final String extraDecl;
 293             public final String decl;
 294             public final String use;
 295             public final String expectedAnnotations;
 296 
 297             public TestCase(String extraDecl, String decl, String use, String expectedAnnotations) {
 298                 this.extraDecl = extraDecl;
 299                 this.decl = decl;
 300                 this.use = use;
 301                 this.expectedAnnotations = expectedAnnotations;
 302             }
 303         }
 304 
 305         TestCase[] testCases = new TestCase[] {
 306             new TestCase("package test; public enum E {A, B;}",
 307                          "public E value();",
 308                          "test.E.A",
 309                          "@test.A(test.E.A)"),
 310             new TestCase("package test; public enum E {A, B;}",
 311                          "public E[] value();",
 312                          "{test.E.A, test.E.B}",
 313                          "@test.A({test.E.A, test.E.B})"),
 314             new TestCase("package test; public class Extra {}",
 315                          "public Class value();",
 316                          "test.Extra.class",
 317                          "@test.A(test.Extra.class)"),
 318             new TestCase("package test; public class Extra {}",
 319                          "public Class[] value();",
 320                          "{test.Extra.class, String.class}",
 321                          "@test.A({test.Extra.class, java.lang.String.class})"),
 322             new TestCase("package test; public @interface Extra { public Class value(); }",
 323                          "public test.Extra value();",
 324                          "@test.Extra(String.class)",
 325                          "@test.A(@test.Extra(java.lang.String.class))"),
 326             new TestCase("package test; public @interface Extra { public Class value(); }",
 327                          "public test.Extra[] value();",
 328                          "{@test.Extra(String.class), @test.Extra(Integer.class)}",
 329                          "@test.A({@test.Extra(java.lang.String.class), @test.Extra(java.lang.Integer.class)})"),
 330             new TestCase("package test; public class Any { }",
 331                          "public int value();",
 332                          "1",
 333                          "@test.A(1)"),
 334             new TestCase("package test; public class Any { }",
 335                          "public int[] value();",
 336                          "{1, 2}",
 337                          "@test.A({1, 2})"),
 338         };
 339 
 340         Path extraSrc = base.resolve("extra-src");
 341         tb.writeJavaFiles(extraSrc,
 342                           "class Any {}");
 343 
 344         int count = 0;
 345 
 346         for (TestCase tc : testCases) {
 347             Path testBase = base.resolve(String.valueOf(count));
 348             Path moduleSrc = testBase.resolve("module-src");
 349             Path m = moduleSrc.resolve("m");
 350 
 351             tb.writeJavaFiles(m,
 352                               "@test.A(" + tc.use + ") module m { }",
 353                               "package test; @java.lang.annotation.Target(java.lang.annotation.ElementType.MODULE) public @interface A { " + tc.decl + "}",
 354                               tc.extraDecl);
 355 
 356             Path modulePath = testBase.resolve("module-path");
 357 
 358             Files.createDirectories(modulePath);
 359 
 360             new JavacTask(tb)
 361                 .options("--module-source-path", moduleSrc.toString())
 362                 .outdir(modulePath)
 363                 .files(findJavaFiles(moduleSrc))
 364                 .run()
 365                 .writeAll();
 366 
 367             Path classes = testBase.resolve("classes");
 368 
 369             Files.createDirectories(classes);
 370 
 371             new JavacTask(tb)
 372                 .options("--module-path", modulePath.toString(),
 373                          "--add-modules", "m",
 374                          "-processorpath", System.getProperty("test.classes"),
 375                          "-processor", ProxyTypeValidator.class.getName(),
 376                          "-A" + OPT_EXPECTED_ANNOTATIONS + "=" + tc.expectedAnnotations)
 377                 .outdir(classes)
 378                 .files(findJavaFiles(extraSrc))
 379                 .run()
 380                 .writeAll();
 381         }
 382     }
 383 
 384     private static final String OPT_EXPECTED_ANNOTATIONS = "expectedAnnotations";
 385 
 386     @SupportedAnnotationTypes("*")
 387     @SupportedOptions(OPT_EXPECTED_ANNOTATIONS)
 388     public static final class ProxyTypeValidator extends AbstractProcessor {
 389 
 390         @Override
 391         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 392             ModuleElement m = processingEnv.getElementUtils().getModuleElement("m");
 393             String actualTypes = m.getAnnotationMirrors()
 394                                   .stream()
 395                                   .map(am -> am.toString())
 396                                   .collect(Collectors.joining(", "));
 397             if (!Objects.equals(actualTypes, processingEnv.getOptions().get(OPT_EXPECTED_ANNOTATIONS))) {
 398                 throw new IllegalStateException("Expected annotations not found, actual: " + actualTypes);
 399             }
 400             return false;
 401         }
 402 
 403     }
 404 
 405 }