Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(bulk-model-sync): use production implementation of INodeAssociati…
Browse files Browse the repository at this point in the history
…on in tests
slisson committed Jan 17, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 9e305a3 commit 56a7eea
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
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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.
@@ -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
@@ -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.
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
@@ -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
@@ -100,7 +97,7 @@ open class ModelSynchronizerTest : AbstractModelSyncTest() {
filter = BasicFilter,
sourceRoot = sourceRoot.asReadableNode(),
targetRoot = targetRoot.asWritableNode(),
nodeAssociation = BasicAssociation(targetBranch),
nodeAssociation = NodeAssociationToModelServer(targetBranch),
)
synchronizer.synchronize()
}
@@ -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
@@ -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 {
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ class ModelSynchronizerWithInvalidationTreeTest : ModelSynchronizerTest() {
filter = invalidationTree,
sourceRoot = sourceRoot.asReadableNode(),
targetRoot = targetRoot.asWritableNode(),
nodeAssociation = BasicAssociation(targetBranch),
nodeAssociation = NodeAssociationToModelServer(targetBranch),
)
synchronizer.synchronize()
}
@@ -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
Original file line number Diff line number Diff line change
@@ -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
@@ -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)
}

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
@@ -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
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 56a7eea

Please sign in to comment.