001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.util; 017 018import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import java.io.BufferedReader; 020import java.io.InputStream; 021import java.io.InputStreamReader; 022import java.io.File; 023import java.io.IOException; 024 025import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 026import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 027import org.opengion.fukurou.system.HybsConst; // fukurou.util.StringUtil → fukurou.system.HybsConst に変更 028import org.opengion.fukurou.system.Closer; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 029import org.opengion.fukurou.system.LogWriter; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 030import org.opengion.fukurou.system.DateSet; // 6.4.2.0 (2016/01/29) 031 032/** 033 * Shell は、Runtime.exec の簡易的に実行するクラスです。 034 * 複雑な処理は通常の Runtime.exec を使用する必要がありますが,ほとんどの 035 * プロセス実行については、このクラスで十分であると考えています。 036 * 037 * このクラスでは、OS(特にWindows)でのバッチファイルの実行において、 038 * OS自動認識を行い、簡易的なコマンドをセットするだけで実行できるように 039 * しています。 040 * 041 * @version 4.0 042 * @author Kazuhiko Hasegawa 043 * @since JDK5.0, 044 */ 045public class Shell { 046 /** Shell オブジェクトの状態を表します。正常 {@value} */ 047 public static final int OK = 0; // 0:正常 048 /** Shell オブジェクトの状態を表します。実行中 {@value} */ 049 public static final int RUNNING = 1; // 1:実行中 050 /** Shell オブジェクトの状態を表します。取消 {@value} */ 051 public static final int CANCEL = 9; // 9:取消 052 /** Shell オブジェクトの状態を表します。異常終了(負) {@value} */ 053 public static final int ERROR = -1; // -1:異常終了(負) 054 055 // private static final String CMD_95 = "C:\\windows\\command.com /c "; 056 private static final String CMD_NT = "C:\\WINNT\\system32\\cmd.exe /c "; 057 private static final String CMD_XP = "C:\\WINDOWS\\system32\\cmd.exe /c "; 058 private static final String OS_NAME = System.getProperty("os.name"); 059 private String command ; 060 private File workDir ; 061 private String[] envp ; 062 private boolean isWait = true; // プロセスの終了を待つかどうか (デフォルト 待つ) 063 private Process prcs ; 064 private ProcessReader pr1 ; 065 private ProcessReader pr2 ; 066 private int rtnCode = ERROR; // 0:正常 1:実行中 9:取消 -1:異常終了(負) 067 068 // 3.6.1.0 (2005/01/05) タイムアウト時間を設定 069 private long timeout ; // 初期値は、タイムアウトなし 070 071 // 3.8.9.2 (2007/07/13) Windows Vista対応 072 // 5.6.7.1 (2013/07/09) NTでもunknown時はCMD_XPとする 073 private static final String CMD_COM ; 074 static { 075 if( (OS_NAME.indexOf( "NT" ) >= 0 || 076 OS_NAME.indexOf( "2000" ) >= 0) 077 && OS_NAME.indexOf( "unknown" ) < 0 ) { 078 CMD_COM = CMD_NT ; 079 } 080 // else if( OS_NAME.indexOf( "XP" ) >= 0 || 081 // OS_NAME.indexOf( "2003" ) >= 0 082 // OS_NAME.indexOf( "Vista" ) >= 0 ) { 083 // CMD_COM = CMD_XP ; 084 // } 085 else { 086 CMD_COM = CMD_XP ; 087 } 088 } 089 090 /** 091 * デフォルトコンストラクター 092 * 093 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 094 */ 095 public Shell() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 096 097 /** 098 * プロセスを実行する時に引き渡すコマンド 099 * 第2引数には、コマンドがBATかEXEかを指定できます。 100 * true の場合は,バッチコマンドとして処理されます。 101 * 102 * @og.rev 3.3.3.0 (2003/07/09) Windows XP 対応 103 * @og.rev 3.7.0.1 (2005/01/31) Windows 2003 対応, Windows 95 除外 104 * @og.rev 3.8.9.2 (2007/07/13) Windows Vista 対応 105 * 106 * @param cmd コマンド 107 * @param batch true:バッチファイル/false:EXEファイル 108 */ 109 public void setCommand( final String cmd,final boolean batch ) { 110 if( batch ) { 111 command = CMD_COM + cmd; 112 } 113 else { 114 command = cmd ; 115 } 116 } 117 118 /** 119 * プロセスを実行する時に引き渡すコマンド。 120 * 121 * @param cmd EXEコマンド 122 */ 123 public void setCommand( final String cmd ) { 124 setCommand( cmd,false ); 125 } 126 127 /** 128 * プロセスの実行処理の終了を待つかどうか。 129 * 130 * @param flag true:待つ(デフォルト)/ false:待たない 131 */ 132 public void setWait( final boolean flag ) { 133 isWait = flag; 134 } 135 136 /** 137 * プロセスの実行処理のタイムアウトを設定します。 138 * ゼロ(0) の場合は、割り込みが入るまで待ちつづけます。 139 * 140 * @param tout タイムアウト時間(秒) ゼロは、無制限 141 * 142 */ 143 public void setTimeout( final int tout ) { 144 timeout = (long)tout * 1000; 145 } 146 147 /** 148 * 作業ディレクトリを指定します。 149 * 150 * シェルを実行する、作業ディレクトリを指定します。 151 * 指定しない場合は、このJava仮想マシンの作業ディレクトリで実行されます。 152 * 153 * @param dir 作業ディレクトリ 154 */ 155 public void setWorkDir( final File dir ) { 156 workDir = dir; 157 } 158 159 /** 160 * 環境変数設定の配列指定します。 161 * 162 * 環境変数を、name=value という形式で、文字列配列で指定します。 163 * null の場合は、現在のプロセスの環境設定を継承します。 164 * 165 * @param env 文字列の配列(可変長引数)。 166 */ 167 public void setEnvP( final String... env ) { 168 if( env != null && env.length > 0 ) { // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。 169 final int size = env.length; 170 envp = new String[size]; 171 System.arraycopy( env,0,envp,0,size ); 172 } 173 else { 174 envp = null; 175 } 176 } 177 178 /** 179 * プロセスの実行処理。 180 * 181 * @return サブプロセスの終了コードを返します。0 は正常終了を示す 182 */ 183 public int exec() { 184 final Runtime rt = Runtime.getRuntime(); 185 Thread wait = null; 186 try { 187 prcs = rt.exec( command,envp,workDir ); // 3.3.3.0 (2003/07/09) 188 pr1 = new ProcessReader( prcs.getInputStream() ); 189 pr1.start(); 190 pr2 = new ProcessReader( prcs.getErrorStream() ); 191 pr2.start(); 192 193 if( isWait ) { 194 // 3.6.1.0 (2005/01/05) 195 wait = new WaitJoin( timeout,prcs ); 196 wait.start(); 197 rtnCode = prcs.waitFor(); 198 if( rtnCode > OK ) { rtnCode = -rtnCode; } 199 } 200 else { 201 rtnCode = RUNNING; // プロセスの終了を待たないので、1:処理中 を返します。 202 } 203 } 204 catch( final IOException ex ) { 205 final String errMsg = "入出力エラーが発生しました。"; 206 LogWriter.log( errMsg ); 207 LogWriter.log( ex ); 208 } 209 catch( final InterruptedException ex ) { 210 final String errMsg = "現在のスレッドが待機中にほかのスレッドによって強制終了されました。"; 211 LogWriter.log( errMsg ); 212 LogWriter.log( ex ); 213 } 214 finally { 215 if( wait != null ) { wait.interrupt(); } 216 } 217 218 return rtnCode; 219 } 220 221 /** 222 * プロセスの実行時の標準出力を取得します。 223 * 224 * @return 実行時の標準出力文字列 225 */ 226 public String getStdoutData() { 227 final String rtn ; 228 if( pr1 == null ) { 229 rtn = "\n.......... Process is not Running. ...."; 230 } 231 else if( pr1.isEnd() ) { 232 rtn = pr1.getString(); 233 } 234 else { 235 rtn = pr1.getString() + "\n......... stdout Process is under execution. ..."; 236 } 237 return rtn ; 238 } 239 240 /** 241 * プロセスの実行時のエラー出力を取得します。 242 * 243 * @return 実行時の標準出力文字列 244 */ 245 public String getStderrData() { 246 final String rtn ; 247 if( pr2 == null ) { 248 rtn = "\n.......... Process is not Running. ...."; 249 } 250 else if( pr2.isEnd() ) { 251 rtn = pr2.getString(); 252 } 253 else { 254 rtn = pr2.getString() + "\n......... stderr Process is under execution. ..."; 255 } 256 return rtn ; 257 } 258 259 /** 260 * プロセスが実際に実行するコマンドを取得します。 261 * バッチコマンドかどうかで、実行されるコマンドが異なりますので、 262 * ここで取得して確認することができます。 263 * 主にデバッグ用途です。 264 * 265 * @return 実行時の標準出力文字列 266 */ 267 public String getCommand() { 268 return command; 269 } 270 271 /** 272 * サブプロセスを終了します。 273 * この Process オブジェクトが表すサブプロセスは強制終了されます。 274 * 275 */ 276 public void destroy() { 277 if( prcs != null ) { prcs.destroy() ; } 278 rtnCode = CANCEL; 279 } 280 281 /** 282 * プロセスが終了しているかどうか[true/false]を確認します。 283 * この Process オブジェクトが表すサブプロセスは強制終了されます。 284 * 285 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 286 * 287 * @return プロセスが終了しているかどうか[true/false] 288 */ 289 public boolean isEnd() { 290 boolean flag = true; 291 if( rtnCode == RUNNING ) { 292 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 293 if( pr1 == null || pr2 == null ) { 294 final String errMsg = "#exec()を先に実行しておいてください。" + CR 295 + " command =" + command ; 296 throw new OgRuntimeException( errMsg ); 297 } 298 299 flag = pr1.isEnd() && pr2.isEnd() ; 300 if( flag ) { rtnCode = OK; } 301 } 302 return flag ; 303 } 304 305 /** 306 * サブプロセスの終了コードを返します。 307 * 308 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 309 * 310 * @return この Process オブジェクトが表すサブプロセスの終了コード。0 は正常終了を示す 311 * @throws IllegalThreadStateException この Process オブジェクトが表すサブプロセスがまだ終了していない場合 312 */ 313 public int exitValue() { 314 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 315 if( prcs == null ) { 316 final String errMsg = "#exec()を先に実行しておいてください。" + CR 317 + " command =" + command ; 318 throw new OgRuntimeException( errMsg ); 319 } 320 321 if( rtnCode == RUNNING && isEnd() ) { 322 rtnCode = prcs.exitValue(); 323 if( rtnCode > OK ) { rtnCode = -rtnCode ; } 324 } 325 return rtnCode; 326 } 327 328 /** 329 * この Shell のインフォメーション(情報)を出力します。 330 * コマンド、開始時刻、終了時刻、状態(実行中、終了)などの情報を、 331 * 出力します。 332 * 333 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。 334 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 335 * 336 * @return インフォメーション(情報) 337 * @og.rtnNotNull 338 */ 339 @Override 340 public String toString() { 341 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 342 if( pr1 == null ) { 343 final String errMsg = "#exec()を先に実行しておいてください。" + CR 344 + " command =" + command ; 345 throw new OgRuntimeException( errMsg ); 346 } 347 348 final boolean isEnd = isEnd() ; 349 final String st = DateSet.getDate( pr1.getStartTime() , "yyyy/MM/dd HH:mm:ss" ) ; 350 final String ed = isEnd ? DateSet.getDate( pr1.getEndTime() , "yyyy/MM/dd HH:mm:ss" ) 351 : "----/--/-- --:--:--" ; 352 353 // 6.0.2.5 (2014/10/31) char を append する。 354 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 355 .append( "command = [" ).append( getCommand() ).append( ']' ).append( CR ) 356 .append( " isEnd = [" ).append( isEnd ).append( ']' ).append( CR ) 357 .append( " rtnCode = [" ).append( exitValue() ).append( ']' ).append( CR ) 358 .append( " startTime = [" ).append( st ).append( ']' ).append( CR ) 359 .append( " endTime = [" ).append( ed ).append( ']' ).append( CR ); 360 361 return buf.toString(); 362 } 363 364 /** 365 * stdout と stderr の取得をスレッド化する為のインナークラスです。 366 * これ自身が、Thread の サブクラスになっています。 367 * 368 * @og.rev 6.3.9.1 (2015/11/27) private static final class に変更。 369 * 370 * @version 4.0 371 * @author Kazuhiko Hasegawa 372 * @since JDK5.0, 373 */ 374 private static final class ProcessReader extends Thread { 375 private final BufferedReader in ; 376 private final StringBuilder inStream = new StringBuilder( BUFFER_MIDDLE ); 377 private long startTime = -1; 378 private long endTime = -1; 379 private boolean endFlag ; 380 381 /** 382 * コンストラクター。 383 * 384 * ここで、スレッド化したい入力ストリームを引数に、オブジェクトを生成します。 385 * 386 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更 387 * 388 * @param ins InputStream 入力ストリーム 389 */ 390 ProcessReader( final InputStream ins ) { 391 super(); 392 in = new BufferedReader( new InputStreamReader(ins,HybsConst.DEFAULT_CHARSET) ); // 6.4.2.0 (2016/01/29) 393 setDaemon( true ); // 3.5.4.6 (2004/01/30) 394 } 395 396 /** 397 * Thread が実行された場合に呼び出される、run メソッドです。 398 * 399 * Thread のサブクラスは、このメソッドをオーバーライドしなければなりません。 400 * 401 */ 402 @Override 403 public void run() { 404 startTime = System.currentTimeMillis() ; 405 String outline; 406 try { 407 while( (outline = in.readLine()) != null ) { 408 inStream.append( outline ); 409 inStream.append( CR ); 410 } 411 } 412 catch( final IOException ex ) { 413 final String errMsg = "入出力エラーが発生しました。"; 414 LogWriter.log( errMsg ); 415 LogWriter.log( ex ); 416 } 417 finally { 418 Closer.ioClose( in ); 419 } 420 endTime = System.currentTimeMillis() ; 421 endFlag = true; 422 } 423 424 /** 425 * 現在書き込みが行われているストリームを文字列にして返します。 426 * 427 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、public → なし(パッケージプライベート)に変更。 428 * 429 * @return ストリームの文字列 430 * @og.rtnNotNull 431 */ 432 /* default */ String getString() { 433 return inStream.toString(); 434 } 435 436 /** 437 * ストリームからの読取が終了しているか確認します。 438 * 439 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、public → なし(パッケージプライベート)に変更。 440 * 441 * @return 読取終了(true) / 読み取り中(false) 442 * 443 */ 444 /* default */ boolean isEnd() { 445 return endFlag; 446 } 447 448 /** 449 * ストリーム処理の開始時刻を返します。 450 * 開始していない状態は、-1 を返します。 451 * 452 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、public → なし(パッケージプライベート)に変更。 453 * 454 * @return 開始時刻 455 */ 456 /* default */ long getStartTime() { 457 return startTime; 458 } 459 460 /** 461 * ストリーム処理の終了時刻を返します。 462 * 終了していない状態は、-1 を返します。 463 * 464 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、public → なし(パッケージプライベート)に変更。 465 * 466 * @return 終了時刻 467 */ 468 /* default */ long getEndTime() { 469 return endTime; 470 } 471 } 472 473 /** 474 * スレッドのウェイト処理クラス 475 * 指定のタイムアウト時間が来ると、設定されたプロセスを、強制終了(destroy)します。 476 * 指定のプロセス側は、処理が終了した場合は、このThreadに、割り込み(interrupt) 477 * をかけて、この処理そのものを終了させてください。 478 * 479 * @og.rev 6.3.9.1 (2015/11/27) private static final class に変更。 480 * 481 * @version 4.0 482 * @author Kazuhiko Hasegawa 483 * @since JDK5.0, 484 */ 485 private static final class WaitJoin extends Thread { 486 private static final long MAX_WAIT = 3600 * 1000 ; // 1時間に設定 487 488 private final long wait ; 489 private final Process prcs; 490 491 /** 492 * コンストラクター 493 * 494 * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor 495 * 496 * @param wait long ウェイトする時間(ミリ秒) 497 * @param prcs Process 強制終了(destroy) させるプロセス 498 */ 499 WaitJoin( final long wait,final Process prcs ) { 500 super(); 501 this.wait = wait > 0L ? wait : MAX_WAIT ; 502 this.prcs = prcs; 503 } 504 505 /** 506 * Thread の run() メソッド 507 * コンストラクタで指定のミリ秒だけウェイトし、それが経過すると、 508 * 指定のプロセスを強制終了(destroy)させます。 509 * 外部より割り込み(interrupt)があると、ウェイト状態から復帰します。 510 * 先に割り込みが入っている場合は、wait せずに抜けます。 511 * 512 * @og.rev 5.4.2.2 (2011/12/14) Threadでwaitをかける場合、synchronized しないとエラーになる 対応 513 */ 514 @Override 515 public void run() { 516 try { 517 final long startTime = System.currentTimeMillis() ; 518 boolean waitFlag = true; 519 synchronized( this ) { 520 while( ! isInterrupted() && waitFlag ) { 521 wait( wait ); 522 waitFlag = ( startTime + wait ) > System.currentTimeMillis() ; 523 } 524 } 525 prcs.destroy() ; 526 System.out.println( "タイムアウトにより強制終了しました。" ); 527 } 528 catch( final InterruptedException ex ) { 529 LogWriter.log( "終了しました。" ); 530 } 531 } 532 } 533}