001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.process;
020
021
022 import com.mucommander.Debug;
023
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.OutputStream;
027
028 /**
029 * muCommander specific version of a process.
030 * <p>
031 * Implementations of this class are used to execute various types of processes.
032 * It can be a {@link com.mucommander.file.impl.local.LocalProcess}, but some types of
033 * {@link com.mucommander.file.AbstractFile abstract files}, such as SFTP files,
034 * allow for commands to be executed.
035 * </p>
036 * <p>
037 * Unlike normal instances of <code>java.lang.Process</code>, abstract processes
038 * will empty their own streams, preventing deadlocks from occuring on some systems.
039 * </p>
040 * <p>
041 * Note that abstract processes should not be created directly. They should be
042 * instanciated through {@link com.mucommander.process.ProcessRunner#execute(String[],com.mucommander.file.AbstractFile,ProcessListener)}.
043 * </p>
044 * @author Nicolas Rinaudo
045 */
046 public abstract class AbstractProcess {
047 // - Instance fields -------------------------------------------------------
048 // -------------------------------------------------------------------------
049 /** Stdout monitor. */
050 private ProcessOutputMonitor stdoutMonitor;
051 /** Stderr monitor. */
052 private ProcessOutputMonitor stderrMonitor;
053
054
055
056 // - Process monitoring ----------------------------------------------------
057 // -------------------------------------------------------------------------
058 /**
059 * Kills the process.
060 */
061 public final void destroy() {
062 // Process destruction occurs in a separate thread, as in some (rare)
063 // cases, deadlocks will occur while trying to kill a native process.
064 // An example of that is executing <code>echo blah | ssh localhost ls -l</code>
065 // under MAC OS X.
066 // Using a separate thread allows muCommander to continue working properly even
067 // when that occurs.
068 new Thread() {
069 public void run() {
070 // Closes the process' streams.
071 if(Debug.ON) Debug.trace("Destroying process...");
072 stdoutMonitor.stopMonitoring();
073 if(stderrMonitor != null)
074 stderrMonitor.stopMonitoring();
075
076 // Destroys the process.
077 try {destroyProcess();}
078 catch(IOException e) {if(Debug.ON) Debug.trace(e);}
079 }
080 }.start();
081 }
082
083 /**
084 * Starts monitoring the process.
085 * @param listener if non <code>null</code>, <code>listener</code> will receive updates about the process' event.
086 * @param encoding encoding that should be used by the process' stdout and stderr streams.
087 */
088 final void startMonitoring(ProcessListener listener, String encoding) throws IOException {
089 // Only monitors stdout if the process uses merged streams.
090 if(usesMergedStreams()) {
091 if(Debug.ON) Debug.trace("Starting process merged output monitor...");
092 new Thread(stdoutMonitor = new ProcessOutputMonitor(getInputStream(), encoding, listener, this), "Process sdtout/stderr monitor").start();
093 }
094 // Monitors both stdout and stderr.
095 else {
096 if(Debug.ON) Debug.trace("Starting process stdout and stderr monitors...");
097 new Thread(stdoutMonitor = new ProcessOutputMonitor(getInputStream(), encoding, listener, this), "Process stdout monitor").start();
098 new Thread(stderrMonitor = new ProcessOutputMonitor(getErrorStream(), encoding, listener), "Process stderr monitor").start();
099 }
100 }
101
102
103
104 // - Abstract methods ------------------------------------------------------
105 // -------------------------------------------------------------------------
106 /**
107 * Returns <code>true</code> if this process only uses one output stream.
108 * <p>
109 * Some processes will use a single stream for their standard error and standard output streams. Such
110 * processes should return <code>true</code> here to prevent both streams from being monitored.<br>
111 * Note that if a process uses merged streams, {@link #getInputStream()} will be monitored.
112 * </p>
113 * @return <code>true</code> if this process merges his output streams, <code>false</code> otherwise.
114 */
115 public abstract boolean usesMergedStreams();
116
117 /**
118 * Makes the current thread wait for the process to die.
119 * @return the process' exit code.
120 * @throws InterruptedException thrown if the current thread is interrupted while wainting on the process to die.
121 * @throws IOException thrown if an error occurs while waiting for the process to die.
122 */
123 public abstract int waitFor() throws InterruptedException, IOException;
124
125 /**
126 * Destroys the process.
127 * @throws IOException thrown if an error occurs while destroying the process.
128 */
129 protected abstract void destroyProcess() throws IOException;
130
131 /**
132 * Returns this process' exit value.
133 * @return this process' exit value.
134 */
135 public abstract int exitValue();
136
137 /**
138 * Returns the stream used to send data to the process.
139 * @return the stream used to send data to the process.
140 * @throws IOException thrown if an error occurs while retrieving the process' output stream.
141 */
142 public abstract OutputStream getOutputStream() throws IOException;
143
144 /**
145 * Returns the process' standard output stream.
146 * @return the process' standard output stream.
147 * @throws IOException thrown if an error occurs while retrieving the process' input stream.
148 */
149 public abstract InputStream getInputStream() throws IOException;
150
151 /**
152 * Returns the process' standard error stream.
153 * @return the process' standard error stream.
154 * @throws IOException thrown if an error occurs while retrieving the process' error stream.
155 */
156 public abstract InputStream getErrorStream() throws IOException;
157
158 }