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     */
016    package org.opengion.fukurou.util;
017    
018    import java.io.BufferedReader;
019    import java.io.InputStream;
020    import java.io.InputStreamReader;
021    import java.io.File;
022    import java.io.IOException;
023    // import java.text.DateFormat;
024    // import java.text.SimpleDateFormat;
025    // import java.util.Date;
026    // import java.util.Locale;
027    
028    /**
029     * Shell は、Runtime.exec の簡易的に実行するクラスです?
030     * ?な処??通常の Runtime.exec を使用する?がありますが?ほとんどの
031     * プロセス実行につ?は、このクラスで十?であると?て?す?
032     *
033     * こ?クラスでは、OS(特にWindows)でのバッチファイルの実行において?
034     * OS自動認識を行い、簡易的なコマンドをセ?する?で実行できるように
035     * して?す?
036     *
037     * @version  4.0
038     * @author   Kazuhiko Hasegawa
039     * @since    JDK5.0,
040     */
041    public class Shell {
042            /** Shell オブジェクト?状態を表します?正常  {@value} */
043            public static final int OK      = 0;            // 0:正常
044            /** Shell オブジェクト?状態を表します?実行中  {@value} */
045            public static final int RUNNING = 1;            // 1:実行中
046            /** Shell オブジェクト?状態を表します?取? {@value} */
047            public static final int CANCEL  = 9;            // 9:取?
048            /** Shell オブジェクト?状態を表します?異常終??  {@value} */
049            public static final int ERROR   = -1;           // -1:異常終??
050    
051            // private static final String CMD_95  = "C:\\windows\\command.com /c ";
052            private static final String CMD_NT  = "C:\\WINNT\\system32\\cmd.exe /c ";
053            private static final String CMD_XP  = "C:\\WINDOWS\\system32\\cmd.exe /c ";
054            private static final String OS_NAME = System.getProperty("os.name");
055            private static final String CR      = System.getProperty("line.separator");
056            private String          command         = null;
057            private File            workDir         = null;
058            private String[]        envp            = null;
059            private boolean         isWait          = true;         // プロセスの終??かど? (?ォル??)
060            private Process         prcs            = null;
061            private ProcessReader pr1               = null;
062            private ProcessReader pr2               = null;
063            private int     rtnCode                 = ERROR;        // 0:正常  1:実行中  9:取? -1:異常終??
064    //      private final DateFormat formatter = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss",Locale.JAPAN );
065    
066            // 3.6.1.0 (2005/01/05) タイ?ウト時間を設?
067            private long timeout                    = 0 ;   // 初期値は、タイ?ウトな?
068    
069            // 3.8.9.2 (2007/07/13) Windows Vista対?
070            // 5.6.7.1 (2013/07/09) NTでもunknown時?CMD_XPとする
071            private static final String CMD_COM ;
072            static {
073                    if( (OS_NAME.indexOf( "NT" ) >= 0 ||
074                                    OS_NAME.indexOf( "2000" ) >= 0)
075                            && OS_NAME.indexOf( "unknown" ) < 0 ) {
076                                    CMD_COM = CMD_NT ;
077                    }
078            //      else if( OS_NAME.indexOf( "XP" ) >= 0 ||
079            //                       OS_NAME.indexOf( "2003" ) >= 0
080            //                       OS_NAME.indexOf( "Vista" ) >= 0 ) {
081            //                      CMD_COM = CMD_XP ;
082            //      }
083                    else {
084                            CMD_COM = CMD_XP ;
085                    }
086            }
087    
088            /**
089             * プロセスを実行する時に引き渡すコマン?
090             * 第?引数には、コマンドがBATかEXEかを?できます?
091             * true の場合??バ?コマンドとして処?れます?
092             *
093             * @og.rev 3.3.3.0 (2003/07/09) Windows XP 対?
094             * @og.rev 3.7.0.1 (2005/01/31) Windows 2003 対? Windows 95 除?
095             * @og.rev 3.8.9.2 (2007/07/13) Windows Vista 対?
096             *
097             * @param       cmd     コマン?
098             * @param       batch   true:バッチファイル/false:EXEファイル
099             */
100            public void setCommand( final String cmd,final boolean batch ) {
101                    if( batch ) {
102                            command = CMD_COM + cmd;
103                    }
104                    else {
105                            command = cmd ;
106                    }
107            }
108    
109            /**
110             * プロセスを実行する時に引き渡すコマン?
111             *
112             * @param   cmd EXEコマン?
113             */
114            public void setCommand( final String cmd ) {
115                    setCommand( cmd,false );
116            }
117    
118            /**
119             * プロセスの実行???終??かど?
120             *
121             * @param       flag    true:?(?ォル?/ false:?な?
122             */
123            public void setWait( final boolean flag ) {
124                    isWait = flag;
125            }
126    
127            /**
128             * プロセスの実行???タイ?ウトを設定します?
129             * ゼロ(0) の場合?、割り込みが?るまで?つづけます?
130             *
131             * @param       tout    タイ?ウト時?? ゼロは、無制?
132             *
133             */
134            public void setTimeout( final int tout ) {
135                    timeout = (long)tout * 1000;
136            }
137    
138            /**
139             * 作業?レクトリを指定します?
140             *
141             * シェルを実行する?作業?レクトリを指定します?
142             * ?しな??合?、このJava仮想マシンの作業?レクトリで実行されます?
143             *
144             * @param   dir 作業?レクトリ
145             */
146            public void setWorkDir( final File dir ) {
147                    workDir = dir;
148            }
149    
150            /**
151             * 環?数設定?配??します?
152             *
153             * 環?数を?name=value と?形式で、文字?配?で?します?
154             * null の場合?、現在のプロセスの環?定を継承します?
155             *
156             * @param   env ??の配?。?列????、name=value と?形式で環?数設定を保持する?
157             */
158            public void setEnvP( final String[] env ) {
159                    if( env != null && env.length > 0 ) {
160                            int size = env.length;
161                            envp = new String[size];
162                            System.arraycopy( env,0,envp,0,size );
163                    }
164                    else {
165                            envp = null;
166                    }
167            }
168    
169            /**
170             * プロセスの実行??
171             *
172             * @return  サブ?ロセスの終?ードを返します?0 は正常終?示?
173             */
174            public int exec() {
175                    Runtime rt = Runtime.getRuntime();
176                    Thread wait = null;
177                    try {
178                            prcs = rt.exec( command,envp,workDir );         // 3.3.3.0 (2003/07/09)
179                            pr1 = new ProcessReader( prcs.getInputStream() );
180                            pr1.start();
181                            pr2 = new ProcessReader( prcs.getErrorStream() );
182                            pr2.start();
183                            if( isWait ) {
184                                    // 3.6.1.0 (2005/01/05)
185                                    wait = new WaitJoin( timeout,prcs );
186                                    wait.start();
187                                    rtnCode = prcs.waitFor();
188                                    if( rtnCode > OK ) { rtnCode = -rtnCode; }
189                            }
190                            else {
191                                    rtnCode = RUNNING;      // プロセスの終??な??で?:処? を返します?
192                            }
193                    }
194                    catch(IOException ex) {
195                            String errMsg = "入出力エラーが発生しました?;
196                            LogWriter.log( errMsg );
197                            LogWriter.log( ex );
198                    }
199                    catch(InterruptedException ex) {
200                            String errMsg = "現在のスレ?が?中にほか?スレ?によって強制終?れました?;
201                            LogWriter.log( errMsg );
202                            LogWriter.log( ex );
203                    }
204                    finally {
205                            if( wait != null ) { wait.interrupt(); }
206                    }
207    
208                    return rtnCode;
209            }
210    
211            /**
212             * プロセスの実行時の標準?力を取得します?
213             *
214             * @return 実行時の標準?力文字?
215             */
216            public String getStdoutData() {
217                    final String rtn ;
218                    if( pr1 == null ) {
219                            rtn = "\n.......... Process is not Running. ....";
220                    }
221                    else if( pr1.isEnd() ) {
222                            rtn = pr1.getString();
223                    }
224                    else {
225                            rtn = pr1.getString() + "\n......... stdout Process is under execution. ...";
226                    }
227                    return rtn ;
228            }
229    
230            /**
231             * プロセスの実行時のエラー出力を取得します?
232             *
233             * @return 実行時の標準?力文字?
234             */
235            public String getStderrData() {
236                    final String rtn ;
237                    if( pr2 == null ) {
238                            rtn = "\n.......... Process is not Running. ....";
239                    }
240                    else if( pr2.isEnd() ) {
241                            rtn = pr2.getString();
242                    }
243                    else {
244                            rtn = pr2.getString() + "\n......... stderr Process is under execution. ...";
245                    }
246                    return rtn ;
247            }
248    
249            /**
250             * プロセスが実際に実行するコマンドを取得します?
251             * バッチコマンドかど?で、実行されるコマンドが異なります?で?
252             * ここで取得して確認することができます?
253             * 主に??用途です?
254             *
255             * @return 実行時の標準?力文字?
256             */
257            public String getCommand() {
258                    return command;
259            }
260    
261            /**
262             * サブ?ロセスを終?ます?
263             * こ? Process オブジェクトが表すサブ?ロセスは強制終?れます?
264             *
265             */
266            public void destroy() {
267                    if( prcs != null ) { prcs.destroy() ; }
268                    rtnCode = CANCEL;
269            }
270    
271            /**
272             * プロセスが終?て?かど?[true/false]を確認します?
273             * こ? Process オブジェクトが表すサブ?ロセスは強制終?れます?
274             *
275             * @return      プロセスが終?て?かど?[true/false]
276             */
277            public boolean isEnd() {
278                    boolean flag = true;
279                    if( rtnCode == RUNNING ) {
280                            flag = pr1.isEnd() && pr2.isEnd() ;
281                            if( flag ) { rtnCode = OK; }
282                    }
283                    return flag ;
284            }
285    
286            /**
287             * サブ?ロセスの終?ードを返します?
288             *
289             * @return こ? Process オブジェクトが表すサブ?ロセスの終?ード?0 は正常終?示?
290             * @throws  IllegalThreadStateException こ? Process オブジェクトが表すサブ?ロセスがま??て????
291             */
292            public int exitValue() {
293                    if( rtnCode == RUNNING && isEnd() ) {
294                            rtnCode = prcs.exitValue();
295                            if( rtnCode > OK ) { rtnCode = -rtnCode ; }
296                    }
297                    return rtnCode;
298            }
299    
300            /**
301             * こ? Shell のインフォメーション(??)を?力します?
302             * コマンド?開始時刻、終?刻、状?実行中、終?などの??を?
303             * 出力します?
304             *
305             * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します?
306             *
307             * @return      インフォメーション(??)
308             */
309            @Override
310            public String toString() {
311                    boolean isEnd = isEnd() ;
312    //              String st = formatter.format( new Date( pr1.getStartTime() ) ) ;
313    //              String ed = ( isEnd ) ? formatter.format( new Date( pr1.getEndTime() ) ) : "----/--/-- --:--:--" ;
314                    String st = HybsDateUtil.getDate( pr1.getStartTime() , "yyyy/MM/dd HH:mm:ss" ) ;
315                    String ed = ( isEnd ) ? HybsDateUtil.getDate( pr1.getEndTime() , "yyyy/MM/dd HH:mm:ss" ) : "----/--/-- --:--:--" ;
316    
317                    StringBuilder buf = new StringBuilder();
318                    buf.append( "command     = [" ).append( getCommand() ).append( "]\n" );
319                    buf.append( "  isEnd     = [" ).append( isEnd        ).append( "]\n" );
320                    buf.append( "  rtnCode   = [" ).append( exitValue()  ).append( "]\n" );
321                    buf.append( "  startTime = [" ).append( st           ).append( "]\n" );
322                    buf.append( "  endTime   = [" ).append( ed           ).append( "]\n" );
323    
324                    return buf.toString();
325            }
326    
327            /**
328             * stdout と stderr の取得をスレ?化する為のインナ?クラスです?
329             * これ自身が?Thread の サブクラスになって?す?
330             *
331             * @version  4.0
332             * @author   Kazuhiko Hasegawa
333             * @since    JDK5.0,
334             */
335            static class ProcessReader extends Thread {
336                    private final BufferedReader in ;
337                    private final StringBuilder inStream = new StringBuilder();
338                    private boolean  endFlag = false;
339                    private long    startTime       = -1;
340                    private long    endTime         = -1;
341    
342                    /**
343                     * コンストラクター?
344                     *
345                     * ここで、スレ?化したい入力ストリー?引数に、オブジェクトを生?します?
346                     *
347                     * @param ins InputStream 入力ストリー?
348                     *
349                     */
350                    ProcessReader( InputStream ins ) {
351    //                      in = new BufferedReader( new InputStreamReader(ins) );
352                            in = new BufferedReader( new InputStreamReader(ins,StringUtil.DEFAULT_CHARSET) );       // 5.5.2.6 (2012/05/25) findbugs対?
353                            setDaemon( true );              // 3.5.4.6 (2004/01/30)
354                    }
355    
356                    /**
357                     * Thread が実行された場合に呼び出される?run メソ?です?
358                     *
359                     * Thread のサブクラスは、このメソ?をオーバ?ライドしなければなりません?
360                     *
361                     */
362                    public void run() {
363                            startTime = System.currentTimeMillis() ;
364                            String outline;
365                            try {
366                                    while ((outline = in.readLine()) != null) {
367                                            inStream.append( outline );
368                                            inStream.append( CR );
369                                    }
370                            }
371                            catch(IOException ex) {
372                                    String errMsg = "入出力エラーが発生しました?;
373                                    LogWriter.log( errMsg );
374                                    LogWriter.log( ex );
375                            }
376                            finally {
377                                    Closer.ioClose( in );
378                            }
379                            endTime = System.currentTimeMillis() ;
380                            endFlag = true;
381                    }
382    
383                    /**
384                     * 現在書き込みが行われて?ストリー???にして返します?
385                     *
386                     * @return      ストリー????
387                     *
388                     */
389                    public String getString() {
390                            return inStream.toString();
391                    }
392    
393                    /**
394                     * ストリー?ら?読取が終?て?か確認します?
395                     *
396                     * @return      読取終?true) / 読み取り中(false)
397                     *
398                     */
399                    public boolean isEnd() {
400                            return endFlag;
401                    }
402    
403                    /**
404                     * ストリー????開始時刻を返します?
405                     * 開始して??態??1 を返します?
406                     *
407                     * @return      開始時刻
408                     *
409                     */
410                    public long getStartTime() {
411                            return startTime;
412                    }
413    
414                    /**
415                     * ストリー????終?刻を返します?
416                     * 終?て??態??1 を返します?
417                     *
418                     * @return      終?刻
419                     *
420                     */
421                    public long getEndTime() {
422                            return endTime;
423                    }
424            }
425    
426            /**
427             * スレ?のウェイト??ラス
428             * ??タイ?ウト時間が来ると、設定されたプロセスを?強制終?destroy)します?
429             * ??プロセス側は、??終?た?合?、このThreadに、割り込み(interrupt)
430             * をかけて、この処?のも?を終?せてください?
431             *
432             * @version  4.0
433             * @author   Kazuhiko Hasegawa
434             * @since    JDK5.0,
435             */
436            static class WaitJoin extends Thread {
437                    private static final long MAX_WAIT = 3600 * 1000 ;      // ?時間に設?
438    
439                    private final long wait ;
440                    private final Process prcs;
441    
442                    /**
443                     * コンストラクター
444                     *
445                     * @param wait long ウェイトする時?ミリ?
446                     * @param prcs Process 強制終?destroy) させる?ロセス
447                     */
448                    WaitJoin( final long wait,Process prcs ) {
449                            this.wait = ( wait > 0L ) ? wait : MAX_WAIT ;
450                            this.prcs = prcs;
451                    }
452    
453                    /**
454                     * Thread の run() メソ?
455                     * コンストラクタで??ミリ秒だけウェイトし、それが経過すると?
456                     * ??プロセスを強制終?destroy)させます?
457                     * 外部より割り込み(interrupt)があると、ウェイト状態から復帰します?
458                     * 先に割り込みが?って?場合?、wait せずに抜けます?
459                     *
460                     * @og.rev 5.4.2.2 (2011/12/14) Threadでwaitをかける場合?synchronized しな?エラーにな?対?
461                     */
462                    public void run() {
463                            try {
464                                    long startTime = System.currentTimeMillis() ;
465                                    boolean waitFlag = true;
466                                    synchronized( this ) {
467                                            while( ! isInterrupted() && waitFlag ) {
468                                                    wait( wait );
469                                                    waitFlag = ( startTime + wait ) > System.currentTimeMillis() ;
470                                            }
471                                    }
472                                    prcs.destroy() ;
473                                    System.out.println( "タイ?ウトにより強制終?ました? );
474                            }
475                            catch( InterruptedException ex ) {
476                                    LogWriter.log( "終?ました? );
477                            }
478                    }
479            }
480    }