jjg@441: /* jjg@1753: * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. jjg@441: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@441: * jjg@441: * This code is free software; you can redistribute it and/or modify it jjg@441: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this jjg@441: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. jjg@441: * jjg@441: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@441: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@441: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@441: * version 2 for more details (a copy is included in the LICENSE file that jjg@441: * accompanied this code). jjg@441: * jjg@441: * You should have received a copy of the GNU General Public License version jjg@441: * 2 along with this work; if not, write to the Free Software Foundation, jjg@441: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@441: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. jjg@441: */ jjg@468: ohrstrom@1224: package genstubs; ohrstrom@1224: jjg@441: import java.io.*; jjg@441: import java.util.*; jjg@441: import javax.tools.JavaFileObject; jjg@441: import javax.tools.StandardJavaFileManager; jjg@441: import javax.tools.StandardLocation; jjg@441: jjg@441: import com.sun.source.tree.CompilationUnitTree; jjg@441: import com.sun.source.util.JavacTask; jjg@441: import com.sun.tools.javac.api.JavacTool; jjg@441: import com.sun.tools.javac.code.Flags; jjg@1379: import com.sun.tools.javac.code.TypeTag; jjg@441: import com.sun.tools.javac.tree.JCTree; jjg@1753: import com.sun.tools.javac.tree.JCTree.JCClassDecl; jjg@441: import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; jjg@468: import com.sun.tools.javac.tree.JCTree.JCFieldAccess; jjg@468: import com.sun.tools.javac.tree.JCTree.JCIdent; jjg@468: import com.sun.tools.javac.tree.JCTree.JCImport; jjg@441: import com.sun.tools.javac.tree.JCTree.JCLiteral; jjg@441: import com.sun.tools.javac.tree.JCTree.JCMethodDecl; jjg@441: import com.sun.tools.javac.tree.JCTree.JCModifiers; jjg@441: import com.sun.tools.javac.tree.JCTree.JCVariableDecl; jjg@441: import com.sun.tools.javac.tree.Pretty; jjg@468: import com.sun.tools.javac.tree.TreeMaker; jjg@468: import com.sun.tools.javac.tree.TreeScanner; jjg@441: import com.sun.tools.javac.tree.TreeTranslator; jjg@468: import com.sun.tools.javac.util.Context; jjg@468: import com.sun.tools.javac.util.ListBuffer; jjg@468: import com.sun.tools.javac.util.Name; jjg@468: import javax.tools.JavaFileManager; jjg@441: jjg@441: /** jjg@441: * Generate stub source files by removing implementation details from input files. jjg@441: * jjg@441: * This is a special purpose stub generator, specific to the needs of generating jjg@441: * stub files for JDK 7 API that are needed to compile langtools files that depend jjg@441: * on that API. The stub generator works by removing as much of the API source code jjg@441: * as possible without affecting the public signature, in order to reduce the jjg@441: * transitive closure of the API being referenced. The resulting stubs can be jjg@441: * put on the langtools sourcepath with -implicit:none to compile the langtools jjg@441: * files that depend on the JDK 7 API. jjg@441: * jjg@441: * Usage: jjg@441: * genstubs -s -sourcepath jjg@441: * jjg@441: * The specified class names are looked up on the sourcepath, and corresponding jjg@441: * stubs are written to the source output directory. jjg@441: * jjg@441: * Classes are parsed into javac ASTs, then processed with a javac TreeTranslator jjg@441: * to remove implementation details, and written out in the source output directory. jjg@441: * Documentation comments and annotations are removed. Method bodies are removed jjg@441: * and methods are marked native. Private and package-private field definitions jjg@441: * have their initializers replace with 0, 0.0, false, null as appropriate. jjg@441: */ jjg@441: jjg@441: public class GenStubs { jjg@441: static class Fault extends Exception { jjg@441: private static final long serialVersionUID = 0; jjg@441: Fault(String message) { jjg@441: super(message); jjg@441: } jjg@441: Fault(String message, Throwable cause) { jjg@441: super(message); jjg@441: initCause(cause); jjg@441: } jjg@441: } jjg@441: jjg@441: public static void main(String[] args) { jjg@441: boolean ok = new GenStubs().run(args); jjg@441: if (!ok) jjg@441: System.exit(1); jjg@441: } jjg@441: ohrstrom@1224: public boolean run(String... args) { jjg@441: File outdir = null; jjg@441: String sourcepath = null; jjg@441: List classes = new ArrayList(); jjg@441: for (ListIterator iter = Arrays.asList(args).listIterator(); iter.hasNext(); ) { jjg@441: String arg = iter.next(); jjg@441: if (arg.equals("-s") && iter.hasNext()) jjg@441: outdir = new File(iter.next()); jjg@441: else if (arg.equals("-sourcepath") && iter.hasNext()) jjg@441: sourcepath = iter.next(); jjg@441: else if (arg.startsWith("-")) jjg@441: throw new IllegalArgumentException(arg); jjg@441: else { jjg@441: classes.add(arg); jjg@441: while (iter.hasNext()) jjg@441: classes.add(iter.next()); jjg@441: } jjg@441: } jjg@441: jjg@441: return run(sourcepath, outdir, classes); jjg@441: } jjg@441: ohrstrom@1224: public boolean run(String sourcepath, File outdir, List classes) { jjg@441: //System.err.println("run: sourcepath:" + sourcepath + " outdir:" + outdir + " classes:" + classes); jjg@441: if (sourcepath == null) jjg@441: throw new IllegalArgumentException("sourcepath not set"); jjg@441: if (outdir == null) jjg@441: throw new IllegalArgumentException("source output dir not set"); jjg@441: jjg@441: JavacTool tool = JavacTool.create(); jjg@441: StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null); jjg@441: jjg@441: try { jjg@441: fm.setLocation(StandardLocation.SOURCE_OUTPUT, Collections.singleton(outdir)); jjg@441: fm.setLocation(StandardLocation.SOURCE_PATH, splitPath(sourcepath)); jjg@441: List files = new ArrayList(); jjg@441: for (String c: classes) { jjg@441: JavaFileObject fo = fm.getJavaFileForInput( jjg@441: StandardLocation.SOURCE_PATH, c, JavaFileObject.Kind.SOURCE); jjg@441: if (fo == null) jjg@441: error("class not found: " + c); jjg@441: else jjg@441: files.add(fo); jjg@441: } jjg@441: jjg@441: JavacTask t = tool.getTask(null, fm, null, null, null, files); jjg@441: Iterable trees = t.parse(); jjg@441: for (CompilationUnitTree tree: trees) { jjg@441: makeStub(fm, tree); jjg@441: } jjg@441: } catch (IOException e) { jjg@441: error("IO error " + e, e); jjg@441: } jjg@441: jjg@441: return (errors == 0); jjg@441: } jjg@441: jjg@441: void makeStub(StandardJavaFileManager fm, CompilationUnitTree tree) throws IOException { jjg@441: CompilationUnitTree tree2 = new StubMaker().translate(tree); jjg@468: CompilationUnitTree tree3 = new ImportCleaner(fm).removeRedundantImports(tree2); jjg@441: jjg@441: String className = fm.inferBinaryName(StandardLocation.SOURCE_PATH, tree.getSourceFile()); jjg@441: JavaFileObject fo = fm.getJavaFileForOutput(StandardLocation.SOURCE_OUTPUT, jjg@441: className, JavaFileObject.Kind.SOURCE, null); jjg@441: // System.err.println("Writing " + className + " to " + fo.getName()); jjg@441: Writer out = fo.openWriter(); jjg@441: try { jjg@468: new Pretty(out, true).printExpr((JCTree) tree3); jjg@441: } finally { jjg@441: out.close(); jjg@441: } jjg@441: } jjg@441: jjg@441: List splitPath(String path) { jjg@441: List list = new ArrayList(); jjg@441: for (String p: path.split(File.pathSeparator)) { jjg@441: if (p.length() > 0) jjg@441: list.add(new File(p)); jjg@441: } jjg@441: return list; jjg@441: } jjg@441: jjg@441: void error(String message) { jjg@441: System.err.println(message); jjg@441: errors++; jjg@441: } jjg@441: jjg@441: void error(String message, Throwable cause) { jjg@441: error(message); jjg@441: } jjg@441: jjg@441: int errors; jjg@441: jjg@441: class StubMaker extends TreeTranslator { jjg@441: CompilationUnitTree translate(CompilationUnitTree tree) { jjg@441: return super.translate((JCCompilationUnit) tree); jjg@441: } jjg@441: jjg@441: /** jjg@441: * compilation units: remove javadoc comments jjg@441: * -- required, in order to remove @deprecated tags, since we jjg@441: * (separately) remove all annotations, including @Deprecated jjg@441: */ jjg@441: public void visitTopLevel(JCCompilationUnit tree) { jjg@441: super.visitTopLevel(tree); erikj@1286: tree.docComments = null; jjg@441: } jjg@441: jjg@441: /** jjg@441: * methods: remove method bodies, make methods native jjg@441: */ jjg@441: @Override jjg@1753: public void visitClassDef(JCClassDecl tree) { jjg@1753: long prevClassMods = currClassMods; jjg@1753: currClassMods = tree.mods.flags; jjg@1753: try { jjg@1753: super.visitClassDef(tree);; jjg@1753: } finally { jjg@1753: currClassMods = prevClassMods; jjg@1753: } jjg@1753: } jjg@1753: private long currClassMods = 0; jjg@1753: jjg@1753: /** jjg@1753: * methods: remove method bodies, make methods native jjg@1753: */ jjg@1753: @Override jjg@441: public void visitMethodDef(JCMethodDecl tree) { jjg@441: tree.mods = translate(tree.mods); jjg@441: tree.restype = translate(tree.restype); jjg@441: tree.typarams = translateTypeParams(tree.typarams); jjg@441: tree.params = translateVarDefs(tree.params); jjg@441: tree.thrown = translate(tree.thrown); jjg@1786: if (tree.body != null) { jjg@1753: if ((currClassMods & Flags.INTERFACE) != 0) { jjg@1786: tree.mods.flags &= ~(Flags.DEFAULT | Flags.STATIC); jjg@1753: } else { jjg@1753: tree.mods.flags |= Flags.NATIVE; jjg@1753: } jjg@441: tree.body = null; jjg@441: } jjg@441: result = tree; jjg@441: } jjg@441: jjg@441: /** jjg@441: * modifiers: remove annotations jjg@441: */ jjg@441: @Override jjg@441: public void visitModifiers(JCModifiers tree) { jjg@441: tree.annotations = com.sun.tools.javac.util.List.nil(); jjg@441: result = tree; jjg@441: } jjg@441: jjg@441: /** jjg@441: * field definitions: replace initializers with 0, 0.0, false etc jjg@441: * when possible -- i.e. leave public, protected initializers alone jjg@441: */ jjg@441: @Override jjg@441: public void visitVarDef(JCVariableDecl tree) { jjg@441: tree.mods = translate(tree.mods); jjg@441: tree.vartype = translate(tree.vartype); jjg@441: if (tree.init != null) { jjg@441: if ((tree.mods.flags & (Flags.PUBLIC | Flags.PROTECTED)) != 0) jjg@441: tree.init = translate(tree.init); jjg@441: else { jjg@441: String t = tree.vartype.toString(); jjg@441: if (t.equals("boolean")) jjg@1379: tree.init = new JCLiteral(TypeTag.BOOLEAN, 0) { }; jjg@441: else if (t.equals("byte")) jjg@1379: tree.init = new JCLiteral(TypeTag.BYTE, 0) { }; jjg@441: else if (t.equals("char")) jjg@1379: tree.init = new JCLiteral(TypeTag.CHAR, 0) { }; jjg@441: else if (t.equals("double")) jjg@1379: tree.init = new JCLiteral(TypeTag.DOUBLE, 0.d) { }; jjg@441: else if (t.equals("float")) jjg@1379: tree.init = new JCLiteral(TypeTag.FLOAT, 0.f) { }; jjg@441: else if (t.equals("int")) jjg@1379: tree.init = new JCLiteral(TypeTag.INT, 0) { }; jjg@441: else if (t.equals("long")) jjg@1379: tree.init = new JCLiteral(TypeTag.LONG, 0) { }; jjg@441: else if (t.equals("short")) jjg@1379: tree.init = new JCLiteral(TypeTag.SHORT, 0) { }; jjg@441: else jjg@1379: tree.init = new JCLiteral(TypeTag.BOT, null) { }; jjg@441: } jjg@441: } jjg@441: result = tree; jjg@441: } jjg@441: } jjg@441: jjg@468: class ImportCleaner extends TreeScanner { jjg@468: private Set names = new HashSet(); jjg@468: private TreeMaker m; jjg@468: jjg@468: ImportCleaner(JavaFileManager fm) { jjg@468: // ImportCleaner itself doesn't require a filemanager, but instantiating jjg@468: // a TreeMaker does, indirectly (via ClassReader, sigh) jjg@468: Context c = new Context(); jjg@468: c.put(JavaFileManager.class, fm); jjg@468: m = TreeMaker.instance(c); jjg@468: } jjg@468: jjg@468: CompilationUnitTree removeRedundantImports(CompilationUnitTree t) { jjg@468: JCCompilationUnit tree = (JCCompilationUnit) t; jjg@468: tree.accept(this); jjg@468: ListBuffer defs = new ListBuffer(); jjg@468: for (JCTree def: tree.defs) { ohrstrom@1224: if (def.getTag() == JCTree.Tag.IMPORT) { jjg@468: JCImport imp = (JCImport) def; ohrstrom@1224: if (imp.qualid.getTag() == JCTree.Tag.SELECT) { jjg@468: JCFieldAccess qualid = (JCFieldAccess) imp.qualid; jjg@468: if (!qualid.name.toString().equals("*") jjg@468: && !names.contains(qualid.name)) { jjg@468: continue; jjg@468: } jjg@468: } jjg@468: } jjg@468: defs.add(def); jjg@468: } jjg@468: return m.TopLevel(tree.packageAnnotations, tree.pid, defs.toList()); jjg@468: } jjg@468: jjg@468: @Override jjg@468: public void visitImport(JCImport tree) { } // ignore names found in imports jjg@468: jjg@468: @Override jjg@468: public void visitIdent(JCIdent tree) { jjg@468: names.add(tree.name); jjg@468: } jjg@468: jjg@468: @Override jjg@468: public void visitSelect(JCFieldAccess tree) { jjg@468: super.visitSelect(tree); jjg@468: names.add(tree.name); jjg@468: } jjg@468: } jjg@441: }