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    }