| 1 | /* |
| 2 | Copyright (C) 2004 MySQL AB |
| 3 | |
| 4 | This program is free software; you can redistribute it and/or modify |
| 5 | it under the terms of the GNU General Public License version 2 as |
| 6 | published by the Free Software Foundation. |
| 7 | |
| 8 | This program is distributed in the hope that it will be useful, |
| 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | GNU General Public License for more details. |
| 12 | |
| 13 | You should have received a copy of the GNU General Public License |
| 14 | along with this program; if not, write to the Free Software |
| 15 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 16 | |
| 17 | */ |
| 18 | package com.mysql.management.util; |
| 19 | |
| 20 | import java.io.File; |
| 21 | import java.io.InputStream; |
| 22 | import java.io.PrintStream; |
| 23 | import java.util.ArrayList; |
| 24 | import java.util.List; |
| 25 | |
| 26 | /* this needs a better name */ |
| 27 | /** |
| 28 | * Represents a command to be executed by the os, like a command line. |
| 29 | * |
| 30 | * Extends <code>java.util.Thread</code> and thus: May execute within the |
| 31 | * current thread by calling <code>run</code> directly. May be launched as a |
| 32 | * separate thread by calling <code>start</code>. |
| 33 | * |
| 34 | * @author Eric Herman <eric@mysql.com> |
| 35 | * @version $Id: Shell.java,v 1.15 2005/12/01 21:45:31 eherman Exp $ |
| 36 | */ |
| 37 | public interface Shell extends Runnable { |
| 38 | |
| 39 | void setEnvironment(String[] envp); |
| 40 | |
| 41 | void setWorkingDir(File workingDir); |
| 42 | |
| 43 | void addCompletionListener(Runnable listener); |
| 44 | |
| 45 | int returnCode(); |
| 46 | |
| 47 | boolean hasReturned(); |
| 48 | |
| 49 | void destroyProcess(); |
| 50 | |
| 51 | String getName(); |
| 52 | |
| 53 | boolean isAlive(); |
| 54 | |
| 55 | boolean isDaemon(); |
| 56 | |
| 57 | void setDaemon(boolean val); |
| 58 | |
| 59 | void join(); |
| 60 | |
| 61 | void start(); |
| 62 | |
| 63 | public static class Factory { |
| 64 | public Shell newShell(String[] args, String name, PrintStream out, |
| 65 | PrintStream err) { |
| 66 | return new Default(args, name, out, err); |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | /* |
| 71 | * We can't extend Thread because join() is final ... why? For the very good |
| 72 | * reason reason of wanting developers to use composition in stead of |
| 73 | * inheritance. |
| 74 | */ |
| 75 | public static final class Default implements Shell { |
| 76 | private Thread me; |
| 77 | |
| 78 | private String[] args; |
| 79 | |
| 80 | private String[] envp; |
| 81 | |
| 82 | private File workingDir; |
| 83 | |
| 84 | private PrintStream out; |
| 85 | |
| 86 | private PrintStream err; |
| 87 | |
| 88 | private Integer returnCode; |
| 89 | |
| 90 | private Process p; |
| 91 | |
| 92 | private List listeners; |
| 93 | |
| 94 | private Exceptions exceptions; |
| 95 | |
| 96 | private RuntimeI runtime; |
| 97 | |
| 98 | public Default(String[] args, String name, PrintStream out, |
| 99 | PrintStream err) { |
| 100 | this.me = new Thread(this, name); |
| 101 | /* Think of this just as if this were an extension of Thread */ |
| 102 | /* |
| 103 | * Don't try to .start() this thread here in the constructor. The |
| 104 | * RULE is: you must not allow any other thread to obtain a |
| 105 | * reference to a partly-constructed object. Since we pass a 'this' |
| 106 | * pointer in, if we were to start the other thread, that very RULE |
| 107 | * could be violated. |
| 108 | */ |
| 109 | this.args = args; |
| 110 | this.out = out; |
| 111 | this.err = err; |
| 112 | this.envp = null; |
| 113 | this.workingDir = null; |
| 114 | this.returnCode = null; |
| 115 | this.listeners = new ArrayList(); |
| 116 | this.exceptions = new Exceptions(); |
| 117 | this.runtime = new RuntimeI.Default(); |
| 118 | } |
| 119 | |
| 120 | public void setEnvironment(String[] envp) { |
| 121 | this.envp = envp; |
| 122 | } |
| 123 | |
| 124 | public void setWorkingDir(File workingDir) { |
| 125 | this.workingDir = workingDir; |
| 126 | } |
| 127 | |
| 128 | void setRuntime(RuntimeI runtime) { |
| 129 | this.runtime = runtime; |
| 130 | } |
| 131 | |
| 132 | public void run() { |
| 133 | if (p != null) { |
| 134 | throw new IllegalStateException("Process already running"); |
| 135 | } |
| 136 | try { |
| 137 | returnCode = null; |
| 138 | p = runtime.exec(args, envp, workingDir); |
| 139 | captureStdOutAndStdErr(); |
| 140 | returnCode = new Integer(p.waitFor()); |
| 141 | } catch (Exception e) { |
| 142 | throw exceptions.toRuntime(e); |
| 143 | } finally { |
| 144 | // p.destroy(); |
| 145 | p = null; |
| 146 | for (int i = 0; i < listeners.size(); i++) { |
| 147 | new Thread((Runnable) listeners.get(i)).start(); |
| 148 | } |
| 149 | listeners.clear(); |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | private void captureStdOutAndStdErr() { |
| 154 | InputStream pOut = p.getInputStream(); |
| 155 | InputStream pErr = p.getErrorStream(); |
| 156 | new StreamConnector(pOut, out, getName() + " std out").start(); |
| 157 | new StreamConnector(pErr, err, getName() + " std err").start(); |
| 158 | } |
| 159 | |
| 160 | public void addCompletionListener(Runnable listener) { |
| 161 | if (listener == null) { |
| 162 | throw new IllegalArgumentException("Listener is null"); |
| 163 | } |
| 164 | listeners.add(listener); |
| 165 | } |
| 166 | |
| 167 | public int returnCode() { |
| 168 | if (!hasReturned()) { |
| 169 | throw new RuntimeException("Process hasn't returned yet"); |
| 170 | } |
| 171 | return returnCode.intValue(); |
| 172 | } |
| 173 | |
| 174 | public boolean hasReturned() { |
| 175 | return returnCode != null; |
| 176 | } |
| 177 | |
| 178 | public void destroyProcess() { |
| 179 | if (p != null) { |
| 180 | p.destroy(); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | public String getName() { |
| 185 | return me.getName(); |
| 186 | } |
| 187 | |
| 188 | public boolean isAlive() { |
| 189 | return me.isAlive(); |
| 190 | } |
| 191 | |
| 192 | public boolean isDaemon() { |
| 193 | return me.isDaemon(); |
| 194 | } |
| 195 | |
| 196 | public void setDaemon(boolean val) { |
| 197 | me.setDaemon(val); |
| 198 | } |
| 199 | |
| 200 | public void join() { |
| 201 | new Exceptions.VoidBlock() { |
| 202 | protected void inner() throws InterruptedException { |
| 203 | me.join(); |
| 204 | } |
| 205 | }.exec(); |
| 206 | } |
| 207 | |
| 208 | public void start() { |
| 209 | me.start(); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | public static class Stub implements Shell { |
| 214 | public void addCompletionListener(Runnable listener) { |
| 215 | throw new NotImplementedException(listener); |
| 216 | } |
| 217 | |
| 218 | public void destroyProcess() { |
| 219 | throw new NotImplementedException(); |
| 220 | } |
| 221 | |
| 222 | public String getName() { |
| 223 | throw new NotImplementedException(); |
| 224 | } |
| 225 | |
| 226 | public boolean hasReturned() { |
| 227 | throw new NotImplementedException(); |
| 228 | } |
| 229 | |
| 230 | public boolean isAlive() { |
| 231 | throw new NotImplementedException(); |
| 232 | } |
| 233 | |
| 234 | public boolean isDaemon() { |
| 235 | throw new NotImplementedException(); |
| 236 | } |
| 237 | |
| 238 | public void join() { |
| 239 | throw new NotImplementedException(); |
| 240 | } |
| 241 | |
| 242 | public int returnCode() { |
| 243 | throw new NotImplementedException(); |
| 244 | } |
| 245 | |
| 246 | public void run() { |
| 247 | throw new NotImplementedException(); |
| 248 | } |
| 249 | |
| 250 | public void setDaemon(boolean val) { |
| 251 | throw new NotImplementedException(new Boolean(val)); |
| 252 | } |
| 253 | |
| 254 | public void setEnvironment(String[] envp) { |
| 255 | throw new NotImplementedException(envp); |
| 256 | } |
| 257 | |
| 258 | public void setWorkingDir(File workingDir) { |
| 259 | throw new NotImplementedException(workingDir); |
| 260 | } |
| 261 | |
| 262 | public void start() { |
| 263 | throw new NotImplementedException(); |
| 264 | } |
| 265 | } |
| 266 | } |