001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: *
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: /**
020: * @author Vasily Zakharov
021: * @version $Revision: 1.1.2.2 $
022: */package org.apache.harmony.rmi.common;
023:
024: import java.io.DataInputStream;
025: import java.io.DataOutputStream;
026: import java.io.EOFException;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.OutputStream;
030:
031: import org.apache.harmony.rmi.internal.nls.Messages;
032:
033: /**
034: * Allows for execution of external applications as subprocesses.
035: *
036: * @author Vasily Zakharov
037: * @version $Revision: 1.1.2.2 $
038: *
039: * @todo Check with <code>ProcessBuilder</code> for Java 5.0.
040: */
041: public final class SubProcess {
042:
043: /**
044: * Default argument to {@link #tell(String)}
045: * and expect {@link #expect(String)} methods.
046: */
047: public static final String READY_STRING = "PROCESS_READY"; //$NON-NLS-1$
048:
049: /**
050: * Process.
051: */
052: private Process process;
053:
054: /**
055: * Data input stream.
056: */
057: private DataInputStream dataInput;
058:
059: /**
060: * Data output stream.
061: */
062: private DataOutputStream dataOutput;
063:
064: /**
065: * Data error stream.
066: */
067: private DataInputStream dataError;
068:
069: /**
070: * Process input stream.
071: */
072: private InputStream processInput;
073:
074: /**
075: * Process output stream.
076: */
077: private OutputStream processOutput;
078:
079: /**
080: * Process error stream.
081: */
082: private InputStream processError;
083:
084: /**
085: * Pipe target input stream.
086: */
087: private InputStream targetInput;
088:
089: /**
090: * Pipe target output stream.
091: */
092: private OutputStream targetOutput;
093:
094: /**
095: * Pipe target error stream.
096: */
097: private OutputStream targetError;
098:
099: /**
100: * Creates subprocess with full control of its streams.
101:
102: * Equivalent to
103: * {@link #SubProcess(String[], boolean, OutputStream, boolean, InputStream, boolean, OutputStream)
104: * SubProcess(args, true, System.out, true, System.in, true, System.err)}.
105: *
106: * @param args
107: * Program name and command line arguments
108: * (as for {@link Runtime#exec(String[])}).
109: *
110: * @throws IOException
111: */
112: public SubProcess(String[] args) throws IOException {
113: this (args, true, System.out, true, System.in, true, System.err);
114: }
115:
116: /**
117: * Creates instance of this class with no control of its streams.
118: * If <code>pipe</code> is <code>true</code>, the streams are piped
119: * to the respective system streams of the current process.
120: * This is equivalent to
121: * {@link #SubProcess(String[], boolean, OutputStream, boolean, InputStream, boolean, OutputStream)
122: * SubProcess(args, false, System.out, false, System.in, false, System.err)}.
123: *
124: * If <code>pipe</code> is <code>false</code>, the streams are discarded.
125: * This is equivalent to
126: * {@link #SubProcess(String[], boolean, OutputStream, boolean, InputStream, boolean, OutputStream)
127: * SubProcess(args, false, null, false, null, false, null)}.
128: *
129: * @param args
130: * Program name and command line arguments
131: * (as for {@link Runtime#exec(String[])}).
132: *
133: * @param pipe
134: * If <code>true</code>, the streams are piped
135: * to the respective system streams of the current process,
136: * if <code>false</code>, the streams are discarded.
137: *
138: * @throws IOException
139: */
140: public SubProcess(String[] args, boolean pipe) throws IOException {
141: this (args, false, (pipe ? System.out : null), false,
142: (pipe ? System.in : null), false, (pipe ? System.err
143: : null));
144: }
145:
146: /**
147: * Creates instance of this class with no control of its streams.
148: * This is equivalent to
149: * {@link #SubProcess(String[], boolean, OutputStream, boolean, InputStream, boolean, OutputStream)
150: * SubProcess(args, false, outputStream, false, inputStream, false, errorStream)}.
151: *
152: * @param args
153: * Program name and command line arguments
154: * (as for {@link Runtime#exec(String[])}).
155: *
156: * @param outputStream
157: * Output stream to pipe program input to
158: * if <code>inputControl</code> is <code>false</code>.
159: * May be <code>null</code>,
160: * in this case input from the program is discarded.
161: *
162: * @param inputStream
163: * Input stream to pipe to the program output stream
164: * if <code>outputControl</code> is <code>false</code>.
165: * May be <code>null</code>,
166: * in this case the program output stream is closed.
167: *
168: * @param errorStream
169: * Error stream to pipe program error input to
170: * if <code>errorControl</code> is <code>false</code>.
171: * May be <code>null</code>,
172: * in this case error input from the program is discarded.
173: *
174: * @throws IOException
175: */
176: public SubProcess(String[] args, OutputStream outputStream,
177: InputStream inputStream, OutputStream errorStream)
178: throws IOException {
179: this (args, false, outputStream, false, inputStream, false,
180: errorStream);
181: }
182:
183: /**
184: * Creates instance of this class.
185: *
186: * @param args
187: * Program name and command line arguments
188: * (as for {@link Runtime#exec(String[])}).
189: *
190: * @param inputControl
191: * If <code>true</code>, input from the program is available
192: * to {@link #expect()} methods and <code>outputStream</code>
193: * parameter is ignored, otherwise it is piped to the specified
194: * <code>outputStream</code>.
195: *
196: * @param outputStream
197: * Output stream to pipe program input to
198: * if <code>inputControl</code> is <code>false</code>.
199: * May be <code>null</code>,
200: * in this case input from the program is discarded.
201: *
202: * @param outputControl
203: * If <code>true</code>, output stream to the program is available
204: * to {@link #tell()} methods and <code>inputStream</code>
205: * parameter is ignored, otherwise the specified
206: * <code>inputStream</code> is piped to program output stream.
207: *
208: * @param inputStream
209: * Input stream to pipe to the program output stream
210: * if <code>outputControl</code> is <code>false</code>.
211: * May be <code>null</code>,
212: * in this case the program output stream is closed.
213: *
214: * @param errorControl
215: * If <code>true</code>, error input from the program is available
216: * to {@link #expectError()} methods and <code>errorStream</code>
217: * parameter is ignored, otherwise it is piped to the specified
218: * <code>errorStream</code>.
219: *
220: * @param errorStream
221: * Error stream to pipe program error input to
222: * if <code>errorControl</code> is <code>false</code>.
223: * May be <code>null</code>,
224: * in this case error input from the program is discarded.
225: *
226: * @throws IOException
227: */
228: public SubProcess(String[] args, boolean inputControl,
229: OutputStream outputStream, boolean outputControl,
230: InputStream inputStream, boolean errorControl,
231: OutputStream errorStream) throws IOException {
232: process = Runtime.getRuntime().exec(args);
233:
234: processInput = process.getInputStream();
235: processOutput = process.getOutputStream();
236: processError = process.getErrorStream();
237:
238: targetInput = inputStream;
239: targetOutput = outputStream;
240: targetError = errorStream;
241:
242: if (inputControl) {
243: dataInput = new DataInputStream(processInput);
244: } else {
245: dataInput = null;
246: doPipeInput();
247: }
248:
249: if (outputControl) {
250: dataOutput = new DataOutputStream(processOutput);
251: } else {
252: dataOutput = null;
253: doPipeOutput();
254: }
255:
256: if (errorControl) {
257: dataError = new DataInputStream(processError);
258: } else {
259: dataError = null;
260: doPipeError();
261: }
262: }
263:
264: /**
265: * Discards the remaining input.
266: * Usable when <code>inputControl</code> is enabled
267: * but there's nothing else to {@linkplain #expect() expect}.
268: */
269: public void discardInput() {
270: pipeInput(null);
271: }
272:
273: /**
274: * Pipes the remaining input to the target output stream
275: * specified in <a href="#constructor_detail">constructor</a>.
276: * Usable when <code>inputControl</code> is enabled
277: * but there's nothing else to {@linkplain #expect() expect}.
278: */
279: public void pipeInput() {
280: pipeInput(targetOutput);
281: }
282:
283: /**
284: * Pipes the remaining input to the specified output stream.
285: * Usable when <code>inputControl</code> is enabled
286: * but there's nothing else to {@linkplain #expect() expect}.
287: *
288: * @param outputStream
289: * Output stream to pipe program input to.
290: * May be <code>null</code>,
291: * in this case input from the program is discarded.
292: */
293: public void pipeInput(OutputStream outputStream) {
294: if (dataInput != null) {
295: dataInput = null;
296: targetOutput = outputStream;
297: doPipeInput();
298: }
299: }
300:
301: /**
302: * Creates pipe from process input to target output.
303: */
304: private void doPipeInput() {
305: new StreamPipe(processInput, targetOutput).start();
306: }
307:
308: /**
309: * Closes the program output.
310: * Usable when <code>outputControl</code> is enabled
311: * but there's nothing else to {@linkplain #tell() tell}.
312: */
313: public void closeOutput() {
314: pipeOutput(null);
315: }
316:
317: /**
318: * Pipes the target input stream specified in
319: * <a href="#constructor_detail">constructor</a>
320: * to the program output stream.
321: * Usable when <code>outputControl</code> is enabled
322: * but there's nothing else to {@linkplain #tell() tell}.
323: */
324: public void pipeOutput() {
325: pipeOutput(targetInput);
326: }
327:
328: /**
329: * Pipes the specified input stream to the program output stream.
330: * Usable when <code>outputControl</code> is enabled
331: * but there's nothing else to {@linkplain #tell() tell}.
332: *
333: * @param inputStream
334: * Input stream to pipe to the program output.
335: * May be <code>null</code>,
336: * in this case the program output stream is closed.
337: */
338: public void pipeOutput(InputStream inputStream) {
339: if (dataOutput != null) {
340: dataOutput = null;
341: targetInput = inputStream;
342: doPipeOutput();
343: }
344: }
345:
346: /**
347: * Creates pipe from target input to process output
348: * or closes process output if target input is <code>null</code>.
349: */
350: private void doPipeOutput() {
351: if (targetInput != null) {
352: new StreamPipe(targetInput, processOutput).start();
353: } else {
354: try {
355: processOutput.close();
356: } catch (IOException e) {
357: }
358: }
359: }
360:
361: /**
362: * Discards the remaining error input.
363: * Usable when <code>errorControl</code> is enabled
364: * but there's nothing else to {@linkplain #expectError() expect}.
365: */
366: public void discardError() {
367: pipeError(null);
368: }
369:
370: /**
371: * Pipes the remaining error input to the target error stream
372: * specified in <a href="#constructor_detail">constructor</a>.
373: * Usable when <code>errorControl</code> is enabled
374: * but there's nothing else to {@linkplain #expectError() expect}.
375: */
376: public void pipeError() {
377: pipeError(targetError);
378: }
379:
380: /**
381: * Pipes the remaining error input to the specified error stream.
382: * Usable when <code>errorControl</code> is enabled
383: * but there's nothing else to {@linkplain #expectError() expect}.
384: *
385: * @param errorStream
386: * Error stream to pipe program error input to.
387: * May be <code>null</code>,
388: * in this case error input from the program is discarded.
389: */
390: public void pipeError(OutputStream errorStream) {
391: if (dataError != null) {
392: dataError = null;
393: targetError = errorStream;
394: doPipeError();
395: }
396: }
397:
398: /**
399: * Creates pipe from process error input to target error output.
400: */
401: private void doPipeError() {
402: new StreamPipe(processError, targetError).start();
403: }
404:
405: /**
406: * Waits for subprocess to terminate and returns its exit code.
407: *
408: * @return The subprocess exit code.
409: */
410: public int waitFor() {
411: while (true) {
412: try {
413: return process.waitFor();
414: } catch (InterruptedException e) {
415: }
416: }
417: }
418:
419: /**
420: * Destroys this subprocess.
421: */
422: public void destroy() {
423: process.destroy();
424: dataInput = null;
425: dataOutput = null;
426: dataError = null;
427: }
428:
429: /**
430: * Writes the {@linkplain #READY_STRING default "ready" string}
431: * to the process output stream.
432: *
433: * @throws IllegalStateException
434: * If subprocess output stream control is disabled.
435: *
436: * @throws IOException
437: * If I/O error occurs.
438: */
439: public void tell() throws IllegalStateException, IOException {
440: tell(READY_STRING);
441: }
442:
443: /**
444: * Writes the specified string to the process output stream.
445: *
446: * @param str
447: * String to write.
448: *
449: * @throws IllegalStateException
450: * If subprocess output stream control is disabled.
451: *
452: * @throws IOException
453: * If I/O error occurs.
454: */
455: public void tell(String str) throws IllegalStateException,
456: IOException {
457: if (dataOutput == null) {
458: // rmi.48=Subprocess output stream control disabled
459: throw new IllegalStateException(Messages
460: .getString("rmi.48")); //$NON-NLS-1$
461: }
462: tell(dataOutput, str);
463: }
464:
465: /**
466: * Writes the {@linkplain #READY_STRING default "ready" string}
467: * to the {@linkplain System#out system output stream}.
468: *
469: * This static method is usable by child subprocesses
470: * for communication with the parent process.
471: *
472: * @throws IOException
473: * If I/O error occurs.
474: */
475: public static void tellOut() throws IOException {
476: tellOut(READY_STRING);
477: }
478:
479: /**
480: * Writes the specified string to the
481: * {@linkplain System#out system output stream}.
482: *
483: * This static method is usable by child subprocesses
484: * for communication with the parent process.
485: *
486: * @param str
487: * String to write.
488: *
489: * @throws IOException
490: * If I/O error occurs.
491: */
492: public static void tellOut(String str) throws IOException {
493: tell(new DataOutputStream(System.out), str);
494: }
495:
496: /**
497: * Writes the {@linkplain #READY_STRING default "ready" string}
498: * to the {@linkplain System#err system error stream}.
499: *
500: * This static method is usable by child subprocesses
501: * for communication with the parent process.
502: *
503: * @throws IOException
504: * If I/O error occurs.
505: */
506: public static void tellError() throws IOException {
507: tellError(READY_STRING);
508: }
509:
510: /**
511: * Writes the specified string to the
512: * {@linkplain System#err system error stream}.
513: *
514: * This static method is usable by child subprocesses
515: * for communication with the parent process.
516: *
517: * @param str
518: * String to write.
519: *
520: * @throws IOException
521: * If I/O error occurs.
522: */
523: public static void tellError(String str) throws IOException {
524: tell(new DataOutputStream(System.err), str);
525: }
526:
527: /**
528: * Writes the specified string to the process output stream.
529: *
530: * @param stream
531: * Stream to write the string to.
532: *
533: * @param str
534: * String to write.
535: *
536: * @throws IOException
537: * If I/O error occurs.
538: */
539: private static void tell(DataOutputStream stream, String str)
540: throws IOException {
541: stream.writeBytes('\n' + str + '\n');
542: stream.flush();
543: }
544:
545: /**
546: * Waits until the {@linkplain #READY_STRING default "ready" string}
547: * appears in the program input. Equivalent to
548: * {@link #expect(String, boolean, boolean) expect(READY_STRING, false, false)}.
549: *
550: * @throws IllegalStateException
551: * If subprocess input stream control is disabled.
552: *
553: * @throws IOException
554: * If I/O error occurs.
555: */
556: public void expect() throws IllegalStateException, IOException {
557: expect(READY_STRING);
558: }
559:
560: /**
561: * Waits until the specified string appears in the program input.
562: * Equivalent to
563: * {@link #expect(String, boolean, boolean) expect(str, false, false)}.
564: *
565: * @param str
566: * String to wait for.
567: *
568: * @throws IllegalStateException
569: * If subprocess input stream control is disabled.
570: *
571: * @throws IOException
572: * If I/O error occurs.
573: */
574: public void expect(String str) throws IllegalStateException,
575: IOException {
576: expect(str, false, false);
577: }
578:
579: /**
580: * Waits until the specified string appears in the program input.
581: *
582: * @param str
583: * String to wait for.
584: *
585: * @param whole
586: * If <code>true</code>, the whole input lines are compared
587: * to the specified string, otherwise the string is considered
588: * to be found if it appears as a substring in any input line.
589: *
590: * @param ignoreCase
591: * If <code>true</code>, case-insensitive comparison is performed.
592: *
593: * @throws IllegalStateException
594: * If subprocess input stream control is disabled.
595: *
596: * @throws IOException
597: * If I/O error occurs.
598: */
599: public void expect(String str, boolean whole, boolean ignoreCase)
600: throws IllegalStateException, IOException {
601: if (dataInput == null) {
602: // rmi.49=Subprocess input stream control disabled
603: throw new IllegalStateException(Messages
604: .getString("rmi.49")); //$NON-NLS-1$
605: }
606: expect(dataInput, str, whole, ignoreCase);
607: }
608:
609: /**
610: * Waits until the {@linkplain #READY_STRING default "ready" string}
611: * appears in the program error input. Equivalent to
612: * {@link #expectError(String, boolean, boolean) expectError(READY_STRING, false, false)}.
613: *
614: * @throws IllegalStateException
615: * If subprocess error stream control is disabled.
616: *
617: * @throws IOException
618: * If I/O error occurs.
619: */
620: public void expectError() throws IllegalStateException, IOException {
621: expectError(READY_STRING);
622: }
623:
624: /**
625: * Waits until the specified string appears in the program error input.
626: * Equivalent to
627: * {@link #expectError(String, boolean, boolean) expectError(str, false, false)}.
628: *
629: * @param str
630: * String to wait for.
631: *
632: * @throws IllegalStateException
633: * If subprocess error stream control is disabled.
634: *
635: * @throws IOException
636: * If I/O error occurs.
637: */
638: public void expectError(String str) throws IllegalStateException,
639: IOException {
640: expectError(str, false, false);
641: }
642:
643: /**
644: * Waits until the specified string appears in the program error input.
645: *
646: * @param str
647: * String to wait for.
648: *
649: * @param whole
650: * If <code>true</code>, the whole input lines are compared
651: * to the specified string, otherwise the string is considered
652: * to be found if it appears as a substring in any input line.
653: *
654: * @param ignoreCase
655: * If <code>true</code>, case-insensitive comparison is performed.
656: *
657: * @throws IllegalStateException
658: * If subprocess error stream control is disabled.
659: *
660: * @throws IOException
661: * If I/O error occurs.
662: */
663: public void expectError(String str, boolean whole,
664: boolean ignoreCase) throws IllegalStateException,
665: IOException {
666: if (dataError == null) {
667: // rmi.4A=Subprocess error stream control disabled
668: throw new IllegalStateException(Messages
669: .getString("rmi.4A")); //$NON-NLS-1$
670: }
671: expect(dataError, str, whole, ignoreCase);
672: }
673:
674: /**
675: * Waits until the {@linkplain #READY_STRING default "ready" string}
676: * appears in {@linkplain System#in system input stream}. Equivalent to
677: * {@link #expectIn(String, boolean, boolean) expectIn(READY_STRING, false, false)}.
678: *
679: * This static method is usable by child subprocesses
680: * for communication with the parent process.
681: *
682: * @throws IOException
683: * If I/O error occurs.
684: */
685: public static void expectIn() throws IOException {
686: expectIn(READY_STRING);
687: }
688:
689: /**
690: * Waits until the specified string appears in
691: * {@linkplain System#in system input stream}.
692: * Equivalent to {@link #expectIn(String, boolean, boolean) expectIn(str, false, false)}.
693: *
694: * This static method is usable by child subprocesses
695: * for communication with the parent process.
696: *
697: * @param str
698: * String to wait for.
699: *
700: * @throws IOException
701: * If I/O error occurs.
702: */
703: public static void expectIn(String str) throws IOException {
704: expectIn(str, false, false);
705: }
706:
707: /**
708: * Waits until the specified string appears in
709: * {@linkplain System#in system input stream}.
710: *
711: * This static method is usable by child subprocesses
712: * for communication with the parent process.
713: *
714: * @param str
715: * String to wait for.
716: *
717: * @param whole
718: * If <code>true</code>, the whole input lines are compared
719: * to the specified string, otherwise the string is considered
720: * to be found if it appears as a substring in any input line.
721: *
722: * @param ignoreCase
723: * If <code>true</code>, case-insensitive comparison is performed.
724: *
725: * @throws IOException
726: * If I/O error occurs.
727: */
728: public static void expectIn(String str, boolean whole,
729: boolean ignoreCase) throws IOException {
730: expect(new DataInputStream(System.in), str, whole, ignoreCase);
731: }
732:
733: /**
734: * Waits until the specified string appears in the specified stream.
735: *
736: * @param stream
737: * Stream to wait for the string in.
738: *
739: * @param str
740: * String to wait for.
741: *
742: * @param whole
743: * If <code>true</code>, the whole input lines are compared
744: * to the specified string, otherwise the string is considered
745: * to be found if it appears as a substring in any input line.
746: *
747: * @param ignoreCase
748: * If <code>true</code>, case-insensitive comparison is performed.
749: *
750: * @throws IOException
751: * If I/O error occurs.
752: */
753: private static void expect(DataInputStream stream, String str,
754: boolean whole, boolean ignoreCase) throws IOException {
755: if (ignoreCase && !whole) {
756: str = str.toLowerCase();
757: }
758:
759: while (true) {
760: String line = stream.readLine();
761:
762: if (line == null) {
763: // End of stream
764: throw new EOFException();
765: }
766:
767: if (whole ? (ignoreCase ? line.equalsIgnoreCase(str) : line
768: .equals(str)) : ((ignoreCase ? line.toLowerCase()
769: : line).indexOf(str) >= 0)) {
770: // expectString is found
771: return;
772: }
773: }
774: }
775:
776: /**
777: * Automatically transfers data from input to output stream
778: * using new thread.
779: *
780: * Use {@link #start()} method to start the transferring thread.
781: * The thread terminates itself when end of input stream is encountered.
782: */
783: private final class StreamPipe extends Thread {
784:
785: /**
786: * Input stream.
787: */
788: private InputStream input;
789:
790: /**
791: * Output stream.
792: */
793: private OutputStream output;
794:
795: /**
796: * Constructs this object.
797: *
798: * @param input
799: * Input stream to read data from.
800: *
801: * @param output
802: * Output stream to write data to.
803: * Can be <code>null</code>, in this case data are just read
804: * from <code>input</code> stream and discarded.
805: */
806: StreamPipe(InputStream input, OutputStream output) {
807: this .input = input;
808: this .output = output;
809: setDaemon(true);
810: }
811:
812: /**
813: * Runs this thread, called by VM.
814: */
815: public void run() {
816: try {
817: byte[] buffer = new byte[1024];
818: int len;
819:
820: /*
821: * Checking the streams' state seems to be unnecessary,
822: * as soon as the thread is a daemon thread,
823: * and will also exit at first error in either stream.
824: */
825: while ((len = input.read(buffer)) > 0) {
826: if (output != null) {
827: output.write(buffer, 0, len);
828: output.flush();
829: }
830: }
831: // rmi.4B=read(byte[]) returned unexpected value: {0}
832: assert (len == -1) : (Messages.getString("rmi.4B", len)); //$NON-NLS-1$
833: } catch (IOException e) {
834: // rmi.console.07=StreamPipe error:
835: System.err.print(Messages.getString("rmi.console.07")); //$NON-NLS-1$
836: e.printStackTrace();
837: }
838: }
839: }
840: }
|