Java Process の実行ラッパーをラムダで

昔、java.lang.Runtime から Runtime.getRuntime().execProcess を作ってスクリプトを実行するラッパーを書いていた。

Process p = Runtime.getRuntime().exec(this.arrange());
_ProcessStreamReader p_stderr = new _ProcessStreamReader(p.getErrorStream());
_ProcessStreamReader p_stdout = new _ProcessStreamReader(p.getInputStream());
/*プロセス起動後、プロセスが入力を求めるなら、PrintWriter pw = new PrintWriter(p.getOutputStream();
 * を用意して、次に PrintWriter  で入力内容を print する
 */
p_stderr.start();
p_stdout.start();
p_stderr.join();
p_stdout.join();
p.waitFor();
int sts = p.exitValue();

このようにするのだが、IOException を InterruptedException 捕捉を書いてどうするかを呼出し側に委ねなくてはならない。
上記をラップしたメソッドの呼び出しも使いづらい。
だったらきちんと例外を捕捉してそれを標準エラー出力結果に出すようにすればいいだけなのですが、
どうせなら、
・実行スクリプトを Supplier<String> で渡す
・標準出力とエラー出力結果を BiConsumer<String, String> = <stdout, stderr>
あるいは、BiConsumer<InputStream, InputStream> = <stdout の InputStream, stderr InputStream>
 で処理する

というのを用意します。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
 * ラムダ・スクリプト実行.
 */
public final class ScriptExecutor{

   private ScriptExecutor(){}

   /**
    * ラムダ・スクリプト実行(String → BiConsumer)
    * @param scriptSupplier 実行するスクリプト
    * @param consumer BiConsumer<String, String> = <stdout, stderr>
    * @return java.lang.Process の exitValue() 結果、例外発生時は 1 を返す。
    */
   public static int run(Supplier<String> scriptSupplier, BiConsumer<String, String> consumer){
      int rtn = 0;
      String stdout;
      String stderr;
      try{
         Process p = Runtime.getRuntime().exec(scriptSupplier.get());
         _processStreamReader p_stderr = new _processStreamReader(p.getErrorStream());
         _processStreamReader p_stdout = new _processStreamReader(p.getInputStream());
         p_stderr.start();
         p_stdout.start();
         p_stderr.join();
         p_stdout.join();
         p.waitFor();
         rtn = p.exitValue();
         stdout = p_stdout.getString();
         stderr = p_stderr.getString();
      }catch(Exception ex){
         rtn = 1;
         stdout = "";
         StringBuilder sb = new StringBuilder();
         sb.append(ex.getMessage());
         sb.append("\n");
         sb.append(Arrays.stream(ex.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         Optional.ofNullable(ex.getCause()).ifPresent(x->{
            sb.append("\n");
            sb.append("Caused by: ");
            sb.append(x.getMessage());
            sb.append("\n");
         sb.append(Arrays.stream(x.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         });
         stderr = sb.toString();
      }
      consumer.accept(stdout, stderr);
      return rtn;
   }
   /**
    * ラムダ・スクリプト実行(InputStream → BiConsumer)
    * @param scriptSupplier 実行するスクリプト
    * @param consumer BiConsumer<InputStream, InputStream> = <stdout の InputStream, stderr InputStream>
    * @return java.lang.Process の exitValue() 結果、例外発生時は 1 を返す。
    */
   public static int runStream(Supplier<String> scriptSupplier, BiConsumer<InputStream, InputStream> consumer){
      int rtn = 0;
      try{
         Process p = Runtime.getRuntime().exec(scriptSupplier.get());
         p.waitFor();
         consumer.accept(p.getInputStream(), p.getErrorStream());
         return p.exitValue();
      }catch(Exception ex){
         rtn = 1;
         StringBuilder sb = new StringBuilder();
         sb.append(ex.getMessage());
         sb.append("\n");
         sb.append(Arrays.stream(ex.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         Optional.ofNullable(ex.getCause()).ifPresent(x->{
            sb.append("\n");
            sb.append("Caused by: ");
            sb.append(x.getMessage());
            sb.append("\n");
         sb.append(Arrays.stream(x.getStackTrace()).map(t->t.toString()).collect(Collectors.joining("\n\t")));
         });
         try{
            consumer.accept(new ByteArrayInputStream("".getBytes())
                        , new ByteArrayInputStream(sb.toString().getBytes("UTF-8")));
         }catch(UnsupportedEncodingException e){
            e.printStackTrace();
         }
      }
      return rtn;
   }
   static class _processStreamReader extends Thread{
      StringBuffer        sb;
      InputStreamReader   inredaer;
      public _processStreamReader(InputStream in){
         super();
         this.inredaer = new InputStreamReader(in);
         this.sb = new StringBuffer();
      }
      @Override
      public void run(){
         try{
            int i;
            int BUFFER_SIZE = 1024;
            char[] c = new char[BUFFER_SIZE];
            while((i = this.inredaer.read(c,0,BUFFER_SIZE - 1)) > 0){
               this.sb.append(c,0,i);
               if (i < BUFFER_SIZE - 1){ break; }
            }
            this.inredaer.close();
         }catch(IOException e){}
      }
      public String getString(){
         return this.sb.toString();
      }
   }
}

これで呼出しは、とても整理できてきます。
例えば テストとして、Python スクリプト foo.py というのを用意して、
Windows 上で実行なら、”cmd.exe /C ”を付けて実行します

int sts = ScriptExecutor.run(()->"cmd.exe /C  python c:/work/hello.py", (t, e)->{
     //  t  = 標準出力 
     //  e  = 標準エラー出力
});
// sts = プロセス実行終了コード、Process の exitValue() 結果