All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalax.io.Resource.scala Maven / Gradle / Ivy

/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2009-2010, Jesse Eichar          **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

package scalax.io

import _root_.resource.{ManagedResourceOperations}
import scala.util.control.Exception.allCatch
import java.nio.channels.{
  ByteChannel, ReadableByteChannel, WritableByteChannel,
  Channels
}
import java.io._
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import nio.SeekableFileChannel
import java.net.{URLConnection, URL}
import CloseAction._
import StandardOpenOption._
import managed._
import collection.immutable.List._
import util.control.Exception
import extractor._
import support.FileUtils

trait OpenedResource[+R] {
  def get:R
  def context:ResourceContext
  def close():List[Throwable] = closeAction(get)
  def closeAction[U >: R]:CloseAction[U]
  def toSingleUseResource = new ManagedResourceOperations[R] {
    def acquireFor[B](f: (R) => B): Either[List[Throwable], B] = {
      val result = f(get)
      close() match {
        case Nil => Right(result)
        case errors => Left(errors)
      }
    }
  }
}
class CloseableOpenedResource[+R <: Closeable](val get:R,val context:ResourceContext, closeResourceAction:CloseAction[R]) extends OpenedResource[R] {
  def closeAction[U >: R] = (closeResourceAction :+ CloseAction ((_:R).close())).asInstanceOf[CloseAction[U]]
}
class UnmanagedOpenedResource[+R](val get:R,val context:ResourceContext) extends OpenedResource[R]{
  def closeAction[U >: R]:CloseAction[U] = CloseAction.Noop
}

/**
 * A trait allowing adding close actions to a Resource.  The purpose of this trait
 * was to allow the correct subclass type to be returned when calling the methods but
 * keeping the Repr type from the main Resource signature
 *
 *
 * @note in standard immutable fashion the methods do not mutate the current object
 * but return a new instance with the modified behaviour
 *
 * @tparam R The type of object that is managed by this resource
 * @tparam Repr The actual type of the concrete subclass
 */
trait ResourceOps[+R, +Repr] {
  /**
   * Get the Resource context associated with this Resource instance.
   *
   * @note as Resources are immutable objects a given Resource instance will always be associated with
   * the same ResourceContext
   *
   * @return the associated ResourceContext
   */
  def context:ResourceContext
  /**
   * Create a Resource instance that is configured with the new ResourceContext
   *
   * @param newContext A new ResourceContext
   *
   * @return a new instance configured with the new context
   */
  def updateContext(newContext:ResourceContext):Repr
  /**
   * Update the current ResourceContext and return a new Resource instance with the updated context
   *
   * @param f A function for transforming the current context to a new context with new values.
   *
   * @return a new instance configured with the new context
   */
  def updateContext(f:ResourceContext => ResourceContext):Repr = updateContext(f(context))
  /**
   * Add a CloseAction that will be executed each time the resource is closed.
   *
   * @param newCloseAction the action to add
   *
   * @return a new resource instance with the close action added
   */
  def addCloseAction(newCloseAction: CloseAction[R]):Repr
}
/**
 * A Resource that can be used to do IO.  Primarily it wraps objects from the java io and makes
 * them more Scala friendly.
 *
 * A common question about Resource is: "Why no simply expose [[scalax.io.Input]],
 * [[scalax.io.Output]], [[scalax.io.Seekable]], [[scalax.io.ReadChars]], [[scalax.io.WriteChars]]?
 * Why do we need a Resource[R] object?"
 *
 * There are several reasons for this.  There are several situations where a developer
 * needs access to the underlying resource.
 *
 * Perhaps they need to interact with a Java API which does not use the [[scalax.io.Input]], etc...
 * APIs.  Another possibility is that the resource may be a specific implementation like PrintStream
 * and they want to use those custom APIs instead of the Scala IO apis.  In that case Resource provides
 * them with the ARM functionality that is very useful.
 *
 * @tparam R
 *          The type of the resource that will be managed by the ManagedResource
 *
 * @author  Jesse Eichar
 * @since   1.0
 */
trait Resource[+R] extends ManagedResourceOperations[R] with ResourceOps[R, Resource[R]] {
  self =>
  /**
   * Creates a new instance of the underlying resource (or opens it).
   * Sometimes the code block used to create the Resource is non-reusable in
   * which case this Resource can only be used once.  This is not recommended.
   *
   * When creating a resource it is recommended to pass the code block for creating
   * the resource to the resource so that the resource can be reused.  Of course this
   * is not always possible
   *
   * This method should only be used with care in cases when Automatic
   * Resource Management cannot be used because the
   * {@link InputStream} must be closed manually.
   *
   * This is public only to permit interoperability with certain Java APIs.
   * A better pattern of use should be:
   * {{{
   * resource.acquireFor {
   *   // call java API
   * }
   * }}}
   * or
   * {{{
   * val calculatedResult = resource.acquireAndGet {
   *   // cal java API that returns a result
   * }
   * }}}
   *
   * @note normally the error handler registered with the associated ResourceContext
   *        will handle any errors opening the resource, but when calling this method
   *        the caller must handle any possible errors that are raised.
   * @return the actual resource that has been opened
   */
    def open(): OpenedResource[R]

    /**
     * Open the resource execute the function and either return all errors as a list or the result of the
     * function execution.
     *
     * On open and close error handlers in ResourceContext are called.  If they then raise errors
     * the errors are captured and returned as a Right[List[Throwable]]
     *
     * Perhaps the worst method I have ever written :-(
     */
  final def acquireFor[B](f: R => B): Either[List[Throwable], B] = {

      // perhaps the worst written method I have ever done :-(
    val resourceEither = allCatch.either {
      open()
    }
    var closeExceptions: List[Throwable] = Nil

    /** Close resource and assign any exceptions to closeException */
    def close(resource:OpenedResource[R]) = try {
      closeExceptions = resource.close()
    } catch {
      case t => closeExceptions = List(t)
    }

    /** Handle error that occurs during resource access */
    def handleAccessError: PartialFunction[Throwable, Either[Throwable, B]] = {
      case c: scala.util.control.ControlThrowable => throw c
      case t => Left(t)
    }

    resourceEither match {
      case Left(t) =>
        val handlerValue = context.openErrorHandler(f, t).map(Right(_))
        handlerValue getOrElse Left(List(t))
      case Right(resource) =>
        val result =
          try Right(f(resource.get))
          catch handleAccessError
          finally close(resource)

        val handleError = result.left.toOption ++ closeExceptions nonEmpty

        if (handleError) {
          try {
            Right(context.errorHandler(f, result, closeExceptions))
          } catch {
            case t => Left(List(t))
          }
        } else {
          Right(result.right.get)
        }

    }
  }
}

/**
 * An Resource object that is a also an [[scalax.io.Input]].  This trait adds methods
 * for converting between common io types such as to a [[scalax.io.managed.ReaderResource]] or
 * [[scalax.io.managed.ReadableByteChannelResource]]
 *
 * @tparam R
 *          the type of underlying resource that is created
 *
 * @author  Jesse Eichar
 * @since   1.0
 */
trait InputResource[+R] extends Resource[R] with Input with ResourceOps[R, InputResource[R]] {

    /**
     * Obtain the [[scalax.io.managed.InputStreamResource]](typically) version of this object.
     *
     * @return the [[scalax.io.managed.InputStreamResource]](typically) version of this object.
     */
  def inputStream: InputResource[InputStream]

  override def copyDataTo(output: Output): Unit =
    output  match {
      case outR: OutputResource[_] =>
        var failedToCopy = false
        for {
          inChan <- this
          outChan <- outR
        } {
          FileUtils.tryCopy(failedToCopy=true)(inChan, outChan)
        }
        if(failedToCopy)
          super.copyDataTo(output)
      case _ => super.copyDataTo(output)
    }

    /**
     * Obtain the [[scalax.io.ReadCharsResource]] version of this object.
     *
     * @param sourceCodec the codec used to convert from bytes to characters
     *
     * @return the [[scalax.io.ReadCharsResource]] version of this object.
     */
    def reader(implicit sourceCodec: Codec = Codec.default) : ReadCharsResource[Reader]
    /**
     * Obtain the [[scalax.io.managed.ReadableByteChannelResource]](typically) version of this object.
     *
     * @return the [[scalax.io.managed.ReadableByteChannelResource]](typically) version of this object.
     */
    def readableByteChannel: InputResource[ReadableByteChannel]
    final def size : Option[Long] = sizeFunc()
    /**
     * Safely calculation the size of the resource or return None in case of failure or if
     * it is not possible to determin size of resource before accessing the resource.
     *
     */
    protected def sizeFunc: () => Option[Long]
}
/**
 * An object that in addition to being a resource is also a [[scalax.io.ReadChars]] Resource.
 *
 * @tparam R The underlying resource
 *
 * @author  Jesse Eichar
 * @since   1.0
 */
trait ReadCharsResource[+R] extends Resource[R] with ReadChars with ResourceOps[R, ReadCharsResource[R]]

/**
 * An Resource object that is a also an [[scalax.io.Output]].  This trait adds methods
 * for converting between common io types such as to a [[scalax.io.managed.WriterResource]] or
 * [[scalax.io.managed.WritableByteChannelResource]]
 *
 * @tparam R
 *          the type of underlying resource that is created
 *
 * @author  Jesse Eichar
 * @since   1.0
 */
trait OutputResource[+R] extends Resource[R] with Output with ResourceOps[R, OutputResource[R]] {
  /**
   * Obtain the [[scalax.io.OutputResource]](typically) version of this object.
   *
   * @return the [[scalax.io.OutputResource]](typically) version of this object.
   */
  def outputStream: OutputResource[OutputStream]
  /**
   * Obtain the [[scalax.io.WriteCharsResource]] version of this object.
   *
   * @param sourceCodec the codec used to convert from bytes to characters
   *
   * @return the [[scalax.io.WriteCharsResource]] version of this object.
   */
  def writer(implicit sourceCodec: Codec = Codec.default) : WriteCharsResource[Writer]
  /**
   * Obtain the [[scalax.io.managed.WritableByteChannelResource]](typically) version of this object.
   *
   * @return the [[scalax.io.managed.WritableByteChannelResource]](typically) version of this object.
   */
  def writableByteChannel: OutputResource[WritableByteChannel]
    override def doCopyFrom(input: Input): Unit =

    input match {
      case inR: InputResource[_] =>
        var failedToCopy = false
        for {
          inChan <- inR
            outChan <- this
        } {
          FileUtils.tryCopy(failedToCopy=true)(inChan, outChan)
        }
        if(failedToCopy)
          super.doCopyFrom(input)
      case _ => super.doCopyFrom(input)
    }

}
/**
 * An object that can be viewed as a Seekable object. For example
 * a FileChannel.
 *
 * @tparam R
 *          the type of the underlying Resource
 *
 * @author  Jesse Eichar
 * @since   1.0
 */
trait SeekableResource[+R] extends Seekable with InputResource[R] with OutputResource[R] with ResourceOps[R, SeekableResource[R]]

/**
 * An object that in addition to being a resource is also a [[scalax.io.WriteChars]] Resource.
 *
 * @tparam R The underlying resource
 *
 * @author  Jesse Eichar
 * @since   1.0
 */
trait WriteCharsResource[+R] extends Resource[R] with WriteChars with ResourceOps[R, WriteCharsResource[R]]

/**
 * Defines several factory methods for creating instances of Resource.
 *
 * '''Note:''' It is very important to try an pass a method that creates/opens the underlying resource or
 * the actual creation code as the opener.  This is important so that the resource can
 * be used more than once.  If an opened resource is passed to the factory method the resulting resource can only
 * be used once since it closes the resource when it is closed.
 *
 * '''Example:'''
 *
 * {{{
 *    val URL = new URL("http://scala-lang.org")
 *    val resource: Resource[InputStream] = Resource.fromInputStream(url.openStream).buffered
 * }}}
 *
 * @author  Jesse Eichar
 * @since   1.0
 *
 * @define openDisclaimer  The opener param is a by-name argument an is use to open a new stream.
 * In other words it is important to try and pass in a function for opening
 * the stream rather than the already opened stream so that the returned
 * Resource can be used multiple time
 */
object Resource {
  // InputStream factory methods
  /**
   *  Create an [[scalax.io.managed.InputStreamResource]] from an InputStream or subclass
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new InputStream
   *
   * @return an InputStreamResource
   */
  def fromInputStream[A <: InputStream](opener: => A): InputStreamResource[A] = new InputStreamResource[A](opener)
  /**
   * Create an Output Resource instance from an OutputStream.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new OutputStream
   *
   *
   *
   * @return an OutputStreamResource
   */
  def fromOutputStream[A <: OutputStream](opener: => A) : OutputStreamResource[A] = new OutputStreamResource[A](opener)
  // Reader factory methods
  /**
   * Create an ReadChars Resource instance from an Reader.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new Reader
   *
   * @return an ReaderResource
   */
  def fromReader[A <: Reader](opener: => A) : ReaderResource[A] = new ReaderResource[A](opener)
  // Writer factory methods
  /**
   * Create an WriteChars Resource instance with conversion traits from an Writer.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new Writer
   *
   * @return an WriterResource
   */
  def fromWriter[A <: Writer](opener: => A) : WriterResource[A] = new WriterResource[A](opener)
  // Channel factory methods
  /**
   * Create an Input Resource instance from an ReadableByteChannel.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new ReadableByteChannel
   *
   * @return an ReadableByteChannelResource
   */
  def fromReadableByteChannel[A <: ReadableByteChannel](opener: => A) : ReadableByteChannelResource[A] = new ReadableByteChannelResource[A](opener)
  /**
   * Create an Output Resource instance from an WritableByteChannel.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new WritableByteChannel
   *
   * @return an WritableByteChannelResource
   */
  def fromWritableByteChannel[A <: WritableByteChannel](opener: => A) : WritableByteChannelResource[A] = new WritableByteChannelResource[A](opener)
  /**
   * Create an Input/Output Resource instance from a ByteChannel.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new ByteChannel
   *
   * @return a ByteChannelResource
   */
  def fromByteChannel[A <: ByteChannel](opener: => A) : ByteChannelResource[A] = new ByteChannelResource[A](opener)
  /**
   * Create an Input/Output/Seekable Resource instance from a SeekableByteChannel.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new SeekableByteChannel
   *
   * @return a SeekableByteChannelResource
   */
  def fromSeekableByteChannel[A <: SeekableByteChannel](opener: => A) : SeekableByteChannelResource[A] = {
    new SeekableByteChannelResource[A](_ => opener,DefaultResourceContext, Noop, seekablesizeFunction(opener),None)
  }

  /**
   * Create an Input/Output/Seekable Resource instance from a function => SeekableByteChannel.  The function
   * takes the open options that describe how the channel should be opened.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new SeekableByteChannel
   *
   * @return a SeekableByteChannelResource
   */
  def fromSeekableByteChannel[A <: SeekableByteChannel](opener: Seq[OpenOption] => A) : SeekableByteChannelResource[A] = {
    new SeekableByteChannelResource[A](opener,DefaultResourceContext, Noop, seekablesizeFunction(opener(Read :: Nil)), None)
  }

  private def seekablesizeFunction(resource: => SeekableByteChannel)= () => {
    val r = resource
    allCatch.opt(try{r.size}finally{r.close()})
  }

  /**
   * Create an Input/Output/Seekable Resource instance from a RandomAccess file.
   *
   * $openDisclaimer
   *
   * @param opener the function for opening a new SeekableByteChannel
   *
   * @return a SeekableByteChannelResource
   */
  def fromRandomAccessFile(opener: => RandomAccessFile) : SeekableByteChannelResource[SeekableFileChannel] = {
    def open = (opts:Seq[OpenOption]) => support.FileUtils.openChannel(opener,opts)
    def sizeFunc = () => allCatch.opt{
      resource.managed(opener).acquireAndGet {
          _.length match {case len if len > -1 => len}
      }
    }
    new SeekableByteChannelResource[SeekableFileChannel](open, new ResourceContext{override def descName=PrefixedName("RandomAccessFile")},Noop, sizeFunc,Some(ReadWrite))
  }

  /**
   * Creates an Input Resource from a URL
   *
   * @param url the url to use for constructing a InputStreamResource
   *
   * @return an InputStreamResource
   */
  def fromURL(url:URL): InputStreamResource[InputStream] = {
    val sizeFunc = () => allCatch.opt {
      val conn: URLConnection = url.openConnection
      try {
        conn.connect()
        conn.getContentLength match {
          case len if len > -1 => len:Long
        }
      } finally {
        conn.getInputStream.close()
      }
    }
    new InputStreamResource(url.openStream,new ResourceContext{override def descName=KnownName(url.toExternalForm)},Noop,sizeFunc)
  }

  /**
   * Converts the string to a URL and creates an Input Resource from the URL
   *
   * @param url the url string to use for constructing a InputStreamResource
   *
   * @return an InputStreamResource
   *
   * @throws java.net.MalformedURLException if the url string is not a valid URL
   */
  def fromURL(url:String): InputStreamResource[InputStream] = fromURL(new URL(url))
  /**
   * Creates a Seekable Resource from a File
   *
   * @param file the file to use for constructing a Seekable Resource
   *
   * @return a SeekableByteChannelResource
   * @throws java.io.IOException if file does not exist
   */
  def fromFile(file:File): SeekableByteChannelResource[SeekableByteChannel] = {
    def open = (opts:Seq[OpenOption]) => support.FileUtils.openChannel(file,opts)
    def sizeFunc = () => allCatch.opt{file.length}
    new SeekableByteChannelResource[SeekableFileChannel](open,new ResourceContext{override def descName=KnownName(file.getPath)},Noop,sizeFunc, None)
  }
  /**
   * Create a file from string then create a Seekable Resource from a File
   *
   * @param file the file to use for constructing a Seekable Resource
   *
   * @return a SeekableByteChannelResource
   * @throws java.io.IOException if file does not exist
   */
  def fromFile(file:String): SeekableByteChannelResource[SeekableByteChannel] =
    fromFile(new File(file))

  /**
   * Create an InputStreamResource from a resource on the classpath.  The classloader from the provided class is used to resolve
   * the resource.
   *
   * An exception is thrown if the resource does not exist
   */
  def fromClasspath(name: String,
                    cl: Class[_] ) : InputStreamResource[InputStream]= {
    val url = cl.getClassLoader.getResource(name)
    require(url != null)
    Resource.fromURL(url)
  }

  /**
   * Create an InputStreamResource from a resource on the classpath.  The current threads context class loader is
   * used to load the resource
   *
   * An exception is thrown if the resource does not exist
   */
  def fromClasspath(name: String) : InputStreamResource[InputStream]= {
    val url = Thread.currentThread.getContextClassLoader.getResource(name)
    require(url != null)
    Resource.fromURL(url)
  }
}

sealed trait ResourceDescName{
  def name:String
}
case class KnownName(name:String) extends ResourceDescName
case class PrefixedName(prefix:String) extends ResourceDescName{
  def name: String = prefix+":"+hashCode
}
case class UnknownName() extends ResourceDescName{
  lazy val name = hashCode.toString
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy