Skip to content

Commit 767cd14

Browse files
committed
Enhance using() in UsingUtils for better exception handling and improve documentation.
1 parent b775ac8 commit 767cd14

File tree

2 files changed

+71
-25
lines changed

2 files changed

+71
-25
lines changed

cobol-parser/src/main/scala/za/co/absa/cobrix/cobol/utils/UsingUtils.scala

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,52 @@
1616

1717
package za.co.absa.cobrix.cobol.utils
1818

19-
import scala.util.control.NonFatal
20-
2119
object UsingUtils {
2220
/**
2321
* Executes the given action with a resource that implements the AutoCloseable interface, ensuring
2422
* proper closure of the resource. Any exception that occurs during the action or resource closure
25-
* is handled appropriately, with suppressed exceptions added where relevant. Null resources are not supported.
23+
* is handled appropriately.
24+
*
25+
* This is for use in Scala before Scala 2.13 introduced `Using` in `scala.util.Using` and works
26+
* similarly to Java try-with-resources with the exception that it does not support multiple resources.
27+
* But `using()` can be nested.
2628
*
27-
* @param resource a lazily evaluated resource that implements AutoCloseable
28-
* @param action a function to be executed using the provided resource
29-
* @tparam T the type of the resource, which must extend AutoCloseable
29+
* Example usage:
30+
* {{{
31+
* val result = UsingUtils.using(new AutoCloseableResource()) { resource =>
32+
* // Perform operations with the resource
33+
* // Return value of any type, including Unit, and null, or throw an exception.
34+
* }
35+
* }}}
36+
*
37+
* @param resource a resource that implements AutoCloseable.
38+
* @param action an action to be executed using the provided resource.
39+
* @tparam T the type of the resource, which must extend AutoCloseable.
3040
* @throws Throwable if either the action or resource closure fails. If both fail, the action's exception
31-
* is thrown with the closure's exception added as suppressed
41+
* is thrown with the closure's exception added as suppressed.
3242
*/
33-
def using[T <: AutoCloseable,U](resource: => T)(action: T => U): U = {
34-
var thrownException: Option[Throwable] = None
35-
var suppressedException: Option[Throwable] = None
43+
def using[T <: AutoCloseable, U](resource: => T)(action: T => U): U = {
44+
var actionException: Throwable = null
3645
val openedResource = resource
3746

38-
val result = try {
39-
Option(action(openedResource))
47+
try {
48+
action(openedResource)
4049
} catch {
41-
case NonFatal(ex) =>
42-
thrownException = Option(ex)
43-
None
50+
case t: Throwable =>
51+
actionException = t
52+
throw t
4453
} finally
4554
if (openedResource != null) {
4655
try
4756
openedResource.close()
4857
catch {
49-
case NonFatal(ex) => suppressedException = Option(ex)
58+
case closeException: Throwable =>
59+
if (actionException != null) {
60+
actionException.addSuppressed(closeException)
61+
} else {
62+
throw closeException
63+
}
5064
}
5165
}
52-
53-
(thrownException, suppressedException) match {
54-
case (Some(thrown), Some(suppressed)) =>
55-
thrown.addSuppressed(suppressed)
56-
throw thrown
57-
case (Some(thrown), None) => throw thrown
58-
case (None, Some(suppressed)) => throw suppressed
59-
case (None, None) => result.getOrElse(throw new IllegalArgumentException("Action returned null"))
60-
}
6166
}
6267
}

cobol-parser/src/test/scala/za/co/absa/cobrix/cobol/utils/UsingUtilsSuite.scala

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,47 @@ class UsingUtilsSuite extends AnyWordSpec {
5252
assert(resource.closeCallCount == 1)
5353
}
5454

55+
"handle a null resource" in {
56+
var resourceWasNull = false
57+
58+
UsingUtils.using(null: AutoCloseableSpy) { res =>
59+
resourceWasNull = res == null
60+
}
61+
62+
assert(resourceWasNull)
63+
}
64+
65+
"handle a null resource and action throw" in {
66+
var exceptionThrown = false
67+
var resourceWasNull = false
68+
69+
try {
70+
UsingUtils.using(null: AutoCloseableSpy) { res =>
71+
resourceWasNull = res == null
72+
throw new RuntimeException("Action throws")
73+
}
74+
} catch {
75+
case ex: Throwable =>
76+
exceptionThrown = true
77+
assert(ex.getMessage.contains("Action throws"))
78+
}
79+
80+
assert(exceptionThrown)
81+
assert(resourceWasNull)
82+
}
83+
84+
"handle a null return value" in {
85+
var resourceWasNull = false
86+
87+
val result = UsingUtils.using(null: AutoCloseableSpy) { res =>
88+
resourceWasNull = res == null
89+
null: String
90+
}
91+
92+
assert(resourceWasNull)
93+
assert(result == null)
94+
}
95+
5596
"handle exceptions when a resource is created" in {
5697
var exceptionThrown = false
5798
var resource: AutoCloseableSpy = null

0 commit comments

Comments
 (0)