net.brtly.monkeyboard.shell.ShellCommand.java Source code

Java tutorial

Introduction

Here is the source code for net.brtly.monkeyboard.shell.ShellCommand.java

Source

/*******************************************************************************
 * This file is part of MonkeyBoard
 * Copyright  2013 Oliver Bartley
 * 
 * MonkeyBoard 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.
 * 
 * MonkeyBoard 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 MonkeyBoard.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package net.brtly.monkeyboard.shell;

import java.io.IOException;

import net.brtly.monkeyboard.shell.StreamMonitor.StreamListener;
import net.brtly.monkeyboard.shell.StreamMonitor.StreamType;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Defines a shell command to be run that can be added to a thread pool for
 * asynchronous execution, or executed on the current thread with an optional
 * timeout
 * 
 * @author obartley
 * 
 */
public class ShellCommand implements Runnable {
    /**
     * Objects of this type can receive events from a running command
     * 
     * @author obartley
     */
    public class ShellCommandListener {
        public void onStdOut(ShellCommand command, String line) {
        };

        public void onStdErr(ShellCommand command, String line) {
        };

        public void onExit(ShellCommand command, int status) {
        };
    }

    /**
     * Basic subclass of CommandEventListener that prints output to
     * STDOUT/STDERR
     * 
     * @author obartley
     * 
     */
    public class StandardShelCommandListener extends ShellCommandListener {
        @Override
        public void onStdOut(ShellCommand command, String line) {
            System.out.println(command.getCommand() + ":" + line);
        }

        @Override
        public void onStdErr(ShellCommand command, String line) {
            System.err.println(command.getCommand() + ":" + line);
        }
    }

    /**
     * Thread class to be used as a worker to timeout ShellCommands
     */
    private static class Worker extends Thread {
        private final Process process;
        private Integer exitValue;

        Worker(final Process process) {
            this.process = process;
        }

        public Integer getExitValue() {
            return exitValue;
        }

        @Override
        public void run() {
            try {
                LOG.trace(this.getName());
                exitValue = process.waitFor();
            } catch (InterruptedException ignore) {
                LOG.trace("interrupted!");
                return;
            }
            LOG.trace("exited (" + Integer.toString(exitValue) + ")");
        }
    }

    protected static final Log LOG = LogFactory.getLog(ShellCommand.class);

    private String _command;

    private boolean _isRunning = false;
    private String _output = null;
    private String _error = null;
    private Integer _status = 0;

    private ShellCommandListener _listener;

    protected ShellCommand() {
    };

    /**
     * Initialize an object that represents a single shell command
     * 
     * @param A
     *            String containing the full command to be executed
     */
    public ShellCommand(String command) {
        _command = command.trim();
        _listener = new ShellCommandListener();
    }

    /**
     * Set the CommandEventListener that will receive output events
     * 
     * @param listener
     *            the CommandEventListener that will receive output events
     */
    public void setListener(ShellCommandListener listener) {
        synchronized (_listener) {
            _listener = listener;
        }
    }

    /**
     * Get the CommandEventListener that will receive output events
     * 
     * @return the CommandEventListener that will receive output events
     */
    public ShellCommandListener getListener() {
        synchronized (_listener) {
            return _listener;
        }
    }

    /**
     * Get the full command, including the path to the adb executable
     * 
     * @return
     */
    public String getCommand() {
        return _command;
    }

    /**
     * Gets the output of the command
     * 
     * @return the output of the command once it has run, null otherwise
     */
    public String getOutput() {
        return _output;
    }

    /**
     * Gets the error output of the command
     * 
     * @return the error output of the command once it has run, null otherwise
     */
    public String getError() {
        return _error;
    }

    /**
     * Gets the exit status of the command once it has run
     * 
     * @return null if the command has not been run or if an exception was
     *         encountered while running, otherwise the command's exit status
     */
    public Integer getStatus() {
        return _status;
    }

    /**
     * Determine if the command is currently being executed
     * 
     * @return
     */
    public boolean isRunning() {
        return _isRunning;
    }

    @Override
    public void run() {
        execute(0);
    }

    /**
     * Execute the command on the current thread with a timeout
     * @param timeOut milliseconds for this command to timeout. A timeout of 0 means to wait forever 
     * @return the exit status of the command, or null if there was an exception
     */
    public Integer execute(final long timeOut) {
        _isRunning = true;
        _status = -1;
        _output = null;
        _error = null;
        final ShellCommand sender = this;
        final String command = getCommand();
        _isRunning = true;
        Process proc = null;
        try {
            // create the process which will run the command
            proc = Runtime.getRuntime().exec(command);

            // consume and record the error and output streams
            final StringBuilder out = new StringBuilder();
            final StringBuilder err = new StringBuilder();
            StreamListener streamListener = new StreamListener() {
                @Override
                public void onOutput(StreamType type, String output) {
                    if (output != null) {
                        switch (type) {
                        case STDOUT:
                            out.append(output + '\n');
                            _output = out.toString();
                            getListener().onStdOut(sender, output);
                            break;
                        case STDERR:
                            err.append(output + '\n');
                            _error = err.toString();
                            getListener().onStdErr(sender, output);
                        }
                    }
                }
            };
            StreamMonitor osm = new StreamMonitor(proc.getInputStream(), StreamType.STDOUT, streamListener);
            StreamMonitor esm = new StreamMonitor(proc.getErrorStream(), StreamType.STDERR, streamListener);
            osm.start();
            esm.start();

            // create and start a Worker thread which this thread will join for
            // the timeout period
            Worker worker = new Worker(proc);
            worker.setName(command);
            worker.start();
            try {
                worker.join(timeOut);
                _status = worker.getExitValue();
                if (_status == null) {
                    // if we get this far then we never got an exit value from the
                    // worker thread as a result of a timeout
                    String errorMessage = "The command [" + command + "] timed out.";
                    LOG.warn(errorMessage);
                }

                //            throw new RuntimeException(errorMessage);
            } catch (InterruptedException ex) {
                worker.interrupt();
                Thread.currentThread().interrupt();
                //            throw ex;
            }
        } catch (IOException ex) {
            String errorMessage = "The command [" + command + "] did not complete due to an IO error.";
            LOG.warn(errorMessage, ex);
            //         throw new RuntimeException(errorMessage, ex);
        }

        return _status;
    }
}