Skip to content

Commit 2d22f52

Browse files
committed
feat(model-api): added flow based API in INode
This is an alternative to the IBulkQuery that already exists in CLTree. It allows a batching of request to the key-value store.
1 parent d219a99 commit 2d22f52

File tree

3 files changed

+101
-5
lines changed

3 files changed

+101
-5
lines changed

model-api/src/commonMain/kotlin/org/modelix/model/api/INode.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package org.modelix.model.api
1717

18+
import kotlinx.coroutines.flow.*
1819
import org.modelix.model.area.IArea
1920

2021
/**
@@ -217,6 +218,25 @@ interface INode {
217218
fun getAllReferenceTargets(): List<Pair<IReferenceLink, INode>> = getReferenceLinks().map { it to getReferenceTarget(it) }.filterSecondNotNull()
218219
fun getAllReferenceTargetRefs(): List<Pair<IReferenceLink, INodeReference>> = getReferenceLinks().map { it to getReferenceTargetRef(it) }.filterSecondNotNull()
219220
// </editor-fold>
221+
222+
// <editor-fold desc="flow API">
223+
fun getParentAsFlow(): Flow<INode> = flowOf(parent).filterNotNull()
224+
fun getPropertyValueAsFlow(role: IProperty): Flow<String?> = flowOf(getPropertyValue(role))
225+
fun getAllChildrenAsFlow(): Flow<INode> = allChildren.asFlow()
226+
fun getAllReferenceTargetsAsFlow(): Flow<Pair<IReferenceLink, INode>> = getAllReferenceTargets().asFlow()
227+
fun getAllReferenceTargetRefsAsFlow(): Flow<Pair<IReferenceLink, INodeReference>> = getAllReferenceTargetRefs().asFlow()
228+
fun getChildrenAsFlow(role: IChildLink): Flow<INode> = getChildren(role).asFlow()
229+
fun getReferenceTargetAsFlow(role: IReferenceLink): Flow<INode> = flowOf(getReferenceTarget(role)).filterNotNull()
230+
fun getReferenceTargetRefAsFlow(role: IReferenceLink): Flow<INodeReference> = flowOf(getReferenceTargetRef(role)).filterNotNull()
231+
232+
fun getDescendantsAsFlow(includeSelf: Boolean = false): Flow<INode> {
233+
return if (includeSelf) {
234+
flowOf(flowOf(this), getDescendantsAsFlow(false)).flattenConcat()
235+
} else {
236+
getAllChildrenAsFlow().flatMapConcat { it.getDescendantsAsFlow(true) }
237+
}
238+
}
239+
// </editor-fold>
220240
}
221241

222242
fun <T1, T2> List<Pair<T1, T2?>>.filterSecondNotNull(): List<Pair<T1, T2>> = filter { it.second != null } as List<Pair<T1, T2>>

model-api/src/commonMain/kotlin/org/modelix/model/api/ITree.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package org.modelix.model.api
1717

18+
import kotlinx.coroutines.flow.*
19+
1820
/**
1921
* Consists of [INode]s.
2022
*/
@@ -243,6 +245,20 @@ interface ITree {
243245
*/
244246
fun deleteNodes(nodeIds: LongArray): ITree
245247

248+
fun getAllChildrenAsFlow(parentId: Long): Flow<Long> = getAllChildren(parentId).asFlow()
249+
fun getDescendantsAsFlow(nodeId: Long, includeSelf: Boolean = false): Flow<Long> {
250+
return if (includeSelf) {
251+
flowOf(flowOf(nodeId), getDescendantsAsFlow(nodeId, false)).flattenConcat()
252+
} else {
253+
getAllChildrenAsFlow(nodeId).flatMapConcat { getDescendantsAsFlow(it, true) }
254+
}
255+
}
256+
fun getAllReferenceTargetsAsFlow(nodeId: Long): Flow<Pair<String, INodeReference>> = getReferenceRoles(nodeId).map { it to getReferenceTarget(nodeId, it) }.filterSecondNotNull().asFlow()
257+
fun getChildrenAsFlow(parentId: Long, role: String): Flow<Long> = getChildren(parentId, role).asFlow()
258+
fun getReferenceTargetAsFlow(nodeId: Long, role: String): Flow<INodeReference> = flowOf(getReferenceTarget(nodeId, role)).filterNotNull()
259+
fun getParentAsFlow(nodeId: Long): Flow<Long> = flowOf(getParent(nodeId)).filter { it != 0L }
260+
fun getPropertyValueAsFlow(nodeId: Long, role: String): Flow<String?> = flowOf(getProperty(nodeId, role))
261+
246262
companion object {
247263
const val ROOT_ID = 1L
248264
const val DETACHED_NODES_ROLE = "detached"

model-api/src/commonMain/kotlin/org/modelix/model/api/PNodeAdapter.kt

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,22 @@
1515

1616
package org.modelix.model.api
1717

18+
import kotlinx.coroutines.flow.Flow
19+
import kotlinx.coroutines.flow.map
20+
import kotlinx.coroutines.flow.mapNotNull
1821
import org.modelix.model.area.ContextArea
1922
import org.modelix.model.area.IArea
2023
import org.modelix.model.area.PArea
24+
import kotlin.coroutines.coroutineContext
2125

2226
open class PNodeAdapter(val nodeId: Long, val branch: IBranch) : INode, INodeEx {
2327

2428
init {
2529
require(nodeId != 0L, { "Invalid node 0" })
2630
}
2731

32+
private fun getTree(): ITree = branch.transaction.tree
33+
2834
override fun getArea(): PArea = PArea(branch)
2935

3036
protected fun unwrap(node: INode?): Long {
@@ -111,12 +117,27 @@ open class PNodeAdapter(val nodeId: Long, val branch: IBranch) : INode, INodeEx
111117
override fun getReferenceTarget(role: String): INode? {
112118
notifyAccess()
113119
val targetRef = getReferenceTargetRef(role) ?: return null
114-
if (targetRef is PNodeReference) {
115-
return targetRef.resolveNode(PArea(branch))
120+
return tryResolveNodeRef(targetRef)
121+
}
122+
123+
private fun tryResolveNodeRef(targetRef: INodeReference): INode? {
124+
return if (targetRef is PNodeReference) {
125+
targetRef.resolveIn(PArea(branch)!!)
126+
} else {
127+
val area = ContextArea.CONTEXT_VALUE.getValue()
128+
?: throw RuntimeException(IArea::class.simpleName + " not available")
129+
targetRef.resolveIn(area!!)
116130
}
117-
val area = ContextArea.CONTEXT_VALUE.getValue()
118-
?: throw RuntimeException(IArea::class.simpleName + " not available")
119-
return targetRef.resolveNode(area)
131+
}
132+
133+
private suspend fun resolveNodeRefInCoroutine(targetRef: INodeReference): INode {
134+
return if (targetRef is PNodeReference) {
135+
targetRef.resolveIn(PArea(branch)!!)
136+
} else {
137+
val scope = coroutineContext[INodeResolutionScope]
138+
?: throw IllegalStateException("INodeResolutionScope not set")
139+
targetRef.resolveIn(scope)
140+
} ?: throw RuntimeException("Failed to resolve node: $targetRef")
120141
}
121142

122143
override fun getReferenceTargetRef(role: String): INodeReference? {
@@ -160,6 +181,45 @@ open class PNodeAdapter(val nodeId: Long, val branch: IBranch) : INode, INodeEx
160181
return branch.transaction.getReferenceRoles(nodeId).toList()
161182
}
162183

184+
override fun getAllChildrenAsFlow(): Flow<INode> {
185+
return getTree().getAllChildrenAsFlow(nodeId).map { wrap(it)!! }
186+
}
187+
188+
override fun getDescendantsAsFlow(includeSelf: Boolean): Flow<INode> {
189+
return getTree().getDescendantsAsFlow(nodeId, includeSelf).map { wrap(it)!! }
190+
}
191+
192+
override fun getAllReferenceTargetsAsFlow(): Flow<Pair<IReferenceLink, INode>> {
193+
return getAllReferenceTargetRefsAsFlow().map { it.first to resolveNodeRefInCoroutine(it.second) }
194+
}
195+
196+
override fun getAllReferenceTargetRefsAsFlow(): Flow<Pair<IReferenceLink, INodeReference>> {
197+
return getTree().getAllReferenceTargetsAsFlow(nodeId).map { this.resolveReferenceLinkOrFallback(it.first) to it.second }
198+
}
199+
200+
override fun getChildrenAsFlow(role: IChildLink): Flow<INode> {
201+
val tree = getTree()
202+
return tree.getChildrenAsFlow(nodeId, role.key(tree)).map { wrap(it)!! }
203+
}
204+
205+
override fun getParentAsFlow(): Flow<INode> {
206+
return getTree().getParentAsFlow(nodeId).map { wrap(it)!! }
207+
}
208+
209+
override fun getPropertyValueAsFlow(role: IProperty): Flow<String?> {
210+
val tree = getTree()
211+
return tree.getPropertyValueAsFlow(nodeId, role.key(tree))
212+
}
213+
214+
override fun getReferenceTargetAsFlow(role: IReferenceLink): Flow<INode> {
215+
return getReferenceTargetRefAsFlow(role).mapNotNull { tryResolveNodeRef(it) }
216+
}
217+
218+
override fun getReferenceTargetRefAsFlow(role: IReferenceLink): Flow<INodeReference> {
219+
val tree = getTree()
220+
return tree.getReferenceTargetAsFlow(nodeId, role.key(tree))
221+
}
222+
163223
override fun equals(o: Any?): Boolean {
164224
if (this === o) {
165225
return true

0 commit comments

Comments
 (0)