1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.lib;
  28 
  29 import java.io.OutputStream;
  30 import java.io.PrintWriter;
  31 import java.lang.reflect.Constructor;
  32 import java.lang.reflect.Method;
  33 import java.lang.reflect.Modifier;
  34 import java.lang.reflect.InvocationTargetException;
  35 import com.sun.javatest.Command;
  36 import com.sun.javatest.Status;
  37 import com.sun.javatest.util.PathClassLoader;
  38 import com.sun.javatest.util.WriterStream;
  39 
  40 /**
  41  * Invoke a Java compiler via reflection.
  42  * The compiler is assumed to have a constructor and compile method
  43  * matching the following signature:
  44  *<pre>
  45  * public class COMPILER {
  46  *     public COMPILER(java.io.OutputStream out, String compilerName);
  47  *     public boolean compile(String[] args);
  48  * }
  49  *</pre>
  50  * or
  51  *<pre>
  52  * public class COMPILER {
  53  *     public COMPILER();
  54  *     public int compile(String[] args);
  55  * }
  56  *</pre>
  57  * or
  58  * <pre>
  59  * public class COMPILER {
  60  *    public static int compile(String[] args, PrintWriter out);
  61  * }
  62  * </pre>
  63  * This means the command is suitable for (but not limited to) the
  64  * compiler javac suplied with JDK. (Note that this uses an internal
  65  * API of javac which is not documented and is not guaranteed to exist
  66  * in any specific release of JDK.)
  67  */
  68 public class JavaCompileCommand extends Command
  69 {
  70     /**
  71      * A stand-alone entry point for this command. An instance of this
  72      * command is created, and its <code>run</code> method invoked,
  73      * passing in the command line args and <code>System.out</code> and
  74      * <code>System.err</code> as the two streams.
  75      * @param args command line arguments for this command.
  76      * @see #run
  77      */
  78     public static void main(String[] args) {
  79         PrintWriter out = new PrintWriter(System.out);
  80         PrintWriter err = new PrintWriter(System.err);
  81         Status s;
  82         try {
  83             JavaCompileCommand c = new JavaCompileCommand();
  84             s = c.run(args, out, err);
  85         }
  86         finally {
  87             out.flush();
  88             err.flush();
  89         }
  90         s.exit();
  91     }
  92 
  93 
  94     /**
  95      * Invoke a specified compiler, or the default, javac.
  96      * If the first word in the <code>args</code> array is "-compiler"
  97      * the second is interpreted as the class name for the compiler to be
  98      * invoked, optionally preceded by a name for the compiler, separated
  99      * from the class name by a colon.  If no -compiler is specified,
 100      * the default is `javac:com.sun.tools.javac.Main'. If -compiler is specified
 101      * but no compiler name is given before the class name, the default name
 102      * will be `java ' followed by the classname. For example, `-compiler Main'
 103      * will result in the class name being `Main' and the compiler name being
 104      * `java Main'. After determining the class and compiler name,
 105      * an instance of the compiler class will be created, passing it a stream
 106      * using the <code>ref</code> parameter, and the name of the compiler.
 107      * Then the `compile' method will be invoked, passing it the remaining
 108      * values of the `args' parameter.  If the compile method returns true,
 109      * the result will be a status of `passed'; if it returns `false', the
 110      * result will be `failed'. If any problems arise, the result will be
 111      * a status of `error'.
 112      * @param args An optional specification for the compiler to be invoked,
 113      *          followed by arguments for the compiler's compile method.
 114      * @param log  Not used.
 115      * @param ref  Passed to the compiler that is invoked.
 116      * @return `passed' if the compilation is successful; `failed' if the
 117      *          compiler is invoked and errors are found in the file(s)
 118      *          being compiler; or `error' if some more serious problem arose
 119      *          that prevented the compiler performing its task.
 120      */
 121     public Status run(String[] args, PrintWriter log, PrintWriter ref) {
 122 
 123         if (args.length == 0)
 124             return Status.error("No args supplied");
 125 
 126         String compilerClassName = null;
 127         String compilerName = null;
 128         String classpath = null;
 129         String[] options = null;
 130 
 131         // If we find a '-' in the args, what comes before it are
 132         // options for this class and what comes after it are args
 133         // for the compiler class. If don't find a '-', there are no
 134         // options for this class, and everything is handed off to
 135         // the compiler class
 136 
 137         for (int i = 0; i < args.length; i++) {
 138             if (args[i].equals("-")) {
 139                 options = new String[i];
 140                 System.arraycopy(args, 0, options, 0, options.length);
 141                 args = shift(args, i+1);
 142                 break;
 143             }
 144         }
 145 
 146         if (options != null) {
 147             for (int i = 0; i < options.length; i++) {
 148                 if (options[i].equals("-compiler")) {
 149                     if (i + 1 == options.length)
 150                         return Status.error("No compiler specified after -compiler option");
 151 
 152                     String s = options[++i];
 153                     int colon = s.indexOf(":");
 154                     if (colon == -1) {
 155                         compilerClassName = s;
 156                         compilerName = "java " + s;
 157                     }
 158                     else {
 159                         compilerClassName = s.substring(colon + 1);
 160                         compilerName = s.substring(0, colon);
 161                     }
 162                 }
 163                 else if (options[i].equals("-cp") || options[i].equals("-classpath")) {
 164                     if (i + 1 == options.length)
 165                         return Status.error("No path specified after -cp or -classpath option");
 166                     classpath = options[++i];
 167                 }
 168                 else if (options[i].equals("-verbose"))
 169                     verbose = true;
 170                 else
 171                     return Status.error("Unrecognized option: " + options[i]);
 172             }
 173         }
 174 
 175         this.log = log;
 176 
 177         try {
 178 
 179             ClassLoader loader;
 180             if (classpath == null)
 181                 loader = null;
 182             else
 183                 loader = new PathClassLoader(classpath);
 184 
 185             Class compilerClass;
 186             if (compilerClassName != null) {
 187                 compilerClass = getClass(loader, compilerClassName);
 188                 if (compilerClass == null)
 189                     return Status.error("Cannot find compiler: " + compilerClassName);
 190             }
 191             else {
 192                 compilerName = "javac";
 193                 compilerClass = getClass(loader, "com.sun.tools.javac.Main");  // JDK1.3+
 194                 if (compilerClass == null)
 195                     compilerClass = getClass(loader, "sun.tools.javac.Main");  // JDK1.1-2
 196                 if (compilerClass == null)
 197                     return Status.error("Cannot find compiler");
 198             }
 199 
 200             loader = null;
 201 
 202             Object[] compileMethodArgs;
 203             Method compileMethod = getMethod(compilerClass, "compile", // JDK1.4+
 204                                              new Class[] { String[].class, PrintWriter.class });
 205             if (compileMethod != null)
 206                 compileMethodArgs = new Object[] { args, ref };
 207             else {
 208                 compileMethod = getMethod(compilerClass, "compile",   // JDK1.1-3
 209                                           new Class[] { String[].class });
 210                 if (compileMethod != null)
 211                     compileMethodArgs = new Object[] { args };
 212                 else
 213                     return Status.error("Cannot find compile method for " + compilerClass.getName());
 214             }
 215 
 216             Object compiler;
 217             if (Modifier.isStatic(compileMethod.getModifiers()))
 218                 compiler =  null;
 219             else {
 220                 Object[] constrArgs;
 221                 Constructor constr = getConstructor(compilerClass, // JDK1.1-2
 222                                                     new Class[] { OutputStream.class, String.class });
 223                 if (constr != null)
 224                     constrArgs = new Object[] { new WriterStream(ref), compilerName };
 225                 else {
 226                     constr = getConstructor(compilerClass, new Class[0]); // JDK1.3
 227                     if (constr != null)
 228                         constrArgs = new Object[0];
 229                     else
 230                         return Status.error("Cannot find suitable constructor for " + compilerClass.getName());
 231                 }
 232                 try {
 233                     compiler = constr.newInstance(constrArgs);
 234                 }
 235                 catch (Throwable t) {
 236                     t.printStackTrace(log);
 237                     return Status.error("Cannot instantiate compiler");
 238                 }
 239             }
 240 
 241             Object result;
 242             try {
 243                 result = compileMethod.invoke(compiler, compileMethodArgs);
 244             }
 245             catch (Throwable t) {
 246                 t.printStackTrace(log);
 247                 return Status.error("Error invoking compiler");
 248             }
 249 
 250             // result might be a boolean (old javac) or an int (new javac)
 251             if (result instanceof Boolean) {
 252                 boolean ok = ((Boolean)result).booleanValue();
 253                 return (ok ? passed : failed);
 254             }
 255             else if (result instanceof Integer) {
 256                 int rc = ((Integer)result).intValue();
 257                 return (rc == 0 ? passed : failed);
 258             }
 259             else
 260                 return Status.error("Unexpected return value from compiler: " + result);
 261         }
 262         finally {
 263             log.flush();
 264             ref.flush();
 265         }
 266     }
 267 
 268     private Class getClass(ClassLoader loader, String name) {
 269         try {
 270             return (loader == null ? Class.forName(name) : loader.loadClass(name));
 271         }
 272         catch (ClassNotFoundException e) {
 273             return null;
 274         }
 275     }
 276 
 277     private Constructor getConstructor(Class<?> c, Class[] argTypes) {
 278         try {
 279             return c.getConstructor(argTypes);
 280         }
 281         catch (NoSuchMethodException e) {
 282             return null;
 283         }
 284         catch (Throwable t) {
 285             if (verbose)
 286                 t.printStackTrace(log);
 287             return null;
 288         }
 289     }
 290 
 291     private Method getMethod(Class<?> c, String name, Class[] argTypes) {
 292         try {
 293             return c.getMethod(name, argTypes);
 294         }
 295         catch (NoSuchMethodException e) {
 296             return null;
 297         }
 298         catch (Throwable t) {
 299             if (verbose)
 300                 t.printStackTrace(log);
 301             return null;
 302         }
 303     }
 304 
 305     private static String[] shift(String[] args, int n) {
 306         String[] newArgs = new String[args.length - n];
 307         System.arraycopy(args, n, newArgs, 0, newArgs.length);
 308         return newArgs;
 309     }
 310 
 311     public static boolean defaultVerbose = Boolean.getBoolean("javatest.JavaCompileCommand.verbose");
 312     private boolean verbose = defaultVerbose;
 313     private PrintWriter log;
 314 
 315     private static final Status passed = Status.passed("Compilation successful");
 316     private static final Status failed = Status.failed("Compilation failed");
 317 }