diff --git a/bulk-model-sync-gradle/src/main/kotlin/org/modelix/model/sync/bulk/gradle/tasks/ExportFromModelServer.kt b/bulk-model-sync-gradle/src/main/kotlin/org/modelix/model/sync/bulk/gradle/tasks/ExportFromModelServer.kt index 1c8d588692..9284f6ac8d 100644 --- a/bulk-model-sync-gradle/src/main/kotlin/org/modelix/model/sync/bulk/gradle/tasks/ExportFromModelServer.kt +++ b/bulk-model-sync-gradle/src/main/kotlin/org/modelix/model/sync/bulk/gradle/tasks/ExportFromModelServer.kt @@ -89,7 +89,7 @@ abstract class ExportFromModelServer : DefaultTask() { val nameRole = BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name return root.allChildren.filter { - val isModule = it.concept?.getUID() == BuiltinLanguages.MPSRepositoryConcepts.Module.getUID() + val isModule = it.concept?.isSubConceptOf(BuiltinLanguages.MPSRepositoryConcepts.Module) == true val moduleName = it.getPropertyValue(nameRole) ?: return@filter false val isIncluded = isModuleIncluded( moduleName, diff --git a/bulk-model-sync-lib/mps-test/build.gradle.kts b/bulk-model-sync-lib/mps-test/build.gradle.kts index febb35e057..b0e39f1092 100644 --- a/bulk-model-sync-lib/mps-test/build.gradle.kts +++ b/bulk-model-sync-lib/mps-test/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { testImplementation(project(":bulk-model-sync-lib")) testImplementation(project(":bulk-model-sync-mps")) testImplementation(project(":mps-model-adapters")) + testImplementation(project(":model-datastructure")) testImplementation(libs.kotlin.serialization.json) testImplementation(libs.xmlunit.matchers) testImplementation(libs.jimfs) diff --git a/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/RecreateProjectFromModelServerTest.kt b/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/RecreateProjectFromModelServerTest.kt new file mode 100644 index 0000000000..6aa00cc172 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/RecreateProjectFromModelServerTest.kt @@ -0,0 +1,265 @@ +package org.modelix.model.sync.bulk.lib.test + +import com.intellij.ide.impl.OpenProjectTask +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.project.ex.ProjectManagerEx +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.TestApplicationManager +import com.intellij.testFramework.UsefulTestCase +import jetbrains.mps.ide.ThreadUtils +import jetbrains.mps.ide.project.ProjectHelper +import jetbrains.mps.project.AbstractModule +import jetbrains.mps.project.MPSProject +import jetbrains.mps.smodel.Language +import jetbrains.mps.smodel.MPSModuleRepository +import org.jetbrains.mps.openapi.model.EditableSModel +import org.jetbrains.mps.openapi.model.SaveOptions +import org.modelix.model.api.PBranch +import org.modelix.model.api.getRootNode +import org.modelix.model.client.IdGenerator +import org.modelix.model.data.ModelData +import org.modelix.model.data.asData +import org.modelix.model.lazy.CLTree +import org.modelix.model.lazy.CLVersion +import org.modelix.model.lazy.ObjectStoreCache +import org.modelix.model.mpsadapters.MPSContextProject +import org.modelix.model.mpsadapters.asReadableNode +import org.modelix.model.mpsadapters.asWritableNode +import org.modelix.model.persistent.MapBaseStore +import org.modelix.model.sync.bulk.ModelSynchronizer +import org.modelix.model.sync.bulk.NodeAssociationFromModelServer +import org.modelix.model.sync.bulk.NodeAssociationToModelServer +import org.modelix.mps.model.sync.bulk.MPSProjectSyncFilter +import org.w3c.dom.Element +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.absolute +import kotlin.io.path.deleteRecursively + +class RecreateProjectFromModelServerTest : UsefulTestCase() { + + init { + // workaround for MPS 2023.3 failing to start in test mode + System.setProperty("intellij.platform.load.app.info.from.resources", "true") + } + + protected lateinit var project: Project + + override fun runInDispatchThread() = false + + override fun setUp() { + super.setUp() + } + + fun `test same file content`() { + TestApplicationManager.getInstance() + + val originalProject = openTestProject("nonTrivialProject") + project = originalProject + + val store = ObjectStoreCache(MapBaseStore()).getAsyncStore() + val idGenerator = IdGenerator.getInstance(0xabcd) + val emptyVersion = CLVersion.createRegularVersion( + id = idGenerator.generate(), + time = null, + author = this::class.java.name, + tree = CLTree.builder(store).repositoryId("unit-test-repo").build(), + baseVersion = null, + operations = emptyArray(), + ) + val branch = PBranch(emptyVersion.getTree(), idGenerator) + mpsProject.modelAccess.runReadAction { + branch.runWrite { + val mpsRoot = mpsProject.repository.asReadableNode() + val modelServerRoot = branch.getRootNode().asWritableNode() + ModelSynchronizer( + filter = MPSProjectSyncFilter(listOf(mpsProject), toMPS = false), + sourceRoot = mpsRoot, + targetRoot = modelServerRoot, + nodeAssociation = NodeAssociationToModelServer(branch), + ).synchronize() + println(ModelData(root = modelServerRoot.asLegacyNode().asData()).toJson()) + } + } + + fun filterFiles(files: Map) = files.filter { + val name = it.key + if (name.startsWith(".mps/")) { + false // name == ".mps/modules.xml" + } else if (name.contains("/source_gen") || name.contains("/classes_gen")) { + false + } else { + true + } + } + + val originalContents = filterFiles(originalProject.captureFileContents()) + originalProject.close() + + val emptyProject = openTestProject(null) + project = emptyProject + + mpsProject.modelAccess.executeCommandInEDT { + branch.runRead { + val mpsRoot = mpsProject.repository.asWritableNode() + val modelServerRoot = branch.getRootNode().asReadableNode() + val modelSynchronizer = ModelSynchronizer( + filter = MPSProjectSyncFilter(listOf(mpsProject), toMPS = true), + sourceRoot = modelServerRoot, + targetRoot = mpsRoot, + nodeAssociation = NodeAssociationFromModelServer(branch, mpsRoot.getModel()), + ) + MPSContextProject.contextValue.computeWith(mpsProject) { + modelSynchronizer.synchronize() + } + } + } + + val syncedContents = filterFiles(emptyProject.captureFileContents()) + + fun Map.contentsAsString(): String { + return entries.sortedBy { it.key }.joinToString("\n\n\n") { "------ ${it.key} ------\n${it.value}" } + .replace(""" writeAction(body: () -> R): R { + return mpsProject.modelAccess.computeWriteAction(body) + } + + protected fun writeActionOnEdt(body: () -> R): R { + return onEdt { writeAction { body() } } + } + + protected fun onEdt(body: () -> R): R { + var result: R? = null + ThreadUtils.runInUIThreadAndWait { + result = body() + } + return result as R + } + + protected fun readAction(body: () -> R): R { + var result: R? = null + mpsProject.modelAccess.runReadAction { + result = body() + } + return result as R + } +} + +fun Project.close() { + ApplicationManager.getApplication().invokeLaterOnWriteThread { + runCatching { + ProjectManager.getInstance().closeAndDispose(this) + } + } + ApplicationManager.getApplication().invokeAndWait { } +} + +private fun Project.captureFileContents(): Map { + ApplicationManager.getApplication().invokeAndWait { + MPSModuleRepository.getInstance().modelAccess.runWriteAction { + for (module in ProjectHelper.fromIdeaProject(this)!!.projectModules.flatMap { + listOf(it) + ((it as? Language)?.generators ?: emptyList()) + }) { + module as AbstractModule + module.save() + for (model in module.models.filterIsInstance()) { + model.save(SaveOptions.FORCE) + } + } + } + ApplicationManager.getApplication().saveAll() + save() + } + return File(this.basePath).walk().filter { it.isFile }.associate { file -> + val name = file.absoluteFile.relativeTo(File(basePath).absoluteFile).path + val content = file.readText().trim() + val normalizedContent = when { + name.endsWith(".mps") -> normalizeModelFile(content) + else -> content + } + name to normalizedContent + } +} + +private fun normalizeModelFile(content: String): String { + val xml = readXmlFile(content.byteInputStream()) + xml.visitAll { node -> + if (node !is Element) return@visitAll + when (node.tagName) { + "node" -> { + node.childElements("property").sortByRole() + node.childElements("ref").sortByRole() + node.childElements("node").sortByRole() + } + } + } + return xmlToString(xml).lineSequence().map { it.trim() }.filter { it.isEmpty() }.joinToString("\n") +} + +private fun List.sortByRole() { + if (size < 2) return + val sorted = sortedBy { it.getAttribute("role") } + for (i in (0..sorted.lastIndex - 1).reversed()) { + sorted[i].parentNode.insertBefore(sorted[i], sorted[i + 1]) + } +} diff --git a/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/ResolveInfoUpdateTest.kt b/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/ResolveInfoUpdateTest.kt index 115e956bba..b04519eee7 100644 --- a/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/ResolveInfoUpdateTest.kt +++ b/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/ResolveInfoUpdateTest.kt @@ -5,7 +5,7 @@ import org.junit.Assert.assertThat import org.modelix.model.api.BuiltinLanguages import org.modelix.model.api.INode import org.modelix.model.data.ModelData -import org.modelix.model.mpsadapters.MPSRepositoryAsNode +import org.modelix.model.mpsadapters.asLegacyNode import org.modelix.model.sync.bulk.ExistingAndExpectedNode import org.modelix.model.sync.bulk.asExported import org.modelix.mps.model.sync.bulk.MPSBulkSynchronizer @@ -46,7 +46,7 @@ class ResolveInfoUpdateTest : MPSTestBase() { var result: INode? = null mpsProject.repository.modelAccess.runReadAction { val repository = mpsProject.repository - val repositoryNode = MPSRepositoryAsNode(repository) + val repositoryNode = repository.asLegacyNode() result = repositoryNode.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules) .single { it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) == "NewSolution" } } diff --git a/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/XMLUtils.kt b/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/XMLUtils.kt new file mode 100644 index 0000000000..e99689c93f --- /dev/null +++ b/bulk-model-sync-lib/mps-test/src/test/kotlin/org/modelix/model/sync/bulk/lib/test/XMLUtils.kt @@ -0,0 +1,69 @@ +package org.modelix.model.sync.bulk.lib.test + +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.Node +import java.io.File +import java.io.InputStream +import java.io.StringWriter +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.Transformer +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +fun xmlToString(doc: Document): String { + val transformerFactory: TransformerFactory = TransformerFactory.newInstance() + val transformer: Transformer = transformerFactory.newTransformer() + transformer.setOutputProperty(OutputKeys.INDENT, "yes") + val source = DOMSource(doc) + val out = StringWriter() + val result = StreamResult(out) + transformer.transform(source, result) + return out.toString() +} + +fun readXmlFile(file: File): Document { + try { + val dbf = DocumentBuilderFactory.newInstance() + // dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) + disableDTD(dbf) + val db = dbf.newDocumentBuilder() + return db.parse(file) + } catch (e: Exception) { + throw RuntimeException("Failed to read ${file.absoluteFile}", e) + } +} + +fun readXmlFile(file: InputStream, name: String? = null): Document { + val dbf = DocumentBuilderFactory.newInstance() + // dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) + disableDTD(dbf) + val db = dbf.newDocumentBuilder() + return db.parse(file, name) +} + +private fun disableDTD(dbf: DocumentBuilderFactory) { + dbf.setValidating(false) + dbf.setNamespaceAware(true) + dbf.setFeature("http://xml.org/sax/features/namespaces", false) + dbf.setFeature("http://xml.org/sax/features/validation", false) + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false) + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) +} + +fun Node.visitAll(visitor: (Node) -> Unit) { + visitor(this) + val childNodes = this.childNodes + for (i in 0 until childNodes.length) childNodes.item(i).visitAll(visitor) +} + +fun Node.childElements(): List = children().filterIsInstance() +fun Node.childElements(tag: String): List = children().filterIsInstance().filter { it.tagName == tag } +fun Node.children(): List { + val children = childNodes + val result = ArrayList(children.length) + for (i in 0 until children.length) result += children.item(i) + return result +} diff --git a/bulk-model-sync-lib/mps-test/testdata/.gitignore b/bulk-model-sync-lib/mps-test/testdata/.gitignore new file mode 100644 index 0000000000..1f02983e16 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/.gitignore @@ -0,0 +1,5 @@ +test_gen +test_gen.caches +classes_gen +source_gen +source_gen.caches diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/.gitignore b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/.gitignore new file mode 100644 index 0000000000..26d33521af --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/migration.xml b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/migration.xml new file mode 100644 index 0000000000..d512ce4547 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/migration.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/modules.xml b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/modules.xml new file mode 100644 index 0000000000..72a75e08b5 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/.mps/modules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/devkits/NewDevkit/NewDevkit.devkit b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/devkits/NewDevkit/NewDevkit.devkit new file mode 100644 index 0000000000..c250d0145c --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/devkits/NewDevkit/NewDevkit.devkit @@ -0,0 +1,10 @@ + + + + + fbc25dd2-5da4-483a-8b19-70928e1b62d7(jetbrains.mps.devkit.general-purpose) + + + 4eb87a8f-881e-4d34-9514-f5002000c363(NewRuntimeSolution) + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/NewLanguage.mpl b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/NewLanguage.mpl new file mode 100644 index 0000000000..489d1fe723 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/NewLanguage.mpl @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/generator/templates/NewLanguage.generator.templates@generator.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/generator/templates/NewLanguage.generator.templates@generator.mps new file mode 100644 index 0000000000..aa61b6dc02 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/generator/templates/NewLanguage.generator.templates@generator.mps @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.behavior.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.behavior.mps new file mode 100644 index 0000000000..cccaca58d6 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.behavior.mps @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.constraints.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.constraints.mps new file mode 100644 index 0000000000..9174bc3220 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.constraints.mps @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.editor.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.editor.mps new file mode 100644 index 0000000000..189b19c9f5 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.editor.mps @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.structure.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.structure.mps new file mode 100644 index 0000000000..a2c8e2ad2f --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.structure.mps @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.typesystem.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.typesystem.mps new file mode 100644 index 0000000000..4633af7a03 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/languages/NewLanguage/models/NewLanguage.typesystem.mps @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewRuntimeSolution/NewRuntimeSolution.msd b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewRuntimeSolution/NewRuntimeSolution.msd new file mode 100644 index 0000000000..3200f3fd15 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewRuntimeSolution/NewRuntimeSolution.msd @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + 6354ebe7-c22a-4a0f-ac54-50b52ab9b065(JDK) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewRuntimeSolution/models/NewRuntimeSolution.plugin.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewRuntimeSolution/models/NewRuntimeSolution.plugin.mps new file mode 100644 index 0000000000..e854d85106 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewRuntimeSolution/models/NewRuntimeSolution.plugin.mps @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/NewSolution.msd b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/NewSolution.msd new file mode 100644 index 0000000000..1eb14f8508 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/NewSolution.msd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/models/NewSolution.a_model.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/models/NewSolution.a_model.mps new file mode 100644 index 0000000000..79809d22bf --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/models/NewSolution.a_model.mps @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/models/NewSolution.b_model.mps b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/models/NewSolution.b_model.mps new file mode 100644 index 0000000000..f06dc94545 --- /dev/null +++ b/bulk-model-sync-lib/mps-test/testdata/nonTrivialProject/solutions/NewSolution/models/NewSolution.b_model.mps @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelSynchronizer.kt b/bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelSynchronizer.kt index aa506425f4..56003fbeb5 100644 --- a/bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelSynchronizer.kt +++ b/bulk-model-sync-lib/src/commonMain/kotlin/org/modelix/model/sync/bulk/ModelSynchronizer.kt @@ -7,6 +7,7 @@ import org.modelix.model.api.IReadableNode import org.modelix.model.api.IReferenceLinkReference import org.modelix.model.api.IRoleReference import org.modelix.model.api.IWritableNode +import org.modelix.model.api.NewNodeSpec import org.modelix.model.api.PNodeAdapter import org.modelix.model.api.getOriginalOrCurrentReference import org.modelix.model.api.getOriginalReference @@ -14,7 +15,11 @@ import org.modelix.model.api.isChildRoleOrdered import org.modelix.model.api.matches import org.modelix.model.api.mergeWith import org.modelix.model.api.remove +import org.modelix.model.api.syncNewChild +import org.modelix.model.api.syncNewChildren +import org.modelix.model.api.tryResolve import org.modelix.model.data.NodeData +import org.modelix.model.sync.bulk.ModelSynchronizer.IFilter /** * Similar to [ModelImporter], but the input is two [INode] instances instead of [INode] and [NodeData]. @@ -136,9 +141,9 @@ class ModelSynchronizer( } } - // optimization that uses the bulk operation .addNewChildren + // optimization that uses the bulk operation .syncNewChildren if (targetNodes.isEmpty() && allExpectedNodesDoNotExist) { - targetParent.addNewChildren(role, -1, sourceNodes.map { it.getConceptReference() }) + targetParent.syncNewChildren(role, -1, sourceNodes.map { NewNodeSpec(it) }) .zip(sourceNodes) .forEach { (newChild, sourceChild) -> nodeAssociation.associate(sourceChild, newChild) @@ -178,13 +183,18 @@ class ModelSynchronizer( val childNode = if (nodeAtIndex?.getOriginalOrCurrentReference() != expectedId) { val existingNode = nodeAssociation.resolveTarget(expected) if (existingNode == null) { - val newChild = targetParent.addNewChild(role, newIndex, expectedConcept) + val newChild = targetParent.syncNewChild(role, newIndex, NewNodeSpec(expected)) nodeAssociation.associate(expected, newChild) newChild } else { // The existing child node is not only moved to a new index, // it is potentially moved to a new parent and role. - targetParent.moveChild(role, newIndex, existingNode) + if (existingNode.getParent() != targetParent || + !existingNode.getContainmentLink().matches(role) || + role.tryResolve(targetParent.getConceptReference())?.isOrdered != false + ) { + targetParent.moveChild(role, newIndex, existingNode) + } // If the old parent and old role synchronized before the move operation, // the existing child node would have been marked as to be deleted. // Now that it is used, it should not be deleted. @@ -205,6 +215,30 @@ class ModelSynchronizer( // the recursive synchronization in the meantime already removed some nodes from node.getChildren(role). nodesToRemove += getFilteredTargetChildren(targetParent, role).intersect(unusedTargetChildren) } + // tryFixCrossRoleOrder(sourceParent, targetParent) + } + + /** + * In MPS and also in Modelix nodes internally are stored in a single list that is filtered when a specific role is + * accessed. The information about this internal order is visible when using getAllChildren(). + * MPS uses this order in its "Generic comments" feature + * (see https://www.jetbrains.com/help/mps/generic-placeholders-and-generic-comments.html). + * Even though it is not semantically relevant, it will still be visible in the editor if we don't preserve that + * order. + */ + private fun tryFixCrossRoleOrder(sourceParent: IReadableNode, targetParent: IWritableNode) { + val sourceChildren = sourceParent.getAllChildren() + val actualTargetChildren = targetParent.getAllChildren() + val expectedTargetChildren = sourceChildren.map { nodeAssociation.resolveTarget(it) ?: return } + if (actualTargetChildren == expectedTargetChildren) return + + for (targetChild in expectedTargetChildren) { + try { + targetParent.moveChild(targetChild.getContainmentLink(), -1, targetChild) + } catch (ex: UnsupportedOperationException) { + return + } + } } inner class PendingReference(val sourceNode: IReadableNode, val targetNode: IWritableNode, val role: IReferenceLinkReference) { @@ -213,7 +247,7 @@ class ModelSynchronizer( fun copyTargetRef() { val oldValue = targetNode.getReferenceTargetRef(role) val newValue = sourceNode.getReferenceTargetRef(role) - if (oldValue != newValue) { + if (oldValue?.serialize() != newValue?.serialize()) { targetNode.setReferenceTargetRef(role, newValue) } } @@ -283,6 +317,34 @@ class ModelSynchronizer( } } +fun IFilter.and(other: IFilter): IFilter = AndFilter(this, other) + +class AndFilter(val filter1: IFilter, val filter2: IFilter) : IFilter { + override fun needsDescentIntoSubtree(subtreeRoot: IReadableNode): Boolean { + return filter1.needsDescentIntoSubtree(subtreeRoot) && filter2.needsDescentIntoSubtree(subtreeRoot) + } + + override fun needsSynchronization(node: IReadableNode): Boolean { + return filter1.needsSynchronization(node) && filter2.needsSynchronization(node) + } + + override fun filterSourceChildren( + parent: IReadableNode, + role: IChildLinkReference, + children: List, + ): List { + return filter2.filterSourceChildren(parent, role, filter1.filterSourceChildren(parent, role, children)) + } + + override fun filterTargetChildren( + parent: IWritableNode, + role: IChildLinkReference, + children: List, + ): List { + return filter2.filterTargetChildren(parent, role, filter1.filterTargetChildren(parent, role, children)) + } +} + private fun INode.originalIdOrFallback(): String? { val originalRef = getOriginalReference() if (originalRef != null) return originalRef diff --git a/bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSBulkSynchronizer.kt b/bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSBulkSynchronizer.kt index 39dd6f4cf5..a9de46c9a6 100644 --- a/bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSBulkSynchronizer.kt +++ b/bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSBulkSynchronizer.kt @@ -30,7 +30,8 @@ import org.modelix.model.data.ModelData import org.modelix.model.lazy.RepositoryId import org.modelix.model.mpsadapters.MPSArea import org.modelix.model.mpsadapters.MPSModuleAsNode -import org.modelix.model.mpsadapters.MPSRepositoryAsNode +import org.modelix.model.mpsadapters.asLegacyNode +import org.modelix.model.mpsadapters.asWritableNode import org.modelix.model.sync.bulk.ExistingAndExpectedNode import org.modelix.model.sync.bulk.InvalidatingVisitor import org.modelix.model.sync.bulk.InvalidationTree @@ -191,7 +192,7 @@ object MPSBulkSynchronizer { } modulesToImport } - importModelsIntoRepository(repository, MPSRepositoryAsNode(repository), continueOnError, getModulesToImport) + importModelsIntoRepository(repository, repository.asLegacyNode(), continueOnError, getModulesToImport) } /** @@ -284,7 +285,7 @@ object MPSBulkSynchronizer { val synchronizer = ModelSynchronizer( CompositeFilter(listOf(invalidationTree, includedModulesFilter)), treePointer.getRootNode().asReadableNode(), - MPSRepositoryAsNode(repository).asWritableNode(), + repository.asWritableNode(), NodeAssociationFromModelServer(treePointer, MPSArea(repository).asModel()), ) synchronizer.synchronize() diff --git a/bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSProjectSyncFilter.kt b/bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSProjectSyncFilter.kt new file mode 100644 index 0000000000..318d326cc4 --- /dev/null +++ b/bulk-model-sync-mps/src/main/kotlin/org/modelix/mps/model/sync/bulk/MPSProjectSyncFilter.kt @@ -0,0 +1,75 @@ +package org.modelix.mps.model.sync.bulk + +import jetbrains.mps.project.MPSProject +import org.modelix.model.api.BuiltinLanguages +import org.modelix.model.api.IChildLinkReference +import org.modelix.model.api.IReadableNode +import org.modelix.model.api.IWritableNode +import org.modelix.model.mpsadapters.MPSModuleAsNode +import org.modelix.model.mpsadapters.MPSProjectAsNode +import org.modelix.model.sync.bulk.ModelSynchronizer + +class MPSProjectSyncFilter(val projects: List, val toMPS: Boolean) : ModelSynchronizer.IFilter { + + private val fromMPS: Boolean get() = !toMPS + + override fun needsDescentIntoSubtree(subtreeRoot: IReadableNode): Boolean { + return true + } + + override fun needsSynchronization(node: IReadableNode): Boolean { + return true + } + + private fun filterChildren( + parent: IReadableNode, + role: IChildLinkReference, + children: List, + isSourceChildren: Boolean, + ): List { + val isMPSSide = fromMPS == isSourceChildren + + return when (parent.getConceptReference()) { + BuiltinLanguages.MPSRepositoryConcepts.Repository.getReference() -> when { + role.matches(BuiltinLanguages.MPSRepositoryConcepts.Repository.tempModules.toReference()) -> emptyList() + role.matches(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules.toReference()) -> { + if (isMPSSide) { + val included = projects.flatMap { it.projectModules }.map { MPSModuleAsNode(it).getNodeReference().serialize() }.toSet() + children.filter { included.contains(it.getNodeReference().serialize()) } + } else { + children + } + } + role.matches(BuiltinLanguages.MPSRepositoryConcepts.Repository.projects.toReference()) -> { + if (isMPSSide) { + val included = projects.map { MPSProjectAsNode(it).asWritableNode().getNodeReference().serialize() }.toSet() + children.filter { included.contains(it.getNodeReference().serialize()) } + } else { + children + } + } + else -> children + } + BuiltinLanguages.MPSRepositoryConcepts.Project.getReference() -> when { + else -> children + } + else -> children + } + } + + override fun filterSourceChildren( + parent: IReadableNode, + role: IChildLinkReference, + children: List, + ): List { + return filterChildren(parent, role, children, true) + } + + override fun filterTargetChildren( + parent: IWritableNode, + role: IChildLinkReference, + children: List, + ): List { + return filterChildren(parent, role, children, false) + } +} diff --git a/model-api/src/commonMain/kotlin/org/modelix/model/api/BuiltinLanguages.kt b/model-api/src/commonMain/kotlin/org/modelix/model/api/BuiltinLanguages.kt index bc03ccd61e..e9e7ce92b8 100644 --- a/model-api/src/commonMain/kotlin/org/modelix/model/api/BuiltinLanguages.kt +++ b/model-api/src/commonMain/kotlin/org/modelix/model/api/BuiltinLanguages.kt @@ -197,12 +197,56 @@ object BuiltinLanguages { directSuperConcepts = listOf(Module), ) { init { addConcept(this) } + + val generators = SimpleChildLink( + simpleName = "generators", + isMultiple = true, + isOptional = true, + isOrdered = false, + targetConcept = Generator, + uid = "0a7577d1-d4e5-431d-98b1-fae38f9aee80/4008363636171860313/7018594982789597990", + ).also(this::addChildLink) } object DevKit : SimpleConcept( conceptName = "DevKit", uid = "mps:0a7577d1-d4e5-431d-98b1-fae38f9aee80/7341098702109598213", directSuperConcepts = listOf(Module), + ) { + init { addConcept(this) } + + val exportedLanguages = SimpleChildLink( + simpleName = "exportedLanguages", + isMultiple = true, + isOptional = true, + isOrdered = false, + targetConcept = ModuleReference, + uid = "0a7577d1-d4e5-431d-98b1-fae38f9aee80/4008363636171860313/3386077560723097553", + ).also(this::addChildLink) + + val exportedSolutions = SimpleChildLink( + simpleName = "exportedSolutions", + isMultiple = true, + isOptional = true, + isOrdered = false, + targetConcept = ModuleReference, + uid = "0a7577d1-d4e5-431d-98b1-fae38f9aee80/4008363636171860313/3386077560723097554", + ).also(this::addChildLink) + + val extendedDevkits = SimpleChildLink( + simpleName = "extendedDevkits", + isMultiple = true, + isOptional = true, + isOrdered = false, + targetConcept = ModuleReference, + uid = "0a7577d1-d4e5-431d-98b1-fae38f9aee80/4008363636171860313/3386077560723097555", + ).also(this::addChildLink) + } + + object Generator : SimpleConcept( + conceptName = "Generator", + uid = "mps:0a7577d1-d4e5-431d-98b1-fae38f9aee80/7018594982789597991", + directSuperConcepts = listOf(Module), ) { init { addConcept(this) } } @@ -249,6 +293,7 @@ object BuiltinLanguages { ) { init { addConcept(this) } + @Deprecated("Use Repository.modules") val modules = SimpleChildLink( simpleName = "modules", isMultiple = true, diff --git a/model-api/src/commonMain/kotlin/org/modelix/model/api/INodeReferenceSerializer.kt b/model-api/src/commonMain/kotlin/org/modelix/model/api/INodeReferenceSerializer.kt index 70b6da209e..2bd2ffb8c3 100644 --- a/model-api/src/commonMain/kotlin/org/modelix/model/api/INodeReferenceSerializer.kt +++ b/model-api/src/commonMain/kotlin/org/modelix/model/api/INodeReferenceSerializer.kt @@ -92,7 +92,7 @@ interface INodeReferenceSerializer { serializer.prefix + INodeReferenceSerializerEx.SEPARATOR + serializer.serialize(ref) } else { legacySerializers.map { it.serialize(ref) }.firstOrNull { it != null } - ?: throw RuntimeException("No serializer found for ${ref::class}") + ?: ref.serialize() } } diff --git a/model-api/src/commonMain/kotlin/org/modelix/model/api/IWritableNode.kt b/model-api/src/commonMain/kotlin/org/modelix/model/api/IWritableNode.kt index 80451d6714..834de4e484 100644 --- a/model-api/src/commonMain/kotlin/org/modelix/model/api/IWritableNode.kt +++ b/model-api/src/commonMain/kotlin/org/modelix/model/api/IWritableNode.kt @@ -64,3 +64,29 @@ interface IWritableNode : IReadableNode { fun setReferenceTarget(role: IReferenceLinkReference, target: IWritableNode?) fun setReferenceTargetRef(role: IReferenceLinkReference, target: INodeReference?) } + +interface ISyncTargetNode : IWritableNode { + fun syncNewChildren(role: IChildLinkReference, index: Int, specs: List): List +} + +fun IWritableNode.syncNewChildren(role: IChildLinkReference, index: Int, sourceNodes: List): List { + return when (this) { + is ISyncTargetNode -> syncNewChildren(role, index, sourceNodes) + else -> addNewChildren(role, index, sourceNodes.map { it.conceptRef }) + } +} + +fun IWritableNode.syncNewChild(role: IChildLinkReference, index: Int, sourceNode: NewNodeSpec): IWritableNode { + return when (this) { + is ISyncTargetNode -> syncNewChildren(role, index, listOf(sourceNode)).single() + else -> addNewChild(role, index, sourceNode.conceptRef) + } +} + +class NewNodeSpec( + val conceptRef: ConceptReference, + val node: IReadableNode? = null, + val preferredNodeReference: INodeReference? = null, +) { + constructor(node: IReadableNode) : this(node.getConceptReference(), node, node.getOriginalReference()?.let { NodeReference(it) }) +} diff --git a/model-api/src/commonMain/kotlin/org/modelix/model/data/ModelData.kt b/model-api/src/commonMain/kotlin/org/modelix/model/data/ModelData.kt index c4c4d427c2..2448f92ec7 100644 --- a/model-api/src/commonMain/kotlin/org/modelix/model/data/ModelData.kt +++ b/model-api/src/commonMain/kotlin/org/modelix/model/data/ModelData.kt @@ -113,7 +113,10 @@ data class NodeData( references = references.entries.sortedBy { it.key }.associate { it.key to it.value }, ) + fun toJson() = prettyJson.encodeToString(this) + companion object { + private val prettyJson = Json { prettyPrint = true } /** * Users should not use this directly. Use [INode.getOriginalReference]. diff --git a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/AllChildrenActuallyReturnsAllChildrenTest.kt b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/AllChildrenActuallyReturnsAllChildrenTest.kt index b9d0d08c0c..922017c6c8 100644 --- a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/AllChildrenActuallyReturnsAllChildrenTest.kt +++ b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/AllChildrenActuallyReturnsAllChildrenTest.kt @@ -6,7 +6,7 @@ class AllChildrenActuallyReturnsAllChildrenTest : MpsAdaptersTestBase("SimplePro fun `test repository adapter consistency`() { readAction { - checkAdapterConsistence(MPSRepositoryAsNode(mpsProject.repository)) + checkAdapterConsistence(mpsProject.repository.asLegacyNode()) } } diff --git a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangePropertyTest.kt b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangePropertyTest.kt index 6812fe2574..b9c1b869d8 100644 --- a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangePropertyTest.kt +++ b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangePropertyTest.kt @@ -10,7 +10,7 @@ class ChangePropertyTest : MpsAdaptersTestBase("SimpleProject") { assertEquals(1, mpsProject.projectModules.size) } - val repositoryNode: INode = MPSRepositoryAsNode(mpsProject.repository) + val repositoryNode: INode = mpsProject.repository.asLegacyNode() runCommandOnEDT { val module = repositoryNode.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules) diff --git a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangeReferenceTest.kt b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangeReferenceTest.kt index c412d3005f..a67337e14c 100644 --- a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangeReferenceTest.kt +++ b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ChangeReferenceTest.kt @@ -9,7 +9,7 @@ class ChangeReferenceTest : MpsAdaptersTestBase("SimpleProject") { // This is some reference link that is technically not part of the concept of the node it is used with. // But for this test, this is fine because nodes might have invalid references. val referenceLink = BuiltinLanguages.MPSRepositoryConcepts.ModelReference.model - val repositoryNode: INode = MPSRepositoryAsNode(mpsProject.repository) + val repositoryNode: INode = mpsProject.repository.asLegacyNode() runCommandOnEDT { val module = repositoryNode.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules) .single { it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) == "Solution1" } @@ -27,7 +27,7 @@ class ChangeReferenceTest : MpsAdaptersTestBase("SimpleProject") { fun testCanNotSetNonMPSNodeAsReferenceTarget() { val referenceLink = BuiltinLanguages.MPSRepositoryConcepts.ModelReference.model - val repositoryNode: INode = MPSRepositoryAsNode(mpsProject.repository) + val repositoryNode: INode = mpsProject.repository.asLegacyNode() runCommandOnEDT { val module = repositoryNode.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules) .single { it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) == "Solution1" } diff --git a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSAreaTest.kt b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSAreaTest.kt index 1d609e859c..c63fe3d0a2 100644 --- a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSAreaTest.kt +++ b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSAreaTest.kt @@ -6,7 +6,7 @@ import org.modelix.model.api.INode class MPSAreaTest : MpsAdaptersTestBase("SimpleProject") { fun testResolveModuleInNonExistingProject() { - val repositoryNode: INode = MPSRepositoryAsNode(mpsProject.repository) + val repositoryNode: INode = mpsProject.repository.asLegacyNode() val area = repositoryNode.getArea() readAction { val nonExistingProject = MPSProjectReference("nonExistingProject") diff --git a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNodeTest.kt b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNodeTest.kt index c4d8652401..d079f3764a 100644 --- a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNodeTest.kt +++ b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNodeTest.kt @@ -6,7 +6,7 @@ import org.modelix.model.api.NodeReference class MPSModuleAsNodeTest : MpsAdaptersTestBase("SimpleProject") { fun `test resolve language dependency from reference`() { - val repositoryNode: INode = MPSRepositoryAsNode(mpsProject.repository) + val repositoryNode: INode = mpsProject.repository.asLegacyNode() val languageDependencyNodeReference = NodeReference( "mps-lang:f3061a53-9226-4cc5-a443-f952ceaf5816#IN#mps-module:6517ba0d-f632-49c5-a166-401587c2c3ca(Solution1)", ) diff --git a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ReplaceNodeTest.kt b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ReplaceNodeTest.kt index cde5f3a8d9..62ab3da329 100644 --- a/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ReplaceNodeTest.kt +++ b/mps-model-adapters-plugin/src/test/kotlin/org/modelix/model/mpsadapters/ReplaceNodeTest.kt @@ -108,7 +108,7 @@ class ReplaceNodeTest : MpsAdaptersTestBase("SimpleProject") { } private fun getModelUnderTest(): INode { - val repositoryNode = MPSRepositoryAsNode(mpsProject.repository) + val repositoryNode = mpsProject.repository.asLegacyNode() val module = repositoryNode.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Repository.modules) .single { it.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name) == "Solution1" } return module.getChildren(BuiltinLanguages.MPSRepositoryConcepts.Module.models).single() diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSArea.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSArea.kt index 34e2f24f78..77397a8ee7 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSArea.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSArea.kt @@ -7,10 +7,12 @@ import jetbrains.mps.project.ProjectManager import jetbrains.mps.project.facets.JavaModuleFacet import jetbrains.mps.project.structure.modules.ModuleReference import jetbrains.mps.smodel.GlobalModelAccess +import jetbrains.mps.smodel.ModelImports import jetbrains.mps.smodel.SNodePointer import org.jetbrains.mps.openapi.model.SNodeReference import org.jetbrains.mps.openapi.module.SRepository import org.jetbrains.mps.openapi.persistence.PersistenceFacade +import org.modelix.model.api.ChildLinkReferenceByUID import org.modelix.model.api.IBranch import org.modelix.model.api.IConcept import org.modelix.model.api.IConceptReference @@ -23,7 +25,7 @@ import org.modelix.model.area.IAreaReference data class MPSArea(val repository: SRepository) : IArea, IAreaReference { - private fun resolveMPSModelReference(ref: INodeReference): INode { + private fun resolveMPSModelReference(ref: INodeReference): INode? { if (ref is MPSModelReference) { return ref.modelReference.resolve(repository).let { MPSModelAsNode(it).asLegacyNode() } } @@ -31,11 +33,11 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference { val serialized = ref.serialize().substringAfter("${MPSModelReference.PREFIX}:") val modelRef = PersistenceFacade.getInstance().createModelReference(serialized) - return MPSModelAsNode(modelRef.resolve(repository)).asLegacyNode() + return modelRef.resolve(repository)?.let { MPSModelAsNode(it).asLegacyNode() } } override fun getRoot(): INode { - return MPSRepositoryAsNode(repository) + return repository.asLegacyNode() } @Deprecated("use ILanguageRepository.resolveConcept") @@ -56,12 +58,13 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference { MPSModelReference.PREFIX -> resolveMPSModelReference(ref) MPSNodeReference.PREFIX, "mps-node" -> resolveMPSNodeReference(ref) // mps-node for backwards compatibility MPSDevKitDependencyReference.PREFIX -> resolveMPSDevKitDependencyReference(ref) - MPSJavaModuleFacetReference.PREFIX -> resolveMPSJavaModuleFacetReference(ref) - MPSModelImportReference.PREFIX -> resolveMPSModelImportReference(ref) - MPSModuleDependencyReference.PREFIX -> resolveMPSModuleDependencyReference(ref) + MPSJavaModuleFacetReference.PREFIX -> resolveMPSJavaModuleFacetReference(ref)?.asLegacyNode() + MPSModelImportReference.PREFIX -> resolveMPSModelImportReference(ref)?.asLegacyNode() + MPSModuleDependencyReference.PREFIX -> resolveMPSModuleDependencyReference(ref)?.asLegacyNode() + MPSModuleReferenceReference.PREFIX -> resolveMPSModuleReferenceReference(ref)?.asLegacyNode() MPSProjectReference.PREFIX -> resolveMPSProjectReference(ref) MPSProjectModuleReference.PREFIX -> resolveMPSProjectModuleReference(ref) - MPSSingleLanguageDependencyReference.PREFIX -> resolveMPSSingleLanguageDependencyReference(ref) + MPSSingleLanguageDependencyReference.PREFIX -> resolveMPSSingleLanguageDependencyReference(ref)?.asLegacyNode() MPSRepositoryReference.PREFIX -> resolveMPSRepositoryReference() else -> null } @@ -151,7 +154,7 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference { throw UnsupportedOperationException("Not implemented") } - private fun resolveMPSModuleReference(ref: INodeReference): MPSModuleAsNode? { + private fun resolveMPSModuleReference(ref: INodeReference): MPSModuleAsNode<*>? { val moduleRef = if (ref is MPSModuleReference) { ref.moduleReference } else { @@ -200,7 +203,7 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference { return when (foundImporter) { is MPSModelAsNode -> foundImporter.findDevKitDependency(moduleId) - is MPSModuleAsNode -> foundImporter.findDevKitDependency(moduleId) + is MPSModuleAsNode<*> -> foundImporter.findDevKitDependency(moduleId) else -> null } } @@ -236,10 +239,10 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference { PersistenceFacade.getInstance().createModelReference(serializedModelRef) } - val importedModel = importedModelRef.resolve(repository) ?: return null val importingModel = importingModelRef.resolve(repository) ?: return null + if (!ModelImports(importingModel).importedModels.contains(importedModelRef)) return null - return MPSModelImportAsNode(importedModel = importedModel, importingModel = importingModel) + return MPSModelImportAsNode(importedModel = importedModelRef, importingModel = importingModel) } private fun resolveMPSModuleDependencyReference(ref: INodeReference): MPSModuleDependencyAsNode? { @@ -265,6 +268,23 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference { ?.findModuleDependency(usedModuleId) } + private fun resolveMPSModuleReferenceReference(ref: INodeReference): MPSModuleReferenceAsNode? { + val ref = if (ref is MPSModuleReferenceReference) { + ref + } else { + val parts = ref.serialize().substringAfter("${MPSModuleReferenceReference.PREFIX}:").split(MPSModuleReferenceReference.SEPARATOR) + MPSModuleReferenceReference( + PersistenceFacade.getInstance().createModuleId(parts[0]), + ChildLinkReferenceByUID(parts[1]), + PersistenceFacade.getInstance().createModuleId(parts[2]), + ) + } + + val parent = MPSModuleAsNode(PersistenceFacade.getInstance().createModuleReference(ref.parent, "").resolve(repository) ?: return null) + return parent.getChildren(ref.link).filterIsInstance() + .find { it.target.moduleId == ref.target } + } + private fun resolveMPSProjectReference(ref: INodeReference): MPSProjectAsNode? { val projectName = if (ref is MPSProjectReference) { ref.projectName @@ -337,12 +357,12 @@ data class MPSArea(val repository: SRepository) : IArea, IAreaReference { return when (foundImporter) { is MPSModelAsNode -> foundImporter.findSingleLanguageDependency(moduleId) - is MPSModuleAsNode -> foundImporter.findSingleLanguageDependency(moduleId) + is MPSModuleAsNode<*> -> foundImporter.findSingleLanguageDependency(moduleId) else -> null } } - private fun resolveMPSRepositoryReference(): MPSRepositoryAsNode { - return MPSRepositoryAsNode(repository) + private fun resolveMPSRepositoryReference(): INode { + return repository.asLegacyNode() } } diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSContextProject.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSContextProject.kt new file mode 100644 index 0000000000..06012d01d9 --- /dev/null +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSContextProject.kt @@ -0,0 +1,8 @@ +package org.modelix.model.mpsadapters + +import jetbrains.mps.project.MPSProject +import org.modelix.kotlin.utils.ContextValue + +object MPSContextProject { + val contextValue = ContextValue() +} diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSGenericNodeAdapter.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSGenericNodeAdapter.kt index 6f794d7b1c..8d61c54559 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSGenericNodeAdapter.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSGenericNodeAdapter.kt @@ -1,22 +1,24 @@ package org.modelix.model.mpsadapters import jetbrains.mps.smodel.MPSModuleRepository -import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration +import org.jetbrains.mps.openapi.language.SConcept import org.jetbrains.mps.openapi.module.SRepository import org.modelix.model.api.ConceptReference import org.modelix.model.api.IChildLinkReference -import org.modelix.model.api.IConcept import org.modelix.model.api.IMutableModel import org.modelix.model.api.INodeReference import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReadableNode import org.modelix.model.api.IReferenceLinkReference +import org.modelix.model.api.ISyncTargetNode import org.modelix.model.api.IWritableNode +import org.modelix.model.api.NewNodeSpec import org.modelix.model.api.upcast -abstract class MPSGenericNodeAdapter : IWritableNode { +abstract class MPSGenericNodeAdapter : IWritableNode, ISyncTargetNode { protected abstract fun getElement(): E - protected abstract fun getRepository(): SRepository? + abstract fun getRepository(): SRepository? protected abstract fun getPropertyAccessors(): List>> protected abstract fun getReferenceAccessors(): List>> protected abstract fun getChildAccessors(): List>> @@ -70,21 +72,23 @@ abstract class MPSGenericNodeAdapter : IWritableNode { } override fun addNewChildren(role: IChildLinkReference, index: Int, concepts: List): List { + return doSyncNewChildren(role, index, concepts.map { it to null }) + } + + override fun syncNewChildren(role: IChildLinkReference, index: Int, sourceNodes: List): List { + return doSyncNewChildren(role, index, sourceNodes.map { it.conceptRef to it }) + } + + private fun doSyncNewChildren(role: IChildLinkReference, index: Int, sourceNodes: List>): List { val accessor = getChildAccessor(role) val repo = getRepository() ?: MPSModuleRepository.getInstance() - val resolvedConcepts = concepts.distinct().associateWith { concept -> - requireNotNull( - concept.let { - MPSLanguageRepository(repo).resolveConcept(it.getUID()) - ?: MPSConcept.tryParseUID(it.getUID()) - }?.concept?.let { MetaAdapterByDeclaration.asInstanceConcept(it) }, - ) { - // A null value for the concept would default to BaseConcept, but then BaseConcept should be used explicitly. - "MPS concept not found: $concept" - }.let { MPSConcept(it) } + val resolvedConcepts = sourceNodes.map { it.first }.distinct().associateWith { concept -> + repo.resolveConcept(concept) } - return accessor.addNew(getElement(), index, concepts.map { resolvedConcepts[it]!! }) + return sourceNodes.map { sourceNode -> + accessor.addNew(getElement(), index, SpecWithResolvedConcept(resolvedConcepts[sourceNode.first]!!, sourceNode.second)) + } } override fun setReferenceTarget(role: IReferenceLinkReference, target: IWritableNode?) { @@ -129,23 +133,33 @@ abstract class MPSGenericNodeAdapter : IWritableNode { interface IPropertyAccessor { fun read(element: E): String? - fun write(element: E, value: String?): Unit = throw UnsupportedOperationException() + fun write(element: E, value: String?): Unit = throw UnsupportedOperationException("$this, $value") } interface IReferenceAccessor { fun read(element: E): IWritableNode? - fun write(element: E, value: IWritableNode?): Unit = throw UnsupportedOperationException() + fun write(element: E, value: IWritableNode?) { + throw UnsupportedOperationException() + } fun write(element: E, value: INodeReference?): Unit = throw UnsupportedOperationException() } interface IChildAccessor { fun read(element: E): List - fun addNew(element: E, index: Int, childConcepts: List): List { - throw UnsupportedOperationException("$this") + fun addNew(element: E, index: Int, sourceNode: SpecWithResolvedConcept): IWritableNode { + throw UnsupportedOperationException("$this, $element, $sourceNode") } fun move(element: E, index: Int, child: IWritableNode) { - throw UnsupportedOperationException() + throw UnsupportedOperationException("$this, $element, $child") } fun remove(element: E, child: IWritableNode): Unit = throw UnsupportedOperationException() } + + class SpecWithResolvedConcept(val concept: SConcept, val spec: NewNodeSpec?) { + fun getNode(): IReadableNode = spec?.node!! + fun getConceptReference(): ConceptReference = MPSConcept(concept).getReference() + override fun toString(): String { + return "SourceNodeAndConcept[$concept, $spec]" + } + } } diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSJavaModuleFacetAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSJavaModuleFacetAsNode.kt index a96d729cdd..2dcbcf9873 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSJavaModuleFacetAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSJavaModuleFacetAsNode.kt @@ -1,61 +1,71 @@ package org.modelix.model.mpsadapters -import jetbrains.mps.project.AbstractModule +import jetbrains.mps.persistence.MementoImpl import jetbrains.mps.project.facets.JavaModuleFacet -import jetbrains.mps.smodel.MPSModuleRepository +import jetbrains.mps.util.MacrosFactory +import org.jetbrains.mps.openapi.module.SRepository +import org.jetbrains.mps.openapi.persistence.Memento import org.modelix.model.api.BuiltinLanguages -import org.modelix.model.api.IChildLink +import org.modelix.model.api.IChildLinkReference import org.modelix.model.api.IConcept -import org.modelix.model.api.INode import org.modelix.model.api.INodeReference -import org.modelix.model.api.IProperty -import org.modelix.model.area.IArea +import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReferenceLinkReference +import org.modelix.model.api.IWritableNode -data class MPSJavaModuleFacetAsNode(val facet: JavaModuleFacet) : IDefaultNodeAdapter { +data class MPSJavaModuleFacetAsNode(val facet: JavaModuleFacet) : MPSGenericNodeAdapter() { - override fun getArea(): IArea { - return MPSArea(facet.module?.repository ?: MPSModuleRepository.getInstance()) + companion object { + private val propertyAccessors = listOf>>( + BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.generated.toReference() to object : IPropertyAccessor { + override fun read(element: JavaModuleFacet): String? { + // Should always be true + // https://github.com/JetBrains/MPS/blob/2820965ff7b8836ed1d14adaf1bde29744c88147/core/project/source/jetbrains/mps/project/facets/JavaModuleFacetImpl.java + return true.toString() + } + }, + BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.path.toReference() to object : IPropertyAccessor { + override fun read(facet: JavaModuleFacet): String? { + return facet.classesGen?.let { MacrosFactory().module(facet.module).shrinkPath(it.path) } + } + + override fun write(element: JavaModuleFacet, value: String?) { + element.classesGen + val memento = MementoImpl() + element.save(memento) + memento.getOrCreateChild("classes").put("path", value?.let { MacrosFactory().module(element.module).expandPath(it) }) + element.load(memento) + } + }, + ) + private val referenceAccessors = listOf>>() + private val childAccessors = listOf>>() } - override val reference: INodeReference - get() { - val module = checkNotNull(facet.module) { "Module of facet $facet not found" } - return MPSJavaModuleFacetReference(module.moduleReference) - } - override val concept: IConcept - get() = BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet - override val parent: INode? - get() = facet.module?.let { MPSModuleAsNode(it).asLegacyNode() } - - override fun getContainmentLink(): IChildLink { - return BuiltinLanguages.MPSRepositoryConcepts.Module.facets + override fun getElement(): JavaModuleFacet = facet + + override fun getRepository(): SRepository? = null + + override fun getPropertyAccessors() = propertyAccessors + + override fun getReferenceAccessors() = referenceAccessors + + override fun getChildAccessors() = childAccessors + + override fun getParent(): IWritableNode? { + return facet.module?.let { MPSModuleAsNode(it) } } - override fun getPropertyValue(property: IProperty): String? { - 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() - } else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.path)) { - getPath() - } else { - null - } + override fun getNodeReference(): INodeReference { + val module = checkNotNull(facet.module) { "Module of facet $facet not found" } + return MPSJavaModuleFacetReference(module.moduleReference) } - private fun getPath(): String? { - val originalPath = facet.classesGen?.path - val module = facet.module - val moduleRoot = if (module is AbstractModule) { - module.descriptorFile?.parent?.path - } else { - null - } - - return if (moduleRoot != null && originalPath?.startsWith(moduleRoot) == true) { - "\${module}${originalPath.substring(moduleRoot.length)}" - } else { - originalPath - } + override fun getConcept(): IConcept = BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet + + override fun getContainmentLink(): IChildLinkReference { + return BuiltinLanguages.MPSRepositoryConcepts.Module.facets.toReference() } } + +private fun Memento.getOrCreateChild(name: String) = getChild(name) ?: createChild(name) diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelAsNode.kt index d75bf4dcd0..9c1626fbf7 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelAsNode.kt @@ -1,11 +1,15 @@ package org.modelix.model.mpsadapters import jetbrains.mps.extapi.model.SModelDescriptorStub +import jetbrains.mps.project.ModuleId import jetbrains.mps.smodel.ModelImports +import jetbrains.mps.smodel.adapter.ids.SLanguageId import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory +import jetbrains.mps.smodel.adapter.structure.language.SLanguageAdapterById import org.jetbrains.mps.openapi.model.SModel import org.jetbrains.mps.openapi.module.SModuleId import org.jetbrains.mps.openapi.module.SRepository +import org.jetbrains.mps.openapi.persistence.PersistenceFacade import org.modelix.model.api.BuiltinLanguages import org.modelix.model.api.IChildLinkReference import org.modelix.model.api.IConcept @@ -13,6 +17,8 @@ import org.modelix.model.api.INodeReference import org.modelix.model.api.IPropertyReference import org.modelix.model.api.IReferenceLinkReference import org.modelix.model.api.IWritableNode +import org.modelix.model.data.asData +import java.util.UUID data class MPSModelAsNode(val model: SModel) : MPSGenericNodeAdapter() { @@ -32,11 +38,47 @@ data class MPSModelAsNode(val model: SModel) : MPSGenericNodeAdapter() { private val childAccessors = listOf>>( BuiltinLanguages.MPSRepositoryConcepts.Model.rootNodes.toReference() to object : IChildAccessor { override fun read(element: SModel): List = element.rootNodes.map { MPSWritableNode(it) } + override fun addNew( + element: SModel, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + val nodeId = sourceNode.spec?.preferredNodeReference + ?.let { MPSNodeReference.tryConvert(it) }?.ref?.nodeId + return element.createNode(sourceNode.concept, nodeId) + .also { element.addRootNode(it) } + .let { MPSWritableNode(it) } + } }, BuiltinLanguages.MPSRepositoryConcepts.Model.modelImports.toReference() to object : IChildAccessor { - override fun read(element: SModel): List = ModelImports(element).importedModels.mapNotNull { modelRef -> - val target = modelRef.resolve(element.repository) - target?.let { MPSModelImportAsNode(it, element).asWritableNode() } + override fun read(element: SModel): List { + return ModelImports(element).importedModels.mapNotNull { modelRef -> + MPSModelImportAsNode(modelRef, element) + } + } + + override fun addNew( + element: SModel, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + require(sourceNode.getConceptReference() == BuiltinLanguages.MPSRepositoryConcepts.ModelReference.getReference()) + + val importedModel = checkNotNull(sourceNode.getNode().getReferenceTarget(BuiltinLanguages.MPSRepositoryConcepts.ModelReference.model.toReference())) { + "Model reference not set: ${sourceNode.getNode().asLegacyNode().asData().toJson()}" + } + val modelId = checkNotNull(importedModel.getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Model.id.toReference())) { + "Target model has no ID: $importedModel" + } + val modelName = importedModel.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference()) ?: "" + val moduleRef = importedModel.getParent()?.let { importedModule -> + val moduleId = importedModule.getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference()) ?: return@let null + val moduleName = importedModule.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference()) ?: "" + PersistenceFacade.getInstance().createModuleReference(ModuleId.fromString(moduleId), moduleName) + } + val modelRef = PersistenceFacade.getInstance().createModelReference(moduleRef, PersistenceFacade.getInstance().createModelId(modelId), modelName) + ModelImports(element).addModelImport(modelRef) + return MPSModelImportAsNode(modelRef, element) } }, BuiltinLanguages.MPSRepositoryConcepts.Model.usedLanguages.toReference() to object : IChildAccessor { @@ -45,19 +87,49 @@ data class MPSModelAsNode(val model: SModel) : MPSGenericNodeAdapter() { return element.importedLanguageIds().filter { it.sourceModuleReference != null }.map { MPSSingleLanguageDependencyAsNode( - it.sourceModuleReference, - element.getLanguageImportVersion(it), + it, modelImporter = element, - ).asWritableNode() + ) } + element.importedDevkits().map { MPSDevKitDependencyAsNode(it, modelImporter = element).asWritableNode() } } + override fun addNew( + element: SModel, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + require(element is SModelDescriptorStub) + return when (sourceNode.getConceptReference()) { + BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency.getReference() -> { + val id = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid.toReference()) + val name = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name.toReference()) + val slang = SLanguageAdapterById(SLanguageId(UUID.fromString(id)), name ?: "") + element.addLanguage(slang) + return MPSSingleLanguageDependencyAsNode( + slang, + modelImporter = element, + ) + } + BuiltinLanguages.MPSRepositoryConcepts.DevkitDependency.getReference() -> { + val id = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid.toReference()) + val name = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name.toReference()) + val ref = PersistenceFacade.getInstance().createModuleReference(ModuleId.regular(UUID.fromString(id)), name ?: "") + element.addDevKit(ref) + return MPSDevKitDependencyAsNode( + moduleReference = ref, + modelImporter = element, + ).asWritableNode() + } + else -> throw UnsupportedOperationException("Unsupported type: ${sourceNode.getConceptReference()}") + } + } + override fun remove(element: SModel, child: IWritableNode) { check(element is SModelDescriptorStub) { "Model '$element' is not a SModelDescriptor." } require(child is MPSSingleLanguageDependencyAsNode) { "Node $child to be removed is not a single language dependency." } - val languageToRemove = MetaAdapterFactory.getLanguage(child.moduleReference) + val languageToRemove = MetaAdapterFactory.getLanguage(child.moduleReference.sourceModuleReference) element.deleteLanguageId(languageToRemove) } }, @@ -97,10 +169,9 @@ data class MPSModelAsNode(val model: SModel) : MPSGenericNodeAdapter() { internal fun findSingleLanguageDependency(dependencyId: SModuleId): MPSSingleLanguageDependencyAsNode? { if (model is SModelDescriptorStub) { model.importedLanguageIds().forEach { entry -> - if (entry.sourceModule?.moduleId == dependencyId) { + if (entry.sourceModuleReference.moduleId == dependencyId) { return MPSSingleLanguageDependencyAsNode( - entry.sourceModuleReference, - model.getLanguageImportVersion(entry), + entry, modelImporter = model, ) } diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelImportAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelImportAsNode.kt index ed586afa83..987c40670b 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelImportAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModelImportAsNode.kt @@ -1,59 +1,65 @@ package org.modelix.model.mpsadapters import org.jetbrains.mps.openapi.model.SModel +import org.jetbrains.mps.openapi.model.SModelReference +import org.jetbrains.mps.openapi.module.SRepository import org.modelix.model.api.BuiltinLanguages -import org.modelix.model.api.IChildLink +import org.modelix.model.api.IChildLinkReference import org.modelix.model.api.IConcept -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.area.IArea +import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReferenceLinkReference +import org.modelix.model.api.IWritableNode -data class MPSModelImportAsNode(val importedModel: SModel, val importingModel: SModel) : IDefaultNodeAdapter { - override fun getArea(): IArea = - MPSArea(importingModel.repository) +data class MPSModelImportAsNode(val importedModel: SModelReference, val importingModel: SModel) : MPSGenericNodeAdapter() { - override val reference: INodeReference - get() = MPSModelImportReference( - importedModel = importedModel.reference, - importingModel = importingModel.reference, + companion object { + private val referenceAccessors = listOf( + BuiltinLanguages.MPSRepositoryConcepts.ModelReference.model.toReference() to object : + IReferenceAccessor { + override fun read(element: MPSModelImportAsNode): IWritableNode? { + return MPSModelAsNode(element.importedModel.resolve(element.importingModel.repository)) + } + }, ) - override val concept: IConcept - get() = BuiltinLanguages.MPSRepositoryConcepts.ModelReference - override val parent: INode - get() = MPSModelAsNode(importingModel).asLegacyNode() + } + + override fun getElement(): MPSModelImportAsNode { + return this + } - override fun getReferenceTarget(link: IReferenceLink): INode { - require(link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.ModelReference.model)) { - "Unknown reference link '$link'" - } - return MPSModelAsNode(importedModel).asLegacyNode() + override fun getRepository(): SRepository? { + return importingModel.repository } - override fun getReferenceTargetRef(role: IReferenceLink): INodeReference { - return getReferenceTarget(role).reference + override fun getPropertyAccessors(): List>> { + return emptyList() } - override fun setReferenceTarget(link: IReferenceLink, target: INode?) { - if (link.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.ModelReference.model)) { - throw UnsupportedOperationException("ModelReference.model is read only.") - } + override fun getReferenceAccessors(): List>> { + return referenceAccessors } - override fun setReferenceTarget(role: IReferenceLink, target: INodeReference?) { - setReferenceTarget(role, null as INode?) + override fun getChildAccessors(): List>> { + return emptyList() } - override fun getPropertyValue(property: IProperty): String? { - return reference.serialize() + override fun getParent(): IWritableNode? { + return MPSModelAsNode(importingModel) + } + + override fun getNodeReference(): INodeReference { + return MPSModelImportReference( + importedModel = importedModel, + importingModel = importingModel.reference, + ) } - override fun setPropertyValue(property: IProperty, value: String?) { - throw UnsupportedOperationException("Concept $concept does not have properties.") + override fun getConcept(): IConcept { + return BuiltinLanguages.MPSRepositoryConcepts.ModelReference } - override fun getContainmentLink(): IChildLink { - return BuiltinLanguages.MPSRepositoryConcepts.Model.modelImports + override fun getContainmentLink(): IChildLinkReference { + return BuiltinLanguages.MPSRepositoryConcepts.Model.modelImports.toReference() } } diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNode.kt index ed47a1afa7..179f76c3da 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleAsNode.kt @@ -1,180 +1,255 @@ package org.modelix.model.mpsadapters +import jetbrains.mps.persistence.MementoImpl import jetbrains.mps.project.AbstractModule import jetbrains.mps.project.DevKit +import jetbrains.mps.project.ModuleId import jetbrains.mps.project.ProjectBase import jetbrains.mps.project.ProjectManager import jetbrains.mps.project.Solution import jetbrains.mps.project.facets.JavaModuleFacet +import jetbrains.mps.smodel.Generator +import jetbrains.mps.smodel.Language +import jetbrains.mps.smodel.MPSModuleRepository +import jetbrains.mps.smodel.SModelId +import jetbrains.mps.smodel.adapter.ids.SLanguageId +import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory import org.jetbrains.mps.openapi.model.SModel -import org.jetbrains.mps.openapi.module.SDependencyScope +import org.jetbrains.mps.openapi.module.FacetsFacade import org.jetbrains.mps.openapi.module.SModule import org.jetbrains.mps.openapi.module.SModuleId +import org.jetbrains.mps.openapi.module.SModuleReference import org.jetbrains.mps.openapi.module.SRepository +import org.jetbrains.mps.openapi.persistence.PersistenceFacade import org.modelix.model.api.BuiltinLanguages import org.modelix.model.api.IChildLinkReference import org.modelix.model.api.IConcept import org.modelix.model.api.INodeReference import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReadableNode import org.modelix.model.api.IReferenceLinkReference import org.modelix.model.api.IWritableNode +import org.modelix.model.data.asData +import java.util.UUID -data class MPSModuleAsNode(val module: SModule) : MPSGenericNodeAdapter() { +fun MPSModuleAsNode(module: SModule) = MPSModuleAsNode.create(module) + +abstract class MPSModuleAsNode : MPSGenericNodeAdapter() { companion object { private val logger = mu.KotlinLogging.logger { } - private val propertyAccessors = listOf>>( - BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference() to object : IPropertyAccessor { - override fun read(element: MPSModuleAsNode): String? = element.module.moduleName + fun create(module: T): MPSModuleAsNode { + return when (module) { + is Solution -> MPSSolutionAsNode(module) + is Language -> MPSLanguageAsNode(module) + is Generator -> MPSGeneratorAsNode(module) + is DevKit -> MPSDevkitAsNode(module) + else -> MPSUnknownModuleAsNode(module) + } as MPSModuleAsNode + } + + private val propertyAccessors = listOf>>( + BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference() to object : IPropertyAccessor { + override fun read(element: SModule): String? = element.moduleName }, - BuiltinLanguages.jetbrains_mps_lang_core.BaseConcept.virtualPackage.toReference() to object : IPropertyAccessor { - override fun read(element: MPSModuleAsNode): String? { + BuiltinLanguages.jetbrains_mps_lang_core.BaseConcept.virtualPackage.toReference() to object : IPropertyAccessor { + override fun read(element: SModule): String? { return ProjectManager.getInstance().openedProjects.asSequence() .filterIsInstance() - .mapNotNull { it.getPath(element.module) } + .mapNotNull { it.getPath(element) } .firstOrNull() ?.virtualFolder + ?.takeIf { it.isNotEmpty() } + } + + override fun write(element: SModule, value: String?) { + MPSContextProject.contextValue.getValue().setVirtualFolder(element, value ?: "") } }, - BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference() to object : IPropertyAccessor { - override fun read(element: MPSModuleAsNode): String? = element.module.moduleId.toString() + BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference() to object : IPropertyAccessor { + override fun read(element: SModule): String? = element.moduleId.toString() }, - BuiltinLanguages.MPSRepositoryConcepts.Module.moduleVersion.toReference() to object : IPropertyAccessor { - override fun read(element: MPSModuleAsNode): String? { + BuiltinLanguages.MPSRepositoryConcepts.Module.moduleVersion.toReference() to object : IPropertyAccessor { + override fun read(element: SModule): String? { val version = (element as? AbstractModule)?.moduleDescriptor?.moduleVersion ?: 0 return version.toString() } }, - BuiltinLanguages.MPSRepositoryConcepts.Module.compileInMPS.toReference() to object : IPropertyAccessor { - override fun read(element: MPSModuleAsNode): String? { + BuiltinLanguages.MPSRepositoryConcepts.Module.compileInMPS.toReference() to object : IPropertyAccessor { + override fun read(element: SModule): String? { return element.getCompileInMPS().toString() } }, ) - private val referenceAccessors = listOf>>() - private val childAccessors = listOf>>( - BuiltinLanguages.MPSRepositoryConcepts.Module.models.toReference() to object : IChildAccessor { - override fun read(element: MPSModuleAsNode): List = element.module.models.withoutDescriptorModel().map { MPSModelAsNode(it) } + private val referenceAccessors = listOf>>() + val childAccessors = listOf>>( + BuiltinLanguages.MPSRepositoryConcepts.Module.models.toReference() to object : IChildAccessor { + override fun read(element: SModule): List { + return element.models.withoutDescriptorModel().map { MPSModelAsNode(it) } + } + override fun addNew( + element: SModule, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + return element.createModel( + name = sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference()) + ?: "${element.moduleName}.unnamed", + id = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Model.id.toReference()) + ?.let { PersistenceFacade.getInstance().createModelId(it) } ?: SModelId.generate(), + ).let { MPSModelAsNode(it) } + } }, - BuiltinLanguages.MPSRepositoryConcepts.Module.facets.toReference() to object : IChildAccessor { - override fun read(element: MPSModuleAsNode): List { - return element.module.facets.filterIsInstance() - .map { MPSJavaModuleFacetAsNode(it).asWritableNode() } + BuiltinLanguages.MPSRepositoryConcepts.Module.facets.toReference() to object : IChildAccessor { + override fun read(element: SModule): List { + return element.facets.mapNotNull { + when (it) { + is JavaModuleFacet -> MPSJavaModuleFacetAsNode(it) + else -> null + } + } } - override fun remove(element: MPSModuleAsNode, child: IWritableNode) { - val module = element.module as AbstractModule + override fun addNew( + element: SModule, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + return when (sourceNode.getConceptReference()) { + BuiltinLanguages.MPSRepositoryConcepts.JavaModuleFacet.getReference() -> { + val module = element as AbstractModule + val moduleDescriptor = checkNotNull(module.moduleDescriptor) { "Has no moduleDescriptor: $module" } + val newFacet = FacetsFacade.getInstance().getFacetFactory(JavaModuleFacet.FACET_TYPE)!!.create(element) + newFacet.load(MementoImpl()) + moduleDescriptor.addFacetDescriptor(newFacet) + module.setModuleDescriptor(moduleDescriptor) + read(element).filterIsInstance().single() + } + else -> error("Unsupported facets type: ${sourceNode.getConceptReference()}") + } + } + + override fun remove(element: SModule, child: IWritableNode) { + val module = element as AbstractModule val moduleDescriptor = checkNotNull(module.moduleDescriptor) { "Has no moduleDescriptor: $module" } - val facet = child.asLegacyNode() as MPSJavaModuleFacetAsNode + val facet = child as MPSJavaModuleFacetAsNode moduleDescriptor.removeFacetDescriptor(facet.facet) } }, - BuiltinLanguages.MPSRepositoryConcepts.Module.dependencies.toReference() to object : IChildAccessor { - override fun read(element: MPSModuleAsNode): List { - val module = element.module + BuiltinLanguages.MPSRepositoryConcepts.Module.dependencies.toReference() to object : IChildAccessor { + override fun read(element: SModule): List { + val module = element if (module !is AbstractModule) return emptyList() val moduleDescriptor = module.moduleDescriptor ?: return emptyList() return moduleDescriptor.dependencyVersions.map { (ref, version) -> - MPSModuleDependencyAsNode( - moduleReference = ref, - moduleVersion = version, - explicit = element.isDirectDependency(module, ref.moduleId), - reexport = element.isReexport(module, ref.moduleId), - importer = module, - dependencyScope = element.getDependencyScope(module, ref.moduleId), - ).asWritableNode() + MPSModuleDependencyAsNode(element, ref) + } + } + + override fun addNew( + element: SModule, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + val module = element as AbstractModule + val moduleDescriptor = checkNotNull(module.moduleDescriptor) { "No descriptor: $module" } + + return when (sourceNode.getConceptReference()) { + BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.getReference() -> { + val id = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.uuid.toReference()) + val name = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.name.toReference()) ?: "" + val version = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.version.toReference())?.toIntOrNull() ?: 0 + val ref = PersistenceFacade.getInstance().createModuleReference(ModuleId.regular(UUID.fromString(id)), name) + moduleDescriptor.dependencyVersions[ref] = version + MPSModuleDependencyAsNode(element, ref) + } + else -> error("Unsupported dependency type: ${sourceNode.getConceptReference()}") } } - override fun remove(element: MPSModuleAsNode, child: IWritableNode) { - val module = element.module as AbstractModule + override fun remove(element: SModule, child: IWritableNode) { + val module = element as AbstractModule val moduleDescriptor = checkNotNull(module.moduleDescriptor) { "Has no moduleDescriptor: $module" } - val dependency = child.asLegacyNode() as MPSModuleDependencyAsNode + val dependency = child as MPSModuleDependencyAsNode moduleDescriptor.dependencyVersions.remove(dependency.moduleReference) } }, - BuiltinLanguages.MPSRepositoryConcepts.Module.languageDependencies.toReference() to object : IChildAccessor { - override fun read(element: MPSModuleAsNode): List { - val module = element.module + BuiltinLanguages.MPSRepositoryConcepts.Module.languageDependencies.toReference() to object : IChildAccessor { + override fun read(element: SModule): List { + val module = element if (module !is AbstractModule) return emptyList() val moduleDescriptor = module.moduleDescriptor ?: return emptyList() return moduleDescriptor.languageVersions.map { (language, version) -> - MPSSingleLanguageDependencyAsNode(language.sourceModuleReference, version, moduleImporter = module).asWritableNode() + MPSSingleLanguageDependencyAsNode(language, moduleImporter = module) } + moduleDescriptor.usedDevkits.map { devKit -> MPSDevKitDependencyAsNode(devKit, module).asWritableNode() } } + + override fun addNew( + element: SModule, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + val module = element as AbstractModule + val moduleDescriptor = checkNotNull(module.moduleDescriptor) { "No descriptor: $module" } + return when (sourceNode.getConceptReference()) { + BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency.getReference() -> { + val id = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid.toReference()) + val name = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name.toReference()) ?: "" + val version = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency.version.toReference()) ?: "" + val lang = MetaAdapterFactory.getLanguage(SLanguageId(UUID.fromString(id)), name) + moduleDescriptor.languageVersions[lang] = version.toIntOrNull() ?: -1 + MPSSingleLanguageDependencyAsNode(lang, moduleImporter = element) + } + BuiltinLanguages.MPSRepositoryConcepts.DevkitDependency.getReference() -> { + val id = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid.toReference()) + val name = sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name.toReference()) ?: "" + val ref = jetbrains.mps.project.structure.modules.ModuleReference(name, ModuleId.regular(UUID.fromString(id))) + moduleDescriptor.usedDevkits.add(ref) + MPSDevKitDependencyAsNode(ref, moduleImporter = element).asWritableNode() + } + else -> error("Unsupported: ${sourceNode.getConceptReference()}") + } + } }, ) } - override fun getElement(): MPSModuleAsNode { - return this + abstract val module: E + + override fun getElement(): E { + return module } override fun getRepository(): SRepository? { return module.repository } - override fun getPropertyAccessors() = propertyAccessors + override fun getPropertyAccessors(): List>> = propertyAccessors - override fun getReferenceAccessors() = referenceAccessors + override fun getReferenceAccessors(): List>> = referenceAccessors - override fun getChildAccessors() = childAccessors + override fun getChildAccessors(): List>> = childAccessors override fun getParent(): IWritableNode? { - return module.repository?.let { MPSRepositoryAsNode(it).asWritableNode() } + return module.repository?.asWritableNode() } override fun getNodeReference(): INodeReference { return MPSModuleReference(module.moduleReference) } - override fun getConcept(): IConcept { - return BuiltinLanguages.MPSRepositoryConcepts.Module - } - override fun getContainmentLink(): IChildLinkReference { return BuiltinLanguages.MPSRepositoryConcepts.Repository.modules.toReference() } - private fun isDirectDependency(module: SModule, moduleId: SModuleId): Boolean { - if (module is Solution) { - return module.moduleDescriptor.dependencies.any { it.moduleRef.moduleId == moduleId } - } - return module.declaredDependencies.any { it.targetModule.moduleId == moduleId } - } - - private fun isReexport(module: SModule, moduleId: SModuleId): Boolean { - return module.declaredDependencies - .firstOrNull { it.targetModule.moduleId == moduleId }?.isReexport ?: false - } - - private fun getDependencyScope(module: SModule, moduleId: SModuleId): SDependencyScope? { - if (module is Solution) { - return module.moduleDescriptor.dependencies - .firstOrNull { it.moduleRef.moduleId == moduleId }?.scope - } - return module.declaredDependencies.firstOrNull { it.targetModule.moduleId == moduleId }?.scope - } - - private fun getCompileInMPS(): Boolean { - if (module is DevKit || module !is AbstractModule) { - return false - } - return try { - module.moduleDescriptor?.compileInMPS ?: false - } catch (ex: UnsupportedOperationException) { - logger.debug { ex } - false - } - } - internal fun findModuleDependency(dependencyId: SModuleId): MPSModuleDependencyAsNode? { + val module = module if (module !is AbstractModule) { return null } @@ -182,12 +257,8 @@ data class MPSModuleAsNode(val module: SModule) : MPSGenericNodeAdapter if (entry.key.moduleId == dependencyId) { return MPSModuleDependencyAsNode( + module, moduleReference = entry.key, - moduleVersion = entry.value, - explicit = isDirectDependency(module, entry.key.moduleId), - reexport = isReexport(module, entry.key.moduleId), - importer = module, - dependencyScope = getDependencyScope(module, entry.key.moduleId), ) } } @@ -195,20 +266,22 @@ data class MPSModuleAsNode(val module: SModule) : MPSGenericNodeAdapter - val sourceModelReference = entry.key.sourceModuleReference - if (sourceModelReference.moduleId == dependencyId) { - return MPSSingleLanguageDependencyAsNode(sourceModelReference, entry.value, moduleImporter = module) + val sourceModelReference = entry + if (sourceModelReference.sourceModuleReference.moduleId == dependencyId) { + return MPSSingleLanguageDependencyAsNode(sourceModelReference, moduleImporter = module) } } return null } internal fun findDevKitDependency(dependencyId: SModuleId): MPSDevKitDependencyAsNode? { + val module = module if (module !is AbstractModule) { return null } @@ -221,6 +294,139 @@ data class MPSModuleAsNode(val module: SModule) : MPSGenericNodeAdapter() { + override fun getConcept(): IConcept = BuiltinLanguages.MPSRepositoryConcepts.Module +} +data class MPSGeneratorAsNode(override val module: Generator) : MPSModuleAsNode() { + override fun getConcept(): IConcept = BuiltinLanguages.MPSRepositoryConcepts.Generator + + override fun getContainmentLink(): IChildLinkReference { + return BuiltinLanguages.MPSRepositoryConcepts.Language.generators.toReference() + } + + override fun getParent(): IWritableNode? { + return module.sourceLanguage().sourceModule?.let { MPSModuleAsNode.create(it) } + } +} +data class MPSLanguageAsNode(override val module: Language) : MPSModuleAsNode() { + companion object { + private val childAccessors: List>> = MPSModuleAsNode.childAccessors + listOf>>( + BuiltinLanguages.MPSRepositoryConcepts.Language.generators.toReference() to object : IChildAccessor { + override fun read(element: Language): List = element.generators.map { MPSGeneratorAsNode(it) } + override fun addNew( + element: Language, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + return GeneratorProducer(MPSContextProject.contextValue.getValue()).create( + element, + sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!, + sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) }, + ).let { MPSGeneratorAsNode(it) } + } + }, + ) + } + + override fun getConcept(): IConcept = BuiltinLanguages.MPSRepositoryConcepts.Language + + override fun getChildAccessors(): List>> = childAccessors +} +data class MPSSolutionAsNode(override val module: Solution) : MPSModuleAsNode() { + override fun getConcept(): IConcept = BuiltinLanguages.MPSRepositoryConcepts.Solution +} +data class MPSDevkitAsNode(override val module: DevKit) : MPSModuleAsNode() { + companion object { + private fun readModuleReference(contextModule: DevKit, refNode: IReadableNode): SModuleReference { + val moduleNode = refNode.getReferenceTarget(BuiltinLanguages.MPSRepositoryConcepts.ModuleReference.module.toReference()) ?: run { + val originalRef = requireNotNull(refNode.getReferenceTargetRef(BuiltinLanguages.MPSRepositoryConcepts.ModuleReference.module.toReference())) { + "Reference to module is not set: ${refNode.asLegacyNode().asData().toJson()}" + } + MPSArea(contextModule.repository ?: MPSModuleRepository.getInstance()).resolveNode(originalRef)?.asWritableNode() + } + checkNotNull(moduleNode) + val moduleId = moduleNode.getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!! + val moduleName = moduleNode.getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference()) ?: "" + return PersistenceFacade.getInstance().createModuleReference(ModuleId.fromString(moduleId), moduleName) + } + + private val childAccessors: List>> = MPSModuleAsNode.childAccessors + listOf>>( + BuiltinLanguages.MPSRepositoryConcepts.DevKit.exportedLanguages.toReference() to object : IChildAccessor { + override fun read(element: DevKit): List { + return element.moduleDescriptor!!.exportedLanguages.map { + MPSModuleReferenceAsNode( + MPSModuleAsNode(element), + BuiltinLanguages.MPSRepositoryConcepts.DevKit.exportedLanguages.toReference(), + it, + ) + } + } + override fun addNew( + element: DevKit, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + val ref = readModuleReference(element, sourceNode.getNode()) + element.moduleDescriptor!!.exportedLanguages.add(ref) + return MPSModuleReferenceAsNode(MPSModuleAsNode(element), BuiltinLanguages.MPSRepositoryConcepts.DevKit.exportedLanguages.toReference(), ref) + } + }, + BuiltinLanguages.MPSRepositoryConcepts.DevKit.exportedSolutions.toReference() to object : IChildAccessor { + override fun read(element: DevKit): List { + return element.moduleDescriptor!!.exportedSolutions.map { + MPSModuleReferenceAsNode( + MPSModuleAsNode(element), + BuiltinLanguages.MPSRepositoryConcepts.DevKit.exportedSolutions.toReference(), + it, + ) + } + } + override fun addNew( + element: DevKit, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + val ref = readModuleReference(element, sourceNode.getNode()) + element.moduleDescriptor!!.exportedSolutions.add(ref) + return MPSModuleReferenceAsNode(MPSModuleAsNode(element), BuiltinLanguages.MPSRepositoryConcepts.DevKit.exportedSolutions.toReference(), ref) + } + }, + BuiltinLanguages.MPSRepositoryConcepts.DevKit.extendedDevkits.toReference() to object : IChildAccessor { + override fun read(element: DevKit): List { + return element.moduleDescriptor!!.extendedDevkits.map { + MPSModuleReferenceAsNode( + MPSModuleAsNode(element), + BuiltinLanguages.MPSRepositoryConcepts.DevKit.extendedDevkits.toReference(), + it, + ) + } + } + override fun addNew( + element: DevKit, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + val ref = readModuleReference(element, sourceNode.getNode()) + element.moduleDescriptor!!.extendedDevkits.add(ref) + return MPSModuleReferenceAsNode(MPSModuleAsNode(element), BuiltinLanguages.MPSRepositoryConcepts.DevKit.extendedDevkits.toReference(), ref) + } + }, + ) + } + + override fun getChildAccessors(): List>> = childAccessors + + override fun getConcept(): IConcept = BuiltinLanguages.MPSRepositoryConcepts.DevKit +} + private fun Iterable.withoutDescriptorModel(): List { return filter { it.name.stereotype != "descriptor" } } + +private fun SModule.getCompileInMPS(): Boolean { + val module = this + if (module is DevKit || module !is AbstractModule) { + return false + } + return module.moduleDescriptor?.compileInMPS ?: false +} diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleDependencyAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleDependencyAsNode.kt index 19f78b4618..2d163b77fb 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleDependencyAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleDependencyAsNode.kt @@ -1,61 +1,170 @@ package org.modelix.model.mpsadapters -import jetbrains.mps.smodel.MPSModuleRepository +import jetbrains.mps.project.AbstractModule +import jetbrains.mps.project.structure.modules.Dependency import org.jetbrains.mps.openapi.module.SDependencyScope import org.jetbrains.mps.openapi.module.SModule import org.jetbrains.mps.openapi.module.SModuleReference +import org.jetbrains.mps.openapi.module.SRepository import org.modelix.model.api.BuiltinLanguages -import org.modelix.model.api.IChildLink +import org.modelix.model.api.IChildLinkReference import org.modelix.model.api.IConcept -import org.modelix.model.api.INode import org.modelix.model.api.INodeReference -import org.modelix.model.api.IProperty -import org.modelix.model.area.IArea +import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReferenceLinkReference +import org.modelix.model.api.IWritableNode data class MPSModuleDependencyAsNode( + val owner: SModule, val moduleReference: SModuleReference, - val moduleVersion: Int, - val explicit: Boolean, - val reexport: Boolean, - val importer: SModule, - val dependencyScope: SDependencyScope?, -) : IDefaultNodeAdapter { +) : MPSGenericNodeAdapter() { - override fun getContainmentLink(): IChildLink { - return BuiltinLanguages.MPSRepositoryConcepts.Module.dependencies + companion object { + val propertyAccessors: List>> = listOf( + BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.explicit.toReference() to object : IPropertyAccessor { + override fun read(element: MPSModuleDependencyAsNode): String? { + val module = element.owner as AbstractModule + val descriptor = module.moduleDescriptor + if (descriptor != null) { + return descriptor.dependencies.any { it.moduleRef == element.moduleReference }.toString() + } + return module.declaredDependencies.any { it.targetModule == element.moduleReference }.toString() + } + + override fun write( + element: MPSModuleDependencyAsNode, + value: String?, + ) { + val module = element.owner as AbstractModule + val descriptor = checkNotNull(module.moduleDescriptor) { "No descriptor: $module" } + if (value?.toBoolean() == true) { + descriptor.dependencies.add(Dependency(element.moduleReference, false)) + } else { + descriptor.dependencies.removeIf { it.moduleRef == element.moduleReference } + } + } + }, + BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.name.toReference() to object : IPropertyAccessor { + override fun read(element: MPSModuleDependencyAsNode): String? { + return element.moduleReference.moduleName + } + }, + BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.reexport.toReference() to object : IPropertyAccessor { + override fun read(element: MPSModuleDependencyAsNode): String? { + return ( + element.owner.declaredDependencies + .firstOrNull { it.targetModule == element.moduleReference } + ?.isReexport + ?: false + ).toString() + } + + override fun write( + element: MPSModuleDependencyAsNode, + value: String?, + ) { + val value = value?.toBoolean() ?: false + val module = element.owner as AbstractModule + val descriptor = checkNotNull(module.moduleDescriptor) { "No descriptor: $module" } + val existing = descriptor.dependencies.firstOrNull { it.moduleRef == element.moduleReference } + if (existing != null) { + existing.isReexport = value + } else { + descriptor.dependencies.add(Dependency(element.moduleReference, value)) + } + } + }, + BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.uuid.toReference() to object : IPropertyAccessor { + override fun read(element: MPSModuleDependencyAsNode): String? { + return element.moduleReference.moduleId.toString() + } + }, + BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.version.toReference() to object : IPropertyAccessor { + override fun read(element: MPSModuleDependencyAsNode): String? { + val module = element.owner as AbstractModule + val moduleDescriptor = checkNotNull(module.moduleDescriptor) { "No descriptor: $module" } + return moduleDescriptor.dependencyVersions[element.moduleReference]?.toString() + } + + override fun write( + element: MPSModuleDependencyAsNode, + value: String?, + ) { + val value = value?.toInt() + val module = element.owner as AbstractModule + val descriptor = checkNotNull(module.moduleDescriptor) { "No descriptor: $module" } + if (value != null) { + descriptor.dependencyVersions[element.moduleReference] = value + } else { + descriptor.dependencyVersions.remove(element.moduleReference) + } + } + }, + BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency.scope.toReference() to object : IPropertyAccessor { + override fun read(element: MPSModuleDependencyAsNode): String? { + val module = element.owner as AbstractModule + val moduleDescriptor = module.moduleDescriptor + if (moduleDescriptor != null) { + return moduleDescriptor.dependencies.firstOrNull { it.moduleRef == element.moduleReference }?.scope?.toString() + } else { + return module.declaredDependencies.firstOrNull { it.targetModule == element.moduleReference }?.scope?.toString() + } + } + + override fun write( + element: MPSModuleDependencyAsNode, + value: String?, + ) { + val value = if (value == null) SDependencyScope.DEFAULT else SDependencyScope.valueOf(value) + val module = element.owner as AbstractModule + val descriptor = checkNotNull(module.moduleDescriptor) { "No descriptor: $module" } + val existing = descriptor.dependencies.firstOrNull { it.moduleRef == element.moduleReference } + if (existing != null) { + existing.scope = value + } else { + descriptor.dependencies.add(Dependency(element.moduleReference, value)) + } + } + }, + ) } - override fun getArea(): IArea { - return MPSArea(importer.repository ?: MPSModuleRepository.getInstance()) + override fun getContainmentLink(): IChildLinkReference { + return BuiltinLanguages.MPSRepositoryConcepts.Module.dependencies.toReference() } - override val reference: INodeReference - get() = MPSModuleDependencyReference( + override fun getElement(): MPSModuleDependencyAsNode { + return this + } + + override fun getRepository(): SRepository? { + return null + } + + override fun getPropertyAccessors(): List>> { + return propertyAccessors + } + + override fun getReferenceAccessors(): List>> { + return emptyList() + } + + override fun getChildAccessors(): List>> { + return emptyList() + } + + override fun getParent(): IWritableNode? { + return MPSModuleAsNode(owner) + } + + override fun getNodeReference(): INodeReference { + return MPSModuleDependencyReference( usedModuleId = moduleReference.moduleId, - userModuleReference = importer.moduleReference, + userModuleReference = owner.moduleReference, ) - override val concept: IConcept - get() = BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency - override val parent: INode - get() = MPSModuleAsNode(importer).asLegacyNode() - - override fun getPropertyValue(property: IProperty): String? { - val moduleDependency = BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency - - return if (property.conformsTo(moduleDependency.explicit)) { - explicit.toString() - } else if (property.conformsTo(moduleDependency.name)) { - moduleReference.moduleName - } else if (property.conformsTo(moduleDependency.reexport)) { - reexport.toString() - } else if (property.conformsTo(moduleDependency.uuid)) { - moduleReference.moduleId.toString() - } else if (property.conformsTo(moduleDependency.version)) { - moduleVersion.toString() - } else if (property.conformsTo(moduleDependency.scope)) { - dependencyScope?.toString() ?: "UNSPECIFIED" - } else { - null - } + } + + override fun getConcept(): IConcept { + return BuiltinLanguages.MPSRepositoryConcepts.ModuleDependency } } diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleReferenceAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleReferenceAsNode.kt new file mode 100644 index 0000000000..e8f2f2a35c --- /dev/null +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSModuleReferenceAsNode.kt @@ -0,0 +1,74 @@ +package org.modelix.model.mpsadapters + +import jetbrains.mps.smodel.MPSModuleRepository +import org.jetbrains.mps.openapi.module.SModuleId +import org.jetbrains.mps.openapi.module.SModuleReference +import org.jetbrains.mps.openapi.module.SRepository +import org.modelix.model.api.BuiltinLanguages +import org.modelix.model.api.ChildLinkReferenceByUID +import org.modelix.model.api.IChildLinkReference +import org.modelix.model.api.IConcept +import org.modelix.model.api.INodeReference +import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReferenceLinkReference +import org.modelix.model.api.IWritableNode + +data class MPSModuleReferenceAsNode( + private val parent: MPSModuleAsNode<*>, + private val containmentLink: IChildLinkReference, + val target: SModuleReference, +) : MPSGenericNodeAdapter() { + override fun getElement(): MPSModuleReferenceAsNode { + return this + } + + override fun getRepository(): SRepository? { + return parent.getRepository() + } + + override fun getPropertyAccessors(): List>> { + return emptyList() + } + + override fun getReferenceAccessors(): List>> { + return listOf( + BuiltinLanguages.MPSRepositoryConcepts.ModuleReference.module.toReference() to object : IReferenceAccessor { + override fun read(element: MPSModuleReferenceAsNode): IWritableNode? { + val repo = parent.getRepository() ?: MPSModuleRepository.getInstance() + return target.resolve(repo)?.let { MPSModuleAsNode(it) } + } + }, + ) + } + + override fun getChildAccessors(): List>> { + return emptyList() + } + + override fun getParent(): IWritableNode? { + return parent + } + + override fun getNodeReference(): INodeReference { + return MPSModuleReferenceReference(parent.module.moduleId, ChildLinkReferenceByUID(containmentLink.getUID()!!), target.moduleId) + } + + override fun getConcept(): IConcept { + return BuiltinLanguages.MPSRepositoryConcepts.ModuleReference + } + + override fun getContainmentLink(): IChildLinkReference { + return containmentLink + } +} + +data class MPSModuleReferenceReference(val parent: SModuleId, val link: ChildLinkReferenceByUID, val target: SModuleId) : INodeReference { + companion object { + internal const val PREFIX = "mps-module-ref" + internal const val SEPARATOR = "#" + } + + override fun serialize(): String { + return "$PREFIX:$parent$SEPARATOR${link.getUID()}$SEPARATOR$target" + } +} diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSProjectAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSProjectAsNode.kt index a0da63828f..4fffd10828 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSProjectAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSProjectAsNode.kt @@ -17,7 +17,7 @@ data class MPSProjectAsNode(val project: ProjectBase) : IDefaultNodeAdapter { override val concept: IConcept get() = BuiltinLanguages.MPSRepositoryConcepts.Project override val parent: INode - get() = MPSRepositoryAsNode(MPSModuleRepository.getInstance()) + get() = MPSModuleRepository.getInstance().asLegacyNode() override val allChildren: Iterable get() = getChildren(BuiltinLanguages.MPSRepositoryConcepts.Project.projectModules) diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSReferences.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSReferences.kt index aa08152277..e17e3532c5 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSReferences.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSReferences.kt @@ -34,7 +34,6 @@ data class MPSNodeReference(val ref: SNodeReference) : INodeReference { internal const val PREFIX = "mps" - @Deprecated("INodeResolutionScope.resolveNode(INodeReference) is now responsible for deserializing supported references") fun tryConvert(ref: INodeReference): MPSNodeReference? { if (ref is MPSNodeReference) return ref val serialized = ref.serialize() diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSRepositoryAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSRepositoryAsNode.kt index 3728ced39c..68d3177fa3 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSRepositoryAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSRepositoryAsNode.kt @@ -1,56 +1,100 @@ package org.modelix.model.mpsadapters +import jetbrains.mps.project.ModuleId import jetbrains.mps.project.ProjectBase import jetbrains.mps.project.ProjectManager +import jetbrains.mps.smodel.Generator import jetbrains.mps.smodel.tempmodel.TempModule import jetbrains.mps.smodel.tempmodel.TempModule2 import org.jetbrains.mps.openapi.module.SModule import org.jetbrains.mps.openapi.module.SRepository import org.modelix.model.api.BuiltinLanguages -import org.modelix.model.api.IChildLink +import org.modelix.model.api.IChildLinkReference import org.modelix.model.api.IConcept import org.modelix.model.api.INode import org.modelix.model.api.INodeReference -import org.modelix.model.api.NullChildLink -import org.modelix.model.area.IArea - -data class MPSRepositoryAsNode(val repository: SRepository) : IDefaultNodeAdapter { - - private val childrenAccessors: Map Iterable> = mapOf( - BuiltinLanguages.MPSRepositoryConcepts.Repository.modules to { repository.modules.filter { !it.isTempModule() }.map { MPSModuleAsNode(it).asLegacyNode() } }, - BuiltinLanguages.MPSRepositoryConcepts.Repository.projects to { - ProjectManager.getInstance().openedProjects - .filterIsInstance() - .map { MPSProjectAsNode(it) } - }, - BuiltinLanguages.MPSRepositoryConcepts.Repository.tempModules to { repository.modules.filter { it.isTempModule() }.map { MPSModuleAsNode(it).asLegacyNode() } }, - ) - - override fun getArea(): IArea { - return MPSArea(repository) - } +import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReadableNode +import org.modelix.model.api.IReferenceLinkReference +import org.modelix.model.api.IWritableNode +import org.modelix.model.api.NullChildLinkReference - override val reference: INodeReference - get() = MPSRepositoryReference - override val concept: IConcept - get() = BuiltinLanguages.MPSRepositoryConcepts.Repository - override val parent: INode? - get() = null +fun SRepository.asLegacyNode(): INode = MPSRepositoryAsNode(this).asLegacyNode() +fun SRepository.asWritableNode(): IWritableNode = MPSRepositoryAsNode(this) +fun SRepository.asReadableNode(): IReadableNode = MPSRepositoryAsNode(this) - override val allChildren: Iterable - get() = childrenAccessors.values.flatMap { it() } +data class MPSRepositoryAsNode(@get:JvmName("getRepository_") val repository: SRepository) : MPSGenericNodeAdapter() { - override fun getContainmentLink(): IChildLink? { - return null - } + companion object { + private val propertyAccessors = listOf>>() + private val referenceAccessors = listOf>>() + private val childAccessors = listOf>>( + BuiltinLanguages.MPSRepositoryConcepts.Repository.modules.toReference() to object : IChildAccessor { + override fun read(element: SRepository): List { + return element.modules.filter { !it.isTempModule() && it !is Generator }.map { MPSModuleAsNode(it) } + } - override fun getChildren(link: IChildLink): Iterable { - if (link is NullChildLink) return emptyList() - for (childrenAccessor in childrenAccessors) { - if (link.conformsTo(childrenAccessor.key)) return childrenAccessor.value() - } - return emptyList() + override fun addNew( + element: SRepository, + index: Int, + sourceNode: SpecWithResolvedConcept, + ): IWritableNode { + return when (sourceNode.getConceptReference()) { + BuiltinLanguages.MPSRepositoryConcepts.Solution.getReference() -> { + SolutionProducer(MPSContextProject.contextValue.getValue()).create( + sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!, + sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) }, + ).let { MPSModuleAsNode(it) } + } + BuiltinLanguages.MPSRepositoryConcepts.Language.getReference() -> { + LanguageProducer(MPSContextProject.contextValue.getValue()).create( + sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!, + sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) }, + ).let { MPSModuleAsNode(it) } + } + BuiltinLanguages.MPSRepositoryConcepts.DevKit.getReference() -> { + DevkitProducer(MPSContextProject.contextValue.getValue()).create( + sourceNode.getNode().getPropertyValue(BuiltinLanguages.jetbrains_mps_lang_core.INamedConcept.name.toReference())!!, + sourceNode.getNode().getPropertyValue(BuiltinLanguages.MPSRepositoryConcepts.Module.id.toReference())!!.let { ModuleId.fromString(it) }, + ).let { MPSModuleAsNode(it) } + } + else -> throw UnsupportedOperationException("Module type not supported yet: ${sourceNode.getConceptReference()}") + } + } + }, + BuiltinLanguages.MPSRepositoryConcepts.Repository.tempModules.toReference() to object : IChildAccessor { + override fun read(element: SRepository): List { + return element.modules.filter { it.isTempModule() }.map { MPSModuleAsNode(it) } + } + }, + BuiltinLanguages.MPSRepositoryConcepts.Repository.projects.toReference() to object : IChildAccessor { + override fun read(element: SRepository): List { + return ProjectManager.getInstance().openedProjects + .filterIsInstance() + .plus(listOfNotNull(MPSContextProject.contextValue.getValueOrNull())) + .map { MPSProjectAsNode(it).asWritableNode() } + } + }, + ) } + + override fun getElement(): SRepository = repository + + override fun getRepository(): SRepository = repository + + override fun getPropertyAccessors() = propertyAccessors + + override fun getReferenceAccessors() = referenceAccessors + + override fun getChildAccessors() = childAccessors + + override fun getParent(): IWritableNode? = null + + override fun getNodeReference(): INodeReference = MPSRepositoryReference + + override fun getConcept(): IConcept = BuiltinLanguages.MPSRepositoryConcepts.Repository + + override fun getContainmentLink(): IChildLinkReference = NullChildLinkReference } private fun SModule.isTempModule(): Boolean = this is TempModule || this is TempModule2 diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSSingleLanguageDependencyAsNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSSingleLanguageDependencyAsNode.kt index 9d045a0c87..841c76ac86 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSSingleLanguageDependencyAsNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSSingleLanguageDependencyAsNode.kt @@ -1,65 +1,111 @@ package org.modelix.model.mpsadapters +import jetbrains.mps.extapi.model.SModelDescriptorStub +import jetbrains.mps.project.AbstractModule +import org.jetbrains.mps.openapi.language.SLanguage import org.jetbrains.mps.openapi.model.SModel import org.jetbrains.mps.openapi.module.SModule -import org.jetbrains.mps.openapi.module.SModuleReference +import org.jetbrains.mps.openapi.module.SRepository import org.modelix.model.api.BuiltinLanguages -import org.modelix.model.api.IChildLink +import org.modelix.model.api.IChildLinkReference import org.modelix.model.api.IConcept -import org.modelix.model.api.INode import org.modelix.model.api.INodeReference -import org.modelix.model.api.IProperty -import org.modelix.model.area.IArea +import org.modelix.model.api.IPropertyReference +import org.modelix.model.api.IReferenceLinkReference +import org.modelix.model.api.IWritableNode data class MPSSingleLanguageDependencyAsNode( - val moduleReference: SModuleReference, - val languageVersion: Int, + val moduleReference: SLanguage, val moduleImporter: SModule? = null, val modelImporter: SModel? = null, -) : IDefaultNodeAdapter { +) : MPSGenericNodeAdapter() { - override fun getPropertyValue(property: IProperty): String? { - return if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency.version)) { - languageVersion.toString() - } else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name)) { - moduleReference.moduleName - } else if (property.conformsTo(BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid)) { - moduleReference.moduleId.toString() - } else { - null - } + companion object { + private val propertyAccessors = listOf>>( + BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency.version.toReference() to object : IPropertyAccessor { + override fun read(element: MPSSingleLanguageDependencyAsNode): String? { + val version = element.moduleImporter?.let { + val module = (it as? AbstractModule) ?: return@let null + val descriptor = module.moduleDescriptor ?: return@let null + descriptor.languageVersions[element.moduleReference] + } ?: element.modelImporter?.let { + val model = it as? SModelDescriptorStub ?: return@let null + model.getLanguageImportVersion(element.moduleReference).takeIf { it != -1 } + } ?: return null + return version.toString() + } + + override fun write(element: MPSSingleLanguageDependencyAsNode, value: String?) { + val version = value?.toInt() ?: -1 + if (element.moduleImporter != null) { + val module = element.moduleImporter as AbstractModule + val descriptor = checkNotNull(module.moduleDescriptor) { + "Has no module descriptor: ${element.moduleImporter}" + } + descriptor.languageVersions[element.moduleReference] = version + } else if (element.modelImporter != null) { + val model = element.modelImporter as SModelDescriptorStub + model.setLanguageImportVersion(element.moduleReference, version) + } else { + error("No importing module or model specified") + } + } + }, + BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.name.toReference() to object : IPropertyAccessor { + override fun read(element: MPSSingleLanguageDependencyAsNode): String? { + return element.moduleReference.qualifiedName + } + }, + BuiltinLanguages.MPSRepositoryConcepts.LanguageDependency.uuid.toReference() to object : IPropertyAccessor { + override fun read(element: MPSSingleLanguageDependencyAsNode): String? { + return element.moduleReference.sourceModuleReference.moduleId.toString() + } + }, + + ) + private val referenceAccessors = listOf>>() + private val childAccessors = listOf>>() } - override fun getContainmentLink(): IChildLink { + override fun getContainmentLink(): IChildLinkReference { return if (moduleImporter != null) { - BuiltinLanguages.MPSRepositoryConcepts.Module.languageDependencies + BuiltinLanguages.MPSRepositoryConcepts.Module.languageDependencies.toReference() } else if (modelImporter != null) { - BuiltinLanguages.MPSRepositoryConcepts.Model.usedLanguages + BuiltinLanguages.MPSRepositoryConcepts.Model.usedLanguages.toReference() } else { error("No importer found for $this") } } - override fun getArea(): IArea { - val repo = moduleImporter?.repository ?: modelImporter?.repository - checkNotNull(repo) { "No importer found for $this" } - return MPSArea(repo) - } + override fun getElement(): MPSSingleLanguageDependencyAsNode = this - override val reference: INodeReference - get() = MPSSingleLanguageDependencyReference( - moduleReference.moduleId, - userModule = moduleImporter?.moduleReference, - userModel = modelImporter?.reference, - ) - override val concept: IConcept - get() = BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency - override val parent: INode - get() = if (moduleImporter != null) { - MPSModuleAsNode(moduleImporter).asLegacyNode() + override fun getRepository(): SRepository? = null + + override fun getPropertyAccessors() = propertyAccessors + + override fun getReferenceAccessors() = referenceAccessors + + override fun getChildAccessors() = childAccessors + + override fun getParent(): IWritableNode? { + return if (moduleImporter != null) { + MPSModuleAsNode(moduleImporter) } else if (modelImporter != null) { - MPSModelAsNode(modelImporter).asLegacyNode() + MPSModelAsNode(modelImporter) } else { error("No importer found for $this") } + } + + override fun getNodeReference(): INodeReference { + return MPSSingleLanguageDependencyReference( + moduleReference.sourceModuleReference.moduleId, + userModule = moduleImporter?.moduleReference, + userModel = modelImporter?.reference, + ) + } + + override fun getConcept(): IConcept { + return BuiltinLanguages.MPSRepositoryConcepts.SingleLanguageDependency + } } diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSWritableNode.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSWritableNode.kt index 7ad0f3c479..b098963ffd 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSWritableNode.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/MPSWritableNode.kt @@ -2,10 +2,12 @@ package org.modelix.model.mpsadapters import jetbrains.mps.smodel.MPSModuleRepository import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration +import org.jetbrains.mps.openapi.language.SConcept import org.jetbrains.mps.openapi.language.SContainmentLink import org.jetbrains.mps.openapi.language.SProperty import org.jetbrains.mps.openapi.language.SReferenceLink import org.jetbrains.mps.openapi.model.SNode +import org.jetbrains.mps.openapi.module.SRepository import org.modelix.incremental.DependencyTracking import org.modelix.model.api.BuiltinLanguages import org.modelix.model.api.ConceptReference @@ -17,10 +19,12 @@ import org.modelix.model.api.IPropertyReference import org.modelix.model.api.IReferenceLinkReference import org.modelix.model.api.IRoleReferenceByName import org.modelix.model.api.IRoleReferenceByUID +import org.modelix.model.api.ISyncTargetNode import org.modelix.model.api.IWritableNode +import org.modelix.model.api.NewNodeSpec import org.modelix.model.api.meta.NullConcept -data class MPSWritableNode(val node: SNode) : IWritableNode { +data class MPSWritableNode(val node: SNode) : IWritableNode, ISyncTargetNode { override fun getModel(): IMutableModel { return MPSArea(node.model?.repository ?: MPSModuleRepository.getInstance()).asModel() } @@ -142,17 +146,13 @@ data class MPSWritableNode(val node: SNode) : IWritableNode { } override fun addNewChildren(role: IChildLinkReference, index: Int, concepts: List): List { + return syncNewChildren(role, index, concepts.map { NewNodeSpec(conceptRef = it) }) + } + + override fun syncNewChildren(role: IChildLinkReference, index: Int, specs: List): List { val repo = node.model?.repository ?: MPSModuleRepository.getInstance() - val resolvedConcepts = concepts.distinct().associateWith { concept -> - requireNotNull( - concept.let { - MPSLanguageRepository(repo).resolveConcept(it.getUID()) - ?: MPSConcept.tryParseUID(it.getUID()) - }?.concept?.let { MetaAdapterByDeclaration.asInstanceConcept(it) }, - ) { - // A null value for the concept would default to BaseConcept, but then BaseConcept should be used explicitly. - "MPS concept not found: $concept" - } + val resolvedConcepts = specs.distinct().associate { spec -> + spec.conceptRef to repo.resolveConcept(spec.conceptRef) } val link = resolve(role) @@ -162,12 +162,17 @@ data class MPSWritableNode(val node: SNode) : IWritableNode { val anchor = if (index == -1 || index == children.size) null else children[index] val model = node.model - return concepts.map { conceptRef -> - val resolvedConcept = checkNotNull(resolvedConcepts[conceptRef]) {} + return specs.map { spec -> + val resolvedConcept = checkNotNull(resolvedConcepts[spec.conceptRef]) + val preferredId = spec.preferredNodeReference?.let { MPSNodeReference.tryConvert(it) }?.ref?.nodeId val newChild = if (model == null) { - jetbrains.mps.smodel.SNode(resolvedConcept) + if (preferredId == null) { + jetbrains.mps.smodel.SNode(resolvedConcept) + } else { + jetbrains.mps.smodel.SNode(resolvedConcept, preferredId) + } } else { - model.createNode(resolvedConcept) + model.createNode(resolvedConcept, preferredId) } if (anchor == null) { @@ -278,3 +283,15 @@ data class MPSWritableNode(val node: SNode) : IWritableNode { ?: MPSChildLink.fromReference(link).link } } + +fun SRepository.resolveConcept(concept: ConceptReference): SConcept { + return requireNotNull( + concept.let { + MPSLanguageRepository(this).resolveConcept(it.getUID()) + ?: MPSConcept.tryParseUID(it.getUID()) + }?.concept?.let { MetaAdapterByDeclaration.asInstanceConcept(it) }, + ) { + // A null value for the concept would default to BaseConcept, but then BaseConcept should be used explicitly. + "MPS concept not found: $concept" + } +} diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/ModelPersistenceWithFixedId.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/ModelPersistenceWithFixedId.kt new file mode 100644 index 0000000000..1cb9271b58 --- /dev/null +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/ModelPersistenceWithFixedId.kt @@ -0,0 +1,93 @@ +package org.modelix.model.mpsadapters + +import jetbrains.mps.extapi.model.SModelData +import jetbrains.mps.persistence.DefaultModelPersistence +import jetbrains.mps.persistence.DefaultModelRoot +import jetbrains.mps.persistence.LazyLoadFacility +import jetbrains.mps.smodel.DefaultSModel +import jetbrains.mps.smodel.DefaultSModelDescriptor +import jetbrains.mps.smodel.SModel +import jetbrains.mps.smodel.SModelHeader +import jetbrains.mps.smodel.loading.ModelLoadResult +import jetbrains.mps.smodel.loading.ModelLoadingState +import jetbrains.mps.smodel.persistence.def.ModelPersistence +import jetbrains.mps.smodel.persistence.def.ModelReadException +import org.jetbrains.mps.openapi.model.SModelId +import org.jetbrains.mps.openapi.model.SModelName +import org.jetbrains.mps.openapi.model.SModelReference +import org.jetbrains.mps.openapi.module.SModule +import org.jetbrains.mps.openapi.module.SModuleReference +import org.jetbrains.mps.openapi.persistence.DataSource +import org.jetbrains.mps.openapi.persistence.ModelLoadingOption +import org.jetbrains.mps.openapi.persistence.ModelSaveException +import org.jetbrains.mps.openapi.persistence.PersistenceFacade +import org.jetbrains.mps.openapi.persistence.StreamDataSource +import org.jetbrains.mps.openapi.persistence.UnsupportedDataSourceException +import java.io.IOException + +/** + * Uses the provided model ID instead of SModelId.generate(). + * Everything else is just copied from DefaultModelPersistence. + */ +open class ModelPersistenceWithFixedId(val moduleRef: SModuleReference, val modelId: SModelId) : + DefaultModelPersistence() { + + @Throws(UnsupportedDataSourceException::class) + override fun create( + dataSource: DataSource, + modelName: SModelName, + vararg options: ModelLoadingOption, + ): org.jetbrains.mps.openapi.model.SModel { + if (!supports(dataSource)) { + throw UnsupportedDataSourceException(dataSource) + } + val header = SModelHeader.create(ModelPersistence.LAST_VERSION) + // We could provide a module ID to createModelReference, but MPS also doesn't provide one when creating a model. + val modelReference: SModelReference = + PersistenceFacade.getInstance().createModelReference(null, modelId, modelName.value) + header.modelReference = modelReference + val rv = DefaultSModelDescriptor(ModelPersistenceFacility(this, dataSource as StreamDataSource), header) + if (dataSource.getTimestamp() != -1L) { + rv.replace(DefaultSModel(modelReference, header)) + } + return rv + } +} + +open class ModelPersistenceFacility(modelFactory: DefaultModelPersistence, dataSource: StreamDataSource) : + LazyLoadFacility(modelFactory, dataSource, true) { + protected val source0: StreamDataSource + get() = super.getSource() as StreamDataSource + + @Throws(ModelReadException::class) + override fun readHeader(): SModelHeader { + return ModelPersistence.loadDescriptor(source0) + } + + @Throws(ModelReadException::class) + override fun readModel(header: SModelHeader, state: ModelLoadingState): ModelLoadResult { + return ModelPersistence.readModel(header, source0, state) + } + + override fun doesSaveUpgradePersistence(header: SModelHeader): Boolean { + // not sure !=-1 is really needed, just left to be ensured about compatibility + return header.persistenceVersion != ModelPersistence.LAST_VERSION && header.persistenceVersion != -1 + } + + @Throws(IOException::class) + override fun saveModel(header: SModelHeader, modelData: SModelData) { + try { + ModelPersistence.saveModel(modelData as SModel, source0, header.persistenceVersion) + } catch (e: ModelSaveException) { + throw RuntimeException(e) + } + } +} + +internal fun SModule.createModel(name: String, id: SModelId): org.jetbrains.mps.openapi.model.SModel { + val modelName = SModelName(name) + val modelRoot = checkNotNull(this.modelRoots.filterIsInstance().firstOrNull { it.canCreateModel(modelName) }) { + "$this contains no model roots for creating new models" + } + return modelRoot.createModel(modelName, null, null, ModelPersistenceWithFixedId(this.moduleReference, id)) +} diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/SolutionProducer.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/SolutionProducer.kt new file mode 100644 index 0000000000..c81f8e5460 --- /dev/null +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/SolutionProducer.kt @@ -0,0 +1,164 @@ +package org.modelix.model.mpsadapters + +import jetbrains.mps.ide.project.ProjectHelper +import jetbrains.mps.persistence.DefaultModelRoot +import jetbrains.mps.project.DevKit +import jetbrains.mps.project.MPSExtentions +import jetbrains.mps.project.MPSProject +import jetbrains.mps.project.ModuleId +import jetbrains.mps.project.Solution +import jetbrains.mps.project.structure.modules.DevkitDescriptor +import jetbrains.mps.project.structure.modules.GeneratorDescriptor +import jetbrains.mps.project.structure.modules.LanguageDescriptor +import jetbrains.mps.project.structure.modules.SolutionDescriptor +import jetbrains.mps.smodel.GeneralModuleFactory +import jetbrains.mps.smodel.Generator +import jetbrains.mps.smodel.Language +import jetbrains.mps.smodel.ModuleDependencyVersions +import jetbrains.mps.smodel.language.LanguageRegistry +import jetbrains.mps.vfs.IFile + +class SolutionProducer(private val myProject: MPSProject) { + + fun create(name: String, id: ModuleId): Solution { + val basePath = checkNotNull(ProjectHelper.toIdeaProject(myProject).getBasePath()) { "Project has no base path: $myProject" } + val projectBaseDir = myProject.getFileSystem().getFile(basePath) + val solutionBaseDir = projectBaseDir.findChild("solutions").findChild(name) + return create(name, id, solutionBaseDir) + } + + fun create(namespace: String, id: ModuleId, moduleDir: IFile): Solution { + val descriptorFile = moduleDir.findChild(namespace + MPSExtentions.DOT_SOLUTION) + val descriptor: SolutionDescriptor = createSolutionDescriptor(namespace, id, descriptorFile) + val module = GeneralModuleFactory().instantiate(descriptor, descriptorFile) as Solution + myProject.addModule(module) + ModuleDependencyVersions( + myProject.getComponent(LanguageRegistry::class.java), + myProject.repository, + ).update(module) + module.save() + return module + } + + private fun createSolutionDescriptor(namespace: String, id: ModuleId, descriptorFile: IFile): SolutionDescriptor { + val descriptor = SolutionDescriptor() + descriptor.namespace = namespace + descriptor.id = id + val moduleLocation = descriptorFile.parent + val modelsDir = moduleLocation!!.findChild(Solution.SOLUTION_MODELS) + check(!(modelsDir.exists() && modelsDir.children?.isNotEmpty() == true)) { + "Trying to create a solution in an existing solution's directory: $moduleLocation" + } + + modelsDir.mkdirs() + + descriptor.modelRootDescriptors.add(DefaultModelRoot.createDescriptor(modelsDir.parent!!, modelsDir)) + descriptor.outputPath = descriptorFile.parent!!.findChild("source_gen").path + return descriptor + } +} + +class LanguageProducer(private val myProject: MPSProject) { + + fun create(name: String, id: ModuleId): Language { + val basePath = checkNotNull(ProjectHelper.toIdeaProject(myProject).getBasePath()) { "Project has no base path: $myProject" } + val projectBaseDir = myProject.getFileSystem().getFile(basePath) + val solutionBaseDir = projectBaseDir.findChild("languages").findChild(name) + return create(name, id, solutionBaseDir) + } + + fun create(namespace: String, id: ModuleId, moduleDir: IFile): Language { + val descriptorFile = moduleDir.findChild(namespace + MPSExtentions.DOT_LANGUAGE) + val descriptor = createDescriptor(namespace, id, descriptorFile) + val module = GeneralModuleFactory().instantiate(descriptor, descriptorFile) as Language + myProject.addModule(module) + ModuleDependencyVersions( + myProject.getComponent(LanguageRegistry::class.java), + myProject.repository, + ).update(module) + module.save() + return module + } + + private fun createDescriptor(namespace: String, id: ModuleId, descriptorFile: IFile): LanguageDescriptor { + val descriptor = LanguageDescriptor() + descriptor.namespace = namespace + descriptor.id = id + val moduleLocation = descriptorFile.parent + val modelsDir = moduleLocation!!.findChild(Language.LANGUAGE_MODELS) + check(!(modelsDir.exists() && modelsDir.children?.isNotEmpty() == true)) { + "Trying to create a language in an existing language's directory: $moduleLocation" + } + + modelsDir.mkdirs() + + descriptor.modelRootDescriptors.add(DefaultModelRoot.createDescriptor(modelsDir.parent!!, modelsDir)) + return descriptor + } +} + +class GeneratorProducer(private val myProject: MPSProject) { + + fun create(language: Language, name: String, id: ModuleId): Generator { + val basePath = checkNotNull(ProjectHelper.toIdeaProject(myProject).getBasePath()) { "Project has no base path: $myProject" } + val projectBaseDir = myProject.fileSystem.getFile(basePath) + val solutionBaseDir = projectBaseDir.findChild("languages").findChild(language.moduleName!!) + return create(language, name, id, solutionBaseDir) + } + + fun create(language: Language, namespace: String, id: ModuleId, languageModuleDir: IFile): Generator { + val generatorLocation: IFile = languageModuleDir.findChild("generator") + generatorLocation.mkdirs() + + val descriptor = createDescriptor(namespace, id, generatorLocation, null) + descriptor.sourceLanguage = language.moduleReference + language.moduleDescriptor.generators.add(descriptor) + language.setModuleDescriptor(language.moduleDescriptor) // instantiate generator module + + return language.generators.first { it.moduleReference.moduleId == id } + } + + private fun createDescriptor(namespace: String, id: ModuleId, generatorModuleLocation: IFile, templateModelsLocation: IFile?): GeneratorDescriptor { + val descriptor = GeneratorDescriptor() + descriptor.namespace = namespace + descriptor.id = id + descriptor.alias = "main" + val modelRoot = if (templateModelsLocation == null) { + DefaultModelRoot.createDescriptor(generatorModuleLocation, generatorModuleLocation.findChild("templates")) + } else { + DefaultModelRoot.createSingleFolderDescriptor(templateModelsLocation) + } + descriptor.modelRootDescriptors.add(modelRoot) + return descriptor + } +} + +class DevkitProducer(private val myProject: MPSProject) { + + fun create(name: String, id: ModuleId): DevKit { + val basePath = checkNotNull(ProjectHelper.toIdeaProject(myProject).getBasePath()) { "Project has no base path: $myProject" } + val projectBaseDir = myProject.getFileSystem().getFile(basePath) + val solutionBaseDir = projectBaseDir.findChild("devkits").findChild(name) + return create(name, id, solutionBaseDir) + } + + fun create(namespace: String, id: ModuleId, moduleDir: IFile): DevKit { + val descriptorFile = moduleDir.findChild(namespace + MPSExtentions.DOT_DEVKIT) + val descriptor = createDescriptor(namespace, id, descriptorFile) + val module = GeneralModuleFactory().instantiate(descriptor, descriptorFile) as DevKit + myProject.addModule(module) + ModuleDependencyVersions( + myProject.getComponent(LanguageRegistry::class.java), + myProject.repository, + ).update(module) + module.save() + return module + } + + private fun createDescriptor(namespace: String, id: ModuleId, descriptorFile: IFile): DevkitDescriptor { + val descriptor = DevkitDescriptor() + descriptor.namespace = namespace + descriptor.id = id + return descriptor + } +} diff --git a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/Util.kt b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/Util.kt index 174f9f325d..3df54a4a10 100644 --- a/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/Util.kt +++ b/mps-model-adapters/src/main/kotlin/org/modelix/model/mpsadapters/Util.kt @@ -4,6 +4,7 @@ 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.api.matches // statically ensures that receiver and parameter are of the same type internal fun IChildLink.conformsTo(other: IChildLink) = conformsToRole(other) @@ -11,7 +12,5 @@ internal fun IReferenceLink.conformsTo(other: IReferenceLink) = conformsToRole(o internal fun IProperty.conformsTo(other: IProperty) = conformsToRole(other) private fun IRole.conformsToRole(other: IRole): Boolean { - return this.getUID().endsWith(other.getUID()) || - this.getUID().contains(other.getSimpleName()) || - this.getSimpleName() == other.getSimpleName() + return this.toReference().matches(other.toReference()) }