diff --git a/.travis.yml b/.travis.yml index 2a5359f..126219b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,9 @@ dist: trusty sudo: false group: beta scala: -- 2.12.1 -- 2.11.8 +- 2.12.8 +- 2.11.12 +- 2.13.0-M5 jdk: - oraclejdk8 cache: diff --git a/build.sbt b/build.sbt index 1c8bb2a..eb1cd36 100644 --- a/build.sbt +++ b/build.sbt @@ -1,42 +1,50 @@ import interplay.ScalaVersions._ -val specsVersion = "3.8.9" +val scala213Version = "2.13.0-M5" + +val specsVersion = "4.3.6" val specsBuild = Seq( "specs2-core", "specs2-junit", "specs2-mock" ).map("org.specs2" %% _ % specsVersion) +val commonSettings = scalariformSettings ++ Seq( + scalaVersion := scala212, + crossScalaVersions := Seq(scala212, scala211, scala213Version), + unmanagedSourceDirectories in Compile += { + val sourceDir = (sourceDirectory in Compile).value + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, n)) if n >= 13 => sourceDir / "scala-2.13+" + case _ => sourceDir / "scala-2.12-" + } + } +) + lazy val `play-iteratees` = project .in(file("iteratees")) .enablePlugins(PlayLibrary) - .settings(scalariformSettings: _*) + .settings(commonSettings: _*) .settings( - scalaVersion := scala212, - crossScalaVersions := Seq(scala212, scala211), libraryDependencies ++= Seq( - "org.scala-stm" %% "scala-stm" % "0.8" + "org.scala-stm" %% "scala-stm" % "0.9", + "org.scala-lang.modules" %% "scala-collection-compat" % "0.2.1" ) ++ specsBuild.map(_ % Test) ) lazy val `play-iteratees-reactive-streams` = project .in(file("streams")) .enablePlugins(PlayLibrary) - .settings(scalariformSettings: _*) + .settings(commonSettings: _*) .settings( - scalaVersion := scala212, - crossScalaVersions := Seq(scala212, scala211), libraryDependencies ++= Seq( - "org.reactivestreams" % "reactive-streams" % "1.0.0" + "org.reactivestreams" % "reactive-streams" % "1.0.2" ) ++ specsBuild.map(_ % Test) ).dependsOn(`play-iteratees`) lazy val `play-iteratees-root` = (project in file(".")) .enablePlugins(PlayRootProject) .aggregate(`play-iteratees`, `play-iteratees-reactive-streams`) - .settings( - scalaVersion := scala212, - crossScalaVersions := Seq(scala212, scala211) - ) + .settings(commonSettings: _*) playBuildRepoName in ThisBuild := "play-iteratees" diff --git a/iteratees/src/main/scala/play/api/libs/iteratee/CharEncoding.scala b/iteratees/src/main/scala-2.12-/play/api/libs/iteratee/CharEncoding.scala similarity index 100% rename from iteratees/src/main/scala/play/api/libs/iteratee/CharEncoding.scala rename to iteratees/src/main/scala-2.12-/play/api/libs/iteratee/CharEncoding.scala diff --git a/iteratees/src/main/scala/play/api/libs/iteratee/Iteratee.scala b/iteratees/src/main/scala-2.12-/play/api/libs/iteratee/Iteratee.scala similarity index 98% rename from iteratees/src/main/scala/play/api/libs/iteratee/Iteratee.scala rename to iteratees/src/main/scala-2.12-/play/api/libs/iteratee/Iteratee.scala index 0558642..5301a70 100644 --- a/iteratees/src/main/scala/play/api/libs/iteratee/Iteratee.scala +++ b/iteratees/src/main/scala-2.12-/play/api/libs/iteratee/Iteratee.scala @@ -7,6 +7,7 @@ import scala.concurrent.{ ExecutionContext, Future } import scala.util.control.NonFatal import play.api.libs.iteratee.Execution.Implicits.{ defaultExecutionContext => dec } import play.api.libs.iteratee.internal.{ eagerFuture, executeFuture, executeIteratee, prepared } +import scala.collection.compat._ /** * Various helper methods to construct, compose and traverse Iteratees. @@ -100,7 +101,7 @@ object Iteratee { * A partially-applied function returned by the `consume` method. */ trait Consume[E] { - def apply[B, That]()(implicit t: E => TraversableOnce[B], bf: scala.collection.generic.CanBuildFrom[E, B, That]): Iteratee[E, That] + def apply[B, That]()(implicit t: E => IterableOnce[B], bf: scala.collection.generic.CanBuildFrom[E, B, That]): Iteratee[E, That] } /** @@ -116,7 +117,7 @@ object Iteratee { * */ def consume[E] = new Consume[E] { - def apply[B, That]()(implicit t: E => TraversableOnce[B], bf: scala.collection.generic.CanBuildFrom[E, B, That]): Iteratee[E, That] = { + def apply[B, That]()(implicit t: E => IterableOnce[B], bf: scala.collection.generic.CanBuildFrom[E, B, That]): Iteratee[E, That] = { fold[E, Seq[E]](Seq.empty) { (els, chunk) => chunk +: els }(dec).map { elts => @@ -571,7 +572,7 @@ trait Iteratee[E, +A] { * @param f a function for transforming the computed result into an Iteratee * $paramEcSingle Note: input concatenation is performed in the iteratee default execution context, not in the user-supplied context. */ - def flatMapTraversable[B, X](f: A => Iteratee[E, B])(implicit p: E => scala.collection.TraversableLike[X, E], bf: scala.collection.generic.CanBuildFrom[E, X, E], ec: ExecutionContext): Iteratee[E, B] = { + def flatMapTraversable[B, X](f: A => Iteratee[E, B])(implicit p: E => play.api.libs.iteratee.ccompat.TraversableLike[X, E], bf: BuildFrom[E, X, E], ec: ExecutionContext): Iteratee[E, B] = { val pec = ec.prepare() self.pureFlatFold { case Step.Done(a, Input.Empty) => f(a) @@ -671,7 +672,7 @@ trait Iteratee[E, +A] { }(dec) }(dec) - def joinConcatI[AIn, X](implicit in: A <:< Iteratee[E, AIn], p: E => scala.collection.TraversableLike[X, E], bf: scala.collection.generic.CanBuildFrom[E, X, E]): Iteratee[E, AIn] = { + def joinConcatI[AIn, X](implicit in: A <:< Iteratee[E, AIn], p: E => play.api.libs.iteratee.ccompat.TraversableLike[X, E], bf: BuildFrom[E, X, E]): Iteratee[E, AIn] = { this.flatMapTraversable { in(_).pureFlatFold[E, AIn] { case Step.Done(a, e) => Done(a, e) diff --git a/iteratees/src/main/scala-2.12-/play/api/libs/iteratee/ccompat/ccompat.scala b/iteratees/src/main/scala-2.12-/play/api/libs/iteratee/ccompat/ccompat.scala new file mode 100644 index 0000000..8ab9792 --- /dev/null +++ b/iteratees/src/main/scala-2.12-/play/api/libs/iteratee/ccompat/ccompat.scala @@ -0,0 +1,5 @@ +package play.api.libs.iteratee + +package object ccompat { + type TraversableLike[X, Y] = scala.collection.TraversableLike[X, Y] +} diff --git a/iteratees/src/main/scala-2.13+/play.api.libs.iteratee.ccompat/package.scala b/iteratees/src/main/scala-2.13+/play.api.libs.iteratee.ccompat/package.scala new file mode 100644 index 0000000..43c88e1 --- /dev/null +++ b/iteratees/src/main/scala-2.13+/play.api.libs.iteratee.ccompat/package.scala @@ -0,0 +1,5 @@ +package play.api.libs.iteratee + +package object ccompat { + type TraversableLike[X, Y] = scala.collection.IterableOps[X, Iterable, Y] +} diff --git a/iteratees/src/main/scala-2.13+/play/api/libs/iteratee/CharEncoding.scala b/iteratees/src/main/scala-2.13+/play/api/libs/iteratee/CharEncoding.scala new file mode 100644 index 0000000..9a1aadb --- /dev/null +++ b/iteratees/src/main/scala-2.13+/play/api/libs/iteratee/CharEncoding.scala @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package play.api.libs.iteratee + +import java.io.{ ByteArrayOutputStream, StringWriter } +import java.nio.charset._ +import java.nio.{ ByteBuffer, CharBuffer } + +import play.api.libs.iteratee.Execution.defaultExecutionContext + +import scala.annotation.tailrec + +/** + * [[Enumeratee]]s for converting chunks of bytes to chunks of chars, and vice-versa. + * + * These methods can handle cases where characters straddle a chunk boundary, and redistribute the data. + * An erroneous encoding or an incompatible decoding causes a [[Step.Error]]. + * + * @define javadoc http://docs.oracle.com/javase/8/docs/api + */ +object CharEncoding { + + private trait Coder[From, To] extends Enumeratee[From, To] { + private type Inner[A] = Iteratee[To, A] + + protected def empty: From + + protected def code(data: From, last: Boolean): Either[CoderResult, (To, From)] + + protected def concat(a: From, b: From): From + + private def step[A](initial: From = empty)(it: Inner[A]): K[From, Inner[A]] = { + case in @ Input.El(chars) => + it.pureFlatFold[From, Inner[A]] { + case Step.Cont(k) => + code(concat(initial, chars), false).fold({ result => + Error(s"coding error: $result", in) + }, { + case (bytes, remaining) => + val newIt = Iteratee.flatten(it.feed(Input.El(bytes))) + Cont(step(remaining)(newIt)) + }) + case _ => Done(it) + }(defaultExecutionContext) + case in @ Input.Empty => + val into = in.asInstanceOf[Input[To]] + val newIt = Iteratee.flatten(it.feed(into)) + Cont(step(initial)(newIt)) + case in @ Input.EOF => + code(initial, true).fold({ result => + Error(s"coding error: $result", in) + }, { + case (string, remaining) => + val newIt = Iteratee.flatten(it.feed(Input.El(string)).flatMap(_.feed(in.asInstanceOf[Input[To]]))(defaultExecutionContext)) + Done(newIt) + }) + } + + def applyOn[A](inner: Inner[A]) = Cont(step()(inner)) + } + + def decode(charset: Charset): Enumeratee[Array[Byte], String] = new Coder[Array[Byte], String] { + protected val empty = Array[Byte]() + + protected def concat(a: Array[Byte], b: Array[Byte]) = a ++ b + + protected def code(bytes: Array[Byte], last: Boolean) = { + val decoder = charset.newDecoder + + val byteBuffer = ByteBuffer.wrap(bytes) + // at least 2, for UTF-32 + val charBuffer = CharBuffer.allocate(2 max math.ceil(bytes.length * decoder.averageCharsPerByte).toInt) + val out = new StringWriter + + @tailrec + def process(charBuffer: CharBuffer): CoderResult = { + val result = decoder.decode(byteBuffer, charBuffer, true) + out.write(charBuffer.array, 0, charBuffer.position) + if (result.isOverflow) { + if (charBuffer.position == 0) { + // shouldn't happen for most encodings + process(CharBuffer.allocate(2 * charBuffer.capacity)) + } else { + charBuffer.clear() + process(charBuffer) + } + } else { + result + } + } + val result = process(charBuffer) + + if (result.isUnmappable || last && result.isMalformed) { + Left(result) + } else { + val remaining = if (result.isError) bytes.drop(byteBuffer.position) else empty + Right((out.toString, remaining)) + } + } + } + + /** + * @throws scala.Exception [[$javadoc/java/nio/charset/UnsupportedCharsetException.html UnsupportedCharsetException]] if no + * charset could be found with the provided name + */ + def decode(charset: String): Enumeratee[Array[Byte], String] = decode(Charset.forName(charset)) + + def encode(charset: Charset): Enumeratee[String, Array[Byte]] = new Coder[String, Array[Byte]] { + protected def empty = "" + + protected def concat(a: String, b: String) = a + b + + protected def code(chars: String, last: Boolean) = { + val encoder = charset.newEncoder + + val charBuffer = CharBuffer.wrap(chars) + // at least 6, for UTF-8 + val byteBuffer = ByteBuffer.allocate(6 max math.ceil(chars.length * encoder.averageBytesPerChar).toInt) + val out = new ByteArrayOutputStream + @tailrec + def process(byteBuffer: ByteBuffer): CoderResult = { + val result = encoder.encode(charBuffer, byteBuffer, true) + out.write(byteBuffer.array, 0, byteBuffer.position) + if (result.isOverflow) { + if (byteBuffer.position == 0) { + // shouldn't happen for most encodings + process(ByteBuffer.allocate(2 * byteBuffer.capacity)) + } else { + byteBuffer.clear() + process(byteBuffer) + } + } else { + result + } + } + val result = process(byteBuffer) + if (result.isUnmappable || last && result.isMalformed) { + Left(result) + } else { + val remaining = if (result.isError) chars.drop(charBuffer.position) else "" + val bytes = out.toByteArray + val bytesWithoutBom = if (charset.name.startsWith("UTF-") && bytes.length >= 2 && bytes(0) == 0xfe.toByte && bytes(1) == 0xff.toByte) { + bytes.drop(2) + } else { + bytes + } + Right((bytesWithoutBom, remaining)) + } + } + + } + + /** + * @throws scala.Exception [[$javadoc/java/nio/charset/UnsupportedCharsetException.html UnsupportedCharsetException]] if no + * charset could be found with the provided name + */ + def encode(charset: String): Enumeratee[String, Array[Byte]] = encode(Charset.forName(charset)) + +} diff --git a/iteratees/src/main/scala-2.13+/play/api/libs/iteratee/Iteratee.scala b/iteratees/src/main/scala-2.13+/play/api/libs/iteratee/Iteratee.scala new file mode 100644 index 0000000..4b35965 --- /dev/null +++ b/iteratees/src/main/scala-2.13+/play/api/libs/iteratee/Iteratee.scala @@ -0,0 +1,798 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package play.api.libs.iteratee + +import scala.concurrent.{ ExecutionContext, Future } +import scala.util.control.NonFatal +import play.api.libs.iteratee.Execution.Implicits.{ defaultExecutionContext => dec } +import play.api.libs.iteratee.internal.{ eagerFuture, executeFuture, executeIteratee, prepared } +import scala.collection.compat._ + +/** + * Various helper methods to construct, compose and traverse Iteratees. + * + * @define paramEcSingle @param ec The context to execute the supplied function with. The context is prepared on the calling thread before being used. + * @define paramEcMultiple @param ec The context to execute the supplied functions with. The context is prepared on the calling thread before being used. + */ +object Iteratee { + + /** + * flatten a [[scala.concurrent.Future]] of [[play.api.libs.iteratee.Iteratee]]] into an Iteratee + * + * @param i a promise of iteratee + */ + def flatten[E, A](i: Future[Iteratee[E, A]]): Iteratee[E, A] = + new FutureIteratee[E, A](i) + + def isDoneOrError[E, A](it: Iteratee[E, A]): Future[Boolean] = it.pureFoldNoEC { case Step.Cont(_) => false; case _ => true } + + /** + * Create an [[play.api.libs.iteratee.Iteratee]] which folds the content of the Input using a given function and an initial state + * + * Example: + * {{{ + * // Count the number of input elements + * def count[E]: Iteratee[E, Int] = Iteratee.fold(0)((c, _) => c + 1) + * }}} + * + * @param state initial state + * @param f a function folding the previous state and an input to a new state + * $paramEcSingle + */ + def fold[E, A](state: A)(f: (A, E) => A)(implicit ec: ExecutionContext): Iteratee[E, A] = foldM(state)((a, e: E) => eagerFuture(f(a, e)))(ec) + + /** + * Create an [[play.api.libs.iteratee.Iteratee]] which folds the content of the Input using a given function and an initial state + * + * M stands for Monadic which in this case means returning a [[scala.concurrent.Future]] for the function argument f, + * so that promises are combined in a complete reactive flow of logic. + * + * + * @param state initial state + * @param f a function folding the previous state and an input to a new promise of state + * $paramEcSingle + */ + def foldM[E, A](state: A)(f: (A, E) => Future[A])(implicit ec: ExecutionContext): Iteratee[E, A] = { + val pec = ec.prepare() + def step(s: A)(i: Input[E]): Iteratee[E, A] = i match { + + case Input.EOF => Done(s, Input.EOF) + case Input.Empty => Cont[E, A](step(s)) + case Input.El(e) => { val newS = executeFuture(f(s, e))(pec); flatten(newS.map(s1 => Cont[E, A](step(s1)))(dec)) } + } + (Cont[E, A](step(state))) + } + + /** + * Create an [[play.api.libs.iteratee.Iteratee]] which folds the content of the Input using a given function and an initial state. + * Like `foldM`, but the fold can be completed earlier by returning a value of `true` in the future result. + * + * @param state initial state + * @param f a function folding the previous state and an input to a promise of state and a boolean indicating whether the fold is done + * $paramEcSingle + */ + def fold2[E, A](state: A)(f: (A, E) => Future[(A, Boolean)])(implicit ec: ExecutionContext): Iteratee[E, A] = { + val pec = ec.prepare() + def step(s: A)(i: Input[E]): Iteratee[E, A] = i match { + + case Input.EOF => Done(s, Input.EOF) + case Input.Empty => Cont[E, A](step(s)) + case Input.El(e) => { val newS = executeFuture(f(s, e))(pec); flatten(newS.map[Iteratee[E, A]] { case (s1, done) => if (!done) Cont[E, A](step(s1)) else Done(s1, Input.Empty) }(dec)) } + } + (Cont[E, A](step(state))) + } + + /** + * Create an [[play.api.libs.iteratee.Iteratee]] which folds the content of the Input using a given function and an initial state + * + * It also gives the opportunity to return a [[scala.concurrent.Future]] so that promises are combined in a complete reactive flow of logic. + * + * + * @param state initial state + * @param f a function folding the previous state and an input to a new promise of state + * $paramEcSingle + */ + def fold1[E, A](state: Future[A])(f: (A, E) => Future[A])(implicit ec: ExecutionContext): Iteratee[E, A] = { + prepared(ec)(pec => flatten(state.map(s => foldM(s)(f)(pec))(dec))) + } + + /** + * A partially-applied function returned by the `consume` method. + */ + trait Consume[E] { + def apply[B, That]()(implicit t: E => IterableOnce[B], bf: scala.collection.BuildFrom[E, B, That]): Iteratee[E, That] + } + + /** + * Create an [[play.api.libs.iteratee.Iteratee]] which consumes and concatenates all Input chunks + * + * Example: + * {{{ + * // Get all chunks of input + * def getAll: Iteratee[Array[Byte], Array[Byte]] = Iteratee.consume[Array[Byte]]() + * }}} + * + * Chunks type should be viewable as TraversableOnce + * + */ + def consume[E] = new Consume[E] { + def apply[B, That]()(implicit t: E => IterableOnce[B], bf: scala.collection.BuildFrom[E, B, That]): Iteratee[E, That] = { + fold[E, Seq[E]](Seq.empty) { (els, chunk) => + chunk +: els + }(dec).map { elts => + if (elts.isEmpty) { + ??? + } else { + val builder = bf.newBuilder(elts.head) + elts.reverse.foreach(builder ++= _) + builder.result() + } + }(dec) + } + } + + /** + * Create an iteratee that takes the first element of the stream, if one occurs before EOF + */ + def head[E]: Iteratee[E, Option[E]] = { + + def step: K[E, Option[E]] = { + case Input.Empty => Cont(step) + case Input.EOF => Done(None, Input.EOF) + case Input.El(e) => Done(Some(e), Input.Empty) + } + Cont(step) + } + + /** + * Consume all the chunks from the stream, and return a list. + */ + def getChunks[E]: Iteratee[E, List[E]] = fold[E, List[E]](Nil) { (els, chunk) => chunk +: els }(dec).map(_.reverse)(dec) + + /** + * Read up to n chunks from the stream stopping when that number of chunks have + * been read or the stream end is reached. If the stream has fewer elements then + * only those elements are returned. Will consume intermediate Input.Empty elements + * but does not consume Input.EOF. + */ + def takeUpTo[E](n: Int): Iteratee[E, Seq[E]] = { + def stepWith(accum: Seq[E]): Iteratee[E, Seq[E]] = { + if (accum.length >= n) Done(accum) else Cont { + case Input.EOF => + Done(accum, Input.EOF) + case Input.Empty => + stepWith(accum) + case Input.El(el) => + stepWith(accum :+ el) + } + } + stepWith(Seq.empty) + } + + /** + * Determines whether or not a stream contains any elements. A stream can be + * empty if it has no inputs (except Input.EOF) or if it consists of only Input.Empty + * elements (and Input.EOF.) A stream is non-empty if it contains an Input.El. + * + * This iteratee consumes the stream as far as the first EOF or Input.El, skipping + * over any Input.Empty elements. When it encounters an Input.EOF or Input.El it + * is Done. + * + * Will consume intermediate Input.Empty elements but does not consume Input.El or + * Input.EOF. + */ + def isEmpty[E]: Iteratee[E, Boolean] = Cont { + case Input.EOF => + Done(true, Input.EOF) + case Input.Empty => + isEmpty[E] + case input @ Input.El(_) => + Done(false, input) + } + + /** + * Ignore all the input of the stream, and return done when EOF is encountered. + */ + def skipToEof[E]: Iteratee[E, Unit] = { + def cont: Iteratee[E, Unit] = Cont { + case Input.EOF => Done((), Input.EOF) + case _ => cont + } + cont + } + + /** + * A partially-applied function returned by the `eofOrElse` method. + */ + trait EofOrElse[E] { + /** + * @param otherwise Value if the input is not [[play.api.libs.iteratee.Input.EOF]] + * @param eofValue Value if the input is [[play.api.libs.iteratee.Input.EOF]] + * @tparam A Type of `eofValue` + * @tparam B Type of `otherwise` + * @return An `Iteratee[E, Either[B, A]]` that consumes one input and produces a `Right(eofValue)` if this input is [[play.api.libs.iteratee.Input.EOF]] otherwise it produces a `Left(otherwise)` + */ + def apply[A, B](otherwise: => B)(eofValue: A): Iteratee[E, Either[B, A]] + } + + def eofOrElse[E] = new EofOrElse[E] { + def apply[A, B](otherwise: => B)(eofValue: A): Iteratee[E, Either[B, A]] = { + def cont: Iteratee[E, Either[B, A]] = Cont((in: Input[E]) => { + in match { + case Input.El(e) => Done(Left(otherwise), in) + case Input.EOF => Done(Right(eofValue), in) + case Input.Empty => cont + } + }) + cont + } + } + + /** + * @return an [[play.api.libs.iteratee.Iteratee]] which just ignores its input + */ + def ignore[E]: Iteratee[E, Unit] = fold[E, Unit](())((_, _) => ())(dec) + + /** + * @return an [[play.api.libs.iteratee.Iteratee]] which executes a provided function for every chunk. Returns Done on EOF. + * + * Example: + * {{{ + * // Get all chunks of input + * def printChunks: Iteratee[String, Unit] = Iteratee.foreach[String]( s => println(s) ) + * }}} + * + * @param f the function that should be executed for every chunk + */ + def foreach[E](f: E => Unit)(implicit ec: ExecutionContext): Iteratee[E, Unit] = fold[E, Unit](())((_, e) => f(e))(ec) + + /** + * + * @return an [[play.api.libs.iteratee.Iteratee]] which pushes the input into the provided [[play.api.libs.iteratee.Iteratee]], starting over again each time it terminates until an EOF is received, collecting a sequence of results of the different use of the iteratee + * + * @param i an iteratee used repeatedly to compute a sequence of results + */ + def repeat[E, A](i: Iteratee[E, A]): Iteratee[E, Seq[A]] = { + + def step(s: Seq[A])(input: Input[E]): Iteratee[E, Seq[A]] = { + input match { + case Input.EOF => Done(s, Input.EOF) + + case Input.Empty => Cont(step(s)) + + case Input.El(e) => i.pureFlatFold { + case Step.Done(a, e) => Done(s :+ a, input) + case Step.Cont(k) => k(input).flatMap(a => repeat(i).map(az => s ++ (a +: az))(dec))(dec) + case Step.Error(msg, e) => Error(msg, e) + }(dec) + } + } + + Cont(step(Seq.empty[A])) + + } + +} + +/** + * Input that can be consumed by an iteratee + */ +sealed trait Input[+E] { + def map[U](f: (E => U)): Input[U] = this match { + case Input.El(e) => Input.El(f(e)) + case Input.Empty => Input.Empty + case Input.EOF => Input.EOF + } +} + +object Input { + + /** + * An input element + */ + case class El[+E](e: E) extends Input[E] + + /** + * An empty input + */ + case object Empty extends Input[Nothing] + + /** + * An end of file input + */ + case object EOF extends Input[Nothing] + +} + +/** + * Represents the state of an iteratee. + */ +sealed trait Step[E, +A] { + + // This version is not called by Step implementations in Play, + // but could be called by custom implementations. + def it: Iteratee[E, A] = this match { + case Step.Done(a, e) => Done(a, e) + case Step.Cont(k) => Cont(k) + case Step.Error(msg, e) => Error(msg, e) + } + +} + +object Step { + + /** + * A done state of an iteratee + * + * @param a The value that the iteratee has consumed + * @param remaining The remaining input that the iteratee received but didn't consume + */ + case class Done[+A, E](a: A, remaining: Input[E]) extends Step[E, A] + + /** + * A continuing state of an iteratee. + * + * @param k A function that can receive input for the iteratee to process. + */ + case class Cont[E, +A](k: Input[E] => Iteratee[E, A]) extends Step[E, A] + + /** + * An error state of an iteratee + * + * @param msg The error message + * @param input The remaining input that the iteratee received but didn't consume + */ + case class Error[E](msg: String, input: Input[E]) extends Step[E, Nothing] +} + +/** + * An Iteratee consumes a stream of elements of type E, producing a result of type A. + * The stream itself is represented by the Input trait. An Iteratee is an immutable + * data type, so each step in consuming the stream generates a new Iteratee with a new + * state. + * + * At a high level, an Iteratee is just a function that takes a piece of input and + * returns either a final result or a new function that takes another piece of input. + * To represent this, an Iteratee can be in one of three states + * (see the [[play.api.libs.iteratee.Step]] trait): + * [[play.api.libs.iteratee.Done]], which means it contains a result and potentially some unconsumed part of the stream; + * [[play.api.libs.iteratee.Cont]], which means it contains a function to be invoked to generate a new Iteratee from the next piece of input; + * [[play.api.libs.iteratee.Error]], which means it contains an error message and potentially some unconsumed part of the stream. + * + * One would expect to transform an Iteratee through the Cont state N times, eventually + * arriving at either the Done or Error state. + * + * Typically an [[play.api.libs.iteratee.Enumerator]] would be used to + * push data into an Iteratee by invoking the function in the [[play.api.libs.iteratee.Cont]] + * state until either 1) the iteratee leaves the Cont state or 2) the enumerator + * runs out of data. + * + * The Iteratee does not do any resource management (such as closing streams); + * the producer pushing stuff into the Iteratee has that responsibility.+ * + * The state of an Iteratee (the current [[play.api.libs.iteratee.Step]] may not be available + * synchronously; it may be pending an asynchronous computation. This is the difference + * between Iteratee and Step. + * @tparam E Input type + * @tparam A Result type of this Iteratee + * + * @define paramEcSingle @param ec The context to execute the supplied function with. The context is prepared on the calling thread. + * @define paramEcMultiple @param ec The context to execute the supplied functions with. The context is prepared on the calling thread. + */ +trait Iteratee[E, +A] { + self => + + /** + * Extracts the computed result of the Iteratee pushing an Input.EOF if necessary + * Extracts the computed result of the Iteratee, pushing an Input.EOF first + * if the Iteratee is in the [[play.api.libs.iteratee.Cont]] state. + * In case of error, an exception may be thrown synchronously or may + * be used to complete the returned Promise; this indeterminate behavior + * is inherited from fold(). + * + * @return a [[scala.concurrent.Future]] of the eventually computed result + */ + def run: Future[A] = fold({ + case Step.Done(a, _) => Future.successful(a) + case Step.Cont(k) => k(Input.EOF).fold({ + case Step.Done(a1, _) => Future.successful(a1) + case Step.Cont(_) => sys.error("diverging iteratee after Input.EOF") + case Step.Error(msg, e) => sys.error(msg) + })(dec) + case Step.Error(msg, e) => sys.error(msg) + })(dec) + + /** + * Sends one element of input to the Iteratee and returns a promise + * containing the new Iteratee. The promise may or may not be completed + * already when it's returned (the iteratee may use an asynchronous operation to handle + * the input). + * @param in input being sent + */ + def feed[AA >: A](in: Input[E]): Future[Iteratee[E, AA]] = { + Enumerator.enumInput(in) |>> this + } + + /** + * Converts the Iteratee into a Promise containing its state. + */ + def unflatten: Future[Step[E, A]] = pureFold(identity)(dec) + + /** + * + * This method provides the means to check on the state of the Iteratee and eventually extract a value in a Promise + * @param done a function that will be called if the Iteratee is a Done + * @param cont a function that will be called if the Iteratee is a Cont + * @param error a function that will be called if the Iteratee is an Error + * $paramEcMultiple + * @return a [[scala.concurrent.Future]] of a value extracted by calling the appropriate provided function + */ + def fold1[B]( + done: (A, Input[E]) => Future[B], + cont: (Input[E] => Iteratee[E, A]) => Future[B], + error: (String, Input[E]) => Future[B] + )(implicit ec: ExecutionContext): Future[B] = fold({ + case Step.Done(a, e) => done(a, e) + case Step.Cont(k) => cont(k) + case Step.Error(msg, e) => error(msg, e) + })(ec) + + /** + * Computes a promised value B from the state of the Iteratee. + * + * The folder function will be run in the supplied ExecutionContext. + * Exceptions thrown by the folder function will be stored in the + * returned Promise. + * + * If the folder function itself is synchronous, it's better to + * use `pureFold()` instead of `fold()`. + * + * @param folder a function that will be called on the current state of the iteratee + * @param ec the ExecutionContext to run folder within + * @return the result returned when folder is called + */ + def fold[B](folder: Step[E, A] => Future[B])(implicit ec: ExecutionContext): Future[B] + + /** + * A version of `fold` that runs `folder` in the current thread rather than in a + * supplied ExecutionContext, called in several places where we are sure the stack + * cannot overflow. This method is designed to be overridden by `StepIteratee`, + * which can execute the `folder` function immediately. + */ + protected[play] def foldNoEC[B](folder: Step[E, A] => Future[B]): Future[B] = + fold(folder)(dec) + + /** + * Like fold but taking functions returning pure values (not in promises) + * + * @return a [[scala.concurrent.Future]] of a value extracted by calling the appropriate provided function + */ + def pureFold[B](folder: Step[E, A] => B)(implicit ec: ExecutionContext): Future[B] = fold(s => eagerFuture(folder(s)))(ec) // Use eagerFuture because fold will ensure folder is run in ec + + /** + * A version of `pureFold` that runs `folder` in the current thread rather than in a + * supplied ExecutionContext, called in several places where we are sure the stack + * cannot overflow. This method is designed to be overridden by `StepIteratee`, + * which can execute the `folder` function immediately. + */ + protected[play] def pureFoldNoEC[B](folder: Step[E, A] => B): Future[B] = + pureFold(folder)(dec) + + /** + * Like pureFold, except taking functions that return an Iteratee + * + * @return an Iteratee extracted by calling the appropriate provided function + */ + def pureFlatFold[B, C](folder: Step[E, A] => Iteratee[B, C])(implicit ec: ExecutionContext): Iteratee[B, C] = Iteratee.flatten(pureFold(folder)(ec)) + + /** + * A version of `pureFlatFold` that runs `folder` in the current thread rather than in a + * supplied ExecutionContext, called in several places where we are sure the stack + * cannot overflow. This method is designed to be overridden by `StepIteratee`, + * which can execute the `folder` function immediately. + */ + protected[play] def pureFlatFoldNoEC[B, C](folder: Step[E, A] => Iteratee[B, C]): Iteratee[B, C] = + pureFlatFold(folder)(dec) + + /** + * Like fold, except flattens the result with Iteratee.flatten. + * + * $paramEcSingle + */ + def flatFold0[B, C](folder: Step[E, A] => Future[Iteratee[B, C]])(implicit ec: ExecutionContext): Iteratee[B, C] = Iteratee.flatten(fold(folder)(ec)) + + /** + * Like fold1, except flattens the result with Iteratee.flatten. + * + * $paramEcSingle + */ + def flatFold[B, C]( + done: (A, Input[E]) => Future[Iteratee[B, C]], + cont: (Input[E] => Iteratee[E, A]) => Future[Iteratee[B, C]], + error: (String, Input[E]) => Future[Iteratee[B, C]] + )(implicit ec: ExecutionContext): Iteratee[B, C] = Iteratee.flatten(fold1(done, cont, error)(ec)) + + /** + * + * Uses the provided function to transform the Iteratee's computed result when the Iteratee is done. + * + * @param f a function for transforming the computed result + * $paramEcSingle + */ + def map[B](f: A => B)(implicit ec: ExecutionContext): Iteratee[E, B] = this.flatMap(a => Done(f(a), Input.Empty))(ec) + + /** + * Like map but allows the map function to execute asynchronously. + * + * This is particularly useful if you want to do blocking operations, so that you can ensure that those operations + * execute in the right execution context, rather than the iteratee execution context, which would potentially block + * all other iteratee operations. + * + * @param f a function for transforming the computed result + * $paramEcSingle + */ + def mapM[B](f: A => Future[B])(implicit ec: ExecutionContext): Iteratee[E, B] = self.flatMapM(a => f(a).map[Iteratee[E, B]](b => Done(b))(dec))(ec) + + /** + * On Done of this Iteratee, the result is passed to the provided function, and the resulting Iteratee is used to continue consuming input + * + * If the resulting Iteratee of evaluating the f function is a Done then its left Input is ignored and its computed result is wrapped in a Done and returned + * + * @param f a function for transforming the computed result into an Iteratee + * $paramEcSingle + */ + def flatMap[B](f: A => Iteratee[E, B])(implicit ec: ExecutionContext): Iteratee[E, B] = self.pureFlatFoldNoEC { + // safe: folder either yields value immediately or executes with another EC + case Step.Done(a, Input.Empty) => executeIteratee(f(a))(ec /* still on same thread; let executeIteratee do preparation */ ) + case Step.Done(a, e) => executeIteratee(f(a))(ec /* still on same thread; let executeIteratee do preparation */ ).pureFlatFold { + case Step.Done(a, _) => Done(a, e) + case Step.Cont(k) => k(e) + case Step.Error(msg, e) => Error(msg, e) + }(dec) + case Step.Cont(k) => { + implicit val pec = ec.prepare() + Cont((in: Input[E]) => executeIteratee(k(in))(dec).flatMap(f)(pec)) + } + case Step.Error(msg, e) => Error(msg, e) + } + + /** + * Like flatMap but allows the flatMap function to execute asynchronously. + * + * This is particularly useful if you want to do blocking operations, so that you can ensure that those operations + * execute in the right execution context, rather than the iteratee execution context, which would potentially block + * all other iteratee operations. + * + * @param f a function for transforming the computed result into an Iteratee + * $paramEcSingle + */ + def flatMapM[B](f: A => Future[Iteratee[E, B]])(implicit ec: ExecutionContext): Iteratee[E, B] = self.flatMap(a => Iteratee.flatten(f(a)))(ec) + + def flatMapInput[B](f: Step[E, A] => Iteratee[E, B])(implicit ec: ExecutionContext): Iteratee[E, B] = self.pureFlatFold(f)(ec) + + /** + * Like flatMap except that it concatenates left over inputs if the Iteratee returned by evaluating f is a Done. + * + * @param f a function for transforming the computed result into an Iteratee + * $paramEcSingle Note: input concatenation is performed in the iteratee default execution context, not in the user-supplied context. + */ + def flatMapTraversable[B, X](f: A => Iteratee[E, B])(implicit p: E => play.api.libs.iteratee.ccompat.TraversableLike[X, E], bf: BuildFrom[E, X, E], ec: ExecutionContext): Iteratee[E, B] = { + val pec = ec.prepare() + self.pureFlatFold { + case Step.Done(a, Input.Empty) => f(a) + case Step.Done(a, e) => executeIteratee(f(a))(pec).pureFlatFold { + case Step.Done(a, eIn) => { + val fullIn = (e, eIn) match { + case (Input.Empty, in) => in + case (in, Input.Empty) => in + case (Input.EOF, _) => Input.EOF + case (in, Input.EOF) => in + case (Input.El(e1), Input.El(e2)) => Input.El[E](p(e1) ++ p(e2)) + } + + Done(a, fullIn) + } + case Step.Cont(k) => k(e) + case Step.Error(msg, e) => Error(msg, e) + }(dec) + case Step.Cont(k) => Cont((in: Input[E]) => k(in).flatMap(f)(pec)) + case Step.Error(msg, e) => Error(msg, e) + }(dec) + } + + /** + * Creates a new Iteratee that will handle any matching exception the original Iteratee may contain. This lets you + * provide a fallback value in case your Iteratee ends up in an error state. + * + * Example: + * + * {{{ + * def it = Iteratee.map(i => 10 / i).recover { case t: Throwable => + * Logger.error("Must have divided by zero!", t) + * Integer.MAX_VALUE + * } + * + * Enumerator(5).run(it) // => 2 + * Enumerator(0).run(it) // => returns Integer.MAX_VALUE and logs "Must have divied by zero!" + * }}} + * + * @param pf + * @param ec + * @tparam B + * @return + */ + def recover[B >: A](pf: PartialFunction[Throwable, B])(implicit ec: ExecutionContext): Iteratee[E, B] = { + recoverM { case t: Throwable if pf.isDefinedAt(t) => Future.successful(pf(t)) }(ec) + } + + /** + * A version of `recover` that allows the partial function to return a Future[B] instead of B. + * + * @param pf + * @param ec + * @tparam B + * @return + */ + def recoverM[B >: A](pf: PartialFunction[Throwable, Future[B]])(implicit ec: ExecutionContext): Iteratee[E, B] = { + val pec = ec.prepare() + recoverWith { case t: Throwable if pf.isDefinedAt(t) => Iteratee.flatten(pf(t).map(b => Done[E, B](b))(pec)) }(ec) + } + + /** + * A version of `recover` that allows the partial function to return an Iteratee[E, B] instead of B. + * + * @param pf + * @param ec + * @tparam B + * @return + */ + def recoverWith[B >: A](pf: PartialFunction[Throwable, Iteratee[E, B]])(implicit ec: ExecutionContext): Iteratee[E, B] = { + implicit val pec = ec.prepare() + + def recoveringIteratee(it: Iteratee[E, A]): Iteratee[E, B] = + Iteratee.flatten(it.pureFold[Iteratee[E, B]] { + case Step.Cont(k) => + Cont { input: Input[E] => recoveringIteratee(k(input)) } + + case Step.Error(msg, _) => + throw new IterateeException(msg) + + case done => done.it + }(dec).recover(pf)(pec)) + + recoveringIteratee(this) + } + + def joinI[AIn](implicit in: A <:< Iteratee[_, AIn]): Iteratee[E, AIn] = + this.flatMap { + in(_).pureFlatFold[E, AIn] { + case Step.Done(a, _) => Done(a, Input.Empty) + case Step.Cont(k) => k(Input.EOF).pureFlatFold[E, AIn] { + case Step.Done(a, _) => Done(a, Input.Empty) + case Step.Cont(k) => Error("divergent inner iteratee on joinI after EOF", Input.EOF) + case Step.Error(msg, e) => Error(msg, Input.EOF) + }(dec) + case Step.Error(msg, e) => Error(msg, Input.Empty) + }(dec) + }(dec) + + def joinConcatI[AIn, X](implicit in: A <:< Iteratee[E, AIn], p: E => play.api.libs.iteratee.ccompat.TraversableLike[X, E], bf: BuildFrom[E, X, E]): Iteratee[E, AIn] = { + this.flatMapTraversable { + in(_).pureFlatFold[E, AIn] { + case Step.Done(a, e) => Done(a, e) + case Step.Cont(k) => k(Input.EOF).pureFlatFold[E, AIn] { + case Step.Done(a, e) => Done(a, e) + case Step.Cont(k) => Error("divergent inner iteratee on joinI after EOF", Input.EOF) + case Step.Error(msg, e) => Error(msg, Input.EOF) + }(dec) + case Step.Error(msg, e) => Error(msg, Input.Empty) + }(dec) + } + } +} + +/** + * An iteratee that already knows its own state, vs [[FutureIteratee]]. + * Several performance improvements are possible when an iteratee's + * state is immediately available. + */ +private sealed trait StepIteratee[E, A] extends Iteratee[E, A] with Step[E, A] { + final override def it: Iteratee[E, A] = this + final def immediateUnflatten: Step[E, A] = this + + final override def unflatten: Future[Step[E, A]] = + Future.successful(immediateUnflatten) + + final override def fold[B](folder: Step[E, A] => Future[B])(implicit ec: ExecutionContext): Future[B] = { + executeFuture { + folder(immediateUnflatten) + }(ec /* executeFuture handles preparation */ ) + } + + final override def pureFold[B](folder: Step[E, A] => B)(implicit ec: ExecutionContext): Future[B] = Future { + folder(immediateUnflatten) + }(ec /* Future.apply handles preparation */ ) + + protected[play] final override def foldNoEC[B](folder: Step[E, A] => Future[B]): Future[B] = folder(immediateUnflatten) + + protected[play] final override def pureFoldNoEC[B](folder: Step[E, A] => B): Future[B] = eagerFuture(folder(immediateUnflatten)) + + protected[play] final override def pureFlatFoldNoEC[B, C](folder: Step[E, A] => Iteratee[B, C]): Iteratee[B, C] = folder(immediateUnflatten) +} + +/** + * An iteratee in the "done" state. + */ +private final class DoneIteratee[E, A](a: A, e: Input[E]) extends Step.Done[A, E](a, e) with StepIteratee[E, A] { + + /** + * Use an optimized implementation because this method is called by Play when running an + * Action over a BodyParser result. + */ + override def mapM[B](f: A => Future[B])(implicit ec: ExecutionContext): Iteratee[E, B] = { + Iteratee.flatten(executeFuture { + f(a).map[Iteratee[E, B]](Done(_, e))(dec) + }(ec /* delegate preparation */ )) + } + +} + +/** + * An iteratee in the "cont" state. + */ +private final class ContIteratee[E, A](k: Input[E] => Iteratee[E, A]) extends Step.Cont[E, A](k) with StepIteratee[E, A] { +} + +/** + * An iteratee in the "error" state. + */ +private final class ErrorIteratee[E](msg: String, e: Input[E]) extends Step.Error[E](msg, e) with StepIteratee[E, Nothing] { +} + +/** + * An iteratee whose state is provided in a Future, vs [[StepIteratee]]. + * Used by `Iteratee.flatten`. + */ +private final class FutureIteratee[E, A]( + itFut: Future[Iteratee[E, A]] +) extends Iteratee[E, A] { + + def fold[B](folder: Step[E, A] => Future[B])(implicit ec: ExecutionContext): Future[B] = + itFut.flatMap { _.fold(folder)(ec.prepare()) }(dec) + +} + +object Done { + /** + * Create an [[play.api.libs.iteratee.Iteratee]] in the “done” state. + * @param a Result + * @param e Remaining unused input + */ + def apply[E, A](a: A, e: Input[E] = Input.Empty): Iteratee[E, A] = new DoneIteratee[E, A](a, e) +} + +object Cont { + /** + * Create an [[play.api.libs.iteratee.Iteratee]] in the “cont” state. + * @param k Continuation which will compute the next Iteratee state according to an input. The continuation is not + * guaranteed to run in any particular execution context. If a particular execution context is needed then the + * continuation should be wrapped before being added, e.g. by running the operation in a Future and flattening the + * result. + */ + def apply[E, A](k: Input[E] => Iteratee[E, A]): Iteratee[E, A] = new ContIteratee[E, A](k) +} + +object Error { + /** + * Create an [[play.api.libs.iteratee.Iteratee]] in the “error” state. + * @param msg Error message + * @param e The input that caused the error + */ + def apply[E](msg: String, e: Input[E]): Iteratee[E, Nothing] = new ErrorIteratee[E](msg, e) +} + +/** + * An Exception that represents an Iteratee that ended up in an Error state with the given + * error message. + */ +class IterateeException(msg: String) extends Exception(msg) diff --git a/iteratees/src/main/scala/play/api/libs/iteratee/Concurrent.scala b/iteratees/src/main/scala/play/api/libs/iteratee/Concurrent.scala index 92e0dd0..04a3376 100644 --- a/iteratees/src/main/scala/play/api/libs/iteratee/Concurrent.scala +++ b/iteratees/src/main/scala/play/api/libs/iteratee/Concurrent.scala @@ -628,16 +628,16 @@ object Concurrent { new Hub[E] { - def noCords() = iteratees.single().isEmpty + def noCords(): Boolean = iteratees.single().isEmpty def close(): Unit = { closeFlag = true } - def closed() = closeFlag + def closed(): Boolean = closeFlag val redeemed = Ref(None: Option[Try[Iteratee[E, Unit]]]) - def getPatchCord() = new Enumerator[E] { + def getPatchCord(): Enumerator[E] = new Enumerator[E] { def apply[A](it: Iteratee[E, A]): Future[Iteratee[E, A]] = { val result = Promise[Iteratee[E, A]]() @@ -819,8 +819,9 @@ object Concurrent { } case err => folder(err) }(ec) - toReturn.onFailure { - case e => doneIteratee.failure(e) + toReturn.onComplete { + case Failure(e) => doneIteratee.failure(e) + case _ => }(dec) toReturn } @@ -848,10 +849,11 @@ object Concurrent { val (consumeRemaining, remaining) = Concurrent.joined[E] result.success((a, remaining)) consumeRemaining - }(dec)).onFailure { - case e => result.tryFailure(e) + }(dec)).onComplete { + case Failure(e) => result.tryFailure(e) + case _ => }(dec) result.future } -} +} \ No newline at end of file diff --git a/iteratees/src/main/scala/play/api/libs/iteratee/Enumeratee.scala b/iteratees/src/main/scala/play/api/libs/iteratee/Enumeratee.scala index 1510bcb..fc9847b 100644 --- a/iteratees/src/main/scala/play/api/libs/iteratee/Enumeratee.scala +++ b/iteratees/src/main/scala/play/api/libs/iteratee/Enumeratee.scala @@ -8,6 +8,7 @@ import play.api.libs.iteratee.internal.{ executeIteratee, executeFuture } import scala.language.reflectiveCalls import scala.util.control.NonFatal import scala.concurrent.{ ExecutionContext, Future } +import scala.collection.compat._ /** * Combines the roles of an Iteratee[From] and a Enumerator[To]. This allows adapting of streams to that modify input @@ -62,7 +63,7 @@ trait Enumeratee[From, To] { * Compose this Enumeratee with another Enumeratee, concatenating any input left by both Enumeratees when they * are done. */ - def composeConcat[X](other: Enumeratee[To, To])(implicit p: To => scala.collection.TraversableLike[X, To], bf: scala.collection.generic.CanBuildFrom[To, X, To]): Enumeratee[From, To] = { + def composeConcat[X](other: Enumeratee[To, To])(implicit p: To => play.api.libs.iteratee.ccompat.TraversableLike[X, To], bf: BuildFrom[To, X, To]): Enumeratee[From, To] = { new Enumeratee[From, To] { def applyOn[A](iteratee: Iteratee[To, A]): Iteratee[From, Iteratee[To, A]] = { parent.applyOn(other.applyOn(iteratee).joinConcatI) @@ -73,7 +74,7 @@ trait Enumeratee[From, To] { /** * Alias for `composeConcat` */ - def >+>[X](other: Enumeratee[To, To])(implicit p: To => scala.collection.TraversableLike[X, To], bf: scala.collection.generic.CanBuildFrom[To, X, To]): Enumeratee[From, To] = composeConcat[X](other) + def >+>[X](other: Enumeratee[To, To])(implicit p: To => play.api.libs.iteratee.ccompat.TraversableLike[X, To], bf: BuildFrom[To, X, To]): Enumeratee[From, To] = composeConcat[X](other) } diff --git a/iteratees/src/main/scala/play/api/libs/iteratee/Enumerator.scala b/iteratees/src/main/scala/play/api/libs/iteratee/Enumerator.scala index 3032acf..7e98c7e 100644 --- a/iteratees/src/main/scala/play/api/libs/iteratee/Enumerator.scala +++ b/iteratees/src/main/scala/play/api/libs/iteratee/Enumerator.scala @@ -10,6 +10,7 @@ import play.api.libs.iteratee.internal.{ eagerFuture, executeFuture } import scala.concurrent.{ ExecutionContext, Future, Promise, blocking } import scala.util.{ Try, Success, Failure } import scala.language.reflectiveCalls +import scala.collection.compat._ /** * A producer which pushes input to an [[play.api.libs.iteratee.Iteratee]]. @@ -529,9 +530,10 @@ object Enumerator { Future.successful(None) }(dec) - next.onFailure { - case reason: Exception => + next.onComplete { + case Failure(reason) => onError(reason.getMessage(), Input.Empty) + case _ => }(dec) next.onComplete { @@ -664,7 +666,7 @@ object Enumerator { * val enumerator: Enumerator[String] = Enumerator( scala.io.Source.fromFile("myfile.txt").getLines ) * }}} */ - def enumerate[E](traversable: TraversableOnce[E])(implicit ctx: scala.concurrent.ExecutionContext): Enumerator[E] = { + def enumerate[E](traversable: IterableOnce[E])(implicit ctx: scala.concurrent.ExecutionContext): Enumerator[E] = { val it = traversable.toIterator Enumerator.unfoldM[scala.collection.Iterator[E], E](it: scala.collection.Iterator[E])({ currentIt => if (currentIt.hasNext) diff --git a/iteratees/src/main/scala/play/api/libs/iteratee/TraversableIteratee.scala b/iteratees/src/main/scala/play/api/libs/iteratee/TraversableIteratee.scala index 486f793..3dd6670 100644 --- a/iteratees/src/main/scala/play/api/libs/iteratee/TraversableIteratee.scala +++ b/iteratees/src/main/scala/play/api/libs/iteratee/TraversableIteratee.scala @@ -15,11 +15,11 @@ object Traversable { * A partially-applied function returned by the `head` method. */ trait Head[E] { - def apply[A](implicit p: E => scala.collection.TraversableLike[A, E]): Iteratee[E, Option[A]] + def apply[A](implicit p: E => play.api.libs.iteratee.ccompat.TraversableLike[A, E]): Iteratee[E, Option[A]] } def head[E] = new Head[E] { - def apply[A](implicit p: E => scala.collection.TraversableLike[A, E]): Iteratee[E, Option[A]] = { + def apply[A](implicit p: E => play.api.libs.iteratee.ccompat.TraversableLike[A, E]): Iteratee[E, Option[A]] = { def step: K[E, Option[A]] = { case Input.Empty => Cont(step) @@ -31,7 +31,7 @@ object Traversable { } } - def takeUpTo[M](count: Long)(implicit p: M => scala.collection.TraversableLike[_, M]): Enumeratee[M, M] = new Enumeratee[M, M] { + def takeUpTo[M](count: Long)(implicit p: M => play.api.libs.iteratee.ccompat.TraversableLike[_, M]): Enumeratee[M, M] = new Enumeratee[M, M] { def applyOn[A](it: Iteratee[M, A]): Iteratee[M, Iteratee[M, A]] = { @@ -58,7 +58,7 @@ object Traversable { } } - def take[M](count: Int)(implicit p: M => scala.collection.TraversableLike[_, M]): Enumeratee[M, M] = new Enumeratee[M, M] { + def take[M](count: Int)(implicit p: M => play.api.libs.iteratee.ccompat.TraversableLike[_, M]): Enumeratee[M, M] = new Enumeratee[M, M] { def applyOn[A](it: Iteratee[M, A]): Iteratee[M, Iteratee[M, A]] = { @@ -95,7 +95,7 @@ object Traversable { * @param p The predicate to split input on. * $paramEcSingle */ - def splitOnceAt[M, E](p: E => Boolean)(implicit traversableLike: M => scala.collection.TraversableLike[E, M], ec: ExecutionContext): Enumeratee[M, M] = new CheckDone[M, M] { + def splitOnceAt[M, E](p: E => Boolean)(implicit traversableLike: M => play.api.libs.iteratee.ccompat.TraversableLike[E, M], ec: ExecutionContext): Enumeratee[M, M] = new CheckDone[M, M] { val pec = ec.prepare() def step[A](k: K[M, A]): K[M, Iteratee[M, A]] = { @@ -117,7 +117,7 @@ object Traversable { } - def drop[M](count: Int)(implicit p: M => scala.collection.TraversableLike[_, M]): Enumeratee[M, M] = new Enumeratee[M, M] { + def drop[M](count: Int)(implicit p: M => play.api.libs.iteratee.ccompat.TraversableLike[_, M]): Enumeratee[M, M] = new Enumeratee[M, M] { def applyOn[A](inner: Iteratee[M, A]): Iteratee[M, Iteratee[M, A]] = { diff --git a/iteratees/src/test/scala/play/api/libs/iteratee/ConcurrentSpec.scala b/iteratees/src/test/scala/play/api/libs/iteratee/ConcurrentSpec.scala index a743884..1ecc17d 100644 --- a/iteratees/src/test/scala/play/api/libs/iteratee/ConcurrentSpec.scala +++ b/iteratees/src/test/scala/play/api/libs/iteratee/ConcurrentSpec.scala @@ -11,6 +11,7 @@ import scala.concurrent._ import scala.concurrent.duration.Duration import scala.concurrent.ExecutionContext.Implicits.global import scala.util.Try +import scala.collection.compat._ object ConcurrentSpec extends Specification with IterateeSpecification with ExecutionSpecification { @@ -83,7 +84,7 @@ object ConcurrentSpec extends Specification Concurrent.buffer(20, (_: Input[Int]) => 1)(bufferEC) |>>> slowIteratee - await(result) must_== ((1 to 10).to[List]) + await(result) must_== ((1 to 10).to(List)) } } diff --git a/iteratees/src/test/scala/play/api/libs/iteratee/EnumerateesSpec.scala b/iteratees/src/test/scala/play/api/libs/iteratee/EnumerateesSpec.scala index 34547ac..da4bf55 100644 --- a/iteratees/src/test/scala/play/api/libs/iteratee/EnumerateesSpec.scala +++ b/iteratees/src/test/scala/play/api/libs/iteratee/EnumerateesSpec.scala @@ -342,7 +342,7 @@ object EnumerateesSpec extends Specification "Enumeratee.grouped" should { "pass along what is consumed by the last folder iteratee on EOF" in { mustExecute(4, 3) { (splitEC, mapEC) => - val upToSpace = Traversable.splitOnceAt[String, Char](c => c != '\n')(implicitly[String => scala.collection.TraversableLike[Char, String]], splitEC) &>> Iteratee.consume() + val upToSpace = Traversable.splitOnceAt[String, Char](c => c != '\n')(implicitly[String => play.api.libs.iteratee.ccompat.TraversableLike[Char, String]], splitEC) &>> Iteratee.consume() val result = (Enumerator("dasdasdas ", "dadadasda\nshouldb\neinnext") &> Enumeratee.grouped(upToSpace) ><> Enumeratee.map[String](_ + "|")(mapEC)) |>>> Iteratee.consume[String]() Await.result(result, Duration.Inf) must equalTo("dasdasdas dadadasda|shouldb|einnext|") diff --git a/iteratees/src/test/scala/play/api/libs/iteratee/IterateesSpec.scala b/iteratees/src/test/scala/play/api/libs/iteratee/IterateesSpec.scala index d816dfb..c9bb09c 100644 --- a/iteratees/src/test/scala/play/api/libs/iteratee/IterateesSpec.scala +++ b/iteratees/src/test/scala/play/api/libs/iteratee/IterateesSpec.scala @@ -172,7 +172,7 @@ object IterateesSpec extends Specification "concatenate unused input with flatMapTraversable" in { mustExecute(1) { flatMapEC => await(Done(3, Input.El(List(1, 2))).flatMapTraversable(_ => Done[List[Int], Int](4, Input.El(List(3, 4))))( - implicitly[List[Int] => scala.collection.TraversableLike[Int, List[Int]]], + implicitly[List[Int] => play.api.libs.iteratee.ccompat.TraversableLike[Int, List[Int]]], implicitly[scala.collection.generic.CanBuildFrom[List[Int], Int, List[Int]]], flatMapEC ).unflatten) must equalTo(Step.Done(4, Input.El(List(1, 2, 3, 4)))) diff --git a/iteratees/src/test/scala/play/api/libs/iteratee/TraversableIterateesSpec.scala b/iteratees/src/test/scala/play/api/libs/iteratee/TraversableIterateesSpec.scala index e66c4e5..2767615 100644 --- a/iteratees/src/test/scala/play/api/libs/iteratee/TraversableIterateesSpec.scala +++ b/iteratees/src/test/scala/play/api/libs/iteratee/TraversableIterateesSpec.scala @@ -13,7 +13,7 @@ object TraversableIterateesSpec extends Specification "yield input while predicate is satisfied" in { mustExecute(1) { splitEC => val e = Traversable.splitOnceAt[String, Char] { c => c != 'e' }( - implicitly[String => scala.collection.TraversableLike[Char, String]], + implicitly[String => play.api.libs.iteratee.ccompat.TraversableLike[Char, String]], splitEC ) mustTransformTo("hello", "there")("h")(e) diff --git a/project/build.properties b/project/build.properties index 64317fd..8e682c5 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.15 +sbt.version=0.13.18 diff --git a/project/plugins.sbt b/project/plugins.sbt index bdd2a4a..4a74c75 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,5 @@ resolvers ++= DefaultOptions.resolvers(snapshot = true) -addSbtPlugin("com.typesafe.play" % "interplay" % "1.3.5") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.8") +addSbtPlugin("com.typesafe.play" % "interplay" % "1.3.18") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.3.0") addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.6.0") - -