AnnotationProcessing.java revision 3865:c6b4fefd764c
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 * @bug 8133884 8162711 8133896 8172158 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 36import java.io.File; 37import java.io.IOException; 38import java.io.OutputStream; 39import java.io.Reader; 40import java.io.Writer; 41import java.nio.file.Files; 42import java.nio.file.Path; 43import java.nio.file.Paths; 44import java.util.Arrays; 45import java.util.HashMap; 46import java.util.HashSet; 47import java.util.List; 48import java.util.Map; 49import java.util.Objects; 50import java.util.Set; 51import java.util.concurrent.Callable; 52import java.util.function.Function; 53import java.util.stream.Collectors; 54 55import javax.annotation.processing.AbstractProcessor; 56import javax.annotation.processing.Filer; 57import javax.annotation.processing.FilerException; 58import javax.annotation.processing.Messager; 59import javax.annotation.processing.ProcessingEnvironment; 60import javax.annotation.processing.RoundEnvironment; 61import javax.annotation.processing.SupportedAnnotationTypes; 62import javax.annotation.processing.SupportedOptions; 63import javax.lang.model.SourceVersion; 64import javax.lang.model.element.Element; 65import javax.lang.model.element.ElementKind; 66import javax.lang.model.element.ModuleElement; 67import javax.lang.model.element.ModuleElement.ProvidesDirective; 68import javax.lang.model.element.ModuleElement.UsesDirective; 69import javax.lang.model.element.PackageElement; 70import javax.lang.model.element.TypeElement; 71import javax.lang.model.element.VariableElement; 72import javax.lang.model.type.TypeKind; 73import javax.lang.model.util.ElementFilter; 74import javax.lang.model.util.ElementScanner9; 75import javax.tools.Diagnostic.Kind; 76import javax.tools.FileObject; 77import javax.tools.JavaCompiler; 78import javax.tools.JavaCompiler.CompilationTask; 79import javax.tools.JavaFileManager; 80import javax.tools.JavaFileManager.Location; 81import javax.tools.JavaFileObject; 82import javax.tools.StandardJavaFileManager; 83import javax.tools.StandardLocation; 84import javax.tools.ToolProvider; 85 86import toolbox.JavacTask; 87import toolbox.Task; 88import toolbox.Task.Mode; 89import toolbox.Task.OutputKind; 90 91public class AnnotationProcessing extends ModuleTestBase { 92 93 public static void main(String... args) throws Exception { 94 new AnnotationProcessing().runTests(); 95 } 96 97 @Test 98 public void testAPSingleModule(Path base) throws Exception { 99 Path moduleSrc = base.resolve("module-src"); 100 Path m1 = moduleSrc.resolve("m1x"); 101 102 Path classes = base.resolve("classes"); 103 104 Files.createDirectories(classes); 105 106 tb.writeJavaFiles(m1, 107 "module m1x { }", 108 "package impl; public class Impl { }"); 109 110 String log = new JavacTask(tb) 111 .options("--module-source-path", moduleSrc.toString(), 112 "-processor", AP.class.getName(), 113 "-AexpectedEnclosedElements=m1x=>impl") 114 .outdir(classes) 115 .files(findJavaFiles(moduleSrc)) 116 .run() 117 .writeAll() 118 .getOutput(Task.OutputKind.DIRECT); 119 120 if (!log.isEmpty()) 121 throw new AssertionError("Unexpected output: " + log); 122 } 123 124 @Test 125 public void testAPMultiModule(Path base) throws Exception { 126 Path moduleSrc = base.resolve("module-src"); 127 Path m1 = moduleSrc.resolve("m1x"); 128 Path m2 = moduleSrc.resolve("m2x"); 129 130 Path classes = base.resolve("classes"); 131 132 Files.createDirectories(classes); 133 134 tb.writeJavaFiles(m1, 135 "module m1x { }", 136 "package impl1; public class Impl1 { }"); 137 138 tb.writeJavaFiles(m2, 139 "module m2x { }", 140 "package impl2; public class Impl2 { }"); 141 142 String log = new JavacTask(tb) 143 .options("--module-source-path", moduleSrc.toString(), 144 "-processor", AP.class.getName(), 145 "-AexpectedEnclosedElements=m1x=>impl1,m2x=>impl2") 146 .outdir(classes) 147 .files(findJavaFiles(moduleSrc)) 148 .run() 149 .writeAll() 150 .getOutput(Task.OutputKind.DIRECT); 151 152 if (!log.isEmpty()) 153 throw new AssertionError("Unexpected output: " + log); 154 } 155 156 @SupportedAnnotationTypes("*") 157 @SupportedOptions("expectedEnclosedElements") 158 public static final class AP extends AbstractProcessor { 159 160 private Map<String, List<String>> module2ExpectedEnclosedElements; 161 private Set<String> seenModules = new HashSet<>(); 162 163 @Override 164 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 165 if (module2ExpectedEnclosedElements == null) { 166 module2ExpectedEnclosedElements = new HashMap<>(); 167 168 String expectedEnclosedElements = 169 processingEnv.getOptions().get("expectedEnclosedElements"); 170 171 for (String moduleDef : expectedEnclosedElements.split(",")) { 172 String[] module2Packages = moduleDef.split("=>"); 173 174 module2ExpectedEnclosedElements.put(module2Packages[0], 175 Arrays.asList(module2Packages[1].split(":"))); 176 } 177 } 178 179 //verify ModuleType and ModuleSymbol behavior: 180 for (Element root : roundEnv.getRootElements()) { 181 ModuleElement module = processingEnv.getElementUtils().getModuleOf(root); 182 183 assertEquals(TypeKind.MODULE, module.asType().getKind()); 184 185 boolean[] seenModule = new boolean[1]; 186 187 module.accept(new ElementScanner9<Void, Void>() { 188 @Override 189 public Void visitModule(ModuleElement e, Void p) { 190 seenModule[0] = true; 191 return null; 192 } 193 @Override 194 public Void scan(Element e, Void p) { 195 throw new AssertionError("Shouldn't get here."); 196 } 197 }, null); 198 199 assertEquals(true, seenModule[0]); 200 201 List<String> actualElements = 202 module.getEnclosedElements() 203 .stream() 204 .map(s -> (PackageElement) s) 205 .map(p -> p.getQualifiedName().toString()) 206 .collect(Collectors.toList()); 207 208 String moduleName = module.getQualifiedName().toString(); 209 210 assertEquals(module2ExpectedEnclosedElements.get(moduleName), 211 actualElements); 212 213 seenModules.add(moduleName); 214 } 215 216 if (roundEnv.processingOver()) { 217 assertEquals(module2ExpectedEnclosedElements.keySet(), seenModules); 218 } 219 220 return false; 221 } 222 223 @Override 224 public SourceVersion getSupportedSourceVersion() { 225 return SourceVersion.latest(); 226 } 227 228 } 229 230 @Test 231 public void testVerifyUsesProvides(Path base) throws Exception { 232 Path moduleSrc = base.resolve("module-src"); 233 Path m1 = moduleSrc.resolve("m1x"); 234 235 Path classes = base.resolve("classes"); 236 237 Files.createDirectories(classes); 238 239 tb.writeJavaFiles(m1, 240 "module m1x { exports api; uses api.Api; provides api.Api with impl.Impl; }", 241 "package api; public class Api { }", 242 "package impl; public class Impl extends api.Api { }"); 243 244 String log = new JavacTask(tb) 245 .options("-doe", "-processor", VerifyUsesProvidesAP.class.getName()) 246 .outdir(classes) 247 .files(findJavaFiles(moduleSrc)) 248 .run() 249 .writeAll() 250 .getOutput(Task.OutputKind.DIRECT); 251 252 if (!log.isEmpty()) 253 throw new AssertionError("Unexpected output: " + log); 254 } 255 256 @SupportedAnnotationTypes("*") 257 public static final class VerifyUsesProvidesAP extends AbstractProcessor { 258 259 @Override 260 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 261 TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api"); 262 263 assertNonNull("Cannot find api.Api", api); 264 265 ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement(); 266 267 assertNonNull("modle is null", modle); 268 269 List<? extends UsesDirective> uses = ElementFilter.usesIn(modle.getDirectives()); 270 assertEquals(1, uses.size()); 271 assertEquals("api.Api", uses.iterator().next().getService().getQualifiedName().toString()); 272 273 List<? extends ProvidesDirective> provides = ElementFilter.providesIn(modle.getDirectives()); 274 assertEquals(1, provides.size()); 275 assertEquals("api.Api", provides.iterator().next().getService().getQualifiedName().toString()); 276 assertEquals("impl.Impl", provides.iterator().next().getImplementations().get(0).getQualifiedName().toString()); 277 278 return false; 279 } 280 281 @Override 282 public SourceVersion getSupportedSourceVersion() { 283 return SourceVersion.latest(); 284 } 285 286 } 287 288 @Test 289 public void testPackageNoModule(Path base) throws Exception { 290 Path src = base.resolve("src"); 291 Path classes = base.resolve("classes"); 292 293 Files.createDirectories(classes); 294 295 tb.writeJavaFiles(src, 296 "package api; public class Api { }"); 297 298 String log = new JavacTask(tb) 299 .options("-processor", VerifyPackageNoModule.class.getName(), 300 "-source", "8", 301 "-Xlint:-options") 302 .outdir(classes) 303 .files(findJavaFiles(src)) 304 .run() 305 .writeAll() 306 .getOutput(Task.OutputKind.DIRECT); 307 308 if (!log.isEmpty()) 309 throw new AssertionError("Unexpected output: " + log); 310 } 311 312 @SupportedAnnotationTypes("*") 313 public static final class VerifyPackageNoModule extends AbstractProcessor { 314 315 @Override 316 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 317 TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api"); 318 319 assertNonNull("Cannot find api.Api", api); 320 321 ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement(); 322 323 assertNull("modle is not null", modle); 324 325 return false; 326 } 327 328 @Override 329 public SourceVersion getSupportedSourceVersion() { 330 return SourceVersion.latest(); 331 } 332 333 } 334 335 @Test 336 public void testQualifiedClassForProcessing(Path base) throws Exception { 337 Path moduleSrc = base.resolve("module-src"); 338 Path m1 = moduleSrc.resolve("m1x"); 339 Path m2 = moduleSrc.resolve("m2x"); 340 341 Path classes = base.resolve("classes"); 342 343 Files.createDirectories(classes); 344 345 tb.writeJavaFiles(m1, 346 "module m1x { }", 347 "package impl; public class Impl { int m1x; }"); 348 349 tb.writeJavaFiles(m2, 350 "module m2x { }", 351 "package impl; public class Impl { int m2x; }"); 352 353 new JavacTask(tb) 354 .options("--module-source-path", moduleSrc.toString()) 355 .outdir(classes) 356 .files(findJavaFiles(moduleSrc)) 357 .run() 358 .writeAll() 359 .getOutput(Task.OutputKind.DIRECT); 360 361 List<String> expected = Arrays.asList("Note: field: m1x"); 362 363 for (Mode mode : new Mode[] {Mode.API, Mode.CMDLINE}) { 364 List<String> log = new JavacTask(tb, mode) 365 .options("-processor", QualifiedClassForProcessing.class.getName(), 366 "--module-path", classes.toString()) 367 .classes("m1x/impl.Impl") 368 .outdir(classes) 369 .run() 370 .writeAll() 371 .getOutputLines(Task.OutputKind.DIRECT); 372 373 if (!expected.equals(log)) 374 throw new AssertionError("Unexpected output: " + log); 375 } 376 } 377 378 @SupportedAnnotationTypes("*") 379 public static final class QualifiedClassForProcessing extends AbstractProcessor { 380 381 @Override 382 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 383 if (processingEnv.getElementUtils().getModuleElement("m1x") == null) { 384 throw new AssertionError("No m1x module found."); 385 } 386 387 Messager messager = processingEnv.getMessager(); 388 389 for (TypeElement clazz : ElementFilter.typesIn(roundEnv.getRootElements())) { 390 for (VariableElement field : ElementFilter.fieldsIn(clazz.getEnclosedElements())) { 391 messager.printMessage(Kind.NOTE, "field: " + field.getSimpleName()); 392 } 393 } 394 395 return false; 396 } 397 398 @Override 399 public SourceVersion getSupportedSourceVersion() { 400 return SourceVersion.latest(); 401 } 402 403 } 404 405 @Test 406 public void testModuleInRootElements(Path base) throws Exception { 407 Path moduleSrc = base.resolve("module-src"); 408 Path m1 = moduleSrc.resolve("m1"); 409 410 Path classes = base.resolve("classes"); 411 412 Files.createDirectories(classes); 413 414 tb.writeJavaFiles(m1, 415 "module m1x { exports api; }", 416 "package api; public class Api { }"); 417 418 List<String> log = new JavacTask(tb) 419 .options("-processor", ModuleInRootElementsAP.class.getName()) 420 .outdir(classes) 421 .files(findJavaFiles(moduleSrc)) 422 .run() 423 .writeAll() 424 .getOutputLines(Task.OutputKind.STDERR); 425 426 assertEquals(Arrays.asList("module: m1x"), log); 427 } 428 429 @SupportedAnnotationTypes("*") 430 public static final class ModuleInRootElementsAP extends AbstractProcessor { 431 432 @Override 433 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 434 roundEnv.getRootElements() 435 .stream() 436 .filter(el -> el.getKind() == ElementKind.MODULE) 437 .forEach(mod -> System.err.println("module: " + mod.getSimpleName())); 438 439 return false; 440 } 441 442 @Override 443 public SourceVersion getSupportedSourceVersion() { 444 return SourceVersion.latest(); 445 } 446 447 } 448 449 @Test 450 public void testAnnotationsInModuleInfo(Path base) throws Exception { 451 Path moduleSrc = base.resolve("module-src"); 452 Path m1 = moduleSrc.resolve("m1"); 453 454 tb.writeJavaFiles(m1, 455 "@Deprecated module m1x { }"); 456 457 Path m2 = moduleSrc.resolve("m2x"); 458 459 tb.writeJavaFiles(m2, 460 "@SuppressWarnings(\"\") module m2x { }"); 461 462 Path classes = base.resolve("classes"); 463 464 Files.createDirectories(classes); 465 466 List<String> log = new JavacTask(tb) 467 .options("-processor", AnnotationsInModuleInfoPrint.class.getName()) 468 .outdir(classes) 469 .files(findJavaFiles(m1)) 470 .run() 471 .writeAll() 472 .getOutputLines(Task.OutputKind.DIRECT); 473 474 List<String> expectedLog = Arrays.asList("Note: AP Invoked", 475 "Note: AP Invoked"); 476 477 assertEquals(expectedLog, log); 478 479 new JavacTask(tb) 480 .options("-processor", AnnotationsInModuleInfoFail.class.getName()) 481 .outdir(classes) 482 .files(findJavaFiles(m2)) 483 .run() 484 .writeAll(); 485 } 486 487 @SupportedAnnotationTypes("java.lang.Deprecated") 488 public static final class AnnotationsInModuleInfoPrint extends AbstractProcessor { 489 490 @Override 491 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 492 processingEnv.getMessager().printMessage(Kind.NOTE, "AP Invoked"); 493 return false; 494 } 495 496 @Override 497 public SourceVersion getSupportedSourceVersion() { 498 return SourceVersion.latest(); 499 } 500 501 } 502 503 @SupportedAnnotationTypes("java.lang.Deprecated") 504 public static final class AnnotationsInModuleInfoFail extends AbstractProcessor { 505 506 @Override 507 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 508 throw new AssertionError(); 509 } 510 511 @Override 512 public SourceVersion getSupportedSourceVersion() { 513 return SourceVersion.latest(); 514 } 515 516 } 517 518 @Test 519 public void testGenerateInMultiModeAPI(Path base) throws Exception { 520 Path moduleSrc = base.resolve("module-src"); 521 Path classes = base.resolve("classes"); 522 523 Files.createDirectories(classes); 524 525 Path m1 = moduleSrc.resolve("m1x"); 526 527 tb.writeJavaFiles(m1, 528 "module m1x { exports api1; }", 529 "package api1; public class Api { GenApi ga; impl.Impl i; }"); 530 531 writeFile("1", m1, "api1", "api"); 532 writeFile("1", m1, "impl", "impl"); 533 534 Path m2 = moduleSrc.resolve("m2x"); 535 536 tb.writeJavaFiles(m2, 537 "module m2x { requires m1x; exports api2; }", 538 "package api2; public class Api { api1.GenApi ga1; GenApi qa2; impl.Impl i;}"); 539 540 writeFile("2", m2, "api2", "api"); 541 writeFile("2", m2, "impl", "impl"); 542 543 for (FileType fileType : FileType.values()) { 544 if (Files.isDirectory(classes)) { 545 tb.cleanDirectory(classes); 546 } else { 547 Files.createDirectories(classes); 548 } 549 550 new JavacTask(tb) 551 .options("-processor", MultiModeAPITestAP.class.getName(), 552 "--module-source-path", moduleSrc.toString(), 553 "-Afiletype=" + fileType.name()) 554 .outdir(classes) 555 .files(findJavaFiles(moduleSrc)) 556 .run() 557 .writeAll(); 558 559 assertFileExists(classes, "m1x", "api1", "GenApi.class"); 560 assertFileExists(classes, "m1x", "impl", "Impl.class"); 561 assertFileExists(classes, "m1x", "api1", "gen1"); 562 assertFileExists(classes, "m2x", "api2", "GenApi.class"); 563 assertFileExists(classes, "m2x", "impl", "Impl.class"); 564 assertFileExists(classes, "m2x", "api2", "gen1"); 565 } 566 } 567 568 enum FileType { 569 SOURCE, 570 CLASS; 571 } 572 573 public static abstract class GeneratingAP extends AbstractProcessor { 574 575 void createSource(CreateFileObject file, String name, String content) { 576 try (Writer out = file.create().openWriter()) { 577 out.write(content); 578 } catch (IOException ex) { 579 throw new IllegalStateException(ex); 580 } 581 } 582 583 void createClass(CreateFileObject file, String name, String content) { 584 String fileNameStub = name.replace(".", File.separator); 585 586 try (OutputStream out = file.create().openOutputStream()) { 587 Path scratch = Files.createDirectories(Paths.get("")); 588 Path scratchSrc = scratch.resolve(fileNameStub + ".java").toAbsolutePath(); 589 590 Files.createDirectories(scratchSrc.getParent()); 591 592 try (Writer w = Files.newBufferedWriter(scratchSrc)) { 593 w.write(content); 594 } 595 596 Path scratchClasses = scratch.resolve("classes"); 597 598 Files.createDirectories(scratchClasses); 599 600 JavaCompiler comp = ToolProvider.getSystemJavaCompiler(); 601 try (StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null)) { 602 List<String> options = Arrays.asList("-d", scratchClasses.toString()); 603 Iterable<? extends JavaFileObject> files = fm.getJavaFileObjects(scratchSrc); 604 CompilationTask task = comp.getTask(null, fm, null, options, null, files); 605 606 if (!task.call()) { 607 throw new AssertionError("compilation failed"); 608 } 609 } 610 611 Path classfile = scratchClasses.resolve(fileNameStub + ".class"); 612 613 Files.copy(classfile, out); 614 } catch (IOException ex) { 615 throw new IllegalStateException(ex); 616 } 617 } 618 619 void doReadResource(CreateFileObject file, String expectedContent) { 620 try { 621 StringBuilder actualContent = new StringBuilder(); 622 623 try (Reader r = file.create().openReader(true)) { 624 int read; 625 626 while ((read = r.read()) != (-1)) { 627 actualContent.append((char) read); 628 } 629 630 } 631 632 assertEquals(expectedContent, actualContent.toString()); 633 } catch (IOException ex) { 634 throw new IllegalStateException(ex); 635 } 636 } 637 638 public interface CreateFileObject { 639 public FileObject create() throws IOException; 640 } 641 642 void expectFilerException(Callable<Object> c) { 643 try { 644 c.call(); 645 throw new AssertionError("Expected exception not thrown"); 646 } catch (FilerException ex) { 647 //expected 648 } catch (Exception ex) { 649 throw new IllegalStateException(ex); 650 } 651 } 652 653 @Override 654 public SourceVersion getSupportedSourceVersion() { 655 return SourceVersion.latest(); 656 } 657 658 } 659 660 @SupportedAnnotationTypes("*") 661 @SupportedOptions({"filetype", "modulename"}) 662 public static final class MultiModeAPITestAP extends GeneratingAP { 663 664 int round; 665 666 @Override 667 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 668 if (round++ != 0) 669 return false; 670 671 createClass("m1x", "api1.GenApi", "package api1; public class GenApi {}"); 672 createClass("m1x", "impl.Impl", "package impl; public class Impl {}"); 673 createClass("m2x", "api2.GenApi", "package api2; public class GenApi {}"); 674 createClass("m2x", "impl.Impl", "package impl; public class Impl {}"); 675 676 createResource("m1x", "api1", "gen1"); 677 createResource("m2x", "api2", "gen1"); 678 679 readResource("m1x", "api1", "api", "1"); 680 readResource("m1x", "impl", "impl", "1"); 681 readResource("m2x", "api2", "api", "2"); 682 readResource("m2x", "impl", "impl", "2"); 683 684 Filer filer = processingEnv.getFiler(); 685 686 expectFilerException(() -> filer.createSourceFile("fail.Fail")); 687 expectFilerException(() -> filer.createClassFile("fail.Fail")); 688 expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "fail", "fail")); 689 expectFilerException(() -> filer.getResource(StandardLocation.MODULE_SOURCE_PATH, "fail", "fail")); 690 691 //must not generate to unnamed package: 692 expectFilerException(() -> filer.createSourceFile("m1/Fail")); 693 expectFilerException(() -> filer.createClassFile("m1/Fail")); 694 695 //cannot generate resources to modules that are not root modules: 696 expectFilerException(() -> filer.createSourceFile("java.base/fail.Fail")); 697 expectFilerException(() -> filer.createClassFile("java.base/fail.Fail")); 698 expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "java.base/fail", "Fail")); 699 700 return false; 701 } 702 703 void createClass(String expectedModule, String name, String content) { 704 Filer filer = processingEnv.getFiler(); 705 FileType filetype = FileType.valueOf(processingEnv.getOptions().getOrDefault("filetype", "")); 706 707 switch (filetype) { 708 case SOURCE: 709 createSource(() -> filer.createSourceFile(expectedModule + "/" + name), name, content); 710 break; 711 case CLASS: 712 createClass(() -> filer.createClassFile(expectedModule + "/" + name), name, content); 713 break; 714 default: 715 throw new AssertionError("Unexpected filetype: " + filetype); 716 } 717 } 718 719 void createResource(String expectedModule, String pkg, String relName) { 720 try { 721 Filer filer = processingEnv.getFiler(); 722 723 filer.createResource(StandardLocation.CLASS_OUTPUT, expectedModule + "/" + pkg, relName) 724 .openOutputStream() 725 .close(); 726 } catch (IOException ex) { 727 throw new IllegalStateException(ex); 728 } 729 } 730 731 void readResource(String expectedModule, String pkg, String relName, String expectedContent) { 732 Filer filer = processingEnv.getFiler(); 733 734 doReadResource(() -> filer.getResource(StandardLocation.MODULE_SOURCE_PATH, expectedModule + "/" + pkg, relName), 735 expectedContent); 736 } 737 738 } 739 740 @Test 741 public void testGenerateInSingleNameModeAPI(Path base) throws Exception { 742 Path classes = base.resolve("classes"); 743 744 Files.createDirectories(classes); 745 746 Path m1 = base.resolve("module-src"); 747 748 tb.writeJavaFiles(m1, 749 "module m1x { }"); 750 751 writeFile("3", m1, "impl", "resource"); 752 753 new JavacTask(tb) 754 .options("-processor", SingleNameModeAPITestAP.class.getName(), 755 "-sourcepath", m1.toString()) 756 .outdir(classes) 757 .files(findJavaFiles(m1)) 758 .run() 759 .writeAll(); 760 761 assertFileExists(classes, "impl", "Impl1.class"); 762 assertFileExists(classes, "impl", "Impl2.class"); 763 assertFileExists(classes, "impl", "Impl3"); 764 assertFileExists(classes, "impl", "Impl4.class"); 765 assertFileExists(classes, "impl", "Impl5.class"); 766 assertFileExists(classes, "impl", "Impl6"); 767 assertFileExists(classes, "impl", "Impl7.class"); 768 assertFileExists(classes, "impl", "Impl8.class"); 769 assertFileExists(classes, "impl", "Impl9"); 770 } 771 772 773 @SupportedAnnotationTypes("*") 774 public static final class SingleNameModeAPITestAP extends GeneratingAP { 775 776 int round; 777 778 @Override 779 public synchronized void init(ProcessingEnvironment processingEnv) { 780 super.init(processingEnv); 781 } 782 783 @Override 784 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 785 if (round++ != 0) 786 return false; 787 788 Filer filer = processingEnv.getFiler(); 789 790 createSource(() -> filer.createSourceFile("impl.Impl1"), "impl.Impl1", "package impl; class Impl1 {}"); 791 createClass(() -> filer.createClassFile("impl.Impl2"), "impl.Impl2", "package impl; class Impl2 {}"); 792 createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "impl", "Impl3"), "impl.Impl3", ""); 793 doReadResource(() -> filer.getResource(StandardLocation.SOURCE_PATH, "impl", "resource"), "3"); 794 795 createSource(() -> filer.createSourceFile("m1x/impl.Impl4"), "impl.Impl4", "package impl; class Impl4 {}"); 796 createClass(() -> filer.createClassFile("m1x/impl.Impl5"), "impl.Impl5", "package impl; class Impl5 {}"); 797 createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "m1x/impl", "Impl6"), "impl.Impl6", ""); 798 doReadResource(() -> filer.getResource(StandardLocation.SOURCE_PATH, "m1x/impl", "resource"), "3"); 799 800 TypeElement jlObject = processingEnv.getElementUtils().getTypeElement("java.lang.Object"); 801 802 //"broken" originating element: 803 createSource(() -> filer.createSourceFile("impl.Impl7", jlObject), "impl.Impl7", "package impl; class Impl7 {}"); 804 createClass(() -> filer.createClassFile("impl.Impl8", jlObject), "impl.Impl8", "package impl; class Impl8 {}"); 805 createSource(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "impl", "Impl9", jlObject), "impl.Impl9", ""); 806 807 //must not generate to unnamed package: 808 expectFilerException(() -> filer.createSourceFile("Fail")); 809 expectFilerException(() -> filer.createClassFile("Fail")); 810 expectFilerException(() -> filer.createSourceFile("m1x/Fail")); 811 expectFilerException(() -> filer.createClassFile("m1x/Fail")); 812 813 //cannot generate resources to modules that are not root modules: 814 expectFilerException(() -> filer.createSourceFile("java.base/fail.Fail")); 815 expectFilerException(() -> filer.createClassFile("java.base/fail.Fail")); 816 expectFilerException(() -> filer.createResource(StandardLocation.CLASS_OUTPUT, "java.base/fail", "Fail")); 817 818 return false; 819 } 820 821 } 822 823 @Test 824 public void testGenerateInUnnamedModeAPI(Path base) throws Exception { 825 Path classes = base.resolve("classes"); 826 827 Files.createDirectories(classes); 828 829 Path src = base.resolve("src"); 830 831 tb.writeJavaFiles(src, 832 "class T {}"); 833 834 new JavacTask(tb) 835 .options("-processor", UnnamedModeAPITestAP.class.getName(), 836 "-sourcepath", src.toString()) 837 .outdir(classes) 838 .files(findJavaFiles(src)) 839 .run() 840 .writeAll(); 841 842 assertFileExists(classes, "Impl1.class"); 843 assertFileExists(classes, "Impl2.class"); 844 } 845 846 @Test 847 public void testGenerateInNoModeAPI(Path base) throws Exception { 848 Path classes = base.resolve("classes"); 849 850 Files.createDirectories(classes); 851 852 Path src = base.resolve("src"); 853 854 tb.writeJavaFiles(src, 855 "class T {}"); 856 857 new JavacTask(tb) 858 .options("-processor", UnnamedModeAPITestAP.class.getName(), 859 "-source", "8", "-target", "8", 860 "-sourcepath", src.toString()) 861 .outdir(classes) 862 .files(findJavaFiles(src)) 863 .run() 864 .writeAll(); 865 866 assertFileExists(classes, "Impl1.class"); 867 assertFileExists(classes, "Impl2.class"); 868 } 869 870 @SupportedAnnotationTypes("*") 871 public static final class UnnamedModeAPITestAP extends GeneratingAP { 872 873 int round; 874 875 @Override 876 public synchronized void init(ProcessingEnvironment processingEnv) { 877 super.init(processingEnv); 878 } 879 880 @Override 881 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 882 if (round++ != 0) 883 return false; 884 885 Filer filer = processingEnv.getFiler(); 886 887 //must not generate to unnamed package: 888 createSource(() -> filer.createSourceFile("Impl1"), "Impl1", "class Impl1 {}"); 889 createClass(() -> filer.createClassFile("Impl2"), "Impl2", "class Impl2 {}"); 890 891 return false; 892 } 893 894 } 895 896 @Test 897 public void testDisambiguateAnnotations(Path base) throws Exception { 898 Path classes = base.resolve("classes"); 899 900 Files.createDirectories(classes); 901 902 Path src = base.resolve("src"); 903 Path m1 = src.resolve("m1x"); 904 905 tb.writeJavaFiles(m1, 906 "module m1x { exports api; }", 907 "package api; public @interface A {}", 908 "package api; public @interface B {}"); 909 910 Path m2 = src.resolve("m2x"); 911 912 tb.writeJavaFiles(m2, 913 "module m2x { exports api; }", 914 "package api; public @interface A {}", 915 "package api; public @interface B {}"); 916 917 Path m3 = src.resolve("m3x"); 918 919 tb.writeJavaFiles(m3, 920 "module m3x { requires m1x; }", 921 "package impl; import api.*; @A @B public class T {}"); 922 923 Path m4 = src.resolve("m4x"); 924 925 tb.writeJavaFiles(m4, 926 "module m4x { requires m2x; }", 927 "package impl; import api.*; @A @B public class T {}"); 928 929 List<String> log; 930 List<String> expected; 931 932 log = new JavacTask(tb) 933 .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(), 934 "--module-source-path", src.toString(), 935 "-m", "m1x,m2x") 936 .outdir(classes) 937 .run() 938 .writeAll() 939 .getOutputLines(OutputKind.STDERR); 940 941 expected = Arrays.asList(""); 942 943 if (!expected.equals(log)) { 944 throw new AssertionError("Output does not match; output: " + log); 945 } 946 947 log = new JavacTask(tb) 948 .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(), 949 "--module-source-path", src.toString(), 950 "-m", "m3x") 951 .outdir(classes) 952 .run() 953 .writeAll() 954 .getOutputLines(OutputKind.STDERR); 955 956 expected = Arrays.asList("SelectAnnotationBTestAP", 957 "SelectAnnotationBTestAP"); 958 959 if (!expected.equals(log)) { 960 throw new AssertionError("Output does not match; output: " + log); 961 } 962 963 log = new JavacTask(tb) 964 .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(), 965 "--module-source-path", src.toString(), 966 "-m", "m4x") 967 .outdir(classes) 968 .run() 969 .writeAll() 970 .getOutputLines(OutputKind.STDERR); 971 972 expected = Arrays.asList("SelectAnnotationATestAP", 973 "SelectAnnotationBTestAP", 974 "SelectAnnotationATestAP", 975 "SelectAnnotationBTestAP"); 976 977 if (!expected.equals(log)) { 978 throw new AssertionError("Output does not match; output: " + log); 979 } 980 } 981 982 @Test 983 public void testDisambiguateAnnotationsNoModules(Path base) throws Exception { 984 Path classes = base.resolve("classes"); 985 986 Files.createDirectories(classes); 987 988 Path src = base.resolve("src"); 989 990 tb.writeJavaFiles(src, 991 "package api; public @interface A {}", 992 "package api; public @interface B {}", 993 "package impl; import api.*; @A @B public class T {}"); 994 995 List<String> log = new JavacTask(tb) 996 .options("-processor", SelectAnnotationATestAP.class.getName() + "," + SelectAnnotationBTestAP.class.getName(), 997 "-source", "8", "-target", "8") 998 .outdir(classes) 999 .files(findJavaFiles(src)) 1000 .run() 1001 .writeAll() 1002 .getOutputLines(OutputKind.STDERR); 1003 1004 List<String> expected = Arrays.asList("SelectAnnotationATestAP", 1005 "SelectAnnotationBTestAP", 1006 "SelectAnnotationATestAP", 1007 "SelectAnnotationBTestAP"); 1008 1009 if (!expected.equals(log)) { 1010 throw new AssertionError("Output does not match; output: " + log); 1011 } 1012 } 1013 1014 @SupportedAnnotationTypes("m2x/api.A") 1015 public static final class SelectAnnotationATestAP extends AbstractProcessor { 1016 1017 @Override 1018 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 1019 System.err.println("SelectAnnotationATestAP"); 1020 1021 return false; 1022 } 1023 1024 } 1025 1026 @SupportedAnnotationTypes("api.B") 1027 public static final class SelectAnnotationBTestAP extends AbstractProcessor { 1028 1029 @Override 1030 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 1031 System.err.println("SelectAnnotationBTestAP"); 1032 1033 return false; 1034 } 1035 1036 } 1037 1038 private static void writeFile(String content, Path base, String... pathElements) throws IOException { 1039 Path file = resolveFile(base, pathElements); 1040 1041 Files.createDirectories(file.getParent()); 1042 1043 try (Writer out = Files.newBufferedWriter(file)) { 1044 out.append(content); 1045 } 1046 } 1047 1048 @Test 1049 public void testUnboundLookup(Path base) throws Exception { 1050 Path src = base.resolve("src"); 1051 1052 tb.writeJavaFiles(src, 1053 "package impl.conflict.src; public class Impl { }"); 1054 1055 Path moduleSrc = base.resolve("module-src"); 1056 Path m1 = moduleSrc.resolve("m1x"); 1057 Path m2 = moduleSrc.resolve("m2x"); 1058 1059 Path classes = base.resolve("classes"); 1060 Path cpClasses = base.resolve("cpClasses"); 1061 1062 Files.createDirectories(classes); 1063 Files.createDirectories(cpClasses); 1064 1065 tb.writeJavaFiles(m1, 1066 "module m1x { }", 1067 "package impl1; public class Impl { }", 1068 "package impl.conflict.module; class Impl { }", 1069 "package impl.conflict.clazz; public class pkg { public static class I { } }", 1070 "package impl.conflict.src; public class Impl { }"); 1071 1072 tb.writeJavaFiles(m2, 1073 "module m2x { }", 1074 "package impl2; public class Impl { }", 1075 "package impl.conflict.module; class Impl { }", 1076 "package impl.conflict; public class clazz { public static class pkg { } }"); 1077 1078 //from source: 1079 new JavacTask(tb) 1080 .options("--module-source-path", moduleSrc.toString(), 1081 "--source-path", src.toString(), 1082 "-processorpath", System.getProperty("test.class.path"), 1083 "-processor", UnboundLookup.class.getName()) 1084 .outdir(classes) 1085 .files(findJavaFiles(moduleSrc)) 1086 .run() 1087 .writeAll(); 1088 1089 new JavacTask(tb) 1090 .options("--source-path", src.toString()) 1091 .outdir(cpClasses) 1092 .files(findJavaFiles(src)) 1093 .run() 1094 .writeAll(); 1095 1096 //from classfiles: 1097 new JavacTask(tb) 1098 .options("--module-path", classes.toString(), 1099 "--class-path", cpClasses.toString(), 1100 "--add-modules", "m1x,m2x", 1101 "-processorpath", System.getProperty("test.class.path"), 1102 "-processor", UnboundLookup.class.getName(), 1103 "-proc:only") 1104 .classes("java.lang.Object") 1105 .run() 1106 .writeAll(); 1107 1108 //source 8: 1109 new JavacTask(tb) 1110 .options("--source-path", src.toString(), 1111 "-source", "8", 1112 "-processorpath", System.getProperty("test.class.path"), 1113 "-processor", UnboundLookup8.class.getName()) 1114 .outdir(cpClasses) 1115 .files(findJavaFiles(src)) 1116 .run() 1117 .writeAll(); 1118 1119 } 1120 1121 @SupportedAnnotationTypes("*") 1122 public static final class UnboundLookup extends AbstractProcessor { 1123 1124 @Override 1125 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 1126 assertTypeElementExists("impl1.Impl", "m1x"); 1127 assertPackageElementExists("impl1", "m1x"); 1128 assertTypeElementExists("impl2.Impl", "m2x"); 1129 assertTypeElementExists("impl.conflict.clazz.pkg.I", "m1x"); 1130 assertTypeElementExists("impl.conflict.clazz", "m2x"); 1131 assertPackageElementExists("impl.conflict.clazz", "m1x"); 1132 assertPackageElementExists("impl2", "m2x"); 1133 assertTypeElementNotFound("impl.conflict.module.Impl"); 1134 assertPackageElementNotFound("impl.conflict.module"); 1135 assertTypeElementNotFound("impl.conflict.src.Impl"); 1136 assertPackageElementNotFound("impl.conflict.src"); 1137 assertTypeElementNotFound("impl.conflict.clazz.pkg"); 1138 1139 return false; 1140 } 1141 1142 private void assertTypeElementExists(String name, String expectedModule) { 1143 assertElementExists(name, "class", processingEnv.getElementUtils() :: getTypeElement, expectedModule); 1144 } 1145 1146 private void assertPackageElementExists(String name, String expectedModule) { 1147 assertElementExists(name, "package", processingEnv.getElementUtils() :: getPackageElement, expectedModule); 1148 } 1149 1150 private void assertElementExists(String name, String type, Function<String, Element> getter, String expectedModule) { 1151 Element clazz = getter.apply(name); 1152 1153 if (clazz == null) { 1154 throw new AssertionError("No " + name + " " + type + " found."); 1155 } 1156 1157 ModuleElement mod = processingEnv.getElementUtils().getModuleOf(clazz); 1158 1159 if (!mod.getQualifiedName().contentEquals(expectedModule)) { 1160 throw new AssertionError(name + " found in an unexpected module: " + mod.getQualifiedName()); 1161 } 1162 } 1163 1164 private void assertTypeElementNotFound(String name) { 1165 assertElementNotFound(name, processingEnv.getElementUtils() :: getTypeElement); 1166 } 1167 1168 private void assertPackageElementNotFound(String name) { 1169 assertElementNotFound(name, processingEnv.getElementUtils() :: getPackageElement); 1170 } 1171 1172 private void assertElementNotFound(String name, Function<String, Element> getter) { 1173 Element found = getter.apply(name); 1174 1175 if (found != null) { 1176 fail("Element found unexpectedly: " + found); 1177 } 1178 } 1179 1180 @Override 1181 public SourceVersion getSupportedSourceVersion() { 1182 return SourceVersion.latest(); 1183 } 1184 1185 } 1186 1187 @SupportedAnnotationTypes("*") 1188 public static final class UnboundLookup8 extends AbstractProcessor { 1189 1190 @Override 1191 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 1192 if (processingEnv.getElementUtils().getTypeElement("impl.conflict.src.Impl") == null) { 1193 throw new AssertionError("impl.conflict.src.Impl."); 1194 } 1195 1196 if (processingEnv.getElementUtils().getModuleElement("java.base") != null) { 1197 throw new AssertionError("getModuleElement != null for -source 8"); 1198 } 1199 1200 return false; 1201 } 1202 1203 @Override 1204 public SourceVersion getSupportedSourceVersion() { 1205 return SourceVersion.latest(); 1206 } 1207 1208 } 1209 1210 private static void assertNonNull(String msg, Object val) { 1211 if (val == null) { 1212 throw new AssertionError(msg); 1213 } 1214 } 1215 1216 private static void assertNull(String msg, Object val) { 1217 if (val != null) { 1218 throw new AssertionError(msg); 1219 } 1220 } 1221 1222 private static void assertEquals(Object expected, Object actual) { 1223 if (!Objects.equals(expected, actual)) { 1224 throw new AssertionError("expected: " + expected + "; actual=" + actual); 1225 } 1226 } 1227 1228 private static void assertFileExists(Path base, String... pathElements) { 1229 Path file = resolveFile(base, pathElements); 1230 1231 if (!Files.exists(file)) { 1232 throw new AssertionError("Expected file: " + file + " exist, but it does not."); 1233 } 1234 } 1235 1236 static Path resolveFile(Path base, String... pathElements) { 1237 Path file = base; 1238 1239 for (String el : pathElements) { 1240 file = file.resolve(el); 1241 } 1242 1243 return file; 1244 } 1245 1246 private static void fail(String msg) { 1247 throw new AssertionError(msg); 1248 } 1249 1250} 1251