001/* Execute a command in a subprocess. 002 003 Copyright (c) 2004-2018 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.lib; 029 030import java.io.BufferedWriter; 031import java.io.File; 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.InputStreamReader; 035import java.io.OutputStreamWriter; 036import java.util.Arrays; 037import java.util.LinkedList; 038import java.util.List; 039 040import ptolemy.actor.TypedIOPort; 041import ptolemy.actor.parameters.PortParameter; 042import ptolemy.data.ArrayToken; 043import ptolemy.data.BooleanToken; 044import ptolemy.data.IntToken; 045import ptolemy.data.RecordToken; 046import ptolemy.data.StringToken; 047import ptolemy.data.expr.FileParameter; 048import ptolemy.data.expr.Parameter; 049import ptolemy.data.type.ArrayType; 050import ptolemy.data.type.BaseType; 051import ptolemy.data.type.RecordType; 052import ptolemy.data.type.Type; 053import ptolemy.kernel.CompositeEntity; 054import ptolemy.kernel.util.IllegalActionException; 055import ptolemy.kernel.util.InternalErrorException; 056import ptolemy.kernel.util.NameDuplicationException; 057import ptolemy.kernel.util.Nameable; 058import ptolemy.util.StringUtilities; 059 060/////////////////////////////////////////////////////////////////// 061//// Exec 062 063/** 064 Execute a command as a separately running subprocess. 065 066 <p>This actor uses java.lang.Runtime.exec() to invoke a subprocess 067 named by the <i>command</i> parameter in a specified <i>directory</i> with a 068 specified <i>environment</i>. Data from the <i>input</i> port (if any) is 069 passed to the input of the subprocess. The subprocess is run until it 070 exits and then contents of the output and error streams of the 071 subprocess (if any) are passed to the <i>output</i> and <i>error</i> 072 ports.</p> 073 074 <p>If the subprocess generates no data on the output or error stream, 075 then the data on the corresponding port(s) will consist of the empty 076 string.</p> 077 078 <p> To get the effect of executing a command provided in a shell interpreter, set the 079 <i>prependPlatformDependentShellCommand</i> parameter to true. 080 This will prepend a default platform-dependent shell command to the command 081 you wish to execute so that your command is executed within the shell. 082 Alternatively, you can set <i>command</i> to "cmd" (Windows) or "sh" (Windows with Cygwin 083 or Linux), and then provide commands at the <i>input</i> port. 084 In this case, however, your model will only work on platforms that have the shell 085 command you have specified. 086 Note that in this case each command must be terminated with a newline. 087 For example, to open a model in vergil and run it, you can 088 set <i>command</i> to "sh" and use a Const actor to provide 089 on the <i>input</i> port the string:</p> 090 <pre> 091 "vergil -run model.xml\n exit\n" 092 </pre> 093 094 <p>A much more interesting actor could be written using a 095 Kahn Process Network. This actor would generate output asynchronously 096 as the process was executing.</p> 097 098 <p>For information about Runtime.exec(), see: 099 <br><a href="http://www.javaworld.com/javaworld/jw-12-2007/jw-1229-traps.html" target="_top">http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html</a> 100 and 101 <a href="http://mindprod.com/jgloss/exec.html" target="_top">http://mindprod.com/jgloss/exec.html</a>. 102 </p> 103 104 @author Christopher Hylands Brooks, Contributors: Edward A. Lee, Daniel Crawl 105 @version $Id$ 106 @since Ptolemy II 4.0 107 @Pt.ProposedRating Yellow (cxh) 2/5/04 108 @Pt.AcceptedRating Yellow (cxh) 2/24/04 109 */ 110public class Exec extends LimitedFiringSource { 111 /** Construct an actor with the given container and name. 112 * @param container The container. 113 * @param name The name of this actor. 114 * @exception IllegalActionException If the actor cannot be contained 115 * by the proposed container. 116 * @exception NameDuplicationException If the container already has an 117 * actor with this name. 118 */ 119 public Exec(CompositeEntity container, String name) 120 throws NameDuplicationException, IllegalActionException { 121 super(container, name); 122 123 // Uncomment the next line to see debugging statements 124 //addDebugListener(new ptolemy.kernel.util.StreamListener()); 125 command = new PortParameter(this, "command"); 126 // Make command be a StringParameter (no surrounding double quotes). 127 command.setStringMode(true); 128 command.setExpression("echo \"Hello world.\""); 129 130 new Parameter(command.getPort(), "_showName", BooleanToken.TRUE); 131 132 directory = new FileParameter(this, "directory"); 133 new Parameter(directory, "allowFiles", BooleanToken.FALSE); 134 new Parameter(directory, "allowDirectories", BooleanToken.TRUE); 135 directory.setExpression("$CWD"); 136 137 environment = new Parameter(this, "environment"); 138 139 String[] labels = new String[] { "name", "value" }; 140 Type[] values = new Type[] { BaseType.STRING, BaseType.STRING }; 141 142 // An array of records {{name = "", value = ""}} 143 environment 144 .setTypeEquals(new ArrayType(new RecordType(labels, values))); 145 146 // Array with an empty name and value means 147 // default environment of the calling process. 148 environment.setExpression("{{name = \"\", value = \"\"}}"); 149 150 error = new TypedIOPort(this, "error", false, true); 151 error.setTypeEquals(BaseType.STRING); 152 new Parameter(error, "_showName", BooleanToken.TRUE); 153 154 ignoreIOExceptionReadErrors = new Parameter(this, 155 "ignoreIOExceptionReadErrors", BooleanToken.FALSE); 156 ignoreIOExceptionReadErrors.setTypeEquals(BaseType.BOOLEAN); 157 158 input = new TypedIOPort(this, "input", true, false); 159 input.setTypeEquals(BaseType.STRING); 160 new Parameter(input, "_showName", BooleanToken.TRUE); 161 162 output.setTypeEquals(BaseType.STRING); 163 new Parameter(output, "_showName", BooleanToken.TRUE); 164 165 exitCode = new TypedIOPort(this, "exitCode", false, true); 166 exitCode.setTypeEquals(BaseType.INT); 167 new Parameter(exitCode, "_showName", BooleanToken.TRUE); 168 169 prependPlatformDependentShellCommand = new Parameter(this, 170 "prependPlatformDependentShellCommand", BooleanToken.FALSE); 171 prependPlatformDependentShellCommand.setTypeEquals(BaseType.BOOLEAN); 172 173 throwExceptionOnNonZeroReturn = new Parameter(this, 174 "throwExceptionOnNonZeroReturn", BooleanToken.TRUE); 175 throwExceptionOnNonZeroReturn.setTypeEquals(BaseType.BOOLEAN); 176 177 waitForProcess = new Parameter(this, "waitForProcess", 178 BooleanToken.TRUE); 179 waitForProcess.setTypeEquals(BaseType.BOOLEAN); 180 181 // Show the firingCountLimit parameter last. 182 firingCountLimit.moveToLast(); 183 } 184 185 /////////////////////////////////////////////////////////////////// 186 //// ports and parameters //// 187 188 /** The command to be executed. The command is parsed by 189 * {@link ptolemy.util.StringUtilities#tokenizeForExec(String)} 190 * into tokens and then executed as a separate subprocess. 191 * The initial default value is the string 192 * <code>echo "Hello, world."</code>. 193 * 194 * <p>The command parameter is read only once during fire(). 195 * If you want to spawn another different command, 196 * use life cycle management actors such RunCompositeActor.</p> 197 */ 198 public PortParameter command; 199 200 /** The directory in which to execute the command. 201 * This parameter is read each time the subprocess is started 202 * in fire(). Once the subprocess is running, this parameter is not 203 * read again until fire() is called again. 204 * 205 * <p>The initial default value of this parameter $CWD, which 206 * corresponds with the value of the Java virtual machine 207 * user.dir property which is the user's current working 208 * directory. Note that if we are running inside a menu launched 209 * application, then ptolemy.actor.gui.jnlp.MenuApplication will 210 * change user.dir to be the value of user.home, which is the 211 * name of the user's home directory.</p> 212 * 213 * <p>If the value of this parameter is the empty string, 214 * then the working directory of the subprocess with be 215 * inherited from the working directory of the parent 216 * process. Typically, this is the value of the 217 * user.dir property.</p> 218 */ 219 public FileParameter directory; 220 221 /** The environment in which to execute the command. 222 * This parameter is read each time the subprocess is started 223 * in fire(). Once the subprocess is running, this parameter is not 224 * read again until fire() is called again. 225 * 226 * <p>This parameter is an array of records that name an environment 227 * variable and the value for the value. The format is:</p> 228 * <pre> 229 * {{name = "<i>NAME1</i>", value = "<i>value1</i>"}...} 230 * </pre> 231 * Where <code><i>NAME1</i></code> is the name of the environment 232 * variable, and <code><i>value1</i></code> is the value. 233 * <p>For example <code>{{name = "PTII", value = "c:/ptII"}}</code> 234 * would set the value of the <code>PTII</code> to <code>c:/ptII</code>.</p> 235 * 236 * <p>If the initial value of the parameter is <code>{{name="", 237 * value = ""}}</code>, then the environment from the calling or parent 238 * process is used in the new command.</p> 239 * 240 * <p>Note that if this parameter sets any environment variable, 241 * then under Windows the other environment variables in the calling 242 * or parent process might not be passed to the subprocess. This 243 * behaviour could be platform or JVM dependent. When in doubt, 244 * try setting the <i>command</i> value to "env" to print out the 245 * environment.</p> 246 */ 247 public Parameter environment; 248 249 /** Data that is generated by the subprocess on its standard 250 * error. While the process is running, any error data generated 251 * by the subprocess is stored until the subprocess exits and 252 * then the stored error data is sent to the <i>error</i> port. 253 * If the subprocess generates no data on standard error, then 254 * the empty string (a string of length zero) is generated. 255 * This port is an output port of type String. 256 */ 257 public TypedIOPort error; 258 259 /** The exit code of the subprocess. Usually, a non-zero exit code 260 * indicate that the subprocess had a problem. This port is an output 261 * port of type int. 262 */ 263 public TypedIOPort exitCode; 264 265 /** If true, ignore IOException errors from the subprocess. 266 * The initial default value is false, indicating that 267 * read errors are not ignored. 268 */ 269 public Parameter ignoreIOExceptionReadErrors; 270 271 /** Strings to pass to the standard input of the subprocess. 272 * Note that a newline is not appended to the string. If you 273 * require a newline, add one using the AddSubtract actor. 274 * This port is an input port of type String. 275 */ 276 public TypedIOPort input; 277 278 /** Data that is generated by the subprocess on standard out. 279 * While the process is running, any output data generated 280 * by the subprocess is stored until the subprocess exits and 281 * then the stored output data is sent to the <i>output</i> port. 282 * If the subprocess generates no data on standard out, then 283 * the empty string (a string of length zero) is generated. 284 * This port is an output port of type String. 285 */ 286 // NOTE: output port is inherited from parent class. 287 //public TypedIOPort output; 288 289 /** If true, then prepend the platform dependent shell command 290 * to the parsed value of the command parameter. 291 * By setting this argument to true, it is possible to invoke 292 * commands in a platform neutral method. 293 * <p>Under Windows NT or XP, the arguments "cmd.exe" and "/C" 294 * are prepended. Under Windows 95, the arguments "command.com" 295 * and "/C" are prepended. Under all other platforms, the 296 * arguments "/bin/sh" and "-c" are prepended. 297 * <p>By prepending sh or cmd, then this actor can use the 298 * file redirection operations. 299 * <p>The default value of this parameter is a boolean of value 300 * false, which allows the user to arbitrarily invoke /bin/sh 301 * scripts on all platforms. 302 */ 303 public Parameter prependPlatformDependentShellCommand; 304 305 /** If true, then throw an exception if the subprocess returns 306 * non-zero. 307 * The default is a boolean of value true. 308 * This parameter is ignored if <i>waitForProcess</i> is false. 309 */ 310 public Parameter throwExceptionOnNonZeroReturn; 311 312 /** If true, then actor will wait until subprocess completes. The 313 * default is a boolean of value true. 314 */ 315 public Parameter waitForProcess; 316 317 /////////////////////////////////////////////////////////////////// 318 //// public methods //// 319 320 /** Invoke a subprocess, read the <i>input</i> data (if any) and 321 * wait for the subprocess to terminate before sending any output 322 * or error data to the appropriate ports. 323 * 324 * <p>If there is no data on the <i>input</i> port, then the 325 * subprocess executes without reading any input. If there is no 326 * output or error data from the subprocess, then the empty 327 * string is sent to the appropriate port(s).</p> 328 * 329 * @exception IllegalActionException If the subprocess cannot be 330 * started, if the input of the subprocess cannot be written, 331 * if the subprocess gets interrupted, or if the return value 332 * of the process is non-zero. 333 */ 334 @Override 335 public void fire() throws IllegalActionException { 336 // NOTE: This used to be synchronized, but this causes a 337 // deadlock with the UI when parameters are edited while 338 // model is running. 339 super.fire(); 340 341 String line = null; 342 343 _exec(); 344 345 if (input.numberOfSources() > 0 && input.hasToken(0)) { 346 if ((line = ((StringToken) input.get(0)).stringValue()) != null) { 347 if (_debugging) { 348 _debug("Exec: Input: '" + line + "'"); 349 } 350 351 if (_inputBufferedWriter != null) { 352 try { 353 _inputBufferedWriter.write(line); 354 _inputBufferedWriter.flush(); 355 } catch (IOException ex) { 356 throw new IllegalActionException(this, ex, 357 "Problem writing input '" + command + "'"); 358 } 359 } 360 } 361 } 362 363 boolean alreadySentOutput = false; 364 365 try { 366 367 // Close the stdin of the subprocess. 368 _process.getOutputStream().close(); 369 370 boolean waitForProcessValue = ((BooleanToken) waitForProcess 371 .getToken()).booleanValue(); 372 373 if (waitForProcessValue) { 374 // The next line waits for the subprocess to finish. 375 int processReturnCode = _process.waitFor(); 376 377 if (processReturnCode != 0) { 378 // We could have a parameter that would enable 379 // or disable this. 380 String outputString = ""; 381 String errorString = ""; 382 383 try { 384 errorString = _errorGobbler.getAndReset(); 385 } catch (Exception ex) { 386 errorString = ex.toString(); 387 } 388 389 try { 390 outputString = _outputGobbler.getAndReset(); 391 } catch (Exception ex) { 392 outputString = ex.toString(); 393 } 394 395 boolean throwExceptionOnNonZeroReturnValue = ((BooleanToken) throwExceptionOnNonZeroReturn 396 .getToken()).booleanValue(); 397 398 if (throwExceptionOnNonZeroReturnValue) { 399 throw new IllegalActionException(this, 400 "Executing command \"" 401 + ((StringToken) command.getToken()) 402 .stringValue() 403 + "\" returned a non-zero return value of " 404 + processReturnCode 405 + ".\nThe last input was: " + line 406 + ".\nThe standard output was: " 407 + outputString 408 + "\nThe error output was: " 409 + errorString); 410 } else { 411 error.send(0, new StringToken(errorString)); 412 output.send(0, new StringToken(outputString)); 413 alreadySentOutput = true; 414 } 415 } 416 417 exitCode.send(0, new IntToken(processReturnCode)); 418 419 } 420 } catch (InterruptedException interrupted) { 421 throw new InternalErrorException(this, interrupted, 422 "_process.waitFor() was interrupted"); 423 } catch (IOException io) { 424 throw new IllegalActionException(this, io, 425 "Closing stdin of the subprocess threw an IOException."); 426 427 } 428 429 // if we sent output when the return value was non-zero, do not send 430 // additional output. 431 // see http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4326 432 if (!alreadySentOutput) { 433 String outputString = _outputGobbler.getAndReset(); 434 String errorString = _errorGobbler.getAndReset(); 435 436 if (_debugging) { 437 _debug("Exec: Error: '" + errorString + "'"); 438 _debug("Exec: Output: '" + outputString + "'"); 439 } 440 441 // We could have a parameter that if it was set 442 // we would throw an exception if there was any error data. 443 error.send(0, new StringToken(errorString)); 444 output.send(0, new StringToken(outputString)); 445 } 446 } 447 448 /** Override the base class and terminate the process. 449 */ 450 @Override 451 public void stop() { 452 // NOTE: This method used to be synchronized, as 453 // was the fire() method, but this caused deadlocks. EAL 454 super.stop(); 455 _terminateProcess(); 456 457 } 458 459 /** Override the base class to stop waiting for input data. 460 */ 461 @Override 462 public void stopFire() { 463 // NOTE: This method used to be synchronized, as 464 // was the fire() method, but this caused deadlocks. EAL 465 super.stopFire(); 466 _stopFireRequested = true; 467 _terminateProcess(); 468 } 469 470 /** Terminate the subprocess. 471 * This method is invoked exactly once per execution 472 * of an application. None of the other action methods should be 473 * be invoked after it. 474 * @exception IllegalActionException Not thrown in this base class. 475 */ 476 @Override 477 public void wrapup() throws IllegalActionException { 478 _terminateProcess(); 479 } 480 481 /////////////////////////////////////////////////////////////////// 482 //// private methods //// 483 // Execute a command, set _process to point to the subprocess 484 // and set up _errorGobbler and _outputGobbler to read data. 485 private void _exec() throws IllegalActionException { 486 // FIXME: Exec, KeyStoreActor, JTextAreaExec have duplicate code. 487 // This is a private method because fire() was getting too long. 488 File directoryAsFile = null; 489 try { 490 _stopFireRequested = false; 491 492 if (_process != null) { 493 // Note that we assume that _process is null upon entry 494 // to this method, but we check again here just to be sure. 495 _terminateProcess(); 496 } 497 498 Runtime runtime = Runtime.getRuntime(); 499 500 command.update(); 501 502 List<String> commandList = new LinkedList<String>(); 503 504 boolean prependPlatformDependentShellCommandValue = ((BooleanToken) prependPlatformDependentShellCommand 505 .getToken()).booleanValue(); 506 if (prependPlatformDependentShellCommandValue) { 507 commandList = _getCommandList(); 508 } 509 510 // tokenizeForExec() handles substrings that start and end 511 // with a double quote so the substring is considered to 512 // be a single token and are returned as a single array 513 // element. 514 // FIXME: tokenizeForExec should return a List<String> 515 String[] commandArray = StringUtilities.tokenizeForExec( 516 ((StringToken) command.getToken()).stringValue()); 517 commandList.addAll(Arrays.asList(commandArray)); 518 519 // If the directory parameter is the empty string, 520 // then directoryAsFile will be null. 521 // If we call java.lang.Runtime.exec() with a null 522 // value for the directory, then the working directory 523 // of the subprocess will be inherited from the current process. 524 // See https://projects.ecoinformatics.org/ecoinfo/issues/6676 525 directoryAsFile = directory.asFile(); 526 if (directoryAsFile != null && !directoryAsFile.isDirectory()) { 527 throw new IllegalActionException( 528 "No such directory: " + directoryAsFile); 529 } 530 531 if (_debugging) { 532 StringBuffer commands = new StringBuffer(); 533 for (String aCommand : commandList) { 534 commands.append(aCommand + " "); 535 } 536 _debug("About to exec \"" + commands + "\"\n in \"" 537 + (directoryAsFile == null 538 ? "the working directory of the parent process" 539 : directoryAsFile) 540 + "\"\n with environment:"); 541 } 542 543 // Process the environment parameter. 544 ArrayToken environmentTokens = (ArrayToken) environment.getToken(); 545 546 if (_debugging) { 547 _debug("environmentTokens: " + environmentTokens); 548 } 549 550 String[] environmentArray = null; 551 552 if (environmentTokens != null && environmentTokens.length() >= 1) { 553 environmentArray = new String[environmentTokens.length()]; 554 555 for (int i = 0; i < environmentTokens.length(); i++) { 556 StringToken nameToken = (StringToken) ((RecordToken) environmentTokens 557 .getElement(i)).get("name"); 558 StringToken valueToken = (StringToken) ((RecordToken) environmentTokens 559 .getElement(i)).get("value"); 560 environmentArray[i] = nameToken.stringValue() + "=" 561 + valueToken.stringValue(); 562 563 if (_debugging) { 564 _debug(" " + i + ". \"" + environmentArray[i] + "\""); 565 } 566 567 if (i == 0 && environmentTokens.length() == 1 568 && environmentArray[0].equals("=")) { 569 if (_debugging) { 570 _debug("There is only one element, " 571 + "it is a string of length 0,\n so we " 572 + "pass Runtime.exec() an null " 573 + "environment so that we use\n " 574 + "the default environment"); 575 } 576 577 environmentArray = null; 578 } 579 } 580 } 581 582 commandArray = commandList.toArray(new String[commandList.size()]); 583 _process = runtime.exec(commandArray, environmentArray, 584 directoryAsFile); 585 586 // Create two threads to read from the subprocess. 587 _outputGobbler = new _StreamReaderThread(_process.getInputStream(), 588 "Exec Stdout Gobbler-" + _streamReaderThreadCount++, this); 589 _errorGobbler = new _StreamReaderThread(_process.getErrorStream(), 590 "Exec Stderr Gobbler-" + _streamReaderThreadCount++, this); 591 _errorGobbler.start(); 592 _outputGobbler.start(); 593 594 if (_streamReaderThreadCount > 1000) { 595 // Avoid overflow in the thread count. 596 _streamReaderThreadCount = 0; 597 } 598 599 OutputStreamWriter inputStreamWriter = new OutputStreamWriter( 600 _process.getOutputStream()); 601 _inputBufferedWriter = new BufferedWriter(inputStreamWriter); 602 } catch (IOException ex) { 603 throw new IllegalActionException(this, ex, 604 "Problem executing the command '" + command.getExpression() 605 + "'\n" + "in the " 606 + (directoryAsFile == null 607 ? "the working directory of the parent process." 608 : directoryAsFile + " directory.")); 609 } 610 } 611 612 /** Get the command list arguments for exec. */ 613 private List<String> _getCommandList() { 614 List<String> retval = new LinkedList<String>(); 615 616 String osName = System.getProperty("os.name"); 617 if (osName.equals("Windows 95")) { 618 retval.add("command.com"); 619 retval.add("/C"); 620 } else if (osName.startsWith("Windows")) { 621 retval.add("cmd.exe"); 622 retval.add("/C"); 623 } else { 624 retval.add("/bin/sh"); 625 retval.add("-c"); 626 } 627 return retval; 628 } 629 630 // Terminate the process and close any associated streams. 631 private void _terminateProcess() { 632 if (_process != null) { 633 _process.destroy(); 634 _process = null; 635 } 636 } 637 638 /////////////////////////////////////////////////////////////////// 639 //// inner classes //// 640 // Private class that reads a stream in a thread and updates the 641 // stringBuffer. 642 private class _StreamReaderThread extends Thread { 643 /** Create a _StreamReaderThread. 644 * @param inputStream The stream to read from. 645 * @param name The name of this StreamReaderThread, 646 * which is useful for debugging. 647 * @param actor The parent actor of this thread, which 648 * is used in error messages. 649 */ 650 _StreamReaderThread(InputStream inputStream, String name, 651 Nameable actor) { 652 super(name); 653 _inputStream = inputStream; 654 _inputStreamReader = new InputStreamReader(_inputStream, 655 java.nio.charset.Charset.defaultCharset()); 656 _actor = actor; 657 _stringBuffer = new StringBuffer(); 658 } 659 660 /** Read any remaining data in the input stream and return the 661 * data read thus far. Calling this method resets the 662 * cache of data read thus far. 663 */ 664 public String getAndReset() { 665 if (_debugging) { 666 try { 667 _debug("getAndReset: Gobbler '" + getName() + "' Ready: " 668 + _inputStreamReader.ready() + " Available: " 669 + _inputStream.available()); 670 } catch (Exception ex) { 671 throw new InternalErrorException(ex); 672 } 673 } 674 675 try { 676 // Read any remaining data. 677 _read(); 678 } catch (Throwable throwable) { 679 if (_debugging) { 680 _debug("WARNING: getAndReset(): _read() threw an " 681 + "exception, which we are ignoring.\n" 682 + throwable.getMessage()); 683 } 684 } 685 686 String results = _stringBuffer.toString(); 687 _stringBuffer = new StringBuffer(); 688 689 try { 690 _inputStreamReader.close(); 691 _inputStreamReaderClosed = true; 692 } catch (Exception ex) { 693 throw new InternalErrorException(null, ex, 694 getName() + " failed to close."); 695 } 696 697 return results; 698 } 699 700 /** Read lines from the inputStream and append them to the 701 * stringBuffer. 702 */ 703 @Override 704 public synchronized void run() { 705 if (!_inputStreamReaderClosed) { 706 _read(); 707 } 708 } 709 710 // Read from the stream until we get to the end of the stream 711 // This is synchronized so that it is not called simultaneously 712 // from run() and getAndReset(). 713 private synchronized void _read() { 714 // We read the data as a char[] instead of using readline() 715 // so that we can get strings that do not end in end of 716 // line chars. 717 char[] chars = new char[80]; 718 int length; // Number of characters read. 719 720 try { 721 // Oddly, InputStreamReader.read() will return -1 722 // if there is no data present, but the string can still 723 // read. 724 while ((length = _inputStreamReader.read(chars, 0, 80)) != -1 725 && !_stopRequested && !_stopFireRequested) { 726 if (_debugging) { 727 // Note that ready might be false here since 728 // we already read the data. 729 _debug("_read(): Gobbler '" + getName() + "' Ready: " 730 + _inputStreamReader.ready() + " Value: '" 731 + String.valueOf(chars, 0, length) + "'"); 732 } 733 734 _stringBuffer.append(chars, 0, length); 735 } 736 } catch (Throwable throwable) { 737 // We set ignoreExceptionReadErrors to true for ExecDemos so that 738 // exporting html does not produce an error when the model exits after 30 seconds. 739 boolean ignoreIOExceptionReadErrorsValue = false; 740 741 try { 742 ignoreIOExceptionReadErrorsValue = ((BooleanToken) ignoreIOExceptionReadErrors 743 .getToken()).booleanValue(); 744 745 } catch (IllegalActionException ex) { 746 throw new InternalErrorException(_actor, ex, getName() 747 + ": Could not get the value of the ignoreIOExceptionReadErrors " 748 + "parameter while trying to throw " + throwable); 749 } 750 if (ignoreIOExceptionReadErrorsValue 751 && throwable instanceof IOException) { 752 new Exception("Warning: " + getFullName() 753 + " had an exception, but " 754 + "ignoreIOExceptionReadErrors was true and the exception was an " 755 + "IOException, so it is being skipped.", throwable) 756 .printStackTrace(); 757 } else { 758 throw new InternalErrorException(_actor, throwable, 759 getName() + ": Failed while reading from " 760 + _inputStream 761 + ". To avoid this, try setting the ignoreIOExceptionReadErrors parameter to true." 762 + throwable.getCause()); 763 } 764 } 765 } 766 767 // The actor associated with this stream reader. 768 private Nameable _actor; 769 770 // Stream from which to read. 771 private InputStream _inputStream; 772 773 // Stream from which to read. 774 private InputStreamReader _inputStreamReader; 775 776 // Indicator that the stream has been closed. 777 private boolean _inputStreamReaderClosed = false; 778 779 // StringBuffer to update. 780 private StringBuffer _stringBuffer; 781 } 782 783 /////////////////////////////////////////////////////////////////// 784 //// private variables //// 785 // The subprocess gets its input from this BufferedWriter. 786 private BufferedWriter _inputBufferedWriter; 787 788 // StreamReader with which we read stderr. 789 private _StreamReaderThread _errorGobbler; 790 791 // StreamReader with which we read stdout. 792 private _StreamReaderThread _outputGobbler; 793 794 // The Process that we are running. 795 private Process _process; 796 797 // Indicator that stopFire() has been called. 798 private boolean _stopFireRequested = false; 799 800 // Instance count of output and error threads, used for debugging. 801 // When the value is greater than 1000, we reset it to 0. 802 private static int _streamReaderThreadCount = 0; 803}