Skip to content

Commit

Permalink
fix(bulk-model-sync): use production implementation of INodeAssociati…
Browse files Browse the repository at this point in the history
…on in tests
  • Loading branch information
slisson committed Jan 17, 2025
1 parent 9e305a3 commit 56a7eea
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 120 deletions.
1 change: 1 addition & 0 deletions bulk-model-sync-lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(project(":model-api"))
implementation(project(":model-datastructure"))
implementation(libs.kotlin.serialization.json)
implementation(libs.kotlin.logging)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ package org.modelix.model.sync.bulk

import org.modelix.model.api.IReadableNode
import org.modelix.model.api.IWritableNode
import org.modelix.model.api.NodeReference
import org.modelix.model.api.getDescendants
import org.modelix.model.api.getOriginalOrCurrentReference
import org.modelix.model.area.IArea
import org.modelix.model.data.NodeData
import org.modelix.model.data.NodeDataAsNode

/**
* A node association is responsible for storing the mapping between a source node and the imported target node.
Expand All @@ -21,36 +16,3 @@ interface INodeAssociation {
resolveTarget(sourceNode) == targetNode
}
}

class TransientNodeAssociation(val writeOriginalIds: Boolean, val targetModel: IArea) : INodeAssociation {
private val associations: MutableMap<String, IWritableNode> by lazy {
HashMap<String, IWritableNode>().also { map ->
if (true) {
targetModel.getRoot().getDescendants(true).forEach { node ->
node.getOriginalReference()?.let { ref ->
map[ref] = node.asWritableNode()
}
}
}
}
}
override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
val ref = sourceNode.getOriginalOrCurrentReference()
return associations[ref]
?: targetModel.resolveNode(NodeReference(ref))?.asWritableNode()
}

override fun associate(
sourceNode: IReadableNode,
targetNode: IWritableNode,
) {
associations[sourceNode.getOriginalOrCurrentReference()] = targetNode
if (writeOriginalIds) {
if ((sourceNode as NodeDataAsNode).data.id == null) return
val expected = sourceNode.getOriginalOrCurrentReference()
if (targetNode.getOriginalOrCurrentReference() != expected) {
targetNode.setPropertyValue(NodeData.ID_PROPERTY_REF, expected)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ModelImporter(
private val continueOnError: Boolean,
private val childFilter: (INode) -> Boolean = { true },
private val writeOriginalIds: Boolean = root is PNodeAdapter,
private val nodeAssociation: INodeAssociation = TransientNodeAssociation(writeOriginalIds, root.getArea()),
private val nodeAssociation: INodeAssociation = TransientNodeAssociation(writeOriginalIds, root.getArea().asModel()),
) {
// For MPS / Java compatibility, where a default value does not work. Can be dropped once the MPS solution is
// updated to the constructor with two arguments.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.modelix.model.sync.bulk

import org.modelix.model.ModelIndex
import org.modelix.model.api.IBranch
import org.modelix.model.api.IMutableModel
import org.modelix.model.api.IReadableNode
import org.modelix.model.api.IWritableNode
import org.modelix.model.api.NodeReference
import org.modelix.model.api.PNodeAdapter
import org.modelix.model.api.getOriginalOrCurrentReference
import org.modelix.model.api.getOriginalReference
import org.modelix.model.area.PArea
import org.modelix.model.data.NodeData

class NodeAssociationToModelServer(val branch: IBranch) : INodeAssociation {

private val modelIndex
get() = ModelIndex.get(branch.transaction, NodeData.Companion.ID_PROPERTY_KEY)

override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
val ref = sourceNode.getOriginalOrCurrentReference()
return modelIndex.find(ref).map { PNodeAdapter(it, branch) }.firstOrNull()?.asWritableNode()
?: PArea(branch).resolveNode(NodeReference(ref))?.asWritableNode()
}

override fun associate(sourceNode: IReadableNode, targetNode: IWritableNode) {
val expected = sourceNode.getOriginalOrCurrentReference()
if (expected != targetNode.getOriginalOrCurrentReference()) {
targetNode.setPropertyValue(NodeData.ID_PROPERTY_REF, expected)
}
}
}

class NodeAssociationFromModelServer(val branch: IBranch, val targetModel: IMutableModel) : INodeAssociation {
private val pendingAssociations = HashMap<Long, String>()

private fun nodeId(sourceNode: IReadableNode): Long {
val pnode = sourceNode.asLegacyNode() as PNodeAdapter
require(pnode.branch == branch) {
"Node is from a different branch. [node = $sourceNode, expected: $branch, actual: ${pnode.branch}]"
}
return pnode.nodeId
}

override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
return (pendingAssociations[nodeId(sourceNode)] ?: sourceNode.getOriginalReference())
?.let { targetModel.resolveNode(NodeReference(it)) }
}

override fun associate(sourceNode: IReadableNode, targetNode: IWritableNode) {
val id = nodeId(sourceNode)
val expected = sourceNode.getOriginalOrCurrentReference()
if (expected != targetNode.getOriginalOrCurrentReference()) {
pendingAssociations[id] = targetNode.getNodeReference().serialize()
}
}

fun writeAssociations() {
for (entry in pendingAssociations) {
branch.writeTransaction.setProperty(entry.key, NodeData.ID_PROPERTY_KEY, entry.value)
}
pendingAssociations.clear()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.modelix.model.sync.bulk

import org.modelix.model.api.IMutableModel
import org.modelix.model.api.IReadableNode
import org.modelix.model.api.IWritableNode
import org.modelix.model.api.NodeReference
import org.modelix.model.api.getDescendants
import org.modelix.model.api.getOriginalOrCurrentReference
import org.modelix.model.api.getOriginalReference
import org.modelix.model.data.NodeData
import org.modelix.model.data.NodeDataAsNode

/**
* Maintains the association in-memory.
* This is the default implementation if the target model doesn't allow any optimizations.
*/
class TransientNodeAssociation(val writeOriginalIds: Boolean, val targetModel: IMutableModel) : INodeAssociation {
private val associations: MutableMap<String, IWritableNode> by lazy {
HashMap<String, IWritableNode>().also { map ->
if (writeOriginalIds) {
targetModel.getRootNode().getDescendants(true).forEach { node ->
node.getOriginalReference()?.let { ref ->
map[ref] = node
}
}
}
}
}

override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
val ref = sourceNode.getOriginalOrCurrentReference()
return associations[ref]
?: targetModel.resolveNode(NodeReference(ref))
}

override fun associate(
sourceNode: IReadableNode,
targetNode: IWritableNode,
) {
associations[sourceNode.getOriginalOrCurrentReference()] = targetNode
if (writeOriginalIds) {
if ((sourceNode as NodeDataAsNode).data.id == null) return
val expected = sourceNode.getOriginalOrCurrentReference()
if (targetNode.getOriginalOrCurrentReference() != expected) {
targetNode.setPropertyValue(NodeData.Companion.ID_PROPERTY_REF, expected)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import org.modelix.model.ModelFacade
import org.modelix.model.api.IBranch
import org.modelix.model.api.IChildLinkReference
import org.modelix.model.api.IReadableNode
import org.modelix.model.api.IWritableNode
import org.modelix.model.api.PBranch
import org.modelix.model.api.PNodeAdapter
import org.modelix.model.api.getDescendants
import org.modelix.model.api.getOriginalOrCurrentReference
import org.modelix.model.api.getRootNode
import org.modelix.model.api.meta.NullConcept
import org.modelix.model.client.IdGenerator
Expand Down Expand Up @@ -100,7 +97,7 @@ open class ModelSynchronizerTest : AbstractModelSyncTest() {
filter = BasicFilter,
sourceRoot = sourceRoot.asReadableNode(),
targetRoot = targetRoot.asWritableNode(),
nodeAssociation = BasicAssociation(targetBranch),
nodeAssociation = NodeAssociationToModelServer(targetBranch),
)
synchronizer.synchronize()
}
Expand Down Expand Up @@ -149,7 +146,7 @@ open class ModelSynchronizerTest : AbstractModelSyncTest() {
filter = BasicFilter,
sourceRoot = sourceBranch.getRootNode().asReadableNode(),
targetRoot = targetBranch.getRootNode().asWritableNode(),
nodeAssociation = BasicAssociation(targetBranch),
nodeAssociation = NodeAssociationToModelServer(targetBranch),
)
}
val operations = otBranch.getPendingChanges().first
Expand All @@ -172,25 +169,6 @@ open class ModelSynchronizerTest : AbstractModelSyncTest() {
return true
}
}

class BasicAssociation(private val target: IBranch) : INodeAssociation {

override fun resolveTarget(sourceNode: IReadableNode): IWritableNode? {
val sourceNode = sourceNode.asLegacyNode()
require(sourceNode is PNodeAdapter)
return target.computeRead {
target.getRootNode().getDescendants(true)
.find { sourceNode.getOriginalOrCurrentReference() == it.getOriginalOrCurrentReference() }
}?.asWritableNode()
}

override fun associate(sourceNode: IReadableNode, targetNode: IWritableNode) {
val expected = sourceNode.getOriginalOrCurrentReference()
if (expected != targetNode.getOriginalOrCurrentReference()) {
targetNode.setPropertyValue(NodeData.ID_PROPERTY_REF, expected)
}
}
}
}

private fun IBranch.toOTBranch(): OTBranch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ModelSynchronizerWithInvalidationTreeTest : ModelSynchronizerTest() {
filter = invalidationTree,
sourceRoot = sourceRoot.asReadableNode(),
targetRoot = targetRoot.asWritableNode(),
nodeAssociation = BasicAssociation(targetBranch),
nodeAssociation = NodeAssociationToModelServer(targetBranch),
)
synchronizer.synchronize()
}
Expand Down Expand Up @@ -89,7 +89,7 @@ class ModelSynchronizerWithInvalidationTreeTest : ModelSynchronizerTest() {
filter = invalidationTree,
sourceRoot = sourceBranch.getRootNode().asReadableNode(),
targetRoot = targetBranch.getRootNode().asWritableNode(),
nodeAssociation = BasicAssociation(targetBranch),
nodeAssociation = NodeAssociationToModelServer(targetBranch),
)
}
val operations = otBranch.getPendingChanges().first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.modelix.model.sync.bulk.InvalidationTree
import org.modelix.model.sync.bulk.ModelExporter
import org.modelix.model.sync.bulk.ModelImporter
import org.modelix.model.sync.bulk.ModelSynchronizer
import org.modelix.model.sync.bulk.NodeAssociationFromModelServer
import org.modelix.model.sync.bulk.isModuleIncluded
import java.io.File
import java.nio.file.Path
Expand Down Expand Up @@ -284,11 +285,14 @@ object MPSBulkSynchronizer {
CompositeFilter(listOf(invalidationTree, includedModulesFilter)),
treePointer.getRootNode().asReadableNode(),
MPSRepositoryAsNode(repository).asWritableNode(),
NodeAssociationToMps(MPSArea(repository)),
NodeAssociationFromModelServer(treePointer, MPSArea(repository).asModel()),
)
synchronizer.synchronize()
}
}

// TODO call NodeAssociationFromModelServer.writeAssociations and write the changes to the model server

println("Import finished.")
persistChanges(repository)
}
Expand Down

This file was deleted.

16 changes: 16 additions & 0 deletions model-api/src/commonMain/kotlin/org/modelix/model/api/NodeUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ fun INode.getDescendants(includeSelf: Boolean): Sequence<INode> {
}
}

fun IReadableNode.getDescendants(includeSelf: Boolean): Sequence<IReadableNode> {
return if (includeSelf) {
(sequenceOf(this) + this.getDescendants(false))
} else {
this.getAllChildren().asSequence().flatMap { it.getDescendants(true) }
}
}

fun IWritableNode.getDescendants(includeSelf: Boolean): Sequence<IWritableNode> {
return if (includeSelf) {
(sequenceOf(this) + this.getDescendants(false))
} else {
this.getAllChildren().asSequence().flatMap { it.getDescendants(true) }
}
}

fun INode?.getAncestor(concept: IConcept?, includeSelf: Boolean): INode? {
if (this == null) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class ModelIndex private constructor(val tree: ITree, val propertyRole: String)
companion object {
fun fromTree(tree: ITree, propertyRole: String): ModelIndex {
val index = ModelIndex(tree, propertyRole)
index.loadAll(ITree.ROOT_ID)
index.loadAll(ITree.Companion.ROOT_ID)
return index
}

Expand Down

0 comments on commit 56a7eea

Please sign in to comment.