scala.util.Using.scala Maven / Gradle / Ivy
The newest version!
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.util
import scala.util.control.{ControlThrowable, NonFatal}
/** A utility for performing automatic resource management. It can be used to perform an
* operation using resources, after which it releases the resources in reverse order
* of their creation.
*
* ==Usage==
*
* There are multiple ways to automatically manage resources with `Using`. If you only need
* to manage a single resource, the [[Using.apply `apply`]] method is easiest; it wraps the
* resource opening, operation, and resource releasing in a `Try`.
*
* Example:
* {{{
* import java.io.{BufferedReader, FileReader}
* import scala.util.{Try, Using}
*
* val lines: Try[Seq[String]] =
* Using(new BufferedReader(new FileReader("file.txt"))) { reader =>
* Iterator.continually(reader.readLine()).takeWhile(_ != null).toSeq
* }
* }}}
*
* If you need to manage multiple resources, [[Using.Manager$.apply `Using.Manager`]] should
* be used. It allows the managing of arbitrarily many resources, whose creation, use, and
* release are all wrapped in a `Try`.
*
* Example:
* {{{
* import java.io.{BufferedReader, FileReader}
* import scala.util.{Try, Using}
*
* val files = List("file1.txt", "file2.txt", "file3.txt", "file4.txt")
* val lines: Try[Seq[String]] = Using.Manager { use =>
* // acquire resources
* def mkreader(filename: String) = use(new BufferedReader(new FileReader(filename)))
*
* // use your resources here
* def lines(reader: BufferedReader): Iterator[String] =
* Iterator.continually(reader.readLine()).takeWhile(_ != null)
*
* files.map(mkreader).flatMap(lines)
* }
* }}}
*
* Composed or "wrapped" resources may be acquired in order of construction,
* if "underlying" resources are not closed. Although redundant in this case,
* here is the previous example with a wrapped call to `use`:
* {{{
* def mkreader(filename: String) = use(new BufferedReader(use(new FileReader(filename))))
* }}}
*
* Custom resources can be registered on construction by requiring an implicit `Manager`.
* This ensures they will be released even if composition fails:
* {{{
* import scala.util.Using
*
* case class X(x: String)(implicit mgr: Using.Manager) extends AutoCloseable {
* override def close() = println(s"CLOSE $x")
* mgr.acquire(this)
* }
* case class Y(y: String)(x: String)(implicit mgr: Using.Manager) extends AutoCloseable {
* val xres = X(x)
* override def close() = println(s"CLOSE $y")
* // an error during construction releases previously acquired resources
* require(y != null, "y is null")
* mgr.acquire(this)
* }
*
* Using.Manager { implicit mgr =>
* val y = Y("Y")("X")
* println(s"USE $y")
* }
* println {
* Using.Manager { implicit mgr =>
* Y(null)("X")
* }
* } // Failure(java.lang.IllegalArgumentException: requirement failed: y is null)
* }}}
*
* If you wish to avoid wrapping management and operations in a `Try`, you can use
* [[Using.resource `Using.resource`]], which throws any exceptions that occur.
*
* Example:
* {{{
* import java.io.{BufferedReader, FileReader}
* import scala.util.Using
*
* val lines: Seq[String] =
* Using.resource(new BufferedReader(new FileReader("file.txt"))) { reader =>
* Iterator.continually(reader.readLine()).takeWhile(_ != null).toSeq
* }
* }}}
*
* ==Suppression Behavior==
*
* If two exceptions are thrown (e.g., by an operation and closing a resource),
* one of them is re-thrown, and the other is
* [[java.lang.Throwable#addSuppressed added to it as a suppressed exception]].
* If the two exceptions are of different 'severities' (see below), the one of a higher
* severity is re-thrown, and the one of a lower severity is added to it as a suppressed
* exception. If the two exceptions are of the same severity, the one thrown first is
* re-thrown, and the one thrown second is added to it as a suppressed exception.
* If an exception is a [[scala.util.control.ControlThrowable `ControlThrowable`]], or
* if it does not support suppression (see
* [[java.lang.Throwable `Throwable`'s constructor with an `enableSuppression` parameter]]),
* an exception that would have been suppressed is instead discarded.
*
* Exceptions are ranked from highest to lowest severity as follows:
* - `java.lang.VirtualMachineError`
* - `java.lang.LinkageError`
* - `java.lang.InterruptedException` and `java.lang.ThreadDeath`
* - [[scala.util.control.NonFatal fatal exceptions]], excluding `scala.util.control.ControlThrowable`
* - `scala.util.control.ControlThrowable`
* - all other exceptions
*
* When more than two exceptions are thrown, the first two are combined and
* re-thrown as described above, and each successive exception thrown is combined
* as it is thrown.
*
* @define suppressionBehavior See the main doc for [[Using `Using`]] for full details of
* suppression behavior.
*/
object Using {
/** Performs an operation using a resource, and then releases the resource,
* even if the operation throws an exception.
*
* $suppressionBehavior
*
* @return a [[Try]] containing an exception if one or more were thrown,
* or the result of the operation if no exceptions were thrown
*/
def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A] = Try { Using.resource(resource)(f) }
/** A resource manager.
*
* Resources can be registered with the manager by calling [[acquire `acquire`]];
* such resources will be released in reverse order of their acquisition
* when the manager is closed, regardless of any exceptions thrown
* during use.
*
* $suppressionBehavior
*
* @note It is recommended for API designers to require an implicit `Manager`
* for the creation of custom resources, and to call `acquire` during those
* resources' construction. Doing so guarantees that the resource ''must'' be
* automatically managed, and makes it impossible to forget to do so.
*
*
* Example:
* {{{
* class SafeFileReader(file: File)(implicit manager: Using.Manager)
* extends BufferedReader(new FileReader(file)) {
*
* def this(fileName: String)(implicit manager: Using.Manager) = this(new File(fileName))
*
* manager.acquire(this)
* }
* }}}
*/
final class Manager private {
import Manager._
private var closed = false
private[this] var resources: List[Resource[_]] = Nil
/** Registers the specified resource with this manager, so that
* the resource is released when the manager is closed, and then
* returns the (unmodified) resource.
*/
def apply[R: Releasable](resource: R): resource.type = {
acquire(resource)
resource
}
/** Registers the specified resource with this manager, so that
* the resource is released when the manager is closed.
*/
def acquire[R: Releasable](resource: R): Unit = {
if (resource == null) throw new NullPointerException("null resource")
if (closed) throw new IllegalStateException("Manager has already been closed")
resources = new Resource(resource) :: resources
}
private def manage[A](op: Manager => A): A = {
var toThrow: Throwable = null
try {
op(this)
} catch {
case t: Throwable =>
toThrow = t
null.asInstanceOf[A] // compiler doesn't know `finally` will throw
} finally {
closed = true
var rs = resources
resources = null // allow GC, in case something is holding a reference to `this`
while (rs.nonEmpty) {
val resource = rs.head
rs = rs.tail
try resource.release()
catch {
case t: Throwable =>
if (toThrow == null) toThrow = t
else toThrow = preferentiallySuppress(toThrow, t)
}
}
if (toThrow != null) throw toThrow
}
}
}
object Manager {
/** Performs an operation using a `Manager`, then closes the `Manager`,
* releasing its resources (in reverse order of acquisition).
*
* Example:
* {{{
* val lines = Using.Manager { use =>
* use(new BufferedReader(new FileReader("file.txt"))).lines()
* }
* }}}
*
* If using resources which require an implicit `Manager` as a parameter,
* this method should be invoked with an `implicit` modifier before the function
* parameter:
*
* Example:
* {{{
* val lines = Using.Manager { implicit use =>
* new SafeFileReader("file.txt").lines()
* }
* }}}
*
* See the main doc for [[Using `Using`]] for full details of suppression behavior.
*
* @param op the operation to perform using the manager
* @tparam A the return type of the operation
* @return a [[Try]] containing an exception if one or more were thrown,
* or the result of the operation if no exceptions were thrown
*/
def apply[A](op: Manager => A): Try[A] = Try { (new Manager).manage(op) }
private final class Resource[R](resource: R)(implicit releasable: Releasable[R]) {
def release(): Unit = releasable.release(resource)
}
}
private def preferentiallySuppress(primary: Throwable, secondary: Throwable): Throwable = {
@annotation.nowarn("cat=deprecation") // avoid warning on mention of ThreadDeath
def score(t: Throwable): Int = t match {
case _: VirtualMachineError => 4
case _: LinkageError => 3
case _: InterruptedException | _: ThreadDeath => 2
case _: ControlThrowable => 0
case e if !NonFatal(e) => 1 // in case this method gets out of sync with NonFatal
case _ => -1
}
@inline def suppress(t: Throwable, suppressed: Throwable): Throwable = { t.addSuppressed(suppressed); t }
if (score(secondary) > score(primary)) suppress(secondary, primary)
else suppress(primary, secondary)
}
/** Performs an operation using a resource, and then releases the resource,
* even if the operation throws an exception. This method behaves similarly
* to Java's try-with-resources.
*
* $suppressionBehavior
*
* @param resource the resource
* @param body the operation to perform with the resource
* @tparam R the type of the resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resource throws
*/
def resource[R, A](resource: R)(body: R => A)(implicit releasable: Releasable[R]): A = {
if (resource == null) throw new NullPointerException("null resource")
var toThrow: Throwable = null
try {
body(resource)
} catch {
case t: Throwable =>
toThrow = t
null.asInstanceOf[A] // compiler doesn't know `finally` will throw
} finally {
if (toThrow eq null) releasable.release(resource)
else {
try releasable.release(resource)
catch { case other: Throwable => toThrow = preferentiallySuppress(toThrow, other) }
finally throw toThrow
}
}
}
/** Performs an operation using two resources, and then releases the resources
* in reverse order, even if the operation throws an exception. This method
* behaves similarly to Java's try-with-resources.
*
* $suppressionBehavior
*
* @param resource1 the first resource
* @param resource2 the second resource
* @param body the operation to perform using the resources
* @tparam R1 the type of the first resource
* @tparam R2 the type of the second resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resources throws
*/
def resources[R1: Releasable, R2: Releasable, A](
resource1: R1,
resource2: => R2
)(body: (R1, R2) => A
): A =
resource(resource1) { r1 =>
resource(resource2) { r2 =>
body(r1, r2)
}
}
/** Performs an operation using three resources, and then releases the resources
* in reverse order, even if the operation throws an exception. This method
* behaves similarly to Java's try-with-resources.
*
* $suppressionBehavior
*
* @param resource1 the first resource
* @param resource2 the second resource
* @param resource3 the third resource
* @param body the operation to perform using the resources
* @tparam R1 the type of the first resource
* @tparam R2 the type of the second resource
* @tparam R3 the type of the third resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resources throws
*/
def resources[R1: Releasable, R2: Releasable, R3: Releasable, A](
resource1: R1,
resource2: => R2,
resource3: => R3
)(body: (R1, R2, R3) => A
): A =
resource(resource1) { r1 =>
resource(resource2) { r2 =>
resource(resource3) { r3 =>
body(r1, r2, r3)
}
}
}
/** Performs an operation using four resources, and then releases the resources
* in reverse order, even if the operation throws an exception. This method
* behaves similarly to Java's try-with-resources.
*
* $suppressionBehavior
*
* @param resource1 the first resource
* @param resource2 the second resource
* @param resource3 the third resource
* @param resource4 the fourth resource
* @param body the operation to perform using the resources
* @tparam R1 the type of the first resource
* @tparam R2 the type of the second resource
* @tparam R3 the type of the third resource
* @tparam R4 the type of the fourth resource
* @tparam A the return type of the operation
* @return the result of the operation, if neither the operation nor
* releasing the resources throws
*/
def resources[R1: Releasable, R2: Releasable, R3: Releasable, R4: Releasable, A](
resource1: R1,
resource2: => R2,
resource3: => R3,
resource4: => R4
)(body: (R1, R2, R3, R4) => A
): A =
resource(resource1) { r1 =>
resource(resource2) { r2 =>
resource(resource3) { r3 =>
resource(resource4) { r4 =>
body(r1, r2, r3, r4)
}
}
}
}
/** A type class describing how to release a particular type of resource.
*
* A resource is anything which needs to be released, closed, or otherwise cleaned up
* in some way after it is finished being used, and for which waiting for the object's
* garbage collection to be cleaned up would be unacceptable. For example, an instance of
* [[java.io.OutputStream]] would be considered a resource, because it is important to close
* the stream after it is finished being used.
*
* An instance of `Releasable` is needed in order to automatically manage a resource
* with [[Using `Using`]]. An implicit instance is provided for all types extending
* [[java.lang.AutoCloseable]].
*
* @tparam R the type of the resource
*/
trait Releasable[-R] {
/** Releases the specified resource. */
def release(resource: R): Unit
}
object Releasable {
// prefer explicit types 2.14
//implicit val AutoCloseableIsReleasable: Releasable[AutoCloseable] = new Releasable[AutoCloseable] {}
/** An implicit `Releasable` for [[java.lang.AutoCloseable `AutoCloseable`s]]. */
implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] {
def release(resource: AutoCloseable): Unit = resource.close()
}
}
}