/* * 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 com.mucommander.commons.file.AbstractFile; import com.mucommander.commons.file.FileFactory; import com.mucommander.commons.file.protocol.local.LocalFile; import java.io.IOException; import java.util.StringTokenizer; /** * Used to run process in as safe a manner as possible. * <p> * The Java process API, while very simple, contains a lot of pitfalls and requires some work to use properly. * Typical errors are forgetting to monitor a process' output streams, which will make it deadlock more often than not. * </p> * <p> * Using the <code>ProcessRunner</code> will take care of all these tasks, while still allowing most of the flexibility * of the standard API. * </p> * @author Nicolas Rinaudo */ public class ProcessRunner { // - Initialisation ------------------------------------------------------ // ----------------------------------------------------------------------- /** * Prevents instances of ProcessRunner from being created. */ private ProcessRunner() {} // - Process running ----------------------------------------------------- // ----------------------------------------------------------------------- /** * Executes the specified command in the specified directory. * <p> * Note that both <code>currentDirectory</code> and <code>listener</code> can be set to <code>null</code>.<br> * If no current directory is specified, the VM's current directory will be used. Moreover, if the current directory * is not on residing on local file system, the user's home directory will be used instead. Finally, if the current * directory is on a local file system but is actually not a {@link AbstractFile#isDirectory() directory} * (an archive entry for instance), the first file's parent that is an actual directory will be used. * <br> * If <code>listener</code> is set to <code>null</code>, nobody will be notified of the process' state. Its streams * will still be emptied to prevent deadlocks. * </p> * @param tokens tokens that compose the command to execute. * @param currentDirectory directory in which to execute the process (user directory if <code>null</code>). * @param listener object that will be notified of modifications in the process' state (ignored if <code>null</code>). * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @return the generated process. * @throws IOException thrown if any error occurs while creating the process. */ public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory, ProcessListener listener, String encoding) throws IOException { AbstractProcess process; // If currentDirectory is null, use the VM's current directory. if(currentDirectory == null) { currentDirectory = FileFactory.getFile(System.getProperty("user.dir"), true); } else { // If currentDirectory is not on a local filesytem, use the user's home. if(!currentDirectory.hasAncestor(LocalFile.class)) { currentDirectory = FileFactory.getFile(System.getProperty("user.home"), true); } // If currentDirectory is not a directory (e.g. an archive entry) else { while(currentDirectory!=null && !currentDirectory.isDirectory()) currentDirectory = currentDirectory.getParent(); // This shouldn't normally happen if(currentDirectory==null) currentDirectory = FileFactory.getFile(System.getProperty("user.dir"), true); } } // // Register a debug process listener. // if(listener == null) // listener = new DebugProcessListener(tokens); // Starts the process. process = new LocalProcess(tokens, (java.io.File)currentDirectory.getUnderlyingFileObject()); process.startMonitoring(listener, encoding); return process; } // - Helper methods ------------------------------------------------------ // ----------------------------------------------------------------------- /** * Executes the specified command in the specified directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(command, currentDirectory, listener, null)</code>. * </p> * @param command command to execute. * @param currentDirectory directory in which to execute the process (user directory if <code>null</code>). * @param listener object that will be notified of modifications in the process' state (ignored if <code>null</code>). * @return the generated process. * @throws IOException thrown if any error occurs while creating the process. */ public static AbstractProcess execute(String command, AbstractFile currentDirectory, ProcessListener listener) throws IOException {return execute(command, currentDirectory, listener, null);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(command, null, null, null)</code>. * </p> * @param command command to execute. * @return the generated process. * @see #execute(String,AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String command) throws IOException {return execute(command, null, null, null);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(command, null, null, encoding)</code>. * </p> * @param command command to execute. * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @return the generated process. * @see #execute(String,AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String command, String encoding) throws IOException {return execute(command, null, null, encoding);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(command, null, listener, null)</code>. * </p> * @param command command to execute. * @param listener object that will be notified of any modification in the process' state (ignored if <code>null</code>). * @return the generated process. * @see #execute(String,AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String command, ProcessListener listener) throws IOException {return execute(command, null, listener, null);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(command, null, listener, encoding)</code>. * </p> * @param command command to execute. * @param listener object that will be notified of any modification in the process' state. * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @return the generated process. * @see #execute(String,AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String command, ProcessListener listener, String encoding) throws IOException {return execute(command, null, listener, encoding);} /** * Executes the specified command in the specified directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(command, currentDirectory, null, null)</code>. * </p> * @param command command to execute. * @param currentDirectory directory in which to run the command. * @return the generated process. * @see #execute(String,AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String command, AbstractFile currentDirectory) throws IOException {return execute(command, currentDirectory, null, null);} /** * Executes the specified command in the specified directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(command, currentDirectory, null, encoding)</code>. * </p> * @param command command to execute. * @param currentDirectory directory in which to run the command (uses the VM's current directory if <code>null</code>). * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @return the generated process. * @see #execute(String,AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String command, AbstractFile currentDirectory, String encoding) throws IOException {return execute(command, currentDirectory, null, encoding);} /** * Executes the specified command in the specified directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, null, encoding)</code> where <code>tokens</code> * is an array contains all the tokens found in <code>command</code>. * </p> * <p> * More precisely, the <code>command</code> string is broken into tokens using a <code>StringTokenizer</code> created by the call * <code>new StringTokenizer(command)</code> with no further modification of the character categories. The tokens produced by the * tokenizer are then placed in the new string array <code>tokens</code>, in the same order. * </p> * @param command command to execute. * @param currentDirectory directory in which to run the command (uses the VM's current directory if <code>null</code>). * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @param listener object that will be notified of modifications in the process' state (ignored if <code>null</code>). * @return the generated process. * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String command, AbstractFile currentDirectory, ProcessListener listener, String encoding) throws IOException { StringTokenizer parser; // Used to parse the command. String[] tokens; // Tokens that make up the command. // Initialisation. parser = new StringTokenizer(command); tokens = new String[parser.countTokens()]; // Breaks command into tokens. for(int i = 0; i < tokens.length; i++) tokens[i] = parser.nextToken(); // Starts the process. return execute(tokens, currentDirectory, listener, encoding); } /** * Executes the specified command in the specified directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, listener, null)</code>. * </p> * @param tokens command to execute. * @param currentDirectory directory in which to run the command (uses the VM's current directory if <code>null</code>). * @param listener object that will be notified of any modification in the process' state. * @return the generated process. * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory, ProcessListener listener) throws IOException {return execute(tokens, currentDirectory, listener, null);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, null, null)</code>. * </p> * @param tokens command to execute. * @return the generated process. * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String[] tokens) throws IOException {return execute(tokens, null, null, null);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, null, encoding)</code>. * </p> * @param tokens command to execute. * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @return the generated process. * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String[] tokens, String encoding) throws IOException {return execute(tokens, null, null, encoding);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, listener, null)</code>. * </p> * @param tokens command to execute. * @param listener object that will be notified of any modification in the process' state (ignored if <code>null</code>). * @return the generated process. * @see #execute(String[],AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String[] tokens, ProcessListener listener) throws IOException {return execute(tokens, null, listener, null);} /** * Executes the specified command in the VM's current directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, null, listener, encoding)</code>. * </p> * @param tokens command to execute. * @param listener object that will be notified of any modification in the process' state (ignored if <code>null</code>). * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @return the generated process. * @see #execute(String[],AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String[] tokens, ProcessListener listener, String encoding) throws IOException {return execute(tokens, null, listener, encoding);} /** * Executes the specified command in the specified directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, null, null)</code>. * </p> * @param tokens command to execute. * @param currentDirectory directory in which to run the command. * @return the generated process. * @see #execute(String[],AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory) throws IOException {return execute(tokens, currentDirectory, null, null);} /** * Executes the specified command in the specified directory. * <p> * This is a convenience method and behaves exactly as a call to <code>execute(tokens, currentDirectory, null, null)</code>. * </p> * @param tokens command to execute. * @param currentDirectory directory in which to run the command. * @param encoding encoding used to read from the process' stream (system default is used if <code>null</code>). * @return the generated process. * @see #execute(String[],AbstractFile,ProcessListener,String) * @throws IOException thrown if an error happens while starting the process. */ public static AbstractProcess execute(String[] tokens, AbstractFile currentDirectory, String encoding) throws IOException {return execute(tokens, currentDirectory, null, encoding);} }