Skip to content

Commit

Permalink
Merge pull request #242 from modelix/feature/model-api-gen-concept-me…
Browse files Browse the repository at this point in the history
…tadata

MODELIX-520 Make concept alias available in generated source code
  • Loading branch information
mhuster23 authored Sep 25, 2023
2 parents 2049ba0 + 7f58125 commit 675cf40
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ Inside of the `metamodel` block the following settings can be configured.

|`registrationHelperName`
|String
|Name of the registration helper
|Fully qualified name of the generated language registration helper

|`conceptPropertiesInterfaceName`
|String
|Fully qualified name of the generated interface, that contains the concept meta-properties of this language set.
If `null` (default), neither the concept meta-properties nor the corresponding interface will be generated.

|`taskDependencies`
|List<Any>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1748,11 +1748,70 @@
</node>
</node>
</node>
<node concept="3cpWs8" id="3FfIz6$e8S2" role="3cqZAp">
<node concept="3cpWsn" id="3FfIz6$e8S5" role="3cpWs9">
<property role="TrG5h" value="metaProperties" />
<node concept="3rvAFt" id="3FfIz6$e8RW" role="1tU5fm">
<node concept="3uibUv" id="3FfIz6$eb7L" role="3rvQeY">
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
</node>
<node concept="3uibUv" id="3FfIz6$edJQ" role="3rvSg0">
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
</node>
</node>
<node concept="2ShNRf" id="BwT8EA3$Cq" role="33vP2m">
<node concept="3rGOSV" id="BwT8EA3$BR" role="2ShVmc">
<node concept="3uibUv" id="BwT8EA3$BS" role="3rHrn6">
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
</node>
<node concept="3uibUv" id="BwT8EA3$BT" role="3rHtpV">
<ref role="3uigEE" to="wyt6:~String" resolve="String" />
</node>
</node>
</node>
</node>
</node>
<node concept="3clFbJ" id="3pYupGUiJ_M" role="3cqZAp">
<node concept="3clFbS" id="3pYupGUiJ_O" role="3clFbx">
<node concept="3clFbF" id="3pYupGUieI_" role="3cqZAp">
<node concept="37vLTI" id="3pYupGUiqAl" role="3clFbG">
<node concept="2OqwBi" id="3pYupGUivav" role="37vLTx">
<node concept="37vLTw" id="3pYupGUitvJ" role="2Oq$k0">
<ref role="3cqZAo" node="3Fg0S50cWmY" resolve="concept" />
</node>
<node concept="3TrcHB" id="3pYupGUixB3" role="2OqNvi">
<ref role="3TsBF5" to="tpce:4qF2Hm2r7ja" resolve="conceptAlias" />
</node>
</node>
<node concept="3EllGN" id="3pYupGUikWF" role="37vLTJ">
<node concept="10M0yZ" id="3pYupGUipfJ" role="3ElVtu">
<ref role="3cqZAo" to="sgfj:~ConceptData.ALIAS_KEY" resolve="ALIAS_KEY" />
<ref role="1PxDUh" to="sgfj:~ConceptData" resolve="ConceptData" />
</node>
<node concept="37vLTw" id="3pYupGUieIz" role="3ElQJh">
<ref role="3cqZAo" node="3FfIz6$e8S5" resolve="metaProperties" />
</node>
</node>
</node>
</node>
</node>
<node concept="3y3z36" id="3pYupGUjlay" role="3clFbw">
<node concept="2OqwBi" id="3pYupGUiNt1" role="3uHU7B">
<node concept="37vLTw" id="3pYupGUiLEL" role="2Oq$k0">
<ref role="3cqZAo" node="3Fg0S50cWmY" resolve="concept" />
</node>
<node concept="3TrcHB" id="3pYupGUiPnm" role="2OqNvi">
<ref role="3TsBF5" to="tpce:4qF2Hm2r7ja" resolve="conceptAlias" />
</node>
</node>
<node concept="10Nm6u" id="3pYupGUiW$u" role="3uHU7w" />
</node>
</node>
<node concept="3clFbH" id="3Fg0S50gLTv" role="3cqZAp" />
<node concept="3clFbF" id="3Fg0S50cWJn" role="3cqZAp">
<node concept="2ShNRf" id="3Fg0S50cWJj" role="3clFbG">
<node concept="1pGfFk" id="3Fg0S50cX7i" role="2ShVmc">
<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" />
<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" />
<node concept="3cpWs3" id="2sGJABKvA1m" role="37wK5m">
<node concept="Xl_RD" id="2sGJABKvBm5" role="3uHU7B">
<property role="Xl_RC" value="mps:" />
Expand Down Expand Up @@ -1799,6 +1858,9 @@
<ref role="3cqZAo" node="3Fg0S50cWmY" resolve="concept" />
</node>
</node>
<node concept="37vLTw" id="3pYupGUi9Li" role="37wK5m">
<ref role="3cqZAo" node="3FfIz6$e8S5" resolve="metaProperties" />
</node>
</node>
</node>
</node>
Expand Down
1 change: 1 addition & 0 deletions model-api-gen-gradle-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ metamodel {
typedNodeImpl.suffix = "Impl"
}
registrationHelperName = "org.modelix.apigen.test.ApigenTestLanguages"
conceptPropertiesInterfaceName = "org.modelix.apigen.test.IMetaConceptProperties"
}

node {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import jetbrains.mps.baseLanguage.jdk8.C_SuperInterfaceMethodCall_old
import jetbrains.mps.baseLanguage.jdk8.SuperInterfaceMethodCall_old
import jetbrains.mps.lang.behavior.C_ConceptMethodDeclaration
import jetbrains.mps.lang.behavior.ConceptMethodDeclaration
import jetbrains.mps.lang.core.C_BaseConcept
import jetbrains.mps.lang.core.L_jetbrains_mps_lang_core
import jetbrains.mps.lang.editor.C_FontStyleStyleClassItem
import jetbrains.mps.lang.editor.L_jetbrains_mps_lang_editor
import jetbrains.mps.lang.editor._FontStyle_Enum
import org.modelix.apigen.test.IMetaConceptProperties
import org.modelix.metamodel.IPropertyValueEnum
import org.modelix.metamodel.TypedLanguagesRegistry
import org.modelix.metamodel.typed
import org.modelix.metamodel.untyped
import org.modelix.model.ModelFacade
import org.modelix.model.api.INode
import org.modelix.model.api.getRootNode
import org.modelix.model.data.ConceptData
import org.modelix.model.data.ModelData
import java.io.File
import kotlin.reflect.KAnnotatedElement
Expand All @@ -23,6 +26,8 @@ import kotlin.reflect.full.isSubclassOf
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue

class GeneratedApiTest {

Expand All @@ -35,7 +40,7 @@ class GeneratedApiTest {
branch.runWrite {
data.load(branch)
val node = findNodeWithStyleAttribute(branch.getRootNode())!!.typed(C_FontStyleStyleClassItem.getInstanceInterface())
assert(_FontStyle_Enum::class.isSubclassOf(IPropertyValueEnum::class))
assertTrue(_FontStyle_Enum::class.isSubclassOf(IPropertyValueEnum::class))
assertContains(_FontStyle_Enum.values(), node.style)
val enumValue = _FontStyle_Enum.BOLD_ITALIC
node.style = enumValue
Expand All @@ -55,15 +60,28 @@ class GeneratedApiTest {
val foundDeprecatedNodeChildLink = ClassConcept::class.members.any { it.hasDeprecationWithMessage() }
val foundDeprecatedNodeReference = SuperInterfaceMethodCall_old::class.members.any { it.hasDeprecationWithMessage() }

assert(foundDeprecatedConcept)
assert(foundDeprecatedProperty)
assert(foundDeprecatedChildLink)
assert(foundDeprecatedReference)
assertTrue(foundDeprecatedConcept)
assertTrue(foundDeprecatedProperty)
assertTrue(foundDeprecatedChildLink)
assertTrue(foundDeprecatedReference)

assert(foundDeprecatedNodeWrapper)
assert(foundDeprecatedNodeProperty)
assert(foundDeprecatedNodeChildLink)
assert(foundDeprecatedNodeReference)
assertTrue(foundDeprecatedNodeWrapper)
assertTrue(foundDeprecatedNodeProperty)
assertTrue(foundDeprecatedNodeChildLink)
assertTrue(foundDeprecatedNodeReference)
}

@Test
fun `metaProperty alias is generated`() {
val hasAlias = IMetaConceptProperties::class.members.any { it.name == ConceptData.ALIAS_KEY }
assertTrue(hasAlias)
assertNull(C_BaseConcept.alias)
}

@Test
fun `metaProperty alias has value`() {
val alias = C_ClassConcept.alias
assertEquals("class", alias)
}

private fun KAnnotatedElement.hasDeprecationWithMessage() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ abstract class GenerateMetaModelSources @Inject constructor(of: ObjectFactory) :
@Optional
val registrationHelperName: Property<String> = of.property(String::class.java)

@get:Input
@Optional
val conceptPropertiesInterfaceName: Property<String> = of.property(String::class.java)

@get: Input
val nameConfig: Property<NameConfig> = of.property(NameConfig::class.java)

Expand Down Expand Up @@ -100,6 +104,7 @@ abstract class GenerateMetaModelSources @Inject constructor(of: ObjectFactory) :
kotlinOutputDir.toPath(),
nameConfig.get(),
this.modelqlKotlinOutputDir.orNull?.asFile?.toPath(),
conceptPropertiesInterfaceName.orNull,
)
generator.generate(processedLanguages)
registrationHelperName.orNull?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class MetaModelGradlePlugin : Plugin<Project> {
task.exportedLanguagesDir.set(exportedLanguagesDir)
}
settings.registrationHelperName?.let { task.registrationHelperName.set(it) }
settings.conceptPropertiesInterfaceName?.let { task.conceptPropertiesInterfaceName.set(it) }
task.nameConfig.set(settings.nameConfig)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ open class MetaModelGradleSettings {
}
var typescriptDir: File? = null
var registrationHelperName: String? = null
var conceptPropertiesInterfaceName: String? = null
val taskDependencies: MutableList<Any> = ArrayList()

internal val nameConfig = NameConfig()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
private lateinit var uid2language: Map<String, ProcessedLanguage>
private lateinit var fqName2concept: Map<String, ProcessedConcept>
private lateinit var uid2concept: Map<String, ProcessedConcept>
private lateinit var conceptMetaProperties: MutableSet<String>

init {
load(dataList)
Expand All @@ -62,6 +63,14 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
initIndexes()
resolveConceptReferences()
fixRoleConflicts()
collectConceptMetaProperties()
}

private fun collectConceptMetaProperties() {
conceptMetaProperties = mutableSetOf()
val concepts = languages.flatMap { it.getConcepts() }
val keys = concepts.flatMap { it.metaProperties.keys }.toSet()
conceptMetaProperties.addAll(keys)
}

private fun initIndexes() {
Expand Down Expand Up @@ -137,6 +146,8 @@ internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLa
fun getLanguages(): List<ProcessedLanguage> {
return languages
}

fun getConceptMetaProperties() = conceptMetaProperties
}

internal class ProcessedLanguage(var name: String, var uid: String?) {
Expand All @@ -162,7 +173,14 @@ internal class ProcessedLanguage(var name: String, var uid: String?) {
fun load(dataList: List<ConceptData>) {
for (data in dataList) {
addConcept(
ProcessedConcept(data.name, data.uid, data.abstract, data.extends.map { ProcessedConceptReference(it) }.toMutableList(), data.deprecationMessage).also { concept ->
ProcessedConcept(
data.name,
data.uid,
data.abstract,
data.extends.map { ProcessedConceptReference(it) }.toMutableList(),
data.deprecationMessage,
data.metaProperties,
).also { concept ->
concept.loadRoles(data)
},
)
Expand Down Expand Up @@ -220,6 +238,7 @@ internal class ProcessedConcept(
var abstract: Boolean,
val extends: MutableList<ProcessedConceptReference>,
override var deprecationMessage: String?,
val metaProperties: MutableMap<String, String>,
) : IProcessedDeprecatable {
lateinit var language: ProcessedLanguage
private val roles: MutableList<ProcessedRole> = ArrayList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ import org.modelix.modelql.typed.TypedModelQL
import java.nio.file.Path
import kotlin.reflect.KClass

class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameConfig(), val modelqlOutputDir: Path? = null) {
class MetaModelGenerator(
val outputDir: Path,
val nameConfig: NameConfig = NameConfig(),
val modelqlOutputDir: Path? = null,
val conceptPropertiesInterfaceName: String? = null,
) {
var alwaysUseNonNullableProperties: Boolean = true

private val headerComment = "\ngenerated by modelix model-api-gen \n"
Expand Down Expand Up @@ -97,6 +102,7 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
}

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

private fun generateConceptMetaPropertiesInterface(languages: IProcessedLanguageSet) {
val fqName = checkNotNull(conceptPropertiesInterfaceName)
require(fqName.contains(".")) { "The name of the concept properties interface does not contain a dot. Use a fully qualified name." }
val interfaceName = ClassName(fqName.substringBeforeLast("."), fqName.substringAfterLast("."))
val metaPropertiesInterface = TypeSpec.interfaceBuilder(interfaceName)
.generateMetaProperties(languages as ProcessedLanguageSet)
.build()

FileSpec.builder(interfaceName.packageName, interfaceName.simpleName)
.addFileComment(headerComment)
.addType(metaPropertiesInterface)
.build()
.write()
}

private fun TypeSpec.Builder.generateMetaProperties(languages: ProcessedLanguageSet): TypeSpec.Builder {
val nullGetter = FunSpec.getterBuilder().addCode("return null").build()
languages.getConceptMetaProperties().forEach {
addProperty(
PropertySpec.builder(it, String::class.asTypeName().copy(nullable = true))
.getter(nullGetter)
.build(),
)
}
return this
}

fun generate(languages: IProcessedLanguageSet) {
generate(languages as ProcessedLanguageSet)
}

private fun generate(languages: ProcessedLanguageSet) {
if (conceptPropertiesInterfaceName != null) {
generateConceptMetaPropertiesInterface(languages)
}

for (language in languages.getLanguages()) {
language.packageDir().toFile().listFiles()?.filter { it.isFile }?.forEach { it.delete() }
val builder =
Expand Down Expand Up @@ -647,6 +684,13 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
for (extended in concept.getDirectSuperConcepts()) {
addSuperinterface(extended.conceptWrapperInterfaceClass().parameterizedBy(nodeT))
}

if (conceptPropertiesInterfaceName != null && concept.extends.isEmpty()) {
val pckgName = conceptPropertiesInterfaceName.substringBeforeLast(".")
val interfaceName = conceptPropertiesInterfaceName.substringAfterLast(".")
addSuperinterface(ClassName(pckgName, interfaceName))
}

for (feature in concept.getOwnRoles()) {
when (feature) {
is ProcessedProperty -> addProperty(
Expand Down Expand Up @@ -694,6 +738,16 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
.addStatement("return %T", concept.conceptObjectType())
.build(),
)
if (conceptPropertiesInterfaceName != null) {
concept.metaProperties.forEach { (key, value) ->
addProperty(
PropertySpec.builder(key, String::class.asTypeName())
.addModifiers(KModifier.OVERRIDE)
.initializer("%S", value)
.build(),
)
}
}
}.build(),
)
}.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ data class ConceptData(
val references: List<ReferenceLinkData> = emptyList(),
val extends: List<String> = emptyList(),
override val deprecationMessage: String? = null,
) : IDeprecatable
val metaProperties: MutableMap<String, String> = mutableMapOf(),
) : IDeprecatable {
companion object {
const val ALIAS_KEY = "alias"
}
}

@Serializable
data class EnumData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ data class NodeData(
val references: Map<String, String> = emptyMap(),
) {
companion object {
const val idPropertyKey = "#mpsNodeId#"
const val ID_PROPERTY_KEY = "#mpsNodeId#"

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

Expand Down

0 comments on commit 675cf40

Please sign in to comment.