Skip to content

Commit 940c553

Browse files
author
Oleksandr Dzhychko
committed
fix(mps-model-adapters): fix MPSNode.replaceNode
* correctly parse concept UID * check preconditions before making changes * remove child from parent before moving to new parent * correctly call `SNode.insertChildBefore`
1 parent f6a2830 commit 940c553

File tree

2 files changed

+63
-32
lines changed
  • mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters
  • mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters

2 files changed

+63
-32
lines changed
Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,71 @@
11
package org.modelix.model.mpsadapters
22

3-
import org.junit.Ignore
43
import org.modelix.model.api.BuiltinLanguages
54
import org.modelix.model.api.ConceptReference
6-
import org.modelix.model.api.INode
75
import org.modelix.model.api.IReplaceableNode
86

9-
@Ignore("Replacing a node through MPS-model-adapters is broken. See MODELIX-920")
107
class ReplaceNodeTest : MpsAdaptersTestBase("SimpleProject") {
118

12-
fun testReplaceNode() {
13-
readAction {
14-
assertEquals(1, mpsProject.projectModules.size)
15-
}
9+
fun `test replace node`() = runCommandOnEDT {
10+
val rootNode = getRootUnderTest()
11+
val nodeToReplace = rootNode.allChildren.first() as IReplaceableNode
12+
val oldProperties = nodeToReplace.getAllProperties().toSet()
13+
check(oldProperties.isNotEmpty()) { "Test should replace node with properties." }
14+
val oldReferences = nodeToReplace.getAllReferenceTargetRefs().toSet()
15+
check(oldReferences.isNotEmpty()) { "Test should replace node with references." }
16+
val oldChildren = nodeToReplace.allChildren.toList()
17+
check(oldChildren.isNotEmpty()) { "Test should replace node with children." }
18+
val newConcept = ConceptReference("mps:f3061a53-9226-4cc5-a443-f952ceaf5816/1083245097125")
19+
20+
val newNode = nodeToReplace.replaceNode(newConcept)
1621

17-
val repositoryNode: INode = MPSRepositoryAsNode(mpsProject.repository)
22+
assertEquals(oldProperties, newNode.getAllProperties().toSet())
23+
assertEquals(oldReferences, newNode.getAllReferenceTargetRefs().toSet())
24+
assertEquals(oldChildren, newNode.allChildren.toList())
25+
assertEquals(newConcept, newNode.getConceptReference())
26+
}
1827

19-
runCommandOnEDT {
20-
val module = repositoryNode.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules)
21-
.single { it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) == "Solution1" }
22-
val model = module.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Module.models)
23-
.single { it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) == "Solution1.model1" }
28+
fun `test fail to replace node without parent`() = runCommandOnEDT {
29+
val rootNode = getRootUnderTest()
30+
val oldChildren = rootNode.allChildren.toList()
31+
check(oldChildren.isNotEmpty()) { "Test should replace node with children." }
2432

25-
val rootNode = model.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes).single() as IReplaceableNode
33+
val newConcept = ConceptReference(BuiltinLanguages.jetbrains_mps_lang_core.BaseConcept.getUID())
2634

27-
val oldProperties = rootNode.getAllProperties().toSet()
28-
check(oldProperties.isNotEmpty()) { "Test should replace node with properties." }
29-
val oldReferences = rootNode.getAllReferenceTargetRefs().toSet()
30-
check(oldReferences.isNotEmpty()) { "Test should replace node with references." }
31-
val oldChildren = rootNode.allChildren.toList()
32-
check(oldChildren.isNotEmpty()) { "Test should replace node with children." }
35+
val expectedMessage = "Cannot replace node `Class1` because it has no parent."
36+
assertThrows(IllegalArgumentException::class.java, expectedMessage) {
37+
rootNode.replaceNode(newConcept)
38+
}
39+
// Assert that precondition is check before children are deleted.
40+
assertEquals(oldChildren, rootNode.allChildren.toList())
41+
}
3342

34-
val newConcept = ConceptReference("mps:f3061a53-9226-4cc5-a443-f952ceaf5816/1083245097125")
35-
val newNode = rootNode.replaceNode(newConcept)
43+
fun `test fail to replace node with null concept`() = runCommandOnEDT {
44+
val rootNode = getRootUnderTest()
45+
val nodeToReplace = rootNode.allChildren.first() as IReplaceableNode
3646

37-
assertEquals(oldProperties, newNode.getAllProperties().toSet())
38-
assertEquals(oldReferences, newNode.getAllReferenceTargetRefs().toSet())
39-
assertEquals(oldChildren, newNode.allChildren.toList())
40-
assertEquals(newConcept, newNode.getConceptReference())
47+
val expectedMessage = "Cannot replace node `method1` with a null concept. Explicitly specify a concept (e.g., `BaseConcept`)."
48+
assertThrows(IllegalArgumentException::class.java, expectedMessage) {
49+
nodeToReplace.replaceNode(null)
4150
}
4251
}
52+
53+
fun `test fail to replace node with non mps concept`() = runCommandOnEDT {
54+
val rootNode = getRootUnderTest()
55+
val nodeToReplace = rootNode.allChildren.first() as IReplaceableNode
56+
val newConcept = ConceptReference("notMpsConcept")
57+
58+
val expectedMessage = "Concept UID `notMpsConcept` cannot be parsed as MPS concept."
59+
assertThrows(IllegalArgumentException::class.java, expectedMessage) {
60+
nodeToReplace.replaceNode(newConcept)
61+
}
62+
}
63+
64+
private fun getRootUnderTest(): IReplaceableNode {
65+
val repositoryNode = MPSRepositoryAsNode(mpsProject.repository)
66+
val module = repositoryNode.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules)
67+
.single { it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) == "Solution1" }
68+
val model = module.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Module.models).single()
69+
return model.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes).single() as IReplaceableNode
70+
}
4371
}

mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSNode.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ package org.modelix.model.mpsadapters
33
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations
44
import jetbrains.mps.smodel.MPSModuleRepository
55
import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration
6-
import jetbrains.mps.smodel.adapter.ids.SConceptId
76
import jetbrains.mps.smodel.adapter.ids.SContainmentLinkId
87
import jetbrains.mps.smodel.adapter.ids.SPropertyId
98
import jetbrains.mps.smodel.adapter.ids.SReferenceLinkId
10-
import jetbrains.mps.smodel.adapter.structure.concept.SConceptAdapterById
119
import jetbrains.mps.smodel.adapter.structure.link.SContainmentLinkAdapterById
1210
import jetbrains.mps.smodel.adapter.structure.property.SPropertyAdapterById
1311
import jetbrains.mps.smodel.adapter.structure.ref.SReferenceLinkAdapterById
@@ -62,20 +60,25 @@ data class MPSNode(val node: SNode) : IDefaultNodeAdapter, IReplaceableNode {
6260
}
6361

6462
override fun replaceNode(concept: ConceptReference?): INode {
65-
requireNotNull(concept) { "Can't replace $node with null concept. Use BaseConcept explicitly." }
63+
requireNotNull(concept) { "Cannot replace node `$node` with a null concept. Explicitly specify a concept (e.g., `BaseConcept`)." }
64+
val parent = requireNotNull(node.parent) { "Cannot replace node `$node` because it has no parent." }
65+
66+
val mpsConcept = MPSConcept.tryParseUID(concept.uid)
67+
requireNotNull(mpsConcept) { "Concept UID `${concept.uid}` cannot be parsed as MPS concept." }
68+
val sConcept = MetaAdapterByDeclaration.asInstanceConcept(mpsConcept.concept)
6669

6770
val id = node.nodeId
6871
val model = checkNotNull(node.model) { "Node is not part of a model" }
69-
val newNode = model.createNode(SConceptAdapterById(SConceptId.deserialize(concept.uid), ""), id)
72+
val newNode = model.createNode(sConcept, id)
7073
node.properties.forEach { newNode.setProperty(it, node.getProperty(it)) }
7174
node.references.forEach { newNode.setReference(it.link, it.targetNodeReference) }
7275
node.children.forEach { child ->
7376
val link = checkNotNull(child.containmentLink) { "Containment link of child node not found" }
77+
node.removeChild(child)
7478
newNode.addChild(link, child)
7579
}
7680

77-
val parent = checkNotNull(node.parent) { "Cannot replace node without a parent" }
78-
parent.insertChildBefore(getMPSContainmentLink(getContainmentLink()), node, newNode)
81+
parent.insertChildBefore(getMPSContainmentLink(getContainmentLink()), newNode, node)
7982
node.delete()
8083
return MPSNode(newNode)
8184
}

0 commit comments

Comments
 (0)