jjg@1096: /* jjg@1096: * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. jjg@1096: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@1096: * jjg@1096: * This code is free software; you can redistribute it and/or modify it jjg@1096: * under the terms of the GNU General Public License version 2 only, as jjg@1096: * published by the Free Software Foundation. jjg@1096: * jjg@1096: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@1096: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@1096: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@1096: * version 2 for more details (a copy is included in the LICENSE file that jjg@1096: * accompanied this code). jjg@1096: * jjg@1096: * You should have received a copy of the GNU General Public License version jjg@1096: * 2 along with this work; if not, write to the Free Software Foundation, jjg@1096: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@1096: * jjg@1096: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jjg@1096: * or visit www.oracle.com if you need additional information or have any jjg@1096: * questions. jjg@1096: */ jjg@1096: jjg@1096: /* jjg@1096: * @test jjg@1096: * @bug 7092965 jjg@1096: * @summary javac should not close processorClassLoader before end of compilation jjg@1096: */ jjg@1096: jjg@1096: import com.sun.source.util.JavacTask; jjg@1096: import com.sun.source.util.TaskEvent; jjg@1096: import com.sun.source.util.TaskListener; jjg@1096: import com.sun.tools.javac.api.ClientCodeWrapper.Trusted; jjg@1096: import com.sun.tools.javac.api.JavacTool; jjg@1096: import com.sun.tools.javac.processing.JavacProcessingEnvironment; jjg@1096: import com.sun.tools.javac.util.Context; jjg@1096: import java.io.ByteArrayOutputStream; jjg@1096: import java.io.File; jjg@1096: import java.io.IOException; jjg@1096: import java.io.PrintStream; jjg@1096: import java.lang.reflect.Field; jjg@1096: import java.net.URI; jjg@1096: import java.util.ArrayList; jjg@1096: import java.util.Arrays; jjg@1096: import java.util.Collections; jjg@1096: import java.util.List; jjg@1096: import javax.annotation.processing.ProcessingEnvironment; jjg@1096: import javax.tools.JavaFileObject; jjg@1096: import javax.tools.SimpleJavaFileObject; jjg@1096: import javax.tools.StandardJavaFileManager; jjg@1096: import javax.tools.StandardLocation; jjg@1096: import javax.tools.ToolProvider; jjg@1096: jjg@1096: /* jjg@1096: * The test compiles an annotation processor and a helper class into a jjg@1096: * custom classes directory. jjg@1096: * jjg@1096: * It then uses them while compiling a dummy file, with the custom classes jjg@1096: * directory on the processor path, thus guaranteeing that references to jjg@1096: * these class are satisfied by the processor class loader. jjg@1096: * jjg@1096: * The annotation processor uses the javac TaskListener to run code jjg@1096: * after annotation processing has completed, to verify that the classloader jjg@1096: * is not closed until the end of the compilation. jjg@1096: */ jjg@1096: jjg@1096: @Trusted // avoids use of ClientCodeWrapper jjg@1096: public class TestClose implements TaskListener { jjg@1096: public static final String annoProc = jjg@1096: "import java.util.*;\n" + jjg@1096: "import javax.annotation.processing.*;\n" + jjg@1096: "import javax.lang.model.*;\n" + jjg@1096: "import javax.lang.model.element.*;\n" + jjg@1096: "import com.sun.source.util.*;\n" + jjg@1096: "import com.sun.tools.javac.processing.*;\n" + jjg@1096: "import com.sun.tools.javac.util.*;\n" + jjg@1096: "@SupportedAnnotationTypes(\"*\")\n" + jjg@1096: "public class AnnoProc extends AbstractProcessor {\n" + jjg@1096: " @Override\n" + jjg@1096: " public SourceVersion getSupportedSourceVersion() {\n" + jjg@1096: " return SourceVersion.latest();\n" + jjg@1096: " }\n" + jjg@1096: " @Override\n" + jjg@1096: " public boolean process(Set annotations, RoundEnvironment roundEnv) {\n" + jjg@1096: " System.out.println(\"in AnnoProc.process\");\n" + jjg@1096: " final ClassLoader cl = getClass().getClassLoader();\n" + jjg@1096: " if (roundEnv.processingOver()) {\n" + jjg@1096: " TestClose.add(processingEnv, new Runnable() {\n" + jjg@1096: " public void run() {\n" + jjg@1096: " System.out.println(getClass().getName() + \": run()\");\n" + jjg@1096: " try {\n" + jjg@1096: " cl.loadClass(\"Callback\")\n" + jjg@1096: " .asSubclass(Runnable.class)\n" + jjg@1096: " .newInstance()\n" + jjg@1096: " .run();\n" + jjg@1096: " } catch (ReflectiveOperationException e) {\n" + jjg@1096: " throw new Error(e);\n" + jjg@1096: " }\n" + jjg@1096: " }\n" + jjg@1096: " });\n" + jjg@1096: " }\n" + jjg@1096: " return true;\n" + jjg@1096: " }\n" + jjg@1096: "}\n"; jjg@1096: jjg@1096: public static final String callback = jjg@1096: "public class Callback implements Runnable {\n" + jjg@1096: " public void run() {\n" + jjg@1096: " System.out.println(getClass().getName() + \": run()\");\n" + jjg@1096: " }\n" + jjg@1096: "}"; jjg@1096: jjg@1096: public static void main(String... args) throws Exception { jjg@1096: new TestClose().run(); jjg@1096: } jjg@1096: jjg@1096: void run() throws IOException { jjg@1096: JavacTool tool = (JavacTool) ToolProvider.getSystemJavaCompiler(); jjg@1096: StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null); jjg@1096: jjg@1096: File classes = new File("classes"); jjg@1096: classes.mkdirs(); jjg@1096: File extraClasses = new File("extraClasses"); jjg@1096: extraClasses.mkdirs(); jjg@1096: jjg@1096: System.out.println("compiling classes to extraClasses"); jjg@1096: { // setup class in extraClasses jjg@1096: fm.setLocation(StandardLocation.CLASS_OUTPUT, jjg@1096: Collections.singleton(extraClasses)); jjg@1096: List files = Arrays.asList( jjg@1096: new MemFile("AnnoProc.java", annoProc), jjg@1096: new MemFile("Callback.java", callback)); jjg@1096: JavacTask task = tool.getTask(null, fm, null, null, null, files); jjg@1096: check(task.call()); jjg@1096: } jjg@1096: jjg@1096: System.out.println("compiling dummy to classes with anno processor"); jjg@1096: { // use that class in a TaskListener after processing has completed jjg@1096: PrintStream prev = System.out; jjg@1096: String out; jjg@1096: ByteArrayOutputStream baos = new ByteArrayOutputStream(); jjg@1096: try (PrintStream ps = new PrintStream(baos)) { jjg@1096: System.setOut(ps); jjg@1096: File testClasses = new File(System.getProperty("test.classes")); jjg@1096: fm.setLocation(StandardLocation.CLASS_OUTPUT, jjg@1096: Collections.singleton(classes)); jjg@1096: fm.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, jjg@1096: Arrays.asList(extraClasses, testClasses)); jjg@1096: List files = Arrays.asList( jjg@1096: new MemFile("my://dummy", "class Dummy { }")); jjg@1096: List options = Arrays.asList("-processor", "AnnoProc"); jjg@1096: JavacTask task = tool.getTask(null, fm, null, options, null, files); jjg@1096: task.setTaskListener(this); jjg@1096: check(task.call()); jjg@1096: } finally { jjg@1096: System.setOut(prev); jjg@1096: out = baos.toString(); jjg@1096: if (!out.isEmpty()) jjg@1096: System.out.println(out); jjg@1096: } jjg@1096: check(out.contains("AnnoProc$1: run()")); jjg@1096: check(out.contains("Callback: run()")); jjg@1096: } jjg@1096: } jjg@1096: jjg@1096: @Override jjg@1096: public void started(TaskEvent e) { jjg@1096: System.out.println("Started: " + e); jjg@1096: } jjg@1096: jjg@1096: @Override jjg@1096: public void finished(TaskEvent e) { jjg@1096: System.out.println("Finished: " + e); jjg@1096: if (e.getKind() == TaskEvent.Kind.ANALYZE) { jjg@1096: for (Runnable r: runnables) { jjg@1096: System.out.println("running " + r); jjg@1096: r.run(); jjg@1096: } jjg@1096: } jjg@1096: } jjg@1096: jjg@1096: void check(boolean b) { jjg@1096: if (!b) jjg@1096: throw new AssertionError(); jjg@1096: } jjg@1096: jjg@1096: jjg@1096: public static void add(ProcessingEnvironment env, Runnable r) { jjg@1096: try { jjg@1096: Context c = ((JavacProcessingEnvironment) env).getContext(); jjg@1096: Object o = c.get(TaskListener.class); jjg@1096: // The TaskListener is an instanceof TestClose, but when using the jjg@1096: // default class loaders. the taskListener uses a different jjg@1096: // instance of Class than the anno processor. jjg@1096: // If you try to evaluate jjg@1096: // TestClose tc = (TestClose) (o). jjg@1096: // you get the following somewhat confusing error: jjg@1096: // java.lang.ClassCastException: TestClose cannot be cast to TestClose jjg@1096: // The workaround is to access the fields of TestClose with reflection. jjg@1096: Field f = o.getClass().getField("runnables"); jjg@1096: @SuppressWarnings("unchecked") jjg@1096: List runnables = (List) f.get(o); jjg@1096: runnables.add(r); jjg@1096: } catch (Throwable t) { jjg@1096: System.err.println(t); jjg@1096: } jjg@1096: } jjg@1096: jjg@1096: public List runnables = new ArrayList<>(); jjg@1096: jjg@1096: class MemFile extends SimpleJavaFileObject { jjg@1096: public final String text; jjg@1096: jjg@1096: MemFile(String name, String text) { jjg@1096: super(URI.create(name), JavaFileObject.Kind.SOURCE); jjg@1096: this.text = text; jjg@1096: } jjg@1096: jjg@1096: @Override jjg@1096: public String getName() { jjg@1096: return uri.toString(); jjg@1096: } jjg@1096: jjg@1096: @Override jjg@1096: public String getCharContent(boolean ignoreEncodingErrors) { jjg@1096: return text; jjg@1096: } jjg@1096: } jjg@1096: }