Skip to content

Commit e75b0a8

Browse files
committed
fix(mps-model-adapters): Module.allChildren didn't return all children
It only returned models, but not nodes in other roles.
1 parent 879132f commit e75b0a8

File tree

7 files changed

+137
-47
lines changed

7 files changed

+137
-47
lines changed

bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelImporter.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import org.modelix.model.data.ModelData
3232
import org.modelix.model.data.NodeData
3333
import kotlin.jvm.JvmName
3434

35+
private val LOG = mu.KotlinLogging.logger { }
36+
3537
/**
3638
* A ModelImporter updates an existing [INode] and its subtree based on a [ModelData] specification.
3739
*
@@ -136,7 +138,12 @@ class ModelImporter(
136138
nodesToRemove.forEach {
137139
doAndPotentiallyContinueOnErrors {
138140
if (it.isValid) { // if it's invalid then it's already removed
139-
it.remove()
141+
try {
142+
it.remove()
143+
} catch (ex: UnsupportedOperationException) {
144+
// it might be read-only
145+
LOG.warn(ex) { "Cannot delete node $it" }
146+
}
140147
}
141148
}
142149
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.modelix.model.mpsadapters
2+
3+
import org.modelix.model.api.INode
4+
5+
/*
6+
* Copyright (c) 2023.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
class AllChildrenActuallyReturnsAllChildrenTest : MpsAdaptersTestBase("SimpleProject") {
22+
23+
fun `test repository adapter consistency`() {
24+
readAction {
25+
checkAdapterConsistence(MPSRepositoryAsNode(mpsProject.repository))
26+
}
27+
}
28+
29+
fun `test module adapter consistency`() {
30+
readAction {
31+
for (module in mpsProject.repository.modules.map { MPSModuleAsNode(it) }) {
32+
checkAdapterConsistence(module)
33+
}
34+
}
35+
}
36+
37+
fun `test model adapter consistency`() {
38+
readAction {
39+
for (model in mpsProject.repository.modules.flatMap { it.models }.map { MPSModelAsNode(it) }) {
40+
checkAdapterConsistence(model)
41+
}
42+
}
43+
}
44+
45+
private fun checkAdapterConsistence(adapter: INode) {
46+
val concept = checkNotNull(adapter.concept)
47+
val expected = concept.getAllChildLinks().flatMap { adapter.getChildren(it) }.toSet()
48+
val actual = adapter.allChildren.toSet()
49+
assertEquals(expected, actual)
50+
}
51+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.modelix.model.mpsadapters
2+
3+
import junit.framework.TestCase
4+
import org.modelix.model.api.BuiltinLanguages
5+
import org.modelix.model.api.IChildLink
6+
import org.modelix.model.api.INode
7+
8+
/*
9+
* Copyright (c) 2023.
10+
*
11+
* Licensed under the Apache License, Version 2.0 (the "License");
12+
* you may not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing, software
18+
* distributed under the License is distributed on an "AS IS" BASIS,
19+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
* See the License for the specific language governing permissions and
21+
* limitations under the License.
22+
*/
23+
24+
class DescriptorModelIsFilteredTest : MpsAdaptersTestBase("SimpleProject") {
25+
26+
fun `test descriptor model is filtered by adapter`() {
27+
readAction {
28+
val module = checkNotNull(mpsProject.projectModules.find { it.moduleName == "Solution1" })
29+
assertEquals(2, module.models.count())
30+
assertEquals(1, module.models.count { it.name.stereotype == "descriptor" })
31+
32+
assertEquals(1, MPSModuleAsNode(module).getChildren(BuiltinLanguages.MPSRepositoryConcepts.Module.models).count())
33+
}
34+
}
35+
}

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

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ import org.modelix.model.area.IArea
2929

3030
data class MPSModelAsNode(val model: SModel) : IDefaultNodeAdapter {
3131

32+
private val childrenAccessors: Map<IChildLink, () -> Iterable<INode>> = mapOf(
33+
BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes to { model.rootNodes.map { MPSNode(it) } },
34+
BuiltinLanguages.MPSRepositoryConcepts.Model.modelImports to {
35+
ModelImports(model).importedModels.mapNotNull { modelRef ->
36+
val target = modelRef.resolve(model.repository)
37+
target?.let { MPSModelImportAsNode(it, model) }
38+
}
39+
},
40+
BuiltinLanguages.MPSRepositoryConcepts.Model.usedLanguages to { getImportedLanguagesAndDevKits() },
41+
)
42+
3243
override fun getArea(): IArea {
3344
return MPSArea(model.repository)
3445
}
@@ -41,14 +52,7 @@ data class MPSModelAsNode(val model: SModel) : IDefaultNodeAdapter {
4152
get() = MPSModuleAsNode(model.module)
4253

4354
override val allChildren: Iterable<INode>
44-
get() {
45-
val childLinks = listOf(
46-
BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes,
47-
BuiltinLanguages.MPSRepositoryConcepts.Model.modelImports,
48-
BuiltinLanguages.MPSRepositoryConcepts.Model.usedLanguages,
49-
)
50-
return childLinks.flatMap { getChildren(it) }
51-
}
55+
get() = childrenAccessors.values.flatMap { it() }
5256

5357
override fun removeChild(child: INode) {
5458
val link = child.getContainmentLink() ?: error("ContainmentLink not found for node $child")
@@ -71,20 +75,11 @@ data class MPSModelAsNode(val model: SModel) : IDefaultNodeAdapter {
7175
}
7276

7377
override fun getChildren(link: IChildLink): Iterable<INode> {
74-
return if (link is NullChildLink) {
75-
emptyList()
76-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes)) {
77-
model.rootNodes.map { MPSNode(it) }
78-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Model.modelImports)) {
79-
ModelImports(model).importedModels.mapNotNull { modelRef ->
80-
val target = modelRef.resolve(model.repository)
81-
target?.let { MPSModelImportAsNode(it, model) }
82-
}
83-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Model.usedLanguages)) {
84-
getImportedLanguagesAndDevKits()
85-
} else {
86-
emptyList()
78+
if (link is NullChildLink) return emptyList()
79+
for (childrenAccessor in childrenAccessors) {
80+
if (link.conformsTo(childrenAccessor.key)) return childrenAccessor.value()
8781
}
82+
return emptyList()
8883
}
8984

9085
private fun getImportedLanguagesAndDevKits(): List<INode> {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import org.modelix.model.api.IProperty
2626
import org.modelix.model.api.IReferenceLink
2727
import org.modelix.model.area.IArea
2828

29-
class MPSModelImportAsNode(val importedModel: SModel, val importingModel: SModel) : IDefaultNodeAdapter {
29+
data class MPSModelImportAsNode(val importedModel: SModel, val importingModel: SModel) : IDefaultNodeAdapter {
3030
override fun getArea(): IArea =
3131
MPSArea(importingModel.repository)
3232

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ data class MPSModuleAsNode(val module: SModule) : IDefaultNodeAdapter {
3737
private val logger = mu.KotlinLogging.logger { }
3838
}
3939

40+
private val childrenAccessors: Map<IChildLink, () -> Iterable<INode>> = mapOf(
41+
BuiltinLanguages.MPSRepositoryConcepts.Module.models to { module.models.map { MPSModelAsNode(it) } },
42+
BuiltinLanguages.MPSRepositoryConcepts.Module.facets to { module.facets.filterIsInstance<JavaModuleFacet>().map { MPSJavaModuleFacetAsNode(it) } },
43+
BuiltinLanguages.MPSRepositoryConcepts.Module.dependencies to { getDependencies() },
44+
BuiltinLanguages.MPSRepositoryConcepts.Module.languageDependencies to { getLanguageDependencies() },
45+
)
46+
4047
override fun getArea(): IArea {
4148
return MPSArea(module.repository ?: MPSModuleRepository.getInstance())
4249
}
@@ -49,24 +56,17 @@ data class MPSModuleAsNode(val module: SModule) : IDefaultNodeAdapter {
4956
get() = module.repository?.let { MPSRepositoryAsNode(it) }
5057

5158
override val allChildren: Iterable<INode>
52-
get() = module.models.map { MPSModelAsNode(it) }
59+
get() = childrenAccessors.values.flatMap { it() }
5360

5461
override fun getContainmentLink(): IChildLink {
5562
return BuiltinLanguages.MPSRepositoryConcepts.Repository.modules
5663
}
5764

5865
override fun getChildren(link: IChildLink): Iterable<INode> {
59-
return if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Module.models)) {
60-
module.models.map { MPSModelAsNode(it) }
61-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Module.facets)) {
62-
module.facets.filterIsInstance<JavaModuleFacet>().map { MPSJavaModuleFacetAsNode(it) }
63-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Module.dependencies)) {
64-
getDependencies()
65-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Module.languageDependencies)) {
66-
getLanguageDependencies()
67-
} else {
68-
emptyList()
66+
for (childrenAccessor in childrenAccessors) {
67+
if (link.conformsTo(childrenAccessor.key)) return childrenAccessor.value()
6968
}
69+
return emptyList()
7070
}
7171

7272
private fun getDependencies(): Iterable<INode> {

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ import org.modelix.model.area.IArea
2929

3030
data class MPSRepositoryAsNode(val repository: SRepository) : IDefaultNodeAdapter {
3131

32+
private val childrenAccessors: Map<IChildLink, () -> Iterable<INode>> = mapOf(
33+
BuiltinLanguages.MPSRepositoryConcepts.Repository.modules to { repository.modules.filter { !it.isTempModule() }.map { MPSModuleAsNode(it) } },
34+
BuiltinLanguages.MPSRepositoryConcepts.Repository.projects to {
35+
ProjectManager.getInstance().openedProjects
36+
.filterIsInstance<ProjectBase>()
37+
.map { MPSProjectAsNode(it) }
38+
},
39+
BuiltinLanguages.MPSRepositoryConcepts.Repository.tempModules to { repository.modules.filter { it.isTempModule() }.map { MPSModuleAsNode(it) } },
40+
)
41+
3242
override fun getArea(): IArea {
3343
return MPSArea(repository)
3444
}
@@ -41,26 +51,18 @@ data class MPSRepositoryAsNode(val repository: SRepository) : IDefaultNodeAdapte
4151
get() = null
4252

4353
override val allChildren: Iterable<INode>
44-
get() = repository.modules.map { MPSModuleAsNode(it) }
54+
get() = childrenAccessors.values.flatMap { it() }
4555

4656
override fun getContainmentLink(): IChildLink? {
4757
return null
4858
}
4959

5060
override fun getChildren(link: IChildLink): Iterable<INode> {
51-
return if (link is NullChildLink) {
52-
return emptyList()
53-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules)) {
54-
repository.modules.filter { !it.isTempModule() }.map { MPSModuleAsNode(it) }
55-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Repository.projects)) {
56-
ProjectManager.getInstance().openedProjects
57-
.filterIsInstance<ProjectBase>()
58-
.map { MPSProjectAsNode(it) }
59-
} else if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Repository.tempModules)) {
60-
repository.modules.filter { it.isTempModule() }.map { MPSModuleAsNode(it) }
61-
} else {
62-
emptyList()
61+
if (link is NullChildLink) return emptyList()
62+
for (childrenAccessor in childrenAccessors) {
63+
if (link.conformsTo(childrenAccessor.key)) return childrenAccessor.value()
6364
}
65+
return emptyList()
6466
}
6567
}
6668

0 commit comments

Comments
 (0)