diff --git a/pramen/core/src/main/scala/za/co/absa/pramen/core/utils/UsingUtils.scala b/pramen/core/src/main/scala/za/co/absa/pramen/core/utils/UsingUtils.scala index 2e37c8a5..1c6aa421 100644 --- a/pramen/core/src/main/scala/za/co/absa/pramen/core/utils/UsingUtils.scala +++ b/pramen/core/src/main/scala/za/co/absa/pramen/core/utils/UsingUtils.scala @@ -16,13 +16,11 @@ 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 @@ -30,33 +28,28 @@ object UsingUtils { * @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")) - } } } diff --git a/pramen/core/src/test/scala/za/co/absa/pramen/core/tests/utils/UsingUtilsSuite.scala b/pramen/core/src/test/scala/za/co/absa/pramen/core/tests/utils/UsingUtilsSuite.scala index 236eb326..f28ad44f 100644 --- a/pramen/core/src/test/scala/za/co/absa/pramen/core/tests/utils/UsingUtilsSuite.scala +++ b/pramen/core/src/test/scala/za/co/absa/pramen/core/tests/utils/UsingUtilsSuite.scala @@ -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 => @@ -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