AnnotationProcessing.java revision 3824:376ee1fd40c3
150276Speter/* 2262685Sdelphij * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 350276Speter * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 450276Speter * 550276Speter * This code is free software; you can redistribute it and/or modify it 650276Speter * under the terms of the GNU General Public License version 2 only, as 750276Speter * published by the Free Software Foundation. 850276Speter * 950276Speter * This code is distributed in the hope that it will be useful, but WITHOUT 1050276Speter * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1150276Speter * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1250276Speter * version 2 for more details (a copy is included in the LICENSE file that 1350276Speter * accompanied this code). 1450276Speter * 1550276Speter * You should have received a copy of the GNU General Public License version 1650276Speter * 2 along with this work; if not, write to the Free Software Foundation, 1750276Speter * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 1850276Speter * 1950276Speter * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2050276Speter * or visit www.oracle.com if you need additional information or have any 2150276Speter * questions. 2250276Speter */ 2350276Speter 2450276Speter/** 2550276Speter * @test 2650276Speter * @bug 8133884 8162711 8133896 2750276Speter * @summary Verify that annotation processing works. 2850276Speter * @library /tools/lib 2950276Speter * @modules 30166124Srafan * jdk.compiler/com.sun.tools.javac.api 3150276Speter * jdk.compiler/com.sun.tools.javac.main 3250276Speter * @build toolbox.ToolBox toolbox.JavacTask ModuleTestBase 3350276Speter * @run main AnnotationProcessing 3450276Speter */ 3550276Speter 3650276Speterimport java.io.File; 3750276Speterimport java.io.IOException; 3850276Speterimport java.io.OutputStream; 3950276Speterimport java.io.Reader; 40262685Sdelphijimport java.io.Writer; 4150276Speterimport java.nio.file.Files; 4250276Speterimport java.nio.file.Path; 4350276Speterimport java.nio.file.Paths; 4450276Speterimport java.util.Arrays; 4550276Speterimport java.util.HashMap; 4650276Speterimport java.util.HashSet; 4750276Speterimport java.util.List; 4850276Speterimport java.util.Map; 4950276Speterimport java.util.Objects; 50262685Sdelphijimport java.util.Set; 5150276Speterimport java.util.concurrent.Callable; 5250276Speterimport java.util.function.Function; 5350276Speterimport java.util.stream.Collectors; 5450276Speter 5550276Speterimport javax.annotation.processing.AbstractProcessor; 5650276Speterimport javax.annotation.processing.Filer; 5750276Speterimport javax.annotation.processing.FilerException; 5850276Speterimport javax.annotation.processing.Messager; 5950276Speterimport javax.annotation.processing.ProcessingEnvironment; 6050276Speterimport javax.annotation.processing.RoundEnvironment; 6150276Speterimport javax.annotation.processing.SupportedAnnotationTypes; 6250276Speterimport javax.annotation.processing.SupportedOptions; 6350276Speterimport javax.lang.model.SourceVersion; 6450276Speterimport javax.lang.model.element.Element; 65166124Srafanimport javax.lang.model.element.ElementKind; 66166124Srafanimport javax.lang.model.element.ModuleElement; 67166124Srafanimport javax.lang.model.element.ModuleElement.ProvidesDirective; 68166124Srafanimport javax.lang.model.element.ModuleElement.UsesDirective; 69166124Srafanimport javax.lang.model.element.PackageElement; 70166124Srafanimport javax.lang.model.element.TypeElement; 7150276Speterimport javax.lang.model.element.VariableElement; 72166124Srafanimport javax.lang.model.type.TypeKind; 73166124Srafanimport javax.lang.model.util.ElementFilter; 7450276Speterimport javax.lang.model.util.ElementScanner9; 75166124Srafanimport javax.tools.Diagnostic.Kind; 7650276Speterimport javax.tools.FileObject; 77184989Srafanimport javax.tools.JavaCompiler; 78166124Srafanimport javax.tools.JavaCompiler.CompilationTask; 7950276Speterimport javax.tools.JavaFileManager; 8050276Speterimport javax.tools.JavaFileManager.Location; 8150276Speterimport javax.tools.JavaFileObject; 8250276Speterimport javax.tools.StandardJavaFileManager; 8350276Speterimport javax.tools.StandardLocation; 84166124Srafanimport javax.tools.ToolProvider; 85166124Srafan 86166124Srafanimport toolbox.JavacTask; 8750276Speterimport toolbox.Task; 8850276Speterimport toolbox.Task.Mode; 89166124Srafanimport toolbox.Task.OutputKind; 9050276Speter 9150276Speterpublic class AnnotationProcessing extends ModuleTestBase { 9250276Speter 9350276Speter public static void main(String... args) throws Exception { 9450276Speter new AnnotationProcessing().runTests(); 9550276Speter } 9650276Speter 9750276Speter @Test 9850276Speter public void testAPSingleModule(Path base) throws Exception { 9950276Speter Path moduleSrc = base.resolve("module-src"); 10050276Speter Path m1 = moduleSrc.resolve("m1x"); 10150276Speter 10250276Speter Path classes = base.resolve("classes"); 10350276Speter 10450276Speter Files.createDirectories(classes); 10550276Speter 10650276Speter tb.writeJavaFiles(m1, 10750276Speter "module m1x { }", 10850276Speter "package impl; public class Impl { }"); 10950276Speter 11050276Speter String log = new JavacTask(tb) 11150276Speter .options("--module-source-path", moduleSrc.toString(), 11250276Speter "-processor", AP.class.getName(), 11350276Speter "-AexpectedEnclosedElements=m1x=>impl") 11450276Speter .outdir(classes) 11550276Speter .files(findJavaFiles(moduleSrc)) 11650276Speter .run() 11776726Speter .writeAll() 11876726Speter .getOutput(Task.OutputKind.DIRECT); 119166124Srafan 12050276Speter if (!log.isEmpty()) 12150276Speter throw new AssertionError("Unexpected output: " + log); 122166124Srafan } 12350276Speter 124262629Sdelphij @Test 125262629Sdelphij public void testAPMultiModule(Path base) throws Exception { 126166124Srafan Path moduleSrc = base.resolve("module-src"); 127166124Srafan Path m1 = moduleSrc.resolve("m1x"); 12850276Speter Path m2 = moduleSrc.resolve("m2x"); 12950276Speter 130166124Srafan Path classes = base.resolve("classes"); 13150276Speter 13250276Speter Files.createDirectories(classes); 133166124Srafan 134166124Srafan tb.writeJavaFiles(m1, 13550276Speter "module m1x { }", 13650276Speter "package impl1; public class Impl1 { }"); 137166124Srafan 13850276Speter tb.writeJavaFiles(m2, 139166124Srafan "module m2x { }", 140166124Srafan "package impl2; public class Impl2 { }"); 141166124Srafan 142166124Srafan String log = new JavacTask(tb) 14350276Speter .options("--module-source-path", moduleSrc.toString(), 144166124Srafan "-processor", AP.class.getName(), 14550276Speter "-AexpectedEnclosedElements=m1x=>impl1,m2x=>impl2") 14650276Speter .outdir(classes) 14750276Speter .files(findJavaFiles(moduleSrc)) 14850276Speter .run() 149166124Srafan .writeAll() 150166124Srafan .getOutput(Task.OutputKind.DIRECT); 151166124Srafan 152166124Srafan if (!log.isEmpty()) 153166124Srafan throw new AssertionError("Unexpected output: " + log); 154166124Srafan } 155166124Srafan 156166124Srafan @SupportedAnnotationTypes("*") 157166124Srafan @SupportedOptions("expectedEnclosedElements") 158166124Srafan public static final class AP extends AbstractProcessor { 159166124Srafan 160166124Srafan private Map<String, List<String>> module2ExpectedEnclosedElements; 161166124Srafan private Set<String> seenModules = new HashSet<>(); 162166124Srafan 163166124Srafan @Override 16450276Speter public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 165166124Srafan if (module2ExpectedEnclosedElements == null) { 166166124Srafan module2ExpectedEnclosedElements = new HashMap<>(); 167166124Srafan 168166124Srafan String expectedEnclosedElements = 169166124Srafan processingEnv.getOptions().get("expectedEnclosedElements"); 17050276Speter 17150276Speter for (String moduleDef : expectedEnclosedElements.split(",")) { 17250276Speter String[] module2Packages = moduleDef.split("=>"); 173166124Srafan 17450276Speter module2ExpectedEnclosedElements.put(module2Packages[0], 17550276Speter Arrays.asList(module2Packages[1].split(":"))); 17650276Speter } 17750276Speter } 17850276Speter 179166124Srafan //verify ModuleType and ModuleSymbol behavior: 180166124Srafan for (Element root : roundEnv.getRootElements()) { 181166124Srafan ModuleElement module = processingEnv.getElementUtils().getModuleOf(root); 182166124Srafan 183166124Srafan assertEquals(TypeKind.MODULE, module.asType().getKind()); 184166124Srafan 185166124Srafan boolean[] seenModule = new boolean[1]; 186166124Srafan 18750276Speter module.accept(new ElementScanner9<Void, Void>() { 18850276Speter @Override 18950276Speter public Void visitModule(ModuleElement e, Void p) { 190166124Srafan seenModule[0] = true; 19150276Speter return null; 19250276Speter } 19350276Speter @Override 19450276Speter public Void scan(Element e, Void p) { 19550276Speter throw new AssertionError("Shouldn't get here."); 19650276Speter } 19750276Speter }, null); 19850276Speter 19950276Speter assertEquals(true, seenModule[0]); 20050276Speter 201262629Sdelphij List<String> actualElements = 20250276Speter module.getEnclosedElements() 20350276Speter .stream() 20450276Speter .map(s -> (PackageElement) s) 20550276Speter .map(p -> p.getQualifiedName().toString()) 20650276Speter .collect(Collectors.toList()); 20750276Speter 20850276Speter String moduleName = module.getQualifiedName().toString(); 20950276Speter 21050276Speter assertEquals(module2ExpectedEnclosedElements.get(moduleName), 21176726Speter actualElements); 212166124Srafan 21350276Speter seenModules.add(moduleName); 21450276Speter } 21550276Speter 21650276Speter if (roundEnv.processingOver()) { 21750276Speter assertEquals(module2ExpectedEnclosedElements.keySet(), seenModules); 21850276Speter } 21950276Speter 22050276Speter return false; 22150276Speter } 22250276Speter 22350276Speter @Override 224262629Sdelphij public SourceVersion getSupportedSourceVersion() { 225166124Srafan return SourceVersion.latest(); 22650276Speter } 22750276Speter 22850276Speter } 229166124Srafan 23050276Speter @Test 231166124Srafan public void testVerifyUsesProvides(Path base) throws Exception { 23250276Speter Path moduleSrc = base.resolve("module-src"); 23350276Speter Path m1 = moduleSrc.resolve("m1x"); 23450276Speter 23550276Speter Path classes = base.resolve("classes"); 236166124Srafan 237166124Srafan Files.createDirectories(classes); 23850276Speter 239166124Srafan tb.writeJavaFiles(m1, 240166124Srafan "module m1x { exports api; uses api.Api; provides api.Api with impl.Impl; }", 241166124Srafan "package api; public class Api { }", 242166124Srafan "package impl; public class Impl extends api.Api { }"); 243166124Srafan 244166124Srafan String log = new JavacTask(tb) 245166124Srafan .options("-doe", "-processor", VerifyUsesProvidesAP.class.getName()) 246166124Srafan .outdir(classes) 24750276Speter .files(findJavaFiles(moduleSrc)) 248166124Srafan .run() 249166124Srafan .writeAll() 250166124Srafan .getOutput(Task.OutputKind.DIRECT); 25150276Speter 252166124Srafan if (!log.isEmpty()) 253166124Srafan throw new AssertionError("Unexpected output: " + log); 25450276Speter } 255166124Srafan 25650276Speter @SupportedAnnotationTypes("*") 257166124Srafan public static final class VerifyUsesProvidesAP extends AbstractProcessor { 258166124Srafan 25950276Speter @Override 260166124Srafan public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 26150276Speter TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api"); 262166124Srafan 263166124Srafan assertNonNull("Cannot find api.Api", api); 26450276Speter 265166124Srafan ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement(); 26650276Speter 267166124Srafan assertNonNull("modle is null", modle); 268166124Srafan 26950276Speter List<? extends UsesDirective> uses = ElementFilter.usesIn(modle.getDirectives()); 270166124Srafan assertEquals(1, uses.size()); 27150276Speter assertEquals("api.Api", uses.iterator().next().getService().getQualifiedName().toString()); 27250276Speter 273166124Srafan List<? extends ProvidesDirective> provides = ElementFilter.providesIn(modle.getDirectives()); 274166124Srafan assertEquals(1, provides.size()); 275166124Srafan assertEquals("api.Api", provides.iterator().next().getService().getQualifiedName().toString()); 276166124Srafan assertEquals("impl.Impl", provides.iterator().next().getImplementations().get(0).getQualifiedName().toString()); 277166124Srafan 278166124Srafan return false; 279166124Srafan } 28050276Speter 281166124Srafan @Override 28250276Speter public SourceVersion getSupportedSourceVersion() { 28350276Speter return SourceVersion.latest(); 284166124Srafan } 285166124Srafan 286166124Srafan } 287166124Srafan 288166124Srafan @Test 289166124Srafan public void testPackageNoModule(Path base) throws Exception { 290166124Srafan Path src = base.resolve("src"); 291166124Srafan Path classes = base.resolve("classes"); 292166124Srafan 29350276Speter Files.createDirectories(classes); 294166124Srafan 295166124Srafan tb.writeJavaFiles(src, 29650276Speter "package api; public class Api { }"); 297166124Srafan 29850276Speter String log = new JavacTask(tb) 29950276Speter .options("-processor", VerifyPackageNoModule.class.getName(), 300166124Srafan "-source", "8", 301166124Srafan "-Xlint:-options") 302166124Srafan .outdir(classes) 303166124Srafan .files(findJavaFiles(src)) 304166124Srafan .run() 305166124Srafan .writeAll() 306166124Srafan .getOutput(Task.OutputKind.DIRECT); 307184989Srafan 308166124Srafan if (!log.isEmpty()) 309166124Srafan throw new AssertionError("Unexpected output: " + log); 310166124Srafan } 31150276Speter 312166124Srafan @SupportedAnnotationTypes("*") 31350276Speter public static final class VerifyPackageNoModule extends AbstractProcessor { 31450276Speter 315166124Srafan @Override 316166124Srafan public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 317166124Srafan TypeElement api = processingEnv.getElementUtils().getTypeElement("api.Api"); 318166124Srafan 319166124Srafan assertNonNull("Cannot find api.Api", api); 320184989Srafan 321166124Srafan ModuleElement modle = (ModuleElement) processingEnv.getElementUtils().getPackageOf(api).getEnclosingElement(); 322166124Srafan 323166124Srafan assertNull("modle is not null", modle); 32450276Speter 325166124Srafan return false; 32650276Speter } 327166124Srafan 328166124Srafan @Override 32950276Speter public SourceVersion getSupportedSourceVersion() { 330166124Srafan return SourceVersion.latest(); 33150276Speter } 332166124Srafan 333166124Srafan } 33450276Speter 335166124Srafan @Test 33650276Speter public void testQualifiedClassForProcessing(Path base) throws Exception { 337166124Srafan Path moduleSrc = base.resolve("module-src"); 338166124Srafan Path m1 = moduleSrc.resolve("m1x"); 339166124Srafan Path m2 = moduleSrc.resolve("m2x"); 340166124Srafan 341166124Srafan Path classes = base.resolve("classes"); 342166124Srafan 343166124Srafan Files.createDirectories(classes); 344166124Srafan 345166124Srafan tb.writeJavaFiles(m1, 346166124Srafan "module m1x { }", 34750276Speter "package impl; public class Impl { int m1x; }"); 348166124Srafan 34950276Speter tb.writeJavaFiles(m2, 350166124Srafan "module m2x { }", 351166124Srafan "package impl; public class Impl { int m2x; }"); 352166124Srafan 353166124Srafan new JavacTask(tb) 354166124Srafan .options("--module-source-path", moduleSrc.toString()) 355166124Srafan .outdir(classes) 356166124Srafan .files(findJavaFiles(moduleSrc)) 357166124Srafan .run() 358166124Srafan .writeAll() 359166124Srafan .getOutput(Task.OutputKind.DIRECT); 36050276Speter 361166124Srafan List<String> expected = Arrays.asList("Note: field: m1x"); 36250276Speter 363166124Srafan for (Mode mode : new Mode[] {Mode.API, Mode.CMDLINE}) { 364166124Srafan List<String> log = new JavacTask(tb, mode) 365166124Srafan .options("-processor", QualifiedClassForProcessing.class.getName(), 366166124Srafan "--module-path", classes.toString()) 367166124Srafan .classes("m1x/impl.Impl") 368166124Srafan .outdir(classes) 369166124Srafan .run() 370166124Srafan .writeAll() 371166124Srafan .getOutputLines(Task.OutputKind.DIRECT); 372166124Srafan 373166124Srafan if (!expected.equals(log)) 374166124Srafan throw new AssertionError("Unexpected output: " + log); 375166124Srafan } 376166124Srafan } 377166124Srafan 378166124Srafan @SupportedAnnotationTypes("*") 37950276Speter public static final class QualifiedClassForProcessing extends AbstractProcessor { 380166124Srafan 38150276Speter @Override 382166124Srafan public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 383166124Srafan if (processingEnv.getElementUtils().getModuleElement("m1x") == null) { 38450276Speter throw new AssertionError("No m1x module found."); 385166124Srafan } 38650276Speter 387166124Srafan Messager messager = processingEnv.getMessager(); 388166124Srafan 389166124Srafan for (TypeElement clazz : ElementFilter.typesIn(roundEnv.getRootElements())) { 390166124Srafan for (VariableElement field : ElementFilter.fieldsIn(clazz.getEnclosedElements())) { 391166124Srafan messager.printMessage(Kind.NOTE, "field: " + field.getSimpleName()); 392166124Srafan } 393166124Srafan } 394166124Srafan 395166124Srafan return false; 39650276Speter } 397166124Srafan 39850276Speter @Override 399166124Srafan public SourceVersion getSupportedSourceVersion() { 400166124Srafan return SourceVersion.latest(); 401166124Srafan } 402166124Srafan 403166124Srafan } 404166124Srafan 405166124Srafan @Test 406166124Srafan public void testModuleInRootElements(Path base) throws Exception { 407166124Srafan Path moduleSrc = base.resolve("module-src"); 408166124Srafan Path m1 = moduleSrc.resolve("m1"); 409166124Srafan 410166124Srafan Path classes = base.resolve("classes"); 411166124Srafan 412166124Srafan Files.createDirectories(classes); 413166124Srafan 414166124Srafan tb.writeJavaFiles(m1, 41550276Speter "module m1x { exports api; }", 416166124Srafan "package api; public class Api { }"); 41750276Speter 418166124Srafan List<String> log = new JavacTask(tb) 419166124Srafan .options("-processor", ModuleInRootElementsAP.class.getName()) 420166124Srafan .outdir(classes) 421166124Srafan .files(findJavaFiles(moduleSrc)) 422166124Srafan .run() 423166124Srafan .writeAll() 424166124Srafan .getOutputLines(Task.OutputKind.STDERR); 425166124Srafan 426166124Srafan assertEquals(Arrays.asList("module: m1x"), log); 427166124Srafan } 428166124Srafan 429166124Srafan @SupportedAnnotationTypes("*") 430166124Srafan public static final class ModuleInRootElementsAP extends AbstractProcessor { 431166124Srafan 432166124Srafan @Override 433166124Srafan public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 43450276Speter roundEnv.getRootElements() 435166124Srafan .stream() 43650276Speter .filter(el -> el.getKind() == ElementKind.MODULE) 437166124Srafan .forEach(mod -> System.err.println("module: " + mod.getSimpleName())); 438166124Srafan 439166124Srafan return false; 440166124Srafan } 441166124Srafan 442166124Srafan @Override 443166124Srafan public SourceVersion getSupportedSourceVersion() { 444166124Srafan return SourceVersion.latest(); 44550276Speter } 446166124Srafan 447166124Srafan } 448166124Srafan 449166124Srafan @Test 45050276Speter public void testAnnotationsInModuleInfo(Path base) throws Exception { 451166124Srafan Path moduleSrc = base.resolve("module-src"); 452166124Srafan Path m1 = moduleSrc.resolve("m1"); 453166124Srafan 454166124Srafan tb.writeJavaFiles(m1, 455166124Srafan "@Deprecated module m1x { }"); 456166124Srafan 457166124Srafan Path m2 = moduleSrc.resolve("m2x"); 458166124Srafan 459166124Srafan tb.writeJavaFiles(m2, 460166124Srafan "@SuppressWarnings(\"\") module m2x { }"); 46150276Speter 462166124Srafan Path classes = base.resolve("classes"); 463166124Srafan 464166124Srafan Files.createDirectories(classes); 465166124Srafan 466166124Srafan List<String> log = new JavacTask(tb) 467166124Srafan .options("-processor", AnnotationsInModuleInfoPrint.class.getName()) 468166124Srafan .outdir(classes) 469166124Srafan .files(findJavaFiles(m1)) 470166124Srafan .run() 471166124Srafan .writeAll() 472166124Srafan .getOutputLines(Task.OutputKind.DIRECT); 473166124Srafan 474166124Srafan List<String> expectedLog = Arrays.asList("Note: AP Invoked", 475166124Srafan "Note: AP Invoked"); 476166124Srafan 477166124Srafan assertEquals(expectedLog, log); 478166124Srafan 479166124Srafan new JavacTask(tb) 480166124Srafan .options("-processor", AnnotationsInModuleInfoFail.class.getName()) 481166124Srafan .outdir(classes) 482166124Srafan .files(findJavaFiles(m2)) 483166124Srafan .run() 484166124Srafan .writeAll(); 485166124Srafan } 486166124Srafan 487166124Srafan @SupportedAnnotationTypes("java.lang.Deprecated") 488166124Srafan public static final class AnnotationsInModuleInfoPrint extends AbstractProcessor { 489166124Srafan 490166124Srafan @Override 491166124Srafan public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 492166124Srafan processingEnv.getMessager().printMessage(Kind.NOTE, "AP Invoked"); 493166124Srafan return false; 494166124Srafan } 495166124Srafan 496166124Srafan @Override 497166124Srafan public SourceVersion getSupportedSourceVersion() { 498166124Srafan return SourceVersion.latest(); 499166124Srafan } 500166124Srafan 501166124Srafan } 502166124Srafan 503166124Srafan @SupportedAnnotationTypes("java.lang.Deprecated") 504166124Srafan public static final class AnnotationsInModuleInfoFail extends AbstractProcessor { 505166124Srafan 506166124Srafan @Override 507166124Srafan public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 508166124Srafan throw new AssertionError(); 509166124Srafan } 510166124Srafan 511166124Srafan @Override 512166124Srafan public SourceVersion getSupportedSourceVersion() { 513166124Srafan return SourceVersion.latest(); 514166124Srafan } 51550276Speter 51650276Speter } 51750276Speter 51850276Speter @Test 519166124Srafan public void testGenerateInMultiModeAPI(Path base) throws Exception { 520166124Srafan Path moduleSrc = base.resolve("module-src"); 521166124Srafan Path classes = base.resolve("classes"); 522166124Srafan 523166124Srafan Files.createDirectories(classes); 524166124Srafan 525166124Srafan Path m1 = moduleSrc.resolve("m1x"); 526166124Srafan 527166124Srafan tb.writeJavaFiles(m1, 528166124Srafan "module m1x { exports api1; }", 529166124Srafan "package api1; public class Api { GenApi ga; impl.Impl i; }"); 530166124Srafan 531166124Srafan writeFile("1", m1, "api1", "api"); 532166124Srafan writeFile("1", m1, "impl", "impl"); 533166124Srafan 534166124Srafan Path m2 = moduleSrc.resolve("m2x"); 53550276Speter 536166124Srafan tb.writeJavaFiles(m2, 537166124Srafan "module m2x { requires m1x; exports api2; }", 538166124Srafan "package api2; public class Api { api1.GenApi ga1; GenApi qa2; impl.Impl i;}"); 53950276Speter 540262685Sdelphij writeFile("2", m2, "api2", "api"); 54150276Speter writeFile("2", m2, "impl", "impl"); 542262685Sdelphij 543262685Sdelphij for (FileType fileType : FileType.values()) { 544262685Sdelphij if (Files.isDirectory(classes)) { 545262685Sdelphij tb.cleanDirectory(classes); 546166124Srafan } else { 547166124Srafan Files.createDirectories(classes); 548166124Srafan } 549166124Srafan 550166124Srafan new JavacTask(tb) 551166124Srafan .options("-processor", MultiModeAPITestAP.class.getName(), 55250276Speter "--module-source-path", moduleSrc.toString(), 553166124Srafan "-Afiletype=" + fileType.name()) 55450276Speter .outdir(classes) 55550276Speter .files(findJavaFiles(moduleSrc)) 55650276Speter .run() 55750276Speter .writeAll(); 55850276Speter 55950276Speter assertFileExists(classes, "m1x", "api1", "GenApi.class"); 56050276Speter 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 @SupportedAnnotationTypes("m2x/api.A") 983 public static final class SelectAnnotationATestAP extends AbstractProcessor { 984 985 @Override 986 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 987 System.err.println("SelectAnnotationATestAP"); 988 989 return false; 990 } 991 992 } 993 994 @SupportedAnnotationTypes("api.B") 995 public static final class SelectAnnotationBTestAP extends AbstractProcessor { 996 997 @Override 998 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 999 System.err.println("SelectAnnotationBTestAP"); 1000 1001 return false; 1002 } 1003 1004 } 1005 1006 private static void writeFile(String content, Path base, String... pathElements) throws IOException { 1007 Path file = resolveFile(base, pathElements); 1008 1009 Files.createDirectories(file.getParent()); 1010 1011 try (Writer out = Files.newBufferedWriter(file)) { 1012 out.append(content); 1013 } 1014 } 1015 1016 @Test 1017 public void testUnboundLookup(Path base) throws Exception { 1018 Path src = base.resolve("src"); 1019 1020 tb.writeJavaFiles(src, 1021 "package impl.conflict.src; public class Impl { }"); 1022 1023 Path moduleSrc = base.resolve("module-src"); 1024 Path m1 = moduleSrc.resolve("m1x"); 1025 Path m2 = moduleSrc.resolve("m2x"); 1026 1027 Path classes = base.resolve("classes"); 1028 Path cpClasses = base.resolve("cpClasses"); 1029 1030 Files.createDirectories(classes); 1031 Files.createDirectories(cpClasses); 1032 1033 tb.writeJavaFiles(m1, 1034 "module m1x { }", 1035 "package impl1; public class Impl { }", 1036 "package impl.conflict.module; class Impl { }", 1037 "package impl.conflict.clazz; public class pkg { public static class I { } }", 1038 "package impl.conflict.src; public class Impl { }"); 1039 1040 tb.writeJavaFiles(m2, 1041 "module m2x { }", 1042 "package impl2; public class Impl { }", 1043 "package impl.conflict.module; class Impl { }", 1044 "package impl.conflict; public class clazz { public static class pkg { } }"); 1045 1046 //from source: 1047 new JavacTask(tb) 1048 .options("--module-source-path", moduleSrc.toString(), 1049 "--source-path", src.toString(), 1050 "-processorpath", System.getProperty("test.class.path"), 1051 "-processor", UnboundLookup.class.getName()) 1052 .outdir(classes) 1053 .files(findJavaFiles(moduleSrc)) 1054 .run() 1055 .writeAll(); 1056 1057 new JavacTask(tb) 1058 .options("--source-path", src.toString()) 1059 .outdir(cpClasses) 1060 .files(findJavaFiles(src)) 1061 .run() 1062 .writeAll(); 1063 1064 //from classfiles: 1065 new JavacTask(tb) 1066 .options("--module-path", classes.toString(), 1067 "--class-path", cpClasses.toString(), 1068 "--add-modules", "m1x,m2x", 1069 "-processorpath", System.getProperty("test.class.path"), 1070 "-processor", UnboundLookup.class.getName(), 1071 "-proc:only") 1072 .classes("java.lang.Object") 1073 .run() 1074 .writeAll(); 1075 1076 //source 8: 1077 new JavacTask(tb) 1078 .options("--source-path", src.toString(), 1079 "-source", "8", 1080 "-processorpath", System.getProperty("test.class.path"), 1081 "-processor", UnboundLookup8.class.getName()) 1082 .outdir(cpClasses) 1083 .files(findJavaFiles(src)) 1084 .run() 1085 .writeAll(); 1086 1087 } 1088 1089 @SupportedAnnotationTypes("*") 1090 public static final class UnboundLookup extends AbstractProcessor { 1091 1092 @Override 1093 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 1094 assertTypeElementExists("impl1.Impl", "m1x"); 1095 assertPackageElementExists("impl1", "m1x"); 1096 assertTypeElementExists("impl2.Impl", "m2x"); 1097 assertTypeElementExists("impl.conflict.clazz.pkg.I", "m1x"); 1098 assertTypeElementExists("impl.conflict.clazz", "m2x"); 1099 assertPackageElementExists("impl.conflict.clazz", "m1x"); 1100 assertPackageElementExists("impl2", "m2x"); 1101 assertTypeElementNotFound("impl.conflict.module.Impl"); 1102 assertPackageElementNotFound("impl.conflict.module"); 1103 assertTypeElementNotFound("impl.conflict.src.Impl"); 1104 assertPackageElementNotFound("impl.conflict.src"); 1105 assertTypeElementNotFound("impl.conflict.clazz.pkg"); 1106 1107 return false; 1108 } 1109 1110 private void assertTypeElementExists(String name, String expectedModule) { 1111 assertElementExists(name, "class", processingEnv.getElementUtils() :: getTypeElement, expectedModule); 1112 } 1113 1114 private void assertPackageElementExists(String name, String expectedModule) { 1115 assertElementExists(name, "package", processingEnv.getElementUtils() :: getPackageElement, expectedModule); 1116 } 1117 1118 private void assertElementExists(String name, String type, Function<String, Element> getter, String expectedModule) { 1119 Element clazz = getter.apply(name); 1120 1121 if (clazz == null) { 1122 throw new AssertionError("No " + name + " " + type + " found."); 1123 } 1124 1125 ModuleElement mod = processingEnv.getElementUtils().getModuleOf(clazz); 1126 1127 if (!mod.getQualifiedName().contentEquals(expectedModule)) { 1128 throw new AssertionError(name + " found in an unexpected module: " + mod.getQualifiedName()); 1129 } 1130 } 1131 1132 private void assertTypeElementNotFound(String name) { 1133 assertElementNotFound(name, processingEnv.getElementUtils() :: getTypeElement); 1134 } 1135 1136 private void assertPackageElementNotFound(String name) { 1137 assertElementNotFound(name, processingEnv.getElementUtils() :: getPackageElement); 1138 } 1139 1140 private void assertElementNotFound(String name, Function<String, Element> getter) { 1141 Element found = getter.apply(name); 1142 1143 if (found != null) { 1144 fail("Element found unexpectedly: " + found); 1145 } 1146 } 1147 1148 @Override 1149 public SourceVersion getSupportedSourceVersion() { 1150 return SourceVersion.latest(); 1151 } 1152 1153 } 1154 1155 @SupportedAnnotationTypes("*") 1156 public static final class UnboundLookup8 extends AbstractProcessor { 1157 1158 @Override 1159 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 1160 if (processingEnv.getElementUtils().getTypeElement("impl.conflict.src.Impl") == null) { 1161 throw new AssertionError("impl.conflict.src.Impl."); 1162 } 1163 1164 if (processingEnv.getElementUtils().getModuleElement("java.base") != null) { 1165 throw new AssertionError("getModuleElement != null for -source 8"); 1166 } 1167 1168 return false; 1169 } 1170 1171 @Override 1172 public SourceVersion getSupportedSourceVersion() { 1173 return SourceVersion.latest(); 1174 } 1175 1176 } 1177 1178 private static void assertNonNull(String msg, Object val) { 1179 if (val == null) { 1180 throw new AssertionError(msg); 1181 } 1182 } 1183 1184 private static void assertNull(String msg, Object val) { 1185 if (val != null) { 1186 throw new AssertionError(msg); 1187 } 1188 } 1189 1190 private static void assertEquals(Object expected, Object actual) { 1191 if (!Objects.equals(expected, actual)) { 1192 throw new AssertionError("expected: " + expected + "; actual=" + actual); 1193 } 1194 } 1195 1196 private static void assertFileExists(Path base, String... pathElements) { 1197 Path file = resolveFile(base, pathElements); 1198 1199 if (!Files.exists(file)) { 1200 throw new AssertionError("Expected file: " + file + " exist, but it does not."); 1201 } 1202 } 1203 1204 static Path resolveFile(Path base, String... pathElements) { 1205 Path file = base; 1206 1207 for (String el : pathElements) { 1208 file = file.resolve(el); 1209 } 1210 1211 return file; 1212 } 1213 1214 private static void fail(String msg) { 1215 throw new AssertionError(msg); 1216 } 1217 1218} 1219