Skip to content

Commit a312a4c

Browse files
authored
Merge pull request #643 from modelix/feature/modelix-832
MODELIX-832: MPSLanguageRepository returns "unknown" Concept for unknown concepts
2 parents 78c95b5 + 1c8c1e3 commit a312a4c

File tree

6 files changed

+105
-6
lines changed

6 files changed

+105
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ class ModelImporter(
252252
} else {
253253
nodeAtIndex
254254
}
255-
check(childNode.getConceptReference() == expectedConcept) { "Unexpected concept change" }
255+
check(childNode.getConceptReference() == expectedConcept) { "Unexpected concept change from $expectedConcept to ${childNode.getConceptReference()}" }
256256

257257
syncNode(childNode, expected, progressReporter)
258258
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ interface INode {
121121
}
122122

123123
fun addNewChildren(role: String?, index: Int, concepts: List<IConceptReference?>): List<INode> {
124-
return concepts.map { addNewChild(role, index, it) }
124+
return concepts.mapIndexed { i, it -> addNewChild(role, if (index >= 0) index + i else index, it) }
125+
}
126+
127+
fun addNewChildren(link: IChildLink, index: Int, concepts: List<IConceptReference?>): List<INode> {
128+
return concepts.mapIndexed { i, it -> addNewChild(link, if (index >= 0) index + i else index, it) }
125129
}
126130

127131
/**
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2024.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.modelix.model.mpsadapters
18+
19+
import org.modelix.model.api.BuiltinLanguages
20+
import org.modelix.model.api.ConceptReference
21+
import org.modelix.model.api.ILanguageRepository
22+
23+
class ConceptResolutionTest : MpsAdaptersTestBase("SimpleProject") {
24+
25+
fun `test resolution of Module concept`() {
26+
val conceptUID = BuiltinLanguages.MPSRepositoryConcepts.Module.getReference().getUID()
27+
val moduleConcept = ILanguageRepository.resolveConcept(ConceptReference(conceptUID))
28+
29+
// There was an issue where MPSLanguageRepository resolved a concept even though it wasn't loaded and no
30+
// metamodel information was available. If a concept can be resolved then it should provide more information
31+
// than just the ID that it extracted from the concept reference.
32+
assertEquals("Module", moduleConcept.getShortName())
33+
assertContainsElements(moduleConcept.getOwnChildLinks().map { it.getSimpleName() }, "models")
34+
}
35+
36+
fun `test MPSLanguageRepository cannot resolve the Module concept`() {
37+
val conceptUID = BuiltinLanguages.MPSRepositoryConcepts.Module.getReference().getUID()
38+
val moduleConcept = MPSLanguageRepository(mpsProject.repository).resolveConcept(conceptUID)
39+
40+
// After making the repository language part of this plugin, this assertion will fail and this test can just be removed.
41+
assertNull(moduleConcept)
42+
}
43+
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ data class MPSConcept(val concept: SAbstractConceptAdapter) : IConcept {
4242
is SInterfaceConceptAdapterById -> concept.id
4343
else -> error("Unknown concept type: $concept")
4444
}
45-
return "mps:" + id.serialize()
45+
return UID_PREFIX + id.serialize()
4646
}
4747

4848
override fun getShortName(): String {
@@ -118,4 +118,18 @@ data class MPSConcept(val concept: SAbstractConceptAdapter) : IConcept {
118118
else -> null
119119
}
120120
}
121+
122+
companion object {
123+
private const val UID_PREFIX = "mps:"
124+
125+
fun tryParseUID(uid: String): MPSConcept? {
126+
if (!uid.startsWith(UID_PREFIX)) return null
127+
val conceptId = SConceptId.deserialize(uid.substringAfter(UID_PREFIX))
128+
129+
// For interface concepts `SInterfaceConceptAdapterById(conceptId, "")` would be correct, but we don't have
130+
// that information. Assuming that this concept is used to create new nodes SConceptAdapterById is the
131+
// better default.
132+
return MPSConcept(SConceptAdapterById(conceptId, ""))
133+
}
134+
}
121135
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import jetbrains.mps.smodel.adapter.ids.SConceptId
2020
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory
2121
import jetbrains.mps.smodel.language.ConceptRegistry
2222
import jetbrains.mps.smodel.language.LanguageRegistry
23+
import jetbrains.mps.smodel.runtime.illegal.IllegalConceptDescriptor
2324
import org.jetbrains.mps.openapi.language.SAbstractConcept
2425
import org.jetbrains.mps.openapi.module.SRepository
2526
import org.modelix.model.api.IConcept
@@ -38,6 +39,8 @@ data class MPSLanguageRepository(private val repository: SRepository) : ILanguag
3839

3940
val conceptDescriptor = ConceptRegistry.getInstance().getConceptDescriptor(conceptId)
4041

42+
if (conceptDescriptor is IllegalConceptDescriptor) return null
43+
4144
return MPSConcept(MetaAdapterFactory.getAbstractConcept(conceptDescriptor))
4245
}
4346

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,41 @@ data class MPSNode(val node: SNode) : IDefaultNodeAdapter {
145145

146146
override fun addNewChild(role: IChildLink, index: Int, concept: IConceptReference?): INode {
147147
val repo = checkNotNull(node.model?.repository)
148-
val targetConcept = concept?.let { MPSLanguageRepository(repo).resolveConcept(it.getUID()) }
148+
val targetConcept = concept?.let {
149+
MPSLanguageRepository(repo).resolveConcept(it.getUID())
150+
?: MPSConcept.tryParseUID(it.getUID())
151+
}
152+
153+
// A null value for the concept would default to BaseConcept, but then BaseConcept should be used explicitly.
154+
checkNotNull(targetConcept) { "MPS concept not found: $concept" }
155+
149156
return addNewChild(role, index, targetConcept)
150157
}
151158

159+
override fun addNewChildren(role: String?, index: Int, concepts: List<IConceptReference?>): List<INode> {
160+
requireNotNull(role) { "containment link required" }
161+
val link = MPSChildLink(getMPSContainmentLink(role))
162+
return addNewChildren(link, index, concepts)
163+
}
164+
165+
override fun addNewChildren(link: IChildLink, index: Int, concepts: List<IConceptReference?>): List<INode> {
166+
return concepts.mapIndexed { i, it -> addNewChild(link, if (index >= 0) index + i else index, it) }
167+
}
168+
169+
@Deprecated("use IChildLink instead of String")
170+
override fun addNewChild(role: String?, index: Int, concept: IConcept?): INode {
171+
requireNotNull(role) { "containment link required" }
172+
val link = MPSChildLink(getMPSContainmentLink(role))
173+
return addNewChild(link, index, concept)
174+
}
175+
176+
@Deprecated("use IChildLink instead of String")
177+
override fun addNewChild(role: String?, index: Int, concept: IConceptReference?): INode {
178+
requireNotNull(role) { "containment link required" }
179+
val link = MPSChildLink(getMPSContainmentLink(role))
180+
return addNewChild(link, index, concept)
181+
}
182+
152183
override fun getReferenceTarget(link: IReferenceLink): INode? {
153184
if (link is MPSReferenceLink) {
154185
DependencyTracking.accessed(MPSReferenceDependency(node, link.link))
@@ -207,7 +238,11 @@ data class MPSNode(val node: SNode) : IDefaultNodeAdapter {
207238

208239
private fun getMPSContainmentLink(childLink: IChildLink): SContainmentLink = when (childLink) {
209240
is MPSChildLink -> childLink.link
210-
else -> node.concept.containmentLinks.find { MPSChildLink(it).getUID() == childLink.getUID() }
211-
?: SContainmentLinkAdapterById(SContainmentLinkId.deserialize(childLink.getUID()), "")
241+
else -> getMPSContainmentLink(childLink.getUID())
242+
}
243+
244+
private fun getMPSContainmentLink(uid: String): SContainmentLink {
245+
return node.concept.containmentLinks.find { MPSChildLink(it).getUID() == uid }
246+
?: SContainmentLinkAdapterById(SContainmentLinkId.deserialize(uid), "")
212247
}
213248
}

0 commit comments

Comments
 (0)