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