001package com.hfg.util; 002 003import java.io.*; 004import java.util.List; 005import java.util.ArrayList; 006 007//------------------------------------------------------------------------------ 008/** 009 More robust wrapper for Process.exec(). 010 <div> 011 @author J. Alex Taylor, hairyfatguy.com 012 </div> 013 */ 014//------------------------------------------------------------------------------ 015// com.hfg XML/HTML Coding Library 016// 017// This library is free software; you can redistribute it and/or 018// modify it under the terms of the GNU Lesser General Public 019// License as published by the Free Software Foundation; either 020// version 2.1 of the License, or (at your option) any later version. 021// 022// This library is distributed in the hope that it will be useful, 023// but WITHOUT ANY WARRANTY; without even the implied warranty of 024// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 025// Lesser General Public License for more details. 026// 027// You should have received a copy of the GNU Lesser General Public 028// License along with this library; if not, write to the Free Software 029// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 030// 031// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 032// jataylor@hairyfatguy.com 033//------------------------------------------------------------------------------ 034// TODO: 035// - Make the internal threads reusable 036 037public class Executor 038{ 039 //*************************************************************************** 040 // PRIVATE FIELDS 041 //*************************************************************************** 042 043 private String mCmd; 044 private List mEnvVars; 045 private File mWorkingDir; 046 private String mSTDINString; 047 private InputStream mSTDINStream; 048 private File mShell = new File("/bin/csh"); 049 050 private boolean mRunning; 051 private String mSTDOUT; 052 private String mSTDERR; 053 private int mExitValue; 054 private long mExecutionTime; 055 056 private static int BUFFER_SIZE = 8192; 057 058 //*************************************************************************** 059 // CONSTRUCTORS 060 //*************************************************************************** 061 062 //--------------------------------------------------------------------------- 063 /** 064 Default constructor. 065 */ 066 public Executor() 067 { 068 } 069 070 //--------------------------------------------------------------------------- 071 /** 072 Constructor specifying the command to execute. 073 */ 074 public Executor(String inCommand) 075 { 076 this(); 077 setCommand(inCommand); 078 } 079 080 //--------------------------------------------------------------------------- 081 /** 082 Constructor specifying the command to execute. 083 */ 084 public Executor(String[] inCommand) 085 { 086 this(StringUtil.join(inCommand)); 087 } 088 089 //*************************************************************************** 090 // PUBLIC METHODS 091 //*************************************************************************** 092 093 //--------------------------------------------------------------------------- 094 /** 095 Sets the command to execute. 096 */ 097 public void setCommand(String inCommand) 098 { 099 mCmd = inCommand; 100 } 101 102 //--------------------------------------------------------------------------- 103 /** 104 Returns the command to execute. 105 */ 106 public String getCommand() 107 { 108 return mCmd; 109 } 110 111 112 113 //--------------------------------------------------------------------------- 114 /** 115 Clears the list of environment variables to be set when executing the command. 116 <i><b>IMPORTANT!</b> If the environment variables are cleared it does NOT mean 117 that no environment variables are present, but rather that the parent process' 118 environment variables are inherited.</i> 119 */ 120 public void clearEnvVars() 121 { 122 mEnvVars = null; 123 } 124 125 //--------------------------------------------------------------------------- 126 /** 127 Adds the specified environment variable to the list to be set when executing 128 the command. 129 <i><b>IMPORTANT!</b> If environment variables are specified they are specified 130 IN PLACE OF the parent process' environment variables and NOT in addition to 131 them.</i> 132 */ 133 public void addEnvVar(String inName, String inValue) 134 { 135 if (null == inName) 136 { 137 throw new ExecutorException("Environment variable names cannot be null!"); 138 } 139 140 if (null == mEnvVars) mEnvVars = new ArrayList(5); 141 142 mEnvVars.add(inName + (inValue != null ? "=" + inValue : "")); 143 } 144 145 146 147 //--------------------------------------------------------------------------- 148 /** 149 Sets the shell to be used for command execution. The default is '/bin/csh'. 150 To run the command without a <shell> -c '<cmd>' construct, set the shell 151 to null. 152 */ 153 public void setShell(File inValue) 154 { 155 if (inValue != null 156 && !inValue.exists()) 157 { 158 throw new ExecutorException("The shell '" + inValue + "' does not exist!"); 159 } 160 161 mShell = inValue; 162 } 163 164 //--------------------------------------------------------------------------- 165 /** 166 Returns the shell to be used for command execution. 167 */ 168 public File getShell() 169 { 170 return mShell; 171 } 172 173 174 //--------------------------------------------------------------------------- 175 /** 176 Sets the working directory for command execution. 177 */ 178 public void setWorkingDir(File inValue) 179 { 180 mWorkingDir = inValue; 181 } 182 183 //--------------------------------------------------------------------------- 184 /** 185 Returns the working directory for command execution. 186 */ 187 public File getWorkingDir() 188 { 189 return mWorkingDir; 190 } 191 192 193 //--------------------------------------------------------------------------- 194 /** 195 Sets the STDIN content for the command to be executed. 196 */ 197 public void setSTDIN(String inValue) 198 { 199 mSTDINString = inValue; 200 mSTDINStream = null; 201 } 202 203 204 //--------------------------------------------------------------------------- 205 /** 206 Sets the STDIN content for the command to be executed. 207 */ 208 public void setSTDIN(InputStream inValue) 209 { 210 mSTDINString = null; 211 mSTDINStream = inValue; 212 } 213 214 215 216 //--------------------------------------------------------------------------- 217 /** 218 Returns the STDOUT output produced by the executed command. 219 */ 220 public String getSTDOUT() 221 { 222 return mSTDOUT; 223 } 224 225 //--------------------------------------------------------------------------- 226 /** 227 Returns the STDERR output produced by the executed command. 228 */ 229 public String getSTDERR() 230 { 231 return mSTDERR; 232 } 233 234 //--------------------------------------------------------------------------- 235 /** 236 Returns the execution time in milliseconds of executed command. This is wall 237 time, not CPU time. 238 */ 239 public long getExecutionTimeMillis() 240 { 241 return mExecutionTime; 242 } 243 244 //--------------------------------------------------------------------------- 245 /** 246 Executes the command. 247 248 @return the exit value of the executed command. 249 @throws ExecutorException 250 */ 251 public int exec() 252 throws ExecutorException 253 { 254 setup(); 255 256 mRunning = true; 257 258 long startTime = System.currentTimeMillis(); 259 260 Job job = new Job(); 261 Thread thread = new Thread(job); 262 thread.start(); 263 264 while (mRunning) 265 { 266 try 267 { 268 Thread.sleep(50); 269 } 270 catch (InterruptedException e) 271 { 272 } 273 } 274 275 mExecutionTime = System.currentTimeMillis() - startTime; 276 277 return mExitValue; 278 } 279 280 281 282 //*************************************************************************** 283 // PRIVATE METHODS 284 //*************************************************************************** 285 286 //--------------------------------------------------------------------------- 287 private void setup() 288 { 289 mSTDOUT = null; 290 mSTDERR = null; 291 } 292 293 //--------------------------------------------------------------------------- 294 private String[] getCmdAsArray() 295 { 296 String[] cmd = {mShell.toString(), 297 "-f", // The shell ignores the ~/.cshrc (or appropriate 298 // login script for the shell). 299 "-c", getCommand()}; 300 301 return cmd; 302 } 303 304 //--------------------------------------------------------------------------- 305 private String[] getEnvVarsAsArray() 306 { 307 String[] env = null; 308 309 if (mEnvVars != null) 310 { 311 env = new String[mEnvVars.size()]; 312 313 for (int i = 0; i < mEnvVars.size(); i++) 314 { 315 env[i] = (String) mEnvVars.get(i); 316 } 317 } 318 319 return env; 320 } 321 322 323 //*************************************************************************** 324 // INNER CLASS 325 //*************************************************************************** 326 327 private class Job implements Runnable 328 { 329 //------------------------------------------------------------------------ 330 public void run() 331 { 332 try 333 { 334 Process process = getProcess(); 335 336 // Set STDIN if specified 337 setSTDIN(process); 338 339 // Use separate threads to read from STDOUT and STDERR to avoid 340 // potential blocking problems. 341 JobOutput stdout = new JobOutput(process.getInputStream()); 342 Thread stdoutThread = new Thread(stdout); 343 stdoutThread.start(); 344 345 JobOutput stderr = new JobOutput(process.getErrorStream()); 346 Thread stderrThread = new Thread(stderr); 347 stderrThread.start(); 348 349 process.waitFor(); 350 351 while ( ! stdout.isDone() 352 || ! stderr.isDone()) 353 { 354 Thread.sleep(25); 355 } 356 357 358 mSTDOUT = stdout.getContent(); 359 mSTDERR = stderr.getContent(); 360 361 mExitValue = process.exitValue(); 362 } 363 catch (Throwable e) 364 { 365 throw new ExecutorException("Command " + StringUtil.singleQuote(getCommand()) 366 + " produced an exception!", e); 367 } 368 finally 369 { 370 mRunning = false; 371 } 372 } 373 374 //------------------------------------------------------------------------ 375 private Process getProcess() 376 throws Exception 377 { 378 Process process = null; 379 380 if (null == mShell) 381 { 382 process = Runtime.getRuntime().exec(getCommand(), 383 getEnvVarsAsArray(), 384 getWorkingDir()); 385 } 386 else 387 { 388 process = Runtime.getRuntime().exec(getCmdAsArray(), 389 getEnvVarsAsArray(), 390 getWorkingDir()); 391 } 392 393 return process; 394 } 395 396 //------------------------------------------------------------------------ 397 private void setSTDIN(Process inProcess) 398 throws IOException 399 { 400 if (mSTDINString != null) 401 { 402 PrintWriter writer = new PrintWriter(inProcess.getOutputStream()); 403 writer.print(mSTDINString); 404 writer.close(); 405 } 406 else if (mSTDINStream != null) 407 { 408 PrintWriter writer = new PrintWriter(inProcess.getOutputStream()); 409 410 InputStreamReader reader = new InputStreamReader(mSTDINStream); 411 412 char[] buffer = new char[BUFFER_SIZE]; 413 int charsRead = 0; 414 415 while ((charsRead = reader.read(buffer, 0, BUFFER_SIZE)) != -1) 416 { 417 writer.write(buffer, 0, charsRead); 418 } 419 420 writer.close(); 421 } 422 } 423 } 424 425 426 //*************************************************************************** 427 // INNER CLASS 428 //*************************************************************************** 429 430 // Generic class to handle reading the STDOUT and STDERR streams from the process. 431 private class JobOutput implements Runnable 432 { 433 private InputStream mInputStream; 434 private StringBuffer mContent = new StringBuffer(); 435 private boolean mDone = false; 436 437 //************************************************************************ 438 // CONSTRUCTORS 439 //************************************************************************ 440 441 //------------------------------------------------------------------------ 442 public JobOutput(InputStream inProcessStream) 443 { 444 mInputStream = inProcessStream; 445 } 446 447 //************************************************************************ 448 // PUBLIC METHODS 449 //************************************************************************ 450 451 //------------------------------------------------------------------------ 452 public void run() 453 { 454 try 455 { 456 InputStreamReader reader = new InputStreamReader(mInputStream); 457 458 char[] buffer = new char[BUFFER_SIZE]; 459 int charsRead = 0; 460 461 while ((charsRead = reader.read(buffer, 0, BUFFER_SIZE)) != -1) 462 { 463 mContent.append(buffer, 0, charsRead); 464 } 465 } 466 catch (Throwable e) 467 { 468 throw new ExecutorException(e); 469 } 470 finally 471 { 472 mDone = true; 473 } 474 475 } 476 477 //------------------------------------------------------------------------ 478 public boolean isDone() 479 { 480 return mDone; 481 } 482 483 //------------------------------------------------------------------------ 484 public String getContent() 485 { 486 return mContent.toString(); 487 } 488 489 490 491 } 492 493 494}