/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.process; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * muCommander specific version of a process, allowing various types of processes to be executed. * <p> * Unlike normal instances of <code>java.lang.Process</code>, abstract processes * will empty their own streams, preventing deadlocks from occuring on some systems. * </p> * <p> * Note that abstract processes should not be created directly. They should be * instantiated through {@link com.mucommander.process.ProcessRunner#execute(String[], com.mucommander.commons.file.AbstractFile,ProcessListener)}. * </p> * @author Nicolas Rinaudo */ public abstract class AbstractProcess { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractProcess.class); // - Instance fields ------------------------------------------------------- // ------------------------------------------------------------------------- /** Stdout monitor. */ private ProcessOutputMonitor stdoutMonitor; /** Stderr monitor. */ private ProcessOutputMonitor stderrMonitor; // - Process monitoring ---------------------------------------------------- // ------------------------------------------------------------------------- /** * Kills the process. */ public final void destroy() { // Process destruction occurs in a separate thread, as in some (rare) // cases, deadlocks will occur while trying to kill a native process. // An example of that is executing <code>echo blah | ssh localhost ls -l</code> // under MAC OS X. // Using a separate thread allows muCommander to continue working properly even // when that occurs. new Thread() { @Override public void run() { // Closes the process' streams. LOGGER.debug("Destroying process..."); stdoutMonitor.stopMonitoring(); if(stderrMonitor != null) stderrMonitor.stopMonitoring(); // Destroys the process. try { destroyProcess(); } catch(IOException e) { LOGGER.debug("IOException caught", e); } } }.start(); } /** * Starts monitoring the process. * @param listener if non <code>null</code>, <code>listener</code> will receive updates about the process' event. * @param encoding encoding that should be used by the process' stdout and stderr streams. */ final void startMonitoring(ProcessListener listener, String encoding) throws IOException { // Only monitors stdout if the process uses merged streams. if(usesMergedStreams()) { LOGGER.debug("Starting process merged output monitor..."); new Thread(stdoutMonitor = new ProcessOutputMonitor(getInputStream(), encoding, listener, this), "Process sdtout/stderr monitor").start(); } // Monitors both stdout and stderr. else { LOGGER.debug("Starting process stdout and stderr monitors..."); new Thread(stdoutMonitor = new ProcessOutputMonitor(getInputStream(), encoding, listener, this), "Process stdout monitor").start(); new Thread(stderrMonitor = new ProcessOutputMonitor(getErrorStream(), encoding, listener), "Process stderr monitor").start(); } } // - Abstract methods ------------------------------------------------------ // ------------------------------------------------------------------------- /** * Returns <code>true</code> if this process only uses one output stream. * <p> * Some processes will use a single stream for their standard error and standard output streams. Such * processes should return <code>true</code> here to prevent both streams from being monitored.<br> * Note that if a process uses merged streams, {@link #getInputStream()} will be monitored. * </p> * @return <code>true</code> if this process merges his output streams, <code>false</code> otherwise. */ public abstract boolean usesMergedStreams(); /** * Makes the current thread wait for the process to die. * @return the process' exit code. * @throws InterruptedException thrown if the current thread is interrupted while wainting on the process to die. * @throws IOException thrown if an error occurs while waiting for the process to die. */ public abstract int waitFor() throws InterruptedException, IOException; /** * Destroys the process. * @throws IOException thrown if an error occurs while destroying the process. */ protected abstract void destroyProcess() throws IOException; /** * Returns this process' exit value. * @return this process' exit value. */ public abstract int exitValue(); /** * Returns the stream used to send data to the process. * @return the stream used to send data to the process. * @throws IOException thrown if an error occurs while retrieving the process' output stream. */ public abstract OutputStream getOutputStream() throws IOException; /** * Returns the process' standard output stream. * @return the process' standard output stream. * @throws IOException thrown if an error occurs while retrieving the process' input stream. */ public abstract InputStream getInputStream() throws IOException; /** * Returns the process' standard error stream. * @return the process' standard error stream. * @throws IOException thrown if an error occurs while retrieving the process' error stream. */ public abstract InputStream getErrorStream() throws IOException; }