Search code examples
javainputstreamapache-commonsoutputstreamapache-commons-exec

Apache Commons exec PumpStreamHandler continuous input


I am trying to solve interaction with command line process using Apache Commons exec. I'm stuck with following code:

ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream ins = new ByteArrayOutputStream();
OutputStreamWriter ow = new OutputStreamWriter(ins);
BufferedWriter writer = new BufferedWriter(ow);
ByteArrayInputStream in = new ByteArrayInputStream(ins.toByteArray());
PumpStreamHandler psh = new PumpStreamHandler(out, null, in);
CommandLine cl = CommandLine.parse(initProcess);
DefaultExecutor exec = new DefaultExecutor();
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
exec.setStreamHandler(psh);
try {
    exec.execute(cl, resultHandler);
    int i = 0;
    while (true) {
        String o = out.toString();
        if (!o.trim().isEmpty()) {
            System.out.println(o);
            out.reset();
        }
        // --- PROBLEM start ---
        if (i == 3) {
            writer.write(internalProcessCommand); 
            // string with or without trailing \n, both tested
            writer.flush();
            writer.close();
            // tested even ins.write(internalProcessCommand.getBytes())
        }
        // --- PROBLEM end ---
        Thread.sleep(3000);
        i++;
    }
} catch (ExecuteException e) {
    System.err.println(e.getMessage());
}

I hope my code is clear. I continuously read out and print it after 3 seconds while clearing stream. Problem is input to in passed to PumpStreamHandler. I need to pass process commands from code itself, continuously and dynamically as if I was interacting with process through CLI. When I simply use System.in as PumpStreamHandler argument, I can write process commands from console fine. How can I manage to have same result passing strings from code?

Edit: I also tried to connect PipedInputStream receiving data from PipedOutputStream, but it seems that data can be read only after closing PipedOutputStream which makes it un-reusable thus I can't achieve interactivity.

Edit 2: Solved myself. Solution in answer below. Howgh. :-)


Solution

  • Okay, I solved this using built-in tools rather than external libraries. I was able to achieve my goal thanks to independant thread which reads Process' InputStream:

    ProcessBuilder builder = new ProcessBuilder(command);
    Process process = builder.start();
    
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
    StreamReader outputReader = new StreamReader(process.getInputStream(), System.out);
    outputReader.start();
    StreamReader err = new StreamReader(process.getErrorStream(), System.err);
    err.start();
    
    for (int i = 0; i < 3; i++) {
        Thread.sleep(5000);
        writer.write(internalProcessCommand + "\n");
        writer.flush();
    }
    writer.write("exit\n");
    writer.flush();
    
    while (process.isAlive()) {
        System.out.println("alive?");
        Thread.sleep(100);
    }
    System.out.println("dead");
    outputReader.shutdown();
    err.shutdown();
    

    StreamReader:

    class StreamReader extends Thread {
    
        private AtomicBoolean running = new AtomicBoolean(false);
        private InputStream in;
        private OutputStream out;
    
        public StreamReader(InputStream in, OutputStream out) {
            this.in = in;
            this.out = out;
            running.set(true);
        }
    
        @Override
        public void run() {
            Scanner scanner = new Scanner(in);
            PrintWriter writer = new PrintWriter(out, true);
            while (running.get()) {
                if (scanner.hasNextLine()) {
                    writer.println(scanner.nextLine());
                }
            }
            scanner.close();
        }
    
        public void shutdown() {
            running.set(false);
        }
    
    }