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 | } |