Fix memory leak in sealed builder. Fine-grained Meta builders.
This commit is contained in:
parent
cbbcd18df3
commit
851fdda311
@ -7,11 +7,11 @@ import kotlin.test.assertEquals
|
|||||||
|
|
||||||
|
|
||||||
fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = ByteArray {
|
fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = ByteArray {
|
||||||
format.writeObject(this@ByteArray, this@toByteArray)
|
format.writeTo(this@ByteArray, this@toByteArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MetaFormat.fromByteArray(packet: ByteArray): Meta {
|
fun MetaFormat.fromByteArray(packet: ByteArray): Meta {
|
||||||
return packet.asBinary().read { readObject(this) }
|
return packet.asBinary().read { readFrom(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetaFormatTest {
|
class MetaFormatTest {
|
||||||
|
@ -4,8 +4,8 @@ import kotlinx.io.buffered
|
|||||||
|
|
||||||
|
|
||||||
fun <T : Any> IOFormat<T>.writeToByteArray(obj: T): ByteArray = ByteArray {
|
fun <T : Any> IOFormat<T>.writeToByteArray(obj: T): ByteArray = ByteArray {
|
||||||
writeObject(this, obj)
|
writeTo(this, obj)
|
||||||
}
|
}
|
||||||
fun <T : Any> IOFormat<T>.readFromByteArray(array: ByteArray): T = ByteArraySource(array).buffered().use {
|
fun <T : Any> IOFormat<T>.readFromByteArray(array: ByteArray): T = ByteArraySource(array).buffered().use {
|
||||||
readObject(it)
|
readFrom(it)
|
||||||
}
|
}
|
@ -50,7 +50,9 @@ public interface Meta : MetaRepr, MetaProvider {
|
|||||||
override fun toMeta(): Meta = this
|
override fun toMeta(): Meta = this
|
||||||
|
|
||||||
override fun toString(): String
|
override fun toString(): String
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean
|
override fun equals(other: Any?): Boolean
|
||||||
|
|
||||||
override fun hashCode(): Int
|
override fun hashCode(): Int
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
@ -11,7 +11,7 @@ import kotlin.js.JsName
|
|||||||
* Mark a meta builder
|
* Mark a meta builder
|
||||||
*/
|
*/
|
||||||
@DslMarker
|
@DslMarker
|
||||||
public annotation class MetaBuilder
|
public annotation class MetaBuilderMarker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic interface that gives access to getting and setting meta notes and values
|
* A generic interface that gives access to getting and setting meta notes and values
|
||||||
@ -27,7 +27,7 @@ public interface MutableMetaProvider : MetaProvider, MutableValueProvider {
|
|||||||
* TODO documentation
|
* TODO documentation
|
||||||
*/
|
*/
|
||||||
@Serializable(MutableMetaSerializer::class)
|
@Serializable(MutableMetaSerializer::class)
|
||||||
@MetaBuilder
|
@MetaBuilderMarker
|
||||||
public interface MutableMeta : Meta, MutableMetaProvider {
|
public interface MutableMeta : Meta, MutableMetaProvider {
|
||||||
|
|
||||||
override val items: Map<NameToken, MutableMeta>
|
override val items: Map<NameToken, MutableMeta>
|
||||||
@ -90,8 +90,8 @@ public interface MutableMeta : Meta, MutableMetaProvider {
|
|||||||
setMeta(this, repr.toMeta())
|
setMeta(this, repr.toMeta())
|
||||||
}
|
}
|
||||||
|
|
||||||
public infix fun Name.put(mutableMeta: MutableMeta.() -> Unit) {
|
public infix fun Name.put(builder: MutableMeta.() -> Unit) {
|
||||||
setMeta(this, Meta(mutableMeta))
|
getOrCreate(this).apply(builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
public infix fun String.put(meta: Meta) {
|
public infix fun String.put(meta: Meta) {
|
||||||
@ -131,7 +131,7 @@ public interface MutableMeta : Meta, MutableMetaProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public infix fun String.put(builder: MutableMeta.() -> Unit) {
|
public infix fun String.put(builder: MutableMeta.() -> Unit) {
|
||||||
setMeta(Name.parse(this), MutableMeta(builder))
|
getOrCreate(parseAsName()).apply(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,16 +381,14 @@ public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value,
|
|||||||
|
|
||||||
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
|
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@JsName("newObservableMutableMeta")
|
||||||
@JsName("newMutableMeta")
|
public fun ObservableMutableMeta(): ObservableMutableMeta = MutableMetaImpl(null)
|
||||||
public fun MutableMeta(): ObservableMutableMeta = MutableMetaImpl(null)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a [MutableMeta] using given transformation
|
* Build a [MutableMeta] using given transformation
|
||||||
*/
|
*/
|
||||||
@Suppress("FunctionName")
|
public inline fun ObservableMutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
|
||||||
public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
|
ObservableMutableMeta().apply(builder)
|
||||||
MutableMeta().apply(builder)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,6 +51,8 @@ private class ObservableMetaWrapper(
|
|||||||
|
|
||||||
override fun setMeta(name: Name, node: Meta?) {
|
override fun setMeta(name: Name, node: Meta?) {
|
||||||
val oldMeta = get(name)
|
val oldMeta = get(name)
|
||||||
|
//don't forget to remove listener
|
||||||
|
oldMeta?.removeListener(this)
|
||||||
root.setMeta(absoluteName + name, node)
|
root.setMeta(absoluteName + name, node)
|
||||||
if (oldMeta != node) {
|
if (oldMeta != node) {
|
||||||
invalidate(name)
|
invalidate(name)
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
package space.kscience.dataforge.meta
|
package space.kscience.dataforge.meta
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import space.kscience.dataforge.names.NameToken
|
import space.kscience.dataforge.names.*
|
||||||
|
import kotlin.js.JsName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The meta implementation which is guaranteed to be immutable.
|
* The meta implementation which is guaranteed to be immutable.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
public class SealedMeta internal constructor(
|
public class SealedMeta(
|
||||||
override val value: Value?,
|
override val value: Value?,
|
||||||
override val items: Map<NameToken, SealedMeta>
|
override val items: Map<NameToken, SealedMeta>,
|
||||||
) : TypedMeta<SealedMeta> {
|
) : TypedMeta<SealedMeta> {
|
||||||
override fun toString(): String = Meta.toString(this)
|
override fun toString(): String = Meta.toString(this)
|
||||||
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
|
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
|
||||||
@ -26,7 +27,7 @@ public class SealedMeta internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate sealed node from [this]. If it is already sealed return it as is.
|
* Generate sealed node from [this]. If it is already sealed, return it as is.
|
||||||
*/
|
*/
|
||||||
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(
|
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(
|
||||||
value,
|
value,
|
||||||
@ -47,7 +48,79 @@ public fun Meta(value: String): SealedMeta = Meta(value.asValue())
|
|||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
public fun Meta(value: Boolean): SealedMeta = Meta(value.asValue())
|
public fun Meta(value: Boolean): SealedMeta = Meta(value.asValue())
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
public inline fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
|
|
||||||
MutableMeta(builder).seal()
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lightweight mutable meta used to create [SealedMeta] instances without bothering with
|
||||||
|
*/
|
||||||
|
@PublishedApi
|
||||||
|
internal class MetaBuilder(
|
||||||
|
override var value: Value? = null,
|
||||||
|
override val items: MutableMap<NameToken, MetaBuilder> = hashMapOf(),
|
||||||
|
) : MutableMeta {
|
||||||
|
|
||||||
|
override fun getOrCreate(name: Name): MetaBuilder {
|
||||||
|
val existing = get(name) as? MetaBuilder
|
||||||
|
return if (existing == null) {
|
||||||
|
val newItem = MetaBuilder()
|
||||||
|
setMeta(name, newItem)
|
||||||
|
newItem
|
||||||
|
} else {
|
||||||
|
existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wrap(meta: Meta): MetaBuilder = meta as? MetaBuilder ?: MetaBuilder(
|
||||||
|
meta.value,
|
||||||
|
meta.items.mapValuesTo(hashMapOf()) { wrap(it.value) }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
override fun setMeta(name: Name, node: Meta?) {
|
||||||
|
when (name.length) {
|
||||||
|
0 -> error("Can't set a meta with empty name")
|
||||||
|
1 -> {
|
||||||
|
val token = name.first()
|
||||||
|
//remove child and invalidate if argument is null
|
||||||
|
if (node == null) {
|
||||||
|
items.remove(token)
|
||||||
|
} else {
|
||||||
|
items[token] = wrap(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
getOrCreate(name.first().asName()).setMeta(name.cutFirst(), node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = Meta.toString(this)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Meta.hashCode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a read-only meta.
|
||||||
|
*/
|
||||||
|
public inline fun Meta(builder: MutableMeta.() -> Unit): Meta =
|
||||||
|
MetaBuilder().apply(builder).seal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an immutable meta.
|
||||||
|
*/
|
||||||
|
public inline fun SealedMeta(builder: MutableMeta.() -> Unit): SealedMeta =
|
||||||
|
MetaBuilder().apply(builder).seal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty meta mutable meta.
|
||||||
|
*/
|
||||||
|
@JsName("newMutableMeta")
|
||||||
|
public fun MutableMeta(): MutableMeta = MetaBuilder()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mutable meta with given builder.
|
||||||
|
*/
|
||||||
|
public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): MutableMeta =
|
||||||
|
MutableMeta().apply(builder)
|
||||||
|
@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
|
|||||||
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
|
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
|
||||||
*/
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public fun generate(source: ObservableMeta): ObservableMeta = MutableMeta().apply {
|
public fun generate(source: ObservableMeta): ObservableMeta = ObservableMutableMeta{
|
||||||
transformations.forEach { rule ->
|
transformations.forEach { rule ->
|
||||||
rule.selectItems(source).forEach { name ->
|
rule.selectItems(source).forEach { name ->
|
||||||
rule.transformItem(name, source[name], this)
|
rule.transformItem(name, source[name], this)
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import kotlin.test.assertFails
|
|
||||||
|
|
||||||
class JvmMutableMetaTest {
|
|
||||||
@Test
|
|
||||||
fun recursiveMeta(){
|
|
||||||
val meta = MutableMeta {
|
|
||||||
"a" put 2
|
|
||||||
}
|
|
||||||
|
|
||||||
assertFails { meta["child.a"] = meta }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user