Skip to content
This repository was archived by the owner on Aug 6, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
34 changes: 21 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]
}

/**
Expand All @@ -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 =>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package play.api.libs.iteratee

package object ccompat {
type TraversableLike[X, Y] = scala.collection.TraversableLike[X, Y]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package play.api.libs.iteratee

package object ccompat {
type TraversableLike[X, Y] = scala.collection.IterableOps[X, Iterable, Y]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <https://www.lightbend.com>
*/
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))

}
Loading