package org.codehaus.mojo.unix.util; /* * The MIT License * * Copyright 2009 The Codehaus. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import org.codehaus.mojo.unix.util.line.*; import org.codehaus.plexus.util.*; import java.io.*; import java.util.*; /** * Executes a system command in a similar fasion to backtick (`) in sh and ruby. * <p> * Standard output and error will be send to System.out and System.err by default. * </p> * <p> * Typical usage: * <pre> * new SystemCommand(). * setBaseDir(directory). * setCommand("dpkg"). * addArgument("-b"). * addArgument("deb"). * addArgument(debFileName). * execute(); * </pre> * <p/> * * TODO: Create FileLineConsumer that puts all the output to a file. * * @author <a href="mailto:trygvis@codehaus.org">Trygve Laugstøl</a> * @version $Id$ */ public class SystemCommand { public static interface LineConsumer { void onLine( String line ) throws IOException; } public static class StringBufferLineConsumer implements LineConsumer { public final StringBuffer buffer; public StringBufferLineConsumer() { this( 1024 ); } public StringBufferLineConsumer( int size ) { buffer = new StringBuffer( size ); } public void onLine( String line ) throws IOException { buffer. append( line ). append( AbstractLineStreamWriter.EOL ); } public String toString() { return buffer.toString(); } } public static class StringListLineConsumer implements LineConsumer { public final List<String> strings; public StringListLineConsumer() { this.strings = new LinkedList<String>(); } public StringListLineConsumer( List<String> strings ) { this.strings = strings; } public void onLine( String line ) throws IOException { strings.add( line ); } public List<String> getStrings() { return strings; } } public static class ExecutionResult { public final int exitValue; public final String command; ExecutionResult( int exitValue, String command ) { this.exitValue = exitValue; this.command = command; } public ExecutionResult assertSuccess() throws IOException { if ( exitValue != 0 ) { throw new IOException( "Command '" + command + "' returned a non-null exit code: " + exitValue ); } return this; } public ExecutionResult assertSuccess( String exceptionMessage ) throws IOException { if ( exitValue != 0 ) { throw new IOException( exceptionMessage ); } return this; } } private static final CommandOutputHandler nullOutputHandler = new NullCommandOutputHandler(); private static final CommandOutputHandler DEFAULT_STDERR_OUTPUT_HANDLER = new OutputStreamCommandOutputHandler( System.err ); private static final CommandOutputHandler DEFAULT_STDOUT_OUTPUT_HANDLER = new OutputStreamCommandOutputHandler(System.out ); // ----------------------------------------------------------------------- // Setup // ----------------------------------------------------------------------- private File basedir; private String command; private List<String> arguments; private List<String> environment; private boolean debug; private CommandOutputHandler stderrHandler; private CommandOutputHandler stdoutHandler; public SystemCommand() { arguments = new ArrayList<String>(); } public SystemCommand setCommand( String command ) { this.command = command; return this; } public SystemCommand setBasedir( File basedir ) { this.basedir = basedir; return this; } public SystemCommand addArgument( String argument ) { arguments.add( argument ); return this; } public SystemCommand addArguments( List<String> strings ) { arguments.addAll(strings); return this; } public SystemCommand addArgumentIf( boolean b, String argument ) { if ( !b ) { return this; } arguments.add( argument ); return this; } public SystemCommand addArgumentIfNotEmpty(String stringToCheck, String argument) { return addArgumentIf( StringUtils.isEmpty( stringToCheck ), argument ); } public SystemCommand addArgumentIfNotEmpty( String argument ) { if ( argument == null || argument.trim().length() == 0 ) { return this; } arguments.add( argument ); return this; } public SystemCommand addEnviroment( String variable ) { if ( environment == null ) { environment = new ArrayList<String>(); } environment.add( variable ); return this; } public SystemCommand dumpCommandIf( boolean debug ) { this.debug = debug; return this; } // ----------------------------------------------------------------------- // Stderr // ----------------------------------------------------------------------- public SystemCommand withNoStderrConsumer() { return setStderrCommandOutputHandler( nullOutputHandler ); } public SystemCommand withNoStderrConsumerIf( boolean flag ) { return flag ? setStderrCommandOutputHandler( nullOutputHandler ) : this; } public SystemCommand withNoStderrConsumerUnless( boolean flag ) { return withNoStderrConsumerIf( !flag ); } public SystemCommand withStderrConsumer( OutputStream consumer ) { return setStderrCommandOutputHandler( new OutputStreamCommandOutputHandler( consumer ) ); } public SystemCommand withStderrConsumerUnless( OutputStream consumer, boolean flag ) { return !flag ? withStderrConsumer( consumer ) : setStderrCommandOutputHandler( nullOutputHandler ); } public SystemCommand withStderrConsumer( LineConsumer consumer ) { return setStderrCommandOutputHandler( new LineConsumerCommandOutputHandler( consumer ) ); } public SystemCommand withStderrConsumerUnless( LineConsumer consumer, boolean flag ) { return !flag ? withStderrConsumer( consumer ) : setStderrCommandOutputHandler( nullOutputHandler ); } // ----------------------------------------------------------------------- // Stdout // ----------------------------------------------------------------------- public SystemCommand withNoStdoutConsumer() { return setStdoutCommandOutputHandler( nullOutputHandler ); } public SystemCommand withNoStdoutConsumerIf( boolean flag ) { return flag ? setStdoutCommandOutputHandler( nullOutputHandler ) : this; } public SystemCommand withNoStdoutConsumerUnless( boolean flag ) { return withNoStdoutConsumerIf( !flag ); } public SystemCommand withStdoutConsumer( OutputStream consumer ) { return setStdoutCommandOutputHandler( new OutputStreamCommandOutputHandler( consumer ) ); } public SystemCommand withStdoutConsumerUnless( OutputStream consumer, boolean flag ) { return !flag ? withStdoutConsumer( consumer ) : setStdoutCommandOutputHandler( nullOutputHandler ); } public SystemCommand withStdoutConsumer( LineConsumer consumer ) { return setStdoutCommandOutputHandler( new LineConsumerCommandOutputHandler( consumer ) ); } public SystemCommand withStdoutConsumerUnless( LineConsumer consumer, boolean flag ) { return !flag ? withStdoutConsumer( consumer ) : setStdoutCommandOutputHandler( nullOutputHandler ); } private SystemCommand setStderrCommandOutputHandler( CommandOutputHandler outputHandler ) { if ( stderrHandler != null ) { throw new RuntimeException( "There can only be one consumer." ); } this.stderrHandler = outputHandler; return this; } private SystemCommand setStdoutCommandOutputHandler( CommandOutputHandler outputHandler ) { if ( stdoutHandler != null ) { throw new RuntimeException( "There can only be one consumer." ); } this.stdoutHandler = outputHandler; return this; } public ExecutionResult execute() throws IOException { if ( basedir == null ) { basedir = new File( "/" ).getAbsoluteFile(); } if ( command == null ) { throw new IOException( "Missing field 'command'" ); } if ( debug ) { System.err.println( "Executing '" + command + "' with arguments (one argument per line):" ); for ( String argument : arguments ) { System.err.println( argument ); } System.err.println( "Executing command in directory: " + basedir ); } if ( !basedir.isDirectory() ) { throw new IOException( "Basedir must be a directory: '" + basedir + "'." ); } arguments.add( 0, command ); return new Execution( command, arguments, environment, basedir, debug, stderrHandler != null ? stderrHandler : DEFAULT_STDERR_OUTPUT_HANDLER, stdoutHandler != null ? stdoutHandler : DEFAULT_STDOUT_OUTPUT_HANDLER).run(); } // ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- /** * Utility method to check if a command is available. * * Executes "which" and asserts that the file exist. It does not check if the file is executable. */ public static boolean available( String command ) { try { StringBufferLineConsumer stdout = new StringBufferLineConsumer(); new SystemCommand().setCommand( "which" ). // dumpCommandIf( true ). addArgument( command ). withStdoutConsumer( stdout ). execute(). assertSuccess(); // System.out.println( "stdout = " + stdout ); // System.out.println( "new File( stdout.toString() ).canRead() = " + new File( stdout.toString() ).canRead() ); // System.out.println( "new File( stdout.toString() ).getCanonicalFile().canRead() = " + new File( stdout.toString() ).getCanonicalFile().canRead() ); return new File( stdout.toString() ).getCanonicalFile().canRead(); } catch ( IOException e ) { return false; } } // ----------------------------------------------------------------------- // Private // ----------------------------------------------------------------------- private static class Execution { private final String command; private final List<String> arguments; private final List<String> environment; private final File basedir; private final boolean debug; private final CommandOutputHandler stderrHandler; private final CommandOutputHandler stdoutHandler; public Process process; private Execution( String command, List<String> arguments, List<String> environment, File basedir, boolean debug, CommandOutputHandler stderrHandler, CommandOutputHandler stdoutHandler ) { this.command = command; this.arguments = arguments; this.environment = environment; this.basedir = basedir; this.debug = debug; this.stderrHandler = stderrHandler; this.stdoutHandler = stdoutHandler; } public ExecutionResult run() throws IOException { String[] args = arguments.toArray( new String[arguments.size()] ); String[] env = null; if ( environment != null ) { env = environment.toArray( new String[environment.size()] ); } process = Runtime.getRuntime().exec( args, env, basedir ); process.getOutputStream().close(); // Close stdin stderrHandler.setup( command + ": stderr", process.getErrorStream() ); stdoutHandler.setup( command + ": stdout", process.getInputStream() ); try { process.waitFor(); } catch ( InterruptedException e ) { IOException ex = new IOException( "Interrupted while waiting for process" ); ex.initCause( e ); throw ex; } stderrHandler.join(); stdoutHandler.join(); int exitValue = process.exitValue(); if ( debug ) { System.out.println( "Command completed: " + command + ", exit value: " + exitValue ); } return new ExecutionResult( exitValue, command ); } } private static abstract interface CommandOutputHandler { void setup(String threadName, InputStream inputStream ); void join(); } private static abstract class ThreadCommandOutputHandler implements CommandOutputHandler { private Thread thread; abstract void handle( InputStream inputStream ) throws IOException; public void setup(String threadName, final InputStream inputStream ) { thread = new Thread(new Runnable() { public void run() { try { handle( inputStream ); } catch ( IOException e ) { // ignore and die } finally { IOUtil.close( inputStream ); } } }, threadName); thread.start(); } public void join() { try { if(thread == null) { return; } thread.join(); } catch (InterruptedException e) { // ignore } } } private static class NullCommandOutputHandler implements CommandOutputHandler { public void setup(String threadName, InputStream inputStream) { IOUtil.close( inputStream ); } public void join() { } } private static class OutputStreamCommandOutputHandler extends ThreadCommandOutputHandler { private OutputStream outputStream; private OutputStreamCommandOutputHandler( OutputStream outputStream ) { this.outputStream = outputStream; } public void handle( InputStream inputStream ) throws IOException { IOUtil.copy( inputStream, outputStream ); outputStream.flush(); } } private static class LineConsumerCommandOutputHandler extends ThreadCommandOutputHandler { private LineConsumer lineConsumer; public LineConsumerCommandOutputHandler( LineConsumer lineConsumer ) { this.lineConsumer = lineConsumer; } public void handle( InputStream inputStream ) throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader( inputStream ) ); String line = reader.readLine(); while ( line != null ) { lineConsumer.onLine( line ); line = reader.readLine(); } } } }