package chatty.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Start a process with the given command and send the resulting output to the
* listener.
*
* @author tduva
*/
public class Proc extends Thread {
private final static Logger LOGGER = Logger.getLogger(Proc.class.getName());
private final String command;
private final ProcListener listener;
private Process process;
private final String label;
private final long created;
/**
* Create a new process.
*
* @param command The command and parameters
* @param listener The listener to monitor the process state and output
* @param label Label for debug output
*/
public Proc(String command, ProcListener listener, String label) {
this.command = command;
this.listener = listener;
this.label = label;
this.created = System.currentTimeMillis();
}
public String getCommand() {
return command;
}
@Override
public void run() {
try {
Runtime rt = Runtime.getRuntime();
String[] cmd = split(command);
Process p = rt.exec(cmd);
this.process = p;
LOGGER.info(String.format("[%s] Process %s started. [%s]",
label, id(), command));
listener.processStarted(this);
// Read both output streams (output of the process, so input), so
// the process keeps running and to output it's output
InputStreamHelper errorStream = new InputStreamHelper(p.getErrorStream());
InputStreamHelper inputStream = new InputStreamHelper(p.getInputStream());
errorStream.start();
inputStream.start();
int exitValue = p.waitFor();
errorStream.join(1000);
inputStream.join(1000);
listener.processFinished(this, exitValue);
LOGGER.info(String.format("[%s] Process %s finished.",
label, id()));
} catch (IOException ex) {
listener.message(this, "Error: " + ex);
LOGGER.warning(String.format(
"[%s] Error starting process / %s", label, ex));
} catch (InterruptedException ex) {
listener.message(this, "Error: " + ex);
LOGGER.warning(String.format(
"[%s] %s", label, ex));
}
}
private String id() {
return Integer.toHexString(process.hashCode());
}
public void kill() {
LOGGER.info(String.format("[%s] Killing Process %s", label, id()));
process.destroy();
}
@Override
public String toString() {
return String.format("[%s] %s (%s)", label, command, DateTime.agoSingleVerbose(created));
}
public static interface ProcListener {
public void processStarted(Proc process);
public void message(Proc process, String message);
public void processFinished(Proc process, int exitValue);
}
/**
* Reads the given stream in it's own thread and outputs it.
*/
private class InputStreamHelper extends Thread {
private final InputStream input;
InputStreamHelper(InputStream input) {
this.input = input;
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
String line;
while ((line = reader.readLine()) != null) {
listener.message(Proc.this, line);
LOGGER.info(String.format("[%s] (%s): %s", label, id(), line));
}
} catch (IOException ex) {
listener.message(Proc.this, "Error: " + ex);
LOGGER.warning(String.format("[%s] (%s): Error reading stream / %s",
label, id(), ex));
}
}
}
/**
* Splits up a line of text into tokens by spaces, ignoring spaces for parts
* that are surrounded by quotes.
*
* <p>
* This can be used for splitting up parameters.</p>
*
* @param input The line of text to tokenize
* @return An array of tokens (tokens in quotes may be empty)
*/
private static String[] split(String input) {
List<String> result = new ArrayList<>();
Matcher m = Pattern.compile("\"([^\"]*)\"|([^\"\\s]+)").matcher(input);
while (m.find()) {
if (m.group(1) != null) {
result.add(m.group(1));
} else {
result.add(m.group(2));
}
}
return result.toArray(new String[result.size()]);
}
public static void main(String[] args) {
String[] split = split("abc -d \"abc jfwe\" fwe\"hmm\"");
System.out.println(Arrays.asList(split));
}
}