Thu, 02 Sep 2010 18:26:21 -0700
6921495: spurious semicolons in class def cause empty NOPOS blocks
Reviewed-by: mcimadamore
1 /*
2 * Copyright (c) 2009, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 import java.io.*;
27 import java.util.*;
28 import javax.tools.JavaFileObject;
29 import javax.tools.StandardJavaFileManager;
30 import javax.tools.StandardLocation;
32 import org.apache.tools.ant.BuildException;
33 import org.apache.tools.ant.DirectoryScanner;
34 import org.apache.tools.ant.taskdefs.MatchingTask;
35 import org.apache.tools.ant.types.Path;
36 import org.apache.tools.ant.types.Reference;
39 import com.sun.source.tree.CompilationUnitTree;
40 import com.sun.source.util.JavacTask;
41 import com.sun.tools.javac.api.JavacTool;
42 import com.sun.tools.javac.code.Flags;
43 import com.sun.tools.javac.code.TypeTags;
44 import com.sun.tools.javac.tree.JCTree;
45 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
46 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
47 import com.sun.tools.javac.tree.JCTree.JCIdent;
48 import com.sun.tools.javac.tree.JCTree.JCImport;
49 import com.sun.tools.javac.tree.JCTree.JCLiteral;
50 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
51 import com.sun.tools.javac.tree.JCTree.JCModifiers;
52 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
53 import com.sun.tools.javac.tree.Pretty;
54 import com.sun.tools.javac.tree.TreeMaker;
55 import com.sun.tools.javac.tree.TreeScanner;
56 import com.sun.tools.javac.tree.TreeTranslator;
57 import com.sun.tools.javac.util.Context;
58 import com.sun.tools.javac.util.ListBuffer;
59 import com.sun.tools.javac.util.Name;
60 import javax.tools.JavaFileManager;
62 /**
63 * Generate stub source files by removing implementation details from input files.
64 *
65 * This is a special purpose stub generator, specific to the needs of generating
66 * stub files for JDK 7 API that are needed to compile langtools files that depend
67 * on that API. The stub generator works by removing as much of the API source code
68 * as possible without affecting the public signature, in order to reduce the
69 * transitive closure of the API being referenced. The resulting stubs can be
70 * put on the langtools sourcepath with -implicit:none to compile the langtools
71 * files that depend on the JDK 7 API.
72 *
73 * Usage:
74 * genstubs -s <outdir> -sourcepath <path> <classnames>
75 *
76 * The specified class names are looked up on the sourcepath, and corresponding
77 * stubs are written to the source output directory.
78 *
79 * Classes are parsed into javac ASTs, then processed with a javac TreeTranslator
80 * to remove implementation details, and written out in the source output directory.
81 * Documentation comments and annotations are removed. Method bodies are removed
82 * and methods are marked native. Private and package-private field definitions
83 * have their initializers replace with 0, 0.0, false, null as appropriate.
84 *
85 * An Ant task, Main$Ant is also provided. Files are specified with an implicit
86 * fileset, using srcdir as a base directory. The set of files to be included
87 * is specified with an includes attribute or nested <includes> set. However,
88 * unlike a normal fileset, an empty includes attribute means "no files" instead
89 * of "all files". The Ant task also accepts "fork=true" and classpath attribute
90 * or nested <classpath> element to run GenStubs in a separate VM with the specified
91 * path. This is likely necessary if a JDK 7 parser is required to read the
92 * JDK 7 input files.
93 */
95 public class GenStubs {
96 static class Fault extends Exception {
97 private static final long serialVersionUID = 0;
98 Fault(String message) {
99 super(message);
100 }
101 Fault(String message, Throwable cause) {
102 super(message);
103 initCause(cause);
104 }
105 }
107 public static void main(String[] args) {
108 boolean ok = new GenStubs().run(args);
109 if (!ok)
110 System.exit(1);
111 }
113 boolean run(String... args) {
114 File outdir = null;
115 String sourcepath = null;
116 List<String> classes = new ArrayList<String>();
117 for (ListIterator<String> iter = Arrays.asList(args).listIterator(); iter.hasNext(); ) {
118 String arg = iter.next();
119 if (arg.equals("-s") && iter.hasNext())
120 outdir = new File(iter.next());
121 else if (arg.equals("-sourcepath") && iter.hasNext())
122 sourcepath = iter.next();
123 else if (arg.startsWith("-"))
124 throw new IllegalArgumentException(arg);
125 else {
126 classes.add(arg);
127 while (iter.hasNext())
128 classes.add(iter.next());
129 }
130 }
132 return run(sourcepath, outdir, classes);
133 }
135 boolean run(String sourcepath, File outdir, List<String> classes) {
136 //System.err.println("run: sourcepath:" + sourcepath + " outdir:" + outdir + " classes:" + classes);
137 if (sourcepath == null)
138 throw new IllegalArgumentException("sourcepath not set");
139 if (outdir == null)
140 throw new IllegalArgumentException("source output dir not set");
142 JavacTool tool = JavacTool.create();
143 StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null);
145 try {
146 fm.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singleton(outdir));
147 fm.setLocation(StandardLocation.SOURCE_PATH, splitPath(sourcepath));
148 List<JavaFileObject> files = new ArrayList<JavaFileObject>();
149 for (String c: classes) {
150 JavaFileObject fo = fm.getJavaFileForInput(
151 StandardLocation.SOURCE_PATH, c, JavaFileObject.Kind.SOURCE);
152 if (fo == null)
153 error("class not found: " + c);
154 else
155 files.add(fo);
156 }
158 JavacTask t = tool.getTask(null, fm, null, null, null, files);
159 Iterable<? extends CompilationUnitTree> trees = t.parse();
160 for (CompilationUnitTree tree: trees) {
161 makeStub(fm, tree);
162 }
163 } catch (IOException e) {
164 error("IO error " + e, e);
165 }
167 return (errors == 0);
168 }
170 void makeStub(StandardJavaFileManager fm, CompilationUnitTree tree) throws IOException {
171 CompilationUnitTree tree2 = new StubMaker().translate(tree);
172 CompilationUnitTree tree3 = new ImportCleaner(fm).removeRedundantImports(tree2);
174 String className = fm.inferBinaryName(StandardLocation.SOURCE_PATH, tree.getSourceFile());
175 JavaFileObject fo = fm.getJavaFileForOutput(StandardLocation.SOURCE_OUTPUT,
176 className, JavaFileObject.Kind.SOURCE, null);
177 // System.err.println("Writing " + className + " to " + fo.getName());
178 Writer out = fo.openWriter();
179 try {
180 new Pretty(out, true).printExpr((JCTree) tree3);
181 } finally {
182 out.close();
183 }
184 }
186 List<File> splitPath(String path) {
187 List<File> list = new ArrayList<File>();
188 for (String p: path.split(File.pathSeparator)) {
189 if (p.length() > 0)
190 list.add(new File(p));
191 }
192 return list;
193 }
195 void error(String message) {
196 System.err.println(message);
197 errors++;
198 }
200 void error(String message, Throwable cause) {
201 error(message);
202 }
204 int errors;
206 class StubMaker extends TreeTranslator {
207 CompilationUnitTree translate(CompilationUnitTree tree) {
208 return super.translate((JCCompilationUnit) tree);
209 }
211 /**
212 * compilation units: remove javadoc comments
213 * -- required, in order to remove @deprecated tags, since we
214 * (separately) remove all annotations, including @Deprecated
215 */
216 public void visitTopLevel(JCCompilationUnit tree) {
217 super.visitTopLevel(tree);
218 tree.docComments = Collections.emptyMap();
219 }
221 /**
222 * methods: remove method bodies, make methods native
223 */
224 @Override
225 public void visitMethodDef(JCMethodDecl tree) {
226 tree.mods = translate(tree.mods);
227 tree.restype = translate(tree.restype);
228 tree.typarams = translateTypeParams(tree.typarams);
229 tree.params = translateVarDefs(tree.params);
230 tree.thrown = translate(tree.thrown);
231 if (tree.restype != null && tree.body != null) {
232 tree.mods.flags |= Flags.NATIVE;
233 tree.body = null;
234 }
235 result = tree;
236 }
238 /**
239 * modifiers: remove annotations
240 */
241 @Override
242 public void visitModifiers(JCModifiers tree) {
243 tree.annotations = com.sun.tools.javac.util.List.nil();
244 result = tree;
245 }
247 /**
248 * field definitions: replace initializers with 0, 0.0, false etc
249 * when possible -- i.e. leave public, protected initializers alone
250 */
251 @Override
252 public void visitVarDef(JCVariableDecl tree) {
253 tree.mods = translate(tree.mods);
254 tree.vartype = translate(tree.vartype);
255 if (tree.init != null) {
256 if ((tree.mods.flags & (Flags.PUBLIC | Flags.PROTECTED)) != 0)
257 tree.init = translate(tree.init);
258 else {
259 String t = tree.vartype.toString();
260 if (t.equals("boolean"))
261 tree.init = new JCLiteral(TypeTags.BOOLEAN, 0) { };
262 else if (t.equals("byte"))
263 tree.init = new JCLiteral(TypeTags.BYTE, 0) { };
264 else if (t.equals("char"))
265 tree.init = new JCLiteral(TypeTags.CHAR, 0) { };
266 else if (t.equals("double"))
267 tree.init = new JCLiteral(TypeTags.DOUBLE, 0.d) { };
268 else if (t.equals("float"))
269 tree.init = new JCLiteral(TypeTags.FLOAT, 0.f) { };
270 else if (t.equals("int"))
271 tree.init = new JCLiteral(TypeTags.INT, 0) { };
272 else if (t.equals("long"))
273 tree.init = new JCLiteral(TypeTags.LONG, 0) { };
274 else if (t.equals("short"))
275 tree.init = new JCLiteral(TypeTags.SHORT, 0) { };
276 else
277 tree.init = new JCLiteral(TypeTags.BOT, null) { };
278 }
279 }
280 result = tree;
281 }
282 }
284 class ImportCleaner extends TreeScanner {
285 private Set<Name> names = new HashSet<Name>();
286 private TreeMaker m;
288 ImportCleaner(JavaFileManager fm) {
289 // ImportCleaner itself doesn't require a filemanager, but instantiating
290 // a TreeMaker does, indirectly (via ClassReader, sigh)
291 Context c = new Context();
292 c.put(JavaFileManager.class, fm);
293 m = TreeMaker.instance(c);
294 }
296 CompilationUnitTree removeRedundantImports(CompilationUnitTree t) {
297 JCCompilationUnit tree = (JCCompilationUnit) t;
298 tree.accept(this);
299 ListBuffer<JCTree> defs = new ListBuffer<JCTree>();
300 for (JCTree def: tree.defs) {
301 if (def.getTag() == JCTree.IMPORT) {
302 JCImport imp = (JCImport) def;
303 if (imp.qualid.getTag() == JCTree.SELECT) {
304 JCFieldAccess qualid = (JCFieldAccess) imp.qualid;
305 if (!qualid.name.toString().equals("*")
306 && !names.contains(qualid.name)) {
307 continue;
308 }
309 }
310 }
311 defs.add(def);
312 }
313 return m.TopLevel(tree.packageAnnotations, tree.pid, defs.toList());
314 }
316 @Override
317 public void visitImport(JCImport tree) { } // ignore names found in imports
319 @Override
320 public void visitIdent(JCIdent tree) {
321 names.add(tree.name);
322 }
324 @Override
325 public void visitSelect(JCFieldAccess tree) {
326 super.visitSelect(tree);
327 names.add(tree.name);
328 }
329 }
331 //---------- Ant Invocation ------------------------------------------------
333 public static class Ant extends MatchingTask {
334 private File srcDir;
335 private File destDir;
336 private boolean fork;
337 private Path classpath;
338 private String includes;
340 public void setSrcDir(File dir) {
341 this.srcDir = dir;
342 }
344 public void setDestDir(File dir) {
345 this.destDir = dir;
346 }
348 public void setFork(boolean v) {
349 this.fork = v;
350 }
352 public void setClasspath(Path cp) {
353 if (classpath == null)
354 classpath = cp;
355 else
356 classpath.append(cp);
357 }
359 public Path createClasspath() {
360 if (classpath == null) {
361 classpath = new Path(getProject());
362 }
363 return classpath.createPath();
364 }
366 public void setClasspathRef(Reference r) {
367 createClasspath().setRefid(r);
368 }
370 public void setIncludes(String includes) {
371 super.setIncludes(includes);
372 this.includes = includes;
373 }
375 @Override
376 public void execute() {
377 if (includes != null && includes.trim().isEmpty())
378 return;
380 DirectoryScanner s = getDirectoryScanner(srcDir);
381 String[] files = s.getIncludedFiles();
382 // System.err.println("Ant.execute: srcDir " + srcDir);
383 // System.err.println("Ant.execute: destDir " + destDir);
384 // System.err.println("Ant.execute: files " + Arrays.asList(files));
386 files = filter(srcDir, destDir, files);
387 if (files.length == 0)
388 return;
389 System.out.println("Generating " + files.length + " stub files to " + destDir);
391 List<String> classNames = new ArrayList<String>();
392 for (String file: files) {
393 classNames.add(file.replaceAll(".java$", "").replace('/', '.'));
394 }
396 if (!fork) {
397 GenStubs m = new GenStubs();
398 boolean ok = m.run(srcDir.getPath(), destDir, classNames);
399 if (!ok)
400 throw new BuildException("genstubs failed");
401 } else {
402 List<String> cmd = new ArrayList<String>();
403 String java_home = System.getProperty("java.home");
404 cmd.add(new File(new File(java_home, "bin"), "java").getPath());
405 if (classpath != null)
406 cmd.add("-Xbootclasspath/p:" + classpath);
407 cmd.add(GenStubs.class.getName());
408 cmd.add("-sourcepath");
409 cmd.add(srcDir.getPath());
410 cmd.add("-s");
411 cmd.add(destDir.getPath());
412 cmd.addAll(classNames);
413 //System.err.println("GenStubs exec " + cmd);
414 ProcessBuilder pb = new ProcessBuilder(cmd);
415 pb.redirectErrorStream(true);
416 try {
417 Process p = pb.start();
418 BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
419 try {
420 String line;
421 while ((line = in.readLine()) != null)
422 System.out.println(line);
423 } finally {
424 in.close();
425 }
426 int rc = p.waitFor();
427 if (rc != 0)
428 throw new BuildException("genstubs failed");
429 } catch (IOException e) {
430 throw new BuildException("genstubs failed", e);
431 } catch (InterruptedException e) {
432 throw new BuildException("genstubs failed", e);
433 }
434 }
435 }
437 String[] filter(File srcDir, File destDir, String[] files) {
438 List<String> results = new ArrayList<String>();
439 for (String f: files) {
440 long srcTime = new File(srcDir, f).lastModified();
441 long destTime = new File(destDir, f).lastModified();
442 if (srcTime > destTime)
443 results.add(f);
444 }
445 return results.toArray(new String[results.size()]);
446 }
447 }
448 }