View Javadoc

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          // Print the stack trace and ignore the problem, this will make us never
99          // time out.  Too bad.  /JW-2006apr10
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         // Start the spawnable and timer if needed
136         spawnable.start();
137         if (timer != null) {
138             timer.startTimer();
139         }
140 
141         // Starting the piped streams and StreamPiper objects
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 }