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  * @summary Verify that annotation processing works.
  27  * @library /tools/lib
  28  * @modules
  29  *      jdk.compiler/com.sun.tools.javac.api
  30  *      jdk.compiler/com.sun.tools.javac.main
  31  * @build toolbox.ToolBox toolbox.JavacTask ModuleTestBase
  32  * @run main AnnotationProcessing
  33  */
  34 
  35 import java.nio.file.Files;
  36 import java.nio.file.Path;
  37 import java.util.Arrays;
  38 import java.util.HashMap;
  39 import java.util.List;
  40 import java.util.Map;
  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.SourceVersion;
  50 import javax.lang.model.element.Element;
  51 import javax.lang.model.element.ModuleElement;
  52 import javax.lang.model.element.ModuleElement.ProvidesDirective;
  53 import javax.lang.model.element.ModuleElement.UsesDirective;
  54 import javax.lang.model.element.PackageElement;
  55 import javax.lang.model.element.TypeElement;
  56 import javax.lang.model.type.TypeKind;
  57 import javax.lang.model.util.ElementFilter;
  58 import javax.lang.model.util.ElementScanner9;
  59 
  60 import toolbox.JavacTask;
  61 import toolbox.Task;
  62 import toolbox.ToolBox;
  63 
  64 public class AnnotationProcessing extends ModuleTestBase {
  65 
  66     public static void main(String... args) throws Exception {
  67         new AnnotationProcessing().runTests();
  68     }
  69 
  70     @Test
  71     public void testAPSingleModule(Path base) throws Exception {
  72         Path moduleSrc = base.resolve("module-src");
  73         Path m1 = moduleSrc.resolve("m1");
  74 
  75         Path classes = base.resolve("classes");
  76 
  77         Files.createDirectories(classes);
  78 
  79         tb.writeJavaFiles(m1,
  80                           "module m1 { }",
  81                           "package impl; public class Impl { }");
  82 
  83         String log = new JavacTask(tb)
  84                 .options("-modulesourcepath", moduleSrc.toString(),
  85                          "-processor", AP.class.getName(),
  86                          "-AexpectedEnclosedElements=m1=>impl")
  87                 .outdir(classes)
  88                 .files(findJavaFiles(moduleSrc))
  89                 .run()
  90                 .writeAll()
  91                 .getOutput(Task.OutputKind.DIRECT);
  92 
  93         if (!log.isEmpty())
  94             throw new AssertionError("Unexpected output: " + log);
  95     }
  96 
  97     @Test
  98     public void testAPMultiModule(Path base) throws Exception {
  99         Path moduleSrc = base.resolve("module-src");
 100         Path m1 = moduleSrc.resolve("m1");
 101         Path m2 = moduleSrc.resolve("m2");
 102 
 103         Path classes = base.resolve("classes");
 104 
 105         Files.createDirectories(classes);
 106 
 107         tb.writeJavaFiles(m1,
 108                           "module m1 { }",
 109                           "package impl1; public class Impl1 { }");
 110 
 111         tb.writeJavaFiles(m2,
 112                           "module m2 { }",
 113                           "package impl2; public class Impl2 { }");
 114 
 115         String log = new JavacTask(tb)
 116                 .options("-modulesourcepath", moduleSrc.toString(),
 117                          "-processor", AP.class.getName(),
 118                          "-AexpectedEnclosedElements=m1=>impl1,m2=>impl2")
 119                 .outdir(classes)
 120                 .files(findJavaFiles(moduleSrc))
 121                 .run()
 122                 .writeAll()
 123                 .getOutput(Task.OutputKind.DIRECT);
 124 
 125         if (!log.isEmpty())
 126             throw new AssertionError("Unexpected output: " + log);
 127     }
 128 
 129     @SupportedAnnotationTypes("*")
 130     @SupportedOptions("expectedEnclosedElements")
 131     public static final class AP extends AbstractProcessor {
 132 
 133         private Map<String, List<String>> module2ExpectedEnclosedElements;
 134 
 135         @Override
 136         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 137             if (module2ExpectedEnclosedElements == null) {
 138                 module2ExpectedEnclosedElements = new HashMap<>();
 139 
 140                 String expectedEnclosedElements =
 141                         processingEnv.getOptions().get("expectedEnclosedElements");
 142 
 143                 for (String moduleDef : expectedEnclosedElements.split(",")) {
 144                     String[] module2Packages = moduleDef.split("=>");
 145 
 146                     module2ExpectedEnclosedElements.put(module2Packages[0],
 147                                                         Arrays.asList(module2Packages[1].split(":")));
 148                 }
 149             }
 150 
 151             //verify ModuleType and ModuleSymbol behavior:
 152             for (Element root : roundEnv.getRootElements()) {
 153                 ModuleElement module = processingEnv.getElementUtils().getModuleOf(root);
 154 
 155                 assertEquals(TypeKind.MODULE, module.asType().getKind());
 156 
 157                 boolean[] seenModule = new boolean[1];
 158 
 159                 module.accept(new ElementScanner9<Void, Void>() {
 160                     @Override
 161                     public Void visitModule(ModuleElement e, Void p) {
 162                         seenModule[0] = true;
 163                         return null;
 164                     }
 165                     @Override
 166                     public Void scan(Element e, Void p) {
 167                         throw new AssertionError("Shouldn't get here.");
 168                     }
 169                 }, null);
 170 
 171                 assertEquals(true, seenModule[0]);
 172 
 173                 List<String> actualElements =
 174                         module.getEnclosedElements()
 175                               .stream()
 176                               .map(s -> (PackageElement) s)
 177                               .map(p -> p.getQualifiedName().toString())
 178                               .collect(Collectors.toList());
 179 
 180                 assertEquals(module2ExpectedEnclosedElements.remove(module.getQualifiedName().toString()),
 181                              actualElements);
 182             }
 183 
 184             if (roundEnv.processingOver()) {
 185                 assertEquals(true, module2ExpectedEnclosedElements.isEmpty());
 186             }
 187 
 188             return false;
 189         }
 190 
 191         @Override
 192         public SourceVersion getSupportedSourceVersion() {
 193             return SourceVersion.latest();
 194         }
 195 
 196     }
 197 
 198     @Test
 199     public void testVerifyUsesProvides(Path base) throws Exception {
 200         Path moduleSrc = base.resolve("module-src");
 201         Path m1 = moduleSrc.resolve("m1");
 202 
 203         Path classes = base.resolve("classes");
 204 
 205         Files.createDirectories(classes);
 206 
 207         tb.writeJavaFiles(m1,
 208                           "module m1 { exports api; uses api.Api; provides api.Api with impl.Impl; }",
 209                           "package api; public class Api { }",
 210                           "package impl; public class Impl extends api.Api { }");
 211 
 212         String log = new JavacTask(tb)
 213                 .options("-doe", "-processor", VerifyUsesProvidesAP.class.getName())
 214                 .outdir(classes)
 215                 .files(findJavaFiles(moduleSrc))
 216                 .run()
 217                 .writeAll()
 218                 .getOutput(Task.OutputKind.DIRECT);
 219 
 220         if (!log.isEmpty())
 221             throw new AssertionError("Unexpected output: " + log);
 222     }
 223 
 224     @SupportedAnnotationTypes("*")
 225     public static final class VerifyUsesProvidesAP extends AbstractProcessor {
 226 
 227         @Override
 228         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 229             TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
 230 
 231             assertNonNull("Cannot find api.Api", api);
 232 
 233             ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
 234 
 235             assertNonNull("modle is null", modle);
 236 
 237             List<? extends UsesDirective> uses = ElementFilter.usesIn(modle.getDirectives());
 238             assertEquals(1, uses.size());
 239             assertEquals("api.Api", uses.iterator().next().getService().getQualifiedName().toString());
 240 
 241             List<? extends ProvidesDirective> provides = ElementFilter.providesIn(modle.getDirectives());
 242             assertEquals(1, provides.size());
 243             assertEquals("api.Api", provides.iterator().next().getService().getQualifiedName().toString());
 244             assertEquals("impl.Impl", provides.iterator().next().getImplementation().getQualifiedName().toString());
 245 
 246             return false;
 247         }
 248 
 249         @Override
 250         public SourceVersion getSupportedSourceVersion() {
 251             return SourceVersion.latest();
 252         }
 253 
 254     }
 255 
 256     @Test
 257     public void testPackageNoModule(Path base) throws Exception {
 258         Path src = base.resolve("src");
 259         Path classes = base.resolve("classes");
 260 
 261         Files.createDirectories(classes);
 262 
 263         tb.writeJavaFiles(src,
 264                           "package api; public class Api { }");
 265 
 266         String log = new JavacTask(tb)
 267                 .options("-processor", VerifyPackageNoModule.class.getName(),
 268                          "-source", "8",
 269                          "-Xlint:-options")
 270                 .outdir(classes)
 271                 .files(findJavaFiles(src))
 272                 .run()
 273                 .writeAll()
 274                 .getOutput(Task.OutputKind.DIRECT);
 275 
 276         if (!log.isEmpty())
 277             throw new AssertionError("Unexpected output: " + log);
 278     }
 279 
 280     @SupportedAnnotationTypes("*")
 281     public static final class VerifyPackageNoModule extends AbstractProcessor {
 282 
 283         @Override
 284         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 285             TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
 286 
 287             assertNonNull("Cannot find api.Api", api);
 288 
 289             ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
 290 
 291             assertNull("modle is not null", modle);
 292 
 293             return false;
 294         }
 295 
 296         @Override
 297         public SourceVersion getSupportedSourceVersion() {
 298             return SourceVersion.latest();
 299         }
 300 
 301     }
 302 
 303     private static void assertNonNull(String msg, Object val) {
 304         if (val == null) {
 305             throw new AssertionError(msg);
 306         }
 307     }
 308 
 309     private static void assertNull(String msg, Object val) {
 310         if (val != null) {
 311             throw new AssertionError(msg);
 312         }
 313     }
 314 
 315     private static void assertEquals(Object expected, Object actual) {
 316         if (!Objects.equals(expected, actual)) {
 317             throw new AssertionError("expected: " + expected + "; actual=" + actual);
 318         }
 319     }
 320 
 321 }