views:

169

answers:

3

im trying to come up with a design for a wrapper for use when invoking command line utilities in java. the trouble with runtime.exec() is that you need to keep reading from the process' out and err streams or it hangs when it fills its buffers. this has led me to the following design:

public class CommandLineInterface {
    private final Thread stdOutThread;
    private final Thread stdErrThread;
    private final OutputStreamWriter stdin;
    private final History history;


    public CommandLineInterface(String command) throws IOException {
        this.history = new History();
        this.history.addEntry(new HistoryEntry(EntryTypeEnum.INPUT, command));
        Process process = Runtime.getRuntime().exec(command);
        stdin = new OutputStreamWriter(process.getOutputStream());
        stdOutThread = new Thread(new Leech(process.getInputStream(), history, EntryTypeEnum.OUTPUT));
        stdOutThread.setDaemon(true);
        stdOutThread.start();
        stdErrThread = new Thread(new Leech(process.getErrorStream(), history, EntryTypeEnum.ERROR));
        stdErrThread.setDaemon(true);
        stdErrThread.start();
    }

    public void write(String input) throws IOException {
        this.history.addEntry(new HistoryEntry(EntryTypeEnum.INPUT, input));
        stdin.write(input);
        stdin.write("\n");
        stdin.flush();
    }
}

And

public class Leech implements Runnable{
    private final InputStream stream;
    private final History history;
    private final EntryTypeEnum type;
    private volatile boolean alive = true;


    public Leech(InputStream stream, History history, EntryTypeEnum type) {
        this.stream = stream;
        this.history = history;
        this.type = type;
    }

    public void run() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        String line;
        try {
            while(alive) {
                line = reader.readLine();
                if (line==null) break;
                history.addEntry(new HistoryEntry(type, line));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

my issue is with the Leech class (used to "leech" the process' out and err streams and feed them into history - which acts like a log file) - on the one hand reading whole lines is nice and easy (and what im currently doing), but it means i miss the last line (usually the prompt line). i only see the prompt line when executing the next command (because there's no line break until that point). on the other hand, if i read characters myself, how can i tell when the process is "done" ? (either complete or waiting for input) has anyone tried something like waiting 100 millis since the last output from the process and declaring it "done" ?

any better ideas on how i can implement a nice wrapper around things like runtime.exec("cmd.exe") ?

A: 

I was looking for the same thing myself, and I found a Java port of Expect, called ExpectJ. I haven't tried it yet, but it looks promising

Kim
interesting, but it doesnt appear to handle errors. if i "expect" a certain output from a run and get some error printed out, ill only know about it when it times out ...
hatchetman82
A: 

I would read the input in with the stream and then write it into a ByteArrayOutputStream. The byte array will continue to grow until there are no longer any available bytes to read. At this point you will flush the data to history by converting the byte array into a String and splitting it on the platform line.separator. You can then iterate over the lines to add history entries. The ByteArrayOutputStream is then reset and the while loop blocks until there is more data or the end of stream is reached (probably because the process is done).

public void run() {
    ByteArrayOutputStream out = new ByteArrayOutputStream();

    int bite;
    try {
        while((bite = stream.read()) != -1) {
            out.write(bite);

            if (stream.available() == 0) {
                String string = new String(out.toByteArray());
                for (String line : string.split(
                        System.getProperty("line.separator"))) {
                    history.addEntry(new HistoryEntry(type, line));
                }

                out.reset();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

This will make sure you pick up that last line of input and it solves your problem of knowing when the stream is ended.

rancidfishbreath
youre thinking one-off process eexecution, where i run a command, wait for its output and thats it.i'd like to be able to handle more complex command line applications, like wget etc that may not write anything for a relatively long time, and may not be one-off
hatchetman82
+1  A: 

Use PlexusUtils it is used by Apache Maven 2 to execute all external processes.

adrian.tarau
not exactly what i was looking for, but probably as close as im going to get.thank you
hatchetman82
I had no issues with this library and since it is trusted to be used in Maven 2 I would say is as good as it gets.
adrian.tarau
i didnt mean to imply anything about the quality of the code. im just saying that it wasnt designed for any real interaction between the process and the java code - just to run external processes and get back a single result
hatchetman82
English is not my primary language, but the question is actually about that: "any better ideas on how i can implement a nice wrapper around things like runtime.exec("cmd.exe")?" :)
adrian.tarau
The purpose of PlexusUtils(or any other library) is to provide a transparent way to get the output of the process. You can still get the Process object and inject data into the invoked process: http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Process.html#getOutputStream%28%29
adrian.tarau