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