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