Wednesday, September 26, 2012

Scala-IO Core: To Resource Converters

In order to simplify integration with existing libraries, most commonly Java libraries, Scala-IO provides a JavaConverters object with implicit methods that add as*** methods (asInput, asOutput, asSeekable, etc...) to several types of objects.  It is the same pattern as in the scala.collection.JavaConverters object.

These methods can be used instead of the Resource.from*** methods to provide a slightly nicer appearing code.

There is one warning. When using JavaConverters, instead of Resource.from*** for creating Input/Output/Seekable/etc... objects, the chances of falling into the trap of creating non-reusable resources or causing a resource leak is increased. See: scala-io-core-reusable-resources for more details on this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// JavaConverters object contains the implicit methods
// which add the as*** methods to the applicable _normal_ objects
import scalax.io.JavaConverters._
 
// Several objects can be converted to input objects using
// asInput.  URLs, File, RandomAccessFile, InputStream, Traversable[Byte]
// Array[Byte], ReadableByteChannel
val input = new URL("http://www.camptocamp.com").asInput
 
// simple demonstation using the newly created input object
println(input.bytes.size)
 
// asSeekable can only be applied to a few objects at the
// moment.  Including RandomAccessFile, SeekableByteChannel,
// File and perhaps in future mutable Sequences
val file = new java.io.File("somefile.txt").asSeekable
 
// demonstrate a seekable method.  This method
// ensures the file is empty
file.truncate(0)
 
// write hi using the Output  API
file.write("hi :)")
 
// output the file to the console to see the results
println(file.string)
 
// asUnmanaged*** created Unmanaged resources for
// operations.  The Unmanaged Resource post will
// discuss this in more detail but essentially the
// resource is not closed and thus is useful when
// dealing with underlying objects that should not
// be closed like System.out
val unmanagedOutput = System.out.asUnmanagedOutput
 
// prints to standard out: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
unmanagedOutput.writeStrings(1 to 10 map (_.toString), ", ")
 
// prints: Hello World
// This demonstrates how a simple Array or Traversable can be easily used
// as an Input object.  The array is Hello world encoded as normal latin1
println(Array(72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100).asInput.string)
 
// prints: Hll Wrld
// Raw Strings and Reader objects cannot currently be converted to an Input object
// but can be converted to ReadChars object (Essentially the character API of Input)
println("Hello World".asReadChars.chars.filterNot(c => "aeiou" contains c) mkString)
 
// Similarly Writer objects cannot be Output objects since they can't write
// bytes so asWriteChars is used to be able to have the string writing
// capabilities of Scala-IO
new java.io.FileWriter("somefile.txt").asWriteChars write "Hi"
 
// prints: Hi
println(new java.io.FileReader("somefile.txt").asReadChars.string)

Wednesday, September 19, 2012

Scala-IO Core: Reusable Resources


One aspect of resources in Scala-IO that can cause problems is the construction of resource objects.  The factory methods that are provided in the Resource object each have a lazy parameter for opening the underlying resource.  However a common error developers can make is to pass in the already open resource to the method which has multiple problems.

Consider:
1
2
3
4
5
6
7
8
9
import scalax.io._
   
val stream = new java.io.FileOutputStream("somefile.txt")
val output = Resource.fromOutputStream(stream) 
output write "hey "
 
// boom, stream was closed last write
// and the stream cannot be reopened.
output write "how's it going?"
In the example above the stream is created and opened at the definition of stream (it is a val).  This has two effects:

  1. the stream is open and if the resource object is not closed you will have a resource leak
  2. since the stream is opened the resource can only be used once since it will be closed after each use.
The correct way to create the resource would be to change val to def so that the stream is only created on demand and therefore there will be no chance of a resource leak.  The following is the correct example:
1
2
3
4
5
6
7
8
9
10
11
12
import scalax.io._
 
def stream = new java.io.FileOutputStream("somefile.txt")
val output = Resource.fromOutputStream(stream)
 
output write "hey "
// the second write will now work.  However since
// the underlying resource is a FileOutputStream
// the file will contain just "how's it going"
output write "how's it going?"
 
println(Resource.fromFile("somefile.txt").string)

This anti-pattern is also a risk when using the converter methods in the JavaConverters object. (A future post will look into this in more detail.) The following example shows the anti-pattern in effect:
1
2
3
4
5
6
7
8
9
import scalax.io._
import JavaConverters._
 
val output = new java.io.FileOutputStream("somefile.txt").asOutput
 
output write "hey "
// Next line will cause exception because
// stream is closed.
output write "how's it going?"
The asOutput method can only be applied to an object (at time of this writing) and therefore the resulting object has all of the negative characteristics mentioned above. Therefore it is recommended that asOutput/asInput/etc... only be used on 1 time use resources (like InputStream) within a scope and not passed out to an external method so that it is easy to view the entirety of the operation.

Thursday, September 13, 2012

Scala-IO Core: ReadChars and WriteChars

The Input and Output objects of Scala-IO assume that the underlying data is composed of bytes.  However, another common pattern is to have the underlying data be composed of characters instead of bytes, for example java.io.Reader and java.io.Writer.  While it is possible to decompose the output into Bytes and construct an Input object from the decorated object, ReadChars and WriteChars can be used in this situation to reduce the work needed to interact with such resources.

ReadChars and WriteChars are traits that contain the character and string methods of Input and Output.  The primary difference is that the Charset is defined by the underlying resource rather than supplied at the method invocation site.  

Compare two methods:

Input:
1
def chars(implicit codec: Codec = Codec.default): LongTraversable[Char]
ReadChars:
1
def chars: LongTraversable[Char]
You will notice that the ReadChars method does not have the codec parameter because there translation is not required, unlike in Input which requires the characters to be created from raw bytes.

Not many examples are needed to explain these concepts but here are a few examples on how to create ReadChar and WriteChar objects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import scalax.io._
import JavaConverters._
 
// JavaConverters has asReadChars and asWriteChars
// for converting some objects to ReadChars and WriteChars
// (The JavaConverters post will explain
// more about the JavaConverters object)
"Hello World".asReadChars.chars.size
 
val writer = new java.io.StringWriter()
// Resource object can be used to create a ReadChars and WriteChars
val writeChars = Resource.fromWriter(writer).write("Yeee Hawww!")
println(Resource.fromReader(new java.io.StringReader(writer.toString)).lines().size)
 
// ReadChars and WriteChars can be obtained from
// InputResource and OutputResource object respectively
// SeekableByteChannelResource[SeekableByteChannel]
// (returned by fromFile) implements both traits and
// therefore has both methods
val fileResource = Resource.fromFile("somefile.txt")
 
implicit val codec = Codec.UTF8
// clear any old data in file
fileResource.truncate(0)
 
// Codec used is declared when calling writer or reader
// methods
fileResource.writer.write("hi")
 
println(fileResource.reader.chars.size)