Resources

Examples of using the Resource API to wrap existing Java IO objects

The Resource API can be used to adapt Java IO objects such as InputStreams and Channels. The Resource object provides several methods for wrapping common Java objects. In the .Net implementation the Resource API would wrap .Net IO objects.

Create Resources

Several examples of creating Resources
    import scalax.io._
    import java.io._
    import java.nio.channels._
    import java.net.URL

    // see codec examples in scala io core for details on why there is an implicit codec here
    implicit val codec = scalax.io.Codec.UTF8

    // get various input streams, readers an channels
    val inputStream: InputStream = new URL("http://someurl.com").openStream
    val in: InputStreamResource[InputStream] = Resource.fromInputStream(inputStream)
    val bufferedIn: InputStreamResource[BufferedInputStream] = in.buffered
    val readableChannel: Resource[ReadableByteChannel] = in.readableByteChannel
    val reader: ReaderResource[Reader] = in.reader
    val bufferedReader: ReaderResource[BufferedReader] = reader.buffered

    // get various output streams and channels
    val outputStream: FileOutputStream = new FileOutputStream("file")
    val out: OutputStreamResource[OutputStream] = Resource.fromOutputStream(outputStream)

    val bufferedOut: OutputStreamResource[BufferedOutputStream] = out.buffered
    val writableChannel: Resource[WritableByteChannel] = out.writableByteChannel
    val writer: WriterResource[Writer] = out.writer
    val bufferedWriter: WriterResource[BufferedWriter] = writer.buffered

    // examples getting ByteChannels
    // default is a read/write/create channel
    val channel: SeekableByteChannelResource[SeekableByteChannel] = Resource.fromFileString("file")
    val channel2: SeekableByteChannelResource[SeekableByteChannel] =
        Resource.fromRandomAccessFile(new RandomAccessFile("file","rw"))
    val seekable: Seekable = channel2
    val inOut: Input with Output = channel

    val channel3: ByteChannelResource[FileChannel] =
      Resource.fromByteChannel(new RandomAccessFile("file","rw").getChannel)
    val inOut2: Input with Output = channel2

    val readableByteChannel = Channels.newChannel(new FileInputStream("file"))
    val readChannel : ReadableByteChannelResource[ReadableByteChannel] =
              Resource.fromReadableByteChannel(readableByteChannel)
    val in2:Input = readChannel

    val writableByteChannel = Channels.newChannel(new FileOutputStream("file"))
    val writeChannel : WritableByteChannelResource[WritableByteChannel] =
              Resource.fromWritableByteChannel(writableByteChannel)
    val out2:Output = writeChannel
  

Using Io Resources

The typical IO objects are java IO objects converted into Resource objects using the Resource object's factory methods. Once a Resource has been created there are methods for converting between them.

The following examples demonstrate using the Resource objects and converting between them.

All resources are also Seekable, Input, Output, ReadChars and/or WriteChars so all normal IO operations are possible but the following examples are Resource only operations

    import scalax.io._
    import java.io._
    import java.nio.channels._

    val resource = Resource.fromInputStream(new FileInputStream("file"))

    // The Resource objects have methods for converting between the common types
    val bufferedInput: InputStreamResource[BufferedInputStream] = resource.buffered
    val readChars: ReaderResource[Reader] = resource.reader(Codec.UTF8)
    val readableByteChannel: ReadableByteChannelResource[ReadableByteChannel] =
              resource.readableByteChannel
    val bufferedReader = readChars.buffered

    // there are also several ways to obtain the underlying java object
    // for certain operations.  Typically this is to micro manage how the
    // data is read from the input
    val availableBytes: Int = bufferedInput.acquireAndGet{
      bufferedInputStream => bufferedInputStream.available
    }

    // If you want to perform an operation and have the option to easily
    // get the exception acquireFor is a good solution
    val firstLine: Either[scala.List[scala.Throwable], String] = bufferedReader.acquireFor{
      reader => reader.readLine
    }
  

Perform Additional Action On Close

Perform additional actions when a resource is closed. One of the important features of the Scala IO is that resources are cleaned up automatically. However occasionally one would like to perform an action on close in addition to the default closing/flushing of the resource. When a resource is created additional close actions can be added and they will be executed just before the resource is closed.
    import scalax.io._
    import nio.SeekableFileChannel

    // a close action can be created by passing a function to execute
    // to the Closer object's apply method
    // '''WARNING''' When defining a CloseAction make its type as generic
    // as possible.  IE if it can be a CloseAction[Closeable] do not
    // make it a CloseAction[InputStream].  The reason has to do
    // with contravariance.  If you don't know what that means
    // don't worry just trust me ;-)
    val closer = CloseAction{(channel:Any) =>
      println("About to close "+channel)
    }

    // another option is the extend/implement the CloseAction trait
    val closer2 = new CloseAction[Any]{

      protected def closeImpl(a: Any):Unit =
        println("Message from second closer")
    }

    // closers can naturally be combined
    val closerThenCloser2 = closer +: closer2
    val closer2ThenCloser = closer :+ closer2

    // we can then create a resource and pass it to the closer parameter
    // now each time resource is used (and closed) the closer will also be executed
    // just before the actual closing.
    val resource = Resource.fromFileString("file")(closer)

    // closeActions can also be added to an existing resource
    // NOTE: Appended actions still are performed BEFORE
    // resource is closed
    resource.appendCloseAction(closerThenCloser2)
    resource.prependCloseAction(closer2)

    // The following are equivalent
    Resource.fromFileString("file")(closer :+ closer2)
    Resource.fromFileString("file")(closer).appendCloseAction(closer2)
    Resource.fromFileString("file").appendCloseAction (closer :+ closer2)

  

Why Are Close Actions Contravariant

This example examines why a CloseAction[Any] can be assigned to a CloseAction[String] but not vice-versa.

Normally one think in terms of ''Covariance'' (List[String] can be assigned to a List[Any]) but that cannot work for CloseActions so CloseActions have the exact opposite characteristics.

    import scalax.io._
    import java.io._

    // Since CloseAction is Defined as CloseAction[-A], the following compiles
    val action:CloseAction[String] = CloseAction[Any]{_ => ()}

    //But
    // val action:CloseAction[Any] = CloseAction[String]{_ => ()}
    // does not.

    // If you want to know why consider the following:
    val resource:Resource[InputStream] = Resource.fromInputStream(new FileInputStream("file"))
    val resource2:Resource[Closeable] = resource

    val closeAction:CloseAction[InputStream] = CloseAction{in:InputStream => println(in.available)}

    //Given the previous declarations it should be obvious that the following works
    val updatedResource:Resource[InputStream] = resource.appendCloseAction(closeAction)

    // However since resource2 is a Resource[Closeable] it should be obvious that one cannot
    // add a closeAction that requires an InputStream.  so the following would fail to compile
    // resource2.appendCloseAction(closeAction)