Skip to content

Commit 05d16d1

Browse files
authored
Merge pull request #379 from modelix/feature/modelql-write-support
MODELIX-644 Support writing in generated typed API with ModelQl
2 parents 9f267df + a570fb1 commit 05d16d1

File tree

4 files changed

+298
-7
lines changed

4 files changed

+298
-7
lines changed

model-api-gen-gradle-test/kotlin-generation/src/test/kotlin/org/modelix/modelql/typed/TypedModelQLTest.kt

Lines changed: 209 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,71 @@ import io.ktor.client.HttpClient
1717
import io.ktor.server.testing.testApplication
1818
import jetbrains.mps.baseLanguage.C_ClassConcept
1919
import jetbrains.mps.baseLanguage.C_IntegerType
20+
import jetbrains.mps.baseLanguage.C_MinusExpression
21+
import jetbrains.mps.baseLanguage.C_ParameterDeclaration
2022
import jetbrains.mps.baseLanguage.C_PlusExpression
2123
import jetbrains.mps.baseLanguage.C_PublicVisibility
2224
import jetbrains.mps.baseLanguage.C_ReturnStatement
2325
import jetbrains.mps.baseLanguage.C_StaticMethodDeclaration
2426
import jetbrains.mps.baseLanguage.C_VariableReference
2527
import jetbrains.mps.baseLanguage.ClassConcept
2628
import jetbrains.mps.baseLanguage.StaticMethodDeclaration
29+
import jetbrains.mps.core.xml.C_XmlComment
30+
import jetbrains.mps.core.xml.C_XmlCommentLine
31+
import jetbrains.mps.core.xml.C_XmlDocument
32+
import jetbrains.mps.core.xml.C_XmlFile
33+
import jetbrains.mps.lang.editor.imageGen.C_ImageGenerator
34+
import jetbrains.mps.lang.editor.imageGen.ImageGenerator
2735
import org.modelix.apigen.test.ApigenTestLanguages
36+
import org.modelix.metamodel.instanceOf
2837
import org.modelix.metamodel.typed
38+
import org.modelix.metamodel.untyped
2939
import org.modelix.model.api.IBranch
3040
import org.modelix.model.api.PBranch
3141
import org.modelix.model.api.getRootNode
3242
import org.modelix.model.api.resolve
3343
import org.modelix.model.client.IdGenerator
3444
import org.modelix.model.lazy.CLTree
3545
import org.modelix.model.lazy.ObjectStoreCache
36-
import org.modelix.model.persistent.MapBaseStore
46+
import org.modelix.model.persistent.MapBasedStore
3747
import org.modelix.model.server.light.LightModelServer
3848
import org.modelix.modelql.client.ModelQLClient
49+
import org.modelix.modelql.core.asMono
3950
import org.modelix.modelql.core.count
51+
import org.modelix.modelql.core.equalTo
4052
import org.modelix.modelql.core.filter
53+
import org.modelix.modelql.core.first
4154
import org.modelix.modelql.core.map
4255
import org.modelix.modelql.core.toList
4356
import org.modelix.modelql.core.toSet
4457
import org.modelix.modelql.core.zip
58+
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.addToMember
59+
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.expression
4560
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.member
4661
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.parameter
62+
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.setExpression
63+
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.setVariableDeclaration
4764
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.variableDeclaration
4865
import org.modelix.modelql.gen.jetbrains.mps.baseLanguage.visibility
66+
import org.modelix.modelql.gen.jetbrains.mps.core.xml.addToLines
67+
import org.modelix.modelql.gen.jetbrains.mps.core.xml.document
68+
import org.modelix.modelql.gen.jetbrains.mps.core.xml.lines
69+
import org.modelix.modelql.gen.jetbrains.mps.core.xml.setDocument
4970
import org.modelix.modelql.gen.jetbrains.mps.lang.core.name
71+
import org.modelix.modelql.gen.jetbrains.mps.lang.core.setName
72+
import org.modelix.modelql.gen.jetbrains.mps.lang.editor.imageGen.node
73+
import org.modelix.modelql.gen.jetbrains.mps.lang.editor.imageGen.node_orNull
74+
import org.modelix.modelql.gen.jetbrains.mps.lang.editor.imageGen.setNode
5075
import org.modelix.modelql.untyped.children
5176
import org.modelix.modelql.untyped.conceptReference
77+
import org.modelix.modelql.untyped.descendants
5278
import kotlin.test.BeforeTest
5379
import kotlin.test.Test
5480
import kotlin.test.assertEquals
81+
import kotlin.test.assertNotEquals
82+
import kotlin.test.assertNotNull
83+
import kotlin.test.assertNull
84+
import kotlin.test.assertTrue
5585

5686
class TypedModelQLTest {
5787
private lateinit var branch: IBranch
@@ -68,7 +98,7 @@ class TypedModelQLTest {
6898
@BeforeTest
6999
fun setup() {
70100
ApigenTestLanguages.registerAll()
71-
val tree = CLTree(ObjectStoreCache(MapBaseStore()))
101+
val tree = CLTree(ObjectStoreCache(MapBasedStore()))
72102
branch = PBranch(tree, IdGenerator.getInstance(1))
73103
val rootNode = branch.getRootNode()
74104
branch.runWrite {
@@ -101,6 +131,16 @@ class TypedModelQLTest {
101131
}
102132
}
103133
}
134+
// Example for optional reference
135+
rootNode.addNewChild("imageGen", -1, C_ImageGenerator.untyped())
136+
.typed<ImageGenerator>()
137+
.apply { node = cls1 }
138+
139+
// Example for single non-abstract child
140+
rootNode.addNewChild("xmlFile", -1, C_XmlFile.untyped())
141+
142+
// Example for mulitple non-abstract child
143+
rootNode.addNewChild("xmlComment", -1, C_XmlComment.untyped())
104144
}
105145
}
106146

@@ -131,7 +171,7 @@ class TypedModelQLTest {
131171
}
132172

133173
@Test
134-
fun testReferences() = runTest { httpClient ->
174+
fun `get references`() = runTest { httpClient ->
135175
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
136176
val usedVariables: Set<String> = client.query { root ->
137177
root.children("classes").ofConcept(C_ClassConcept)
@@ -147,7 +187,7 @@ class TypedModelQLTest {
147187
}
148188

149189
@Test
150-
fun testReferencesFqName() = runTest { httpClient ->
190+
fun `get references - fqName`() = runTest { httpClient ->
151191
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
152192
val usedVariables: Set<String> = client.query { root ->
153193
root.children("classes").ofConcept(C_ClassConcept)
@@ -170,7 +210,7 @@ class TypedModelQLTest {
170210
}
171211

172212
@Test
173-
fun testNodeSerialization() = runTest { httpClient ->
213+
fun `node serialization`() = runTest { httpClient ->
174214
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
175215
val result: List<StaticMethodDeclaration> = client.query { root ->
176216
root.children("classes").ofConcept(C_ClassConcept)
@@ -184,7 +224,7 @@ class TypedModelQLTest {
184224
}
185225

186226
@Test
187-
fun returnTypedNode() = runTest { httpClient ->
227+
fun `return typed node`() = runTest { httpClient ->
188228
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
189229
val result: List<StaticMethodDeclaration> = client.query { root ->
190230
root.children("classes").ofConcept(C_ClassConcept)
@@ -195,4 +235,167 @@ class TypedModelQLTest {
195235
}
196236
assertEquals("plus", branch.computeRead { result[0].name })
197237
}
238+
239+
@Test
240+
fun `set property`() = runTest { httpClient ->
241+
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
242+
val expected = "myRenamedMethod"
243+
client.query { root ->
244+
root.children("classes").ofConcept(C_ClassConcept)
245+
.member
246+
.ofConcept(C_StaticMethodDeclaration)
247+
.first()
248+
.setName(expected.asMono())
249+
}
250+
val actual = client.query { root ->
251+
root.children("classes").ofConcept(C_ClassConcept)
252+
.member
253+
.ofConcept(C_StaticMethodDeclaration)
254+
.first()
255+
}
256+
assertEquals(expected, actual.name)
257+
}
258+
259+
@Test
260+
fun `set reference`() = runTest { httpClient ->
261+
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
262+
263+
val oldValue = client.query { root ->
264+
root.children("classes").ofConcept(C_ClassConcept)
265+
.member
266+
.ofConcept(C_StaticMethodDeclaration)
267+
.descendants()
268+
.ofConcept(C_ParameterDeclaration)
269+
.first()
270+
.name
271+
}
272+
val expected = "b"
273+
client.query { root ->
274+
val descendants = root.children("classes").ofConcept(C_ClassConcept)
275+
.member
276+
.ofConcept(C_StaticMethodDeclaration)
277+
.descendants()
278+
279+
val target = descendants.ofConcept(C_ParameterDeclaration)
280+
.filter { it.name.equalTo(expected) }
281+
.first()
282+
283+
descendants.ofConcept(C_VariableReference)
284+
.first()
285+
.setVariableDeclaration(target)
286+
}
287+
288+
val actual = client.query { root ->
289+
root.children("classes").ofConcept(C_ClassConcept)
290+
.member
291+
.ofConcept(C_StaticMethodDeclaration)
292+
.descendants()
293+
.ofConcept(C_VariableReference)
294+
.first()
295+
}
296+
assertNotEquals(expected, oldValue)
297+
assertEquals(expected, actual.variableDeclaration.name)
298+
}
299+
300+
@Test
301+
fun `set reference - null`() = runTest { httpClient ->
302+
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
303+
val oldValue = client.query { root ->
304+
root.children("imageGen").ofConcept(C_ImageGenerator).first().node
305+
}
306+
307+
client.query { root ->
308+
root.children("imageGen").ofConcept(C_ImageGenerator).first().setNode(null)
309+
}
310+
311+
val actual = client.query { root ->
312+
root.children("imageGen").ofConcept(C_ImageGenerator).first().node_orNull
313+
}
314+
assertNotNull(oldValue)
315+
assertNull(actual)
316+
}
317+
318+
@Test
319+
fun `add new child`() = runTest { httpClient ->
320+
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
321+
322+
val oldNumChildren = client.query { root ->
323+
root.children("classes").ofConcept(C_ClassConcept)
324+
.first()
325+
.member
326+
.ofConcept(C_StaticMethodDeclaration)
327+
.count()
328+
}
329+
330+
client.query { root ->
331+
root.children("classes").ofConcept(C_ClassConcept)
332+
.first()
333+
.addToMember(C_StaticMethodDeclaration)
334+
}
335+
336+
val children = client.query { root ->
337+
root.children("classes").ofConcept(C_ClassConcept)
338+
.first()
339+
.member
340+
.ofConcept(C_StaticMethodDeclaration)
341+
.toList()
342+
}
343+
344+
assertEquals(oldNumChildren + 1, children.size)
345+
assertEquals(C_StaticMethodDeclaration.untyped().getUID(), children.last().untyped().concept?.getUID())
346+
}
347+
348+
@Test
349+
fun `add new child - default concept`() = runTest { httpClient ->
350+
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
351+
client.query { root ->
352+
root.descendants().ofConcept(C_XmlComment)
353+
.first()
354+
.addToLines()
355+
}
356+
357+
val actual = client.query { root ->
358+
root.descendants().ofConcept(C_XmlComment)
359+
.first()
360+
.lines
361+
.first()
362+
}
363+
364+
assertTrue { actual.instanceOf(C_XmlCommentLine) }
365+
}
366+
367+
@Test
368+
fun `set child`() = runTest { httpClient ->
369+
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
370+
371+
client.query { root ->
372+
root.descendants().ofConcept(C_ReturnStatement)
373+
.first()
374+
.setExpression(C_MinusExpression)
375+
}
376+
377+
val actual = client.query { root ->
378+
root.descendants().ofConcept(C_ReturnStatement).first().expression
379+
}
380+
assertNotNull(actual)
381+
assertTrue(actual.instanceOf(C_MinusExpression))
382+
}
383+
384+
@Test
385+
fun `set child - default concept`() = runTest { httpClient ->
386+
val client = ModelQLClient.builder().url("http://localhost/query").httpClient(httpClient).build()
387+
client.query { root ->
388+
root.descendants().ofConcept(C_XmlFile)
389+
.first()
390+
.setDocument()
391+
}
392+
393+
val actual = client.query { root ->
394+
root.descendants().ofConcept(C_XmlFile)
395+
.first()
396+
.document
397+
}
398+
399+
assertTrue { actual.instanceOf(C_XmlDocument) }
400+
}
198401
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,10 @@ internal sealed class ProcessedLink(name: String, uid: String?, optional: Boolea
302302
}
303303

304304
internal class ProcessedChildLink(name: String, uid: String?, optional: Boolean, var multiple: Boolean, type: ProcessedConceptReference, deprecationMessage: String?) :
305-
ProcessedLink(name, uid, optional, type, deprecationMessage)
305+
ProcessedLink(name, uid, optional, type, deprecationMessage) {
306+
307+
fun adderMethodName() = "addTo" + generatedName.take(1).uppercase() + generatedName.drop(1)
308+
}
306309

307310
internal class ProcessedReferenceLink(name: String, uid: String?, optional: Boolean, type: ProcessedConceptReference, deprecationMessage: String?) :
308311
ProcessedLink(name, uid, optional, type, deprecationMessage)

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.squareup.kotlinpoet.CodeBlock
66
import com.squareup.kotlinpoet.FileSpec
77
import com.squareup.kotlinpoet.FunSpec
88
import com.squareup.kotlinpoet.KModifier
9+
import com.squareup.kotlinpoet.ParameterSpec
910
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
1011
import com.squareup.kotlinpoet.PropertySpec
1112
import com.squareup.kotlinpoet.TypeAliasSpec
@@ -463,6 +464,48 @@ class MetaModelGenerator(
463464
)
464465
.build(),
465466
)
467+
val returnType = IMonoStep::class.asTypeName().parameterizedBy(targetType)
468+
val receiverType = IMonoStep::class.asTypeName().parameterizedBy(concept.nodeWrapperInterfaceType())
469+
val conceptParameter = ParameterSpec.builder("concept", ITypedConcept::class.asTypeName()).apply {
470+
if (!feature.type.resolved.abstract) {
471+
defaultValue("%T", feature.type.resolved.conceptWrapperInterfaceClass())
472+
}
473+
}.build()
474+
475+
if (feature.multiple) {
476+
addFunction(
477+
FunSpec.builder(feature.adderMethodName())
478+
.returns(returnType)
479+
.receiver(receiverType)
480+
.addParameter(conceptParameter)
481+
.addParameter(
482+
ParameterSpec.builder("index", Int::class.asTypeName())
483+
.defaultValue("-1")
484+
.build(),
485+
)
486+
.addStatement(
487+
"return %T.addNewChild(this, %T.%N, index, concept)",
488+
TypedModelQL::class.asTypeName(),
489+
concept.conceptObjectType(),
490+
feature.generatedName,
491+
)
492+
.build(),
493+
)
494+
} else {
495+
addFunction(
496+
FunSpec.builder(feature.setterName())
497+
.returns(returnType)
498+
.receiver(receiverType)
499+
.addParameter(conceptParameter)
500+
.addStatement(
501+
"return %T.setChild(this, %T.%N, concept)",
502+
TypedModelQL::class.asTypeName(),
503+
concept.conceptObjectType(),
504+
feature.generatedName,
505+
)
506+
.build(),
507+
)
508+
}
466509
}
467510

468511
is ProcessedReferenceLink -> {
@@ -504,6 +547,26 @@ class MetaModelGenerator(
504547
.build(),
505548
)
506549
}
550+
551+
val inputStepType = IMonoStep::class.asTypeName()
552+
.parameterizedBy(concept.nodeWrapperInterfaceType())
553+
addFunction(
554+
FunSpec.builder(feature.setterName())
555+
.returns(inputStepType)
556+
.receiver(inputStepType)
557+
.addParameter(
558+
"target",
559+
IMonoStep::class.asTypeName().parameterizedBy(targetType)
560+
.let { if (feature.optional) it.copy(nullable = true) else it },
561+
)
562+
.addStatement(
563+
"return %T.setReference(this, %T.%N, target)",
564+
TypedModelQL::class.asTypeName(),
565+
concept.conceptWrapperInterfaceClass(),
566+
feature.generatedName,
567+
)
568+
.build(),
569+
)
507570
}
508571
}
509572
}

0 commit comments

Comments
 (0)