Merge pull request #462 from oschulz/bs-iterators

ByteString performance improvements and support for binary I/O
This commit is contained in:
Viktor Klang (√) 2012-07-01 09:49:58 -07:00
commit ec585ed4fa
5 changed files with 1530 additions and 114 deletions

View file

@ -9,12 +9,21 @@ import org.scalacheck.Arbitrary._
import org.scalacheck.Prop._
import org.scalacheck.Gen._
import scala.collection.mutable.Builder
import java.nio.{ ByteBuffer, ShortBuffer, IntBuffer, FloatBuffer, DoubleBuffer }
import java.nio.ByteOrder, ByteOrder.{ BIG_ENDIAN, LITTLE_ENDIAN }
import java.lang.Float.floatToRawIntBits
import java.lang.Double.doubleToRawLongBits
class ByteStringSpec extends WordSpec with MustMatchers with Checkers {
def genSimpleByteString(min: Int, max: Int) = for {
n choose(min, max)
b Gen.containerOfN[Array, Byte](n, arbitrary[Byte])
} yield ByteString(b)
from choose(0, b.length)
until choose(from, b.length)
} yield ByteString(b).slice(from, until)
implicit val arbitraryByteString: Arbitrary[ByteString] = Arbitrary {
Gen.sized { s
@ -25,14 +34,483 @@ class ByteStringSpec extends WordSpec with MustMatchers with Checkers {
}
}
type ByteStringSlice = (ByteString, Int, Int)
implicit val arbitraryByteStringSlice: Arbitrary[ByteStringSlice] = Arbitrary {
for {
xs arbitraryByteString.arbitrary
from choose(0, xs.length)
until choose(from, xs.length)
} yield (xs, from, until)
}
type ArraySlice[A] = (Array[A], Int, Int)
def arbSlice[A](arbArray: Arbitrary[Array[A]]): Arbitrary[ArraySlice[A]] = Arbitrary {
for {
xs arbArray.arbitrary
from choose(0, xs.length)
until choose(from, xs.length)
} yield (xs, from, until)
}
val arbitraryByteArray: Arbitrary[Array[Byte]] = Arbitrary { Gen.sized { n Gen.containerOfN[Array, Byte](n, arbitrary[Byte]) } }
implicit val arbitraryByteArraySlice: Arbitrary[ArraySlice[Byte]] = arbSlice(arbitraryByteArray)
val arbitraryShortArray: Arbitrary[Array[Short]] = Arbitrary { Gen.sized { n Gen.containerOfN[Array, Short](n, arbitrary[Short]) } }
implicit val arbitraryShortArraySlice: Arbitrary[ArraySlice[Short]] = arbSlice(arbitraryShortArray)
val arbitraryIntArray: Arbitrary[Array[Int]] = Arbitrary { Gen.sized { n Gen.containerOfN[Array, Int](n, arbitrary[Int]) } }
implicit val arbitraryIntArraySlice: Arbitrary[ArraySlice[Int]] = arbSlice(arbitraryIntArray)
val arbitraryLongArray: Arbitrary[Array[Long]] = Arbitrary { Gen.sized { n Gen.containerOfN[Array, Long](n, arbitrary[Long]) } }
implicit val arbitraryLongArraySlice: Arbitrary[ArraySlice[Long]] = arbSlice(arbitraryLongArray)
val arbitraryFloatArray: Arbitrary[Array[Float]] = Arbitrary { Gen.sized { n Gen.containerOfN[Array, Float](n, arbitrary[Float]) } }
implicit val arbitraryFloatArraySlice: Arbitrary[ArraySlice[Float]] = arbSlice(arbitraryFloatArray)
val arbitraryDoubleArray: Arbitrary[Array[Double]] = Arbitrary { Gen.sized { n Gen.containerOfN[Array, Double](n, arbitrary[Double]) } }
implicit val arbitraryDoubleArraySlice: Arbitrary[ArraySlice[Double]] = arbSlice(arbitraryDoubleArray)
def likeVector(bs: ByteString)(body: IndexedSeq[Byte] Any): Boolean = {
val vec = Vector(bs: _*)
body(bs) == body(vec)
}
def likeVectors(bsA: ByteString, bsB: ByteString)(body: (IndexedSeq[Byte], IndexedSeq[Byte]) Any): Boolean = {
val vecA = Vector(bsA: _*)
val vecB = Vector(bsB: _*)
body(bsA, bsB) == body(vecA, vecB)
}
def likeVecIt(bs: ByteString)(body: BufferedIterator[Byte] Any, strict: Boolean = true): Boolean = {
val bsIterator = bs.iterator
val vecIterator = Vector(bs: _*).iterator.buffered
(body(bsIterator) == body(vecIterator)) &&
(!strict || (bsIterator.toSeq == vecIterator.toSeq))
}
def likeVecIts(a: ByteString, b: ByteString)(body: (BufferedIterator[Byte], BufferedIterator[Byte]) Any, strict: Boolean = true): Boolean = {
val (bsAIt, bsBIt) = (a.iterator, b.iterator)
val (vecAIt, vecBIt) = (Vector(a: _*).iterator.buffered, Vector(b: _*).iterator.buffered)
(body(bsAIt, bsBIt) == body(vecAIt, vecBIt)) &&
(!strict || (bsAIt.toSeq, bsBIt.toSeq) == (vecAIt.toSeq, vecBIt.toSeq))
}
def likeVecBld(body: Builder[Byte, _] Unit): Boolean = {
val bsBuilder = ByteString.newBuilder
val vecBuilder = Vector.newBuilder[Byte]
body(bsBuilder)
body(vecBuilder)
bsBuilder.result == vecBuilder.result
}
def testShortDecoding(slice: ByteStringSlice, byteOrder: ByteOrder): Boolean = {
val elemSize = 2
val (bytes, from, until) = slice
val (n, a, b) = (bytes.length / elemSize, from / elemSize, until / elemSize)
val reference = Array.ofDim[Short](n)
bytes.asByteBuffer.order(byteOrder).asShortBuffer.get(reference, 0, n)
val input = bytes.iterator
val decoded = Array.ofDim[Short](n)
for (i 0 to a - 1) decoded(i) = input.getShort(byteOrder)
input.getShorts(decoded, a, b - a)(byteOrder)
for (i b to n - 1) decoded(i) = input.getShort(byteOrder)
(decoded.toSeq == reference.toSeq) && (input.toSeq == bytes.drop(n * elemSize))
}
def testIntDecoding(slice: ByteStringSlice, byteOrder: ByteOrder): Boolean = {
val elemSize = 4
val (bytes, from, until) = slice
val (n, a, b) = (bytes.length / elemSize, from / elemSize, until / elemSize)
val reference = Array.ofDim[Int](n)
bytes.asByteBuffer.order(byteOrder).asIntBuffer.get(reference, 0, n)
val input = bytes.iterator
val decoded = Array.ofDim[Int](n)
for (i 0 to a - 1) decoded(i) = input.getInt(byteOrder)
input.getInts(decoded, a, b - a)(byteOrder)
for (i b to n - 1) decoded(i) = input.getInt(byteOrder)
(decoded.toSeq == reference.toSeq) && (input.toSeq == bytes.drop(n * elemSize))
}
def testLongDecoding(slice: ByteStringSlice, byteOrder: ByteOrder): Boolean = {
val elemSize = 8
val (bytes, from, until) = slice
val (n, a, b) = (bytes.length / elemSize, from / elemSize, until / elemSize)
val reference = Array.ofDim[Long](n)
bytes.asByteBuffer.order(byteOrder).asLongBuffer.get(reference, 0, n)
val input = bytes.iterator
val decoded = Array.ofDim[Long](n)
for (i 0 to a - 1) decoded(i) = input.getLong(byteOrder)
input.getLongs(decoded, a, b - a)(byteOrder)
for (i b to n - 1) decoded(i) = input.getLong(byteOrder)
(decoded.toSeq == reference.toSeq) && (input.toSeq == bytes.drop(n * elemSize))
}
def testFloatDecoding(slice: ByteStringSlice, byteOrder: ByteOrder): Boolean = {
val elemSize = 4
val (bytes, from, until) = slice
val (n, a, b) = (bytes.length / elemSize, from / elemSize, until / elemSize)
val reference = Array.ofDim[Float](n)
bytes.asByteBuffer.order(byteOrder).asFloatBuffer.get(reference, 0, n)
val input = bytes.iterator
val decoded = Array.ofDim[Float](n)
for (i 0 to a - 1) decoded(i) = input.getFloat(byteOrder)
input.getFloats(decoded, a, b - a)(byteOrder)
for (i b to n - 1) decoded(i) = input.getFloat(byteOrder)
((decoded.toSeq map floatToRawIntBits) == (reference.toSeq map floatToRawIntBits)) &&
(input.toSeq == bytes.drop(n * elemSize))
}
def testDoubleDecoding(slice: ByteStringSlice, byteOrder: ByteOrder): Boolean = {
val elemSize = 8
val (bytes, from, until) = slice
val (n, a, b) = (bytes.length / elemSize, from / elemSize, until / elemSize)
val reference = Array.ofDim[Double](n)
bytes.asByteBuffer.order(byteOrder).asDoubleBuffer.get(reference, 0, n)
val input = bytes.iterator
val decoded = Array.ofDim[Double](n)
for (i 0 to a - 1) decoded(i) = input.getDouble(byteOrder)
input.getDoubles(decoded, a, b - a)(byteOrder)
for (i b to n - 1) decoded(i) = input.getDouble(byteOrder)
((decoded.toSeq map doubleToRawLongBits) == (reference.toSeq map doubleToRawLongBits)) &&
(input.toSeq == bytes.drop(n * elemSize))
}
def testShortEncoding(slice: ArraySlice[Short], byteOrder: ByteOrder): Boolean = {
val elemSize = 2
val (data, from, until) = slice
val reference = Array.ofDim[Byte](data.length * elemSize)
ByteBuffer.wrap(reference).order(byteOrder).asShortBuffer.put(data)
val builder = ByteString.newBuilder
for (i 0 to from - 1) builder.putShort(data(i))(byteOrder)
builder.putShorts(data, from, until - from)(byteOrder)
for (i until to data.length - 1) builder.putShort(data(i))(byteOrder)
reference.toSeq == builder.result
}
def testIntEncoding(slice: ArraySlice[Int], byteOrder: ByteOrder): Boolean = {
val elemSize = 4
val (data, from, until) = slice
val reference = Array.ofDim[Byte](data.length * elemSize)
ByteBuffer.wrap(reference).order(byteOrder).asIntBuffer.put(data)
val builder = ByteString.newBuilder
for (i 0 to from - 1) builder.putInt(data(i))(byteOrder)
builder.putInts(data, from, until - from)(byteOrder)
for (i until to data.length - 1) builder.putInt(data(i))(byteOrder)
reference.toSeq == builder.result
}
def testLongEncoding(slice: ArraySlice[Long], byteOrder: ByteOrder): Boolean = {
val elemSize = 8
val (data, from, until) = slice
val reference = Array.ofDim[Byte](data.length * elemSize)
ByteBuffer.wrap(reference).order(byteOrder).asLongBuffer.put(data)
val builder = ByteString.newBuilder
for (i 0 to from - 1) builder.putLong(data(i))(byteOrder)
builder.putLongs(data, from, until - from)(byteOrder)
for (i until to data.length - 1) builder.putLong(data(i))(byteOrder)
reference.toSeq == builder.result
}
def testFloatEncoding(slice: ArraySlice[Float], byteOrder: ByteOrder): Boolean = {
val elemSize = 4
val (data, from, until) = slice
val reference = Array.ofDim[Byte](data.length * elemSize)
ByteBuffer.wrap(reference).order(byteOrder).asFloatBuffer.put(data)
val builder = ByteString.newBuilder
for (i 0 to from - 1) builder.putFloat(data(i))(byteOrder)
builder.putFloats(data, from, until - from)(byteOrder)
for (i until to data.length - 1) builder.putFloat(data(i))(byteOrder)
reference.toSeq == builder.result
}
def testDoubleEncoding(slice: ArraySlice[Double], byteOrder: ByteOrder): Boolean = {
val elemSize = 8
val (data, from, until) = slice
val reference = Array.ofDim[Byte](data.length * elemSize)
ByteBuffer.wrap(reference).order(byteOrder).asDoubleBuffer.put(data)
val builder = ByteString.newBuilder
for (i 0 to from - 1) builder.putDouble(data(i))(byteOrder)
builder.putDoubles(data, from, until - from)(byteOrder)
for (i until to data.length - 1) builder.putDouble(data(i))(byteOrder)
reference.toSeq == builder.result
}
"A ByteString" must {
"have correct size" when {
"concatenating" in { check((a: ByteString, b: ByteString) (a ++ b).size == a.size + b.size) }
"dropping" in { check((a: ByteString, b: ByteString) (a ++ b).drop(b.size).size == a.size) }
}
"be sequential" when {
"taking" in { check((a: ByteString, b: ByteString) (a ++ b).take(a.size) == a) }
"dropping" in { check((a: ByteString, b: ByteString) (a ++ b).drop(a.size) == b) }
}
"be equal to the original" when {
"compacting" in { check { xs: ByteString val ys = xs.compact; (xs == ys) && ys.isCompact } }
"recombining" in {
check { (xs: ByteString, from: Int, until: Int)
val (tmp, c) = xs.splitAt(until)
val (a, b) = tmp.splitAt(from)
(a ++ b ++ c) == xs
}
}
}
"behave as expected" when {
"created from and decoding to String" in { check { s: String ByteString(s, "UTF-8").decodeString("UTF-8") == s } }
"compacting" in {
check { a: ByteString
val wasCompact = a.isCompact
val b = a.compact
((!wasCompact) || (b eq a)) &&
(b == a) &&
b.isCompact &&
(b.compact eq b)
}
}
}
"behave like a Vector" when {
"concatenating" in { check { (a: ByteString, b: ByteString) likeVectors(a, b) { (a, b) (a ++ b) } } }
"calling apply" in {
check { slice: ByteStringSlice
slice match {
case (xs, i1, i2) likeVector(xs) { seq
(if ((i1 >= 0) && (i1 < seq.length)) seq(i1) else 0,
if ((i2 >= 0) && (i2 < seq.length)) seq(i2) else 0)
}
}
}
}
"calling head" in { check { a: ByteString a.isEmpty || likeVector(a) { _.head } } }
"calling tail" in { check { a: ByteString a.isEmpty || likeVector(a) { _.tail } } }
"calling last" in { check { a: ByteString a.isEmpty || likeVector(a) { _.last } } }
"calling init" in { check { a: ByteString a.isEmpty || likeVector(a) { _.init } } }
"calling length" in { check { a: ByteString likeVector(a) { _.length } } }
"calling span" in { check { (a: ByteString, b: Byte) likeVector(a)({ _.span(_ != b) match { case (a, b) (a, b) } }) } }
"calling takeWhile" in { check { (a: ByteString, b: Byte) likeVector(a)({ _.takeWhile(_ != b) }) } }
"calling dropWhile" in { check { (a: ByteString, b: Byte) likeVector(a) { _.dropWhile(_ != b) } } }
"calling indexWhere" in { check { (a: ByteString, b: Byte) likeVector(a) { _.indexWhere(_ == b) } } }
"calling indexOf" in { check { (a: ByteString, b: Byte) likeVector(a) { _.indexOf(b) } } }
"calling foreach" in { check { a: ByteString likeVector(a) { it var acc = 0; it foreach { acc += _ }; acc } } }
"calling foldLeft" in { check { a: ByteString likeVector(a) { _.foldLeft(0) { _ + _ } } } }
"calling toArray" in { check { a: ByteString likeVector(a) { _.toArray.toSeq } } }
"calling slice" in {
check { slice: ByteStringSlice
slice match {
case (xs, from, until) likeVector(xs)({
_.slice(from, until)
})
}
}
}
"calling take and drop" in {
check { slice: ByteStringSlice
slice match {
case (xs, from, until) likeVector(xs)({
_.drop(from).take(until - from)
})
}
}
}
"calling copyToArray" in {
check { slice: ByteStringSlice
slice match {
case (xs, from, until) likeVector(xs)({ it
val array = Array.ofDim[Byte](xs.length)
it.slice(from, until).copyToArray(array, from, until)
array.toSeq
})
}
}
}
}
}
"A ByteStringIterator" must {
"behave like a buffered Vector Iterator" when {
"concatenating" in { check { (a: ByteString, b: ByteString) likeVecIts(a, b) { (a, b) (a ++ b).toSeq } } }
"calling head" in { check { a: ByteString a.isEmpty || likeVecIt(a) { _.head } } }
"calling next" in { check { a: ByteString a.isEmpty || likeVecIt(a) { _.next() } } }
"calling hasNext" in { check { a: ByteString likeVecIt(a) { _.hasNext } } }
"calling length" in { check { a: ByteString likeVecIt(a) { _.length } } }
"calling duplicate" in { check { a: ByteString likeVecIt(a)({ _.duplicate match { case (a, b) (a.toSeq, b.toSeq) } }, strict = false) } }
// Have to used toList instead of toSeq here, iterator.span (new in
// Scala-2.9) seems to be broken in combination with toSeq for the
// scala.collection default Iterator (see Scala issue SI-5838).
"calling span" in { check { (a: ByteString, b: Byte) likeVecIt(a)({ _.span(_ != b) match { case (a, b) (a.toList, b.toList) } }, strict = false) } }
"calling takeWhile" in { check { (a: ByteString, b: Byte) likeVecIt(a)({ _.takeWhile(_ != b).toSeq }, strict = false) } }
"calling dropWhile" in { check { (a: ByteString, b: Byte) likeVecIt(a) { _.dropWhile(_ != b).toSeq } } }
"calling indexWhere" in { check { (a: ByteString, b: Byte) likeVecIt(a) { _.indexWhere(_ == b) } } }
"calling indexOf" in { check { (a: ByteString, b: Byte) likeVecIt(a) { _.indexOf(b) } } }
"calling toSeq" in { check { a: ByteString likeVecIt(a) { _.toSeq } } }
"calling foreach" in { check { a: ByteString likeVecIt(a) { it var acc = 0; it foreach { acc += _ }; acc } } }
"calling foldLeft" in { check { a: ByteString likeVecIt(a) { _.foldLeft(0) { _ + _ } } } }
"calling toArray" in { check { a: ByteString likeVecIt(a) { _.toArray.toSeq } } }
"calling slice" in {
check { slice: ByteStringSlice
slice match {
case (xs, from, until) likeVecIt(xs)({
_.slice(from, until).toSeq
}, strict = false)
}
}
}
"calling take and drop" in {
check { slice: ByteStringSlice
slice match {
case (xs, from, until) likeVecIt(xs)({
_.drop(from).take(until - from).toSeq
}, strict = false)
}
}
}
"calling copyToArray" in {
check { slice: ByteStringSlice
slice match {
case (xs, from, until) likeVecIt(xs)({ it
val array = Array.ofDim[Byte](xs.length)
it.slice(from, until).copyToArray(array, from, until)
array.toSeq
}, strict = false)
}
}
}
}
"function as expected" when {
"getting Bytes, using getByte and getBytes" in {
// mixing getByte and getBytes here for more rigorous testing
check { slice: ByteStringSlice
val (bytes, from, until) = slice
val input = bytes.iterator
val output = Array.ofDim[Byte](bytes.length)
for (i 0 to from - 1) output(i) = input.getByte
input.getBytes(output, from, until - from)
for (i until to bytes.length - 1) output(i) = input.getByte
(output.toSeq == bytes) && (input.isEmpty)
}
}
"getting Bytes, using the InputStream wrapper" in {
// combining skip and both read methods here for more rigorous testing
check { slice: ByteStringSlice
val (bytes, from, until) = slice
val a = (0 max from) min bytes.length
val b = (a max until) min bytes.length
val input = bytes.iterator
val output = Array.ofDim[Byte](bytes.length)
input.asInputStream.skip(a)
val toRead = b - a
var (nRead, eof) = (0, false)
while ((nRead < toRead) && !eof) {
val n = input.asInputStream.read(output, a + nRead, toRead - nRead)
if (n == -1) eof = true
else nRead += n
}
if (eof) throw new RuntimeException("Unexpected EOF")
for (i b to bytes.length - 1) output(i) = input.asInputStream.read().toByte
(output.toSeq.drop(a) == bytes.drop(a)) &&
(input.asInputStream.read() == -1) &&
((output.length < 1) || (input.asInputStream.read(output, 0, 1) == -1))
}
}
"calling copyToBuffer" in {
check { bytes: ByteString
import java.nio.ByteBuffer
val buffer = ByteBuffer.allocate(bytes.size)
bytes.copyToBuffer(buffer)
buffer.flip()
val array = Array.ofDim[Byte](bytes.size)
buffer.get(array)
bytes == array.toSeq
}
}
}
"decode data correctly" when {
"decoding Short in big-endian" in { check { slice: ByteStringSlice testShortDecoding(slice, BIG_ENDIAN) } }
"decoding Short in little-endian" in { check { slice: ByteStringSlice testShortDecoding(slice, LITTLE_ENDIAN) } }
"decoding Int in big-endian" in { check { slice: ByteStringSlice testIntDecoding(slice, BIG_ENDIAN) } }
"decoding Int in little-endian" in { check { slice: ByteStringSlice testIntDecoding(slice, LITTLE_ENDIAN) } }
"decoding Long in big-endian" in { check { slice: ByteStringSlice testLongDecoding(slice, BIG_ENDIAN) } }
"decoding Long in little-endian" in { check { slice: ByteStringSlice testLongDecoding(slice, LITTLE_ENDIAN) } }
"decoding Float in big-endian" in { check { slice: ByteStringSlice testFloatDecoding(slice, BIG_ENDIAN) } }
"decoding Float in little-endian" in { check { slice: ByteStringSlice testFloatDecoding(slice, LITTLE_ENDIAN) } }
"decoding Double in big-endian" in { check { slice: ByteStringSlice testDoubleDecoding(slice, BIG_ENDIAN) } }
"decoding Double in little-endian" in { check { slice: ByteStringSlice testDoubleDecoding(slice, LITTLE_ENDIAN) } }
}
}
"A ByteStringBuilder" must {
"function like a VectorBuilder" when {
"adding various contents using ++= and +=" in {
check { (array1: Array[Byte], array2: Array[Byte], bs1: ByteString, bs2: ByteString, bs3: ByteString)
likeVecBld { builder
builder ++= array1
bs1 foreach { b builder += b }
builder ++= bs2
bs3 foreach { b builder += b }
builder ++= Vector(array2: _*)
}
}
}
}
"function as expected" when {
"putting Bytes, using putByte and putBytes" in {
// mixing putByte and putBytes here for more rigorous testing
check { slice: ArraySlice[Byte]
val (data, from, until) = slice
val builder = ByteString.newBuilder
for (i 0 to from - 1) builder.putByte(data(i))
builder.putBytes(data, from, until - from)
for (i until to data.length - 1) builder.putByte(data(i))
data.toSeq == builder.result
}
}
"putting Bytes, using the OutputStream wrapper" in {
// mixing the write methods here for more rigorous testing
check { slice: ArraySlice[Byte]
val (data, from, until) = slice
val builder = ByteString.newBuilder
for (i 0 to from - 1) builder.asOutputStream.write(data(i).toInt)
builder.asOutputStream.write(data, from, until - from)
for (i until to data.length - 1) builder.asOutputStream.write(data(i).toInt)
data.toSeq == builder.result
}
}
}
"encode data correctly" when {
"encoding Short in big-endian" in { check { slice: ArraySlice[Short] testShortEncoding(slice, BIG_ENDIAN) } }
"encoding Short in little-endian" in { check { slice: ArraySlice[Short] testShortEncoding(slice, LITTLE_ENDIAN) } }
"encoding Int in big-endian" in { check { slice: ArraySlice[Int] testIntEncoding(slice, BIG_ENDIAN) } }
"encoding Int in little-endian" in { check { slice: ArraySlice[Int] testIntEncoding(slice, LITTLE_ENDIAN) } }
"encoding Long in big-endian" in { check { slice: ArraySlice[Long] testLongEncoding(slice, BIG_ENDIAN) } }
"encoding Long in little-endian" in { check { slice: ArraySlice[Long] testLongEncoding(slice, LITTLE_ENDIAN) } }
"encoding Float in big-endian" in { check { slice: ArraySlice[Float] testFloatEncoding(slice, BIG_ENDIAN) } }
"encoding Float in little-endian" in { check { slice: ArraySlice[Float] testFloatEncoding(slice, LITTLE_ENDIAN) } }
"encoding Double in big-endian" in { check { slice: ArraySlice[Double] testDoubleEncoding(slice, BIG_ENDIAN) } }
"encoding Double in little-endian" in { check { slice: ArraySlice[Double] testDoubleEncoding(slice, LITTLE_ENDIAN) } }
}
}
}

View file

@ -0,0 +1,610 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.util
import java.nio.{ ByteBuffer, ByteOrder }
import scala.collection.{ LinearSeq, IndexedSeqOptimized }
import scala.collection.mutable.{ Builder, WrappedArray }
import scala.collection.immutable.{ IndexedSeq, VectorBuilder }
import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.{ ListBuffer }
import scala.annotation.tailrec
import java.nio.ByteBuffer
object ByteIterator {
object ByteArrayIterator {
private val emptyArray: Array[Byte] = Array.ofDim[Byte](0)
protected[akka] def apply(array: Array[Byte]): ByteArrayIterator =
new ByteArrayIterator(array, 0, array.length)
protected[akka] def apply(array: Array[Byte], from: Int, until: Int): ByteArrayIterator =
new ByteArrayIterator(array, from, until)
val empty: ByteArrayIterator = apply(emptyArray)
}
class ByteArrayIterator private (private var array: Array[Byte], private var from: Int, private var until: Int) extends ByteIterator {
iterator
@inline final def len: Int = until - from
@inline final def hasNext: Boolean = from < until
@inline final def head: Byte = array(from)
final def next(): Byte = {
if (!hasNext) Iterator.empty.next
else { val i = from; from = from + 1; array(i) }
}
def clear(): Unit = { this.array = ByteArrayIterator.emptyArray; from = 0; until = from }
final override def length: Int = { val l = len; clear(); l }
final override def ++(that: TraversableOnce[Byte]): ByteIterator = that match {
case that: ByteIterator
if (that.isEmpty) this
else if (this.isEmpty) that
else that match {
case that: ByteArrayIterator
if ((this.array eq that.array) && (this.until == that.from)) {
this.until = that.until
that.clear()
this
} else {
val result = MultiByteArrayIterator(List(this, that))
this.clear()
result
}
case that: MultiByteArrayIterator this ++: that
}
case _ super.++(that)
}
final override def clone: ByteArrayIterator = new ByteArrayIterator(array, from, until)
final override def take(n: Int): this.type = {
if (n < len) until = { if (n > 0) (from + n) else from }
this
}
final override def drop(n: Int): this.type = {
if (n > 0) from = { if (n < len) (from + n) else until }
this
}
final override def takeWhile(p: Byte Boolean): this.type = {
val prev = from
dropWhile(p)
until = from; from = prev
this
}
final override def dropWhile(p: Byte Boolean): this.type = {
var stop = false
while (!stop && hasNext) {
if (p(array(from))) { from = from + 1 } else { stop = true }
}
this
}
final override def copyToArray[B >: Byte](xs: Array[B], start: Int, len: Int): Unit = {
val n = 0 max ((xs.length - start) min this.len min len)
Array.copy(this.array, from, xs, start, n)
this.drop(n)
}
final override def toByteString: ByteString = {
val result =
if ((from == 0) && (until == array.length)) ByteString.ByteString1C(array)
else ByteString.ByteString1(array, from, len)
clear()
result
}
def getBytes(xs: Array[Byte], offset: Int, n: Int): this.type = {
if (n <= this.len) {
Array.copy(this.array, this.from, xs, offset, n)
this.drop(n)
} else Iterator.empty.next
}
private def wrappedByteBuffer: ByteBuffer = ByteBuffer.wrap(array, from, len).asReadOnlyBuffer
def getShorts(xs: Array[Short], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
{ wrappedByteBuffer.order(byteOrder).asShortBuffer.get(xs, offset, n); drop(2 * n) }
def getInts(xs: Array[Int], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
{ wrappedByteBuffer.order(byteOrder).asIntBuffer.get(xs, offset, n); drop(4 * n) }
def getLongs(xs: Array[Long], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
{ wrappedByteBuffer.order(byteOrder).asLongBuffer.get(xs, offset, n); drop(8 * n) }
def getFloats(xs: Array[Float], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
{ wrappedByteBuffer.order(byteOrder).asFloatBuffer.get(xs, offset, n); drop(4 * n) }
def getDoubles(xs: Array[Double], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
{ wrappedByteBuffer.order(byteOrder).asDoubleBuffer.get(xs, offset, n); drop(8 * n) }
def copyToBuffer(buffer: ByteBuffer): Int = {
val copyLength = math.min(buffer.remaining, len)
if (copyLength > 0) {
buffer.put(array, from, copyLength)
drop(copyLength)
}
copyLength
}
def asInputStream: java.io.InputStream = new java.io.InputStream {
override def available: Int = iterator.len
def read: Int = if (hasNext) (next().toInt & 0xff) else -1
override def read(b: Array[Byte], off: Int, len: Int): Int = {
if ((off < 0) || (len < 0) || (off + len > b.length)) throw new IndexOutOfBoundsException
if (len == 0) 0
else if (!isEmpty) {
val nRead = math.min(available, len)
copyToArray(b, off, nRead)
nRead
} else -1
}
override def skip(n: Long): Long = {
val nSkip = math.min(iterator.len, n.toInt)
iterator.drop(nSkip)
nSkip
}
}
}
object MultiByteArrayIterator {
protected val clearedList: List[ByteArrayIterator] = List(ByteArrayIterator.empty)
val empty: MultiByteArrayIterator = new MultiByteArrayIterator(Nil)
protected[akka] def apply(iterators: LinearSeq[ByteArrayIterator]): MultiByteArrayIterator =
new MultiByteArrayIterator(iterators)
}
class MultiByteArrayIterator private (private var iterators: LinearSeq[ByteArrayIterator]) extends ByteIterator {
// After normalization:
// * iterators.isEmpty == false
// * (!iterator.head.isEmpty || iterators.tail.isEmpty) == true
private def normalize(): this.type = {
@tailrec def norm(xs: LinearSeq[ByteArrayIterator]): LinearSeq[ByteArrayIterator] = {
if (xs.isEmpty) MultiByteArrayIterator.clearedList
else if (xs.head.isEmpty) norm(xs.tail)
else xs
}
iterators = norm(iterators)
this
}
normalize()
@inline private def current: ByteArrayIterator = iterators.head
@inline private def dropCurrent(): Unit = { iterators = iterators.tail }
@inline def clear(): Unit = { iterators = MultiByteArrayIterator.empty.iterators }
@inline final def hasNext: Boolean = current.hasNext
@inline final def head: Byte = current.head
final def next(): Byte = {
val result = current.next()
normalize()
result
}
final override def len: Int = iterators.foldLeft(0) { _ + _.len }
final override def length: Int = {
var result = len
clear()
result
}
private[akka] def ++:(that: ByteArrayIterator): this.type = {
iterators = that +: iterators
this
}
final override def ++(that: TraversableOnce[Byte]): ByteIterator = that match {
case that: ByteIterator
if (that.isEmpty) this
else if (this.isEmpty) that
else {
that match {
case that: ByteArrayIterator
iterators = this.iterators :+ that
that.clear()
this
case that: MultiByteArrayIterator
iterators = this.iterators ++ that.iterators
that.clear()
this
}
}
case _ super.++(that)
}
final override def clone: MultiByteArrayIterator = {
val clonedIterators: List[ByteArrayIterator] = iterators.map(_.clone)(collection.breakOut)
new MultiByteArrayIterator(clonedIterators)
}
final override def take(n: Int): this.type = {
var rest = n
val builder = new ListBuffer[ByteArrayIterator]
while ((rest > 0) && !iterators.isEmpty) {
current.take(rest)
if (current.hasNext) {
rest -= current.len
builder += current
}
iterators = iterators.tail
}
iterators = builder.result
normalize()
}
@tailrec final override def drop(n: Int): this.type = if ((n > 0) && !isEmpty) {
val nCurrent = math.min(n, current.len)
current.drop(n)
val rest = n - nCurrent
assert(current.isEmpty || (rest == 0))
normalize()
drop(rest)
} else this
final override def takeWhile(p: Byte Boolean): this.type = {
var stop = false
var builder = new ListBuffer[ByteArrayIterator]
while (!stop && !iterators.isEmpty) {
val lastLen = current.len
current.takeWhile(p)
if (current.hasNext) builder += current
if (current.len < lastLen) stop = true
dropCurrent()
}
iterators = builder.result
normalize()
}
@tailrec final override def dropWhile(p: Byte Boolean): this.type = if (!isEmpty) {
current.dropWhile(p)
val dropMore = current.isEmpty
normalize()
if (dropMore) dropWhile(p) else this
} else this
final override def copyToArray[B >: Byte](xs: Array[B], start: Int, len: Int): Unit = {
var pos = start
var rest = len
while ((rest > 0) && !iterators.isEmpty) {
val n = 0 max ((xs.length - pos) min current.len min rest)
current.copyToArray(xs, pos, n)
pos += n
rest -= n
dropCurrent()
}
normalize()
}
override def foreach[@specialized U](f: Byte U): Unit = {
iterators foreach { _ foreach f }
clear()
}
final override def toByteString: ByteString = {
if (iterators.tail isEmpty) iterators.head.toByteString
else {
val result = iterators.foldLeft(ByteString.empty) { _ ++ _.toByteString }
clear()
result
}
}
@tailrec protected final def getToArray[A](xs: Array[A], offset: Int, n: Int, elemSize: Int)(getSingle: A)(getMult: (Array[A], Int, Int) Unit): this.type = if (n <= 0) this else {
if (isEmpty) Iterator.empty.next
val nDone = if (current.len >= elemSize) {
val nCurrent = math.min(n, current.len / elemSize)
getMult(xs, offset, nCurrent)
nCurrent
} else {
xs(offset) = getSingle
1
}
normalize()
getToArray(xs, offset + nDone, n - nDone, elemSize)(getSingle)(getMult)
}
def getBytes(xs: Array[Byte], offset: Int, n: Int): this.type =
getToArray(xs, offset, n, 1) { getByte } { current.getBytes(_, _, _) }
def getShorts(xs: Array[Short], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
getToArray(xs, offset, n, 2) { getShort(byteOrder) } { current.getShorts(_, _, _)(byteOrder) }
def getInts(xs: Array[Int], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
getToArray(xs, offset, n, 4) { getInt(byteOrder) } { current.getInts(_, _, _)(byteOrder) }
def getLongs(xs: Array[Long], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
getToArray(xs, offset, n, 8) { getLong(byteOrder) } { current.getLongs(_, _, _)(byteOrder) }
def getFloats(xs: Array[Float], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
getToArray(xs, offset, n, 8) { getFloat(byteOrder) } { current.getFloats(_, _, _)(byteOrder) }
def getDoubles(xs: Array[Double], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
getToArray(xs, offset, n, 8) { getDouble(byteOrder) } { current.getDoubles(_, _, _)(byteOrder) }
def copyToBuffer(buffer: ByteBuffer): Int = {
val n = iterators.foldLeft(0) { _ + _.copyToBuffer(buffer) }
normalize()
n
}
def asInputStream: java.io.InputStream = new java.io.InputStream {
override def available: Int = current.len
def read: Int = if (hasNext) (next().toInt & 0xff) else -1
override def read(b: Array[Byte], off: Int, len: Int): Int = {
val nRead = current.asInputStream.read(b, off, len)
normalize()
nRead
}
override def skip(n: Long): Long = {
@tailrec def skipImpl(n: Long, skipped: Long): Long = if (n > 0) {
if (!isEmpty) {
val m = current.asInputStream.skip(n)
normalize()
val newN = n - m
val newSkipped = skipped + m
if (newN > 0) skipImpl(newN, newSkipped)
else newSkipped
} else 0
} else 0
skipImpl(n, 0)
}
}
}
}
/**
* An iterator over a ByteString.
*/
abstract class ByteIterator extends BufferedIterator[Byte] {
def len: Int
def head: Byte
def next(): Byte
protected def clear(): Unit
def ++(that: TraversableOnce[Byte]): ByteIterator = if (that.isEmpty) this else ByteIterator.ByteArrayIterator(that.toArray)
// *must* be overridden by derived classes. This construction is necessary
// to specialize the return type, as the method is already implemented in
// the parent class.
override def clone: ByteIterator = throw new UnsupportedOperationException("Method clone is not implemented in ByteIterator")
override def duplicate: (ByteIterator, ByteIterator) = (this, clone)
// *must* be overridden by derived classes. This construction is necessary
// to specialize the return type, as the method is already implemented in
// the parent class.
override def take(n: Int): this.type = throw new UnsupportedOperationException("Method take is not implemented in ByteIterator")
// *must* be overridden by derived classes. This construction is necessary
// to specialize the return type, as the method is already implemented in
// the parent class.
override def drop(n: Int): this.type = throw new UnsupportedOperationException("Method drop is not implemented in ByteIterator")
override def slice(from: Int, until: Int): this.type = {
if (from > 0) drop(from).take(until - from)
else take(until)
}
// *must* be overridden by derived classes. This construction is necessary
// to specialize the return type, as the method is already implemented in
// the parent class.
override def takeWhile(p: Byte Boolean): this.type = throw new UnsupportedOperationException("Method takeWhile is not implemented in ByteIterator")
// *must* be overridden by derived classes. This construction is necessary
// to specialize the return type, as the method is already implemented in
// the parent class.
override def dropWhile(p: Byte Boolean): this.type = throw new UnsupportedOperationException("Method dropWhile is not implemented in ByteIterator")
override def span(p: Byte Boolean): (ByteIterator, ByteIterator) = {
val that = clone
this.takeWhile(p)
that.drop(this.len)
(this, that)
}
override def indexWhere(p: Byte Boolean): Int = {
var index = 0
var found = false
while (!found && hasNext) if (p(next())) { found = true } else { index += 1 }
if (found) index else -1
}
def indexOf(elem: Byte): Int = indexWhere { _ == elem }
override def indexOf[B >: Byte](elem: B): Int = indexWhere { _ == elem }
def toByteString: ByteString
override def toSeq: ByteString = toByteString
override def foreach[@specialized U](f: Byte U): Unit =
while (hasNext) f(next())
override def foldLeft[@specialized B](z: B)(op: (B, Byte) B): B = {
var acc = z
foreach { byte acc = op(acc, byte) }
acc
}
override def toArray[B >: Byte](implicit arg0: ClassManifest[B]): Array[B] = {
val target = Array.ofDim[B](len)
copyToArray(target)
target
}
/**
* Get a single Byte from this iterator. Identical to next().
*/
def getByte: Byte = next()
/**
* Get a single Short from this iterator.
*/
def getShort(implicit byteOrder: ByteOrder): Short = {
if (byteOrder == ByteOrder.BIG_ENDIAN)
((next() & 0xff) << 8 | (next() & 0xff) << 0).toShort
else if (byteOrder == ByteOrder.LITTLE_ENDIAN)
((next() & 0xff) << 0 | (next() & 0xff) << 8).toShort
else throw new IllegalArgumentException("Unknown byte order " + byteOrder)
}
/**
* Get a single Int from this iterator.
*/
def getInt(implicit byteOrder: ByteOrder): Int = {
if (byteOrder == ByteOrder.BIG_ENDIAN)
((next() & 0xff) << 24
| (next() & 0xff) << 16
| (next() & 0xff) << 8
| (next() & 0xff) << 0)
else if (byteOrder == ByteOrder.LITTLE_ENDIAN)
((next() & 0xff) << 0
| (next() & 0xff) << 8
| (next() & 0xff) << 16
| (next() & 0xff) << 24)
else throw new IllegalArgumentException("Unknown byte order " + byteOrder)
}
/**
* Get a single Long from this iterator.
*/
def getLong(implicit byteOrder: ByteOrder): Long = {
if (byteOrder == ByteOrder.BIG_ENDIAN)
((next().toLong & 0xff) << 56
| (next().toLong & 0xff) << 48
| (next().toLong & 0xff) << 40
| (next().toLong & 0xff) << 32
| (next().toLong & 0xff) << 24
| (next().toLong & 0xff) << 16
| (next().toLong & 0xff) << 8
| (next().toLong & 0xff) << 0)
else if (byteOrder == ByteOrder.LITTLE_ENDIAN)
((next().toLong & 0xff) << 0
| (next().toLong & 0xff) << 8
| (next().toLong & 0xff) << 16
| (next().toLong & 0xff) << 24
| (next().toLong & 0xff) << 32
| (next().toLong & 0xff) << 40
| (next().toLong & 0xff) << 48
| (next().toLong & 0xff) << 56)
else throw new IllegalArgumentException("Unknown byte order " + byteOrder)
}
def getFloat(implicit byteOrder: ByteOrder): Float =
java.lang.Float.intBitsToFloat(getInt(byteOrder))
def getDouble(implicit byteOrder: ByteOrder): Double =
java.lang.Double.longBitsToDouble(getLong(byteOrder))
/**
* Get a specific number of Bytes from this iterator. In contrast to
* copyToArray, this method will fail if this.len < xs.length.
*/
def getBytes(xs: Array[Byte]): this.type = getBytes(xs, 0, xs.length)
/**
* Get a specific number of Bytes from this iterator. In contrast to
* copyToArray, this method will fail if length < n or if (xs.length - offset) < n.
*/
def getBytes(xs: Array[Byte], offset: Int, n: Int): this.type
/**
* Get a number of Shorts from this iterator.
*/
def getShorts(xs: Array[Short])(implicit byteOrder: ByteOrder): this.type =
getShorts(xs, 0, xs.length)(byteOrder)
/**
* Get a number of Shorts from this iterator.
*/
def getShorts(xs: Array[Short], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type
/**
* Get a number of Ints from this iterator.
*/
def getInts(xs: Array[Int])(implicit byteOrder: ByteOrder): this.type =
getInts(xs, 0, xs.length)(byteOrder)
/**
* Get a number of Ints from this iterator.
*/
def getInts(xs: Array[Int], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type
/**
* Get a number of Longs from this iterator.
*/
def getLongs(xs: Array[Long])(implicit byteOrder: ByteOrder): this.type =
getLongs(xs, 0, xs.length)(byteOrder)
/**
* Get a number of Longs from this iterator.
*/
def getLongs(xs: Array[Long], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type
/**
* Get a number of Floats from this iterator.
*/
def getFloats(xs: Array[Float])(implicit byteOrder: ByteOrder): this.type =
getFloats(xs, 0, xs.length)(byteOrder)
/**
* Get a number of Floats from this iterator.
*/
def getFloats(xs: Array[Float], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type
/**
* Get a number of Doubles from this iterator.
*/
def getDoubles(xs: Array[Double])(implicit byteOrder: ByteOrder): this.type =
getDoubles(xs, 0, xs.length)(byteOrder)
/**
* Get a number of Doubles from this iterator.
*/
def getDoubles(xs: Array[Double], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type
/**
* Copy as many bytes as possible to a ByteBuffer, starting from it's
* current position. This method will not overflow the buffer.
*
* @param buffer a ByteBuffer to copy bytes to
* @return the number of bytes actually copied
*/
def copyToBuffer(buffer: ByteBuffer): Int
/**
* Directly wraps this ByteIterator in an InputStream without copying.
* Read and skip operations on the stream will advance the iterator
* accordingly.
*/
def asInputStream: java.io.InputStream
}

View file

@ -4,14 +4,13 @@
package akka.util
import java.nio.ByteBuffer
import java.nio.{ ByteBuffer, ByteOrder }
import scala.collection.IndexedSeqOptimized
import scala.collection.mutable.{ Builder, WrappedArray }
import scala.collection.immutable.{ IndexedSeq, VectorBuilder }
import scala.collection.generic.CanBuildFrom
//FIXME MORE DOCS
object ByteString {
/**
@ -67,7 +66,7 @@ object ByteString {
}
/**
* A compact (unsliced) and unfragmented ByteString, implementaton of ByteString1C.
* A compact (unsliced) and unfragmented ByteString, implementation of ByteString1C.
*/
@SerialVersionUID(3956956327691936932L)
final class ByteString1C private (private val bytes: Array[Byte]) extends CompactByteString {
@ -75,32 +74,29 @@ object ByteString {
override def length: Int = bytes.length
def toArray: Array[Byte] = bytes.clone
override def iterator: ByteIterator.ByteArrayIterator = ByteIterator.ByteArrayIterator(bytes, 0, bytes.length)
def toByteString1: ByteString1 = ByteString1(bytes)
private[akka] def toByteString1: ByteString1 = ByteString1(bytes)
override def clone: ByteString1C = new ByteString1C(toArray)
def compact: ByteString1C = this
def asByteBuffer: ByteBuffer = toByteString1.asByteBuffer
def asByteBuffer: ByteBuffer =
toByteString1.asByteBuffer
def decodeString(charset: String): String = new String(bytes, charset)
def ++(that: ByteString): ByteString = if (!that.isEmpty) toByteString1 ++ that else this
def ++(that: ByteString): ByteString =
if (that.isEmpty) this
else if (this.isEmpty) that
else toByteString1 ++ that
override def slice(from: Int, until: Int): ByteString =
if ((from != 0) || (until != length)) toByteString1.slice(from, until)
else this
override def copyToArray[A >: Byte](xs: Array[A], start: Int, len: Int): Unit =
toByteString1.copyToArray(xs, start, len)
def copyToBuffer(buffer: ByteBuffer): Int = toByteString1.copyToBuffer(buffer)
}
private[akka] object ByteString1 {
def apply(bytes: Array[Byte]): ByteString1 = new ByteString1(bytes)
def apply(bytes: Array[Byte], startIndex: Int, length: Int): ByteString1 =
new ByteString1(bytes, startIndex, length)
}
/**
@ -112,6 +108,9 @@ object ByteString {
def apply(idx: Int): Byte = bytes(checkRangeConvert(idx))
override def iterator: ByteIterator.ByteArrayIterator =
ByteIterator.ByteArrayIterator(bytes, startIndex, startIndex + length)
private def checkRangeConvert(index: Int): Int = {
if (0 <= index && length > index)
index + startIndex
@ -119,15 +118,10 @@ object ByteString {
throw new IndexOutOfBoundsException(index.toString)
}
def toArray: Array[Byte] = {
val ar = new Array[Byte](length)
Array.copy(bytes, startIndex, ar, 0, length)
ar
}
def isCompact: Boolean = (length == bytes.length)
override def clone: CompactByteString = ByteString1C(toArray)
def compact: CompactByteString = if (length == bytes.length) ByteString1C(bytes) else clone
def compact: CompactByteString =
if (isCompact) ByteString1C(bytes) else ByteString1C(toArray)
def asByteBuffer: ByteBuffer = {
val buffer = ByteBuffer.wrap(bytes, startIndex, length).asReadOnlyBuffer
@ -138,26 +132,17 @@ object ByteString {
def decodeString(charset: String): String =
new String(if (length == bytes.length) bytes else toArray, charset)
def ++(that: ByteString): ByteString = that match {
case b: ByteString1C ByteStrings(this, b.toByteString1)
case b: ByteString1 ByteStrings(this, b)
case bs: ByteStrings ByteStrings(this, bs)
}
override def slice(from: Int, until: Int): ByteString = {
val newStartIndex = math.max(from, 0) + startIndex
val newLength = math.min(until, length) - from
if (newLength <= 0) ByteString.empty
else new ByteString1(bytes, newStartIndex, newLength)
}
override def copyToArray[A >: Byte](xs: Array[A], start: Int, len: Int): Unit =
Array.copy(bytes, startIndex, xs, start, math.min(math.min(length, len), xs.length - start))
def copyToBuffer(buffer: ByteBuffer): Int = {
val copyLength = math.min(buffer.remaining, length)
if (copyLength > 0) buffer.put(bytes, startIndex, copyLength)
copyLength
def ++(that: ByteString): ByteString = {
if (that.isEmpty) this
else if (this.isEmpty) that
else that match {
case b: ByteString1C ByteStrings(this, b.toByteString1)
case b: ByteString1
if ((bytes eq b.bytes) && (startIndex + length == b.startIndex))
new ByteString1(bytes, startIndex, length + b.length)
else ByteStrings(this, b)
case bs: ByteStrings ByteStrings(this, bs)
}
}
}
@ -195,7 +180,6 @@ object ByteString {
}
// 0: both empty, 1: 2nd empty, 2: 1st empty, 3: neither empty
// Using length to check emptiness is prohibited by law
def compare(b1: ByteString, b2: ByteString): Int =
if (b1.isEmpty)
if (b2.isEmpty) 0 else 2
@ -206,7 +190,8 @@ object ByteString {
/**
* A ByteString with 2 or more fragments.
*/
final class ByteStrings private (val bytestrings: Vector[ByteString1], val length: Int) extends ByteString {
final class ByteStrings private (private[akka] val bytestrings: Vector[ByteString1], val length: Int) extends ByteString {
if (bytestrings.isEmpty) throw new IllegalArgumentException("bytestrings must not be empty")
def apply(idx: Int): Byte =
if (0 <= idx && idx < length) {
@ -219,71 +204,37 @@ object ByteString {
bytestrings(pos)(idx - seen)
} else throw new IndexOutOfBoundsException(idx.toString)
override def slice(from: Int, until: Int): ByteString = {
val start = math.max(from, 0)
val end = math.min(until, length)
if (end <= start)
ByteString.empty
else {
val iter = bytestrings.iterator
var cur = iter.next
var pos = 0
var seen = 0
while (from >= seen + cur.length) {
seen += cur.length
pos += 1
cur = iter.next
}
val startpos = pos
val startidx = start - seen
while (until > seen + cur.length) {
seen += cur.length
pos += 1
cur = iter.next
}
val endpos = pos
val endidx = end - seen
if (startpos == endpos)
cur.slice(startidx, endidx)
else {
val first = bytestrings(startpos).drop(startidx).asInstanceOf[ByteString1]
val last = cur.take(endidx).asInstanceOf[ByteString1]
if ((endpos - startpos) == 1)
new ByteStrings(Vector(first, last), until - from)
else
new ByteStrings(first +: bytestrings.slice(startpos + 1, endpos) :+ last, until - from)
}
override def iterator: ByteIterator.MultiByteArrayIterator =
ByteIterator.MultiByteArrayIterator(bytestrings.toStream map { _.iterator })
def ++(that: ByteString): ByteString = {
if (that.isEmpty) this
else if (this.isEmpty) that
else that match {
case b: ByteString1C ByteStrings(this, b.toByteString1)
case b: ByteString1 ByteStrings(this, b)
case bs: ByteStrings ByteStrings(this, bs)
}
}
def ++(that: ByteString): ByteString = that match {
case b: ByteString1C ByteStrings(this, b.toByteString1)
case b: ByteString1 ByteStrings(this, b)
case bs: ByteStrings ByteStrings(this, bs)
}
def isCompact: Boolean = if (bytestrings.length == 1) bytestrings.head.isCompact else false
def compact: CompactByteString = {
val ar = new Array[Byte](length)
var pos = 0
bytestrings foreach { b
b.copyToArray(ar, pos, b.length)
pos += b.length
if (isCompact) bytestrings.head.compact
else {
val ar = new Array[Byte](length)
var pos = 0
bytestrings foreach { b
b.copyToArray(ar, pos, b.length)
pos += b.length
}
ByteString1C(ar)
}
ByteString1C(ar)
}
def asByteBuffer: ByteBuffer = compact.asByteBuffer
def decodeString(charset: String): String = compact.decodeString(charset)
def copyToBuffer(buffer: ByteBuffer): Int = {
val copyLength = math.min(buffer.remaining, length)
val iter = bytestrings.iterator
while (iter.hasNext && buffer.hasRemaining) {
iter.next.copyToBuffer(buffer)
}
copyLength
}
}
}
@ -297,7 +248,44 @@ object ByteString {
* TODO: Add performance characteristics
*/
sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimized[Byte, ByteString] {
override protected[this] def newBuilder = ByteString.newBuilder
def apply(idx: Int): Byte
override protected[this] def newBuilder: ByteStringBuilder = ByteString.newBuilder
// *must* be overridden by derived classes. This construction is necessary
// to specialize the return type, as the method is already implemented in
// a parent trait.
override def iterator: ByteIterator = throw new UnsupportedOperationException("Method iterator is not implemented in ByteString")
override def head: Byte = apply(0)
override def tail: ByteString = drop(1)
override def last: Byte = apply(length - 1)
override def init: ByteString = dropRight(1)
override def slice(from: Int, until: Int): ByteString =
if ((from == 0) && (until == length)) this
else iterator.slice(from, until).toByteString
override def take(n: Int): ByteString = slice(0, n)
override def takeRight(n: Int): ByteString = slice(length - n, length)
override def drop(n: Int): ByteString = slice(n, length)
override def dropRight(n: Int): ByteString = slice(0, length - n)
override def takeWhile(p: Byte Boolean): ByteString = iterator.takeWhile(p).toByteString
override def dropWhile(p: Byte Boolean): ByteString = iterator.dropWhile(p).toByteString
override def span(p: Byte Boolean): (ByteString, ByteString) =
{ val (a, b) = iterator.span(p); (a.toByteString, b.toByteString) }
override def splitAt(n: Int): (ByteString, ByteString) = (take(n), drop(n))
override def indexWhere(p: Byte Boolean): Int = iterator.indexWhere(p)
override def indexOf[B >: Byte](elem: B): Int = iterator.indexOf(elem)
override def toArray[B >: Byte](implicit arg0: ClassManifest[B]): Array[B] = iterator.toArray
override def copyToArray[B >: Byte](xs: Array[B], start: Int, len: Int): Unit =
iterator.copyToArray(xs, start, len)
override def foreach[@specialized U](f: Byte U): Unit = iterator foreach f
/**
* Efficiently concatenate another ByteString.
@ -311,14 +299,24 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
* @param buffer a ByteBuffer to copy bytes to
* @return the number of bytes actually copied
*/
def copyToBuffer(buffer: ByteBuffer): Int
def copyToBuffer(buffer: ByteBuffer): Int = iterator.copyToBuffer(buffer)
/**
* Create a new ByteString with all contents compacted into a single
* byte array.
* Create a new ByteString with all contents compacted into a single,
* full byte array.
* If isCompact returns true, compact is an O(1) operation, but
* might return a different object with an optimized implementation.
*/
def compact: CompactByteString
/**
* Check whether this ByteString is compact in memory.
* If the ByteString is compact, it might, however, not be represented
* by an object that takes full advantage of that fact. Use compact to
* get such an object.
*/
def isCompact: Boolean
/**
* Returns a read-only ByteBuffer that directly wraps this ByteString
* if it is not fragmented.
@ -329,7 +327,7 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
* Creates a new ByteBuffer with a copy of all bytes contained in this
* ByteString.
*/
final def toByteBuffer: ByteBuffer = ByteBuffer.wrap(toArray)
def toByteBuffer: ByteBuffer = ByteBuffer.wrap(toArray)
/**
* Decodes this ByteString as a UTF-8 encoded String.
@ -420,9 +418,15 @@ object CompactByteString {
}
/**
* A compact, unfragmented ByteString.
* A compact ByteString.
*
* The ByteString is guarantied to be contiguous in memory and to use only
* as much memory as required for its contents.
*/
sealed abstract class CompactByteString extends ByteString with Serializable
sealed abstract class CompactByteString extends ByteString with Serializable {
def isCompact: Boolean = true
def compact: this.type = this
}
/**
* A mutable builder for efficiently creating a [[akka.util.ByteString]].
@ -430,12 +434,37 @@ sealed abstract class CompactByteString extends ByteString with Serializable
* The created ByteString is not automatically compacted.
*/
final class ByteStringBuilder extends Builder[Byte, ByteString] {
builder
import ByteString.{ ByteString1C, ByteString1, ByteStrings }
private var _length = 0
private val _builder = new VectorBuilder[ByteString1]()
private var _length: Int = 0
private val _builder: VectorBuilder[ByteString1] = new VectorBuilder[ByteString1]()
private var _temp: Array[Byte] = _
private var _tempLength = 0
private var _tempCapacity = 0
private var _tempLength: Int = 0
private var _tempCapacity: Int = 0
protected def fillArray(len: Int)(fill: (Array[Byte], Int) Unit): this.type = {
ensureTempSize(_tempLength + len)
fill(_temp, _tempLength)
_tempLength += len
_length += len
this
}
protected def fillByteBuffer(len: Int, byteOrder: ByteOrder)(fill: ByteBuffer Unit): this.type = {
fillArray(len) {
case (array, start)
val buffer = ByteBuffer.wrap(array, start, len)
buffer.order(byteOrder)
fill(buffer)
}
}
def length: Int = _length
override def sizeHint(len: Int): Unit = {
resizeTemp(len - (_length - _tempLength))
}
private def clearTemp(): Unit = {
if (_tempLength > 0) {
@ -487,15 +516,169 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
clearTemp()
_builder += ByteString1(xs.array.clone)
_length += xs.length
case _: collection.IndexedSeq[_]
case seq: collection.IndexedSeq[_]
ensureTempSize(_tempLength + xs.size)
xs.copyToArray(_temp, _tempLength)
_tempLength += seq.length
_length += seq.length
case _
super.++=(xs)
}
this
}
/**
* Add a single Byte to this builder.
*/
def putByte(x: Byte): this.type = this += x
/**
* Add a single Short to this builder.
*/
def putShort(x: Int)(implicit byteOrder: ByteOrder): this.type = {
if (byteOrder == ByteOrder.BIG_ENDIAN) {
this += (x >>> 8).toByte
this += (x >>> 0).toByte
} else if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
this += (x >>> 0).toByte
this += (x >>> 8).toByte
} else throw new IllegalArgumentException("Unknown byte order " + byteOrder)
}
/**
* Add a single Int to this builder.
*/
def putInt(x: Int)(implicit byteOrder: ByteOrder): this.type = {
fillArray(4) {
case (target, offset)
if (byteOrder == ByteOrder.BIG_ENDIAN) {
target(offset + 0) = (x >>> 24).toByte
target(offset + 1) = (x >>> 16).toByte
target(offset + 2) = (x >>> 8).toByte
target(offset + 3) = (x >>> 0).toByte
} else if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
target(offset + 0) = (x >>> 0).toByte
target(offset + 1) = (x >>> 8).toByte
target(offset + 2) = (x >>> 16).toByte
target(offset + 3) = (x >>> 24).toByte
} else throw new IllegalArgumentException("Unknown byte order " + byteOrder)
}
this
}
/**
* Add a single Long to this builder.
*/
def putLong(x: Long)(implicit byteOrder: ByteOrder): this.type = {
fillArray(8) {
case (target, offset)
if (byteOrder == ByteOrder.BIG_ENDIAN) {
target(offset + 0) = (x >>> 56).toByte
target(offset + 1) = (x >>> 48).toByte
target(offset + 2) = (x >>> 40).toByte
target(offset + 3) = (x >>> 32).toByte
target(offset + 4) = (x >>> 24).toByte
target(offset + 5) = (x >>> 16).toByte
target(offset + 6) = (x >>> 8).toByte
target(offset + 7) = (x >>> 0).toByte
} else if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
target(offset + 0) = (x >>> 0).toByte
target(offset + 1) = (x >>> 8).toByte
target(offset + 2) = (x >>> 16).toByte
target(offset + 3) = (x >>> 24).toByte
target(offset + 4) = (x >>> 32).toByte
target(offset + 5) = (x >>> 40).toByte
target(offset + 6) = (x >>> 48).toByte
target(offset + 7) = (x >>> 56).toByte
} else throw new IllegalArgumentException("Unknown byte order " + byteOrder)
}
this
}
/**
* Add a single Float to this builder.
*/
def putFloat(x: Float)(implicit byteOrder: ByteOrder): this.type =
putInt(java.lang.Float.floatToRawIntBits(x))(byteOrder)
/**
* Add a single Double to this builder.
*/
def putDouble(x: Double)(implicit byteOrder: ByteOrder): this.type =
putLong(java.lang.Double.doubleToRawLongBits(x))(byteOrder)
/**
* Add a number of Bytes from an array to this builder.
*/
def putBytes(array: Array[Byte]): this.type =
putBytes(array, 0, array.length)
/**
* Add a number of Bytes from an array to this builder.
*/
def putBytes(array: Array[Byte], start: Int, len: Int): this.type =
fillArray(len) { case (target, targetOffset) Array.copy(array, start, target, targetOffset, len) }
/**
* Add a number of Shorts from an array to this builder.
*/
def putShorts(array: Array[Short])(implicit byteOrder: ByteOrder): this.type =
putShorts(array, 0, array.length)(byteOrder)
/**
* Add a number of Shorts from an array to this builder.
*/
def putShorts(array: Array[Short], start: Int, len: Int)(implicit byteOrder: ByteOrder): this.type =
fillByteBuffer(len * 2, byteOrder) { _.asShortBuffer.put(array, start, len) }
/**
* Add a number of Ints from an array to this builder.
*/
def putInts(array: Array[Int])(implicit byteOrder: ByteOrder): this.type =
putInts(array, 0, array.length)(byteOrder)
/**
* Add a number of Ints from an array to this builder.
*/
def putInts(array: Array[Int], start: Int, len: Int)(implicit byteOrder: ByteOrder): this.type =
fillByteBuffer(len * 4, byteOrder) { _.asIntBuffer.put(array, start, len) }
/**
* Add a number of Longs from an array to this builder.
*/
def putLongs(array: Array[Long])(implicit byteOrder: ByteOrder): this.type =
putLongs(array, 0, array.length)(byteOrder)
/**
* Add a number of Longs from an array to this builder.
*/
def putLongs(array: Array[Long], start: Int, len: Int)(implicit byteOrder: ByteOrder): this.type =
fillByteBuffer(len * 8, byteOrder) { _.asLongBuffer.put(array, start, len) }
/**
* Add a number of Floats from an array to this builder.
*/
def putFloats(array: Array[Float])(implicit byteOrder: ByteOrder): this.type =
putFloats(array, 0, array.length)(byteOrder)
/**
* Add a number of Floats from an array to this builder.
*/
def putFloats(array: Array[Float], start: Int, len: Int)(implicit byteOrder: ByteOrder): this.type =
fillByteBuffer(len * 4, byteOrder) { _.asFloatBuffer.put(array, start, len) }
/**
* Add a number of Doubles from an array to this builder.
*/
def putDoubles(array: Array[Double])(implicit byteOrder: ByteOrder): this.type =
putDoubles(array, 0, array.length)(byteOrder)
/**
* Add a number of Doubles from an array to this builder.
*/
def putDoubles(array: Array[Double], start: Int, len: Int)(implicit byteOrder: ByteOrder): this.type =
fillByteBuffer(len * 8, byteOrder) { _.asDoubleBuffer.put(array, start, len) }
def clear(): Unit = {
_builder.clear
_length = 0
@ -513,4 +696,13 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
ByteStrings(bytestrings, _length)
}
/**
* Directly wraps this ByteStringBuilder in an OutputStream. Write
* operations on the stream are forwarded to the builder.
*/
def asOutputStream: java.io.OutputStream = new java.io.OutputStream {
def write(b: Int): Unit = builder += b.toByte
override def write(b: Array[Byte], off: Int, len: Int): Unit = { builder.putBytes(b, off, len) }
}
}

View file

@ -0,0 +1,84 @@
/**
* Copyright (C) 2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.docs.io
//#imports
import akka.actor._
import akka.util.{ ByteString, ByteStringBuilder, ByteIterator }
//#imports
abstract class BinaryDecoding {
//#decoding
implicit val byteOrder = java.nio.ByteOrder.BIG_ENDIAN
val FrameDecoder = for {
frameLenBytes IO.take(4)
frameLen = frameLenBytes.iterator.getInt
frame IO.take(frameLen)
} yield {
val in = frame.iterator
val n = in.getInt
val m = in.getInt
val a = Array.newBuilder[Short]
val b = Array.newBuilder[Long]
for (i 1 to n) {
a += in.getShort
b += in.getInt
}
val data = Array.ofDim[Double](m)
in.getDoubles(data)
(a.result, b.result, data)
}
//#decoding
}
abstract class RestToSeq {
implicit val byteOrder = java.nio.ByteOrder.BIG_ENDIAN
val bytes: ByteString
val in = bytes.iterator
//#rest-to-seq
val n = in.getInt
val m = in.getInt
// ... in.get...
val rest: ByteString = in.toSeq
//#rest-to-seq
}
abstract class BinaryEncoding {
//#encoding
implicit val byteOrder = java.nio.ByteOrder.BIG_ENDIAN
val a: Array[Short]
val b: Array[Long]
val data: Array[Double]
val frameBuilder = ByteString.newBuilder
val n = a.length
val m = data.length
frameBuilder.putInt(n)
frameBuilder.putInt(m)
for (i 0 to n - 1) {
frameBuilder.putShort(a(i))
frameBuilder.putLong(b(i))
}
frameBuilder.putDoubles(data)
val frame = frameBuilder.result()
//#encoding
//#sending
val socket: IO.SocketHandle
socket.write(ByteString.newBuilder.putInt(frame.length).result)
socket.write(frame)
//#sending
}

View file

@ -17,10 +17,62 @@ ByteString
A primary goal of Akka's IO module is to only communicate between actors with immutable objects. When dealing with network IO on the jvm ``Array[Byte]`` and ``ByteBuffer`` are commonly used to represent collections of ``Byte``\s, but they are mutable. Scala's collection library also lacks a suitably efficient immutable collection for ``Byte``\s. Being able to safely and efficiently move ``Byte``\s around is very important for this IO module, so ``ByteString`` was developed.
``ByteString`` is a `Rope-like <http://en.wikipedia.org/wiki/Rope_(computer_science)>`_ data structure that is immutable and efficient. When 2 ``ByteString``\s are concatenated together they are both stored within the resulting ``ByteString`` instead of copying both to a new ``Array``. Operations such as ``drop`` and ``take`` return ``ByteString``\s that still reference the original ``Array``, but just change the offset and length that is visible. Great care has also been taken to make sure that the internal ``Array`` cannot be modified. Whenever a potentially unsafe ``Array`` is used to create a new ``ByteString`` a defensive copy is created.
``ByteString`` is a `Rope-like <http://en.wikipedia.org/wiki/Rope_(computer_science)>`_ data structure that is immutable and efficient. When 2 ``ByteString``\s are concatenated together they are both stored within the resulting ``ByteString`` instead of copying both to a new ``Array``. Operations such as ``drop`` and ``take`` return ``ByteString``\s that still reference the original ``Array``, but just change the offset and length that is visible. Great care has also been taken to make sure that the internal ``Array`` cannot be modified. Whenever a potentially unsafe ``Array`` is used to create a new ``ByteString`` a defensive copy is created. If you require a ``ByteString`` that only blocks a much memory as necessary for it's content, use the ``compact`` method to get a ``CompactByteString`` instance. If the ``ByteString`` represented only a slice of the original array, this will result in copying all bytes in that slice.
``ByteString`` inherits all methods from ``IndexedSeq``, and it also has some new ones. For more information, look up the ``akka.util.ByteString`` class and it's companion object in the ScalaDoc.
``ByteString`` also comes with it's own optimized builder and iterator classes ``ByteStringBuilder`` and ``ByteIterator`` which provides special features in addition to the standard builder / iterator methods:
Compatibility with java.io
..........................
A ``ByteStringBuilder`` can be wrapped in a `java.io.OutputStream` via the ``asOutputStream`` method. Likewise, ``ByteIterator`` can we wrapped in a ``java.io.InputStream`` via ``asInputStream``. Using these, ``akka.io`` applications can integrate legacy code based on ``java.io`` streams.
Encoding and decoding of binary data
....................................
``ByteStringBuilder`` and ``ByteIterator`` support encoding and decoding of binary data. As an example, consider a stream of binary data frames with the following format:
.. code-block:: text
frameLen: Int
n: Int
m: Int
n times {
a: Short
b: Long
}
data: m times Double
In this example, the data is to be stored in arrays of ``a``, ``b`` and ``data``.
Decoding of such frames can be efficiently implemented in the following fashion:
.. includecode:: code/akka/docs/io/BinaryCoding.scala
:include: decoding
This implementation naturally follows the example data format. In a true Scala application, one might, of course, want use specialized immutable Short/Long/Double containers instead of mutable Arrays.
After extracting data from a ``ByteIterator``, the remaining content can also be turned back into a ``ByteString`` using the ``toSeq`` method
.. includecode:: code/akka/docs/io/BinaryCoding.scala
:include: rest-to-seq
with no copying from bytes to rest involved. In general, conversions from ByteString to ByteIterator and vice versa are O(1) for non-chunked ``ByteString``s and (at worst) O(nChunks) for chunked ByteStrings.
Encoding of data also is very natural, using ``ByteStringBuilder``
.. includecode:: code/akka/docs/io/BinaryCoding.scala
:include: encoding
The encoded data then can be sent over socket (see ``IOManager``):
.. includecode:: code/akka/docs/io/BinaryCoding.scala
:include: sending
IO.Handle
^^^^^^^^^