- Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathExpect.java
More file actions
Latest commit
539 lines (503 loc) · 17 KB
/
Copy pathExpect.java
File metadata and controls
539 lines (503 loc) · 17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.io.PrintStream;
importjava.nio.ByteBuffer;
importjava.nio.channels.Channels;
importjava.nio.channels.Pipe;
importjava.nio.channels.SelectionKey;
importjava.nio.channels.Selector;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
importorg.apache.log4j.ConsoleAppender;
importorg.apache.log4j.FileAppender;
importorg.apache.log4j.Layout;
importorg.apache.log4j.Level;
importorg.apache.log4j.Logger;
importorg.apache.log4j.PatternLayout;
/**
* Provides similar functions as the Unix Expect tool.<br>
* There are two ways to create an Expect object: a constructor that takes an
* {@link InputStream} handle and {@link OutputStream} handle; or spawning a
* process by providing a comamnd String. <br>
* <br>
* The API is loosely based on Perl Expect library:<br>
* <a href="http://search.cpan.org/~rgiersig/Expect-1.15/Expect.pod">
* http://search.cpan.org/~rgiersig/Expect-1.15/Expect.pod</a>
*
* <br>
* If you are not familiar with the Tcl version of Expect, take a look at:<br>
* <a href="http://oreilly.com/catalog/expect/chapter/ch03.html">
* http://oreilly.com/catalog/expect/chapter/ch03.html</a> <br>
* <br>
* Expect uses a thread to convert InputStream to a SelectableChannel; other
* than this, no multi-threading is used.<br>
* A call to expect() will block for at most timeout seconds. Expect is not
* designed to be thread-safe, in other words, do not call methods of the same
* Expect object in different threads.
*
* @author Ronnie Dong
* @version 1.1
*/
publicclassExpect {
staticfinalLoggerlog = Logger.getLogger(Expect.class);
/**Logging is turned off by default.*/
static {
log.setLevel(Level.OFF);
}
privateOutputStreamoutput;
privatePipe.SourceChannelinputChannel;
privateSelectorselector;
publicExpect(InputStreaminput, OutputStreamoutput) {
try {
this.inputChannel = inputStreamToSelectableChannel(input);
selector = Selector.open();
inputChannel.register(selector, SelectionKey.OP_READ);
} catch (IOExceptione) {
log.fatal("Fatal error when initializing pipe or selector", e);
//e.printStackTrace();
}
this.output = output;
}
/**
* Essentially, this method converts an {@link InputStream} to a
* {@link SelectableChannel}. A thread is created to read from the
* InputStream, and write to a pipe. The source of the pipe is returned as
* an input handle from which you can perform unblocking read. The thread
* will terminate when reading EOF from InputStream, or when InputStream is
* closed, or when the returned Channel is closed(pipe broken).
*
* @param input
* @return a non-blocking Channel you can read from
* @throws IOException
* most unlikely
*
*/
privatestaticPipe.SourceChannelinputStreamToSelectableChannel(
finalInputStreaminput) throwsIOException {
Pipepipe = Pipe.open();
pipe.source().configureBlocking(false);
finalOutputStreamout = Channels.newOutputStream(pipe.sink());
Threadpiping = newThread(newRunnable() {
@Override
publicvoidrun() {
//LOG
byte[] buffer = newbyte[1024];
try {
for (intn = 0; n != -1; n = input.read(buffer)) {
out.write(buffer, 0, n);
if (duplicatedTo != null) {
StringtoWrite = newString(buffer, 0, n);
duplicatedTo.append(toWrite); // no Exception will be thrown
}
}
log.debug("EOF from InputStream");
input.close(); // now that input has EOF, close it.
// other than this, do not close input
} catch (IOExceptione) {
log.warn("IOException when piping from InputStream, "
+ "now the piping thread will end", e);
//e.printStackTrace();
} finally {
try {
log.debug("closing sink of the pipe");
out.close();
} catch (IOExceptione) {
}
}
}
});
piping.setName("Piping InputStream to SelectableChannel Thread");
piping.setDaemon(true);
piping.start();
returnpipe.source();
}
privateProcessprocess = null;
/**
* @return the spawned process, if this {@link Expect} object is created by
* spawning a process
*/
publicProcessgetProcess() {
returnprocess;
}
/**
* Creates an Expect object by spawning a command.<br>
* To Linux users, perhaps you need to use "bash -i" if you want to spawn
* Bash.<br>
* Note: error stream of the process is redirected to output stream.
*
* @param command
* @return Expect object created using the input and output handles from the
* spawned process
*/
publicstaticExpectspawn(Stringcommand) {
ProcessBuilderpb = newProcessBuilder(command.split(" "));
pb.redirectErrorStream(true);
Processp;
try {
p = pb.start();
} catch (IOExceptione) {
//e.printStackTrace();
log.error("Error when spawning command: " + command, e);
returnnull;
}
Expectretv = newExpect(p.getInputStream(), p.getOutputStream());
retv.process = p;
returnretv;
}
/**
* @param str
* Convenience method to send a string to output handle
*/
publicvoidsend(Stringstr) {
this.send(str.getBytes());
}
/**
* @param toWrite
* Write a byte array to the output handle, notice flush()
*/
publicvoidsend(byte[] toWrite) {
//System.out.println("sending: " + bytesToPrintableString(toWrite));
log.info("sending: " + bytesToPrintableString(toWrite));
try {
output.write(toWrite);
output.flush();
} catch (IOExceptione) {
log.error("Error when sending bytes to output", e);
//e.printStackTrace();
}
}
privateintdefault_timeout = 60;
privatebooleanrestart_timeout_upon_receive = false;
privateStringBufferbuffer = newStringBuffer();
privatebooleannotransfer = false;
/**String before the last match(if there was a match),
* updated after each expect() call*/
publicStringbefore;
/**String representing the last match(if there was a match),
* updated after each expect() call*/
publicStringmatch;
/**Whether the last match was successful,
* updated after each expect() call*/
publicbooleanisSuccess = false;
publicstaticfinalintRETV_TIMEOUT = -1, RETV_EOF = -2,
RETV_IOEXCEPTION = -9;
/**
* Convenience method, same as calling {@link #expect(int, Object...)
* expect(default_timeout, patterns)}
*
* @param patterns
* @return
*/
publicintexpect(Object... patterns) {
returnexpect(default_timeout, patterns);
}
/**
* Convenience method, internally it constructs a List{@literal <Pattern>}
* using the object array, and call {@link #expect(int, List) } using the
* List. The {@link String}s in the object array will be treated as
* literals; meanwhile {@link Pattern}s will be directly added to the List.
* If the array contains other objects, they will be converted by
* {@link #toString()} and then used as literal strings.
*
* @param patterns
* @return
*/
publicintexpect(inttimeout, Object... patterns) {
ArrayList<Pattern> list = newArrayList<Pattern>();
for (Objecto : patterns) {
if (oinstanceofString)
list.add(Pattern.compile(Pattern.quote((String) o))); // requires 1.5 and up
elseif (oinstanceofPattern)
list.add((Pattern) o);
else{
log.warn("Object " + o.toString() + " (class: "
+ o.getClass().getName() + ") is neither a String nor "
+ "a java.util.regex.Pattern, using as a literal String");
list.add(Pattern.compile(Pattern.quote(o.toString())));
}
}
returnexpect(timeout, list);
}
/**
* Expect will wait for the input handle to produce one of the patterns in
* the list. If a match is found, this method returns immediately;
* otherwise, the methods waits for up to timeout seconds, then returns. If
* timeout is less than or equal to 0 Expect will check one time to see if
* the internal buffer contains the pattern.
*
* @param timeout
* timeout in seconds
* @param list
* List of Java {@link Pattern}s used for match the internal
* buffer obtained by reading the InputStream
* @return position of the matched pattern within the list (starting from
* 0); or a negative number if there is an IOException, EOF or
* timeout
*/
publicintexpect(inttimeout, List<Pattern> list) {
log.debug("Expecting " + list);
clearGlobalVariables();
longendTime = System.currentTimeMillis() + (long)timeout * 1000;
try {
ByteBufferbytes = ByteBuffer.allocate(1024);
intn;
while (true) {
for (inti = 0; i < list.size(); i++) {
log.trace("trying to match " + list.get(i)
+ " against buffer \"" + buffer + "\"");
Matcherm = list.get(i).matcher(buffer);
if (m.find()) {
log.trace("success!");
intmatchStart = m.start(), matchEnd = m.end();
this.before = buffer.substring(0, matchStart);
this.match = m.group();
this.isSuccess = true;
if(!notransfer)buffer.delete(0, matchEnd);
returni;
}
}
longwaitTime = endTime - System.currentTimeMillis();
if (restart_timeout_upon_receive)
waitTime = timeout * 1000;
if (waitTime <= 0) {
log.debug("Timeout when expecting " + list);
returnRETV_TIMEOUT;
}
//System.out.println("waiting for "+waitTime);
selector.select(waitTime);
//System.out.println(selector.selectedKeys().size());
if (selector.selectedKeys().size() == 0) {
//System.err.println("timeout!");
//break; //we can directly "break" here
log.debug("Timeout when expecting " + list);
returnRETV_TIMEOUT;
}
selector.selectedKeys().clear();
if ((n = inputChannel.read(bytes)) == -1) {
//System.err.println("EOF!");
//break;
log.debug("EOF when expecting " + list);
returnRETV_EOF;
}
StringBuildertmp = newStringBuilder();
for (inti = 0; i < n; i++) {
buffer.append((char) bytes.get(i));
tmp.append(byteToPrintableString(bytes.get(i)));
}
log.debug("Obtained following from InputStream: " + tmp);
bytes.clear();
//System.out.println(buffer);
}
} catch (IOExceptione) {
//e.printStackTrace();
log.error("IOException when selecting or reading", e);
thrownIOE = e;
returnRETV_IOEXCEPTION;
}
}
/**
* Convenience method, internally it calls {@link #expect(int, List)
* expect(timeout, new ArrayList<Pattern>())}. Given an empty list,
* {@link #expect(int, List)} will not perform any regex matching, therefore
* the only conditions for it to return is EOF or timeout (or IOException).
* If EOF is detected, {@link #isSuccess} and {@link #before} are properly
* set.
*
* @param timeout
* @return same as return value of {@link #expect(int, List)}
*/
publicintexpectEOF(inttimeout) {
intretv = expect(timeout, newArrayList<Pattern>());
if (retv == RETV_EOF) {
this.isSuccess = true;
this.before = this.buffer.toString();
this.buffer.delete(0, buffer.length());
}
returnretv;
}
/**Convenience method, same as calling {@link #expectEOF(int)
* expectEOF(default_timeout)}*/
publicintexpectEOF() {
returnexpectEOF(default_timeout);
}
/**
* Throws checked exceptions when expectEOF was not successful.
*/
publicintexpectEOFOrThrow(inttimeout) throwsTimeoutException,
IOException {
intretv = expectEOF(timeout);
if (retv == RETV_TIMEOUT)
thrownewTimeoutException();
if (retv == RETV_IOEXCEPTION)
throwthrownIOE;
returnretv;
}
/**Convenience method, same as calling {@link #expectEOF(int)
* expectEOF(default_timeout)}*/
publicintexpectEOFOrThrow() throwsTimeoutException, IOException {
returnexpectEOFOrThrow(default_timeout);
}
/**useful when calling {@link #expectOrThrow(int, Object...)}*/
privateIOExceptionthrownIOE;
/**
* This method calls {@link #expect(int, Object...) expect(timeout,
* patterns)}, and throws checked exceptions when expect was not successful.
* Useful when you want to simplify error handling: for example, when you
* send a series of commands to an SSH server, you expect a prompt after
* each send, however the server may die or the prompt may take forever to
* appear, you would want to skip the following commands if those occurred.
* In such a case this method will be handy.
*
* @param timeout
* @param patterns
* @throws TimeoutException
* when expect times out
* @throws EOFException
* when EOF is encountered
* @throws IOException
* when there is a problem reading from the InputStream
* @return same as {@link #expect(int, Object...) expect(timeout, patterns)}
*/
publicintexpectOrThrow(inttimeout, Object... patterns)
throwsTimeoutException, EOFException, IOException {
intretv = expect(timeout, patterns);
switch (retv) {
caseRETV_TIMEOUT:
thrownewTimeoutException();
caseRETV_EOF:
thrownewEOFException();
caseRETV_IOEXCEPTION:
throwthrownIOE;
default:
returnretv;
}
}
/**Convenience method, same as calling {@link #expectOrThrow(int, Object...)
* expectOrThrow(default_timeout, patterns)}*/
publicintexpectOrThrow(Object... patterns) throwsTimeoutException,
EOFException, IOException {
returnexpectOrThrow(default_timeout, patterns);
}
privatevoidclearGlobalVariables() {
isSuccess = false;
match = null;
before = null;
}
/**
* The OutputStream passed to Expect constructor is closed; the InputStream
* is not closed (there is no need to close the InputStream).<br>
* It is suggested that this method be called after the InputStream has come
* to EOF. For example, when you connect through SSH, send an "exit" command
* first, and then call this method.<br>
* <br>
*
* When this method is called, the thread which write to the sink of the
* pipe will end.
*/
publicvoidclose() {
try {
this.output.close();
} catch (IOExceptione) {
log.warn("Exception when closing OutputStream", e);
//e.printStackTrace();
}
try {
this.inputChannel.close();
} catch (IOExceptione) {
log.warn("Exception when closing input Channel", e);
//e.printStackTrace();
}
}
publicintgetDefault_timeout() {
returndefault_timeout;
}
publicvoidsetDefault_timeout(intdefault_timeout) {
this.default_timeout = default_timeout;
}
publicbooleanisRestart_timeout_upon_receive() {
returnrestart_timeout_upon_receive;
}
publicvoidsetRestart_timeout_upon_receive(booleanrestart_timeout_upon_receive) {
this.restart_timeout_upon_receive = restart_timeout_upon_receive;
}
publicvoidsetNotransfer(booleannotransfer) {
this.notransfer = notransfer;
}
publicbooleanisNotransfer() {
returnnotransfer;
}
/**
* Static method used for convert byte array to string, each byte is
* converted to an ASCII character, if the byte represents a control
* character, it is replaced by a printable caret notation <a
* href="http://en.wikipedia.org/wiki/ASCII">
* http://en.wikipedia.org/wiki/ASCII </a>, or an escape code if possible.
*
* @param bytes
* bytes to be printed
* @return String representation of the byte array
*/
publicstaticStringbytesToPrintableString(byte[] bytes) {
StringBuildersb = newStringBuilder();
for (byteb : bytes)
sb.append(byteToPrintableString(b));
returnsb.toString();
}
publicstaticStringbyteToPrintableString(byteb) {
Strings = newString(newbyte[] { b });
// control characters
if (b >= 0 && b < 32) s = "^" + (char) (b + 64);
elseif (b == 127) s = "^?";
// some escape characters
if (b == 9) s = "\\t";
if (b == 10) s = "\\n";
if (b == 13) s = "\\r";
returns;
}
@SuppressWarnings("serial")
publicstaticclassTimeoutExceptionextendsException{
}
@SuppressWarnings("serial")
publicstaticclassEOFExceptionextendsException{
}
privatestaticLayoutlayout = newPatternLayout(
PatternLayout.TTCC_CONVERSION_PATTERN);
publicstaticvoidaddLogToConsole(Levellevel) {
log.setLevel(Level.ALL);
ConsoleAppenderconsole = newConsoleAppender(layout);
console.setThreshold(level);
log.addAppender(console);
}
publicstaticvoidaddLogToFile(Stringfilename, Levellevel) throwsIOException {
log.setLevel(Level.ALL);
FileAppenderfile = newFileAppender(layout, filename);
file.setThreshold(level);
log.addAppender(file);
}
publicstaticvoidturnOffLogging(){
log.setLevel(Level.OFF);
log.removeAllAppenders();
}
privatestaticPrintStreamduplicatedTo = null;
/**
* While performing expect operations on the InputStream provided, duplicate
* the contents obtained from InputStream to a PrintStream (you can use
* System.err or System.out). <b>DO NOT</b> call this function while there
* are live Expect objects as this may cause the piping thread to end due to
* unsynchronized code; if you need this feature, add the following to both
* {@link #inputStreamToSelectableChannel(InputStream)} and
* {@link #forwardInputStreamTo(PrintStream)}:
* <pre>
* {@code
* synchronized(Expect.duplicatedTo) {...}
* </pre>
* @param duplicatedTo
* call with null if you want to turn off
*/
publicstaticvoidforwardInputStreamTo(PrintStreamduplicatedTo) {
Expect.duplicatedTo = duplicatedTo;
}
}