开发者

design for a wrapper around command-line utilities

开发者 https://www.devze.com 2022-12-23 10:03 出处:网络
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 s

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 t开发者_如何学Che 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") ?


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


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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号