Skip to content

Commit 9ad7a77

Browse files
authored
Merge pull request #138 from modelix/issue/MODELIX-458
MODELIX-458 Using generated enums leads to IllegalArgumentException
2 parents a14f95f + dcc6880 commit 9ad7a77

File tree

7 files changed

+157
-28
lines changed

7 files changed

+157
-28
lines changed

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

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,17 +1836,27 @@
18361836
<node concept="3clFbF" id="4zSRxm70zVx" role="3cqZAp">
18371837
<node concept="2ShNRf" id="4zSRxm70zVv" role="3clFbG">
18381838
<node concept="1pGfFk" id="4zSRxm70Bwh" role="2ShVmc">
1839-
<ref role="37wK5l" to="sgfj:~EnumMemberData.&lt;init&gt;(java.lang.String,java.lang.String)" resolve="EnumMemberData" />
1840-
<node concept="2OqwBi" id="1lNY4J8WpV6" role="37wK5m">
1841-
<node concept="2YIFZM" id="1lNY4J8Wg5q" role="2Oq$k0">
1842-
<ref role="37wK5l" to="e8bb:~MetaIdByDeclaration.getEnumLiteralId(org.jetbrains.mps.openapi.model.SNode)" resolve="getEnumLiteralId" />
1843-
<ref role="1Pybhc" to="e8bb:~MetaIdByDeclaration" resolve="MetaIdByDeclaration" />
1844-
<node concept="37vLTw" id="1lNY4J8Wh67" role="37wK5m">
1845-
<ref role="3cqZAo" node="4zSRxm70ylt" resolve="it" />
1839+
<ref role="37wK5l" to="sgfj:~EnumMemberData.&lt;init&gt;(java.lang.String,java.lang.String,java.lang.String)" resolve="EnumMemberData" />
1840+
<node concept="2OqwBi" id="7ryKvClc3z$" role="37wK5m">
1841+
<node concept="2ShNRf" id="7ryKvClbWLm" role="2Oq$k0">
1842+
<node concept="1pGfFk" id="7ryKvClc1fD" role="2ShVmc">
1843+
<ref role="37wK5l" to="w1kc:~JavaFriendlyBase64.&lt;init&gt;()" resolve="JavaFriendlyBase64" />
18461844
</node>
18471845
</node>
1848-
<node concept="liA8E" id="1lNY4J8Wsd4" role="2OqNvi">
1849-
<ref role="37wK5l" to="e8bb:~SEnumerationLiteralId.toString()" resolve="toString" />
1846+
<node concept="liA8E" id="7ryKvClc4X2" role="2OqNvi">
1847+
<ref role="37wK5l" to="w1kc:~JavaFriendlyBase64.toString(long)" resolve="toString" />
1848+
<node concept="2YIFZM" id="7ryKvClcjfc" role="37wK5m">
1849+
<ref role="37wK5l" to="wyt6:~Long.parseLong(java.lang.String)" resolve="parseLong" />
1850+
<ref role="1Pybhc" to="wyt6:~Long" resolve="Long" />
1851+
<node concept="2OqwBi" id="7ryKvClcmPJ" role="37wK5m">
1852+
<node concept="37vLTw" id="7ryKvClclkY" role="2Oq$k0">
1853+
<ref role="3cqZAo" node="4zSRxm70ylt" resolve="it" />
1854+
</node>
1855+
<node concept="3TrcHB" id="7ryKvClcomT" role="2OqNvi">
1856+
<ref role="3TsBF5" to="tpce:1eSXJRel0SS" resolve="memberId" />
1857+
</node>
1858+
</node>
1859+
</node>
18501860
</node>
18511861
</node>
18521862
<node concept="2OqwBi" id="1lNY4J8Wmi4" role="37wK5m">
@@ -1857,6 +1867,14 @@
18571867
<ref role="3TsBF5" to="tpck:h0TrG11" resolve="name" />
18581868
</node>
18591869
</node>
1870+
<node concept="2OqwBi" id="7ryKvClczo$" role="37wK5m">
1871+
<node concept="37vLTw" id="7ryKvClcxDd" role="2Oq$k0">
1872+
<ref role="3cqZAo" node="4zSRxm70ylt" resolve="it" />
1873+
</node>
1874+
<node concept="3TrcHB" id="7ryKvClc$FU" role="2OqNvi">
1875+
<ref role="3TsBF5" to="tpce:_jzzDSlxy8" resolve="presentation" />
1876+
</node>
1877+
</node>
18601878
</node>
18611879
</node>
18621880
</node>

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ val modelixCoreVersion: String = projectDir.resolve("../version.txt").readText()
3939
dependencies {
4040
mps("com.jetbrains:mps:2021.1.4")
4141
implementation("org.modelix:model-api-gen-runtime:$modelixCoreVersion")
42+
testImplementation(kotlin("test"))
43+
testImplementation("org.modelix:model-api:$modelixCoreVersion")
44+
testImplementation("org.modelix:model-client:$modelixCoreVersion")
45+
testImplementation(files("$buildDir/metamodel/kotlin_gen") {
46+
builtBy("generateMetaModelSources")
47+
})
48+
}
49+
50+
tasks.test {
51+
useJUnitPlatform()
4252
}
4353

4454
val resolveMps by tasks.registering(Sync::class) {
@@ -58,7 +68,7 @@ metamodel {
5868
kotlinDir = kotlinGenDir
5969
kotlinProject = project
6070
includeNamespace("jetbrains")
61-
//exportModules("jetbrains.mps.baseLanguage")
71+
exportModules("jetbrains.mps.baseLanguage")
6272

6373
names {
6474
languageClass.prefix = "L_"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import jetbrains.mps.lang.core.L_jetbrains_mps_lang_core
2+
import jetbrains.mps.lang.editor.*
3+
import org.modelix.metamodel.TypedLanguagesRegistry
4+
import org.modelix.metamodel.typed
5+
import org.modelix.metamodel.untyped
6+
import org.modelix.model.ModelFacade
7+
import org.modelix.model.api.INode
8+
import org.modelix.model.api.getRootNode
9+
import org.modelix.model.data.ModelData
10+
import java.io.File
11+
import kotlin.test.Test
12+
import kotlin.test.assertContains
13+
import kotlin.test.assertEquals
14+
15+
class GeneratedApiTest {
16+
17+
@Test
18+
fun `can handle enums`() {
19+
val branch = ModelFacade.toLocalBranch(ModelFacade.newLocalTree())
20+
TypedLanguagesRegistry.register(L_jetbrains_mps_lang_editor)
21+
TypedLanguagesRegistry.register(L_jetbrains_mps_lang_core)
22+
val data = ModelData.fromJson(File("build/metamodel/exported-modules/jetbrains.mps.baseLanguage.blTypes.json").readText())
23+
branch.runWrite {
24+
data.load(branch)
25+
val node = findNodeWithStyleAttribute(branch.getRootNode())!!.typed(C_FontStyleStyleClassItem.getInstanceInterface())
26+
assertContains(_FontStyle_Enum.values(), node.style)
27+
val enumValue = _FontStyle_Enum.BOLD_ITALIC
28+
node.style = enumValue
29+
assertEquals(enumValue, node.untyped().typed(C_FontStyleStyleClassItem.getInstanceInterface()).style)
30+
}
31+
}
32+
33+
private fun findNodeWithStyleAttribute(node: INode) : INode? {
34+
var found = node.allChildren.find { it.getPropertyRoles().contains("style") }
35+
if (found != null) return found
36+
37+
for (child in node.allChildren) {
38+
found = findNodeWithStyleAttribute(child)
39+
if (found != null) {
40+
return found
41+
}
42+
}
43+
return null
44+
}
45+
46+
}

model-api-gen-runtime/src/commonMain/kotlin/org/modelix/metamodel/IPropertyValueSerializer.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,23 +78,36 @@ object OptionalIntPropertySerializer : IPropertyValueSerializer<Int?> {
7878
}
7979
}
8080

81-
class MandatoryEnumSerializer<E : Enum<*>>(private val valueOf: (String?) -> E) : IPropertyValueSerializer<E> {
81+
abstract class EnumSerializer {
82+
protected fun serializeEnumMember(id: String, name: String) = "$id/$name"
83+
protected fun deserializeEnumMemberId(serialized: String?) = serialized?.substringBefore('/')
84+
}
85+
86+
class MandatoryEnumSerializer<E : Enum<*>>(
87+
private val memberIdOf: (E) -> String,
88+
private val fromMemberId: (String?) -> E
89+
) : EnumSerializer(), IPropertyValueSerializer<E> {
90+
8291
override fun serialize(value: E): String {
83-
return value.name
92+
return serializeEnumMember(memberIdOf(value), value.name)
8493
}
8594

8695
override fun deserialize(serialized: String?): E {
87-
return valueOf(serialized)
96+
return fromMemberId(deserializeEnumMemberId(serialized))
8897
}
8998
}
9099

91-
class OptionalEnumSerializer<E : Enum<*>>(private val valueOf: (String?) -> E?) : IPropertyValueSerializer<Enum<*>?> {
92-
override fun serialize(value: Enum<*>?): String? {
93-
return value?.name
100+
class OptionalEnumSerializer<E : Enum<*>>(
101+
private val memberIdOf: (E) -> String,
102+
private val fromMemberId: (String) -> E
103+
) : EnumSerializer(), IPropertyValueSerializer<E?> {
104+
105+
override fun serialize(value: E?): String? {
106+
return value?.let { serializeEnumMember(memberIdOf(it), it.name) }
94107
}
95108

96-
override fun deserialize(serialized: String?): Enum<*>? {
97-
return serialized?.let { valueOf(serialized) }
109+
override fun deserialize(serialized: String?): E? {
110+
return deserializeEnumMemberId(serialized)?.let { fromMemberId(it) }
98111
}
99112
}
100113

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ internal class ProcessedLanguage(var name: String, var uid: String?) {
168168
for (data in dataList) {
169169
val enum = ProcessedEnum(data.name, data.uid, maxOf(0, data.defaultIndex))
170170
for (memberData in data.members) {
171-
val member = ProcessedEnumMember(memberData.name, memberData.uid)
171+
val member = ProcessedEnumMember(memberData.name, memberData.uid, memberData.presentation)
172172
enum.addMember(member)
173173
}
174174
addEnum(enum)
@@ -196,7 +196,7 @@ internal class ProcessedEnum(var name: String, var uid: String?, var defaultInde
196196
}
197197
}
198198

199-
internal class ProcessedEnumMember(var name: String, var uid: String?) {
199+
internal class ProcessedEnumMember(var name: String, var uid: String, var presentation: String?) {
200200
lateinit var enum: ProcessedEnum
201201
}
202202

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

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,59 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
115115
}
116116

117117
private fun generateEnumFile(enum: ProcessedEnum) {
118-
val builder = TypeSpec.enumBuilder(enum.name)
118+
val constructorSpec = FunSpec.constructorBuilder()
119+
.addParameter("uid", String::class)
120+
.addParameter("presentation", String::class.asTypeName().copy(nullable = true))
121+
.build()
122+
123+
val enumBuilder = TypeSpec.enumBuilder(enum.name)
124+
.primaryConstructor(constructorSpec)
125+
.addProperty(
126+
PropertySpec.builder("uid", String::class)
127+
.initializer("uid")
128+
.build()
129+
)
130+
.addProperty(
131+
PropertySpec.builder("presentation", String::class.asTypeName().copy(nullable = true))
132+
.initializer("presentation")
133+
.build()
134+
)
135+
136+
val getLiteralFunBuilder = FunSpec.builder("getLiteralByMemberId")
137+
.addParameter("uid", String::class)
138+
val getLiteralCodeBuilder = CodeBlock.builder().beginControlFlow("return when (uid) {")
119139

120140
for (member in enum.getAllMembers()) {
121-
builder.addEnumConstant(member.name)
141+
enumBuilder.addEnumConstant(
142+
member.name,
143+
TypeSpec.anonymousClassBuilder()
144+
.addSuperclassConstructorParameter("%S", member.uid)
145+
.addSuperclassConstructorParameter(
146+
if (member.presentation == null) "null" else "%S",
147+
member.presentation ?: "")
148+
.build()
149+
)
150+
getLiteralCodeBuilder.addStatement("%S -> %L", member.uid, member.name)
122151
}
123152

153+
getLiteralFunBuilder.addCode(
154+
getLiteralCodeBuilder
155+
.addStatement("else -> defaultValue()")
156+
.endControlFlow()
157+
.build()
158+
)
159+
124160
val companion = TypeSpec.companionObjectBuilder()
125161
.addFunction(
126162
FunSpec.builder("defaultValue")
127163
.addCode("return values()[%L]", enum.defaultIndex)
128164
.build())
165+
.addFunction(
166+
getLiteralFunBuilder.build()
167+
)
129168
.build()
130169

131-
val generatedEnum = builder.addType(companion).build()
170+
val generatedEnum = enumBuilder.addType(companion).build()
132171

133172
FileSpec.builder(enum.language.name, enum.name)
134173
.addFileComment(headerComment)
@@ -317,7 +356,9 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
317356
if (feature.type is EnumPropertyType) {
318357
if (serializer == MandatoryEnumSerializer::class.asTypeName()) {
319358
propBuilder.initializer(
320-
"""newProperty(%S, %S, %T { if (it != null) %T.valueOf(it) else %T.defaultValue() }, ${feature.optional})""",
359+
"""newProperty(%S, %S, %T({ it.uid },
360+
|{ if (it != null) %T.getLiteralByMemberId(it) else %T.defaultValue() }),
361+
|${feature.optional})""".trimMargin(),
321362
feature.originalName,
322363
feature.uid,
323364
serializer,
@@ -326,7 +367,8 @@ class MetaModelGenerator(val outputDir: Path, val nameConfig: NameConfig = NameC
326367
)
327368
} else {
328369
propBuilder.initializer(
329-
"""newProperty(%S, %S, %T { %T.valueOf(it) }, ${feature.optional})""",
370+
"""newProperty(%S, %S, %T( { it.uid }, { %T.getLiteralByMemberId(it) }),
371+
|${feature.optional})""".trimMargin(),
330372
feature.originalName,
331373
feature.uid,
332374
serializer,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.modelix.model.data
22

33
import kotlinx.serialization.KSerializer
44
import kotlinx.serialization.Serializable
5-
import kotlinx.serialization.decodeFromString
65
import kotlinx.serialization.descriptors.PrimitiveKind
76
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
87
import kotlinx.serialization.descriptors.SerialDescriptor
@@ -51,8 +50,9 @@ data class EnumData(
5150

5251
@Serializable
5352
data class EnumMemberData(
54-
val uid: String?,
55-
val name: String
53+
val uid: String,
54+
val name: String,
55+
val presentation: String? = null
5656
)
5757

5858
sealed interface IConceptFeatureData {

0 commit comments

Comments
 (0)