1 /*
  2  * Copyright (c) 2021, 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 8272234
 27  * @summary Verify proper handling of originating elements in javac's Filer.
 28  * @library /tools/lib
 29  * @modules jdk.compiler/com.sun.tools.javac.api
 30  *          jdk.compiler/com.sun.tools.javac.main
 31  * @build toolbox.TestRunner toolbox.ToolBox TestOriginatingElements
 32  * @run main TestOriginatingElements
 33  */
 34 
 35 import java.nio.file.Files;
 36 import java.nio.file.Path;
 37 import java.nio.file.Paths;
 38 import java.util.List;
 39 import java.util.Set;
 40 
 41 import javax.annotation.processing.AbstractProcessor;
 42 import javax.annotation.processing.RoundEnvironment;
 43 import javax.annotation.processing.SupportedAnnotationTypes;
 44 import javax.lang.model.SourceVersion;
 45 import javax.lang.model.element.TypeElement;
 46 import javax.tools.JavaCompiler;
 47 import javax.tools.JavaFileObject;
 48 import javax.tools.StandardJavaFileManager;
 49 import javax.tools.ToolProvider;
 50 
 51 import java.io.IOException;
 52 import java.io.OutputStream;
 53 import java.net.URI;
 54 import java.util.ArrayList;
 55 import java.util.Base64;
 56 import java.util.Iterator;
 57 import java.util.stream.Stream;
 58 import javax.annotation.processing.Filer;
 59 import javax.annotation.processing.SupportedOptions;
 60 import javax.lang.model.element.Element;
 61 import javax.lang.model.element.ModuleElement;
 62 import javax.lang.model.element.PackageElement;
 63 import javax.lang.model.util.Elements;
 64 import javax.tools.FileObject;
 65 import javax.tools.ForwardingJavaFileManager;
 66 import javax.tools.JavaFileManager;
 67 import javax.tools.SimpleJavaFileObject;
 68 import javax.tools.StandardLocation;
 69 import toolbox.JavacTask;
 70 import toolbox.TestRunner;
 71 import toolbox.ToolBox;
 72 import toolbox.ToolBox.MemoryFileManager;
 73 
 74 public class TestOriginatingElements extends TestRunner {
 75 
 76     public static void main(String... args) throws Exception {
 77         new TestOriginatingElements().runTests(m -> new Object[] { Paths.get(m.getName()) });
 78     }
 79 
 80     private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 81     private final ToolBox tb = new ToolBox();
 82 
 83     public TestOriginatingElements() {
 84         super(System.err);
 85     }
 86 
 87     @Test
 88     public void testOriginatingElements(Path outerBase) throws Exception {
 89         Path libSrc = outerBase.resolve("lib-src");
 90         tb.writeJavaFiles(libSrc,
 91                           """
 92                           module lib { exports lib1; exports lib2; }
 93                           """,
 94                           """
 95                           package lib1;
 96                           public @interface A {
 97                           }
 98                           """,
 99                           """
100                           package lib2;
101                           public class Lib {
102                           }
103                           """);
104         tb.writeFile(libSrc.resolve("lib1/package-info.java"), "@A package lib1;");
105         Path libClasses = outerBase.resolve("lib-classes");
106         Path libClassesModule = libClasses.resolve("lib");
107         Files.createDirectories(libClassesModule);
108 
109         new JavacTask(tb)
110                 .files(tb.findJavaFiles(libSrc))
111                 .outdir(libClassesModule)
112                 .run();
113 
114         Path src = outerBase.resolve("src");
115         tb.writeJavaFiles(src,
116                           """
117                           module m {}
118                           """,
119                           """
120                           package t;
121                           public class T1 {
122                           }
123                           """,
124                           """
125                           package t;
126                           public class T2 {
127                           }
128                           """,
129                           """
130                           package t;
131                           public class T3 {
132                           }
133                           """);
134         tb.writeFile(src.resolve("p/package-info.java"), "package p;");
135         Path classes = outerBase.resolve("classes");
136         Files.createDirectories(classes);
137         List<String> testOutput = new ArrayList<>();
138         try (StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null);
139              JavaFileManager fm = capturingFileManager(sjfm, testOutput)) {
140             String generatedData;
141             try (MemoryFileManager mfm = new MemoryFileManager(sjfm, /* shouldClose */ false)) {
142                 compiler.getTask(null, mfm, null, null, null,
143                                 List.of(new ToolBox.JavaSource("package test; public class Generated2 {}")))
144                         .call();
145                 generatedData =
146                         Base64.getEncoder().encodeToString(mfm.getFileBytes(StandardLocation.CLASS_OUTPUT, "test.Generated2"));
147             }
148             List<String> options = List.of("-sourcepath", src.toString(),
149                     "-processor", "TestOriginatingElements$P",
150                     "-processorpath", System.getProperty("test.class.path"),
151                     "--module-path", libClasses.toString(),
152                     "--add-modules", "lib",
153                     "-d", classes.toString(),
154                     "-AgeneratedData=" + generatedData);
155             ToolProvider.getSystemJavaCompiler()
156                     .getTask(null, fm, null, options, null, sjfm.getJavaFileObjects(tb.findJavaFiles(src)))
157                     .call();
158         } catch (IOException ex) {
159             throw new IllegalStateException(ex);
160         }
161         List<String> expectedOriginatingFiles = List.of("t.T1(SOURCE)",
162                 "java.lang.String(CLASS)",
163                 "p.package-info(SOURCE)",
164                 "lib1.package-info(CLASS)",
165                 "module-info(SOURCE)",
166                 "module-info(CLASS)",
167                 "t.T2(SOURCE)",
168                 "java.lang.CharSequence(CLASS)",
169                 "p.package-info(SOURCE)",
170                 "lib1.package-info(CLASS)",
171                 "module-info(SOURCE)",
172                 "module-info(CLASS)",
173                 "t.T3(SOURCE)",
174                 "java.lang.Exception(CLASS)",
175                 "p.package-info(SOURCE)",
176                 "lib1.package-info(CLASS)",
177                 "module-info(SOURCE)",
178                 "module-info(CLASS)");
179         assertEquals(expectedOriginatingFiles, testOutput);
180     }
181 
182     static JavaFileManager capturingFileManager(JavaFileManager sjfm, List<String> testOutput) {
183         return new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
184             @Override
185             public JavaFileObject getJavaFileForOutputForOriginatingFiles(Location location,
186                                                                           String className,
187                                                                           JavaFileObject.Kind kind,
188                                                                           FileObject... originatingFiles) throws IOException {
189                 Stream.of(originatingFiles)
190                         .map(this::getInfo)
191                         .forEach(testOutput::add);
192                 return super.getJavaFileForOutputForOriginatingFiles(location, className, kind, originatingFiles);
193             }
194 
195             @Override
196             public FileObject getFileForOutputForOriginatingFiles(Location location,
197                                                                   String packageName,
198                                                                   String relativeName,
199                                                                   FileObject... originatingFiles) throws IOException {
200                 Stream.of(originatingFiles)
201                         .map(this::getInfo)
202                         .forEach(testOutput::add);
203                 return super.getFileForOutputForOriginatingFiles(location, packageName, relativeName, originatingFiles);
204             }
205 
206             private String getInfo(FileObject fo) {
207                 try {
208                     JavaFileObject jfo = (JavaFileObject) fo; //the test only expects JavaFileObjects here:
209                     JavaFileManager.Location location = jfo.getKind() == JavaFileObject.Kind.SOURCE
210                             ? StandardLocation.SOURCE_PATH
211                             : sjfm.getLocationForModule(StandardLocation.SYSTEM_MODULES, "java.base");
212                     String binaryName = inferBinaryName(location, jfo);
213                     return binaryName + "(" + jfo.getKind() + ")";
214                 } catch (IOException ex) {
215                     throw new AssertionError(ex);
216                 }
217             }
218 
219             @Override
220             public void close() {
221                 // Don't close the delegate.
222             }
223         };
224     }
225 
226     @SupportedAnnotationTypes("*")
227     @SupportedOptions("generatedData")
228     public static class P extends AbstractProcessor {
229         int round;
230         @Override
231         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
232             if (round++ == 0) {
233                 Elements elems = processingEnv.getElementUtils();
234                 ModuleElement mdl = elems.getModuleElement("m");
235                 ModuleElement java_base = elems.getModuleElement("java.base");
236                 PackageElement pack = elems.getPackageElement("p");
237                 PackageElement lib1Pack = elems.getPackageElement("lib1");
238                 PackageElement lib2Pack = elems.getPackageElement("lib2");
239                 Filer filer = processingEnv.getFiler();
240                 try {
241                     filer.createSourceFile("test.Generated1",
242                                            element("t.T1"),
243                                            element("java.lang.String"),
244                                            pack,
245                                            lib1Pack,
246                                            lib2Pack,
247                                            mdl,
248                                            java_base).openOutputStream().close();
249                     try (OutputStream out = filer.createClassFile("test.Generated2",
250                                                                   element("t.T2"),
251                                                                   element("java.lang.CharSequence"),
252                                                                   pack,
253                                                                   lib1Pack,
254                                                                   lib2Pack,
255                                                                   mdl,
256                                                                   java_base).openOutputStream()) {
257                         out.write(Base64.getDecoder().decode(processingEnv.getOptions().get("generatedData")));
258                     }
259                     filer.createResource(StandardLocation.CLASS_OUTPUT,
260                                          "test",
261                                          "Generated3.txt",
262                                          element("t.T3"),
263                                          element("java.lang.Exception"),
264                                          pack,
265                                          lib1Pack,
266                                          lib2Pack,
267                                          mdl,
268                                          java_base).openOutputStream().close();
269                 } catch (IOException ex) {
270                     throw new AssertionError(ex);
271                 }
272             }
273             return false;
274         }
275 
276         private Element element(String type) {
277             return processingEnv.getElementUtils().getTypeElement(type);
278         }
279 
280         @Override
281         public SourceVersion getSupportedSourceVersion() {
282             return SourceVersion.latest();
283         }
284     }
285 
286     @Test
287     public void testVacuousJavaFileManager(Path outerBase) throws Exception {
288         List<String> log = new ArrayList<>();
289         JavaFileObject expectedOut = new SimpleJavaFileObject(new URI("Out.java"), JavaFileObject.Kind.SOURCE) {};
290         JavaFileManager fm = new MinimalJavaFileManager() {
291             @Override
292             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
293                 log.add("getJavaFileForOutput(" + location + ", " + className + ", " + kind + ", " + sibling);
294                 return expectedOut;
295             }
296             @Override
297             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
298                 log.add("getFileForOutput(" + location + ", " + packageName + ", " + relativeName  + ", " + sibling);
299                 return expectedOut;
300             }
301         };
302 
303         FileObject fo1 = new SimpleJavaFileObject(new URI("Test1.java"), JavaFileObject.Kind.SOURCE) {
304             @Override
305             public String toString() {
306                 return "Test1 - FO";
307             }
308         };
309         FileObject fo2 = new SimpleJavaFileObject(new URI("Test2.java"), JavaFileObject.Kind.SOURCE) {
310             @Override
311             public String toString() {
312                 return "Test2 - FO";
313             }
314         };
315 
316         assertEquals(expectedOut,
317                      fm.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE, fo1, fo2));
318         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, Test1 - FO"), log); log.clear();
319 
320         assertEquals(expectedOut,
321                      fm.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE));
322         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, null"), log); log.clear();
323 
324         assertEquals(expectedOut,
325                      fm.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java", fo1, fo2));
326         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, Test1 - FO"), log); log.clear();
327         assertEquals(expectedOut,
328                      fm.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java"));
329         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, null"), log); log.clear();
330     }
331 
332     @Test
333     public void testForwardingJavaFileManager(Path outerBase) throws Exception {
334         List<String> log = new ArrayList<>();
335         JavaFileObject expectedOut = new SimpleJavaFileObject(new URI("Out.java"), JavaFileObject.Kind.SOURCE) {};
336 
337         FileObject fo1 = new SimpleJavaFileObject(new URI("Test1.java"), JavaFileObject.Kind.SOURCE) {
338             @Override
339             public String toString() {
340                 return "Test1 - FO";
341             }
342         };
343         FileObject fo2 = new SimpleJavaFileObject(new URI("Test2.java"), JavaFileObject.Kind.SOURCE) {
344             @Override
345             public String toString() {
346                 return "Test2 - FO";
347             }
348         };
349 
350         JavaFileManager forwardingWithOverride = new ForwardingJavaFileManager<>(new MinimalJavaFileManager() {
351             @Override
352             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
353                 log.add("getJavaFileForOutput(" + location + ", " + className + ", " + kind + ", " + sibling);
354                 return expectedOut;
355             }
356             @Override
357             public JavaFileObject getJavaFileForOutputForOriginatingFiles(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject... originatingFiles) throws IOException {
358                 throw new AssertionError("Should not be called.");
359             }
360             @Override
361             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
362                 log.add("getFileForOutput(" + location + ", " + packageName + ", " + relativeName  + ", " + sibling);
363                 return expectedOut;
364             }
365             @Override
366             public FileObject getFileForOutputForOriginatingFiles(JavaFileManager.Location location, String packageName, String relativeName, FileObject... originatingFiles) throws IOException {
367                 throw new AssertionError("Should not be called.");
368             }
369         }) {
370             @Override
371             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
372                 return super.getJavaFileForOutput(location, className, kind, sibling);
373             }
374             @Override
375             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
376                 return super.getFileForOutput(location, packageName, relativeName, sibling);
377             }
378         };
379 
380         assertEquals(expectedOut,
381                      forwardingWithOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE, fo1, fo2));
382         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, Test1 - FO"), log); log.clear();
383 
384         assertEquals(expectedOut,
385                      forwardingWithOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE));
386         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, null"), log); log.clear();
387 
388         assertEquals(expectedOut,
389                      forwardingWithOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java", fo1, fo2));
390         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, Test1 - FO"), log); log.clear();
391         assertEquals(expectedOut,
392                      forwardingWithOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java"));
393         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, null"), log); log.clear();
394 
395         JavaFileManager forwardingWithOutOverride = new ForwardingJavaFileManager<>(new MinimalJavaFileManager() {
396             @Override
397             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
398                 throw new AssertionError("Should not be called.");
399             }
400             @Override
401             public JavaFileObject getJavaFileForOutputForOriginatingFiles(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject... originatingFiles) throws IOException {
402                 log.add("getJavaFileForOutputForOriginatingFiles(" + location + ", " + className + ", " + kind + ", " + List.of(originatingFiles));
403                 return expectedOut;
404             }
405             @Override
406             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
407                 throw new AssertionError("Should not be called.");
408             }
409             @Override
410             public FileObject getFileForOutputForOriginatingFiles(JavaFileManager.Location location, String packageName, String relativeName, FileObject... originatingFiles) throws IOException {
411                 log.add("getFileForOutputForOriginatingFiles(" + location + ", " + packageName + ", " + relativeName  + ", " + List.of(originatingFiles));
412                 return expectedOut;
413             }
414         }) {};
415 
416         assertEquals(expectedOut,
417                      forwardingWithOutOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE, fo1, fo2));
418         assertEquals(List.of("getJavaFileForOutputForOriginatingFiles(CLASS_OUTPUT, test.Test, SOURCE, [Test1 - FO, Test2 - FO]"), log); log.clear();
419 
420         assertEquals(expectedOut,
421                      forwardingWithOutOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE));
422         assertEquals(List.of("getJavaFileForOutputForOriginatingFiles(CLASS_OUTPUT, test.Test, SOURCE, []"), log); log.clear();
423 
424         assertEquals(expectedOut,
425                      forwardingWithOutOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java", fo1, fo2));
426         assertEquals(List.of("getFileForOutputForOriginatingFiles(CLASS_OUTPUT, test, Test.java, [Test1 - FO, Test2 - FO]"), log); log.clear();
427         assertEquals(expectedOut,
428                      forwardingWithOutOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java"));
429         assertEquals(List.of("getFileForOutputForOriginatingFiles(CLASS_OUTPUT, test, Test.java, []"), log); log.clear();
430     }
431 
432     class MinimalJavaFileManager implements JavaFileManager {
433             @Override
434             public ClassLoader getClassLoader(JavaFileManager.Location location) {
435                 throw new UnsupportedOperationException("Not supported.");
436             }
437             @Override
438             public Iterable<JavaFileObject> list(JavaFileManager.Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
439                 throw new UnsupportedOperationException("Not supported.");
440             }
441             @Override
442             public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) {
443                 throw new UnsupportedOperationException("Not supported.");
444             }
445             @Override
446             public boolean isSameFile(FileObject a, FileObject b) {
447                 throw new UnsupportedOperationException("Not supported.");
448             }
449             @Override
450             public boolean handleOption(String current, Iterator<String> remaining) {
451                 throw new UnsupportedOperationException("Not supported.");
452             }
453             @Override
454             public boolean hasLocation(JavaFileManager.Location location) {
455                 throw new UnsupportedOperationException("Not supported.");
456             }
457             @Override
458             public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException {
459                 throw new UnsupportedOperationException("Not supported.");
460             }
461             @Override
462             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
463                 throw new UnsupportedOperationException("Not supported.");
464             }
465             @Override
466             public FileObject getFileForInput(JavaFileManager.Location location, String packageName, String relativeName) throws IOException {
467                 throw new UnsupportedOperationException("Not supported.");
468             }
469             @Override
470             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
471                 throw new UnsupportedOperationException("Not supported.");
472             }
473             @Override
474             public void flush() throws IOException {
475                 throw new UnsupportedOperationException("Not supported.");
476             }
477             @Override
478             public void close() throws IOException {
479                 throw new UnsupportedOperationException("Not supported.");
480             }
481             @Override
482             public int isSupportedOption(String option) {
483                 throw new UnsupportedOperationException("Not supported.");
484             }
485         };
486 
487     private void assertEquals(Object expected, Object actual) throws AssertionError {
488         if (!expected.equals(actual)) {
489             throw new AssertionError("Unexpected  output: " + actual + ", expected: " + expected);
490         }
491     }
492 
493 }