1 package expectj;
2
3 import java.io.IOException;
4 import java.io.OutputStream;
5 import java.nio.channels.Channels;
6 import java.nio.channels.Pipe;
7
8 import org.apache.commons.logging.Log;
9 import org.apache.commons.logging.LogFactory;
10
11 import expectj.Spawnable.CloseListener;
12
13 /***
14 * Helper class that wraps Spawnables to make them crunchier for ExpectJ to run.
15 *
16 * @author Johan Walles
17 */
18 class SpawnableHelper
19 implements TimerEventListener
20 {
21 /***
22 * Log messages go here.
23 */
24 private final static Log LOG = LogFactory.getLog(SpawnableHelper.class);
25
26 /***
27 * The spawnable we're wrapping.
28 */
29 private Spawnable spawnable;
30
31 /***
32 * @param timeOutSeconds time interval in seconds to be allowed for spawn execution
33 * @param runMe the spawnable to execute
34 */
35 SpawnableHelper(Spawnable runMe, long timeOutSeconds) {
36 if (timeOutSeconds < -1) {
37 throw new IllegalArgumentException("Time-out is invalid");
38 }
39 if (timeOutSeconds != -1) {
40 timer = new Timer(timeOutSeconds, this);
41 }
42 this.spawnable = runMe;
43 }
44
45 /***
46 * @param runMe the spawnable to execute
47 */
48 SpawnableHelper(Spawnable runMe) {
49 this(runMe, -1);
50 }
51
52 /*** Timer object to monitor our Spawnable */
53 private Timer timer = null;
54
55 /***
56 * Handle spawn's stdout.
57 */
58 private Pipe systemOut;
59
60 /***
61 * Handle spawn's stderr.
62 */
63 private Pipe systemErr;
64
65 /***
66 * Drive the pipe from spawn's stdout to {@link #systemOut}.
67 */
68 private StreamPiper spawnOutToSystemOut = null;
69
70 /***
71 * Drive the pipe from spawn's stderr to {@link #systemErr}.
72 */
73 private StreamPiper spawnErrToSystemErr = null;
74
75 public void timerTimedOut() {
76 stop();
77 }
78
79 /***
80 * This method stops the spawn.
81 */
82 void stop() {
83 spawnOutToSystemOut.stopProcessing();
84 if (spawnErrToSystemErr != null) {
85 spawnErrToSystemErr.stopProcessing();
86 }
87 spawnable.stop();
88 close();
89 }
90
91 /***
92 * This method is invoked by the {@link Timer}, when the timer thread
93 * receives an interrupted exception.
94 *
95 * @param reason The reason we were interrupted.
96 */
97 public void timerInterrupted(InterruptedException reason) {
98
99
100 LOG.error("Timer interrupted", reason);
101 }
102
103 /***
104 * From now on, don't copy any piped content to stdout.
105 * @see #startPipingToStandardOut()
106 * @see Spawn#interact()
107 */
108 synchronized void stopPipingToStandardOut() {
109 spawnOutToSystemOut.stopPipingToStandardOut();
110 if (spawnErrToSystemErr != null) {
111 spawnErrToSystemErr.stopPipingToStandardOut();
112 }
113 }
114
115 /***
116 * From now on, copy all piped content to stdout.
117 * @see #stopPipingToStandardOut()
118 * @see Spawn#interact()
119 */
120 synchronized void startPipingToStandardOut() {
121 spawnOutToSystemOut.startPipingToStandardOut();
122 if (spawnErrToSystemErr != null) {
123 spawnErrToSystemErr.startPipingToStandardOut();
124 }
125 }
126
127 /***
128 * This method launches our Spawnable within the specified time
129 * limit. It tells the spawnable to start, and starts the timer when
130 * enabled. It starts the piped streams to enable copying of spawn
131 * stream contents to standard streams.
132 * @throws IOException if launching the spawnable fails
133 */
134 void start() throws IOException {
135
136 spawnable.start();
137 if (timer != null) {
138 timer.startTimer();
139 }
140
141
142 systemOut = Pipe.open();
143 systemOut.source().configureBlocking(false);
144 spawnOutToSystemOut = new StreamPiper(System.out,
145 spawnable.getStdout(),
146 Channels.newOutputStream(systemOut.sink()));
147 spawnOutToSystemOut.start();
148
149 if (spawnable.getStderr() != null) {
150 systemErr = Pipe.open();
151 systemErr.source().configureBlocking(false);
152
153 spawnErrToSystemErr = new StreamPiper(System.err,
154 spawnable.getStderr(),
155 Channels.newOutputStream(systemErr.sink()));
156 spawnErrToSystemErr.start();
157 }
158 }
159
160 /***
161 * Shut down operations and free system resources.
162 * <p>
163 * Any exception on closing resources will be logged.
164 */
165 void close() {
166 if (spawnErrToSystemErr != null) {
167 spawnErrToSystemErr.stopProcessing();
168 }
169 if (spawnOutToSystemOut != null) {
170 spawnOutToSystemOut.stopProcessing();
171 }
172 if (systemOut != null) {
173 try {
174 systemOut.sink().close();
175 } catch (IOException e) {
176 LOG.warn("Closing stdout sink failed", e);
177 }
178 try {
179 systemOut.source().close();
180 } catch (IOException e) {
181 LOG.warn("Closing stdout source failed", e);
182 }
183 }
184 if (systemErr != null) {
185 try {
186 systemErr.sink().close();
187 } catch (IOException e) {
188 LOG.warn("Closing stderr sink failed", e);
189 }
190 try {
191 systemErr.source().close();
192 } catch (IOException e) {
193 LOG.warn("Closing stderr source failed", e);
194 }
195 }
196 }
197
198 /***
199 * @return a channel from which data produced by the spawn can be read
200 */
201 Pipe.SourceChannel getStdoutChannel() {
202 return systemOut.source();
203 }
204
205 /***
206 * @return the output stream of the spawn.
207 */
208 OutputStream getStdin() {
209 return spawnable.getStdin();
210 }
211
212 /***
213 * @return a channel from which stderr data produced by the spawn can be read, or
214 * null if there is no channel to stderr.
215 */
216 Pipe.SourceChannel getStderrChannel() {
217 if (systemErr == null) {
218 return null;
219 }
220 return systemErr.source();
221 }
222
223 /***
224 * @return true if the spawn has exited.
225 */
226 boolean isClosed() {
227 return spawnable.isClosed();
228 }
229
230 /***
231 * If the spawn represented by this object has already exited, it
232 * returns the exit code. isClosed() should be used in conjunction
233 * with this method.
234 * @return The exit code from the exited spawn.
235 * @throws ExpectJException If the spawn is still running.
236 */
237 int getExitValue()
238 throws ExpectJException
239 {
240 if (!isClosed()) {
241 throw new ExpectJException("Spawn is still running");
242 }
243 return spawnable.getExitValue();
244 }
245
246
247 /***
248 * @return the available contents of Standard Out
249 */
250 String getCurrentStandardOutContents() {
251 return spawnOutToSystemOut.getCurrentContents();
252 }
253
254 /***
255 * @return the available contents of Standard Err, or null if stderr is not available
256 */
257 String getCurrentStandardErrContents() {
258 if (spawnErrToSystemErr == null) {
259 return null;
260 }
261 return spawnErrToSystemErr.getCurrentContents();
262 }
263
264 /***
265 * Register a listener that will be called when the spawnable we're wrapping
266 * closes.
267 *
268 * @param closeListener The listener that will be notified when this
269 * spawnable closes.
270 */
271 void setCloseListener(CloseListener closeListener) {
272 spawnable.setCloseListener(closeListener);
273 }
274 }