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 javax.annotation.processing.Filer;
 58 import javax.annotation.processing.SupportedOptions;
 59 import javax.lang.model.element.Element;
 60 import javax.lang.model.element.ModuleElement;
 61 import javax.lang.model.element.PackageElement;
 62 import javax.lang.model.util.Elements;
 63 import javax.tools.FileObject;
 64 import javax.tools.ForwardingJavaFileManager;
 65 import javax.tools.JavaFileManager;
 66 import javax.tools.SimpleJavaFileObject;
 67 import javax.tools.StandardLocation;
 68 import toolbox.JavacTask;
 69 import toolbox.TestRunner;
 70 import toolbox.TestRunner.Test;
 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         try (StandardJavaFileManager sjfm = compiler.getStandardFileManager(null, null, null)) {
138             List<String> testOutput = new ArrayList<>();
139             JavaFileManager fm = new ForwardingJavaFileManager<JavaFileManager>(sjfm) {
140                 @Override
141                 public JavaFileObject getJavaFileForOutputForOriginatingFiles(Location location,
142                                                                               String className,
143                                                                               JavaFileObject.Kind kind,
144                                                                               FileObject... originatingFiles) throws IOException {
145                     List.of(originatingFiles)
146                         .stream()
147                         .map(fo -> getInfo(fo))
148                         .forEach(testOutput::add);
149                     return super.getJavaFileForOutputForOriginatingFiles(location, className, kind, originatingFiles);
150                 }
151                 @Override
152                 public FileObject getFileForOutputForOriginatingFiles(Location location,
153                                                                       String packageName,
154                                                                       String relativeName,
155                                                                       FileObject... originatingFiles) throws IOException {
156                     List.of(originatingFiles)
157                         .stream()
158                         .map(fo -> getInfo(fo))
159                         .forEach(testOutput::add);
160                     return super.getFileForOutputForOriginatingFiles(location, packageName, relativeName, originatingFiles);
161                 }
162                 private String getInfo(FileObject fo) {
163                     try {
164                         JavaFileObject jfo = (JavaFileObject) fo; //the test only expects JavaFileObjects here:
165                         JavaFileManager.Location location = jfo.getKind() == JavaFileObject.Kind.SOURCE
166                                 ? StandardLocation.SOURCE_PATH
167                                 : sjfm.getLocationForModule(StandardLocation.SYSTEM_MODULES, "java.base");
168                         String binaryName = inferBinaryName(location, jfo);
169                         return binaryName + "(" + jfo.getKind() + ")";
170                     } catch (IOException ex) {
171                         throw new AssertionError(ex);
172                     }
173                 }
174             };
175             try {
176                 String generatedData;
177                 try (MemoryFileManager mfm = new MemoryFileManager(sjfm)) {
178                     compiler.getTask(null, mfm, null, null, null,
179                                      List.of(new ToolBox.JavaSource("package test; public class Generated2 {}")))
180                             .call();
181                     generatedData =
182                             Base64.getEncoder().encodeToString(mfm.getFileBytes(StandardLocation.CLASS_OUTPUT, "test.Generated2"));
183                 }
184                 List<String> options = List.of("-sourcepath", src.toString(),
185                                                "-processor", "TestOriginatingElements$P",
186                                                "-processorpath", System.getProperty("test.class.path"),
187                                                "--module-path", libClasses.toString(),
188                                                "--add-modules", "lib",
189                                                "-d", classes.toString(),
190                                                "-AgeneratedData=" + generatedData);
191                 ToolProvider.getSystemJavaCompiler()
192                             .getTask(null, fm, null, options, null, sjfm.getJavaFileObjects(tb.findJavaFiles(src)))
193                             .call();
194                 List<String> expectedOriginatingFiles = List.of("t.T1(SOURCE)",
195                                                                 "java.lang.String(CLASS)",
196                                                                 "p.package-info(SOURCE)",
197                                                                 "lib1.package-info(CLASS)",
198                                                                 "module-info(SOURCE)",
199                                                                 "module-info(CLASS)",
200                                                                 "t.T2(SOURCE)",
201                                                                 "java.lang.CharSequence(CLASS)",
202                                                                 "p.package-info(SOURCE)",
203                                                                 "lib1.package-info(CLASS)",
204                                                                 "module-info(SOURCE)",
205                                                                 "module-info(CLASS)",
206                                                                 "t.T3(SOURCE)",
207                                                                 "java.lang.Exception(CLASS)",
208                                                                 "p.package-info(SOURCE)",
209                                                                 "lib1.package-info(CLASS)",
210                                                                 "module-info(SOURCE)",
211                                                                 "module-info(CLASS)");
212                 assertEquals(expectedOriginatingFiles, testOutput);
213             } catch (IOException ex) {
214                 throw new IllegalStateException(ex);
215             }
216         }
217     }
218 
219     @SupportedAnnotationTypes("*")
220     @SupportedOptions("generatedData")
221     public static class P extends AbstractProcessor {
222         int round;
223         @Override
224         public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
225             if (round++ == 0) {
226                 Elements elems = processingEnv.getElementUtils();
227                 ModuleElement mdl = elems.getModuleElement("m");
228                 ModuleElement java_base = elems.getModuleElement("java.base");
229                 PackageElement pack = elems.getPackageElement("p");
230                 PackageElement lib1Pack = elems.getPackageElement("lib1");
231                 PackageElement lib2Pack = elems.getPackageElement("lib2");
232                 Filer filer = processingEnv.getFiler();
233                 try {
234                     filer.createSourceFile("test.Generated1",
235                                            element("t.T1"),
236                                            element("java.lang.String"),
237                                            pack,
238                                            lib1Pack,
239                                            lib2Pack,
240                                            mdl,
241                                            java_base).openOutputStream().close();
242                     try (OutputStream out = filer.createClassFile("test.Generated2",
243                                                                   element("t.T2"),
244                                                                   element("java.lang.CharSequence"),
245                                                                   pack,
246                                                                   lib1Pack,
247                                                                   lib2Pack,
248                                                                   mdl,
249                                                                   java_base).openOutputStream()) {
250                         out.write(Base64.getDecoder().decode(processingEnv.getOptions().get("generatedData")));
251                     }
252                     filer.createResource(StandardLocation.CLASS_OUTPUT,
253                                          "test",
254                                          "Generated3.txt",
255                                          element("t.T3"),
256                                          element("java.lang.Exception"),
257                                          pack,
258                                          lib1Pack,
259                                          lib2Pack,
260                                          mdl,
261                                          java_base).openOutputStream().close();
262                 } catch (IOException ex) {
263                     throw new AssertionError(ex);
264                 }
265             }
266             return false;
267         }
268 
269         private Element element(String type) {
270             return processingEnv.getElementUtils().getTypeElement(type);
271         }
272 
273         @Override
274         public SourceVersion getSupportedSourceVersion() {
275             return SourceVersion.latest();
276         }
277     }
278 
279     @Test
280     public void testVacuousJavaFileManager(Path outerBase) throws Exception {
281         List<String> log = new ArrayList<>();
282         JavaFileObject expectedOut = new SimpleJavaFileObject(new URI("Out.java"), JavaFileObject.Kind.SOURCE) {};
283         JavaFileManager fm = new MinimalJavaFileManager() {
284             @Override
285             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
286                 log.add("getJavaFileForOutput(" + location + ", " + className + ", " + kind + ", " + sibling);
287                 return expectedOut;
288             }
289             @Override
290             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
291                 log.add("getFileForOutput(" + location + ", " + packageName + ", " + relativeName  + ", " + sibling);
292                 return expectedOut;
293             }
294         };
295 
296         FileObject fo1 = new SimpleJavaFileObject(new URI("Test1.java"), JavaFileObject.Kind.SOURCE) {
297             @Override
298             public String toString() {
299                 return "Test1 - FO";
300             }
301         };
302         FileObject fo2 = new SimpleJavaFileObject(new URI("Test2.java"), JavaFileObject.Kind.SOURCE) {
303             @Override
304             public String toString() {
305                 return "Test2 - FO";
306             }
307         };
308 
309         assertEquals(expectedOut,
310                      fm.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE, fo1, fo2));
311         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, Test1 - FO"), log); log.clear();
312 
313         assertEquals(expectedOut,
314                      fm.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE));
315         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, null"), log); log.clear();
316 
317         assertEquals(expectedOut,
318                      fm.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java", fo1, fo2));
319         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, Test1 - FO"), log); log.clear();
320         assertEquals(expectedOut,
321                      fm.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java"));
322         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, null"), log); log.clear();
323     }
324 
325     @Test
326     public void testForwardingJavaFileManager(Path outerBase) throws Exception {
327         List<String> log = new ArrayList<>();
328         JavaFileObject expectedOut = new SimpleJavaFileObject(new URI("Out.java"), JavaFileObject.Kind.SOURCE) {};
329 
330         FileObject fo1 = new SimpleJavaFileObject(new URI("Test1.java"), JavaFileObject.Kind.SOURCE) {
331             @Override
332             public String toString() {
333                 return "Test1 - FO";
334             }
335         };
336         FileObject fo2 = new SimpleJavaFileObject(new URI("Test2.java"), JavaFileObject.Kind.SOURCE) {
337             @Override
338             public String toString() {
339                 return "Test2 - FO";
340             }
341         };
342 
343         JavaFileManager forwardingWithOverride = new ForwardingJavaFileManager<>(new MinimalJavaFileManager() {
344             @Override
345             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
346                 log.add("getJavaFileForOutput(" + location + ", " + className + ", " + kind + ", " + sibling);
347                 return expectedOut;
348             }
349             @Override
350             public JavaFileObject getJavaFileForOutputForOriginatingFiles(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject... originatingFiles) throws IOException {
351                 throw new AssertionError("Should not be called.");
352             }
353             @Override
354             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
355                 log.add("getFileForOutput(" + location + ", " + packageName + ", " + relativeName  + ", " + sibling);
356                 return expectedOut;
357             }
358             @Override
359             public FileObject getFileForOutputForOriginatingFiles(JavaFileManager.Location location, String packageName, String relativeName, FileObject... originatingFiles) throws IOException {
360                 throw new AssertionError("Should not be called.");
361             }
362         }) {
363             @Override
364             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
365                 return super.getJavaFileForOutput(location, className, kind, sibling);
366             }
367             @Override
368             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
369                 return super.getFileForOutput(location, packageName, relativeName, sibling);
370             }
371         };
372 
373         assertEquals(expectedOut,
374                      forwardingWithOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE, fo1, fo2));
375         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, Test1 - FO"), log); log.clear();
376 
377         assertEquals(expectedOut,
378                      forwardingWithOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE));
379         assertEquals(List.of("getJavaFileForOutput(CLASS_OUTPUT, test.Test, SOURCE, null"), log); log.clear();
380 
381         assertEquals(expectedOut,
382                      forwardingWithOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java", fo1, fo2));
383         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, Test1 - FO"), log); log.clear();
384         assertEquals(expectedOut,
385                      forwardingWithOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java"));
386         assertEquals(List.of("getFileForOutput(CLASS_OUTPUT, test, Test.java, null"), log); log.clear();
387 
388         JavaFileManager forwardingWithOutOverride = new ForwardingJavaFileManager<>(new MinimalJavaFileManager() {
389             @Override
390             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
391                 throw new AssertionError("Should not be called.");
392             }
393             @Override
394             public JavaFileObject getJavaFileForOutputForOriginatingFiles(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject... originatingFiles) throws IOException {
395                 log.add("getJavaFileForOutputForOriginatingFiles(" + location + ", " + className + ", " + kind + ", " + List.of(originatingFiles));
396                 return expectedOut;
397             }
398             @Override
399             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
400                 throw new AssertionError("Should not be called.");
401             }
402             @Override
403             public FileObject getFileForOutputForOriginatingFiles(JavaFileManager.Location location, String packageName, String relativeName, FileObject... originatingFiles) throws IOException {
404                 log.add("getFileForOutputForOriginatingFiles(" + location + ", " + packageName + ", " + relativeName  + ", " + List.of(originatingFiles));
405                 return expectedOut;
406             }
407         }) {};
408 
409         assertEquals(expectedOut,
410                      forwardingWithOutOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE, fo1, fo2));
411         assertEquals(List.of("getJavaFileForOutputForOriginatingFiles(CLASS_OUTPUT, test.Test, SOURCE, [Test1 - FO, Test2 - FO]"), log); log.clear();
412 
413         assertEquals(expectedOut,
414                      forwardingWithOutOverride.getJavaFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test.Test", JavaFileObject.Kind.SOURCE));
415         assertEquals(List.of("getJavaFileForOutputForOriginatingFiles(CLASS_OUTPUT, test.Test, SOURCE, []"), log); log.clear();
416 
417         assertEquals(expectedOut,
418                      forwardingWithOutOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java", fo1, fo2));
419         assertEquals(List.of("getFileForOutputForOriginatingFiles(CLASS_OUTPUT, test, Test.java, [Test1 - FO, Test2 - FO]"), log); log.clear();
420         assertEquals(expectedOut,
421                      forwardingWithOutOverride.getFileForOutputForOriginatingFiles(StandardLocation.CLASS_OUTPUT, "test", "Test.java"));
422         assertEquals(List.of("getFileForOutputForOriginatingFiles(CLASS_OUTPUT, test, Test.java, []"), log); log.clear();
423     }
424 
425     class MinimalJavaFileManager implements JavaFileManager {
426             @Override
427             public ClassLoader getClassLoader(JavaFileManager.Location location) {
428                 throw new UnsupportedOperationException("Not supported.");
429             }
430             @Override
431             public Iterable<JavaFileObject> list(JavaFileManager.Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
432                 throw new UnsupportedOperationException("Not supported.");
433             }
434             @Override
435             public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) {
436                 throw new UnsupportedOperationException("Not supported.");
437             }
438             @Override
439             public boolean isSameFile(FileObject a, FileObject b) {
440                 throw new UnsupportedOperationException("Not supported.");
441             }
442             @Override
443             public boolean handleOption(String current, Iterator<String> remaining) {
444                 throw new UnsupportedOperationException("Not supported.");
445             }
446             @Override
447             public boolean hasLocation(JavaFileManager.Location location) {
448                 throw new UnsupportedOperationException("Not supported.");
449             }
450             @Override
451             public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException {
452                 throw new UnsupportedOperationException("Not supported.");
453             }
454             @Override
455             public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
456                 throw new UnsupportedOperationException("Not supported.");
457             }
458             @Override
459             public FileObject getFileForInput(JavaFileManager.Location location, String packageName, String relativeName) throws IOException {
460                 throw new UnsupportedOperationException("Not supported.");
461             }
462             @Override
463             public FileObject getFileForOutput(JavaFileManager.Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
464                 throw new UnsupportedOperationException("Not supported.");
465             }
466             @Override
467             public void flush() throws IOException {
468                 throw new UnsupportedOperationException("Not supported.");
469             }
470             @Override
471             public void close() throws IOException {
472                 throw new UnsupportedOperationException("Not supported.");
473             }
474             @Override
475             public int isSupportedOption(String option) {
476                 throw new UnsupportedOperationException("Not supported.");
477             }
478         };
479 
480     private void assertEquals(Object expected, Object actual) throws AssertionError {
481         if (!expected.equals(actual)) {
482             throw new AssertionError("Unexpected  output: " + actual + ", expected: " + expected);
483         }
484     }
485 
486 }