1 /* 2 * Copyright (c) 2011, 2012, 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 7092965 27 * @summary javac should not close processorClassLoader before end of compilation 28 */ 29 30 import com.sun.source.util.JavacTask; 31 import com.sun.source.util.TaskEvent; 32 import com.sun.source.util.TaskListener; 33 import com.sun.tools.javac.api.ClientCodeWrapper.Trusted; 34 import com.sun.tools.javac.api.BasicJavacTask; 35 import com.sun.tools.javac.api.JavacTool; 36 import com.sun.tools.javac.processing.JavacProcessingEnvironment; 37 import com.sun.tools.javac.util.Context; 38 import java.io.ByteArrayOutputStream; 39 import java.io.File; 40 import java.io.IOException; 41 import java.io.PrintStream; 42 import java.lang.reflect.Field; 43 import java.net.URI; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collections; 47 import java.util.List; 48 import javax.annotation.processing.ProcessingEnvironment; 49 import javax.tools.JavaFileObject; 50 import javax.tools.SimpleJavaFileObject; 51 import javax.tools.StandardJavaFileManager; 52 import javax.tools.StandardLocation; 53 import javax.tools.ToolProvider; 54 55 /* 56 * The test compiles an annotation processor and a helper class into a 57 * custom classes directory. 58 * 59 * It then uses them while compiling a dummy file, with the custom classes 60 * directory on the processor path, thus guaranteeing that references to 61 * these class are satisfied by the processor class loader. 62 * 63 * The annotation processor uses the javac TaskListener to run code 64 * after annotation processing has completed, to verify that the classloader 65 * is not closed until the end of the compilation. 66 */ 67 68 @Trusted // avoids use of ClientCodeWrapper 69 public class TestClose implements TaskListener { 70 public static final String annoProc = 71 "import java.util.*;\n" + 72 "import javax.annotation.processing.*;\n" + 73 "import javax.lang.model.*;\n" + 74 "import javax.lang.model.element.*;\n" + 75 "import com.sun.source.util.*;\n" + 76 "import com.sun.tools.javac.processing.*;\n" + 77 "import com.sun.tools.javac.util.*;\n" + 78 "@SupportedAnnotationTypes(\"*\")\n" + 79 "public class AnnoProc extends AbstractProcessor {\n" + 80 " @Override\n" + 81 " public SourceVersion getSupportedSourceVersion() {\n" + 82 " return SourceVersion.latest();\n" + 83 " }\n" + 84 " @Override\n" + 85 " public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n" + 86 " System.out.println(\"in AnnoProc.process\");\n" + 87 " final ClassLoader cl = getClass().getClassLoader();\n" + 88 " if (roundEnv.processingOver()) {\n" + 89 " TestClose.add(processingEnv, new Runnable() {\n" + 90 " public void run() {\n" + 91 " System.out.println(getClass().getName() + \": run()\");\n" + 92 " try {\n" + 93 " cl.loadClass(\"Callback\")\n" + 94 " .asSubclass(Runnable.class)\n" + 95 " .newInstance()\n" + 96 " .run();\n" + 97 " } catch (ReflectiveOperationException e) {\n" + 98 " throw new Error(e);\n" + 99 " }\n" + 100 " }\n" + 101 " });\n" + 102 " }\n" + 103 " return true;\n" + 104 " }\n" + 105 "}\n"; 106 107 public static final String callback = 108 "public class Callback implements Runnable {\n" + 109 " public void run() {\n" + 110 " System.out.println(getClass().getName() + \": run()\");\n" + 111 " }\n" + 112 "}"; 113 main(String... args)114 public static void main(String... args) throws Exception { 115 new TestClose().run(); 116 } 117 run()118 void run() throws IOException { 119 JavacTool tool = (JavacTool) ToolProvider.getSystemJavaCompiler(); 120 StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null); 121 122 File classes = new File("classes"); 123 classes.mkdirs(); 124 File extraClasses = new File("extraClasses"); 125 extraClasses.mkdirs(); 126 127 System.out.println("compiling classes to extraClasses"); 128 { // setup class in extraClasses 129 fm.setLocation(StandardLocation.CLASS_OUTPUT, 130 Collections.singleton(extraClasses)); 131 List<? extends JavaFileObject> files = Arrays.asList( 132 new MemFile("AnnoProc.java", annoProc), 133 new MemFile("Callback.java", callback)); 134 JavacTask task = tool.getTask(null, fm, null, null, null, files); 135 check(task.call()); 136 } 137 138 System.out.println("compiling dummy to classes with anno processor"); 139 { // use that class in a TaskListener after processing has completed 140 PrintStream prev = System.out; 141 String out; 142 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 143 try (PrintStream ps = new PrintStream(baos)) { 144 System.setOut(ps); 145 File testClasses = new File(System.getProperty("test.classes")); 146 fm.setLocation(StandardLocation.CLASS_OUTPUT, 147 Collections.singleton(classes)); 148 fm.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, 149 Arrays.asList(extraClasses, testClasses)); 150 List<? extends JavaFileObject> files = Arrays.asList( 151 new MemFile("my://dummy", "class Dummy { }")); 152 List<String> options = Arrays.asList("-processor", "AnnoProc"); 153 JavacTask task = tool.getTask(null, fm, null, options, null, files); 154 task.setTaskListener(this); 155 check(task.call()); 156 } finally { 157 System.setOut(prev); 158 out = baos.toString(); 159 if (!out.isEmpty()) 160 System.out.println(out); 161 } 162 check(out.contains("AnnoProc$1: run()")); 163 check(out.contains("Callback: run()")); 164 } 165 } 166 167 @Override started(TaskEvent e)168 public void started(TaskEvent e) { 169 System.out.println("Started: " + e); 170 } 171 172 @Override finished(TaskEvent e)173 public void finished(TaskEvent e) { 174 System.out.println("Finished: " + e); 175 if (e.getKind() == TaskEvent.Kind.ANALYZE) { 176 for (Runnable r: runnables) { 177 System.out.println("running " + r); 178 r.run(); 179 } 180 } 181 } 182 check(boolean b)183 void check(boolean b) { 184 if (!b) 185 throw new AssertionError(); 186 } 187 add(ProcessingEnvironment env, Runnable r)188 public static void add(ProcessingEnvironment env, Runnable r) { 189 try { 190 JavacTask task = JavacTask.instance(env); 191 TaskListener l = ((BasicJavacTask) task).getTaskListeners().iterator().next(); 192 // The TaskListener is an instanceof TestClose, but when using the 193 // default class loaders. the taskListener uses a different 194 // instance of Class<TestClose> than the anno processor. 195 // If you try to evaluate 196 // TestClose tc = (TestClose) (l). 197 // you get the following somewhat confusing error: 198 // java.lang.ClassCastException: TestClose cannot be cast to TestClose 199 // The workaround is to access the fields of TestClose with reflection. 200 Field f = l.getClass().getField("runnables"); 201 @SuppressWarnings("unchecked") 202 List<Runnable> runnables = (List<Runnable>) f.get(l); 203 runnables.add(r); 204 } catch (Throwable t) { 205 t.printStackTrace(); 206 } 207 } 208 209 public List<Runnable> runnables = new ArrayList<>(); 210 211 class MemFile extends SimpleJavaFileObject { 212 public final String text; 213 MemFile(String name, String text)214 MemFile(String name, String text) { 215 super(URI.create(name), JavaFileObject.Kind.SOURCE); 216 this.text = text; 217 } 218 219 @Override getName()220 public String getName() { 221 return uri.toString(); 222 } 223 224 @Override getCharContent(boolean ignoreEncodingErrors)225 public String getCharContent(boolean ignoreEncodingErrors) { 226 return text; 227 } 228 } 229 } 230