OSDN Git Service

Change directory structure.
[dvibrowser/dvi2epub.git] / src / main / java / jp / sourceforge / dvibrowser / dvicore / util / CommandShell.java
diff --git a/src/main/java/jp/sourceforge/dvibrowser/dvicore/util/CommandShell.java b/src/main/java/jp/sourceforge/dvibrowser/dvicore/util/CommandShell.java
new file mode 100644 (file)
index 0000000..e48f938
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2009, Takeyuki Nagao
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the
+ * following conditions are met:
+ * 
+ *  * Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *  * Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer in the documentation and/or other
+ *    materials provided with the distribution.
+ *    
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+package jp.sourceforge.dvibrowser.dvicore.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jp.sourceforge.dvibrowser.dvicore.DviException;
+
+
+public class CommandShell
+{
+  public static final int RETCODE_SUCCESS = 0;
+  
+  private final ScheduledExecutorService exe = Executors.newSingleThreadScheduledExecutor
+    (new DaemonThreadFactory());
+  
+  private static final Logger LOGGER = Logger.getLogger(CommandShell.class.getName());
+  private ArrayList<String> commandLine = new ArrayList<String>();
+
+  public void setCommandLine(String ... commandLine)
+  {
+    ArrayList<String> list = new ArrayList<String>();
+    for (String arg : commandLine) {
+      list.add(arg);
+    }
+    this.commandLine = list;
+  }
+
+  public void setCommandLine(Collection<String> cmdLine)
+  {
+    ArrayList<String> list = new ArrayList<String>();
+    list.addAll(cmdLine);
+    this.commandLine = list;
+  }
+  
+  public Collection<String> getCommandLine()
+  {
+    return Collections.unmodifiableCollection(commandLine);
+  }
+
+  private ArrayList<String> envs = null;
+  public void setEnvironment(Collection<String> envs)
+  {
+    if (envs == null) {
+      this.envs = null;
+      return;
+    } else {
+      ArrayList<String> list = new ArrayList<String>();
+      list.addAll(envs);
+      this.envs = list;
+    }
+  }
+
+  public void setEnvironment(String ... envs)
+  {
+    ArrayList<String> list = new ArrayList<String>();
+    for (String arg : envs) {
+      list.add(arg);
+    }
+    this.envs = list;
+  }
+  
+  public Collection<String> getEnvironment()
+  {
+    if (envs == null) return null;
+    return Collections.unmodifiableCollection(envs);
+  }
+
+  private File dir = null;
+  public void setWorkingDirectory(File dir)
+  {
+    this.dir = dir;
+  }
+  
+  public File getWorkingDirectory()
+  {
+    return dir;
+  }
+  
+  private TimeUnit timeUnit;
+  private long timeout = 0;
+  public void setTimeout(long timeout, TimeUnit timeUnit)
+  {
+    if (timeout < 0) {
+      throw new IllegalArgumentException("timeout is negative");
+    }
+    this.timeout = timeout;
+    this.timeUnit = timeUnit;
+  }
+
+  private CommandShellHandler handler = null;
+  public void setHandler(CommandShellHandler handler)
+  {
+    this.handler = handler;
+  }
+  
+  public CommandShellHandler getHandler()
+  {
+    return handler;
+  }
+
+  protected void checkVars()
+  {
+    if (commandLine == null)
+      throw new IllegalArgumentException
+        ("commandLine can't be null");
+    if (commandLine.size() < 1)
+      throw new IllegalArgumentException
+        ("commandLine can't be empty");
+  }
+
+  private volatile Process p = null;
+  private Thread stdoutThread = null;
+  private Thread stderrThread = null;
+  private Thread stdinThread = null;
+  private volatile Throwable stdoutThrowable = null;
+  private volatile Throwable stderrThrowable = null;
+  private volatile Throwable stdinThrowable = null;
+
+  public int execute()
+  throws IOException, InterruptedException, DviException
+  {
+    int result = -1;
+
+    p = null;
+    checkVars();
+
+    try {
+      final String commandLineStr = DviUtils.join(" ", commandLine);
+      LOGGER.fine("Running command: " + commandLineStr);
+      p = Runtime.getRuntime().exec(
+          commandLine.toArray(new String[commandLine.size()]),
+          getEnvironmentAsArray(), dir);
+      processStreams();
+      ScheduledFuture<?> future = null;
+      if (timeUnit != null && timeout > 0) {
+        LOGGER.fine("Starting timer: timeout=" + timeout + " timeUnit=" + timeUnit);
+        future = exe.schedule(new Runnable() {
+          public void run() {
+            LOGGER.warning("Command timed out: " + commandLineStr);
+            p.destroy();
+          }
+        }, timeout, timeUnit);
+      }
+      LOGGER.fine("waiting for the process to terminate.");
+      result = p.waitFor();
+      LOGGER.fine("process exit with retcode " + result);
+      if (future != null) {
+        future.cancel(false);
+      }
+    } catch (InterruptedException ex) {
+      if (p != null) {
+        p.destroy();
+      }
+      throw ex;
+    } catch (IOException ex) {
+      if (p != null) {
+        p.destroy();
+      }
+      throw ex;
+    } finally {
+      reapThread(stderrThread, "stderr");
+      stderrThread = null;
+      reapThread(stdoutThread, "stdout");
+      stdoutThread = null;
+      reapThread(stdinThread, "stdin");
+      stdinThread = null;
+      
+      if (p != null) {
+        DviUtils.silentClose(p.getErrorStream());
+        DviUtils.silentClose(p.getInputStream());
+        DviUtils.silentClose(p.getOutputStream());
+      }
+    }
+    
+    if (stdinThrowable != null) {
+      throw new DviException(stdinThrowable);
+    }
+    if (stdoutThrowable != null) {
+      throw new DviException(stdoutThrowable);
+    }
+    if (stderrThrowable != null) {
+      throw new DviException(stderrThrowable);
+    }
+    
+    p = null;
+
+    return result;
+  }
+
+  private String[] getEnvironmentAsArray() {
+    if (envs == null) return null;
+    return envs.toArray(new String[envs.size()]);
+  }
+
+  private void reapThread(Thread thread, String name)
+  {
+    try {
+      LOGGER.fine("waiting for the " + name + " thread");
+      if (thread != null)
+        thread.join();
+    } catch (InterruptedException ex) {
+      DviUtils.logStackTrace(LOGGER, Level.WARNING, ex);
+    }
+  }
+
+  protected void processStreams()
+  throws IOException
+  {
+    if (handler != null) {
+      stdinThread = new Thread(new Runnable() {
+        public void run() {
+          OutputStream os = p.getOutputStream();
+          try {
+            handler.handleStdin(os);
+          } catch (Throwable ex) {
+            DviUtils.logStackTrace(LOGGER, Level.WARNING, ex);
+            stdinThrowable = ex;
+            p.destroy();
+          } finally {
+            DviUtils.silentClose(os);
+          }
+        }
+      });
+      stderrThread = new Thread(new Runnable() {
+        public void run() {
+          InputStream is = p.getErrorStream();
+          try {
+            handler.handleStderr(is);
+          } catch (Throwable ex) {
+            DviUtils.logStackTrace(LOGGER, Level.WARNING, ex);
+            stderrThrowable = ex;
+            p.destroy();
+          } finally {
+            DviUtils.silentClose(is);
+          }
+        }
+      });
+      stdoutThread = new Thread(new Runnable() {
+        public void run() {
+          InputStream is = p.getInputStream();
+          try {
+            handler.handleStdout(is);
+          } catch (Throwable ex) {
+            DviUtils.logStackTrace(LOGGER, Level.WARNING, ex);
+            stdoutThrowable = ex;
+            p.destroy();
+          } finally {
+            DviUtils.silentClose(is);
+          }
+        }
+      });
+
+      stdinThread .start();
+      stderrThread.start();
+      stdoutThread.start();
+    } else {
+      DviUtils.silentClose(p.getOutputStream());
+      DviUtils.silentClose(p.getInputStream());
+      DviUtils.silentClose(p.getErrorStream());
+    }
+  }
+}