/**********************************************************************
* Copyright (c) 2005-2009 ant4eclipse project team.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich
**********************************************************************/
package org.ant4eclipse.lib.jdt.internal.model.jre;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.ant4eclipse.lib.core.Assure;
import org.ant4eclipse.lib.core.exception.Ant4EclipseException;
import org.ant4eclipse.lib.core.logging.A4ELogging;
import org.ant4eclipse.lib.core.util.ClassLoadingHelper;
import org.ant4eclipse.lib.jdt.JdtExceptionCode;
/**
* <p>
* An instance of type {@link JavaExecuter} can be used to execute a java application with a specific java runtime
* environment. You have to specify the directory that contains the java runtime within the constructor of this class.
* To set the java class that should be executed you have to call <code>setMainClass(String)</code>. To set class path
* entries you can use one of the <code>setClasspathEntries()</code>.
* </p>
* <p>
* Example: <code><pre>
* JavaExecuter javaExecuter = new JavaExecuter(location);
* javaExecuter.setClasspathEntries(new File("c:\temp\myjar.jar"));
* javaExecuter.setMainClass("com.example.Main");
* javaExecuter.execute();
* </pre></code>
* </p>
* <p>
* It is also possible to create a new instance that is already configured with the ant4eclipse class path.
* </p>
* <p>
* Example: <code><pre>
* JavaExecuter javaExecuter = JavaExecuter.createWithA4eClasspath(location);
* javaExecuter.setMainClass("net.sf.ant4eclipse.model.jdt.jre.internal.support.LibraryDetector");
* javaExecuter.execute();
* </pre></code>
* </p>
*
* @author Gerd W¨therich (gerd@gerd-wuetherich.de)
*/
public class JavaExecuter {
/** the directory of the java runtime environment */
private File _jreDirectory;
/** the class path entries */
private File[] _classpathEntries;
/** the qualified name of the main class */
private String _mainClass;
/** vm arguments */
private String[] _vmargs = new String[0];
/** the program arguments */
private String[] _args = new String[0];
/** the system out result */
private String[] _systemOut;
/** the system err result */
private String[] _systemErr;
/**
* <p>
* Returns a new {@link JavaExecuter} that has the ant4eclipse classes set on the class path.
* </p>
*
* @param jreLocation
* the location of the java runtime
*
* @return a new {@link JavaExecuter}
*/
public static JavaExecuter createWithA4eClasspath(File jreLocation) {
Assure.isDirectory("jreLocation", jreLocation);
// check if the location points to a JDK (instead a JRE)...
File jreDirectory = new File(jreLocation, "jre");
if (!jreDirectory.isDirectory()) {
jreDirectory = jreLocation;
}
// create new java launcher
JavaExecuter javaExecuter = new JavaExecuter(jreDirectory);
// resolve the class path entries
String[] classpathentries = ClassLoadingHelper.getClasspathEntriesFor(JavaRuntimeImpl.class);
// TODO
// patch for the usage with clover instrumented classes...
String ant4eclipseCloverPath = System.getProperty("clover.path");
if (ant4eclipseCloverPath != null) {
// need more ram
javaExecuter.setVmargs(new String[] { "-Xmx128m" });
String[] ant4eclipseCloverPathEntries = ant4eclipseCloverPath.split(File.pathSeparator);
String[] finalEntries = new String[ant4eclipseCloverPathEntries.length + classpathentries.length];
System.arraycopy(ant4eclipseCloverPathEntries, 0, finalEntries, 0, ant4eclipseCloverPathEntries.length);
System.arraycopy(classpathentries, 0, finalEntries, ant4eclipseCloverPathEntries.length, classpathentries.length);
classpathentries = finalEntries;
}
javaExecuter.setClasspathEntries(classpathentries);
// return result
return javaExecuter;
}
/**
* <p>
* Creates a new instance of type {@link JavaExecuter}.
* </p>
*
* @param jreDirectory
* the directory of the java runtime.
*/
public JavaExecuter(File jreDirectory) {
Assure.isDirectory("jreDirectory", jreDirectory);
this._jreDirectory = jreDirectory;
}
/**
* <p>
* Sets the specified class path entries.
* </p>
*
* @param classpathEntries
* the class path entries
*/
public void setClasspathEntries(String[] classpathEntries) {
Assure.notNull("classpathEntries", classpathEntries);
// create file array
File[] files = new File[classpathEntries.length];
for (int i = 0; i < classpathEntries.length; i++) {
files[i] = new File(classpathEntries[i]);
}
// sets the class path entries
setClasspathEntries(files);
}
/**
* <p>
* Sets the specified class path entry.
* </p>
*
* @param classpathEntry
* the class path entry
*/
public void setClasspathEntries(File classpathEntry) {
Assure.notNull("classpathEntry", classpathEntry);
setClasspathEntries(new File[] { classpathEntry });
}
/**
* <p>
* Sets the specified class path entries.
* </p>
*
* @param classpathEntries
* the class path entries
*/
public void setClasspathEntries(File[] classpathEntries) {
Assure.notNull("classpathEntries", classpathEntries);
this._classpathEntries = classpathEntries;
}
/**
* <p>
* Specifies the main class that should be executed.
* </p>
*
* @param mainClass
* the main class
*/
public void setMainClass(String mainClass) {
Assure.notNull("mainClass", mainClass);
this._mainClass = mainClass;
}
/**
* <p>
* Specifies the program arguments.
* </p>
*
* @param args
* the program arguments.
*/
public void setArgs(String[] args) {
Assure.notNull("args", args);
this._args = args;
}
/**
* @param vmargs
* the vmargs to set
*/
public void setVmargs(String[] vmargs) {
Assure.notNull("vmargs", vmargs);
this._vmargs = vmargs;
}
/**
* @throws IOException
*/
public void execute() {
// get runtime
Runtime runtime = Runtime.getRuntime();
// create class path
StringBuffer classpathBuffer = new StringBuffer();
for (int i = 0; i < this._classpathEntries.length; i++) {
File file = this._classpathEntries[i];
classpathBuffer.append(file.getAbsolutePath());
if (i + 1 < this._classpathEntries.length) {
classpathBuffer.append(File.pathSeparatorChar);
}
}
String classPath = classpathBuffer.toString();
// create java command
List<String> cmdList = new ArrayList<String>(this._vmargs.length + this._args.length + 4);
File javaExecutable = getJavaExecutable();
cmdList.add(javaExecutable.getAbsolutePath());
// add VM arguments
cmdList.addAll(Arrays.asList(this._vmargs));
// add classpath
cmdList.add("-cp");
cmdList.add(classPath);
// add main class
cmdList.add(this._mainClass);
// add program arguments
cmdList.addAll(Arrays.asList(this._args));
// execute
try {
// debug
A4ELogging.debug("JavaExecuter.execute(): Executing '%s'.", cmdList.toString());
Process proc = runtime.exec(cmdList.toArray(new String[cmdList.size()]), new String[] { "JavaHome=" });
List<String> errorLinesList = new LinkedList<String>();
StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), errorLinesList);
List<String> outputLinesList = new LinkedList<String>();
StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), outputLinesList);
errorGobbler.start();
outputGobbler.start();
// wait for result
proc.waitFor();
// read out and err stream
this._systemOut = outputLinesList.toArray(new String[0]);
this._systemErr = errorLinesList.toArray(new String[0]);
// debug
A4ELogging.debug("JavaExecuter.execute(): System.out -> '%s'.", Arrays.asList(this._systemOut));
A4ELogging.debug("JavaExecuter.execute(): System.err -> '%s'.", Arrays.asList(this._systemErr));
// log error...
if (this._systemErr != null && this._systemErr.length > 0) {
// TODO
throw new RuntimeException("ERROR JAVAEXCUTOR FAILED: " + Arrays.asList(this._systemErr));
}
} catch (IOException e) {
// throw Ant4EclipseException
throw new Ant4EclipseException(e, JdtExceptionCode.JAVA_LAUNCHER_EXECUTION_EXCEPTION, cmdListToStr(cmdList));
} catch (InterruptedException e) {
// throw Ant4EclipseException
throw new Ant4EclipseException(e, JdtExceptionCode.JAVA_LAUNCHER_EXECUTION_EXCEPTION, cmdListToStr(cmdList));
}
}
/**
* Converts a command list to a String, adding quotes where they are needed (spaced args)
*
* @param cmdList
* The command list to convert
* @return The command list as a String
*/
private String cmdListToStr(List<String> cmdList) {
StringBuilder cmd = new StringBuilder();
for (String arg : cmdList) {
boolean containSpace = arg.contains(" ");
if (containSpace) {
cmd.append("\"");
}
cmd.append(arg);
if (containSpace) {
cmd.append("\"");
}
}
return cmd.toString();
}
/**
* <p>
* Returns the output that was written to system out as a string array .
* </p>
*
* @return the output that was written to system out as a string array .
*/
public String[] getSystemOut() {
return this._systemOut;
}
/**
* <p>
* Returns the output that was written to system err as a string array .
* </p>
*
* @return the output that was written to system err as a string array .
*/
public String[] getSystemErr() {
return this._systemErr;
}
/**
* <p>
* Returns the java executable. If the java executable could not be resolved, a {@link Ant4EclipseException} with the
* ExecptionCode {@link JdtModelExceptionCode#INVALID_JRE_DIRECTORY} is thrown.
* </p>
*
* @return the java executable
*/
private File getJavaExecutable() {
// try 'bin/java'
File result = new File(this._jreDirectory, "bin/java");
// try 'bin/java.exe'
if (!result.exists()) {
result = new File(this._jreDirectory, "bin/java.exe");
}
// try 'bin/j9'
if (!result.exists()) {
result = new File(this._jreDirectory, "bin/j9");
}
// try 'bin/j9.exe'
if (!result.exists()) {
result = new File(this._jreDirectory, "bin/j9.exe");
}
// throw Ant4EclipseException
if (!result.exists()) {
throw new Ant4EclipseException(JdtExceptionCode.INVALID_JRE_DIRECTORY, this._jreDirectory.getAbsolutePath());
}
// return result
return result;
}
/**
* StreamGlobber thread is responsible for proper consuming of {@link java.lang.Process} output and error streams.
* Without it <code>java.lang.Process.waitFor()</code> can hang on some platforms.
*
* @author Karol Kański (karkan)
*
*/
private class StreamGobbler extends Thread {
private InputStream is;
private List<String> streamLinesList;
StreamGobbler(InputStream is, List<String> streamLinesList) {
this.is = is;
this.streamLinesList = streamLinesList;
}
@Override
public void run() {
try {
InputStreamReader isr = new InputStreamReader(this.is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
this.streamLinesList.add(line);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}