Writing With Typeclasses
Details on how output is converted to bytes and how the design can be extended and used
The Common Cases
import scalax.io._
import Resource._
val out = fromFileString("out")
// Selected Converter is OutputConverter.IntConverter
out.write(3)
// Selected Converter is OutputConverter.LongConverter
out.write(3L)
// Selected Converter is OutputConverter.ByteConverter
out.write(3.toByte)
// Selected Converter is OutputConverter.DoubleConverter
out.write(3.0)
// Selected Converter is OutputConverter.IntConverter
out.write(List[Byte](3))
// Selected Converter is OutputConverter.LongConverter
out.write(3L)
// Selected Converter is OutputConverter.ByteConverter
out.write(3.toByte)
// Selected Converter is OutputConverter.DoubleConverter
out.write(3.0)
// Selected Converter is OutputConverter.TraversableIntConverter
out.write(List(1,2,3,4))
Ints As Bytes
Scala IO differs in that an integer is written as 4 bytes and one must explicitely coerce an Int to a byte. The following examples demostrate how one might do that.
import scalax.io._
import Resource._
val out = fromFileString("out")
// One of the easiest ways is to coerce the
// ints into bytes before passing them to a
// write method
out.write(List[Byte](1,2,3,4))
out.write(1.toByte)
// writeIntsAsBytes (or patchIntsAsBytes) is
// another good solution
out.writeIntsAsBytes(1,2,3,4)
out.insertIntsAsBytes(4,1,2,3)
out.patchIntsAsBytes(3,OverwriteAll,1,2,3)
out.appendIntsAsBytes(1,2,3)
// The final option is to pass in the
//(OutputConverter.IntAsByteConverter) object to the write method:
out.write(3)(OutputConverter.IntAsByteConverter)
out.write(List(1,2,3,4))(OutputConverter.TraversableIntAsByteConverter)
Writing Arrays
The point of this example is explain that if one is creating a custom converter he will have to consider creating both a OutputConverter[Traversable[_]] as well as a OutputConverter[Array[_]]
import scalax.io._
import Resource._
val out = fromFileString("out")
out.write(Array(1,2,3,4))(OutputConverter.IntAsByteArrayConverter)
out.write(Array(1,2,3,4))
Strings And Characters
The result is that writing strings is a simple exercise but writing characters or Traversables of characters is less trivial. The examples below show how to write strings and characters.
import scalax.io._
import Resource._
val out = fromFileString("out")
// codec can be passed implicitely or explicitly
out.write("A string")(Codec.UTF8)
implicit val codec = Codec.UTF8
out.write("c")
// out.write('c') will not compile since a converter cannot
// be resolved by the implicit resolution mechanism because
// character converters require a codec and only concrete
// objects are resolved.
out.write('c')(OutputConverter.charToOutputFunction)
out.write(Set('a','e','i','o','u'))(OutputConverter.charsToOutputFunction)
// converters can be passed implicitly
implicit val traversableCharCoverter = OutputConverter.charsToOutputFunction
out.write(Set('a','e','i','o','u') )
out.write('a' to 'z')
Custom Data Types
Naturally being able to write objects other than those defined by Scala IO can be beneficial and it is a simple process. All that is needed is a new implementation of a OutputConverter which is imported into scopoe.
The examples below show two design patterns.
import scalax.io._
import Resource._
import OutputConverter._
val out = fromFileString("out")
// Simplest design pattern is to create a new implicit object in scope
implicit object DateConverter extends OutputConverter[Date] {
def sizeInBytes = 8
def toBytes(data: Date) = LongConverter.toBytes(data.getTime)
}
out.write(new Date(2012,01,01))
// write, append, patch and insert all follow the same pattern
out.append(3)
// The second (an more reusable design pattern) is to create an object
// that contains the converters that you want to use and then they can be
// reused through out the code base.
object CustomConverters {
case class User(name:String,id:Int)
// first you need converter for a collection of your type
implicit object UserTraversableConverter extends OutputConverter[TraversableOnce[User]] {
def sizeInBytes = 2
def toBytes(users: TraversableOnce[User]):TraversableOnce[Byte] = {
// Create a single instance of a buffer for encoding the id value.
val idBuffer = new OutputConverter.Buffer[Int](4,(byteBuffer,data) => {byteBuffer.putInt(data)})
users.toIterator.flatMap{
user =>
user.name.getBytes("ASCII").toIterator ++ idBuffer.put(user.id)
}
}
}
// next you need converters for the basic type and arrays
implicit object UserConverter extends NonTraversableAdapter(UserTraversableConverter)
implicit object UserArrayConverter extends ArrayAdapter(UserTraversableConverter)
}
// finally you can import the definitions into scope and write away
import CustomConverters._
out.write(User("Jesse Eichar",888888))
out.insert(2,User("Jesse",23421))