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 }