make/tools/GenStubs/GenStubs.java

Tue, 25 May 2010 15:54:51 -0700

author
ohair
date
Tue, 25 May 2010 15:54:51 -0700
changeset 554
9d9f26857129
parent 468
51011e02c02f
child 962
0ff2bbd38f10
permissions
-rw-r--r--

6943119: Rebrand source copyright notices
Reviewed-by: darcy

     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 }

mercurial