Skip to content

Commit 2b733bb

Browse files
authored
Merge pull request #392 from modelix/refactor/split-metamodel-generator
MODELIX-667 Refactor MetaModelGenerator
2 parents e716a3f + 5e29944 commit 2b733bb

13 files changed

+1842
-1056
lines changed

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

Lines changed: 76 additions & 1056 deletions
Large diffs are not rendered by default.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* Copyright (c) 2024.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.modelix.metamodel.generator.internal
18+
19+
import com.squareup.kotlinpoet.FileSpec
20+
import com.squareup.kotlinpoet.FunSpec
21+
import com.squareup.kotlinpoet.ParameterizedTypeName
22+
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
23+
import com.squareup.kotlinpoet.PropertySpec
24+
import com.squareup.kotlinpoet.TypeAliasSpec
25+
import com.squareup.kotlinpoet.TypeName
26+
import com.squareup.kotlinpoet.asTypeName
27+
import org.modelix.metamodel.generator.MetaModelGenerator
28+
import org.modelix.metamodel.generator.NameConfig
29+
import org.modelix.metamodel.generator.ProcessedChildLink
30+
import org.modelix.metamodel.generator.ProcessedConcept
31+
import org.modelix.metamodel.generator.ProcessedProperty
32+
import org.modelix.metamodel.generator.ProcessedReferenceLink
33+
import org.modelix.metamodel.generator.runBuild
34+
import org.modelix.model.api.INode
35+
import java.nio.file.Path
36+
37+
internal class ConceptFileGenerator(
38+
private val concept: ProcessedConcept,
39+
override val outputDir: Path,
40+
override val nameConfig: NameConfig,
41+
private val conceptPropertiesInterfaceName: String?,
42+
private val alwaysUseNonNullableProperties: Boolean,
43+
) : NameConfigBasedGenerator(nameConfig), FileGenerator {
44+
45+
override fun generateFileSpec(): FileSpec {
46+
val conceptObject = ConceptObjectGenerator(concept, nameConfig).generate()
47+
val conceptWrapperInterface = ConceptWrapperInterfaceGenerator(
48+
concept,
49+
nameConfig,
50+
conceptPropertiesInterfaceName,
51+
alwaysUseNonNullableProperties,
52+
).generate()
53+
val nodeWrapperInterface = NodeWrapperInterfaceGenerator(concept, nameConfig, alwaysUseNonNullableProperties).generate()
54+
val nodeWrapperImpl = NodeWrapperImplGenerator(concept, nameConfig, alwaysUseNonNullableProperties).generate()
55+
56+
val typeAliasSpec = TypeAliasSpec.builder(concept.conceptTypeAliasName(), concept.conceptWrapperInterfaceType()).build()
57+
58+
return FileSpec.builder(concept.language.name, concept.name).runBuild {
59+
addFileComment(MetaModelGenerator.HEADER_COMMENT)
60+
addType(conceptObject)
61+
addTypeAlias(typeAliasSpec)
62+
addType(conceptWrapperInterface)
63+
addType(nodeWrapperInterface)
64+
addType(nodeWrapperImpl)
65+
addConceptFeatureShortcuts()
66+
}
67+
}
68+
69+
private fun FileSpec.Builder.addConceptFeatureShortcuts() {
70+
// allow to write `nodes.myChildren` instead of `nodes.flatMap { it.myChildren }`
71+
for (feature in concept.getOwnRoles()) {
72+
val receiverType = Iterable::class.asTypeName().parameterizedBy(concept.nodeWrapperInterfaceType())
73+
when (feature) {
74+
is ProcessedProperty -> {
75+
addRegularConceptProperty(feature, receiverType)
76+
addRawConceptProperty(feature, receiverType)
77+
}
78+
is ProcessedChildLink -> addConceptChildLink(feature, receiverType)
79+
is ProcessedReferenceLink -> addConceptReferences(feature, receiverType)
80+
}
81+
}
82+
}
83+
84+
private fun FileSpec.Builder.addConceptReferences(
85+
referenceLink: ProcessedReferenceLink,
86+
receiverType: ParameterizedTypeName,
87+
) {
88+
val targetType = referenceLink.type.resolved.nodeWrapperInterfaceType().copy(nullable = referenceLink.optional)
89+
val rawTargetType = INode::class.asTypeName().copy(nullable = true)
90+
91+
addRegularConceptReference(referenceLink, targetType, receiverType)
92+
addOrNullConceptReference(referenceLink, targetType, receiverType)
93+
addRawConceptReference(referenceLink, rawTargetType, receiverType)
94+
}
95+
96+
private fun FileSpec.Builder.addRawConceptReference(
97+
referenceLink: ProcessedReferenceLink,
98+
rawTargetType: TypeName,
99+
receiverType: ParameterizedTypeName,
100+
) {
101+
val refType = List::class.asTypeName().parameterizedBy(rawTargetType)
102+
val getterSpec = FunSpec.getterBuilder().runBuild {
103+
addStatement("return map { it.%N }", "raw_" + referenceLink.generatedName)
104+
}
105+
106+
val propertySpec = PropertySpec.builder("raw_" + referenceLink.generatedName, refType).runBuild {
107+
receiver(receiverType)
108+
getter(getterSpec)
109+
}
110+
addProperty(propertySpec)
111+
}
112+
113+
private fun FileSpec.Builder.addOrNullConceptReference(
114+
referenceLink: ProcessedReferenceLink,
115+
targetType: TypeName,
116+
receiverType: ParameterizedTypeName,
117+
) {
118+
val refType = List::class.asTypeName().parameterizedBy(targetType.copy(nullable = true))
119+
120+
val getterSpec = FunSpec.getterBuilder().runBuild {
121+
addStatement("return map { it.%N }", referenceLink.generatedName + "_orNull")
122+
}
123+
124+
val propertySpec = PropertySpec.builder(referenceLink.generatedName + "_orNull", refType).runBuild {
125+
receiver(receiverType)
126+
getter(getterSpec)
127+
}
128+
addProperty(propertySpec)
129+
}
130+
131+
private fun FileSpec.Builder.addRegularConceptReference(
132+
referenceLink: ProcessedReferenceLink,
133+
targetType: TypeName,
134+
receiverType: ParameterizedTypeName,
135+
) {
136+
val refType = List::class.asTypeName().parameterizedBy(targetType)
137+
138+
val getterSpec = FunSpec.getterBuilder().runBuild {
139+
addStatement("return map { it.%N }", referenceLink.generatedName)
140+
}
141+
142+
val propertySpec = PropertySpec.builder(referenceLink.generatedName, refType).runBuild {
143+
receiver(receiverType)
144+
getter(getterSpec)
145+
}
146+
addProperty(propertySpec)
147+
}
148+
149+
private fun FileSpec.Builder.addConceptChildLink(
150+
childLink: ProcessedChildLink,
151+
receiverType: ParameterizedTypeName,
152+
) {
153+
val targetType = childLink.type.resolved.nodeWrapperInterfaceType()
154+
val returnType = List::class.asTypeName().parameterizedBy(targetType)
155+
val getterSpec = FunSpec.getterBuilder().runBuild {
156+
addStatement("return flatMap { it.%N }", childLink.generatedName)
157+
}
158+
159+
val propertySpec = PropertySpec.builder(childLink.generatedName, returnType).runBuild {
160+
receiver(receiverType)
161+
getter(getterSpec)
162+
}
163+
addProperty(propertySpec)
164+
}
165+
166+
private fun FileSpec.Builder.addRawConceptProperty(
167+
property: ProcessedProperty,
168+
receiverType: ParameterizedTypeName,
169+
) {
170+
val returnType = List::class.asTypeName().parameterizedBy(String::class.asTypeName().copy(nullable = true))
171+
172+
val getterSpec = FunSpec.getterBuilder().runBuild {
173+
addStatement("return map { it.%N }", "raw_" + property.generatedName)
174+
}
175+
176+
val propertySpec = PropertySpec.builder("raw_" + property.generatedName, returnType).runBuild {
177+
receiver(receiverType)
178+
getter(getterSpec)
179+
}
180+
addProperty(propertySpec)
181+
}
182+
183+
private fun FileSpec.Builder.addRegularConceptProperty(
184+
property: ProcessedProperty,
185+
receiverType: ParameterizedTypeName,
186+
) {
187+
val returnType = List::class.asTypeName().parameterizedBy(property.asKotlinType(alwaysUseNonNullableProperties))
188+
val getterSpec = FunSpec.getterBuilder().runBuild {
189+
addStatement("return map { it.%N }", property.generatedName)
190+
}
191+
val propertySpec = PropertySpec.builder(property.generatedName, returnType).runBuild {
192+
receiver(receiverType)
193+
getter(getterSpec)
194+
}
195+
addProperty(propertySpec)
196+
}
197+
}

0 commit comments

Comments
 (0)