diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala | 25 | ||||
-rw-r--r-- | src/library/scala/collection/mutable/HashTable.scala | 32 | ||||
-rw-r--r-- | src/library/scala/collection/parallel/mutable/ParHashMap.scala | 10 | ||||
-rw-r--r-- | src/library/scala/sys/process/BasicIO.scala | 129 | ||||
-rw-r--r-- | src/library/scala/sys/process/Process.scala | 67 | ||||
-rw-r--r-- | src/library/scala/sys/process/ProcessBuilder.scala | 306 | ||||
-rw-r--r-- | src/library/scala/sys/process/ProcessIO.scala | 49 | ||||
-rw-r--r-- | src/library/scala/sys/process/ProcessLogger.scala | 26 | ||||
-rw-r--r-- | src/library/scala/sys/process/package.scala | 212 | ||||
-rw-r--r-- | test/files/jvm/serialization.check | 8 | ||||
-rw-r--r-- | test/files/run/t5293-map.scala | 88 | ||||
-rw-r--r-- | test/files/specialized/SI-5005.check | 33 | ||||
-rw-r--r-- | test/files/specialized/SI-5005.scala | 23 |
13 files changed, 824 insertions, 184 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 05f5dbc379..8c34a9139d 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -69,7 +69,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { ScalaValueClasses, isValueClass, isScalaValueType, SpecializedClass, RepeatedParamClass, JavaRepeatedParamClass, AnyRefClass, ObjectClass, AnyRefModule, - GroupOfSpecializable, uncheckedVarianceClass + GroupOfSpecializable, uncheckedVarianceClass, ScalaInlineClass } /** TODO - this is a lot of maps. @@ -832,7 +832,6 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { log("-->d SETTING PRIVATE WITHIN TO " + sym.enclosingPackage + " for " + sym) } - sym.resetFlag(FINAL) val specMember = subst(outerEnv)(specializedOverload(owner, sym, spec)) typeEnv(specMember) = typeEnv(sym) ++ outerEnv ++ spec wasSpecializedForTypeVars(specMember) ++= spec collect { case (s, tp) if s.tpe == tp => s } @@ -1733,9 +1732,27 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { class SpecializationTransformer(unit: CompilationUnit) extends Transformer { informProgress("specializing " + unit) - override def transform(tree: Tree) = - if (settings.nospecialization.value) tree + override def transform(tree: Tree) = { + val resultTree = if (settings.nospecialization.value) tree else atPhase(phase.next)(specializeCalls(unit).transform(tree)) + + // Remove the final modifier and @inline annotation from anything in the + // original class (since it's being overridden in at least onesubclass). + // + // We do this here so that the specialized subclasses will correctly copy + // final and @inline. + info.foreach { + case (sym, SpecialOverload(target, _)) => { + sym.resetFlag(FINAL) + target.resetFlag(FINAL) + sym.removeAnnotation(ScalaInlineClass) + target.removeAnnotation(ScalaInlineClass) + } + case _ => {} + } + + resultTree + } } def printSpecStats() { diff --git a/src/library/scala/collection/mutable/HashTable.scala b/src/library/scala/collection/mutable/HashTable.scala index cdf1b78f29..5b3e07b826 100644 --- a/src/library/scala/collection/mutable/HashTable.scala +++ b/src/library/scala/collection/mutable/HashTable.scala @@ -52,6 +52,10 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU */ @transient protected var sizemap: Array[Int] = null + @transient var seedvalue: Int = tableSizeSeed + + protected def tableSizeSeed = Integer.bitCount(table.length - 1) + protected def initialSize: Int = HashTable.initialSize private def lastPopulatedIndex = { @@ -70,14 +74,16 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU private[collection] def init[B](in: java.io.ObjectInputStream, f: (A, B) => Entry) { in.defaultReadObject - _loadFactor = in.readInt + _loadFactor = in.readInt() assert(_loadFactor > 0) - val size = in.readInt + val size = in.readInt() tableSize = 0 assert(size >= 0) - - val smDefined = in.readBoolean + + seedvalue = in.readInt() + + val smDefined = in.readBoolean() table = new Array(capacity(sizeForThreshold(_loadFactor, size))) threshold = newThreshold(_loadFactor, table.size) @@ -86,7 +92,7 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU var index = 0 while (index < size) { - addEntry(f(in.readObject.asInstanceOf[A], in.readObject.asInstanceOf[B])) + addEntry(f(in.readObject().asInstanceOf[A], in.readObject().asInstanceOf[B])) index += 1 } } @@ -103,6 +109,7 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU out.defaultWriteObject out.writeInt(_loadFactor) out.writeInt(tableSize) + out.writeInt(seedvalue) out.writeBoolean(isSizeMapDefined) foreachEntry { entry => out.writeObject(entry.key) @@ -314,7 +321,7 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU // this is of crucial importance when populating the table in parallel protected final def index(hcode: Int) = { val ones = table.length - 1 - val improved = improve(hcode) + val improved = improve(hcode, seedvalue) val shifted = (improved >> (32 - java.lang.Integer.bitCount(ones))) & ones shifted } @@ -325,6 +332,7 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU table = c.table tableSize = c.tableSize threshold = c.threshold + seedvalue = c.seedvalue sizemap = c.sizemap } if (alwaysInitSizeMap && sizemap == null) sizeMapInitAndRebuild @@ -335,6 +343,7 @@ trait HashTable[A, Entry >: Null <: HashEntry[A, Entry]] extends HashTable.HashU table, tableSize, threshold, + seedvalue, sizemap ) } @@ -368,7 +377,7 @@ private[collection] object HashTable { protected def elemHashCode(key: KeyType) = key.## - protected final def improve(hcode: Int) = { + protected final def improve(hcode: Int, seed: Int) = { /* Murmur hash * m = 0x5bd1e995 * r = 24 @@ -396,7 +405,7 @@ private[collection] object HashTable { * */ var i = hcode * 0x9e3775cd i = java.lang.Integer.reverseBytes(i) - i * 0x9e3775cd + i = i * 0x9e3775cd // a slower alternative for byte reversal: // i = (i << 16) | (i >> 16) // i = ((i >> 8) & 0x00ff00ff) | ((i << 8) & 0xff00ff00) @@ -420,6 +429,11 @@ private[collection] object HashTable { // h = h ^ (h >>> 14) // h = h + (h << 4) // h ^ (h >>> 10) + + // the rest of the computation is due to SI-5293 + val rotation = seed % 32 + val rotated = (i >>> rotation) | (i << (32 - rotation)) + rotated } } @@ -442,6 +456,7 @@ private[collection] object HashTable { val table: Array[HashEntry[A, Entry]], val tableSize: Int, val threshold: Int, + val seedvalue: Int, val sizemap: Array[Int] ) { import collection.DebugUtils._ @@ -452,6 +467,7 @@ private[collection] object HashTable { append("Table: [" + arrayString(table, 0, table.length) + "]") append("Table size: " + tableSize) append("Load factor: " + loadFactor) + append("Seedvalue: " + seedvalue) append("Threshold: " + threshold) append("Sizemap: [" + arrayString(sizemap, 0, sizemap.length) + "]") } diff --git a/src/library/scala/collection/parallel/mutable/ParHashMap.scala b/src/library/scala/collection/parallel/mutable/ParHashMap.scala index 15ffd3fdd2..21a5b05749 100644 --- a/src/library/scala/collection/parallel/mutable/ParHashMap.scala +++ b/src/library/scala/collection/parallel/mutable/ParHashMap.scala @@ -160,10 +160,11 @@ extends collection.parallel.BucketCombiner[(K, V), ParHashMap[K, V], DefaultEntr import collection.parallel.tasksupport._ private var mask = ParHashMapCombiner.discriminantmask private var nonmasklen = ParHashMapCombiner.nonmasklength + private var seedvalue = 27 def +=(elem: (K, V)) = { sz += 1 - val hc = improve(elemHashCode(elem._1)) + val hc = improve(elemHashCode(elem._1), seedvalue) val pos = (hc >>> nonmasklen) if (buckets(pos) eq null) { // initialize bucket @@ -176,7 +177,7 @@ extends collection.parallel.BucketCombiner[(K, V), ParHashMap[K, V], DefaultEntr def result: ParHashMap[K, V] = if (size >= (ParHashMapCombiner.numblocks * sizeMapBucketSize)) { // 1024 // construct table - val table = new AddingHashTable(size, tableLoadFactor) + val table = new AddingHashTable(size, tableLoadFactor, seedvalue) val bucks = buckets.map(b => if (b ne null) b.headPtr else null) val insertcount = executeAndWaitResult(new FillBlocks(bucks, table, 0, bucks.length)) table.setSize(insertcount) @@ -210,11 +211,12 @@ extends collection.parallel.BucketCombiner[(K, V), ParHashMap[K, V], DefaultEntr * and true if the key was successfully inserted. It does not update the number of elements * in the table. */ - private[ParHashMapCombiner] class AddingHashTable(numelems: Int, lf: Int) extends HashTable[K, DefaultEntry[K, V]] { + private[ParHashMapCombiner] class AddingHashTable(numelems: Int, lf: Int, _seedvalue: Int) extends HashTable[K, DefaultEntry[K, V]] { import HashTable._ _loadFactor = lf table = new Array[HashEntry[K, DefaultEntry[K, V]]](capacity(sizeForThreshold(_loadFactor, numelems))) tableSize = 0 + seedvalue = _seedvalue threshold = newThreshold(_loadFactor, table.length) sizeMapInit(table.length) def setSize(sz: Int) = tableSize = sz @@ -285,7 +287,7 @@ extends collection.parallel.BucketCombiner[(K, V), ParHashMap[K, V], DefaultEntr insertcount } private def assertCorrectBlock(block: Int, k: K) { - val hc = improve(elemHashCode(k)) + val hc = improve(elemHashCode(k), seedvalue) if ((hc >>> nonmasklen) != block) { println(hc + " goes to " + (hc >>> nonmasklen) + ", while expected block is " + block) assert((hc >>> nonmasklen) == block) diff --git a/src/library/scala/sys/process/BasicIO.scala b/src/library/scala/sys/process/BasicIO.scala index 44e573896f..5b7244e98e 100644 --- a/src/library/scala/sys/process/BasicIO.scala +++ b/src/library/scala/sys/process/BasicIO.scala @@ -13,15 +13,25 @@ import processInternal._ import java.io.{ BufferedReader, InputStreamReader, FilterInputStream, FilterOutputStream } import java.util.concurrent.LinkedBlockingQueue import scala.collection.immutable.Stream +import scala.annotation.tailrec /** * This object contains factories for [[scala.sys.process.ProcessIO]], * which can be used to control the I/O of a [[scala.sys.process.Process]] * when a [[scala.sys.process.ProcessBuilder]] is started with the `run` * command. + * + * It also contains some helper methods that can be used to in the creation of + * `ProcessIO`. + * + * It is used by other classes in the package in the implementation of various + * features, but can also be used by client code. */ object BasicIO { + /** Size of the buffer used in all the functions that copy data */ final val BufferSize = 8192 + + /** Used to separate lines in the `processFully` function that takes `Appendable`. */ final val Newline = props("line.separator") private[process] final class Streamed[T]( @@ -52,15 +62,70 @@ object BasicIO { def protect(out: OutputStream): OutputStream = if ((out eq stdout) || (out eq stderr)) Uncloseable(out) else out } + /** Creates a `ProcessIO` from a function `String => Unit`. It can attach the + * process input to stdin, and it will either send the error stream to + * stderr, or to a `ProcessLogger`. + * + * For example, the `ProcessIO` created below will print all normal output + * while ignoring all error output. No input will be provided. + * {{{ + * import scala.sys.process.BasicIO + * val errToDevNull = BasicIO(false, println(_), None) + * }}} + * + * @param withIn True if the process input should be attached to stdin. + * @param output A function that will be called with the process output. + * @param log An optional `ProcessLogger` to which the output should be + * sent. If `None`, output will be sent to stderr. + * @return A `ProcessIO` with the characteristics above. + */ def apply(withIn: Boolean, output: String => Unit, log: Option[ProcessLogger]) = new ProcessIO(input(withIn), processFully(output), getErr(log)) + /** Creates a `ProcessIO` that appends its output to a `StringBuffer`. It can + * attach the process input to stdin, and it will either send the error + * stream to stderr, or to a `ProcessLogger`. + * + * For example, the `ProcessIO` created by the function below will store the + * normal output on the buffer provided, and print all error on stderr. The + * input will be read from stdin. + * {{{ + * import scala.sys.process.{BasicIO, ProcessLogger} + * val printer = ProcessLogger(println(_)) + * def appendToBuffer(b: StringBuffer) = BasicIO(true, b, Some(printer)) + * }}} + * + * @param withIn True if the process input should be attached to stdin. + * @param buffer A `StringBuffer` which will receive the process normal + * output. + * @param log An optional `ProcessLogger` to which the output should be + * sent. If `None`, output will be sent to stderr. + * @return A `ProcessIO` with the characteristics above. + */ def apply(withIn: Boolean, buffer: StringBuffer, log: Option[ProcessLogger]) = new ProcessIO(input(withIn), processFully(buffer), getErr(log)) + /** Creates a `ProcessIO` from a `ProcessLogger` . It can attach the + * process input to stdin. + * + * @param withIn True if the process input should be attached to stdin. + * @param log A `ProcessLogger` to receive all output, normal and error. + * @return A `ProcessIO` with the characteristics above. + */ def apply(withIn: Boolean, log: ProcessLogger) = new ProcessIO(input(withIn), processOutFully(log), processErrFully(log)) + /** Returns a function `InputStream => Unit` given an optional + * `ProcessLogger`. If no logger is passed, the function will send the output + * to stderr. This function can be used to create a + * [[scala.sys.process.ProcessIO]]. + * + * @param log An optional `ProcessLogger` to which the contents of + * the `InputStream` will be sent. + * @return A function `InputStream => Unit` (used by + * [[scala.sys.process.ProcessIO]]) which will send the data to + * either the provided `ProcessLogger` or, if `None`, to stderr. + */ def getErr(log: Option[ProcessLogger]) = log match { case Some(lg) => processErrFully(lg) case None => toStdErr @@ -69,13 +134,40 @@ object BasicIO { private def processErrFully(log: ProcessLogger) = processFully(log err _) private def processOutFully(log: ProcessLogger) = processFully(log out _) + /** Closes a `Closeable` without throwing an exception */ def close(c: Closeable) = try c.close() catch { case _: IOException => () } + + /** Returns a function `InputStream => Unit` that appends all data read to the + * provided `Appendable`. This function can be used to create a + * [[scala.sys.process.ProcessIO]]. The buffer will be appended line by line. + * + * @param buffer An `Appendable` such as `StringBuilder` or `StringBuffer`. + * @return A function `InputStream => Unit` (used by + * [[scala.sys.process.ProcessIO]] which will append all data read + * from the stream to the buffer. + */ def processFully(buffer: Appendable): InputStream => Unit = processFully(appendLine(buffer)) + + /** Returns a function `InputStream => Unit` that will call the passed + * function with all data read. This function can be used to create a + * [[scala.sys.process.ProcessIO]]. The `processLine` function will be called + * with each line read, and `Newline` will be appended after each line. + * + * @param processLine A function that will be called with all data read from + * the stream. + * @return A function `InputStream => Unit` (used by + * [[scala.sys.process.ProcessIO]] which will call `processLine` + * with all data read from the stream. + */ def processFully(processLine: String => Unit): InputStream => Unit = in => { val reader = new BufferedReader(new InputStreamReader(in)) processLinesFully(processLine)(reader.readLine) + reader.close() } + /** Calls `processLine` with the result of `readLine` until the latter returns + * `null`. + */ def processLinesFully(processLine: String => Unit)(readLine: () => String) { def readFully() { val line = readLine() @@ -86,14 +178,38 @@ object BasicIO { } readFully() } - def connectToIn(o: OutputStream): Unit = transferFully(stdin, o) - def input(connect: Boolean): OutputStream => Unit = if (connect) connectToIn else _ => () + + /** Copy contents of stdin to the `OutputStream`. */ + def connectToIn(o: OutputStream): Unit = transferFully(Uncloseable protect stdin, o) + + /** Returns a function `OutputStream => Unit` that either reads the content + * from stdin or does nothing. This function can be used by + * [[scala.sys.process.ProcessIO]]. + */ + def input(connect: Boolean): OutputStream => Unit = { outputToProcess => + if (connect) connectToIn(outputToProcess) + outputToProcess.close() + } + + /** Returns a `ProcessIO` connected to stdout and stderr, and, optionally, stdin. */ def standard(connectInput: Boolean): ProcessIO = standard(input(connectInput)) + + /** Retruns a `ProcessIO` connected to stdout, stderr and the provided `in` */ def standard(in: OutputStream => Unit): ProcessIO = new ProcessIO(in, toStdOut, toStdErr) + /** Send all the input from the stream to stderr, and closes the input stream + * afterwards. + */ def toStdErr = (in: InputStream) => transferFully(in, stderr) + + /** Send all the input from the stream to stdout, and closes the input stream + * afterwards. + */ def toStdOut = (in: InputStream) => transferFully(in, stdout) + /** Copy all input from the input stream to the output stream. Closes the + * input stream once it's all read. + */ def transferFully(in: InputStream, out: OutputStream): Unit = try transferFullyImpl(in, out) catch onInterrupt(()) @@ -105,13 +221,14 @@ object BasicIO { private[this] def transferFullyImpl(in: InputStream, out: OutputStream) { val buffer = new Array[Byte](BufferSize) - def loop() { + @tailrec def loop() { val byteCount = in.read(buffer) if (byteCount > 0) { out.write(buffer, 0, byteCount) - out.flush() - loop() - } + // flush() will throw an exception once the process has terminated + val available = try { out.flush(); true } catch { case _: IOException => false } + if (available) loop() else in.close() + } else in.close() } loop() } diff --git a/src/library/scala/sys/process/Process.scala b/src/library/scala/sys/process/Process.scala index b8765aa615..c2a61af936 100644 --- a/src/library/scala/sys/process/Process.scala +++ b/src/library/scala/sys/process/Process.scala @@ -13,7 +13,7 @@ import processInternal._ import ProcessBuilder._ /** Represents a process that is running or has finished running. - * It may be a compound process with several underlying native processes (such as 'a #&& b`). + * It may be a compound process with several underlying native processes (such as `a #&& b`). * * This trait is often not used directly, though its companion object contains * factories for [[scala.sys.process.ProcessBuilder]], the main component of this @@ -42,28 +42,28 @@ object Process extends ProcessImpl with ProcessCreation { } * found on and used through [[scala.sys.process.Process]]'s companion object. */ trait ProcessCreation { - /** Create a [[scala.sys.process.ProcessBuilder]] from a `String`, including the + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `String`, including the * parameters. * * @example {{{ apply("cat file.txt") }}} */ def apply(command: String): ProcessBuilder = apply(command, None) - /** Create a [[scala.sys.process.ProcessBuilder]] from a sequence of `String`, + /** Creates a [[scala.sys.process.ProcessBuilder]] from a sequence of `String`, * where the head is the command and each element of the tail is a parameter. * * @example {{{ apply("cat" :: files) }}} */ def apply(command: Seq[String]): ProcessBuilder = apply(command, None) - /** Create a [[scala.sys.process.ProcessBuilder]] from a command represented by a `String`, + /** Creates a [[scala.sys.process.ProcessBuilder]] from a command represented by a `String`, * and a sequence of `String` representing the arguments. * * @example {{{ apply("cat", files) }}} */ def apply(command: String, arguments: Seq[String]): ProcessBuilder = apply(command +: arguments, None) - /** Create a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra * environment variables. * * @example {{{ apply("java", new java.ioFile("/opt/app"), "CLASSPATH" -> "library.jar") }}} @@ -71,7 +71,7 @@ trait ProcessCreation { def apply(command: String, cwd: File, extraEnv: (String, String)*): ProcessBuilder = apply(command, Some(cwd), extraEnv: _*) - /** Create a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir set to `File` and extra * environment variables. * * @example {{{ apply("java" :: javaArgs, new java.ioFile("/opt/app"), "CLASSPATH" -> "library.jar") }}} @@ -79,7 +79,7 @@ trait ProcessCreation { def apply(command: Seq[String], cwd: File, extraEnv: (String, String)*): ProcessBuilder = apply(command, Some(cwd), extraEnv: _*) - /** Create a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to * `File` and extra environment variables. * * @example {{{ apply("java", params.get("cwd"), "CLASSPATH" -> "library.jar") }}} @@ -93,7 +93,7 @@ trait ProcessCreation { }*/ } - /** Create a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to + /** Creates a [[scala.sys.process.ProcessBuilder]] with working dir optionally set to * `File` and extra environment variables. * * @example {{{ apply("java" :: javaArgs, params.get("cwd"), "CLASSPATH" -> "library.jar") }}} @@ -105,7 +105,7 @@ trait ProcessCreation { apply(jpb) } - /** create a [[scala.sys.process.ProcessBuilder]] from a `java.lang.ProcessBuilder`. + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `java.lang.ProcessBuilder`. * * @example {{{ * apply((new java.lang.ProcessBuilder("ls", "-l")) directory new java.io.File(System.getProperty("user.home"))) @@ -113,19 +113,19 @@ trait ProcessCreation { */ def apply(builder: JProcessBuilder): ProcessBuilder = new Simple(builder) - /** create a [[scala.sys.process.ProcessBuilder]] from a `java.io.File`. This + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `java.io.File`. This * `ProcessBuilder` can then be used as a `Source` or a `Sink`, so one can * pipe things from and to it. */ def apply(file: File): FileBuilder = new FileImpl(file) - /** Create a [[scala.sys.process.ProcessBuilder]] from a `java.net.URL`. This + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `java.net.URL`. This * `ProcessBuilder` can then be used as a `Source`, so that one can pipe things * from it. */ def apply(url: URL): URLBuilder = new URLImpl(url) - /** Create a [[scala.sys.process.ProcessBuilder]] from a Scala XML Element. + /** Creates a [[scala.sys.process.ProcessBuilder]] from a Scala XML Element. * This can be used as a way to template strings. * * @example {{{ @@ -134,23 +134,23 @@ trait ProcessCreation { */ def apply(command: scala.xml.Elem): ProcessBuilder = apply(command.text.trim) - /** Create a [[scala.sys.process.ProcessBuilder]] from a `Boolean`. This can be + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `Boolean`. This can be * to force an exit value. */ def apply(value: Boolean): ProcessBuilder = apply(value.toString, if (value) 0 else 1) - /** Create a [[scala.sys.process.ProcessBuilder]] from a `String` name and a + /** Creates a [[scala.sys.process.ProcessBuilder]] from a `String` name and a * `Boolean`. This can be used to force an exit value, with the name being * used for `toString`. */ def apply(name: String, exitValue: => Int): ProcessBuilder = new Dummy(name, exitValue) - /** Create a sequence of [[scala.sys.process.ProcessBuilder.Source]] from a sequence of + /** Creates a sequence of [[scala.sys.process.ProcessBuilder.Source]] from a sequence of * something else for which there's an implicit conversion to `Source`. */ def applySeq[T](builders: Seq[T])(implicit convert: T => Source): Seq[Source] = builders.map(convert) - /** Create a [[scala.sys.process.ProcessBuilder]] from one or more + /** Creates a [[scala.sys.process.ProcessBuilder]] from one or more * [[scala.sys.process.ProcessBuilder.Source]], which can then be * piped to something else. * @@ -170,7 +170,7 @@ trait ProcessCreation { */ def cat(file: Source, files: Source*): ProcessBuilder = cat(file +: files) - /** Create a [[scala.sys.process.ProcessBuilder]] from a non-empty sequence + /** Creates a [[scala.sys.process.ProcessBuilder]] from a non-empty sequence * of [[scala.sys.process.ProcessBuilder.Source]], which can then be * piped to something else. * @@ -198,18 +198,41 @@ trait ProcessImplicits { /** Implicitly convert a `java.lang.ProcessBuilder` into a Scala one. */ implicit def builderToProcess(builder: JProcessBuilder): ProcessBuilder = apply(builder) - /** Implicitly convert a `java.io.File` into a [[scala.sys.process.ProcessBuilder]] */ + /** Implicitly convert a `java.io.File` into a + * [[scala.sys.process.ProcessBuilder.FileBuilder]], which can be used as + * either input or output of a process. For example: + * {{{ + * import scala.sys.process._ + * "ls" #> new java.io.File("dirContents.txt") ! + * }}} + */ implicit def fileToProcess(file: File): FileBuilder = apply(file) - /** Implicitly convert a `java.net.URL` into a [[scala.sys.process.ProcessBuilder]] */ + /** Implicitly convert a `java.net.URL` into a + * [[scala.sys.process.ProcessBuilder.URLBuilder]] , which can be used as + * input to a process. For example: + * {{{ + * import scala.sys.process._ + * Seq("xmllint", "--html", "-") #< new java.net.URL("http://www.scala-lang.org") #> new java.io.File("fixed.html") ! + * }}} + */ implicit def urlToProcess(url: URL): URLBuilder = apply(url) - /** Implicitly convert a [[scala.xml.Elem]] into a [[scala.sys.process.ProcessBuilder]] */ + /** Implicitly convert a [[scala.xml.Elem]] into a + * [[scala.sys.process.ProcessBuilder]]. This is done by obtaining the text + * elements of the element, trimming spaces, and then converting the result + * from string to a process. Importantly, tags are completely ignored, so + * they cannot be used to separate parameters. + */ implicit def xmlToProcess(command: scala.xml.Elem): ProcessBuilder = apply(command) - /** Implicitly convert a `String` into a [[scala.sys.process.ProcessBuilder]] */ + /** Implicitly convert a `String` into a [[scala.sys.process.ProcessBuilder]]. */ implicit def stringToProcess(command: String): ProcessBuilder = apply(command) - /** Implicitly convert a sequence of `String` into a [[scala.sys.process.ProcessBuilder]] */ + /** Implicitly convert a sequence of `String` into a + * [[scala.sys.process.ProcessBuilder]]. The first argument will be taken to + * be the command to be executed, and the remaining will be its arguments. + * When using this, arguments may contain spaces. + */ implicit def stringSeqToProcess(command: Seq[String]): ProcessBuilder = apply(command) } diff --git a/src/library/scala/sys/process/ProcessBuilder.scala b/src/library/scala/sys/process/ProcessBuilder.scala index 214d908012..20270d423f 100644 --- a/src/library/scala/sys/process/ProcessBuilder.scala +++ b/src/library/scala/sys/process/ProcessBuilder.scala @@ -12,133 +12,265 @@ package process import processInternal._ import ProcessBuilder._ -/** Represents a runnable process. +/** Represents a sequence of one or more external processes that can be + * executed. A `ProcessBuilder` can be a single external process, or a + * combination of other `ProcessBuilder`. One can control where a + * the output of an external process will go to, and where its input will come + * from, or leave that decision to whoever starts it. * - * This is the main component of this package. A `ProcessBuilder` may be composed with - * others, either concatenating their outputs or piping them from one to the next, and - * possibly with conditional execution depending on the last process exit value. + * One creates a `ProcessBuilder` through factories provided in + * [[scala.sys.process.Process]]'s companion object, or implicit conversions + * based on these factories made available in the package object + * [[scala.sys.process]]. Here are some examples: + * {{{ + * import.scala.sys.process._ * - * Once executed, one can retrieve the output or redirect it to a - * [[scala.sys.process.ProcessLogger]], or one can get the exit value, discarding or - * redirecting the output. + * // Executes "ls" and sends output to stdout + * "ls".! * - * One creates a `ProcessBuilder` through factories provided in [[scala.sys.process.Process]]'s - * companion object, or implicit conversions based on these factories made available in the - * package object [[scala.sys.process]]. + * // Execute "ls" and assign a `Stream[String]` of its output to "contents". + * // Because [[scala.Predef]] already defines a `lines` method for `String`, + * // we use [[scala.sys.process.Process]]'s object companion to create it. + * val contents = Process("ls").lines * - * Let's examine in detail one example of usage: + * // Here we use a `Seq` to make the parameter whitespace-safe + * def contentsOf(dir: String): String = Seq("ls", dir).!! + * }}} + * + * The methods of `ProcessBuilder` are divided in three categories: the ones that + * combine two `ProcessBuilder` to create a third, the ones that redirect input + * or output of a `ProcessBuilder`, and the ones that execute + * the external processes associated with it. + * + * ==Combining `ProcessBuilder`== + * + * Two existing `ProcessBuilder` can be combined in the following ways: + * + * * They can be executed in parallel, with the output of the first being fed + * as input to the second, like Unix pipes. This is achieved with the `#|` + * method. + * * They can be executed in sequence, with the second starting as soon as + * the first ends. This is done by the `###` method. + * * The execution of the second one can be conditioned by the return code + * (exit status) of the first, either only when it's zero, or only when it's + * not zero. The methods `#&&` and `#||` accomplish these tasks. + * + * ==Redirecting Input/Output== + * + * Though control of input and output can be done when executing the process, + * there's a few methods that create a new `ProcessBuilder` with a + * pre-configured input or output. They are `#<`, `#>` and `#>>`, and may take + * as input either another `ProcessBuilder` (like the pipe described above), or + * something else such as a `java.io.File` or a `java.lang.InputStream`. + * For example: + * {{{ + * new URL("http://databinder.net/dispatch/About") #> "grep JSON" #>> new File("About_JSON") ! + * }}} + * + * ==Starting Processes== + * + * To execute all external commands associated with a `ProcessBuilder`, one + * may use one of four groups of methods. Each of these methods have various + * overloads and variations to enable further control over the I/O. These + * methods are: + * + * * `run`: the most general method, it returns a + * [[scala.sys.process.Process]] immediately, and the external command + * executes concurrently. + * * `!`: blocks until all external commands exit, and returns the exit code + * of the last one in the chain of execution. + * * `!!`: blocks until all external commands exit, and returns a `String` + * with the output generated. + * * `lines`: returns immediately like `run`, and the output being generared + * is provided through a `Stream[String]`. Getting the next element of that + * `Stream` may block until it becomes available. This method will throw an + * exception if the return code is different than zero -- if this is not + * desired, use the `lines_!` method. + * + * ==Handling Input and Output== + * + * If not specified, the input of the external commands executed with `run` or + * `!` will not be tied to anything, and the output will be redirected to the + * stdout and stderr of the Scala process. For the methods `!!` and `lines`, no + * input will be provided, and the output will be directed according to the + * semantics of these methods. * + * Some methods will cause stdin to be used as input. Output can be controlled + * with a [[scala.sys.process.ProcessLogger]] -- `!!` and `lines` will only + * redirect error output when passed a `ProcessLogger`. If one desires full + * control over input and output, then a [[scala.sys.process.ProcessIO]] can be + * used with `run`. + * + * For example, we could silence the error output from `lines_!` like this: + * {{{ + * val etcFiles = "find /etc" lines_! ProcessLogger(line => ()) + * }}} + * + * ==Extended Example== + * + * Let's examine in detail one example of usage: * {{{ * import scala.sys.process._ * "find src -name *.scala -exec grep null {} ;" #| "xargs test -z" #&& "echo null-free" #|| "echo null detected" ! * }}} - * * Note that every `String` is implicitly converted into a `ProcessBuilder` * through the implicits imported from [[scala.sys.process]]. These `ProcessBuilder` are then * combined in three different ways. * * 1. `#|` pipes the output of the first command into the input of the second command. It - * mirrors a shell pipe (`|`). - * 2. `#&&` conditionally executes the second command if the previous one finished with - * exit value 0. It mirrors shell's `&&`. - * 3. `#||` conditionally executes the third command if the exit value of the previous - * command is different than zero. It mirrors shell's `&&`. - * - * Not shown here, the equivalent of a shell's `;` would be `###`. The reason for this name is - * that `;` is a reserved token in Scala. - * - * Finally, `!` at the end executes the commands, and returns the exit value. If the output - * was desired instead, one could run that with `!!` instead. - * - * If one wishes to execute the commands in background, one can either call `run`, which - * returns a [[scala.sys.process.Process]] from which the exit value can be obtained, or - * `lines`, which returns a [scala.collection.immutable.Stream] of output lines. This throws - * an exception at the end of the `Stream` is the exit value is non-zero. To avoid exceptions, - * one can use `lines_!` instead. - * - * One can also start the commands in specific ways to further control their I/O. Using `!<` to - * start the commands will use the stdin from the current process for them. All methods can - * be used passing a [[scala.sys.process.ProcessLogger]] to capture the output, both stderr and - * stdout. And, when using `run`, one can pass a [[scala.sys.process.ProcessIO]] to control - * stdin, stdout and stderr. - * - * The stdin of a command can be redirected from a `java.io.InputStream`, a `java.io.File`, a - * `java.net.URL` or another `ProcessBuilder` through the method `#<`. Likewise, the stdout - * can be sent to a `java.io.OutputStream`, a `java.io.File` or another `ProcessBuilder` with - * the method `#>`. The method `#>>` can be used to append the output to a `java.io.File`. - * For example: + * mirrors a shell pipe (`|`). + * 1. `#&&` conditionally executes the second command if the previous one finished with + * exit value 0. It mirrors shell's `&&`. + * 1. `#||` conditionally executes the third command if the exit value of the previous + * command is different than zero. It mirrors shell's `&&`. * - * {{{ - * new URL("http://databinder.net/dispatch/About") #> "grep JSON" #>> new File("About_JSON") ! - * }}} + * Finally, `!` at the end executes the commands, and returns the exit value. + * Whatever is printed will be sent to the Scala process standard output. If + * we wanted to caputre it, we could run that with `!!` instead. + * + * Note: though it is not shown above, the equivalent of a shell's `;` would be + * `###`. The reason for this name is that `;` is a reserved token in Scala. */ trait ProcessBuilder extends Source with Sink { - /** Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is - * sent to the console. If the exit code is non-zero, an exception is thrown.*/ + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the console. If + * the exit code is non-zero, an exception is thrown. + */ def !! : String - /** Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is - * sent to the provided ProcessLogger. If the exit code is non-zero, an exception is thrown.*/ + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the provided + * ProcessLogger. If the exit code is non-zero, an exception is thrown. + */ def !!(log: ProcessLogger): String - /** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination and then throw an exception. */ + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the console. If + * the exit code is non-zero, an exception is thrown. The newly started + * process reads from standard input of the current process. + */ + def !!< : String + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the output as a String. Standard error is sent to the provided + * ProcessLogger. If the exit code is non-zero, an exception is thrown. The + * newly started process reads from standard input of the current process. + */ + def !!<(log: ProcessLogger): String + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the console. If the process exits + * with a non-zero value, the Stream will provide all lines up to termination + * and then throw an exception. + */ def lines: Stream[String] - /** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination but will not throw an exception. */ + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the Stream will provide all lines up + * to termination but will not throw an exception. + */ def lines(log: ProcessLogger): Stream[String] - /** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination but will not throw an exception. */ + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the console. If the process exits + * with a non-zero value, the Stream will provide all lines up to termination + * but will not throw an exception. + */ def lines_! : Stream[String] - /** Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available - * but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value, - * the Stream will provide all lines up to termination but will not throw an exception. */ + + /** Starts the process represented by this builder. The output is returned as + * a Stream that blocks when lines are not available but the process has not + * completed. Standard error is sent to the provided ProcessLogger. If the + * process exits with a non-zero value, the Stream will provide all lines up + * to termination but will not throw an exception. + */ def lines_!(log: ProcessLogger): Stream[String] - /** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the console.*/ + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the console. + */ def ! : Int - /** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the given ProcessLogger.*/ + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the given + * ProcessLogger. + */ def !(log: ProcessLogger): Int - /** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the console. The newly started process reads from standard input of the current process.*/ + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the console. + * The newly started process reads from standard input of the current process. + */ def !< : Int - /** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the given ProcessLogger. The newly started process reads from standard input of the current process.*/ + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the given + * ProcessLogger. The newly started process reads from standard input of the + * current process. + */ def !<(log: ProcessLogger): Int - /** Starts the process represented by this builder. Standard output and error are sent to the console.*/ + + /** Starts the process represented by this builder. Standard output and error + * are sent to the console.*/ def run(): Process - /** Starts the process represented by this builder. Standard output and error are sent to the given ProcessLogger.*/ + + /** Starts the process represented by this builder. Standard output and error + * are sent to the given ProcessLogger. + */ def run(log: ProcessLogger): Process - /** Starts the process represented by this builder. I/O is handled by the given ProcessIO instance.*/ + + /** Starts the process represented by this builder. I/O is handled by the + * given ProcessIO instance. + */ def run(io: ProcessIO): Process - /** Starts the process represented by this builder. Standard output and error are sent to the console. - * The newly started process reads from standard input of the current process if `connectInput` is true.*/ + + /** Starts the process represented by this builder. Standard output and error + * are sent to the console. The newly started process reads from standard + * input of the current process if `connectInput` is true. + */ def run(connectInput: Boolean): Process - /** Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are - * sent to the given ProcessLogger. - * The newly started process reads from standard input of the current process if `connectInput` is true.*/ + + /** Starts the process represented by this builder, blocks until it exits, and + * returns the exit code. Standard output and error are sent to the given + * ProcessLogger. The newly started process reads from standard input of the + * current process if `connectInput` is true. + */ def run(log: ProcessLogger, connectInput: Boolean): Process - /** Constructs a command that runs this command first and then `other` if this command succeeds.*/ + /** Constructs a command that runs this command first and then `other` if this + * command succeeds. + */ def #&& (other: ProcessBuilder): ProcessBuilder - /** Constructs a command that runs this command first and then `other` if this command does not succeed.*/ + + /** Constructs a command that runs this command first and then `other` if this + * command does not succeed. + */ def #|| (other: ProcessBuilder): ProcessBuilder - /** Constructs a command that will run this command and pipes the output to `other`. `other` must be a simple command.*/ + + /** Constructs a command that will run this command and pipes the output to + * `other`. `other` must be a simple command. + */ def #| (other: ProcessBuilder): ProcessBuilder - /** Constructs a command that will run this command and then `other`. The exit code will be the exit code of `other`.*/ + + /** Constructs a command that will run this command and then `other`. The + * exit code will be the exit code of `other`. + */ def ### (other: ProcessBuilder): ProcessBuilder - /** True if this command can be the target of a pipe. - */ + + /** True if this command can be the target of a pipe. */ def canPipeTo: Boolean - /** True if this command has an exit code which should be propagated to the user. - * Given a pipe between A and B, if B.hasExitValue is true then the exit code will - * be the one from B; if it is false, the one from A. This exists to prevent output - * redirections (implemented as pipes) from masking useful process error codes. - */ + /** True if this command has an exit code which should be propagated to the + * user. Given a pipe between A and B, if B.hasExitValue is true then the + * exit code will be the one from B; if it is false, the one from A. This + * exists to prevent output redirections (implemented as pipes) from masking + * useful process error codes. + */ def hasExitValue: Boolean } diff --git a/src/library/scala/sys/process/ProcessIO.scala b/src/library/scala/sys/process/ProcessIO.scala index 261e837a4d..fa0674670f 100644 --- a/src/library/scala/sys/process/ProcessIO.scala +++ b/src/library/scala/sys/process/ProcessIO.scala @@ -11,14 +11,40 @@ package process import processInternal._ -/** This class is used to control the I/O of every [[scala.sys.process.ProcessBuilder]]. - * Most of the time, there is no need to interact with `ProcessIO` directly. However, if - * fine control over the I/O of a `ProcessBuilder` is desired, one can use the factories - * on [[scala.sys.process.BasicIO]] stand-alone object to create one. - * - * Each method will be called in a separate thread. - * If daemonizeThreads is true, they will all be marked daemon threads. - */ +/** This class is used to control the I/O of every + * [[scala.sys.process.Process]]. The functions used to create it will be + * called with the process streams once it has been started. It might not be + * necessary to use `ProcessIO` directly -- + * [[scala.sys.process.ProcessBuilder]] can return the process output to the + * caller, or use a [[scala.sys.process.ProcessLogger]] which avoids direct + * interaction with a stream. One can even use the factories at `BasicIO` to + * create a `ProcessIO`, or use its helper methods when creating one's own + * `ProcessIO`. + * + * When creating a `ProcessIO`, it is important to ''close all streams'' when + * finished, since the JVM might use system resources to capture the process + * input and output, and will not release them unless the streams are + * explicitly closed. + * + * `ProcessBuilder` will call `writeInput`, `processOutput` and `processError` + * in separate threads, and if daemonizeThreads is true, they will all be + * marked as daemon threads. + * + * @param writeInput Function that will be called with the `OutputStream` to + * which all input to the process must be written. This will + * be called in a newly spawned thread. + * @param processOutput Function that will be called with the `InputStream` + * from which all normal output of the process must be + * read from. This will be called in a newly spawned + * thread. + * @param processError Function that will be called with the `InputStream` from + * which all error output of the process must be read from. + * This will be called in a newly spawned thread. + * @param daemonizeThreads Indicates whether the newly spawned threads that + * will run `processOutput`, `processError` and + * `writeInput` should be marked as daemon threads. + * @note Failure to close the passed streams may result in resource leakage. + */ final class ProcessIO( val writeInput: OutputStream => Unit, val processOutput: InputStream => Unit, @@ -27,8 +53,15 @@ final class ProcessIO( ) { def this(in: OutputStream => Unit, out: InputStream => Unit, err: InputStream => Unit) = this(in, out, err, false) + /** Creates a new `ProcessIO` with a different handler for the process input. */ def withInput(write: OutputStream => Unit): ProcessIO = new ProcessIO(write, processOutput, processError, daemonizeThreads) + + /** Creates a new `ProcessIO` with a different handler for the normal output. */ def withOutput(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, process, processError, daemonizeThreads) + + /** Creates a new `ProcessIO` with a different handler for the error output. */ def withError(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, processOutput, process, daemonizeThreads) + + /** Creates a new `ProcessIO`, with `daemonizeThreads` true. */ def daemonized(): ProcessIO = new ProcessIO(writeInput, processOutput, processError, true) } diff --git a/src/library/scala/sys/process/ProcessLogger.scala b/src/library/scala/sys/process/ProcessLogger.scala index 67146dd70e..a8241db53c 100644 --- a/src/library/scala/sys/process/ProcessLogger.scala +++ b/src/library/scala/sys/process/ProcessLogger.scala @@ -11,12 +11,26 @@ package process import java.io._ -/** Encapsulates the output and error streams of a running process. - * Many of the methods of `ProcessBuilder` accept a `ProcessLogger` as - * an argument. - * - * @see [[scala.sys.process.ProcessBuilder]] - */ +/** Encapsulates the output and error streams of a running process. This is used + * by [[scala.sys.process.ProcessBuilder]] when starting a process, as an + * alternative to [[scala.sys.process.ProcessIO]], which can be more difficult + * to use. Note that a `ProcessLogger` will be used to create a `ProcessIO` + * anyway. The object `BasicIO` has some functions to do that. + * + * Here is an example that counts the number of lines in the normal and error + * output of a process: + * {{{ + * import scala.sys.process._ + * + * var normalLines = 0 + * var errorLines = 0 + * val countLogger = ProcessLogger(line => normalLines += 1, + * line => errorLines += 1) + * "find /etc" ! countLogger + * }}} + * + * @see [[scala.sys.process.ProcessBuilder]] + */ trait ProcessLogger { /** Will be called with each line read from the process output stream. */ diff --git a/src/library/scala/sys/process/package.scala b/src/library/scala/sys/process/package.scala index 3eb0e5bb89..c1bf470831 100644 --- a/src/library/scala/sys/process/package.scala +++ b/src/library/scala/sys/process/package.scala @@ -11,40 +11,175 @@ // for process debugging output. // package scala.sys { - /** - * This package is used to create process pipelines, similar to Unix command pipelines. + /** This package handles the execution of external processes. The contents of + * this package can be divided in three groups, according to their + * responsibilities: * - * The key concept is that one builds a [[scala.sys.process.Process]] that will run and return an exit - * value. This `Process` is usually composed of one or more [[scala.sys.process.ProcessBuilder]], fed by a - * [[scala.sys.process.ProcessBuilder.Source]] and feeding a [[scala.sys.process.ProcessBuilder.Sink]]. A - * `ProcessBuilder` itself is both a `Source` and a `Sink`. + * - Indicating what to run and how to run it. + * - Handling a process input and output. + * - Running the process. * - * As `ProcessBuilder`, `Sink` and `Source` are abstract, one usually creates them with `apply` methods on - * the companion object of [[scala.sys.process.Process]], or through implicit conversions available in this - * package object from `String` and other types. The pipe is composed through unix-like pipeline and I/O - * redirection operators available on [[scala.sys.process.ProcessBuilder]]. + * For simple uses, the only group that matters is the first one. Running an + * external command can be as simple as `"ls".!`, or as complex as building a + * pipeline of commands such as this: * - * The example below shows how to build and combine such commands. It searches for `null` uses in the `src` - * directory, printing a message indicating whether they were found or not. The first command pipes its - * output to the second command, whose exit value is then used to choose between the third or fourth - * commands. This same example is explained in greater detail on [[scala.sys.process.ProcessBuilder]]. + * {{{ + * import scala.sys.process._ + * "ls" #| "grep .scala" #&& "scalac *.scala" #|| "echo nothing found" lines + * }}} + * + * We describe below the general concepts and architecture of the package, + * and then take a closer look at each of the categories mentioned above. + * + * ==Concepts and Architecture== + * + * The underlying basis for the whole package is Java's `Process` and + * `ProcessBuilder` classes. While there's no need to use these Java classes, + * they impose boundaries on what is possible. One cannot, for instance, + * retrieve a ''process id'' for whatever is executing. + * + * When executing an external process, one can provide a command's name, + * arguments to it, the directory in which it will be executed and what + * environment variables will be set. For each executing process, one can + * feed its standard input through a `java.io.OutputStream`, and read from + * its standard output and standard error through a pair of + * `java.io.InputStream`. One can wait until a process finishes execution and + * then retrieve its return value, or one can kill an executing process. + * Everything else must be built on those features. + * + * This package provides a DSL for running and chaining such processes, + * mimicking Unix shells ability to pipe output from one process to the input + * of another, or control the execution of further processes based on the + * return status of the previous one. + * + * In addition to this DSL, this package also provides a few ways of + * controlling input and output of these processes, going from simple and + * easy to use to complex and flexible. * + * When processes are composed, a new `ProcessBuilder` is created which, when + * run, will execute the `ProcessBuilder` instances it is composed of + * according to the manner of the composition. If piping one process to + * another, they'll be executed simultaneously, and each will be passed a + * `ProcessIO` that will copy the output of one to the input of the other. + * + * ==What to Run and How== + * + * The central component of the process execution DSL is the + * [[scala.sys.process.ProcessBuilder]] trait. It is `ProcessBuilder` that + * implements the process execution DSL, that creates the + * [[scala.sys.process.Process]] that will handle the execution, and return + * the results of such execution to the caller. We can see that DSL in the + * introductory example: `#|`, `#&&` and `#!!` are methods on + * `ProcessBuilder` used to create a new `ProcessBuilder` through + * composition. + * + * One creates a `ProcessBuilder` either through factories on the + * [[scala.sys.process.Process]]'s companion object, or through implicit + * conversions available in this package object itself. Implicitly, each + * process is created either out of a `String`, with arguments separated by + * spaces -- no escaping of spaces is possible -- or out of a + * [[scala.collection.Seq]], where the first element represents the command + * name, and the remaining elements are arguments to it. In this latter case, + * arguments may contain spaces. One can also implicitly convert + * [[scala.xml.Elem]] and `java.lang.ProcessBuilder` into a `ProcessBuilder`. + * In the introductory example, the strings were converted into + * `ProcessBuilder` implicitly. + * + * To further control what how the process will be run, such as specifying + * the directory in which it will be run, see the factories on + * [[scala.sys.process.Process]]'s object companion. + * + * Once the desired `ProcessBuilder` is available, it can be executed in + * different ways, depending on how one desires to control its I/O, and what + * kind of result one wishes for: + * + * - Return status of the process (`!` methods) + * - Output of the process as a `String` (`!!` methods) + * - Continuous output of the process as a `Stream[String]` (`lines` methods) + * - The `Process` representing it (`run` methods) + * + * Some simple examples of these methods: * {{{ * import scala.sys.process._ - * ( - * "find src -name *.scala -exec grep null {} ;" - * #| "xargs test -z" - * #&& "echo null-free" #|| "echo null detected" - * ) ! + * + * // This uses ! to get the exit code + * def fileExists(name: String) = Seq("test", "-f", name).! == 0 + * + * // This uses !! to get the whole result as a string + * val dirContents = "ls".!! + * + * // This "fire-and-forgets" the method, which can be lazily read through + * // a Stream[String] + * def sourceFilesAt(baseDir: String): Stream[String] = { + * val cmd = Seq("find", baseDir, "-name", "*.scala", "-type", "f") + * cmd.lines + * } * }}} * - * Other implicits available here are for [[scala.sys.process.ProcessBuilder.FileBuilder]], which extends - * both `Sink` and `Source`, and for [[scala.sys.process.ProcessBuilder.URLBuilder]], which extends - * `Source` alone. + * We'll see more details about controlling I/O of the process in the next + * section. + * + * ==Handling Input and Output== + * + * In the underlying Java model, once a `Process` has been started, one can + * get `java.io.InputStream` and `java.io.OutpuStream` representing its + * output and input respectively. That is, what one writes to an + * `OutputStream` is turned into input to the process, and the output of a + * process can be read from an `InputStream` -- of which there are two, one + * representing normal output, and the other representing error output. + * + * This model creates a difficulty, which is that the code responsible for + * actually running the external processes is the one that has to take + * decisions about how to handle its I/O. + * + * This package presents an alternative model: the I/O of a running process + * is controlled by a [[scala.sys.process.ProcessIO]] object, which can be + * passed _to_ the code that runs the external process. A `ProcessIO` will + * have direct access to the java streams associated with the process I/O. It + * must, however, close these streams afterwards. + * + * Simpler abstractions are available, however. The components of this + * package that handle I/O are: + * + * - [[scala.sys.process.ProcessIO]]: provides the low level abstraction. + * - [[scala.sys.process.ProcessLogger]]: provides a higher level abstraction + * for output, and can be created through its object companion + * - [[scala.sys.process.BasicIO]]: a library of helper methods for the + * creation of `ProcessIO`. + * - This package object itself, with a few implicit conversions. * - * One can even create a `Process` solely out of these, without running any command. For example, this will - * download from a URL to a file: + * Some examples of I/O handling: + * {{{ + * import scala.sys.process._ + * + * // An overly complex way of computing size of a compressed file + * def gzFileSize(name: String) = { + * val cat = Seq("zcat", "name") + * var count = 0 + * def byteCounter(input: java.io.InputStream) = { + * while(input.read() != -1) count += 1 + * input.close() + * } + * cat ! new ProcessIO(_.close(), byteCounter, _.close()) + * count + * } + * + * // This "fire-and-forgets" the method, which can be lazily read through + * // a Stream[String], and accumulates all errors on a StringBuffer + * def sourceFilesAt(baseDir: String): (Stream[String], StringBuffer) = { + * val buffer = new StringBuffer() + * val cmd = Seq("find", baseDir, "-name", "*.scala", "-type", "f") + * val lines = cmd lines_! ProcessLogger(buffer append _) + * (lines, buffer) + * } + * }}} * + * Instances of the java classes `java.io.File` and `java.net.URL` can both + * be used directly as input to other processes, and `java.io.File` can be + * used as output as well. One can even pipe one to the other directly + * without any intervening process, though that's not a design goal or + * recommended usage. For example, the following code will copy a web page to + * a file: * {{{ * import java.io.File * import java.net.URL @@ -52,26 +187,33 @@ package scala.sys { * new URL("http://www.scala-lang.org/") #> new File("scala-lang.html") ! * }}} * - * One may use a `Process` directly through `ProcessBuilder`'s `run` method, which starts the process in - * the background, and returns a `Process`. If background execution is not desired, one can get a - * `ProcessBuilder` to execute through a method such as `!`, `lines`, `run` or variations thereof. That - * will create the `Process` to execute the commands, and return either the exit value or the output, maybe - * throwing an exception. - * - * Finally, when executing a `ProcessBuilder`, one may pass a [[scala.sys.process.ProcessLogger]] to - * capture stdout and stderr of the executing processes. A `ProcessLogger` may be created through its - * companion object from functions of type `(String) => Unit`, or one might redirect it to a file, using - * [[scala.sys.process.FileProcessLogger]], which can also be created through `ProcessLogger`'s object - * companion. + * More information about the other ways of controlling I/O can be looked at + * in the scaladoc for the associated objects, traits and classes. + * + * ==Running the Process== + * + * Paradoxically, this is the simplest component of all, and the one least + * likely to be interacted with. It consists solely of + * [[scala.sys.process.Process]], and it provides only two methods: + * + * - `exitValue()`: blocks until the process exit, and then returns the exit + * value. This is what happens when one uses the `!` method of + * `ProcessBuilder`. + * - `destroy()`: this will kill the external process and close the streams + * associated with it. */ package object process extends ProcessImplicits { + /** The arguments passed to `java` when creating this process */ def javaVmArguments: List[String] = { import collection.JavaConversions._ java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments().toList } + /** The input stream of this process */ def stdin = java.lang.System.in + /** The output stream of this process */ def stdout = java.lang.System.out + /** The error stream of this process */ def stderr = java.lang.System.err } // private val shell: String => Array[String] = diff --git a/test/files/jvm/serialization.check b/test/files/jvm/serialization.check index 67b77639a2..81b68f0f5d 100644 --- a/test/files/jvm/serialization.check +++ b/test/files/jvm/serialization.check @@ -156,8 +156,8 @@ x = BitSet(0, 8, 9) y = BitSet(0, 8, 9) x equals y: true, y equals x: true -x = Map(C -> 3, B -> 2, A -> 1) -y = Map(C -> 3, A -> 1, B -> 2) +x = Map(A -> 1, C -> 3, B -> 2) +y = Map(A -> 1, C -> 3, B -> 2) x equals y: true, y equals x: true x = Set(buffers, title, layers) @@ -283,8 +283,8 @@ x = ParArray(abc, def, etc) y = ParArray(abc, def, etc) x equals y: true, y equals x: true -x = ParHashMap(1 -> 2, 2 -> 4) -y = ParHashMap(1 -> 2, 2 -> 4) +x = ParHashMap(2 -> 4, 1 -> 2) +y = ParHashMap(2 -> 4, 1 -> 2) x equals y: true, y equals x: true x = ParCtrie(1 -> 2, 2 -> 4) diff --git a/test/files/run/t5293-map.scala b/test/files/run/t5293-map.scala new file mode 100644 index 0000000000..9e186894fc --- /dev/null +++ b/test/files/run/t5293-map.scala @@ -0,0 +1,88 @@ + + + +import scala.collection.JavaConverters._ + + + +object Test extends App { + + def bench(label: String)(body: => Unit): Long = { + val start = System.nanoTime + + 0.until(10).foreach(_ => body) + + val end = System.nanoTime + + //println("%s: %s ms".format(label, (end - start) / 1000.0 / 1000.0)) + + end - start + } + + def benchJava(values: java.util.Map[Int, Int]) = { + bench("Java Map") { + val m = new java.util.HashMap[Int, Int] + + m.putAll(values) + } + } + + def benchScala(values: Iterable[(Int, Int)]) = { + bench("Scala Map") { + val m = new scala.collection.mutable.HashMap[Int, Int] + + m ++= values + } + } + + def benchScalaSorted(values: Iterable[(Int, Int)]) = { + bench("Scala Map sorted") { + val m = new scala.collection.mutable.HashMap[Int, Int] + + m ++= values.toArray.sorted + } + } + + def benchScalaPar(values: Iterable[(Int, Int)]) = { + bench("Scala ParMap") { + val m = new scala.collection.parallel.mutable.ParHashMap[Int, Int] map { x => x } + + m ++= values + } + } + + val total = 50000 + val values = (0 until total) zip (0 until total) + val map = scala.collection.mutable.HashMap.empty[Int, Int] + + map ++= values + + // warmup + for (x <- 0 until 5) { + benchJava(map.asJava) + benchScala(map) + benchScalaPar(map) + benchJava(map.asJava) + benchScala(map) + benchScalaPar(map) + } + + val javamap = benchJava(map.asJava) + val scalamap = benchScala(map) + val scalaparmap = benchScalaPar(map) + + // println(javamap) + // println(scalamap) + // println(scalaparmap) + + assert(scalamap < (javamap * 4)) + assert(scalaparmap < (javamap * 4)) +} + + + + + + + + diff --git a/test/files/specialized/SI-5005.check b/test/files/specialized/SI-5005.check new file mode 100644 index 0000000000..d2a97512ae --- /dev/null +++ b/test/files/specialized/SI-5005.check @@ -0,0 +1,33 @@ +[[syntax trees at end of specialize]]// Scala source: newSource1 +package <empty> { + class C2[@specialized(scala.Boolean) U >: Nothing <: Any] extends Object with ScalaObject { + def <init>(): C2[U] = { + C2.super.<init>(); + () + }; + def apply(x: U): U = x; + <specialized> def apply$mcZ$sp(x: Boolean): Boolean = C2.this.apply(x.asInstanceOf[U]()).asInstanceOf[Boolean]() + }; + class B extends Object with ScalaObject { + def <init>(): B = { + B.super.<init>(); + () + }; + new C2$mcZ$sp().apply$mcZ$sp(true) + }; + <specialized> class C2$mcZ$sp extends C2[Boolean] { + <specialized> def <init>(): C2$mcZ$sp = { + C2$mcZ$sp.super.<init>(); + () + }; + @inline final override <specialized> def apply(x: Boolean): Boolean = C2$mcZ$sp.this.apply$mcZ$sp(x); + @inline final override <specialized> def apply$mcZ$sp(x: Boolean): Boolean = x + } +} + +[log inliner] Analyzing C2.apply count 0 with 1 blocks +[log inliner] C2.apply blocks before inlining: 1 (2) after: 1 (2) +[log inliner] Analyzing C2.apply$mcZ$sp count 0 with 1 blocks +[log inliner] C2.apply$mcZ$sp blocks before inlining: 1 (8) after: 1 (8) +[log inliner] Not inlining into apply because it is marked @inline. +[log inliner] Not inlining into apply$mcZ$sp because it is marked @inline. diff --git a/test/files/specialized/SI-5005.scala b/test/files/specialized/SI-5005.scala new file mode 100644 index 0000000000..cc9d327b08 --- /dev/null +++ b/test/files/specialized/SI-5005.scala @@ -0,0 +1,23 @@ +import scala.tools.partest._ +import java.io._ + +object Test extends DirectTest { + + override def extraSettings: String = "-usejavacp -Xprint:spec -optimize -Ylog:inliner -d " + testOutput.path + + override def code = """ + class C2[@specialized(Boolean) U]() { + @inline final def apply(x: U): U = x + } + + class B { + (new C2[Boolean]())(true) + } + """ + + override def show(): Unit = { + // redirect err to out, for inliner log + System.setErr(new PrintStream(System.out)); + compile() + } +} |