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 }