/* __ *\ ** ________ ___ / / ___ Scala API ** ** / __/ __// _ | / / / _ | (c) 2003-2009, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** \* */ package scala.io import annotation.experimental import concurrent.ThreadRunner import util.Properties.{ isWin, isMac } import util.control.Exception.catching import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder } import java.io.{ IOException, InputStream, OutputStream, BufferedReader, InputStreamReader, PrintWriter, File => JFile } import java.util.concurrent.LinkedBlockingQueue /** The <code>Process</code> object contains convenience functions * for running external processes. * * An example usage: * <pre> * io.Process("ls", cwd = io.File("/")) foreach println * </pre> * * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4109888 * for a dated list of the many obstacles to a clean interface. * * This is not finished!! Do not rely upon it yet. * * TODO - remove requirement that process complete before we * can get an iterator. * * @author Paul Phillips * @since 2.8 */ @experimental object Process { lazy val javaVmArguments = java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments() lazy val runtime = Runtime.getRuntime() @experimental private[Process] class ProcessBuilder(val pb: JProcessBuilder) { def this(cmd: String*) = this(new JProcessBuilder(cmd.toArray: _*)) def start() = new Process(() => pb.start()) def withOnlyEnv(env: Map[String, String]): this.type = { pb.environment.clear() withEnv(env) } def withEnv(env: Map[String,String]): this.type = { if (env != null) { val jmap = pb.environment() for ((k, v) <- env) jmap.put(k, v) } this } def withCwd(cwd: File): this.type = { if (cwd != null) pb directory cwd.jfile this } def withRedirectedErrorStream(merged: Boolean): this.type = { pb redirectErrorStream merged this } override def toString() = "ProcessBuilder(%s)" format pb.command() } // This can be fleshed out if more variations come up private val shell: String => Array[String] = if (isWin) Array("cmd.exe", "/C", _) else Array("sh", "-c", _) /** Executes the given command line in a shell. * * @param command the command line * @return a Process object */ def apply( command: String, env: Map[String, String] = null, cwd: File = null, redirect: Boolean = false ): Process = exec(shell(command), env, cwd) /** Executes the given command line. * * @param command the command line * @return a Process object */ def exec( command: Seq[String], env: Map[String, String] = null, cwd: File = null, redirect: Boolean = false ): Process = new ProcessBuilder(command: _*) withEnv env withCwd cwd start } import Process._ @experimental class Process(processCreator: () => JProcess) extends Iterable[String] { lazy val process = processCreator() def exitValue(): Option[Int] = catching(classOf[IllegalThreadStateException]) opt process.exitValue() def waitFor() = process.waitFor() def destroy() = process.destroy() def rerun() = new Process(processCreator) def stdout = iterator def iterator = _out.iterator def stderr = _err.iterator lazy val stdin = new PrintWriter(_in, true) class StreamedConsumer(in: InputStream) extends Thread with Iterable[String] { private val queue = new LinkedBlockingQueue[String] private val reader = new BufferedReader(new InputStreamReader(in)) def iterator = { join() // make sure this thread is complete new Iterator[String] { val it = queue.iterator() def hasNext = it.hasNext def next = it.next } } override def run() { reader.readLine match { case null => case x => queue put x run() } } } private val _err = createConsumer(process.getErrorStream) private val _out = createConsumer(process.getInputStream) private val _in = process.getOutputStream() private def createConsumer(in: InputStream) = { val t = new StreamedConsumer(in) t.start() t } override def toString() = "Process(%s)" format process.toString() }