Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,40 @@

package za.co.absa.pramen.core.utils

import scala.util.control.NonFatal

object UsingUtils {
/**
* Executes the given action with a resource that implements the AutoCloseable interface, ensuring
* proper closure of the resource. Any exception that occurs during the action or resource closure
* is handled appropriately, with suppressed exceptions added where relevant. Null resources are not supported.
* is handled appropriately, with suppressed exceptions added where relevant.
*
* @param resource a lazily evaluated resource that implements AutoCloseable
* @param action a function to be executed using the provided resource
* @tparam T the type of the resource, which must extend AutoCloseable
* @throws Throwable if either the action or resource closure fails. If both fail, the action's exception
* is thrown with the closure's exception added as suppressed
*/
def using[T <: AutoCloseable,U](resource: => T)(action: T => U): U = {
var thrownException: Option[Throwable] = None
var suppressedException: Option[Throwable] = None
def using[T <: AutoCloseable, U](resource: => T)(action: T => U): U = {
var actionException: Throwable = null
val openedResource = resource

val result = try {
Option(action(openedResource))
try {
action(openedResource)
} catch {
case NonFatal(ex) =>
thrownException = Option(ex)
None
case t: Throwable =>
actionException = t
throw t
} finally
if (openedResource != null) {
try
openedResource.close()
catch {
case NonFatal(ex) => suppressedException = Option(ex)
case closeException: Throwable =>
if (actionException != null) {
actionException.addSuppressed(closeException)
} else {
throw closeException
}
}
}

(thrownException, suppressedException) match {
case (Some(thrown), Some(suppressed)) =>
thrown.addSuppressed(suppressed)
throw thrown
case (Some(thrown), None) => throw thrown
case (None, Some(suppressed)) => throw suppressed
case (None, None) => result.getOrElse(throw new IllegalArgumentException("Action returned null"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class UsingUtilsSuite extends AnyWordSpec {
assert(resource.closeCallCount == 1)
}

"throw an exception on null resource" in {
"handle a null resource" in {
var resourceWasNull = false

UsingUtils.using(null: AutoCloseableSpy) { res =>
Expand All @@ -64,6 +64,37 @@ class UsingUtilsSuite extends AnyWordSpec {
assert(resourceWasNull)
}

"handle a null resource and action throw" in {
var exceptionThrown = false
var resourceWasNull = false

try {
UsingUtils.using(null: AutoCloseableSpy) { res =>
resourceWasNull = res == null
throw new RuntimeException("Action throws")
}
} catch {
case ex: Throwable =>
exceptionThrown = true
assert(ex.getMessage.contains("Action throws"))
}

assert(exceptionThrown)
assert(resourceWasNull)
}

"handle a null return value" in {
var resourceWasNull = false

val result = UsingUtils.using(null: AutoCloseableSpy) { res =>
resourceWasNull = res == null
null: String
}

assert(resourceWasNull)
assert(result == null)
}

"handle exceptions when a resource is created" in {
var exceptionThrown = false
var resource: AutoCloseableSpy = null
Expand Down
Loading