1 /*
   2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 8133884 8162711 8133896 8172158 8172262 8173636 8175119
  27  * @summary Verify that annotation processing works.
  28  * @library /tools/lib
  29  * @modules
  30  *      jdk.compiler/com.sun.tools.javac.api
  31  *      jdk.compiler/com.sun.tools.javac.main
  32  * @build toolbox.ToolBox toolbox.JavacTask ModuleTestBase
  33  * @run main AnnotationProcessing
  34  */
  35 
  36 import java.io.File;
  37 import java.io.IOException;
  38 import java.io.OutputStream;
  39 import java.io.Reader;
  40 import java.io.UncheckedIOException;
  41 import java.io.Writer;
  42 import java.nio.file.Files;
  43 import java.nio.file.Path;
  44 import java.nio.file.Paths;
  45 import java.util.ArrayList;
  46 import java.util.Arrays;
  47 import java.util.HashMap;
  48 import java.util.HashSet;
  49 import java.util.List;
  50 import java.util.Map;
  51 import java.util.Objects;
  52 import java.util.Set;
  53 import java.util.concurrent.Callable;
  54 import java.util.function.Consumer;
  55 import java.util.function.Function;
  56 import java.util.regex.Pattern;
  57 import java.util.stream.Collectors;
  58 
  59 import javax.annotation.processing.AbstractProcessor;
  60 import javax.annotation.processing.Filer;
  61 import javax.annotation.processing.FilerException;
  62 import javax.annotation.processing.Messager;
  63 import javax.annotation.processing.ProcessingEnvironment;
  64 import javax.annotation.processing.RoundEnvironment;
  65 import javax.annotation.processing.SupportedAnnotationTypes;
  66 import javax.annotation.processing.SupportedOptions;
  67 import javax.lang.model.SourceVersion;
  68 import javax.lang.model.element.Element;
  69 import javax.lang.model.element.ElementKind;
  70 import javax.lang.model.element.ModuleElement;
  71 import javax.lang.model.element.ModuleElement.ProvidesDirective;
  72 import javax.lang.model.element.ModuleElement.UsesDirective;
  73 import javax.lang.model.element.PackageElement;
  74 import javax.lang.model.element.TypeElement;
  75 import javax.lang.model.element.VariableElement;
  76 import javax.lang.model.type.TypeKind;
  77 import javax.lang.model.util.ElementFilter;
  78 import javax.lang.model.util.ElementScanner9;
  79 import javax.tools.Diagnostic.Kind;
  80 import javax.tools.FileObject;
  81 import javax.tools.JavaCompiler;
  82 import javax.tools.JavaCompiler.CompilationTask;
  83 import javax.tools.JavaFileManager;
  84 import javax.tools.JavaFileManager.Location;
  85 import javax.tools.JavaFileObject;
  86 import javax.tools.StandardJavaFileManager;
  87 import javax.tools.StandardLocation;
  88 import javax.tools.ToolProvider;
  89 
  90 import toolbox.JavacTask;
  91 import toolbox.Task;
  92 import toolbox.Task.Mode;
  93 import toolbox.Task.OutputKind;
  94 
  95 public class AnnotationProcessing extends ModuleTestBase {
  96 
  97     public static void main(String... args) throws Exception {
  98         new AnnotationProcessing().runTests();
  99     }
 100 
 101     @Test
 102     public void testAPSingleModule(Path base) throws Exception {
 103         Path moduleSrc = base.resolve("module-src");
 104         Path m1 = moduleSrc.resolve("m1x");
 105 
 106         Path classes = base.resolve("classes");
 107 
 108         Files.createDirectories(classes);
 109 
 110         tb.writeJavaFiles(m1,
 111                           "module m1x { }",
 112                           "package impl; public class Impl { }");
 113 
 114         String log = new JavacTask(tb)
 115                 .options("--module-source-path", moduleSrc.toString(),
 116                          "-processor", AP.class.getName(),
 117                          "-AexpectedEnclosedElements=m1x=>impl")
 118                 .outdir(classes)
 119                 .files(findJavaFiles(moduleSrc))
 120                 .run()
 121                 .writeAll()
 122                 .getOutput(Task.OutputKind.DIRECT);
 123 
 124         if (!log.isEmpty())
 125             throw new AssertionError("Unexpected output: " + log);
 126     }
 127 
 128     @Test
 129     public void testAPMultiModule(Path base) throws Exception {
 130         Path moduleSrc = base.resolve("module-src");
 131         Path m1 = moduleSrc.resolve("m1x");
 132         Path m2 = moduleSrc.resolve("m2x");
 133 
 134         Path classes = base.resolve("classes");
 135 
 136         Files.createDirectories(classes);
 137 
 138         tb.writeJavaFiles(m1,
 139                           "module m1x { }",
 140                           "package impl1; public class Impl1 { }");
 141 
 142         tb.writeJavaFiles(m2,
 143                           "module m2x { }",
 144                           "package impl2; public class Impl2 { }");
 145 
 146         String log = new JavacTask(tb)
 147                 .options("--module-source-path", moduleSrc.toString(),
 148                          "-processor", AP.class.getName(),
 149                          "-AexpectedEnclosedElements=m1x=>impl1,m2x=>impl2")
 150                 .outdir(classes)
 151                 .files(findJavaFiles(moduleSrc))
 152                 .run()
 153                 .writeAll()
 154                 .getOutput(Task.OutputKind.DIRECT);
 155 
 156         if (!log.isEmpty())
 157             throw new AssertionError("Unexpected output: " + log);
 158     }
 159 
 160     @SupportedAnnotationTypes("*")
 161     @SupportedOptions("expectedEnclosedElements")
 162     public static final class AP extends AbstractProcessor {
 163 
 164         private Map<String, List<String>> module2ExpectedEnclosedElements;
 165         private Set<String> seenModules = new HashSet<>();
 166 
 167         @Override
 168         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 169             if (module2ExpectedEnclosedElements == null) {
 170                 module2ExpectedEnclosedElements = new HashMap<>();
 171 
 172                 String expectedEnclosedElements =
 173                         processingEnv.getOptions().get("expectedEnclosedElements");
 174 
 175                 for (String moduleDef : expectedEnclosedElements.split(",")) {
 176                     String[] module2Packages = moduleDef.split("=>");
 177 
 178                     module2ExpectedEnclosedElements.put(module2Packages[0],
 179                                                         List.of(module2Packages[1].split(":")));
 180                 }
 181             }
 182 
 183             //verify ModuleType and ModuleSymbol behavior:
 184             for (Element root : roundEnv.getRootElements()) {
 185                 ModuleElement module = processingEnv.getElementUtils().getModuleOf(root);
 186 
 187                 assertEquals(TypeKind.MODULE, module.asType().getKind());
 188 
 189                 boolean[] seenModule = new boolean[1];
 190 
 191                 module.accept(new ElementScanner9<Void, Void>() {
 192                     @Override
 193                     public Void visitModule(ModuleElement e, Void p) {
 194                         seenModule[0] = true;
 195                         return null;
 196                     }
 197                     @Override
 198                     public Void scan(Element e, Void p) {
 199                         throw new AssertionError("Shouldn't get here.");
 200                     }
 201                 }, null);
 202 
 203                 assertEquals(true, seenModule[0]);
 204 
 205                 List<String> actualElements =
 206                         module.getEnclosedElements()
 207                               .stream()
 208                               .map(s -> (PackageElement) s)
 209                               .map(p -> p.getQualifiedName().toString())
 210                               .collect(Collectors.toList());
 211 
 212                 String moduleName = module.getQualifiedName().toString();
 213 
 214                 assertEquals(module2ExpectedEnclosedElements.get(moduleName),
 215                              actualElements);
 216 
 217                 seenModules.add(moduleName);
 218             }
 219 
 220             if (roundEnv.processingOver()) {
 221                 assertEquals(module2ExpectedEnclosedElements.keySet(), seenModules);
 222             }
 223 
 224             return false;
 225         }
 226 
 227         @Override
 228         public SourceVersion getSupportedSourceVersion() {
 229             return SourceVersion.latest();
 230         }
 231 
 232     }
 233 
 234     @Test
 235     public void testVerifyUsesProvides(Path base) throws Exception {
 236         Path moduleSrc = base.resolve("module-src");
 237         Path m1 = moduleSrc.resolve("m1x");
 238 
 239         Path classes = base.resolve("classes");
 240 
 241         Files.createDirectories(classes);
 242 
 243         tb.writeJavaFiles(m1,
 244                           "module m1x { exports api; uses api.Api; provides api.Api with impl.Impl; }",
 245                           "package api; public class Api { }",
 246                           "package impl; public class Impl extends api.Api { }");
 247 
 248         String log = new JavacTask(tb)
 249                 .options("-doe", "-processor", VerifyUsesProvidesAP.class.getName())
 250                 .outdir(classes)
 251                 .files(findJavaFiles(moduleSrc))
 252                 .run()
 253                 .writeAll()
 254                 .getOutput(Task.OutputKind.DIRECT);
 255 
 256         if (!log.isEmpty())
 257             throw new AssertionError("Unexpected output: " + log);
 258     }
 259 
 260     @SupportedAnnotationTypes("*")
 261     public static final class VerifyUsesProvidesAP extends AbstractProcessor {
 262 
 263         @Override
 264         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 265             TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
 266 
 267             assertNonNull("Cannot find api.Api", api);
 268 
 269             ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
 270 
 271             assertNonNull("modle is null", modle);
 272 
 273             List<? extends UsesDirective> uses = ElementFilter.usesIn(modle.getDirectives());
 274             assertEquals(1, uses.size());
 275             assertEquals("api.Api", uses.iterator().next().getService().getQualifiedName().toString());
 276 
 277             List<? extends ProvidesDirective> provides = ElementFilter.providesIn(modle.getDirectives());
 278             assertEquals(1, provides.size());
 279             assertEquals("api.Api", provides.iterator().next().getService().getQualifiedName().toString());
 280             assertEquals("impl.Impl", provides.iterator().next().getImplementations().get(0).getQualifiedName().toString());
 281 
 282             return false;
 283         }
 284 
 285         @Override
 286         public SourceVersion getSupportedSourceVersion() {
 287             return SourceVersion.latest();
 288         }
 289 
 290     }
 291 
 292     @Test
 293     public void testPackageNoModule(Path base) throws Exception {
 294         Path src = base.resolve("src");
 295         Path classes = base.resolve("classes");
 296 
 297         Files.createDirectories(classes);
 298 
 299         tb.writeJavaFiles(src,
 300                           "package api; public class Api { }");
 301 
 302         String log = new JavacTask(tb)
 303                 .options("-processor", VerifyPackageNoModule.class.getName(),
 304                          "-source", "8",
 305                          "-Xlint:-options")
 306                 .outdir(classes)
 307                 .files(findJavaFiles(src))
 308                 .run()
 309                 .writeAll()
 310                 .getOutput(Task.OutputKind.DIRECT);
 311 
 312         if (!log.isEmpty())
 313             throw new AssertionError("Unexpected output: " + log);
 314     }
 315 
 316     @SupportedAnnotationTypes("*")
 317     public static final class VerifyPackageNoModule extends AbstractProcessor {
 318 
 319         @Override
 320         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 321             TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api");
 322 
 323             assertNonNull("Cannot find api.Api", api);
 324 
 325             ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement();
 326 
 327             assertNull("modle is not null", modle);
 328 
 329             return false;
 330         }
 331 
 332         @Override
 333         public SourceVersion getSupportedSourceVersion() {
 334             return SourceVersion.latest();
 335         }
 336 
 337     }
 338 
 339     @Test
 340     public void testQualifiedClassForProcessing(Path base) throws Exception {
 341         Path moduleSrc = base.resolve("module-src");
 342         Path m1 = moduleSrc.resolve("m1x");
 343         Path m2 = moduleSrc.resolve("m2x");
 344 
 345         Path classes = base.resolve("classes");
 346 
 347         Files.createDirectories(classes);
 348 
 349         tb.writeJavaFiles(m1,
 350                           "module m1x { }",
 351                           "package impl; public class Impl { int m1x; }");
 352 
 353         tb.writeJavaFiles(m2,
 354                           "module m2x { }",
 355                           "package impl; public class Impl { int m2x; }");
 356 
 357         new JavacTask(tb)
 358             .options("--module-source-path", moduleSrc.toString())
 359             .outdir(classes)
 360             .files(findJavaFiles(moduleSrc))
 361             .run()
 362             .writeAll()
 363             .getOutput(Task.OutputKind.DIRECT);
 364 
 365         List<String> expected = List.of("Note: field: m1x");
 366 
 367         for (Mode mode : new Mode[] {Mode.API, Mode.CMDLINE}) {
 368             List<String> log = new JavacTask(tb, mode)
 369                     .options("-processor", QualifiedClassForProcessing.class.getName(),
 370                              "--module-path", classes.toString())
 371                     .classes("m1x/impl.Impl")
 372                     .outdir(classes)
 373                     .run()
 374                     .writeAll()
 375                     .getOutputLines(Task.OutputKind.DIRECT);
 376 
 377             if (!expected.equals(log))
 378                 throw new AssertionError("Unexpected output: " + log);
 379         }
 380     }
 381 
 382     @SupportedAnnotationTypes("*")
 383     public static final class QualifiedClassForProcessing extends AbstractProcessor {
 384 
 385         @Override
 386         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 387             if (processingEnv.getElementUtils().getModuleElement("m1x") == null) {
 388                 throw new AssertionError("No m1x module found.");
 389             }
 390 
 391             Messager messager = processingEnv.getMessager();
 392 
 393             for (TypeElement clazz : ElementFilter.typesIn(roundEnv.getRootElements())) {
 394                 for (VariableElement field : ElementFilter.fieldsIn(clazz.getEnclosedElements())) {
 395                     messager.printMessage(Kind.NOTE, "field: " + field.getSimpleName());
 396                 }
 397             }
 398 
 399             return false;
 400         }
 401 
 402         @Override
 403         public SourceVersion getSupportedSourceVersion() {
 404             return SourceVersion.latest();
 405         }
 406 
 407     }
 408 
 409     @Test
 410     public void testModuleInRootElements(Path base) throws Exception {
 411         Path moduleSrc = base.resolve("module-src");
 412         Path m1 = moduleSrc.resolve("m1");
 413 
 414         Path classes = base.resolve("classes");
 415 
 416         Files.createDirectories(classes);
 417 
 418         tb.writeJavaFiles(m1,
 419                           "module m1x { exports api; }",
 420                           "package api; public class Api { }");
 421 
 422         List<String> log = new JavacTask(tb)
 423                 .options("-processor", ModuleInRootElementsAP.class.getName())
 424                 .outdir(classes)
 425                 .files(findJavaFiles(moduleSrc))
 426                 .run()
 427                 .writeAll()
 428                 .getOutputLines(Task.OutputKind.STDERR);
 429 
 430         assertEquals(List.of("module: m1x"), log);
 431     }
 432 
 433     @SupportedAnnotationTypes("*")
 434     public static final class ModuleInRootElementsAP extends AbstractProcessor {
 435 
 436         @Override
 437         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 438             roundEnv.getRootElements()
 439                     .stream()
 440                     .filter(el -> el.getKind() == ElementKind.MODULE)
 441                     .forEach(mod -> System.err.println("module: " + mod.getSimpleName()));
 442 
 443             return false;
 444         }
 445 
 446         @Override
 447         public SourceVersion getSupportedSourceVersion() {
 448             return SourceVersion.latest();
 449         }
 450 
 451     }
 452 
 453     @Test
 454     public void testAnnotationsInModuleInfo(Path base) throws Exception {
 455         Path moduleSrc = base.resolve("module-src");
 456         Path m1 = moduleSrc.resolve("m1");
 457 
 458         tb.writeJavaFiles(m1,
 459                           "@Deprecated module m1x { }");
 460 
 461         Path m2 = moduleSrc.resolve("m2x");
 462 
 463         tb.writeJavaFiles(m2,
 464                           "@SuppressWarnings(\"\") module m2x { }");
 465 
 466         Path classes = base.resolve("classes");
 467 
 468         Files.createDirectories(classes);
 469 
 470         List<String> log = new JavacTask(tb)
 471                 .options("-processor", AnnotationsInModuleInfoPrint.class.getName())
 472                 .outdir(classes)
 473                 .files(findJavaFiles(m1))
 474                 .run()
 475                 .writeAll()
 476                 .getOutputLines(Task.OutputKind.DIRECT);
 477 
 478         List<String> expectedLog = List.of("Note: AP Invoked",
 479                                            "Note: AP Invoked");
 480 
 481         assertEquals(expectedLog, log);
 482 
 483         new JavacTask(tb)
 484             .options("-processor", AnnotationsInModuleInfoFail.class.getName())
 485             .outdir(classes)
 486             .files(findJavaFiles(m2))
 487             .run()
 488             .writeAll();
 489     }
 490 
 491     @SupportedAnnotationTypes("java.lang.Deprecated")
 492     public static final class AnnotationsInModuleInfoPrint extends AbstractProcessor {
 493 
 494         @Override
 495         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 496             processingEnv.getMessager().printMessage(Kind.NOTE, "AP Invoked");
 497             return false;
 498         }
 499 
 500         @Override
 501         public SourceVersion getSupportedSourceVersion() {
 502             return SourceVersion.latest();
 503         }
 504 
 505     }
 506 
 507     @SupportedAnnotationTypes("java.lang.Deprecated")
 508     public static final class AnnotationsInModuleInfoFail extends AbstractProcessor {
 509 
 510         @Override
 511         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 512             throw new AssertionError();
 513         }
 514 
 515         @Override
 516         public SourceVersion getSupportedSourceVersion() {
 517             return SourceVersion.latest();
 518         }
 519 
 520     }
 521 
 522     @Test
 523     public void testGenerateInMultiModeAPI(Path base) throws Exception {
 524         Path moduleSrc = base.resolve("module-src");
 525         Path classes = base.resolve("classes");
 526 
 527         Files.createDirectories(classes);
 528 
 529         Path m1 = moduleSrc.resolve("m1x");
 530 
 531         tb.writeJavaFiles(m1,
 532                           "module m1x { exports api1; }",
 533                           "package api1; public class Api { }",
 534                           "package clash; public class C { }");
 535 
 536         writeFile("1", m1, "api1", "api");
 537         writeFile("2", m1, "clash", "clash");
 538 
 539         Path m2 = moduleSrc.resolve("m2x");
 540 
 541         tb.writeJavaFiles(m2,
 542                           "module m2x { requires m1x; exports api2; }",
 543                           "package api2; public class Api { }",
 544                           "package clash; public class C { }");
 545 
 546         writeFile("3", m2, "api2", "api");
 547         writeFile("4", m2, "clash", "api");
 548 
 549         //passing testcases:
 550         for (String module : Arrays.asList("", "m1x/")) {
 551             for (String originating : Arrays.asList("", ", jlObject")) {
 552                 tb.writeJavaFiles(m1,
 553                                   "package test; class Test { api1.Impl i; }");
 554 
 555                 //source:
 556                 runCompiler(base,
 557                             moduleSrc,
 558                             classes,
 559                             "createSource(() -> filer.createSourceFile(\"" + module + "api1.Impl\"" + originating + "), \"api1.Impl\", \"package api1; public class Impl {}\")",
 560                             "--module-source-path", moduleSrc.toString());
 561                 assertFileExists(classes, "m1x", "api1", "Impl.class");
 562 
 563                 //class:
 564                 runCompiler(base,
 565                             moduleSrc,
 566                             classes,
 567                             "createClass(() -> filer.createClassFile(\"" + module + "api1.Impl\"" + originating + "), \"api1.Impl\", \"package api1; public class Impl {}\")",
 568                             "--module-source-path", moduleSrc.toString());
 569                 assertFileExists(classes, "m1x", "api1", "Impl.class");
 570 
 571                 deleteFile(m1.resolve("test").resolve("Test.java"));
 572 
 573                 //resource class output:
 574                 runCompiler(base,
 575                             moduleSrc,
 576                             classes,
 577                             "createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"" + module + "api1\", \"impl\"" + originating + "), \"impl\", \"impl\")",
 578                             "--module-source-path", moduleSrc.toString());
 579                 assertFileExists(classes, "m1x", "api1", "impl");
 580             }
 581         }
 582 
 583         //get resource module source path:
 584         runCompiler(base,
 585                     m1,
 586                     classes,
 587                     "doReadResource(() -> filer.getResource(StandardLocation.MODULE_SOURCE_PATH, \"m1x/api1\", \"api\"), \"1\")",
 588                     "--module-source-path", moduleSrc.toString());
 589 
 590         //can generate resources to the single root module:
 591         runCompiler(base,
 592                     m1,
 593                     classes,
 594                     "createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"m1x/impl\", \"impl\"), \"impl\", \"impl\")",
 595                     "--module-source-path", moduleSrc.toString());
 596         assertFileExists(classes, "m1x", "impl", "impl");
 597 
 598         //check --default-module-for-created-files option:
 599         for (String pack : Arrays.asList("clash", "doesnotexist")) {
 600             tb.writeJavaFiles(m1,
 601                               "package test; class Test { " + pack + ".Pass i; }");
 602             runCompiler(base,
 603                         moduleSrc,
 604                         classes,
 605                         "createSource(() -> filer.createSourceFile(\"" + pack + ".Pass\")," +
 606                         "                                          \"" + pack + ".Pass\"," +
 607                         "                                          \"package " + pack + ";" +
 608                         "                                            public class Pass { }\")",
 609                         "--module-source-path", moduleSrc.toString(),
 610                         "--default-module-for-created-files=m1x");
 611             assertFileExists(classes, "m1x", pack, "Pass.class");
 612             assertFileNotExists(classes, "m2x", pack, "Pass.class");
 613 
 614             runCompiler(base,
 615                         moduleSrc,
 616                         classes,
 617                         "createClass(() -> filer.createClassFile(\"" + pack + ".Pass\")," +
 618                         "                                        \"" + pack + ".Pass\"," +
 619                         "                                        \"package " + pack + ";" +
 620                         "                                          public class Pass { }\")",
 621                         "--module-source-path", moduleSrc.toString(),
 622                         "--default-module-for-created-files=m1x");
 623             assertFileExists(classes, "m1x", pack, "Pass.class");
 624             assertFileNotExists(classes, "m2x", pack, "Pass.class");
 625 
 626             deleteFile(m1.resolve("test").resolve("Test.java"));
 627 
 628             runCompiler(base,
 629                         moduleSrc,
 630                         classes,
 631                         "createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT," +
 632                         "                                        \"" + pack + "\", \"impl\"), \"impl\", \"impl\")",
 633                         "--module-source-path", moduleSrc.toString(),
 634                         "--default-module-for-created-files=m1x");
 635             assertFileExists(classes, "m1x", pack, "impl");
 636             assertFileNotExists(classes, "m2x", pack, "impl");
 637 
 638             runCompiler(base,
 639                         moduleSrc,
 640                         classes,
 641                         "doReadResource(() -> filer.getResource(StandardLocation.CLASS_OUTPUT," +
 642                         "                                       \"" + pack + "\", \"resource\"), \"1\")",
 643                         p -> writeFile("1", p.resolve("m1x"), pack, "resource"),
 644                         "--module-source-path", moduleSrc.toString(),
 645                         "--default-module-for-created-files=m1x");
 646         }
 647 
 648         //wrong default module:
 649         runCompiler(base,
 650                     moduleSrc,
 651                     classes,
 652                     "expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT," +
 653                     "                                                \"clash\", \"impl\"))",
 654                     "--module-source-path", moduleSrc.toString(),
 655                     "--default-module-for-created-files=doesnotexist");
 656 
 657         String[] failingCases = {
 658             //must not generate to unnamed package:
 659             "expectFilerException(() -> filer.createSourceFile(\"Fail\"))",
 660             "expectFilerException(() -> filer.createClassFile(\"Fail\"))",
 661             "expectFilerException(() -> filer.createSourceFile(\"m1x/Fail\"))",
 662             "expectFilerException(() -> filer.createClassFile(\"m1x/Fail\"))",
 663 
 664             //cannot infer module name, package clash:
 665             "expectFilerException(() -> filer.createSourceFile(\"clash.Fail\"))",
 666             "expectFilerException(() -> filer.createClassFile(\"clash.Fail\"))",
 667             "expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"clash\", \"impl\"))",
 668             "expectFilerException(() -> filer.getResource(StandardLocation.CLASS_OUTPUT, \"clash\", \"impl\"))",
 669 
 670             //cannot infer module name, package does not exist:
 671             "expectFilerException(() -> filer.createSourceFile(\"doesnotexist.Fail\"))",
 672             "expectFilerException(() -> filer.createClassFile(\"doesnotexist.Fail\"))",
 673             "expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"doesnotexist\", \"impl\"))",
 674             "expectFilerException(() -> filer.getResource(StandardLocation.CLASS_OUTPUT, \"doesnotexist\", \"impl\"))",
 675 
 676             //cannot generate sources/classes to modules that are not root modules:
 677             "expectFilerException(() -> filer.createSourceFile(\"java.base/fail.Fail\"))",
 678             "expectFilerException(() -> filer.createClassFile(\"java.base/fail.Fail\"))",
 679 
 680             //cannot read from module locations if module not given and not inferable:
 681             "expectFilerException(() -> filer.getResource(StandardLocation.SYSTEM_MODULES, \"fail\", \"Fail\"))",
 682 
 683             //wrong module given:
 684             "expectException(() -> filer.getResource(StandardLocation.SYSTEM_MODULES, \"java.compiler/java.lang\", \"Object.class\"))",
 685         };
 686 
 687         for (String failingCode : failingCases) {
 688             System.err.println("failing code: " + failingCode);
 689             runCompiler(base,
 690                         moduleSrc,
 691                         classes,
 692                         failingCode,
 693                         "--module-source-path", moduleSrc.toString());
 694         }
 695     }
 696 
 697     private void deleteFile(Path file) throws IOException {
 698         long startTime = System.currentTimeMillis();
 699 
 700         do {
 701             Files.delete(file);
 702             if (!Files.exists(file)) {
 703                 return;
 704             }
 705             System.err.println("!! File not deleted !!");
 706             System.gc(); // allow finalizers and cleaners to run
 707             try {
 708                 Thread.sleep(RETRY_DELETE_MILLIS);
 709             } catch (InterruptedException e) {
 710                 throw new IOException("Interrupted while deleting " + file, e);
 711             }
 712         } while ((System.currentTimeMillis() - startTime) <= MAX_RETRY_DELETE_MILLIS);
 713 
 714         throw new IOException("Can't delete " + file);
 715     }
 716 
 717 
 718     private static final int RETRY_DELETE_MILLIS;
 719     private static final int MAX_RETRY_DELETE_MILLIS;
 720 
 721     static {
 722         boolean isWindows = System.getProperty("os.name").startsWith("Windows");
 723         RETRY_DELETE_MILLIS = isWindows ? 500 : 0;
 724         MAX_RETRY_DELETE_MILLIS = isWindows ? 15 * 1000 : 0;
 725     }
 726 
 727     public static abstract class GeneratingAP extends AbstractProcessor {
 728 
 729         public void createSource(CreateFileObject file, String name, String content) {
 730             try (Writer out = file.create().openWriter()) {
 731                 out.write(content);
 732             } catch (IOException ex) {
 733                 throw new IllegalStateException(ex);
 734             }
 735         }
 736 
 737         public void createClass(CreateFileObject file, String name, String content) {
 738             String fileNameStub = name.replace(".", File.separator);
 739 
 740             try (OutputStream out = file.create().openOutputStream()) {
 741                 Path scratch = Files.createDirectories(Paths.get(""));
 742                 Path scratchSrc = scratch.resolve(fileNameStub + ".java").toAbsolutePath();
 743 
 744                 Files.createDirectories(scratchSrc.getParent());
 745 
 746                 try (Writer w = Files.newBufferedWriter(scratchSrc)) {
 747                     w.write(content);
 748                 }
 749 
 750                 Path scratchClasses = scratch.resolve("classes");
 751 
 752                 Files.createDirectories(scratchClasses);
 753 
 754                 JavaCompiler comp = ToolProvider.getSystemJavaCompiler();
 755                 try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) {
 756                     List<String> options = List.of("-d", scratchClasses.toString());
 757                     Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(scratchSrc);
 758                     CompilationTask task = comp.getTask(null, fm, null, options, null, files);
 759 
 760                     if (!task.call()) {
 761                         throw new AssertionError("compilation failed");
 762                     }
 763                 }
 764 
 765                 Path classfile = scratchClasses.resolve(fileNameStub + ".class");
 766 
 767                 Files.copy(classfile, out);
 768             } catch (IOException ex) {
 769                 throw new IllegalStateException(ex);
 770             }
 771         }
 772 
 773         public void doReadResource(CreateFileObject file, String expectedContent) {
 774             try {
 775                 StringBuilder actualContent = new StringBuilder();
 776 
 777                 try (Reader r = file.create().openReader(true)) {
 778                     int read;
 779 
 780                     while ((read = r.read()) != (-1)) {
 781                         actualContent.append((char) read);
 782                     }
 783 
 784                 }
 785 
 786                 assertEquals(expectedContent, actualContent.toString());
 787             } catch (IOException ex) {
 788                 throw new IllegalStateException(ex);
 789             }
 790         }
 791 
 792         public void checkResourceExists(CreateFileObject file) {
 793             try {
 794                 file.create().openInputStream().close();
 795             } catch (IOException ex) {
 796                 throw new IllegalStateException(ex);
 797             }
 798         }
 799 
 800         public interface CreateFileObject {
 801             public FileObject create() throws IOException;
 802         }
 803 
 804         public void expectFilerException(Callable<Object> c) {
 805             try {
 806                 c.call();
 807                 throw new AssertionError("Expected exception not thrown");
 808             } catch (FilerException ex) {
 809                 //expected
 810             } catch (Exception ex) {
 811                 throw new IllegalStateException(ex);
 812             }
 813         }
 814 
 815         public void expectException(Callable<Object> c) {
 816             try {
 817                 c.call();
 818                 throw new AssertionError("Expected exception not thrown");
 819             } catch (IOException ex) {
 820                 //expected
 821             } catch (Exception ex) {
 822                 throw new IllegalStateException(ex);
 823             }
 824         }
 825 
 826         @Override
 827         public SourceVersion getSupportedSourceVersion() {
 828             return SourceVersion.latest();
 829         }
 830 
 831     }
 832 
 833     @Test
 834     public void testGenerateSingleModule(Path base) throws Exception {
 835         Path classes = base.resolve("classes");
 836 
 837         Files.createDirectories(classes);
 838 
 839         Path src = base.resolve("module-src");
 840         Path m1 = src.resolve("m1x");
 841 
 842         tb.writeJavaFiles(m1,
 843                           "module m1x { }",
 844                           "package test; class Test { impl.Impl i; }");
 845         Path m2 = src.resolve("m2x");
 846 
 847         tb.writeJavaFiles(m2,
 848                           "module m2x { }");
 849 
 850         for (String[] options : new String[][] {new String[] {"-sourcepath", m1.toString()},
 851                                                 new String[] {"--module-source-path", src.toString()}}) {
 852             String modulePath = options[0].equals("--module-source-path") ? "m1x" : "";
 853             //passing testcases:
 854             for (String module : Arrays.asList("", "m1x/")) {
 855                 for (String originating : Arrays.asList("", ", jlObject")) {
 856                     tb.writeJavaFiles(m1,
 857                                       "package test; class Test { impl.Impl i; }");
 858 
 859                     //source:
 860                     runCompiler(base,
 861                                 m1,
 862                                 classes,
 863                                 "createSource(() -> filer.createSourceFile(\"" + module + "impl.Impl\"" + originating + "), \"impl.Impl\", \"package impl; public class Impl {}\")",
 864                                 options);
 865                     assertFileExists(classes, modulePath, "impl", "Impl.class");
 866 
 867                     //class:
 868                     runCompiler(base,
 869                                 m1,
 870                                 classes,
 871                                 "createClass(() -> filer.createClassFile(\"" + module + "impl.Impl\"" + originating + "), \"impl.Impl\", \"package impl; public class Impl {}\")",
 872                                 options);
 873                     assertFileExists(classes, modulePath, "impl", "Impl.class");
 874 
 875                     deleteFile(m1.resolve("test").resolve("Test.java"));
 876 
 877                     //resource class output:
 878                     runCompiler(base,
 879                                 m1,
 880                                 classes,
 881                                 "createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"impl\", \"impl\"" + originating + "), \"impl\", \"impl\")",
 882                                 options);
 883                     assertFileExists(classes, modulePath, "impl", "impl");
 884                 }
 885             }
 886         }
 887 
 888         //get resource source path:
 889         writeFile("1", m1, "impl", "resource");
 890         runCompiler(base,
 891                     m1,
 892                     classes,
 893                     "doReadResource(() -> filer.getResource(StandardLocation.SOURCE_PATH, \"impl\", \"resource\"), \"1\")",
 894                     "-sourcepath", m1.toString());
 895         //must not specify module when reading non-module oriented locations:
 896         runCompiler(base,
 897                     m1,
 898                     classes,
 899                     "expectFilerException(() -> filer.getResource(StandardLocation.SOURCE_PATH, \"m1x/impl\", \"resource\"))",
 900                     "-sourcepath", m1.toString());
 901 
 902         deleteFile(m1.resolve("impl").resolve("resource"));
 903 
 904         //can read resources from the system module path if module name given:
 905         runCompiler(base,
 906                     m1,
 907                     classes,
 908                     "checkResourceExists(() -> filer.getResource(StandardLocation.SYSTEM_MODULES, \"java.base/java.lang\", \"Object.class\"))",
 909                     "-sourcepath", m1.toString());
 910 
 911         //can read resources from the system module path if module inferable:
 912         runCompiler(base,
 913                     m1,
 914                     classes,
 915                     "expectFilerException(() -> filer.getResource(StandardLocation.SYSTEM_MODULES, \"java.lang\", \"Object.class\"))",
 916                     "-sourcepath", m1.toString());
 917 
 918         //cannot generate resources to modules that are not root modules:
 919         runCompiler(base,
 920                     m1,
 921                     classes,
 922                     "expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"java.base/fail\", \"Fail\"))",
 923                     "--module-source-path", src.toString());
 924 
 925         //can generate resources to the single root module:
 926         runCompiler(base,
 927                     m1,
 928                     classes,
 929                     "createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"impl\", \"impl\"), \"impl\", \"impl\")",
 930                     "--module-source-path", src.toString());
 931         assertFileExists(classes, "m1x", "impl", "impl");
 932 
 933         String[] failingCases = {
 934             //must not generate to unnamed package:
 935             "expectFilerException(() -> filer.createSourceFile(\"Fail\"))",
 936             "expectFilerException(() -> filer.createClassFile(\"Fail\"))",
 937             "expectFilerException(() -> filer.createSourceFile(\"m1x/Fail\"))",
 938             "expectFilerException(() -> filer.createClassFile(\"m1x/Fail\"))",
 939 
 940             //cannot generate sources/classes to modules that are not root modules:
 941             "expectFilerException(() -> filer.createSourceFile(\"java.base/fail.Fail\"))",
 942             "expectFilerException(() -> filer.createClassFile(\"java.base/fail.Fail\"))",
 943 
 944             //cannot specify module name for class output when not in the multi-module mode:
 945             "expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, \"m1x/fail\", \"Fail\"))",
 946 
 947             //cannot read from module locations if module not given:
 948             "expectFilerException(() -> filer.getResource(StandardLocation.SYSTEM_MODULES, \"fail\", \"Fail\"))",
 949 
 950             //wrong module given:
 951             "expectException(() -> filer.getResource(StandardLocation.SYSTEM_MODULES, \"java.compiler/java.lang\", \"Object.class\"))",
 952         };
 953 
 954         for (String failingCode : failingCases) {
 955             System.err.println("failing code: " + failingCode);
 956             runCompiler(base,
 957                         m1,
 958                         classes,
 959                         failingCode,
 960                         "-sourcepath", m1.toString());
 961         }
 962 
 963         deleteFile(m1.resolve("module-info.java"));
 964         tb.writeJavaFiles(m1,
 965                           "package test; class Test { }");
 966 
 967         runCompiler(base,
 968                     m1,
 969                     classes,
 970                     "expectFilerException(() -> filer.createSourceFile(\"m1x/impl.Impl\"))",
 971                     "-sourcepath", m1.toString(),
 972                     "-source", "8");
 973 
 974         runCompiler(base,
 975                     m1,
 976                     classes,
 977                     "expectFilerException(() -> filer.createClassFile(\"m1x/impl.Impl\"))",
 978                     "-sourcepath", m1.toString(),
 979                     "-source", "8");
 980     }
 981 
 982     private void runCompiler(Path base, Path src, Path classes,
 983                              String code, String... options) throws IOException {
 984         runCompiler(base, src, classes, code, p -> {}, options);
 985     }
 986 
 987     private void runCompiler(Path base, Path src, Path classes,
 988                              String code, Consumer<Path> generateToClasses,
 989                              String... options) throws IOException {
 990         Path apClasses = base.resolve("ap-classes");
 991         if (Files.exists(apClasses)) {
 992             tb.cleanDirectory(apClasses);
 993         } else {
 994             Files.createDirectories(apClasses);
 995         }
 996         compileAP(apClasses, code);
 997         if (Files.exists(classes)) {
 998             tb.cleanDirectory(classes);
 999         } else {
1000             Files.createDirectories(classes);
1001         }
1002         generateToClasses.accept(classes);
1003         List<String> opts = new ArrayList<>();
1004         opts.addAll(Arrays.asList(options));
1005         opts.add("-processorpath");
1006         opts.add(System.getProperty("test.class.path") + File.pathSeparator + apClasses.toString());
1007         opts.add("-processor");
1008         opts.add("AP");
1009         new JavacTask(tb)
1010           .options(opts)
1011           .outdir(classes)
1012           .files(findJavaFiles(src))
1013           .run()
1014           .writeAll();
1015     }
1016 
1017     private void compileAP(Path target, String code) {
1018         String processorCode =
1019             "import java.util.*;\n" +
1020             "import javax.annotation.processing.*;\n" +
1021             "import javax.lang.model.*;\n" +
1022             "import javax.lang.model.element.*;\n" +
1023             "import javax.lang.model.type.*;\n" +
1024             "import javax.lang.model.util.*;\n" +
1025             "import javax.tools.*;\n" +
1026             "@SupportedAnnotationTypes(\"*\")\n" +
1027             "public final class AP extends AnnotationProcessing.GeneratingAP {\n" +
1028             "\n" +
1029             "        int round;\n" +
1030             "\n" +
1031             "        @Override\n" +
1032             "        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n" +
1033             "            if (round++ != 0)\n" +
1034             "                return false;\n" +
1035             "            Filer filer = processingEnv.getFiler();\n" +
1036             "            TypeElement jlObject = processingEnv.getElementUtils().getTypeElement(\"java.lang.Object\");\n" +
1037             code + ";\n" +
1038             "            return false;\n" +
1039             "        }\n" +
1040             "    }\n";
1041         new JavacTask(tb)
1042           .options("-classpath", System.getProperty("test.class.path"))
1043           .sources(processorCode)
1044           .outdir(target)
1045           .run()
1046           .writeAll();
1047     }
1048 
1049     @Test
1050     public void testGenerateInUnnamedModeAPI(Path base) throws Exception {
1051         Path classes = base.resolve("classes");
1052 
1053         Files.createDirectories(classes);
1054 
1055         Path src = base.resolve("src");
1056 
1057         tb.writeJavaFiles(src,
1058                           "class T {}");
1059 
1060         new JavacTask(tb)
1061           .options("-processor", UnnamedModeAPITestAP.class.getName(),
1062                    "-sourcepath", src.toString())
1063           .outdir(classes)
1064           .files(findJavaFiles(src))
1065           .run()
1066           .writeAll();
1067 
1068         assertFileExists(classes, "Impl1.class");
1069         assertFileExists(classes, "Impl2.class");
1070     }
1071 
1072     @Test
1073     public void testGenerateInNoModeAPI(Path base) throws Exception {
1074         Path classes = base.resolve("classes");
1075 
1076         Files.createDirectories(classes);
1077 
1078         Path src = base.resolve("src");
1079 
1080         tb.writeJavaFiles(src,
1081                           "class T {}");
1082 
1083         new JavacTask(tb)
1084           .options("-processor", UnnamedModeAPITestAP.class.getName(),
1085                    "-source", "8", "-target", "8",
1086                    "-sourcepath", src.toString())
1087           .outdir(classes)
1088           .files(findJavaFiles(src))
1089           .run()
1090           .writeAll();
1091 
1092         assertFileExists(classes, "Impl1.class");
1093         assertFileExists(classes, "Impl2.class");
1094     }
1095 
1096     @SupportedAnnotationTypes("*")
1097     public static final class UnnamedModeAPITestAP extends GeneratingAP {
1098 
1099         int round;
1100 
1101         @Override
1102         public synchronized void init(ProcessingEnvironment processingEnv) {
1103             super.init(processingEnv);
1104         }
1105 
1106         @Override
1107         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1108             if (round++ != 0)
1109                 return false;
1110 
1111             Filer filer = processingEnv.getFiler();
1112 
1113             //must not generate to unnamed package:
1114             createSource(() -> filer.createSourceFile("Impl1"), "Impl1", "class Impl1 {}");
1115             createClass(() -> filer.createClassFile("Impl2"), "Impl2", "class Impl2 {}");
1116 
1117             return false;
1118         }
1119 
1120     }
1121 
1122     @Test
1123     public void testDisambiguateAnnotations(Path base) throws Exception {
1124         Path classes = base.resolve("classes");
1125 
1126         Files.createDirectories(classes);
1127 
1128         Path src = base.resolve("src");
1129         Path m1 = src.resolve("m1x");
1130 
1131         tb.writeJavaFiles(m1,
1132                           "module m1x { exports api; }",
1133                           "package api; public @interface A {}",
1134                           "package api; public @interface B {}");
1135 
1136         Path m2 = src.resolve("m2x");
1137 
1138         tb.writeJavaFiles(m2,
1139                           "module m2x { exports api; }",
1140                           "package api; public @interface A {}",
1141                           "package api; public @interface B {}");
1142 
1143         Path m3 = src.resolve("m3x");
1144 
1145         tb.writeJavaFiles(m3,
1146                           "module m3x { requires m1x; }",
1147                           "package impl; import api.*; @A @B public class T {}");
1148 
1149         Path m4 = src.resolve("m4x");
1150 
1151         tb.writeJavaFiles(m4,
1152                           "module m4x { requires m2x; }",
1153                           "package impl; import api.*; @A @B public class T {}");
1154 
1155         List<String> log;
1156         List<String> expected;
1157 
1158         log = new JavacTask(tb)
1159             .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
1160                      "--module-source-path", src.toString(),
1161                      "-m", "m1x,m2x")
1162             .outdir(classes)
1163             .run()
1164             .writeAll()
1165             .getOutputLines(OutputKind.STDERR);
1166 
1167         expected = List.of("");
1168 
1169         if (!expected.equals(log)) {
1170             throw new AssertionError("Output does not match; output: " + log);
1171         }
1172 
1173         log = new JavacTask(tb)
1174             .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(),
1175                      "--module-source-path", src.toString(),
1176                      "-m", "m3x")
1177             .outdir(classes)
1178             .run()
1179             .writeAll()
1180             .getOutputLines(OutputKind.STDERR);
1181 
1182         expected = List.of("SelectAnnotationBTestAP",
1183                            "SelectAnnotationBTestAP");
1184 
1185         if (!expected.equals(log)) {
1186             throw new AssertionError("Output does not match; output: " + log);
1187         }
1188 
1189         log = new JavacTask(tb)
1190             .options("-processor", SelectAnnotationATestAP.class.getName() + "," +
1191                                    SelectAnnotationBTestAP.class.getName() + "," +
1192                                    SelectAnnotationAStrictTestAP.class.getName(),
1193                      "--module-source-path", src.toString(),
1194                      "-m", "m4x")
1195             .outdir(classes)
1196             .run()
1197             .writeAll()
1198             .getOutputLines(OutputKind.STDERR);
1199 
1200         expected = List.of("SelectAnnotationATestAP",
1201                            "SelectAnnotationBTestAP",
1202                            "SelectAnnotationAStrictTestAP",
1203                            "SelectAnnotationATestAP",
1204                            "SelectAnnotationBTestAP",
1205                            "SelectAnnotationAStrictTestAP");
1206 
1207         if (!expected.equals(log)) {
1208             throw new AssertionError("Output does not match; output: " + log);
1209         }
1210     }
1211 
1212     @Test
1213     public void testDisambiguateAnnotationsUnnamedModule(Path base) throws Exception {
1214         Path classes = base.resolve("classes");
1215 
1216         Files.createDirectories(classes);
1217 
1218         Path src = base.resolve("src");
1219 
1220         tb.writeJavaFiles(src,
1221                           "package api; public @interface A {}",
1222                           "package api; public @interface B {}",
1223                           "package impl; import api.*; @A @B public class T {}");
1224 
1225         List<String> log = new JavacTask(tb)
1226             .options("-processor", SelectAnnotationATestAP.class.getName() + "," +
1227                                    SelectAnnotationBTestAP.class.getName() + "," +
1228                                    SelectAnnotationAStrictTestAP.class.getName())
1229             .outdir(classes)
1230             .files(findJavaFiles(src))
1231             .run()
1232             .writeAll()
1233             .getOutputLines(OutputKind.STDERR);
1234 
1235         List<String> expected = List.of("SelectAnnotationBTestAP",
1236                                         "SelectAnnotationBTestAP");
1237 
1238         if (!expected.equals(log)) {
1239             throw new AssertionError("Output does not match; output: " + log);
1240         }
1241     }
1242 
1243     @Test
1244     public void testDisambiguateAnnotationsNoModules(Path base) throws Exception {
1245         Path classes = base.resolve("classes");
1246 
1247         Files.createDirectories(classes);
1248 
1249         Path src = base.resolve("src");
1250 
1251         tb.writeJavaFiles(src,
1252                           "package api; public @interface A {}",
1253                           "package api; public @interface B {}",
1254                           "package impl; import api.*; @A @B public class T {}");
1255 
1256         List<String> log = new JavacTask(tb)
1257             .options("-processor", SelectAnnotationATestAP.class.getName() + "," +
1258                                    SelectAnnotationBTestAP.class.getName() + "," +
1259                                    SelectAnnotationAStrictTestAP.class.getName(),
1260                      "-source", "8", "-target", "8")
1261             .outdir(classes)
1262             .files(findJavaFiles(src))
1263             .run()
1264             .writeAll()
1265             .getOutputLines(OutputKind.STDERR);
1266 
1267         List<String> expected = List.of("SelectAnnotationATestAP",
1268                                         "SelectAnnotationBTestAP",
1269                                         "SelectAnnotationATestAP",
1270                                         "SelectAnnotationBTestAP");
1271 
1272         if (!expected.equals(log)) {
1273             throw new AssertionError("Output does not match; output: " + log);
1274         }
1275     }
1276 
1277     @SupportedAnnotationTypes("m2x/api.A")
1278     public static final class SelectAnnotationATestAP extends AbstractProcessor {
1279 
1280         @Override
1281         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1282             System.err.println("SelectAnnotationATestAP");
1283 
1284             return false;
1285         }
1286 
1287     }
1288 
1289     @SupportedAnnotationTypes("api.B")
1290     public static final class SelectAnnotationBTestAP extends AbstractProcessor {
1291 
1292         @Override
1293         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1294             System.err.println("SelectAnnotationBTestAP");
1295 
1296             return false;
1297         }
1298 
1299     }
1300 
1301     public static final class SelectAnnotationAStrictTestAP extends AbstractProcessor {
1302 
1303         @Override
1304         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1305             System.err.println("SelectAnnotationAStrictTestAP");
1306 
1307             return false;
1308         }
1309 
1310         @Override
1311         public Set<String> getSupportedAnnotationTypes() {
1312             return Set.of("m2x/api.A");
1313         }
1314 
1315     }
1316 
1317     private static void writeFile(String content, Path base, String... pathElements) {
1318         try {
1319             Path file = resolveFile(base, pathElements);
1320 
1321             Files.createDirectories(file.getParent());
1322 
1323             try (Writer out = Files.newBufferedWriter(file)) {
1324                 out.append(content);
1325             }
1326         } catch (IOException ex) {
1327             throw new UncheckedIOException(ex);
1328         }
1329     }
1330 
1331     @Test
1332     public void testUnboundLookup(Path base) throws Exception {
1333         Path src = base.resolve("src");
1334 
1335         tb.writeJavaFiles(src,
1336                           "package impl.conflict.src; public class Impl { }");
1337 
1338         Path moduleSrc = base.resolve("module-src");
1339         Path m1 = moduleSrc.resolve("m1x");
1340         Path m2 = moduleSrc.resolve("m2x");
1341 
1342         Path classes = base.resolve("classes");
1343         Path cpClasses = base.resolve("cpClasses");
1344 
1345         Files.createDirectories(classes);
1346         Files.createDirectories(cpClasses);
1347 
1348         tb.writeJavaFiles(m1,
1349                           "module m1x { }",
1350                           "package impl1; public class Impl { }",
1351                           "package impl.conflict.module; class Impl { }",
1352                           "package impl.conflict.clazz; public class pkg { public static class I { } }",
1353                           "package impl.conflict.src; public class Impl { }",
1354                           "package nested.pack.pack; public class Impl { }",
1355                           "package unique.nested; public class Impl { }");
1356 
1357         tb.writeJavaFiles(m2,
1358                           "module m2x { }",
1359                           "package impl2; public class Impl { }",
1360                           "package impl.conflict.module; class Impl { }",
1361                           "package impl.conflict; public class clazz { public static class pkg { } }",
1362                           "package nested.pack; public class Impl { }");
1363 
1364         //from source:
1365         String log = new JavacTask(tb)
1366             .options("--module-source-path", moduleSrc.toString(),
1367                      "--source-path", src.toString(),
1368                      "-processorpath", System.getProperty("test.class.path"),
1369                      "-processor", UnboundLookup.class.getName(),
1370                      "-XDrawDiagnostics")
1371             .outdir(classes)
1372             .files(findJavaFiles(moduleSrc))
1373             .run()
1374             .writeAll()
1375             .getOutput(OutputKind.DIRECT);
1376 
1377         String moduleImplConflictString =
1378                 "- compiler.note.multiple.elements: getTypeElement, impl.conflict.module.Impl, m2x, m1x";
1379         String srcConflictString =
1380                 "- compiler.note.multiple.elements: getTypeElement, impl.conflict.src.Impl, m1x, unnamed module";
1381 
1382         if (!log.contains(moduleImplConflictString) ||
1383             !log.contains(srcConflictString)) {
1384             throw new AssertionError("Expected output not found: " + log);
1385         }
1386 
1387         if (log.split(Pattern.quote(moduleImplConflictString)).length > 2) {
1388             throw new AssertionError("Too many warnings in: " + log);
1389         }
1390 
1391         new JavacTask(tb)
1392             .options("--source-path", src.toString())
1393             .outdir(cpClasses)
1394             .files(findJavaFiles(src))
1395             .run()
1396             .writeAll();
1397 
1398         //from classfiles:
1399         new JavacTask(tb)
1400             .options("--module-path", classes.toString(),
1401                      "--class-path", cpClasses.toString(),
1402                      "--add-modules", "m1x,m2x",
1403                      "-processorpath", System.getProperty("test.class.path"),
1404                      "-processor", UnboundLookup.class.getName(),
1405                      "-proc:only")
1406             .classes("java.lang.Object")
1407             .run()
1408             .writeAll();
1409 
1410         //source 8:
1411         new JavacTask(tb)
1412             .options("--source-path", src.toString(),
1413                      "-source", "8",
1414                      "-processorpath", System.getProperty("test.class.path"),
1415                      "-processor", UnboundLookup8.class.getName())
1416             .outdir(cpClasses)
1417             .files(findJavaFiles(src))
1418             .run()
1419             .writeAll();
1420 
1421     }
1422 
1423     @SupportedAnnotationTypes("*")
1424     public static final class UnboundLookup extends AbstractProcessor {
1425 
1426         @Override
1427         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1428             assertTypeElementExists("impl1.Impl", "m1x");
1429             assertPackageElementExists("impl1", "m1x");
1430             assertTypeElementExists("impl2.Impl", "m2x");
1431             assertTypeElementExists("impl.conflict.clazz.pkg.I", "m1x");
1432             assertTypeElementExists("impl.conflict.clazz", "m2x");
1433             assertPackageElementExists("impl.conflict.clazz", "m1x");
1434             assertPackageElementExists("impl2", "m2x");
1435             assertPackageElementExists("nested.pack.pack", "m1x");
1436             assertPackageElementExists("nested.pack", "m2x");
1437             assertTypeElementExists("unique.nested.Impl", "m1x");
1438             assertTypeElementNotFound("impl.conflict.module.Impl");
1439             assertTypeElementNotFound("impl.conflict.module.Impl"); //check that the warning/note is produced only once
1440             assertPackageElementNotFound("impl.conflict.module");
1441             assertTypeElementNotFound("impl.conflict.src.Impl");
1442             assertPackageElementNotFound("impl.conflict.src");
1443             assertTypeElementNotFound("impl.conflict.clazz.pkg");
1444             assertPackageElementNotFound("unique"); //do not return packages without members in module mode
1445             assertTypeElementNotFound("nested"); //cannot distinguish between m1x and m2x
1446 
1447             return false;
1448         }
1449 
1450         private void assertTypeElementExists(String name, String expectedModule) {
1451             assertElementExists(name, "class", processingEnv.getElementUtils() :: getTypeElement, expectedModule);
1452         }
1453 
1454         private void assertPackageElementExists(String name, String expectedModule) {
1455             assertElementExists(name, "package", processingEnv.getElementUtils() :: getPackageElement, expectedModule);
1456         }
1457 
1458         private void assertElementExists(String name, String type, Function<String, Element> getter, String expectedModule) {
1459             Element clazz = getter.apply(name);
1460 
1461             if (clazz == null) {
1462                 throw new AssertionError("No " + name + " " + type + " found.");
1463             }
1464 
1465             ModuleElement mod = processingEnv.getElementUtils().getModuleOf(clazz);
1466 
1467             if (!mod.getQualifiedName().contentEquals(expectedModule)) {
1468                 throw new AssertionError(name + " found in an unexpected module: " + mod.getQualifiedName());
1469             }
1470         }
1471 
1472         private void assertTypeElementNotFound(String name) {
1473             assertElementNotFound(name, processingEnv.getElementUtils() :: getTypeElement);
1474         }
1475 
1476         private void assertPackageElementNotFound(String name) {
1477             assertElementNotFound(name, processingEnv.getElementUtils() :: getPackageElement);
1478         }
1479 
1480         private void assertElementNotFound(String name, Function<String, Element> getter) {
1481             Element found = getter.apply(name);
1482 
1483             if (found != null) {
1484                 fail("Element found unexpectedly: " + found);
1485             }
1486         }
1487 
1488         @Override
1489         public SourceVersion getSupportedSourceVersion() {
1490             return SourceVersion.latest();
1491         }
1492 
1493     }
1494 
1495     @SupportedAnnotationTypes("*")
1496     public static final class UnboundLookup8 extends AbstractProcessor {
1497 
1498         @Override
1499         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1500             if (processingEnv.getElementUtils().getTypeElement("impl.conflict.src.Impl") == null) {
1501                 throw new AssertionError("impl.conflict.src.Impl.");
1502             }
1503 
1504             if (processingEnv.getElementUtils().getModuleElement("java.base") != null) {
1505                 throw new AssertionError("getModuleElement != null for -source 8");
1506             }
1507 
1508             return false;
1509         }
1510 
1511         @Override
1512         public SourceVersion getSupportedSourceVersion() {
1513             return SourceVersion.latest();
1514         }
1515 
1516     }
1517 
1518     @Test
1519     public void testWrongDefaultTargetModule(Path base) throws Exception {
1520         Path src = base.resolve("src");
1521 
1522         tb.writeJavaFiles(src,
1523                           "package test; public class Test { }");
1524 
1525         Path classes = base.resolve("classes");
1526 
1527         Files.createDirectories(classes);
1528 
1529         List<String> log = new JavacTask(tb)
1530             .options("--default-module-for-created-files=m!",
1531                      "-XDrawDiagnostics")
1532             .outdir(classes)
1533             .files(findJavaFiles(src))
1534             .run(Task.Expect.FAIL)
1535             .writeAll()
1536             .getOutputLines(OutputKind.DIRECT);
1537 
1538         List<String> expected = Arrays.asList(
1539             "- compiler.err.bad.name.for.option: --default-module-for-created-files, m!"
1540         );
1541 
1542         if (!log.equals(expected)) {
1543             throw new AssertionError("Expected output not found.");
1544         }
1545     }
1546 
1547     private static void assertNonNull(String msg, Object val) {
1548         if (val == null) {
1549             throw new AssertionError(msg);
1550         }
1551     }
1552 
1553     private static void assertNull(String msg, Object val) {
1554         if (val != null) {
1555             throw new AssertionError(msg);
1556         }
1557     }
1558 
1559     private static void assertEquals(Object expected, Object actual) {
1560         if (!Objects.equals(expected, actual)) {
1561             throw new AssertionError("expected: " + expected + "; actual=" + actual);
1562         }
1563     }
1564 
1565     private static void assertFileExists(Path base, String... pathElements) {
1566         Path file = resolveFile(base, pathElements);
1567 
1568         if (!Files.exists(file)) {
1569             throw new AssertionError("Expected file: " + file + " exist, but it does not.");
1570         }
1571     }
1572 
1573     private static void assertFileNotExists(Path base, String... pathElements) {
1574         Path file = resolveFile(base, pathElements);
1575 
1576         if (Files.exists(file)) {
1577             throw new AssertionError("Expected file: " + file + " exist, but it does not.");
1578         }
1579     }
1580 
1581     static Path resolveFile(Path base, String... pathElements) {
1582         Path file = base;
1583 
1584         for (String el : pathElements) {
1585             file = file.resolve(el);
1586         }
1587 
1588         return file;
1589     }
1590 
1591     private static void fail(String msg) {
1592         throw new AssertionError(msg);
1593     }
1594 
1595 }