Skip to content

Commit 675cf40

Browse files
authored
Merge pull request #242 from modelix/feature/model-api-gen-concept-metadata
MODELIX-520 Make concept alias available in generated source code
2 parents 2049ba0 + 7f58125 commit 675cf40

File tree

11 files changed

+189
-15
lines changed

11 files changed

+189
-15
lines changed

docs/global/modules/core/pages/reference/component-model-api-gen-gradle.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,12 @@ Inside of the `metamodel` block the following settings can be configured.
100100

101101
|`registrationHelperName`
102102
|String
103-
|Name of the registration helper
103+
|Fully qualified name of the generated language registration helper
104+
105+
|`conceptPropertiesInterfaceName`
106+
|String
107+
|Fully qualified name of the generated interface, that contains the concept meta-properties of this language set.
108+
If `null` (default), neither the concept meta-properties nor the corresponding interface will be generated.
104109

105110
|`taskDependencies`
106111
|List<Any>

metamodel-export/org.modelix.metamodel.export/models/org.modelix.metamodel.export.mps

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1748,11 +1748,70 @@
17481748
</node>
17491749
</node>
17501750
</node>
1751+
<node concept="3cpWs8" id="3FfIz6$e8S2" role="3cqZAp">
1752+
<node concept="3cpWsn" id="3FfIz6$e8S5" role="3cpWs9">
1753+
<property role="TrG5h" value="metaProperties" />
1754+
<node concept="3rvAFt" id="3FfIz6$e8RW" role="1tU5fm">
1755+
<node concept="3uibUv" id="3FfIz6$eb7L" role="3rvQeY">
1756+
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
1757+
</node>
1758+
<node concept="3uibUv" id="3FfIz6$edJQ" role="3rvSg0">
1759+
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
1760+
</node>
1761+
</node>
1762+
<node concept="2ShNRf" id="BwT8EA3$Cq" role="33vP2m">
1763+
<node concept="3rGOSV" id="BwT8EA3$BR" role="2ShVmc">
1764+
<node concept="3uibUv" id="BwT8EA3$BS" role="3rHrn6">
1765+
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
1766+
</node>
1767+
<node concept="3uibUv" id="BwT8EA3$BT" role="3rHtpV">
1768+
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
1769+
</node>
1770+
</node>
1771+
</node>
1772+
</node>
1773+
</node>
1774+
<node concept="3clFbJ" id="3pYupGUiJ_M" role="3cqZAp">
1775+
<node concept="3clFbS" id="3pYupGUiJ_O" role="3clFbx">
1776+
<node concept="3clFbF" id="3pYupGUieI_" role="3cqZAp">
1777+
<node concept="37vLTI" id="3pYupGUiqAl" role="3clFbG">
1778+
<node concept="2OqwBi" id="3pYupGUivav" role="37vLTx">
1779+
<node concept="37vLTw" id="3pYupGUitvJ" role="2Oq$k0">
1780+
<ref role="3cqZAo" node="3Fg0S50cWmY" resolve="concept" />
1781+
</node>
1782+
<node concept="3TrcHB" id="3pYupGUixB3" role="2OqNvi">
1783+
<ref role="3TsBF5" to="tpce:4qF2Hm2r7ja" resolve="conceptAlias" />
1784+
</node>
1785+
</node>
1786+
<node concept="3EllGN" id="3pYupGUikWF" role="37vLTJ">
1787+
<node concept="10M0yZ" id="3pYupGUipfJ" role="3ElVtu">
1788+
<ref role="3cqZAo" to="sgfj:~ConceptData.ALIAS_KEY" resolve="ALIAS_KEY" />
1789+
<ref role="1PxDUh" to="sgfj:~ConceptData" resolve="ConceptData" />
1790+
</node>
1791+
<node concept="37vLTw" id="3pYupGUieIz" role="3ElQJh">
1792+
<ref role="3cqZAo" node="3FfIz6$e8S5" resolve="metaProperties" />
1793+
</node>
1794+
</node>
1795+
</node>
1796+
</node>
1797+
</node>
1798+
<node concept="3y3z36" id="3pYupGUjlay" role="3clFbw">
1799+
<node concept="2OqwBi" id="3pYupGUiNt1" role="3uHU7B">
1800+
<node concept="37vLTw" id="3pYupGUiLEL" role="2Oq$k0">
1801+
<ref role="3cqZAo" node="3Fg0S50cWmY" resolve="concept" />
1802+
</node>
1803+
<node concept="3TrcHB" id="3pYupGUiPnm" role="2OqNvi">
1804+
<ref role="3TsBF5" to="tpce:4qF2Hm2r7ja" resolve="conceptAlias" />
1805+
</node>
1806+
</node>
1807+
<node concept="10Nm6u" id="3pYupGUiW$u" role="3uHU7w" />
1808+
</node>
1809+
</node>
17511810
<node concept="3clFbH" id="3Fg0S50gLTv" role="3cqZAp" />
17521811
<node concept="3clFbF" id="3Fg0S50cWJn" role="3cqZAp">
17531812
<node concept="2ShNRf" id="3Fg0S50cWJj" role="3clFbG">
17541813
<node concept="1pGfFk" id="3Fg0S50cX7i" role="2ShVmc">
1755-
<ref role="37wK5l" to="sgfj:~ConceptData.&lt;init&gt;(java.lang.String,java.lang.String,boolean,java.util.List,java.util.List,java.util.List,java.util.List,java.lang.String)" resolve="ConceptData" />
1814+
<ref role="37wK5l" to="sgfj:~ConceptData.&lt;init&gt;(java.lang.String,java.lang.String,boolean,java.util.List,java.util.List,java.util.List,java.util.List,java.lang.String,java.util.Map)" resolve="ConceptData" />
17561815
<node concept="3cpWs3" id="2sGJABKvA1m" role="37wK5m">
17571816
<node concept="Xl_RD" id="2sGJABKvBm5" role="3uHU7B">
17581817
<property role="Xl_RC" value="mps:" />
@@ -1799,6 +1858,9 @@
17991858
<ref role="3cqZAo" node="3Fg0S50cWmY" resolve="concept" />
18001859
</node>
18011860
</node>
1861+
<node concept="37vLTw" id="3pYupGUi9Li" role="37wK5m">
1862+
<ref role="3cqZAo" node="3FfIz6$e8S5" resolve="metaProperties" />
1863+
</node>
18021864
</node>
18031865
</node>
18041866
</node>

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/GeneratedApiTest.kt renamed to model-api-gen-gradle-test/src/test/kotlin/org/modelix/metamodel/generator/GeneratedApiTest.kt

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@ import jetbrains.mps.baseLanguage.jdk8.C_SuperInterfaceMethodCall_old
44
import jetbrains.mps.baseLanguage.jdk8.SuperInterfaceMethodCall_old
55
import jetbrains.mps.lang.behavior.C_ConceptMethodDeclaration
66
import jetbrains.mps.lang.behavior.ConceptMethodDeclaration
7+
import jetbrains.mps.lang.core.C_BaseConcept
78
import jetbrains.mps.lang.core.L_jetbrains_mps_lang_core
89
import jetbrains.mps.lang.editor.C_FontStyleStyleClassItem
910
import jetbrains.mps.lang.editor.L_jetbrains_mps_lang_editor
1011
import jetbrains.mps.lang.editor._FontStyle_Enum
12+
import org.modelix.apigen.test.IMetaConceptProperties
1113
import org.modelix.metamodel.IPropertyValueEnum
1214
import org.modelix.metamodel.TypedLanguagesRegistry
1315
import org.modelix.metamodel.typed
1416
import org.modelix.metamodel.untyped
1517
import org.modelix.model.ModelFacade
1618
import org.modelix.model.api.INode
1719
import org.modelix.model.api.getRootNode
20+
import org.modelix.model.data.ConceptData
1821
import org.modelix.model.data.ModelData
1922
import java.io.File
2023
import kotlin.reflect.KAnnotatedElement
@@ -23,6 +26,8 @@ import kotlin.reflect.full.isSubclassOf
2326
import kotlin.test.Test
2427
import kotlin.test.assertContains
2528
import kotlin.test.assertEquals
29+
import kotlin.test.assertNull
30+
import kotlin.test.assertTrue
2631

2732
class GeneratedApiTest {
2833

@@ -35,7 +40,7 @@ class GeneratedApiTest {
3540
branch.runWrite {
3641
data.load(branch)
3742
val node = findNodeWithStyleAttribute(branch.getRootNode())!!.typed(C_FontStyleStyleClassItem.getInstanceInterface())
38-
assert(_FontStyle_Enum::class.isSubclassOf(IPropertyValueEnum::class))
43+
assertTrue(_FontStyle_Enum::class.isSubclassOf(IPropertyValueEnum::class))
3944
assertContains(_FontStyle_Enum.values(), node.style)
4045
val enumValue = _FontStyle_Enum.BOLD_ITALIC
4146
node.style = enumValue
@@ -55,15 +60,28 @@ class GeneratedApiTest {
5560
val foundDeprecatedNodeChildLink = ClassConcept::class.members.any { it.hasDeprecationWithMessage() }
5661
val foundDeprecatedNodeReference = SuperInterfaceMethodCall_old::class.members.any { it.hasDeprecationWithMessage() }
5762

58-
assert(foundDeprecatedConcept)
59-
assert(foundDeprecatedProperty)
60-
assert(foundDeprecatedChildLink)
61-
assert(foundDeprecatedReference)
63+
assertTrue(foundDeprecatedConcept)
64+
assertTrue(foundDeprecatedProperty)
65+
assertTrue(foundDeprecatedChildLink)
66+
assertTrue(foundDeprecatedReference)
6267

63-
assert(foundDeprecatedNodeWrapper)
64-
assert(foundDeprecatedNodeProperty)
65-
assert(foundDeprecatedNodeChildLink)
66-
assert(foundDeprecatedNodeReference)
68+
assertTrue(foundDeprecatedNodeWrapper)
69+
assertTrue(foundDeprecatedNodeProperty)
70+
assertTrue(foundDeprecatedNodeChildLink)
71+
assertTrue(foundDeprecatedNodeReference)
72+
}
73+
74+
@Test
75+
fun `metaProperty alias is generated`() {
76+
val hasAlias = IMetaConceptProperties::class.members.any { it.name == ConceptData.ALIAS_KEY }
77+
assertTrue(hasAlias)
78+
assertNull(C_BaseConcept.alias)
79+
}
80+
81+
@Test
82+
fun `metaProperty alias has value`() {
83+
val alias = C_ClassConcept.alias
84+
assertEquals("class", alias)
6785
}
6886

6987
private fun KAnnotatedElement.hasDeprecationWithMessage() =

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: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +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 conceptMetaProperties: MutableSet<String>
3940

4041
init {
4142
load(dataList)
@@ -62,6 +63,14 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
6263
initIndexes()
6364
resolveConceptReferences()
6465
fixRoleConflicts()
66+
collectConceptMetaProperties()
67+
}
68+
69+
private fun collectConceptMetaProperties() {
70+
conceptMetaProperties = mutableSetOf()
71+
val concepts = languages.flatMap { it.getConcepts() }
72+
val keys = concepts.flatMap { it.metaProperties.keys }.toSet()
73+
conceptMetaProperties.addAll(keys)
6574
}
6675

6776
private fun initIndexes() {
@@ -137,6 +146,8 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
137146
fun getLanguages(): List<ProcessedLanguage> {
138147
return languages
139148
}
149+
150+
fun getConceptMetaProperties() = conceptMetaProperties
140151
}
141152

142153
internal class ProcessedLanguage(var name: String, var uid: String?) {
@@ -162,7 +173,14 @@ internal class ProcessedLanguage(var name: String, var uid: String?) {
162173
fun load(dataList: List<ConceptData>) {
163174
for (data in dataList) {
164175
addConcept(
165-
ProcessedConcept(data.name, data.uid, data.abstract, data.extends.map { ProcessedConceptReference(it) }.toMutableList(), data.deprecationMessage).also { concept ->
176+
ProcessedConcept(
177+
data.name,
178+
data.uid,
179+
data.abstract,
180+
data.extends.map { ProcessedConceptReference(it) }.toMutableList(),
181+
data.deprecationMessage,
182+
data.metaProperties,
183+
).also { concept ->
166184
concept.loadRoles(data)
167185
},
168186
)
@@ -220,6 +238,7 @@ internal class ProcessedConcept(
220238
var abstract: Boolean,
221239
val extends: MutableList<ProcessedConceptReference>,
222240
override var deprecationMessage: String?,
241+
val metaProperties: MutableMap<String, String>,
223242
) : IProcessedDeprecatable {
224243
lateinit var language: ProcessedLanguage
225244
private val roles: MutableList<ProcessedRole> = ArrayList()

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

Lines changed: 55 additions & 1 deletion
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"
@@ -97,6 +102,7 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
97102
}
98103

99104
internal fun generateRegistrationHelper(classFqName: String, languages: ProcessedLanguageSet) {
105+
require(classFqName.contains(".")) { "The name of the registrationHelper does not contain a dot. Use a fully qualified name." }
100106
val typeName = ClassName(classFqName.substringBeforeLast("."), classFqName.substringAfterLast("."))
101107
val cls = TypeSpec.objectBuilder(typeName)
102108
.addProperty(
@@ -117,11 +123,42 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
117123
.write()
118124
}
119125

126+
private fun generateConceptMetaPropertiesInterface(languages: IProcessedLanguageSet) {
127+
val fqName = checkNotNull(conceptPropertiesInterfaceName)
128+
require(fqName.contains(".")) { "The name of the concept properties interface does not contain a dot. Use a fully qualified name." }
129+
val interfaceName = ClassName(fqName.substringBeforeLast("."), fqName.substringAfterLast("."))
130+
val metaPropertiesInterface = TypeSpec.interfaceBuilder(interfaceName)
131+
.generateMetaProperties(languages as ProcessedLanguageSet)
132+
.build()
133+
134+
FileSpec.builder(interfaceName.packageName, interfaceName.simpleName)
135+
.addFileComment(headerComment)
136+
.addType(metaPropertiesInterface)
137+
.build()
138+
.write()
139+
}
140+
141+
private fun TypeSpec.Builder.generateMetaProperties(languages: ProcessedLanguageSet): TypeSpec.Builder {
142+
val nullGetter = FunSpec.getterBuilder().addCode("return null").build()
143+
languages.getConceptMetaProperties().forEach {
144+
addProperty(
145+
PropertySpec.builder(it, String::class.asTypeName().copy(nullable = true))
146+
.getter(nullGetter)
147+
.build(),
148+
)
149+
}
150+
return this
151+
}
152+
120153
fun generate(languages: IProcessedLanguageSet) {
121154
generate(languages as ProcessedLanguageSet)
122155
}
123156

124157
private fun generate(languages: ProcessedLanguageSet) {
158+
if (conceptPropertiesInterfaceName != null) {
159+
generateConceptMetaPropertiesInterface(languages)
160+
}
161+
125162
for (language in languages.getLanguages()) {
126163
language.packageDir().toFile().listFiles()?.filter { it.isFile }?.forEach { it.delete() }
127164
val builder =
@@ -647,6 +684,13 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
647684
for (extended in concept.getDirectSuperConcepts()) {
648685
addSuperinterface(extended.conceptWrapperInterfaceClass().parameterizedBy(nodeT))
649686
}
687+
688+
if (conceptPropertiesInterfaceName != null && concept.extends.isEmpty()) {
689+
val pckgName = conceptPropertiesInterfaceName.substringBeforeLast(".")
690+
val interfaceName = conceptPropertiesInterfaceName.substringAfterLast(".")
691+
addSuperinterface(ClassName(pckgName, interfaceName))
692+
}
693+
650694
for (feature in concept.getOwnRoles()) {
651695
when (feature) {
652696
is ProcessedProperty -> addProperty(
@@ -694,6 +738,16 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
694738
.addStatement("return %T", concept.conceptObjectType())
695739
.build(),
696740
)
741+
if (conceptPropertiesInterfaceName != null) {
742+
concept.metaProperties.forEach { (key, value) ->
743+
addProperty(
744+
PropertySpec.builder(key, String::class.asTypeName())
745+
.addModifiers(KModifier.OVERRIDE)
746+
.initializer("%S", value)
747+
.build(),
748+
)
749+
}
750+
}
697751
}.build(),
698752
)
699753
}.build()

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ data class ConceptData(
4343
val references: List<ReferenceLinkData> = emptyList(),
4444
val extends: List<String> = emptyList(),
4545
override val deprecationMessage: String? = null,
46-
) : IDeprecatable
46+
val metaProperties: MutableMap<String, String> = mutableMapOf(),
47+
) : IDeprecatable {
48+
companion object {
49+
const val ALIAS_KEY = "alias"
50+
}
51+
}
4752

4853
@Serializable
4954
data class EnumData(

0 commit comments

Comments
 (0)