WALA/com.ibm.wala.util/src/com/ibm/wala/util/processes/Launcher.java

377 lines
9.8 KiB
Java

/*******************************************************************************
* Copyright (c) 2002 - 2006 IBM Corporation.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.wala.util.processes;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;
/**
* Abstract base class for a process launcher
*/
public abstract class Launcher {
// use a fairly big buffer size to avoid performance problems with the default
private final static int BUFFER_SIZE = 32 * 1024;
protected File workingDir = null;
protected Map<String,String> env = null;
protected byte[] stdOut = null;
protected byte[] stdErr = null;
private byte[] input = null;
/**
* capture the contents of stdout?
*/
private final boolean captureOutput;
/**
* capture the contents of stderr?
*/
private final boolean captureErr;
private final Logger logger;
protected Launcher(Logger logger) {
super();
this.captureOutput = false;
this.captureErr = false;
this.logger = logger;
}
protected Launcher(boolean captureOutput, boolean captureErr, Logger logger) {
super();
this.captureOutput = captureOutput;
this.captureErr = captureErr;
this.logger = logger;
}
public File getWorkingDir() {
return workingDir;
}
public void setWorkingDir(File newWorkingDir) {
workingDir = newWorkingDir;
}
public Map<String,String> getEnv() {
return env;
}
public void setEnv(Map<String,String> newEnv) {
env = newEnv;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer(super.toString());
result.append(" (workingDir: ");
result.append(workingDir);
result.append(", env: ");
result.append(env);
result.append(')');
return result.toString();
}
/**
* Spawn a process to execute the given command
*
* @return an object representing the process
*/
protected Process spawnProcess(String cmd) throws IllegalArgumentException, IOException {
if (cmd == null) {
throw new IllegalArgumentException("cmd cannot be null");
}
if (logger != null) {
logger.info("spawning process " + cmd);
}
String[] ev = getEnv() == null ? null : buildEnv(getEnv());
Process p = Runtime.getRuntime().exec(cmd, ev, getWorkingDir());
return p;
}
/**
* Spawn a process to execute the given command
*
* @return an object representing the process
*/
protected Process spawnProcess(String[] cmd) throws IllegalArgumentException, IOException {
if (cmd == null) {
throw new IllegalArgumentException("cmd cannot be null");
}
if (logger != null) {
logger.info("spawning process " + Arrays.toString(cmd));
}
String[] ev = getEnv() == null ? null : buildEnv(getEnv());
Process p = Runtime.getRuntime().exec(cmd, ev, getWorkingDir());
return p;
}
private static String[] buildEnv(Map<String,String> ev) {
String[] result = new String[ev.size()];
int i = 0;
for (Iterator<Map.Entry<String,String>> it = ev.entrySet().iterator(); it.hasNext();) {
Map.Entry<String,String> e = it.next();
result[i++] = e.getKey() + "=" + e.getValue();
}
return result;
}
protected Thread drainStdOut(Process p) {
final BufferedInputStream out = new BufferedInputStream(p.getInputStream(), BUFFER_SIZE);
Thread result = new Drainer(p) {
@Override
void drain() throws IOException {
drainAndPrint(out, System.out);
}
@Override
void blockingDrain() throws IOException {
blockingDrainAndPrint(out, System.out);
}
};
result.start();
return result;
}
protected Drainer captureStdOut(Process p) {
final BufferedInputStream out = new BufferedInputStream(p.getInputStream(), BUFFER_SIZE);
final ByteArrayOutputStream b = new ByteArrayOutputStream(BUFFER_SIZE);
Drainer result = new Drainer(p) {
@Override
void drain() throws IOException {
drainAndCatch(out, b);
}
@Override
void blockingDrain() throws IOException {
blockingDrainAndCatch(out, b);
}
};
result.setCapture(b);
result.start();
return result;
}
protected Thread drainStdErr(Process p) {
final BufferedInputStream err = new BufferedInputStream(p.getErrorStream(), BUFFER_SIZE);
Thread result = new Drainer(p) {
@Override
void drain() throws IOException {
drainAndPrint(err, System.err);
}
@Override
void blockingDrain() throws IOException {
blockingDrainAndPrint(err, System.err);
}
};
result.start();
return result;
}
protected Drainer captureStdErr(Process p) {
final BufferedInputStream out = new BufferedInputStream(p.getErrorStream(), BUFFER_SIZE);
final ByteArrayOutputStream b = new ByteArrayOutputStream(BUFFER_SIZE);
Drainer result = new Drainer(p) {
@Override
void drain() throws IOException {
drainAndCatch(out, b);
}
@Override
void blockingDrain() throws IOException {
blockingDrainAndCatch(out, b);
}
};
result.setCapture(b);
result.start();
return result;
}
/**
* A thread that runs in a loop, performing the drain() action until a process terminates
*/
protected abstract class Drainer extends Thread {
// how many ms to sleep before waking up to check the streams?
private static final int SLEEP_MS = 5;
private final Process p;
private ByteArrayOutputStream capture;
/**
* Drain data from the stream, but don't block.
*/
abstract void drain() throws IOException;
/**
* Drain data from the stream until it is finished. Block if necessary.
*/
abstract void blockingDrain() throws IOException;
Drainer(Process p) {
this.p = p;
}
@Override
public void run() {
try {
boolean repeat = true;
while (repeat) {
try {
Thread.sleep(SLEEP_MS);
} catch (InterruptedException e1) {
// e1.printStackTrace();
// just ignore and continue
}
drain();
try {
p.exitValue();
// if we get here, the process has terminated
repeat = false;
blockingDrain();
if (logger != null) {
logger.fine("process terminated with exit code " + p.exitValue());
}
} catch (IllegalThreadStateException e) {
// this means the process has not yet terminated.
repeat = true;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public ByteArrayOutputStream getCapture() {
return capture;
}
public void setCapture(ByteArrayOutputStream capture) {
this.capture = capture;
}
}
/**
* Drain some data from the input stream, and print said data to p. Do not block.
*/
private static void drainAndPrint(BufferedInputStream s, PrintStream p) {
try {
while (s.available() > 0) {
byte[] data = new byte[s.available()];
s.read(data);
p.print(new String(data));
}
} catch (IOException e) {
// assume the stream has been closed (e.g. the process died)
// so, just exit
}
}
/**
* Drain all data from the input stream, and print said data to p. Block if necessary.
*/
private static void blockingDrainAndPrint(BufferedInputStream s, PrintStream p) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
try {
// gather all the data from the stream.
int next = s.read();
while (next != -1) {
b.write(next);
next = s.read();
}
} catch (IOException e) {
// assume the stream has been closed (e.g. the process died)
// so, just print the data and then leave
}
// print the data.
p.print(b.toString());
}
/**
* Drain some data from the input stream, and append said data to b. Do not block.
*/
private static void drainAndCatch(BufferedInputStream s, ByteArrayOutputStream b) {
try {
while (s.available() > 0) {
byte[] data = new byte[s.available()];
int nRead = s.read(data);
b.write(data, 0, nRead);
}
} catch (IOException e) {
// assume the stream has been closed (e.g. the process died)
// so, just exit
}
}
/**
* Drain all data from the input stream, and append said data to p. Block if necessary.
*/
private static void blockingDrainAndCatch(BufferedInputStream s, ByteArrayOutputStream b) {
try {
int next = s.read();
while (next != -1) {
b.write(next);
next = s.read();
}
} catch (IOException e) {
// assume the stream has been closed (e.g. the process died)
// so, just exit
}
}
public boolean isCaptureOutput() {
return captureOutput;
}
public boolean isCaptureErr() {
return captureErr;
}
public byte[] getStdOut() {
return stdOut;
}
public byte[] getStderr() {
return stdErr;
}
protected void setStdOut(byte[] newOutput) {
stdOut = newOutput;
}
protected void setStdErr(byte[] newErr) {
stdErr = newErr;
}
public byte[] getInput() {
return input;
}
/**
* Set input which will be fed to the launched process's stdin
*/
public void setInput(byte[] input) {
this.input = input;
}
}