001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 
007 * '$Revision: 24234 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.sdm.spa;
031
032import java.io.BufferedReader;
033import java.io.BufferedWriter;
034import java.io.File;
035import java.io.IOException;
036import java.io.InputStream;
037import java.io.InputStreamReader;
038import java.io.OutputStreamWriter;
039import java.util.Iterator;
040import java.util.List;
041
042import ptolemy.actor.IOPort;
043import ptolemy.actor.TypedAtomicActor;
044import ptolemy.actor.TypedIOPort;
045import ptolemy.actor.parameters.FilePortParameter;
046import ptolemy.actor.parameters.PortParameter;
047import ptolemy.data.ArrayToken;
048import ptolemy.data.BooleanToken;
049import ptolemy.data.RecordToken;
050import ptolemy.data.StringToken;
051import ptolemy.data.expr.FileParameter;
052import ptolemy.data.expr.Parameter;
053import ptolemy.data.expr.SingletonParameter;
054import ptolemy.data.type.BaseType;
055import ptolemy.kernel.CompositeEntity;
056import ptolemy.kernel.util.Attribute;
057import ptolemy.kernel.util.IllegalActionException;
058import ptolemy.kernel.util.InternalErrorException;
059import ptolemy.kernel.util.NameDuplicationException;
060import ptolemy.kernel.util.Nameable;
061
062// ////////////////////////////////////////////////////////////////////////
063// // CommandLineExec
064/**
065 * Execute a command string.
066 * <p>
067 * Given a command string, the <I>CommandLineExec</I> actor executes it using
068 * the java Runtime class.
069 * </p>
070 * <p>
071 * <b>SUPPORTED COMMAND TYPES:</b>
072 * <ul>
073 * <li>command</li>
074 * <li>command &lt; infile &gt; outfile</li>
075 * <li>command &gt; outfile</li>
076 * <li>command &lt; infile</li>
077 * <li>command [arg1..argn] &gt; outfile</li>
078 * <li>command [arg1..argn]</li>
079 * <li>command [arg1..argn] &lt; infile &gt; outfile</li>
080 * <li>command1 | command 2 <i>(<B>Warning</B>: This type of commands doesn't
081 * give the output of all the commands. Instead it outputs only the result of
082 * the last one.)</i></li>
083 * </ul>
084 * </p>
085 * <p>
086 * <b>Example commands:</b>
087 * <ul>
088 * <li>C:/Program Files/Internet Explorer/IEXPLORE.EXE <br/>
089 * To generate this command, just double click on the actor and type this in the
090 * <i>command</i> parameter field.</li>
091 * <li>C:/cygwin/bin/perl.exe c:/project/kepler/test/workflows/example.pl &gt;
092 * c:/project/kepler/test/workflows/example.out</li>
093 * <li>C:/cygwin/bin/dir.exe &gt; dirTemp.txt</li>
094 * </ul>
095 * </p>
096 * 
097 * @author Ilkay Altintas, Christopher Hylands Brooks, Bilsay Yildirim,
098 *         Contributor: Edward A. Lee
099 * @version $Id: CommandLineExec.java 24234 2010-05-06 05:21:26Z welker $
100 */
101
102public class CommandLineExec extends TypedAtomicActor {
103
104        /**
105         * Construct a CommandLine actor with the given container and name.
106         * 
107         * @param container
108         *            The container.
109         * @param name
110         *            The name of this actor.
111         * @exception IllegalActionException
112         *                If the actor cannot be contained by the proposed
113         *                container.
114         * @exception NameDuplicationException
115         *                If the container already has an actor with this name.
116         */
117        public CommandLineExec(CompositeEntity container, String name)
118                        throws IllegalActionException, NameDuplicationException {
119                super(container, name);
120
121                // Uncomment the next line to see debugging statements
122                // addDebugListener(new ptolemy.kernel.util.StreamListener());
123
124                stdString = errString = null;
125
126                // Construct parameter
127                command = new PortParameter(this, "command");
128                new Attribute(command, "_showName");
129
130                outputFile = new FilePortParameter(this, "outputFile");
131                outputFile.setTypeEquals(BaseType.STRING);
132                new Attribute(outputFile, "_showName");
133
134                // Array with an empty name and value means
135                // default environment of the calling process.
136                environment = new Parameter(this, "environment");
137                environment.setExpression("{{name = \"\", value = \"\"}}");
138
139                directory = new FileParameter(this, "directory");
140                directory.setExpression("$CWD");
141
142                // Construct input ports.
143                arguments = new TypedIOPort(this, "arguments", true, false);
144                arguments.setMultiport(true);
145                new Attribute(arguments, "_showName");
146
147                inputStream = new TypedIOPort(this, "inputStream", true, false);
148                new Attribute(inputStream, "_showName");
149
150                infileHandle = new TypedIOPort(this, "infileHandle", true, false);
151                infileHandle.setTypeEquals(BaseType.STRING);
152                new Attribute(infileHandle, "_showName");
153
154                trigger = new TypedIOPort(this, "trigger", true, false);
155
156                hide = new SingletonParameter(trigger, "_hide");
157                hide.setToken(BooleanToken.TRUE);
158
159                // Construct output ports.
160                outfileHandle = new TypedIOPort(this, "outfileHandle", false, true);
161                outfileHandle.setTypeEquals(BaseType.STRING);
162                new Attribute(outfileHandle, "_showName");
163
164                exitCode = new TypedIOPort(this, "exitCode", false, true);
165                exitCode.setTypeEquals(BaseType.STRING);
166                new Attribute(exitCode, "_showName");
167
168                output = new TypedIOPort(this, "output", false, true);
169                output.setTypeEquals(BaseType.STRING);
170                new Attribute(output, "_showName");
171
172                // Set Flags.
173                outputLineByLine = new Parameter(this, "outputLineByLine",
174                                new BooleanToken(false));
175                outputLineByLine.setTypeEquals(BaseType.BOOLEAN);
176
177                waitForProcess = new Parameter(this, "waitForProcess",
178                                new BooleanToken(false));
179                waitForProcess.setTypeEquals(BaseType.BOOLEAN);
180
181                hasTrigger = new Parameter(this, "hasTrigger", new BooleanToken(false));
182                hasTrigger.setTypeEquals(BaseType.BOOLEAN);
183
184                _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
185                                + "width=\"60\" height=\"30\" " + "style=\"fill:white\"/>\n"
186                                + "<text x=\"20\" y=\"25\" "
187                                + "style=\"font-size:30; fill:blue; font-family:SansSerif\">"
188                                + "$</text>\n" + "</svg>\n");
189
190        } // constructor
191
192        // //////////////// Public ports and parameters ///////////////////////
193
194        /**
195         * The attribute to hide or show the trigger port without deleting it.
196         */
197        public SingletonParameter hide;
198
199        /**
200         * The command to execute.
201         */
202        public PortParameter command;
203
204        /**
205         * Needs to be filled in if the user wants the command to output to a file.
206         */
207        public FilePortParameter outputFile;
208
209        /**
210         * The arguments to the command. Implemented as a multi/input port to
211         * support more than one argument. It concatanates the inputs in all the
212         * channels. <br/>
213         * <I>If there is an input file, this port can be empty.</I>
214         */
215        public TypedIOPort arguments;
216
217        /**
218         * Strings to pass to the standard input of the subprocess. This port is an
219         * input port of type String.
220         */
221        public TypedIOPort inputStream;
222
223        /**
224         * This is an optional port. Used if the file accepts an input file instead
225         * of a list of arguments.
226         */
227        public TypedIOPort infileHandle;
228
229        /**
230         * The trigger port. The type of this port is undeclared, meaning that it
231         * will resolve to any data type.
232         */
233        public TypedIOPort trigger;
234
235        /**
236         * A string that forwards the outputFile parameter if it exists.
237         */
238        public TypedIOPort outfileHandle;
239        /**
240         * The result stream of the command.
241         */
242        public TypedIOPort output;
243
244        /**
245         * Exit code will be 1 if the command executes successfully.
246         */
247        public TypedIOPort exitCode;
248
249        /**
250         * If selected, broadcasts the output of the command line by line.
251         * 
252         */
253        public Parameter outputLineByLine;
254
255        /**
256         * Unhide the trigger port when this parameter is true. This Parameter is
257         * type of boolean.
258         * 
259         * @UserLevelDocumentation <br/>
260         *                         <b>NOTE: </b>in fact, user can use the port
261         *                         configuration window to hide or unhide a port.
262         *                         This paremeter is here to provide a more
263         *                         intuitive interface for this actor.
264         */
265        public Parameter hasTrigger;
266
267        /*
268         * The environment to execute the command.
269         */
270        public Parameter environment;
271
272        /*
273         * The directory to execute the command
274         */
275        public FileParameter directory;
276
277        /*
278         * This allows parameter to allow the process to block the threads until it
279         * exits.
280         */
281        public Parameter waitForProcess;
282
283        // /////////////////////////////////////////////////////////////////
284        // // public methods ////
285
286        /**
287         * If the specified attribute is <i>showTriggerPort</i>, then get the value
288         * of it and re-render the trigger port. If it is true, show the trigger
289         * port; if it is false, hide the trigger port.
290         * 
291         * @param attribute
292         *            The attribute that has changed.
293         * @exception IllegalActionException.
294         */
295        public void attributeChanged(Attribute attribute)
296                        throws IllegalActionException {
297                if (attribute == hasTrigger) {
298                        _triggerFlag = ((BooleanToken) hasTrigger.getToken())
299                                        .booleanValue();
300                        _debug("<TRIGGER_FLAG>" + _triggerFlag + "</TRIGGER_FLAG>");
301                        if (_triggerFlag) {
302                                try {
303                                        trigger.setContainer(this);
304                                        // new Attribute(trigger, "_showName");
305                                        hide.setToken(BooleanToken.FALSE); // DFH
306                                } catch (NameDuplicationException ndex) {
307                                        _debug("111: " + ndex.getMessage());
308                                }
309                        } else {
310                                List inPortList = this.inputPortList();
311                                Iterator ports = inPortList.iterator();
312                                while (ports.hasNext()) {
313                                        IOPort p = (IOPort) ports.next();
314                                        if (p.isInput()) {
315                                                try {
316                                                        if (p.getName().equals("trigger")) {
317                                                                // new Attribute(trigger, "_hideName"); //DFH
318                                                                // p.setContainer(null); //DFH
319                                                                hide.setToken(BooleanToken.TRUE); // DFH
320                                                        }
321                                                } catch (Exception e) {
322                                                        throw new IllegalActionException(this, e
323                                                                        .getMessage());
324                                                }
325                                        }
326                                }// while
327                        }// else
328                } else {
329                        super.attributeChanged(attribute);
330                }
331        }// attributeChanged
332
333        /**
334         * Send the exitCode and outputFileHandle(optional) to the result port.
335         * 
336         * @exception IllegalActionException
337         *                If there is no director.
338         */
339        public void fire() throws IllegalActionException {
340
341                // update the values in the PortParameters
342                command.update();
343                outputFile.update();
344
345                stdString = null;
346                errString = "1";
347                _lineFlag = ((BooleanToken) outputLineByLine.getToken()).booleanValue();
348                _debug("<TRIGGER_FLAG>" + _lineFlag + "</TRIGGER_FLAG>");
349
350                _waitForProcessFlag = ((BooleanToken) waitForProcess.getToken())
351                                .booleanValue();
352
353                // Get Directory
354                directoryAsFile = directory.asFile();
355                if (_debugging) {
356                        _debug("About to exec \""
357                                        + ((StringToken) command.getToken()).stringValue() + "\""
358                                        + "\n in \"" + directoryAsFile + "\"\n with environment:");
359                }
360                // Process the environment parameter.
361                ArrayToken environmentTokens = (ArrayToken) environment.getToken();
362
363                if (_debugging) {
364                        _debug("environmentTokens: " + environmentTokens);
365                }
366
367                environmentArray = null;
368
369                if (environmentTokens.length() >= 1) {
370                        environmentArray = new String[environmentTokens.length()];
371
372                        for (int i = 0; i < environmentTokens.length(); i++) {
373                                StringToken nameToken = (StringToken) (((RecordToken) environmentTokens
374                                                .getElement(i)).get("name"));
375                                StringToken valueToken = (StringToken) (((RecordToken) environmentTokens
376                                                .getElement(i)).get("value"));
377                                environmentArray[i] = nameToken.stringValue() + "="
378                                                + valueToken.stringValue();
379
380                                if (_debugging) {
381                                        _debug("  " + i + ". \"" + environmentArray[i] + "\"");
382                                }
383
384                                if ((i == 0) && (environmentTokens.length() == 1)
385                                                && environmentArray[0].equals("=")) {
386                                        if (_debugging) {
387                                                _debug("There is only one element, "
388                                                                + "it is a string of length 0,\n so we "
389                                                                + "pass Runtime.exec() an null "
390                                                                + "environment so that we use\n "
391                                                                + "the default environment");
392                                        }
393                                        environmentArray = null;
394                                }
395                        }
396                }
397
398                if (((StringToken) command.getToken()).stringValue() != null) {
399                        String _commandStr = ((StringToken) command.getToken())
400                                        .stringValue();
401
402                        // simply consume the trigger token if there is some.
403                        if (_triggerFlag) {
404                                if (trigger.getWidth() > 0) {
405                                        // if (!((BooleanToken) trigger.get(0)).equals(null)) {
406                                        if (trigger.hasToken(0)) {
407                                                trigger.get(0);
408                                                _debug("consume the token at the trigger port.");
409                                        }
410                                }
411                        }
412
413                        // Consume the input file token if there's one.
414
415                        try {
416                                if ((infileHandle.numberOfSources() > 0)) {
417                                        String value = ((StringToken) infileHandle.get(0))
418                                                        .stringValue();
419                                        if (value.length() > 0) {
420                                                _debug("infileHandle(i) = " + value);
421                                                if (value.startsWith("file:/"))
422                                                        value = value.substring(7);
423                                                _commandStr += " < " + value;
424                                        }
425                                }
426                        } catch (IllegalActionException ex) {
427                                _debug("Input file is null.");
428                        }
429
430                        // Create the argument string. Consume data in all the channels and
431                        // combine them
432                        String argString = "";
433                        int i = 0;
434                        int width = arguments.getWidth();
435
436                        for (i = 0; i < width; i++) {
437                                String value = ((StringToken) arguments.get(i)).stringValue();
438                                if (value.length() > 0) {
439                                        _debug("arguments(i) = " + value);
440                                        while (value.indexOf("\\\"") != -1) {
441                                                int ind = value.indexOf("\\\"");
442                                                value = value.substring(0, ind)
443                                                                + value.substring(ind + 1, value.length());
444                                                _debug(value);
445                                        }
446
447                                        argString += value + " ";
448                                        _debug("argString = " + argString);
449                                }
450                        }
451                        _commandStr += " " + argString;
452
453                        int commandCount = getSystemProps();
454
455                        // Get the output file path if there's one and add it to the command
456                        // string.
457                        if (((StringToken) outputFile.getToken()).stringValue() == "") {
458                                _debug("Output file is null.");
459                                outfileHandle.broadcast(new StringToken(""));
460                        } else {
461                                String outFilePath = outputFile.stringValue();
462                                if (outFilePath.startsWith("file:///")) {
463                                        if (_charsToSkip == 6) {
464                                                _charsToSkip = 8;
465                                        } else if (_charsToSkip == 5) {
466                                                _charsToSkip = 7;
467                                        }
468                                }
469                                if (outFilePath.startsWith("file:/")) {
470                                        outFilePath = outputFile.stringValue().substring(
471                                                        _charsToSkip);
472                                }
473                                _commandStr += " > " + outFilePath;
474
475                                if (!outFilePath.equals(""))
476                                        outfileHandle.broadcast(new StringToken(outFilePath));
477                        }
478
479                        _commandArr[commandCount] = _commandStr;
480                        _debug("<COMMAND>" + _commandArr[commandCount] + "</COMMAND>");
481                        // EXECUTION OF THE GENERATED COMMAND.
482                        _debug("Executing the command...");
483
484                        try {
485                                _stopFireRequested = false;
486
487                                // Execute the command
488                                Runtime rt = Runtime.getRuntime();
489                                proc = rt.exec(_commandArr, environmentArray, directoryAsFile);
490
491                                InputStream inStream = proc.getInputStream();
492                                _outputGobbler = new _StreamReaderThread(inStream,
493                                                "Exec Stdout Gobbler-" + _streamReaderThreadCount++,
494                                                this, 1);
495
496                                InputStream errStream = proc.getErrorStream();
497                                _errorGobbler = new _StreamReaderThread(errStream,
498                                                "Exec Stderr Gobbler-" + _streamReaderThreadCount++,
499                                                this, 2);
500
501                                if (_streamReaderThreadCount > 1000) {
502                                        // Avoid overflow in the thread count.
503                                        _streamReaderThreadCount = 0;
504                                }
505
506                                _errorGobbler.start();
507                                _outputGobbler.start();
508
509                                // We could have a parameter that if it was set
510                                // we would throw an exception if there was any error data.
511                                if (!procDone(proc)) {
512                                        String line = null;
513                                        if ((inputStream.numberOfSources() > 0)) {
514                                                line = ((StringToken) inputStream.get(0)).stringValue();
515                                                if (line.length() > 0) {
516                                                        if (_debugging) {
517                                                                _debug("CommandLine: Input: '" + line + "'");
518                                                        }
519                                                        OutputStreamWriter inputStreamWriter = new OutputStreamWriter(
520                                                                        proc.getOutputStream());
521                                                        _inputBufferedWriter = new BufferedWriter(
522                                                                        inputStreamWriter);
523                                                        if (_inputBufferedWriter != null) {
524                                                                try {
525                                                                        _inputBufferedWriter.write(line);
526                                                                        _inputBufferedWriter.flush();
527                                                                } catch (IOException ex) {
528                                                                        throw new IllegalActionException(this, ex,
529                                                                                        "Problem writing input '" + line
530                                                                                                        + "'");
531                                                                }
532                                                        }
533                                                }
534                                        }
535                                }
536
537                                // close the child proc's stdin
538                                // (some programs reading stdin expect it to be
539                                // closed before excuting, e.g. cat).
540                                proc.getOutputStream().close();
541
542                                // Wait for the Process to finish if it is indicated
543                                if (_waitForProcessFlag) {
544                                        try {
545                                                // The next line waits for the subprocess to finish.
546                                                int processReturnCode = proc.waitFor();
547                                                // wait for stream gobbler threads to finish
548                                                while (_errorGobbler.isAlive()
549                                                                || _outputGobbler.isAlive()) {
550                                                        Thread.yield();
551                                                }
552                                                if (processReturnCode != 0) {
553                                                        // We could have a parameter that would enable or
554                                                        // disable this.
555                                                        throw new IllegalActionException(
556                                                                        this,
557                                                                        "Executing command \""
558                                                                                        + ((StringToken) command.getToken())
559                                                                                                        .stringValue()
560                                                                                        + "\" returned a non-zero return value of "
561                                                                                        + processReturnCode);
562                                                }
563                                        } catch (InterruptedException interrupted) {
564                                                throw new InternalErrorException(this, interrupted,
565                                                                "_process.waitFor() was interrupted");
566                                        } catch (IllegalActionException e) {
567                                                throw new InternalErrorException(this, e,
568                                                                "exec'd process exited with non zero exit code");
569                                        }
570                                }
571
572                                if (_debugging) {
573                                        _debug("Exec: Error: '" + errString + "'");
574                                        _debug("Exec: Output: '" + stdString + "'");
575                                }
576                        } catch (Exception ex) {
577                                _debug("<EXCEPTION> An exception occured when executing the command. "
578                                                + " \n\t Exception: " + ex + "\n</EXCEPTION>");
579                        }
580                        // Send error and output to ports
581                        output.send(0, new StringToken(stdString));
582                        exitCode.send(0, new StringToken(errString));
583                }
584        } // end-of-fire
585
586        // Check if the process is done or not
587        private boolean procDone(Process p) {
588                try {
589                        p.exitValue();
590                        return true;
591                } catch (IllegalThreadStateException e) {
592                        return false;
593                }
594        }
595
596        // Get system properties and set the command array according to it
597        public int getSystemProps() throws IllegalActionException {
598                // Get OS name
599                String osName = System.getProperty("os.name");
600                // System.out.println(((StringToken)
601                // shell.getToken()).stringValue().toString());
602                // String sh="sh";
603                _debug("<OS>" + osName + "</OS>");
604
605                if (osName.equals("Windows NT") || osName.equals("Windows XP")
606                                || osName.equals("Windows 2000")) {
607
608                        _commandArr[0] = "cmd.exe";
609                        _commandArr[1] = "/C";
610                        _charsToSkip = 6;
611                        /*
612                         * } else{
613                         * 
614                         * _commandArr[0] ="C:'\''cygwin'\''cywgin.bat"; _commandArr[1] =
615                         * "-c"; _charsToSkip = 6; }
616                         */
617                        return 2;
618                } else if (osName.equals("Windows 95")) {
619                        _commandArr[0] = "command.com";
620                        _commandArr[1] = "/C";
621                        _charsToSkip = 6;
622                        return 2;
623                } else {
624                        _commandArr[0] = "/bin/sh";
625                        _commandArr[1] = "-c";
626                        _charsToSkip = 5;
627                        return 2;
628                        /* return 0; */
629                }
630        } // end-of-getSystemProps
631
632        /**
633         * Override the base class to stop waiting for input data.
634         */
635        public void stopFire() {
636                // NOTE: This method used to be synchronized, as
637                // was the fire() method, but this caused deadlocks. EAL
638                super.stopFire();
639                _stopFireRequested = true;
640
641                try {
642                        _terminateProcess();
643                } catch (IllegalActionException ex) {
644                        throw new InternalErrorException(ex);
645                }
646        }
647
648        private void _terminateProcess() throws IllegalActionException {
649                if (proc != null) {
650                        proc.destroy();
651                        proc = null;
652                }
653        }
654
655        /**
656         * Terminate the subprocess. This method is invoked exactly once per
657         * execution of an application. None of the other action methods should be
658         * be invoked after it.
659         * 
660         * @exception IllegalActionException
661         *                Not thrown in this base class.
662         */
663        public void wrapup() throws IllegalActionException {
664                _terminateProcess();
665        }
666
667        // /////////////////////////////////////////////////////////////////
668        // // inner classes ////
669        // Private class that reads a stream in a thread and updates the
670        // stringBuffer.
671        private class _StreamReaderThread extends Thread {
672                /**
673                 * Create a _StreamReaderThread.
674                 * 
675                 * @param inputStream
676                 *            The stream to read from.
677                 * @param name
678                 *            The name of this StreamReaderThread, which is useful for
679                 *            debugging.
680                 * @param actor
681                 *            The parent actor of this thread, which is used in error
682                 *            messages.
683                 */
684                _StreamReaderThread(InputStream inputStream, String name,
685                                Nameable actor, int ID) {
686                        super(name);
687                        _inputStream = inputStream;
688                        _inputStreamReader = new InputStreamReader(_inputStream);
689                        _actor = actor;
690                        _stringBuffer = new StringBuffer();
691                        myID = ID;
692                }
693
694                /**
695                 * Read lines from the inputStream and append them to the stringBuffer.
696                 */
697                public void run() {
698                        if (!_inputStreamReaderClosed) {
699                                _read();
700                        }
701                }
702
703                private void _read() {
704                        // We read the data as a char[] instead of using readline()
705                        // so that we can get strings that do not end in end of
706                        // line chars.
707
708                        try {
709                                br = new BufferedReader(_inputStreamReader);
710                                String line = null;
711
712                                while ((line = br.readLine()) != null && !_stopFireRequested) {
713                                        if (line.length() != 0) {
714                                                if (_debugging) {
715                                                        // Note that ready might be false here since
716                                                        // we already read the data.
717                                                        _debug("_read(): Gobbler '" + getName()
718                                                                        + "' Ready: " + line + " Value: '" + line
719                                                                        + "'");
720                                                }
721                                                _stringBuffer.append(line + "\n");
722                                        }
723                                }
724                        } catch (IOException ex) {
725                                throw new InternalErrorException(_actor, ex, getName()
726                                                + ": Failed while reading from " + _inputStream);
727                        }
728                        // if this thread is reading in stdout, then give this new string to
729                        // the actor's stdString
730                        if (myID == 1) {
731                                stdString = _stringBuffer.toString();
732                        }
733                        // if this thread is reading in stderr, then give this new string to
734                        // the actor's errString
735                        if (myID == 2) {
736                                errString = _stringBuffer.toString();
737                                if ((errString == null) || (errString.length() == 0))
738                                        errString = "1";
739                        }
740                }
741
742                // Last parameter entered on the constructor.
743                // 1 indicates storing the standard output, 2 indicates coying to the
744                // standard error.
745                private int myID;
746
747                // The actor associated with this stream reader.
748                private Nameable _actor;
749
750                // Stream from which to read.
751                private InputStreamReader _inputStreamReader;
752
753                // Indicator that the stream has been closed.
754                private boolean _inputStreamReaderClosed = false;
755
756                // StringBuffer to update.
757                private StringBuffer _stringBuffer;
758
759                // BufferReader
760                private BufferedReader br;
761        }
762
763        // ////////////////////////////////////////////////////////////////////
764        // // private variables ////
765
766        // StreamReader with which we read stderr.
767        private _StreamReaderThread _errorGobbler;
768
769        // Stream from which to read.
770        private InputStream _inputStream;
771        // StreamReader with which we read stdout.
772        private _StreamReaderThread _outputGobbler;
773        // The subprocess gets its input from this BufferedWriter.
774        private BufferedWriter _inputBufferedWriter;
775        // The combined command to execute.
776        private String _commandArr[] = new String[3];
777        private boolean _lineFlag = false;
778        private boolean _waitForProcessFlag = false;
779        private String[] environmentArray;
780        private File directoryAsFile;
781        private boolean _triggerFlag = false;
782        // Indicator that stopFire() has been called.
783        private boolean _stopFireRequested = false;
784        private Process proc;
785        private int _charsToSkip = 6;
786        // Instance count of output and error threads, used for debugging.
787        // When the value is greater than 1000, we reset it to 0.
788        private static int _streamReaderThreadCount = 0;
789
790        private String stdString, errString;
791} // end-of-class-CommandLine