langtools/make/tools/GenStubs/GenStubs.java
changeset 12085 ce2780cb121f
parent 12084 b367473ef0f1
child 12086 734243132d25
equal deleted inserted replaced
12084:b367473ef0f1 12085:ce2780cb121f
     1 /*
       
     2  * Copyright (c) 2009, 2010, 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  */
       
    25 
       
    26 import java.io.*;
       
    27 import java.util.*;
       
    28 import javax.tools.JavaFileObject;
       
    29 import javax.tools.StandardJavaFileManager;
       
    30 import javax.tools.StandardLocation;
       
    31 
       
    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;
       
    37 
       
    38 
       
    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;
       
    61 
       
    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  */
       
    94 
       
    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     }
       
   106 
       
   107     public static void main(String[] args) {
       
   108         boolean ok = new GenStubs().run(args);
       
   109         if (!ok)
       
   110             System.exit(1);
       
   111     }
       
   112 
       
   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         }
       
   131 
       
   132         return run(sourcepath, outdir, classes);
       
   133     }
       
   134 
       
   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");
       
   141 
       
   142         JavacTool tool = JavacTool.create();
       
   143         StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null);
       
   144 
       
   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             }
       
   157 
       
   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         }
       
   166 
       
   167         return (errors == 0);
       
   168     }
       
   169 
       
   170     void makeStub(StandardJavaFileManager fm, CompilationUnitTree tree) throws IOException {
       
   171         CompilationUnitTree tree2 = new StubMaker().translate(tree);
       
   172         CompilationUnitTree tree3 = new ImportCleaner(fm).removeRedundantImports(tree2);
       
   173 
       
   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     }
       
   185 
       
   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     }
       
   194 
       
   195     void error(String message) {
       
   196         System.err.println(message);
       
   197         errors++;
       
   198     }
       
   199 
       
   200     void error(String message, Throwable cause) {
       
   201         error(message);
       
   202     }
       
   203 
       
   204     int errors;
       
   205 
       
   206     class StubMaker extends TreeTranslator {
       
   207         CompilationUnitTree translate(CompilationUnitTree tree) {
       
   208             return super.translate((JCCompilationUnit) tree);
       
   209         }
       
   210 
       
   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         }
       
   220 
       
   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         }
       
   237 
       
   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         }
       
   246 
       
   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     }
       
   283 
       
   284     class ImportCleaner extends TreeScanner {
       
   285         private Set<Name> names = new HashSet<Name>();
       
   286         private TreeMaker m;
       
   287 
       
   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         }
       
   295 
       
   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         }
       
   315 
       
   316         @Override
       
   317         public void visitImport(JCImport tree) { } // ignore names found in imports
       
   318 
       
   319         @Override
       
   320         public void visitIdent(JCIdent tree) {
       
   321             names.add(tree.name);
       
   322         }
       
   323 
       
   324         @Override
       
   325         public void visitSelect(JCFieldAccess tree) {
       
   326             super.visitSelect(tree);
       
   327             names.add(tree.name);
       
   328         }
       
   329     }
       
   330 
       
   331     //---------- Ant Invocation ------------------------------------------------
       
   332 
       
   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;
       
   339 
       
   340         public void setSrcDir(File dir) {
       
   341             this.srcDir = dir;
       
   342         }
       
   343 
       
   344         public void setDestDir(File dir) {
       
   345             this.destDir = dir;
       
   346         }
       
   347 
       
   348         public void setFork(boolean v) {
       
   349             this.fork = v;
       
   350         }
       
   351 
       
   352         public void setClasspath(Path cp) {
       
   353             if (classpath == null)
       
   354                 classpath = cp;
       
   355             else
       
   356                 classpath.append(cp);
       
   357         }
       
   358 
       
   359         public Path createClasspath() {
       
   360             if (classpath == null) {
       
   361                 classpath = new Path(getProject());
       
   362             }
       
   363             return classpath.createPath();
       
   364         }
       
   365 
       
   366         public void setClasspathRef(Reference r) {
       
   367             createClasspath().setRefid(r);
       
   368         }
       
   369 
       
   370         public void setIncludes(String includes) {
       
   371             super.setIncludes(includes);
       
   372             this.includes = includes;
       
   373         }
       
   374 
       
   375         @Override
       
   376         public void execute() {
       
   377             if (includes != null && includes.trim().isEmpty())
       
   378                 return;
       
   379 
       
   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));
       
   385 
       
   386             files = filter(srcDir, destDir, files);
       
   387             if (files.length == 0)
       
   388                 return;
       
   389             System.out.println("Generating " + files.length + " stub files to " + destDir);
       
   390 
       
   391             List<String> classNames = new ArrayList<String>();
       
   392             for (String file: files) {
       
   393                 classNames.add(file.replaceAll(".java$", "").replace('/', '.'));
       
   394             }
       
   395 
       
   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         }
       
   436 
       
   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 }