Reputation: 2121
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))
}
}
}
Upvotes: 34
Views: 11512
Reputation: 12670
Adding my own take here because none of the answers were acceptable to me:
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
private fun closeChain(items: List<AutoCloseable>) {
if (resources.isEmpty()) return
try {
resources.last().close()
} finally {
resources.dropLast(1).also(::closeChain)
}
}
class ResourceInitializationScope {
private val itemsToCloseLater = mutableListOf<AutoCloseable>()
/**
* Adds a resource to be closed later.
*
* @param resource the resource.
*/
fun closeLater(item: AutoCloseable) {
itemsToCloseLater.add(item)
}
fun closeNow() = closeChain(itemsToCloseLater)
}
/**
* Begins a block to initialize multiple resources.
*
* This is done safely. If any failure occurs, all previously-initialized resources are closed.
*
* @param block the block of code to run.
* @return an object which should be closed later to close all resources opened during the block.
*/
@OptIn(ExperimentalContracts::class)
inline fun initializingResources(block: ResourceInitializationScope.() -> Unit): AutoCloseable {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val scope = ResourceInitializationScope()
var success = false
try {
scope.apply(block)
success = true
return AutoCloseable { scope.closeNow() }
} finally {
if (!success) scope.closeNow()
}
}
Example usage: (maybe don't trust the code, other than an example of how to use it, I don't trust it yet)
// ... omitting surrounding code ...
private val buffers: Array<AudioBuffer>
private val device: AudioDevice
private val context: AudioContext
private val source: AudioSource
private val resourcesToClose: AutoCloseable
init {
resourcesToClose = initializingResources {
buffers = Array(BUFFER_COUNT) { AudioBuffer.generate().also(::closeLater) }
device = AudioDevice.openDefaultDevice().also(::closeLater)
context = AudioContext.create(device, intArrayOf(0)).also(::closeLater)
context.makeCurrent()
device.createCapabilities()
source = AudioSource.generate().also(::closeLater)
for (i in 0 until BUFFER_COUNT) {
bufferSamples(ShortArray(0))
}
source.play()
device.detectInternalExceptions()
}
}
override fun close() {
resourcesToClose.close()
}
// ... omitting surrounding code ...
Upvotes: 0
Reputation: 1021
Well there's plenty of answers already, but I noticed that this answer, especially Method 3 (with varargs), accepts already created resources, which is a bit different from Java's try-with-resources.
You see, according to this, Java not only closes all the resources after the try block, but it also catches all exceptions during resource initialisation. For example if resource2
wasn't created because of an exception, resource1
is still closed:
try (
var resource1 = acquireResource();
var resource2 = acquireResource() <- error here, but still is ok
) {
// ...
}
I think that's why we have this nesting uses in Kotlin in the first place.
So, keeping the above detail in mind, I created this varargs variant:
inline fun <reified T : AutoCloseable?, R> withResources(vararg providers: () -> T, crossinline block: (Array<T?>) -> R): R {
val array = arrayOfNulls<T>(providers.size)
var useBlock = { block(array) }
// reversed because we go from the deepest `use` to the top
for (i in providers.indices.reversed()) {
// we need a local variable here to pass it to the lambda
// NOT by reference
// otherwise lambda will capture the useBlock by ref
// and create infinite recursion
val goInside = useBlock
useBlock = {
array[i] = providers[i]()
array[i].use { goInside.invoke() }
}
}
return useBlock.invoke()
}
This function works exactly like Java's try-with-resources.
class TestAutoCloseable(val name: String, val closingMessage: String) : AutoCloseable {
init {
println("build $name")
}
override fun close() {
println(closingMessage)
}
override fun toString(): String {
return name
}
}
fun main(args: Array<String>) {
withResources(
{ TestAutoCloseable("A", "closing A") },
{ TestAutoCloseable("B", "closing B") },
{
// throw RuntimeException("boom")
TestAutoCloseable("C", "closing C")
}
) { (a, b, c) ->
println("MY CODE, RESOURCES ARE: $a, $b, $c")
}
}
Above example prints:
build A
build B
build C
MY CODE, RESOURCES ARE: A, B, C
closing C
closing B
closing A
But if I uncomment the RuntimeException
in the C initialiser, I still get the "closing" parts for A and B:
build A
build B
closing B
closing A
Exception in thread "main" java.lang.RuntimeException: boom
The main downside of this function is the type inference, but there is nothing we can do about that.
I'd personally stick to the much simpler and more useful solution of just creating an inline function for 2 or 3 resources with simple use
nesting inside. You'll probably never need more than 3 at the same time.
Upvotes: 0
Reputation: 16928
Method 1: For two resources and using native java resource manager:
Define jUsing()
in Kotlin:
// crossinline version:
inline fun <R, A : Closeable?, B : Closeable?>
jUsing(a: A, b: B, crossinline block: (A, B) -> R): R =
J.jUsing(a, b) { c, d -> block(c, d) }
And also Util.jUsing()
in Util.java
:
Note: Below code
is compatible with Java 9+. You can implement it with try-catch-finally
to make it compatible with previous versions. See here for an example.
public static <R, A extends AutoCloseable, B extends AutoCloseable> R
jUsing(A a, B b, Function2<A, B, R> block) throws Exception {
try (a; b) {
return block.invoke(a, b);
}
}
(Function2
is kotlin.jvm.functions.Function2
.)
Then use like below:
// Download url to destFile and close streams correctly:
jUsing(URL(url).openStream(), FileOutputStream(destFile), InputStream::transferTo)
Note: Above code
used Java 9+ InputStream.transferTo()
method. See here for a transferTo()
Kotlin alternative that is compatible with previous versions.
Note: You can write Kotlin jUsing()
method more simple using noinline
keyword instead of crossinline
. But I think crossinline
version has more performance:
// noinline version:
inline fun <R, A : Closeable?, B : Closeable?>
jUsing(a: A, b: B, noinline block: (A, B) -> R): R =
Util.jUsing(a, b, block)
Method 2: For two resources (and with similar usage to method 1):
Thank @zsmb13's answer for the link
/**
* Based on https://github.com/FelixEngl/KotlinUsings/blob/master/Usings.kt
* and with some changes
*/
inline fun <R, A : Closeable, B : Closeable> using(a: A, b: B, block: (A, B) -> R): R {
var exception: Throwable? = null
try {
return block(a, b)
} catch (e: Throwable) {
exception = e
throw e
} finally {
if (exception == null) {
a.close()
b.close()
} else {
try {
a.close()
} catch (closeException: Throwable) {
exception.addSuppressed(closeException)
}
try {
b.close()
} catch (closeException: Throwable) {
exception.addSuppressed(closeException)
}
}
}
}
Method 3: For any number of resources (arrayOf(stream1, stream2, ...).use {...}
):
/**
* Based on https://medium.com/@appmattus/effective-kotlin-item-9-prefer-try-with-resources-to-try-finally-aec8c202c30a
* and with a few changes
*/
inline fun <T : Closeable?, R> Array<T>.use(block: (Array<T>) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when (exception) {
null -> forEach { it?.close() }
else -> forEach {
try {
it?.close()
} catch (closeException: Throwable) {
exception.addSuppressed(closeException)
}
}
}
}
}
See referenced link for more details.
Upvotes: 5
Reputation: 10959
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
Upvotes: 5
Reputation: 6991
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()
This would look like this:
A().use {a ->
a.makeB().use {b ->
b.makeC().use {c ->
println(c)
}
}
}
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.
Upvotes: 6
Reputation: 89548
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.
Upvotes: 13