/*
Copyright (C) 2004 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.mysql.management.util;
import java.io.File;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
/* this needs a better name */
/**
* Represents a command to be executed by the os, like a command line.
*
* Extends <code>java.util.Thread</code> and thus: May execute within the
* current thread by calling <code>run</code> directly. May be launched as a
* separate thread by calling <code>start</code>.
*
* @author Eric Herman <eric@mysql.com>
* @version $Id: Shell.java,v 1.2 2007-04-22 09:57:54 nambar Exp $
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public interface Shell extends Runnable {
void setEnvironment(String[] envp);
void setWorkingDir(File workingDir);
void addCompletionListener(Runnable listener);
int returnCode();
boolean hasReturned();
void destroyProcess();
String getName();
boolean isAlive();
boolean isDaemon();
void setDaemon(boolean val);
void join();
void start();
public static class Factory {
public Shell newShell(String[] args, String name, PrintStream out,
PrintStream err) {
return new Default(args, name, out, err);
}
}
/*
* We can't extend Thread because join() is final ... why? For the very good
* reason reason of wanting developers to use composition in stead of
* inheritance.
*/
public static final class Default implements Shell {
private Thread me;
private String[] args;
private String[] envp;
private File workingDir;
private PrintStream out;
private PrintStream err;
private Integer returnCode;
private Process p;
private List listeners;
private Exceptions exceptions;
private RuntimeI runtime;
public Default(String[] args, String name, PrintStream out,
PrintStream err) {
this.me = new Thread(this, name);
/* Think of this just as if this were an extension of Thread */
/*
* Don't try to .start() this thread here in the constructor. The
* RULE is: you must not allow any other thread to obtain a
* reference to a partly-constructed object. Since we pass a 'this'
* pointer in, if we were to start the other thread, that very RULE
* could be violated.
*/
this.args = args;
this.out = out;
this.err = err;
this.envp = null;
this.workingDir = null;
this.returnCode = null;
this.listeners = new ArrayList();
this.exceptions = new Exceptions();
this.runtime = new RuntimeI.Default();
}
@Override
public void setEnvironment(String[] envp) {
this.envp = envp;
}
@Override
public void setWorkingDir(File workingDir) {
this.workingDir = workingDir;
}
void setRuntime(RuntimeI runtime) {
this.runtime = runtime;
}
@Override
public void run() {
if (p != null) {
throw new IllegalStateException("Process already running");
}
try {
returnCode = null;
p = runtime.exec(args, envp, workingDir);
captureStdOutAndStdErr();
returnCode = Integer.valueOf(p.waitFor());
} catch (Exception e) {
throw exceptions.toRuntime(e);
} finally {
// p.destroy();
p = null;
for (int i = 0; i < listeners.size(); i++) {
new Thread((Runnable) listeners.get(i)).start();
}
listeners.clear();
}
}
private void captureStdOutAndStdErr() {
InputStream pOut = p.getInputStream();
InputStream pErr = p.getErrorStream();
new StreamConnector(pOut, out, getName() + " std out").start();
new StreamConnector(pErr, err, getName() + " std err").start();
}
@Override
public void addCompletionListener(Runnable listener) {
if (listener == null) {
throw new IllegalArgumentException("Listener is null");
}
listeners.add(listener);
}
@Override
public int returnCode() {
if (!hasReturned()) {
throw new RuntimeException("Process hasn't returned yet");
}
return returnCode.intValue();
}
@Override
public boolean hasReturned() {
return returnCode != null;
}
@Override
public void destroyProcess() {
if (p != null) {
p.destroy();
}
}
@Override
public String getName() {
return me.getName();
}
@Override
public boolean isAlive() {
return me.isAlive();
}
@Override
public boolean isDaemon() {
return me.isDaemon();
}
@Override
public void setDaemon(boolean val) {
me.setDaemon(val);
}
@Override
public void join() {
new Exceptions.VoidBlock() {
@Override
protected void inner() throws InterruptedException {
me.join();
}
}.exec();
}
@Override
public void start() {
me.start();
}
}
public static class Stub implements Shell {
@Override
public void addCompletionListener(Runnable listener) {
throw new NotImplementedException(listener);
}
@Override
public void destroyProcess() {
throw new NotImplementedException();
}
@Override
public String getName() {
throw new NotImplementedException();
}
@Override
public boolean hasReturned() {
throw new NotImplementedException();
}
@Override
public boolean isAlive() {
throw new NotImplementedException();
}
@Override
public boolean isDaemon() {
throw new NotImplementedException();
}
@Override
public void join() {
throw new NotImplementedException();
}
@Override
public int returnCode() {
throw new NotImplementedException();
}
@Override
public void run() {
throw new NotImplementedException();
}
@Override
public void setDaemon(boolean val) {
throw new NotImplementedException(Boolean.valueOf(val));
}
@Override
public void setEnvironment(String[] envp) {
throw new NotImplementedException(envp);
}
@Override
public void setWorkingDir(File workingDir) {
throw new NotImplementedException(workingDir);
}
@Override
public void start() {
throw new NotImplementedException();
}
}
}