I'm using a Java-API which heavily uses the Autoclosable-Interface and thus in Java try-with-resources. However in Java you can specify
try (res1, res2, res3...) {
...
}
Do we have a way to use more than one resource? It looks like the well known callback-hell:
val database = Databases.openDatabase(dbFile)
database.use {
database.createResource(ResourceConfiguration.Builder(resPathName, config).build())
val resMgr = database.getResourceManager(ResourceManagerConfiguration.Builder(resPathName).build())
resMgr.use {
val wtx = resMgr.beginNodeWriteTrx()
wtx.use {
wtx.insertSubtreeAsFirstChild(XMLShredder.createStringReader(resFileToStore))
}
}
}
For simplicity I will use A,B and C for the chained autocloseables.
import java.io.Closeable
open class MockCloseable: Closeable {
override fun close() = TODO("Just for compilation")
}
class A: MockCloseable(){
fun makeB(): B = TODO()
}
class B: MockCloseable(){
fun makeC(): C = TODO()
}
class C: MockCloseable()
Using uses
This would look like this:
A().use {a ->
a.makeB().use {b ->
b.makeC().use {c ->
println(c)
}
}
}
Making a chain use function with a wrapper
Definition
class ChainedCloseable<T: Closeable>(val payload: T, val parents: List<Closeable>) {
fun <U> use(block: (T)->U): U {
try {
return block(payload)
} finally {
payload.close()
parents.asReversed().forEach { it.close() }
}
}
fun <U: Closeable> convert(block: (T)->U): ChainedCloseable<U> {
val newPayload = block(payload)
return ChainedCloseable(newPayload, parents + payload)
}
}
fun <T: Closeable, U: Closeable> T.convert(block:(T)->U): ChainedCloseable<U> {
val new = block(this)
}
Usage
A()
.convert(A::makeB)
.convert(B::makeC)
.use { c ->
println(c)
}
This allows you to avoid having to nest deeply, at the cost of creating wrapper objects.
There is no standard solution for this. If you had all of the Closable
instances ready at the start, you could use your own self-defined methods to handle them, like this blog post or this repository shows (and here is the discussion on the official forums that led to the latter).
In your case however, where subsequent objects rely on the previous ones, none of these apply like a regular try-with-resources
would.
The only thing I can suggest is trying to define helper functions for yourself that hide the nested use
calls, and immediately place you in the second/third/nth layer of these resourcs acquisitions, if that's at all possible.
Yet another approach for this:
val CloseableContext = ThreadLocal<MutableList<AutoCloseable>>()
inline fun scopeDef(inScope: () -> Unit) {
val oldContext = CloseableContext.get()
val currentContext = mutableListOf<AutoCloseable>()
CloseableContext.set(currentContext)
try {
inScope()
}
finally {
for(i in (currentContext.size - 1) downTo 0) {
try {
currentContext[i].close()
}
catch(e: Exception) {
// TODO: Record as suppressed exception
}
}
CloseableContext.set(oldContext)
}
}
fun <T: AutoCloseable> autoClose(resource: T): T {
CloseableContext.get()?.add(resource) ?: throw IllegalStateException(
"Calling autoClose outside of scopeDef is forbidden")
return resource
}
Usage:
class Close1(val name: String): AutoCloseable {
override fun close() {
println("close $name")
}
}
fun main(args : Array<String>) {
scopeDef {
val c1 = autoClose(Close1("1"))
scopeDef {
val c3 = autoClose(Close1("3"))
}
val c2 = autoClose(Close1(c1.name + "+1"))
}
}
Output:
close 3
close 1+1
close 1