blob: 10480b3e16d31fc1444e7ada6987f269d9f370c6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.tests.session;
import java.io.*;
import org.eclipse.core.internal.runtime.Policy;
/**
* Executes an external process synchronously, allowing the client to define
* a maximum amount of time for the process to complete.
*/
public class ProcessController {
/**
* Thrown when a process being executed exceeds the maximum amount
* of time allowed for it to complete.
*/
public class TimeOutException extends Exception {
public TimeOutException() {
super();
}
public TimeOutException(String message) {
super(message);
}
}
private boolean finished;
private OutputStream forwardStdErr;
private OutputStream forwardStdOut;
private boolean killed;
private String params;
private Process process;
private long startupTime;
private long timeLimit;
/**
* Constructs an instance of ProcessController. This does not creates an
* OS process. <code>run()</code> does that.
*
* @param timeout the maximum time the process should take to run
* @param params the parameters to be passed to the controlled process
*/
public ProcessController(long timeout, String params) {
this.timeLimit = timeout;
this.params = params;
}
private void controlProcess() {
new Thread() {
public void run() {
while (!isFinished() && !timedOut())
synchronized (this) {
try {
wait(100);
} catch (InterruptedException e) {
break;
}
}
kill();
}
}.start();
}
/**
* Causes the process to start executing. This call will block until the
* process has completed. If <code>timeout</code> is specified, the
* process will be interrupted if it takes more than the specified amount
* of time to complete, causing a <code>TimedOutException</code> to be thrown.
* Specifying zero as <code>timeout</code> means
* the process is not time constrained.
*
* @return the process exit value
* @throws InterruptedException
* @throws IOException
* @throws TimeOutException if the process did not complete in time
*/
public int execute() throws InterruptedException, IOException, TimeOutException {
this.startupTime = System.currentTimeMillis();
// starts the process
process = Runtime.getRuntime().exec(params);
if (forwardStdErr != null)
forwardStream(process.getErrorStream(), forwardStdErr);
if (forwardStdOut != null)
forwardStream(process.getInputStream(), forwardStdOut);
if (timeLimit > 0)
// ensures process execution time does not exceed the time limit
controlProcess();
try {
return process.waitFor();
} finally {
markFinished();
if (wasKilled())
throw new TimeOutException();
}
}
/**
* Forwards the process standard error output to the given output stream.
* Must be called before execution has started.
*
* @param err an output stream where to forward the process
* standard error output to
*/
public void forwardErrorOutput(OutputStream err) {
this.forwardStdErr = err;
}
/**
* Forwards the process standard output to the given output stream.
* Must be called before execution has started.
*
* @param err an output stream where to forward the process
* standard output to
*/
public void forwardOutput(OutputStream out) {
this.forwardStdOut = out;
}
private void forwardStream(final InputStream in, final OutputStream out) {
new Thread() {
public void run() {
try {
while (!isFinished()) {
while (in.available() > 0)
out.write(in.read());
synchronized (this) {
this.wait(100);
}
}
out.flush();
} catch (IOException ioe) {
//TODO only log/show if debug is on
ioe.printStackTrace();
} catch (InterruptedException e) {
//TODO only log/show if debug is on
e.printStackTrace();
}
}
}.start();
}
/**
* Returns the controled process. Will return <code>null</code> before
* <code>execute</code> is called.
*
* @return the underlying process
*/
public Process getProcess() {
return process;
}
private synchronized boolean isFinished() {
return finished;
}
/**
* Kills the process. Does nothing if it has been finished already.
*/
public void kill() {
synchronized (this) {
if (isFinished())
return;
killed = true;
}
Policy.debug("Having to kill the process");
process.destroy();
}
private synchronized void markFinished() {
finished = true;
notifyAll();
}
private synchronized boolean timedOut() {
return System.currentTimeMillis() - startupTime > timeLimit;
}
/**
* Returns whether the process was killed due to a time out.
*
* @return <code>true</code> if the process was killed,
* <code>false</code> if the completed normally
*/
public boolean wasKilled() {
return killed;
}
}