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