Skip to content

Commit 588d73b

Browse files
committed
feat(model-api-gen-gradle): configurable super interface for concept meta properties
1 parent 0fde227 commit 588d73b

File tree

7 files changed

+66
-39
lines changed

7 files changed

+66
-39
lines changed

model-api-gen-gradle-test/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ metamodel {
104104
typedNodeImpl.suffix = "Impl"
105105
}
106106
registrationHelperName = "org.modelix.apigen.test.ApigenTestLanguages"
107+
conceptPropertiesInterfaceName = "org.modelix.apigen.test.IMetaConceptProperties"
107108
}
108109

109110
node {

model-api-gen-gradle-test/src/test/kotlin/org/modelix/metamodel/generator/GeneratedApiTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import jetbrains.mps.lang.core.L_jetbrains_mps_lang_core
99
import jetbrains.mps.lang.editor.C_FontStyleStyleClassItem
1010
import jetbrains.mps.lang.editor.L_jetbrains_mps_lang_editor
1111
import jetbrains.mps.lang.editor._FontStyle_Enum
12+
import org.modelix.apigen.test.IMetaConceptProperties
1213
import org.modelix.metamodel.IPropertyValueEnum
1314
import org.modelix.metamodel.TypedLanguagesRegistry
1415
import org.modelix.metamodel.typed
@@ -72,7 +73,7 @@ class GeneratedApiTest {
7273

7374
@Test
7475
fun `metaProperty alias is generated`() {
75-
val hasAlias = C_BaseConcept::class.members.any { it.name == ConceptData.ALIAS_KEY }
76+
val hasAlias = IMetaConceptProperties::class.members.any { it.name == ConceptData.ALIAS_KEY }
7677
assertTrue(hasAlias)
7778
assertNull(C_BaseConcept.alias)
7879
}

model-api-gen-gradle/src/main/kotlin/org/modelix/metamodel/gradle/GenerateMetaModelSources.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ abstract class GenerateMetaModelSources @Inject constructor(of: ObjectFactory) :
5252
@Optional
5353
val registrationHelperName: Property<String> = of.property(String::class.java)
5454

55+
@get:Input
56+
@Optional
57+
val conceptPropertiesInterfaceName: Property<String> = of.property(String::class.java)
58+
5559
@get: Input
5660
val nameConfig: Property<NameConfig> = of.property(NameConfig::class.java)
5761

@@ -100,6 +104,7 @@ abstract class GenerateMetaModelSources @Inject constructor(of: ObjectFactory) :
100104
kotlinOutputDir.toPath(),
101105
nameConfig.get(),
102106
this.modelqlKotlinOutputDir.orNull?.asFile?.toPath(),
107+
conceptPropertiesInterfaceName.orNull,
103108
)
104109
generator.generate(processedLanguages)
105110
registrationHelperName.orNull?.let {

model-api-gen-gradle/src/main/kotlin/org/modelix/metamodel/gradle/MetaModelGradlePlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class MetaModelGradlePlugin : Plugin<Project> {
9898
task.exportedLanguagesDir.set(exportedLanguagesDir)
9999
}
100100
settings.registrationHelperName?.let { task.registrationHelperName.set(it) }
101+
settings.conceptPropertiesInterfaceName?.let { task.conceptPropertiesInterfaceName.set(it) }
101102
task.nameConfig.set(settings.nameConfig)
102103
}
103104
}

model-api-gen-gradle/src/main/kotlin/org/modelix/metamodel/gradle/MetaModelGradleSettings.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ open class MetaModelGradleSettings {
3030
}
3131
var typescriptDir: File? = null
3232
var registrationHelperName: String? = null
33+
var conceptPropertiesInterfaceName: String? = null
3334
val taskDependencies: MutableList<Any> = ArrayList()
3435

3536
internal val nameConfig = NameConfig()

model-api-gen/src/main/kotlin/org/modelix/metamodel/generator/GeneratorInput.kt

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
3636
private lateinit var uid2language: Map<String, ProcessedLanguage>
3737
private lateinit var fqName2concept: Map<String, ProcessedConcept>
3838
private lateinit var uid2concept: Map<String, ProcessedConcept>
39-
private lateinit var conceptFqName2RootMetaProperties: MutableMap<String, MutableSet<String>>
39+
private lateinit var conceptMetaProperties: MutableSet<String>
4040

4141
init {
4242
load(dataList)
@@ -63,18 +63,14 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
6363
initIndexes()
6464
resolveConceptReferences()
6565
fixRoleConflicts()
66-
propagateMetaProperties()
66+
collectConceptMetaProperties()
6767
}
6868

69-
private fun propagateMetaProperties() {
70-
conceptFqName2RootMetaProperties = mutableMapOf()
69+
private fun collectConceptMetaProperties() {
70+
conceptMetaProperties = mutableSetOf()
7171
val concepts = languages.flatMap { it.getConcepts() }
72-
concepts.forEach { concept ->
73-
val keys = concept.metaProperties.keys
74-
// TODO the computation of rootConcept will visit some concepts multiple times across loop iterations; could potentially be optimized
75-
val rootConcept = concept.getNonInterfaceRootConcept()
76-
conceptFqName2RootMetaProperties.getOrPut(rootConcept.fqName()) { mutableSetOf() }.addAll(keys)
77-
}
72+
val keys = concepts.flatMap { it.metaProperties.keys }.toSet()
73+
conceptMetaProperties.addAll(keys)
7874
}
7975

8076
private fun initIndexes() {
@@ -151,9 +147,7 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
151147
return languages
152148
}
153149

154-
fun getMetaPropertyRoots(fqConceptName: String): Set<String> {
155-
return conceptFqName2RootMetaProperties[fqConceptName] ?: emptySet()
156-
}
150+
fun getConceptMetaProperties() = conceptMetaProperties
157151
}
158152

159153
internal class ProcessedLanguage(var name: String, var uid: String?) {
@@ -278,17 +272,6 @@ internal class ProcessedConcept(
278272
fun getAllSuperConcepts(): Sequence<ProcessedConcept> = getAllSuperConcepts_().distinct()
279273
fun getAllSuperConceptsAndSelf(): Sequence<ProcessedConcept> = getAllSuperConceptsAndSelf_().distinct()
280274
fun getDuplicateSuperConcepts() = getAllSuperConcepts_().groupBy { it }.filter { it.value.size > 1 }.map { it.key }
281-
282-
fun getNonInterfaceSuperConcept() = getDirectSuperConcepts().firstOrNull()
283-
fun getNonInterfaceRootConcept(): ProcessedConcept {
284-
var current: ProcessedConcept? = this
285-
var result = this
286-
while (current != null) {
287-
result = current
288-
current = current.getNonInterfaceSuperConcept()
289-
}
290-
return result
291-
}
292275
}
293276

294277
internal sealed class ProcessedRole(

model-api-gen/src/main/kotlin/org/modelix/metamodel/generator/MetaModelGenerator.kt

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ import org.modelix.modelql.typed.TypedModelQL
5656
import java.nio.file.Path
5757
import kotlin.reflect.KClass
5858

59-
class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameConfig(), val modelqlOutputDir: Path? = null) {
59+
class MetaModelGenerator(
60+
val outputDir: Path,
61+
val nameConfig: NameConfig = NameConfig(),
62+
val modelqlOutputDir: Path? = null,
63+
val conceptPropertiesInterfaceName: String? = null,
64+
) {
6065
var alwaysUseNonNullableProperties: Boolean = true
6166

6267
private val headerComment = "\ngenerated by modelix model-api-gen \n"
@@ -117,11 +122,41 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
117122
.write()
118123
}
119124

125+
private fun generateConceptMetaPropertiesInterface(languages: IProcessedLanguageSet) {
126+
val fqName = checkNotNull(conceptPropertiesInterfaceName)
127+
val interfaceName = ClassName(fqName.substringBeforeLast("."), fqName.substringAfterLast("."))
128+
val metaPropertiesInterface = TypeSpec.interfaceBuilder(interfaceName)
129+
.generateMetaProperties(languages as ProcessedLanguageSet)
130+
.build()
131+
132+
FileSpec.builder(interfaceName.packageName, interfaceName.simpleName)
133+
.addFileComment(headerComment)
134+
.addType(metaPropertiesInterface)
135+
.build()
136+
.write()
137+
}
138+
139+
private fun TypeSpec.Builder.generateMetaProperties(languages: ProcessedLanguageSet): TypeSpec.Builder {
140+
val nullGetter = FunSpec.getterBuilder().addCode("return null").build()
141+
languages.getConceptMetaProperties().forEach {
142+
addProperty(
143+
PropertySpec.builder(it, String::class.asTypeName().copy(nullable = true))
144+
.getter(nullGetter)
145+
.build(),
146+
)
147+
}
148+
return this
149+
}
150+
120151
fun generate(languages: IProcessedLanguageSet) {
121152
generate(languages as ProcessedLanguageSet)
122153
}
123154

124155
private fun generate(languages: ProcessedLanguageSet) {
156+
if (conceptPropertiesInterfaceName != null) {
157+
generateConceptMetaPropertiesInterface(languages)
158+
}
159+
125160
for (language in languages.getLanguages()) {
126161
language.packageDir().toFile().listFiles()?.filter { it.isFile }?.forEach { it.delete() }
127162
val builder =
@@ -648,12 +683,10 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
648683
addSuperinterface(extended.conceptWrapperInterfaceClass().parameterizedBy(nodeT))
649684
}
650685

651-
for (metaPropertyName in concept.language.languageSet.getMetaPropertyRoots(concept.fqName())) {
652-
addProperty(
653-
PropertySpec.builder(metaPropertyName, String::class.asTypeName().copy(nullable = true))
654-
.getter(FunSpec.getterBuilder().addCode("return null").build())
655-
.build(),
656-
)
686+
if (conceptPropertiesInterfaceName != null && concept.extends.isEmpty()) {
687+
val pckgName = conceptPropertiesInterfaceName.substringBeforeLast(".")
688+
val interfaceName = conceptPropertiesInterfaceName.substringAfterLast(".")
689+
addSuperinterface(ClassName(pckgName, interfaceName))
657690
}
658691

659692
for (feature in concept.getOwnRoles()) {
@@ -703,13 +736,15 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
703736
.addStatement("return %T", concept.conceptObjectType())
704737
.build(),
705738
)
706-
concept.metaProperties.forEach { (key, value) ->
707-
addProperty(
708-
PropertySpec.builder(key, String::class.asTypeName())
709-
.addModifiers(KModifier.OVERRIDE)
710-
.initializer("%S", value)
711-
.build(),
712-
)
739+
if (conceptPropertiesInterfaceName != null) {
740+
concept.metaProperties.forEach { (key, value) ->
741+
addProperty(
742+
PropertySpec.builder(key, String::class.asTypeName())
743+
.addModifiers(KModifier.OVERRIDE)
744+
.initializer("%S", value)
745+
.build(),
746+
)
747+
}
713748
}
714749
}.build(),
715750
)

0 commit comments

Comments
 (0)