diff --git a/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala b/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala index 94997a49de..981d407472 100644 --- a/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala @@ -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) } } + } } } diff --git a/akka-actor/src/main/scala/akka/util/ByteIterator.scala b/akka-actor/src/main/scala/akka/util/ByteIterator.scala new file mode 100644 index 0000000000..dc5e4a3d5c --- /dev/null +++ b/akka-actor/src/main/scala/akka/util/ByteIterator.scala @@ -0,0 +1,610 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +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 +} diff --git a/akka-actor/src/main/scala/akka/util/ByteString.scala b/akka-actor/src/main/scala/akka/util/ByteString.scala index ac074d5b28..c2a690aa26 100644 --- a/akka-actor/src/main/scala/akka/util/ByteString.scala +++ b/akka-actor/src/main/scala/akka/util/ByteString.scala @@ -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) } + } } diff --git a/akka-docs/scala/code/akka/docs/io/BinaryCoding.scala b/akka-docs/scala/code/akka/docs/io/BinaryCoding.scala new file mode 100644 index 0000000000..d9aeb334fc --- /dev/null +++ b/akka-docs/scala/code/akka/docs/io/BinaryCoding.scala @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2012 Typesafe Inc. + */ +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 +} diff --git a/akka-docs/scala/io.rst b/akka-docs/scala/io.rst index 2c4e1608f3..9063e010f5 100644 --- a/akka-docs/scala/io.rst +++ b/akka-docs/scala/io.rst @@ -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 `_ 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 `_ 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 ^^^^^^^^^