Skip to content

Commit 2d686be

Browse files
authored
Merge pull request #553 from modelix/issues/MODELIX-789
MODELIX-789 bulk-model-sync from model-server to MPS results in many renamed files without content changes
2 parents dc5c5c1 + 6abc939 commit 2d686be

File tree

18 files changed

+88
-41
lines changed

18 files changed

+88
-41
lines changed

bulk-model-sync-gradle-test/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ modelSync {
8787
direction("testPush") {
8888
includeModulesByPrefix("GraphSolution")
8989
fromLocal {
90-
mpsHeapSize = "2g"
90+
mpsHeapSize = "4g"
9191
repositoryDir = repoDir
9292
}
9393
toModelServer {
@@ -107,6 +107,7 @@ modelSync {
107107
}
108108
toLocal {
109109
repositoryDir = repoDir
110+
mpsHeapSize = "4g"
110111
}
111112
}
112113
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ class ModelImporter(
265265
}
266266

267267
private fun syncProperties(node: INode, nodeData: NodeData) {
268-
if (node.getPropertyValue(NodeData.idPropertyKey) == null) {
268+
if (node.originalId() == null) {
269269
node.setPropertyValue(NodeData.idPropertyKey, nodeData.originalId())
270270
}
271271

@@ -285,6 +285,7 @@ class ModelImporter(
285285
nodeData.references.forEach {
286286
val expectedTargetId = it.value
287287
val actualTargetId = node.getReferenceTarget(it.key)?.originalId()
288+
?: node.getReferenceTargetRef(it.key)?.serialize()
288289
if (actualTargetId != expectedTargetId) {
289290
val expectedTarget = originalIdToExisting[expectedTargetId]
290291
if (expectedTarget == null) {
@@ -296,18 +297,17 @@ class ModelImporter(
296297
}
297298
val toBeRemoved = node.getReferenceRoles().toSet() - nodeData.references.keys
298299
toBeRemoved.forEach {
299-
val nullReference: INodeReference? = null
300-
node.setReferenceTarget(it, nullReference)
300+
node.setReferenceTarget(it, null as INodeReference?)
301301
}
302302
}
303303
}
304304

305305
internal fun INode.originalId(): String? {
306-
return this.getPropertyValue(NodeData.idPropertyKey)
306+
return this.getOriginalReference()
307307
}
308308

309309
internal fun NodeData.originalId(): String? {
310-
return properties[NodeData.idPropertyKey] ?: id
310+
return properties[NodeData.ID_PROPERTY_KEY] ?: id
311311
}
312312

313313
data class ExistingAndExpectedNode(

bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSBulkSynchronizer.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,18 @@ package org.modelix.mps.model.sync.bulk
1919
import com.intellij.openapi.application.ApplicationManager
2020
import com.intellij.openapi.project.ProjectManager
2121
import jetbrains.mps.ide.project.ProjectHelper
22+
import jetbrains.mps.smodel.SNodeUtil
23+
import jetbrains.mps.smodel.adapter.ids.MetaIdHelper
24+
import jetbrains.mps.smodel.adapter.ids.SConceptId
25+
import jetbrains.mps.smodel.adapter.structure.concept.SConceptAdapterById
26+
import jetbrains.mps.smodel.language.ConceptRegistry
27+
import jetbrains.mps.smodel.language.StructureRegistry
28+
import jetbrains.mps.smodel.runtime.ConceptDescriptor
29+
import jetbrains.mps.smodel.runtime.illegal.IllegalConceptDescriptor
2230
import kotlinx.serialization.ExperimentalSerializationApi
2331
import kotlinx.serialization.json.Json
2432
import kotlinx.serialization.json.decodeFromStream
33+
import org.jetbrains.mps.openapi.model.EditableSModel
2534
import org.jetbrains.mps.openapi.module.SModule
2635
import org.jetbrains.mps.openapi.module.SRepository
2736
import org.modelix.model.data.ModelData
@@ -125,12 +134,60 @@ object MPSBulkSynchronizer {
125134
ApplicationManager.getApplication().invokeAndWait {
126135
println("Persisting changes...")
127136
repository.modelAccess.runWriteAction {
137+
enableWorkaroundForFilePerRootPersistence(repository)
128138
repository.saveAll()
129139
}
130140
println("Changes persisted.")
131141
}
132142
}
133143

144+
/**
145+
* Workaround for MPS not being able to read the name property of the node during the save process
146+
* in case FilePerRootPersistence is used.
147+
* This is because the concept is not properly loaded and in the MPS code it checks if the concept is a subconcept
148+
* of INamedConcept.
149+
* Without this workaround the id of the root node will be used instead of the name, resulting in renamed files.
150+
*/
151+
@JvmStatic
152+
private fun enableWorkaroundForFilePerRootPersistence(repository: SRepository) {
153+
val structureRegistry: StructureRegistry = ConceptRegistry.getInstance().readField("myStructureRegistry")
154+
val myConceptDescriptorsById: MutableMap<SConceptId, ConceptDescriptor> = structureRegistry.readField("myConceptDescriptorsById")
155+
156+
repository.modules
157+
.asSequence()
158+
.flatMap { it.models }
159+
.mapNotNull { it as? EditableSModel }
160+
.filter { it.isChanged }
161+
.flatMap { it.rootNodes }
162+
.mapNotNull { (it.concept as? SConceptAdapterById) }
163+
.forEach {
164+
myConceptDescriptorsById.putIfAbsent(it.id, DummyNamedConceptDescriptor(it))
165+
}
166+
}
167+
168+
@Suppress("UNCHECKED_CAST")
169+
private fun <R> Any.readField(name: String): R {
170+
return this::class.java.getDeclaredField(name).also { it.isAccessible = true }.get(this) as R
171+
}
172+
173+
private class DummyNamedConceptDescriptor(concept: SConceptAdapterById) : ConceptDescriptor by IllegalConceptDescriptor(concept.id, concept.qualifiedName) {
174+
override fun isAssignableTo(other: SConceptId?): Boolean {
175+
return MetaIdHelper.getConcept(SNodeUtil.concept_INamedConcept) == other
176+
}
177+
178+
override fun getSuperConceptId(): SConceptId {
179+
return MetaIdHelper.getConcept(SNodeUtil.concept_BaseConcept)
180+
}
181+
182+
override fun getAncestorsIds(): MutableSet<SConceptId> {
183+
return mutableSetOf(MetaIdHelper.getConcept(SNodeUtil.concept_INamedConcept))
184+
}
185+
186+
override fun getParentsIds(): MutableList<SConceptId> {
187+
return mutableListOf(MetaIdHelper.getConcept(SNodeUtil.concept_INamedConcept))
188+
}
189+
}
190+
134191
@JvmStatic
135192
private fun parseRawPropertySet(rawProperty: String): Set<String> {
136193
return if (rawProperty.isEmpty()) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.flatMapConcat
2222
import kotlinx.coroutines.flow.flattenConcat
2323
import kotlinx.coroutines.flow.flowOf
2424
import org.modelix.model.area.IArea
25+
import org.modelix.model.data.NodeData
2526

2627
/**
2728
* Representation of a model element.
@@ -206,6 +207,12 @@ interface INode {
206207
@Deprecated("use getReferenceLinks()")
207208
fun getReferenceRoles(): List<String>
208209

210+
/**
211+
* @return the serialized reference of the source node, if this one was created during an import
212+
*/
213+
fun getOriginalReference(): String? = getPropertyValue(IProperty.fromName(NodeData.ID_PROPERTY_KEY))
214+
?: getPropertyValue(IProperty.fromName("#mpsNodeID#")) // for backwards compatibility
215+
209216
// <editor-fold desc="non-string based API">
210217
fun usesRoleIds(): Boolean = false
211218
fun getContainmentLink(): IChildLink? = roleInParent?.let { role ->

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ package org.modelix.model.api
1515

1616
import kotlin.jvm.JvmOverloads
1717

18-
class SimpleProperty
18+
data class SimpleProperty
1919
@JvmOverloads constructor(
2020
private val simpleName: String,
2121
override val isOptional: Boolean = true,

model-api/src/commonMain/kotlin/org/modelix/model/data/ModelData.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ data class NodeData(
8686
val references: Map<String, String> = emptyMap(),
8787
) {
8888
companion object {
89-
const val ID_PROPERTY_KEY = "#mpsNodeId#"
89+
90+
/**
91+
* Users should not use this directly. Use [INode.getOriginalReference].
92+
*/
93+
const val ID_PROPERTY_KEY = "#originalRef#"
9094

9195
@Deprecated("Use ID_PROPERTY_KEY", replaceWith = ReplaceWith("ID_PROPERTY_KEY"))
9296
const val idPropertyKey = ID_PROPERTY_KEY

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ interface IDefaultNodeAdapter : IDeprecatedNodeDefaults {
3838
return concept?.getReference()
3939
}
4040

41+
override fun getOriginalReference(): String? {
42+
return reference.serialize()
43+
}
44+
4145
override fun getPropertyLinks(): List<IProperty> {
4246
return concept?.getAllProperties() ?: emptyList()
4347
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ data class MPSDevKitDependencyAsNode(
6767
}
6868

6969
override fun getPropertyValue(property: IProperty): String? {
70-
return if (property.isIdProperty()) {
71-
reference.serialize()
72-
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name)) {
70+
return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name)) {
7371
moduleReference.moduleName
7472
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid)) {
7573
moduleReference.moduleId.toString()

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ data class MPSJavaModuleFacetAsNode(val facet: JavaModuleFacet) : IDefaultNodeAd
4848
}
4949

5050
override fun getPropertyValue(property: IProperty): String? {
51-
return if (property.isIdProperty()) {
52-
reference.serialize()
53-
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.generated)) {
51+
return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.generated)) {
5452
// Should always be true
5553
// https://github.com/JetBrains/MPS/blob/2820965ff7b8836ed1d14adaf1bde29744c88147/core/project/source/jetbrains/mps/project/facets/JavaModuleFacetImpl.java
5654
true.toString()

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,6 @@ data class MPSModelAsNode(val model: SModel) : IDefaultNodeAdapter {
111111
model.name.value
112112
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Model.id)) {
113113
model.modelId.toString()
114-
} else if (property.isIdProperty()) {
115-
reference.serialize()
116114
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Model.stereotype)) {
117115
model.name.stereotype
118116
} else {

0 commit comments

Comments
 (0)