package com.framsticks.running; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.apache.log4j.Logger; import com.framsticks.params.annotations.AutoAppendAnnotation; import com.framsticks.params.annotations.FramsClassAnnotation; import com.framsticks.params.annotations.ParamAnnotation; import com.framsticks.util.FramsticksException; import com.framsticks.util.Misc; import com.framsticks.util.dispatching.AbstractJoinable; import com.framsticks.util.dispatching.Dispatching; import com.framsticks.util.dispatching.Joinable; import com.framsticks.util.dispatching.JoinableParent; import com.framsticks.util.dispatching.JoinableState; import com.framsticks.util.dispatching.RunAt; import com.framsticks.util.dispatching.Thread; import com.framsticks.util.io.Encoding; @FramsClassAnnotation public class ExternalProcess extends AbstractJoinable implements JoinableParent { private static final Logger log = Logger.getLogger(ExternalProcess.class); protected List arguments = new ArrayList<>(); protected Process process; protected final ProcessBuilder builder = new ProcessBuilder(); protected Thread readerThread = new Thread(); protected PrintWriter input; protected BufferedReader output; protected Integer exitCode; protected final List listeners = new LinkedList<>(); @AutoAppendAnnotation public void addListener(OutputListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * */ public ExternalProcess() { super(); setName("process"); arguments.add(null); builder.redirectErrorStream(true); } /** * @return the command */ @ParamAnnotation public String getCommand() { return arguments.get(0); } /** * @param command the command to set */ @ParamAnnotation public void setCommand(String command) { arguments.set(0, command); } protected void readerTask() { String line; try { try { while ((line = output.readLine()) != null) { log.trace("read line: " + line); synchronized (listeners) { for (OutputListener l : listeners) { l.onLineRead(line); } } } } catch (IOException e) { throw new FramsticksException().msg("failed to read line from output of process").cause(e); } try { exitCode = process.waitFor(); } catch (InterruptedException e) { throw new FramsticksException().msg("failed to wait for process").cause(e); } log.debug("process ended " + this); // process = null; } catch (FramsticksException e) { log.error("exception caught in process " + this, e); } interrupt(); // finish(); } @ParamAnnotation public void setDirectory(String directory) { builder.directory(new File(directory)); } @ParamAnnotation public String getDirectory() { return builder.directory().getName(); } @Override protected void joinableStart() { log.debug("running process with arguments: " + arguments); builder.command(arguments); try { process = builder.start(); input = new PrintWriter(new OutputStreamWriter(process.getOutputStream(), Encoding.getDefaultCharset())); output = new BufferedReader(new InputStreamReader(process.getInputStream(), Encoding.getDefaultCharset())); } catch (IOException e) { throw new FramsticksException().msg("failed to start process").cause(e); } readerThread.dispatch(new RunAt() { @Override public void run() { readerTask(); } }); Dispatching.use(readerThread, this); } @Override public String toString() { return getName() + "[" + Misc.returnNotNull(getCommand(), "?") + "]"; } /** * @return the input */ public PrintWriter getInput() { return input; } @Override protected void joinableInterrupt() { process.destroy(); Dispatching.drop(readerThread, this); // finish(); } @Override @ParamAnnotation public String getName() { return readerThread.getName(); } /** * @param name the name to set */ @ParamAnnotation public void setName(String name) { readerThread.setName(name); } @Override protected void joinableFinish() { } @Override protected void joinableJoin() throws InterruptedException { Dispatching.join(readerThread); } @Override public void childChangedState(Joinable joinable, JoinableState state) { proceedToState(state); } }