Skip to content

Commit

Permalink
Merge pull request #553 from modelix/issues/MODELIX-789
Browse files Browse the repository at this point in the history
MODELIX-789 bulk-model-sync from model-server to MPS results in many renamed files without content changes
  • Loading branch information
mhuster23 authored Mar 7, 2024
2 parents dc5c5c1 + 6abc939 commit 2d686be
Show file tree
Hide file tree
Showing 18 changed files with 88 additions and 41 deletions.
3 changes: 2 additions & 1 deletion bulk-model-sync-gradle-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ modelSync {
direction("testPush") {
includeModulesByPrefix("GraphSolution")
fromLocal {
mpsHeapSize = "2g"
mpsHeapSize = "4g"
repositoryDir = repoDir
}
toModelServer {
Expand All @@ -107,6 +107,7 @@ modelSync {
}
toLocal {
repositoryDir = repoDir
mpsHeapSize = "4g"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ class ModelImporter(
}

private fun syncProperties(node: INode, nodeData: NodeData) {
if (node.getPropertyValue(NodeData.idPropertyKey) == null) {
if (node.originalId() == null) {
node.setPropertyValue(NodeData.idPropertyKey, nodeData.originalId())
}

Expand All @@ -285,6 +285,7 @@ class ModelImporter(
nodeData.references.forEach {
val expectedTargetId = it.value
val actualTargetId = node.getReferenceTarget(it.key)?.originalId()
?: node.getReferenceTargetRef(it.key)?.serialize()
if (actualTargetId != expectedTargetId) {
val expectedTarget = originalIdToExisting[expectedTargetId]
if (expectedTarget == null) {
Expand All @@ -296,18 +297,17 @@ class ModelImporter(
}
val toBeRemoved = node.getReferenceRoles().toSet() - nodeData.references.keys
toBeRemoved.forEach {
val nullReference: INodeReference? = null
node.setReferenceTarget(it, nullReference)
node.setReferenceTarget(it, null as INodeReference?)
}
}
}

internal fun INode.originalId(): String? {
return this.getPropertyValue(NodeData.idPropertyKey)
return this.getOriginalReference()
}

internal fun NodeData.originalId(): String? {
return properties[NodeData.idPropertyKey] ?: id
return properties[NodeData.ID_PROPERTY_KEY] ?: id
}

data class ExistingAndExpectedNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,18 @@ package org.modelix.mps.model.sync.bulk
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.ProjectManager
import jetbrains.mps.ide.project.ProjectHelper
import jetbrains.mps.smodel.SNodeUtil
import jetbrains.mps.smodel.adapter.ids.MetaIdHelper
import jetbrains.mps.smodel.adapter.ids.SConceptId
import jetbrains.mps.smodel.adapter.structure.concept.SConceptAdapterById
import jetbrains.mps.smodel.language.ConceptRegistry
import jetbrains.mps.smodel.language.StructureRegistry
import jetbrains.mps.smodel.runtime.ConceptDescriptor
import jetbrains.mps.smodel.runtime.illegal.IllegalConceptDescriptor
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.jetbrains.mps.openapi.model.EditableSModel
import org.jetbrains.mps.openapi.module.SModule
import org.jetbrains.mps.openapi.module.SRepository
import org.modelix.model.data.ModelData
Expand Down Expand Up @@ -125,12 +134,60 @@ object MPSBulkSynchronizer {
ApplicationManager.getApplication().invokeAndWait {
println("Persisting changes...")
repository.modelAccess.runWriteAction {
enableWorkaroundForFilePerRootPersistence(repository)
repository.saveAll()
}
println("Changes persisted.")
}
}

/**
* Workaround for MPS not being able to read the name property of the node during the save process
* in case FilePerRootPersistence is used.
* This is because the concept is not properly loaded and in the MPS code it checks if the concept is a subconcept
* of INamedConcept.
* Without this workaround the id of the root node will be used instead of the name, resulting in renamed files.
*/
@JvmStatic
private fun enableWorkaroundForFilePerRootPersistence(repository: SRepository) {
val structureRegistry: StructureRegistry = ConceptRegistry.getInstance().readField("myStructureRegistry")
val myConceptDescriptorsById: MutableMap<SConceptId, ConceptDescriptor> = structureRegistry.readField("myConceptDescriptorsById")

repository.modules
.asSequence()
.flatMap { it.models }
.mapNotNull { it as? EditableSModel }
.filter { it.isChanged }
.flatMap { it.rootNodes }
.mapNotNull { (it.concept as? SConceptAdapterById) }
.forEach {
myConceptDescriptorsById.putIfAbsent(it.id, DummyNamedConceptDescriptor(it))
}
}

@Suppress("UNCHECKED_CAST")
private fun <R> Any.readField(name: String): R {
return this::class.java.getDeclaredField(name).also { it.isAccessible = true }.get(this) as R
}

private class DummyNamedConceptDescriptor(concept: SConceptAdapterById) : ConceptDescriptor by IllegalConceptDescriptor(concept.id, concept.qualifiedName) {
override fun isAssignableTo(other: SConceptId?): Boolean {
return MetaIdHelper.getConcept(SNodeUtil.concept_INamedConcept) == other
}

override fun getSuperConceptId(): SConceptId {
return MetaIdHelper.getConcept(SNodeUtil.concept_BaseConcept)
}

override fun getAncestorsIds(): MutableSet<SConceptId> {
return mutableSetOf(MetaIdHelper.getConcept(SNodeUtil.concept_INamedConcept))
}

override fun getParentsIds(): MutableList<SConceptId> {
return mutableListOf(MetaIdHelper.getConcept(SNodeUtil.concept_INamedConcept))
}
}

@JvmStatic
private fun parseRawPropertySet(rawProperty: String): Set<String> {
return if (rawProperty.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flattenConcat
import kotlinx.coroutines.flow.flowOf
import org.modelix.model.area.IArea
import org.modelix.model.data.NodeData

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

/**
* @return the serialized reference of the source node, if this one was created during an import
*/
fun getOriginalReference(): String? = getPropertyValue(IProperty.fromName(NodeData.ID_PROPERTY_KEY))
?: getPropertyValue(IProperty.fromName("#mpsNodeID#")) // for backwards compatibility

// <editor-fold desc="non-string based API">
fun usesRoleIds(): Boolean = false
fun getContainmentLink(): IChildLink? = roleInParent?.let { role ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ package org.modelix.model.api

import kotlin.jvm.JvmOverloads

class SimpleProperty
data class SimpleProperty
@JvmOverloads constructor(
private val simpleName: String,
override val isOptional: Boolean = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ data class NodeData(
val references: Map<String, String> = emptyMap(),
) {
companion object {
const val ID_PROPERTY_KEY = "#mpsNodeId#"

/**
* Users should not use this directly. Use [INode.getOriginalReference].
*/
const val ID_PROPERTY_KEY = "#originalRef#"

@Deprecated("Use ID_PROPERTY_KEY", replaceWith = ReplaceWith("ID_PROPERTY_KEY"))
const val idPropertyKey = ID_PROPERTY_KEY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ interface IDefaultNodeAdapter : IDeprecatedNodeDefaults {
return concept?.getReference()
}

override fun getOriginalReference(): String? {
return reference.serialize()
}

override fun getPropertyLinks(): List<IProperty> {
return concept?.getAllProperties() ?: emptyList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ data class MPSDevKitDependencyAsNode(
}

override fun getPropertyValue(property: IProperty): String? {
return if (property.isIdProperty()) {
reference.serialize()
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name)) {
return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name)) {
moduleReference.moduleName
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid)) {
moduleReference.moduleId.toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ data class MPSJavaModuleFacetAsNode(val facet: JavaModuleFacet) : IDefaultNodeAd
}

override fun getPropertyValue(property: IProperty): String? {
return if (property.isIdProperty()) {
reference.serialize()
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.generated)) {
return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.generated)) {
// Should always be true
// https://github.com/JetBrains/MPS/blob/2820965ff7b8836ed1d14adaf1bde29744c88147/core/project/source/jetbrains/mps/project/facets/JavaModuleFacetImpl.java
true.toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ data class MPSModelAsNode(val model: SModel) : IDefaultNodeAdapter {
model.name.value
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Model.id)) {
model.modelId.toString()
} else if (property.isIdProperty()) {
reference.serialize()
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Model.stereotype)) {
model.name.stereotype
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ class MPSModelImportAsNode(val importedModel: SModel, val importingModel: SModel
}

override fun getPropertyValue(property: IProperty): String? {
if (!property.isIdProperty()) {
return super.getPropertyValue(property)
}
return reference.serialize()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ data class MPSModuleAsNode(val module: SModule) : IDefaultNodeAdapter {
version.toString()
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.Module.compileInMPS)) {
getCompileInMPS().toString()
} else if (property.isIdProperty()) {
reference.serialize()
} else {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ data class MPSModuleDependencyAsNode(
override fun getPropertyValue(property: IProperty): String? {
val moduleDependency = BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency

return if (property.isIdProperty()) {
reference.serialize()
} else if (property.conformsTo(moduleDependency.explicit)) {
return if (property.conformsTo(moduleDependency.explicit)) {
explicit.toString()
} else if (property.conformsTo(moduleDependency.name)) {
moduleReference.moduleName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@ import org.modelix.model.api.ConceptReference
import org.modelix.model.api.IChildLink
import org.modelix.model.api.IConcept
import org.modelix.model.api.IConceptReference
import org.modelix.model.api.IDeprecatedNodeDefaults
import org.modelix.model.api.INode
import org.modelix.model.api.INodeReference
import org.modelix.model.api.IProperty
import org.modelix.model.api.IReferenceLink
import org.modelix.model.api.resolveIn
import org.modelix.model.area.IArea

data class MPSNode(val node: SNode) : IDeprecatedNodeDefaults {
data class MPSNode(val node: SNode) : IDefaultNodeAdapter {
override fun getArea(): IArea {
return MPSArea(node.model?.repository ?: MPSModuleRepository.getInstance())
}
Expand Down Expand Up @@ -159,9 +158,6 @@ data class MPSNode(val node: SNode) : IDeprecatedNodeDefaults {
}

override fun getPropertyValue(property: IProperty): String? {
if (property.isIdProperty()) {
return node.nodeId.toString()
}
val mpsProperty = node.properties.firstOrNull { MPSProperty(it).getUID() == property.getUID() } ?: return null
return node.getProperty(mpsProperty)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ data class MPSProjectAsNode(val project: ProjectBase) : IDefaultNodeAdapter {
}

override fun getPropertyValue(property: IProperty): String? {
return if (property.isIdProperty()) {
reference.serialize()
} else if (property.conformsTo(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name)) {
return if (property.conformsTo(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name)) {
project.name
} else {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ data class MPSProjectModuleAsNode(val project: ProjectBase, val module: SModule)
}

override fun getPropertyValue(property: IProperty): String? {
return if (property.isIdProperty()) {
reference.serialize()
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.ProjectModule.virtualFolder)) {
return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.ProjectModule.virtualFolder)) {
project.getPath(module)?.virtualFolder
} else {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ data class MPSSingleLanguageDependencyAsNode(
) : IDefaultNodeAdapter {

override fun getPropertyValue(property: IProperty): String? {
return if (property.isIdProperty()) {
reference.serialize()
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency.version)) {
return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency.version)) {
languageVersion.toString()
} else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name)) {
moduleReference.moduleName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import org.modelix.model.api.IChildLink
import org.modelix.model.api.IProperty
import org.modelix.model.api.IReferenceLink
import org.modelix.model.api.IRole
import org.modelix.model.data.NodeData

internal fun IProperty.isIdProperty() = getSimpleName() == NodeData.ID_PROPERTY_KEY

// statically ensures that receiver and parameter are of the same type
internal fun IChildLink.conformsTo(other: IChildLink) = conformsToRole(other)
Expand Down

0 comments on commit 2d686be

Please sign in to comment.