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 }