Skip to content

Commit ee71fe5

Browse files
authored
Merge pull request #35 from modelix/gradle-plugin-test
Test project for the metamodel-gradle plugin
2 parents 75dd4cd + c5350c1 commit ee71fe5

File tree

16 files changed

+888
-231
lines changed

16 files changed

+888
-231
lines changed

.github/workflows/build.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ jobs:
2020
- name: Build
2121
env:
2222
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23-
run: ./gradlew build -PciBuild=true
23+
run: ./gradlew build publishToMavenLocal -PciBuild=true
24+
- name: Test Metamodel Gradle Plugin
25+
env:
26+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27+
run: metamodel-gradle-test/ci.sh
2428
- name: Archive test report
2529
uses: actions/upload-artifact@v3
2630
if: always()

build.gradle.kts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ println("Version: $version")
1717
fun computeVersion(): Any {
1818
val versionFile = file("version.txt")
1919
val gitVersion: groovy.lang.Closure<String> by extra
20-
var version = if (versionFile.exists()) versionFile.readText().trim() else gitVersion()
21-
if (!versionFile.exists() && "true" != project.findProperty("ciBuild")) {
22-
version = "$version-SNAPSHOT"
20+
return if (versionFile.exists()) {
21+
versionFile.readText().trim()
22+
} else {
23+
gitVersion().let{ if (it.endsWith("-SNAPSHOT")) it else "$it-SNAPSHOT" }.also { versionFile.writeText(it) }
2324
}
24-
return version
2525
}
2626

2727
subprojects {
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package org.modelix.metamodel.generator
15+
16+
import org.modelix.model.api.IConcept
17+
import org.modelix.model.data.ConceptData
18+
import org.modelix.model.data.LanguageData
19+
import org.modelix.model.data.PropertyType
20+
import kotlin.reflect.KClass
21+
22+
private val reservedPropertyNames: Set<String> = setOf(
23+
"constructor", // already exists on JS objects
24+
) + IConcept::class.members.map { it.name }
25+
26+
interface IProcessedLanguageSet
27+
fun LanguageSet.process(): IProcessedLanguageSet = ProcessedLanguageSet(getLanguages().map { it.language })
28+
29+
internal class ProcessedLanguageSet(dataList: List<LanguageData>) : IProcessedLanguageSet {
30+
31+
private val languages: MutableList<ProcessedLanguage> = ArrayList()
32+
33+
private lateinit var fqName2language: Map<String, ProcessedLanguage>
34+
private lateinit var uid2language: Map<String, ProcessedLanguage>
35+
private lateinit var fqName2concept: Map<String, ProcessedConcept>
36+
private lateinit var uid2concept: Map<String, ProcessedConcept>
37+
38+
init {
39+
load(dataList)
40+
}
41+
42+
fun addLanguage(language: ProcessedLanguage) {
43+
languages.add(language)
44+
language.languageSet = this
45+
}
46+
47+
fun load(dataList: List<LanguageData>) {
48+
for (data in dataList) {
49+
addLanguage(ProcessedLanguage(data.name, data.uid).also { lang ->
50+
lang.load(data.concepts)
51+
})
52+
}
53+
process()
54+
}
55+
56+
private fun process() {
57+
initIndexes()
58+
resolveConceptReferences()
59+
fixRoleConflicts()
60+
}
61+
62+
private fun initIndexes() {
63+
fqName2language = languages.associateBy { it.name }
64+
uid2language = languages.filter { it.uid != null }.associateBy { it.uid!! }
65+
fqName2concept = languages.flatMap { it.getConcepts() }.associateBy { it.fqName() }
66+
uid2concept = languages.flatMap { it.getConcepts() }.filter { it.uid != null }.associateBy { it.uid!! }
67+
languages.forEach { lang -> lang.simpleName2concept = lang.getConcepts().associateBy { it.name } }
68+
}
69+
70+
private fun resolveConceptReferences() {
71+
val visitor: (ProcessedConceptReference, ProcessedLanguage) -> Unit = { ref, contextLanguage ->
72+
ref.resolved = uid2concept[ref.name]
73+
?: fqName2concept[ref.name]
74+
?: contextLanguage.simpleName2concept[ref.name]
75+
?: throw RuntimeException("Failed to resolve concept '${ref.name}'")
76+
}
77+
languages.forEach { lang -> lang.visitConceptReferences { visitor(it, lang) } }
78+
}
79+
80+
private fun fixRoleConflicts() {
81+
val allConcepts = languages.asSequence().flatMap { it.getConcepts() }
82+
83+
// add type suffix if there are two roles of the same name, but different type
84+
allConcepts.forEach { concept ->
85+
val conflicts: List<List<ProcessedRole>> = concept.getOwnRoles().groupBy { it.generatedName }.values.filter { it.size > 1 }
86+
for (conflict in conflicts) {
87+
val conflictsByType: Map<KClass<out ProcessedRole>, List<ProcessedRole>> = conflict.groupBy { it::class }
88+
conflictsByType.entries.forEach { conflictByType ->
89+
conflictByType.value.forEach {
90+
it.generatedName += getTypeSuffix(it)
91+
}
92+
}
93+
}
94+
}
95+
96+
// add number suffix if there are still two roles of the same name
97+
allConcepts.forEach { concept ->
98+
val conflicts: List<List<ProcessedRole>> = concept.getOwnRoles().groupBy { it.generatedName }.values.filter { it.size > 1 }
99+
for (conflict in conflicts) {
100+
conflict.forEachIndexed { index, role -> role.generatedName += "_$index" }
101+
}
102+
}
103+
104+
// add concept name suffix if there is a role with the same name in a super concept
105+
val sameInHierarchyConflicts = HashSet<ProcessedRole>()
106+
allConcepts.forEach { concept ->
107+
concept.getAllSuperConceptsAndSelf()
108+
.flatMap { it.getOwnRoles() }
109+
.groupBy { it.generatedName }
110+
.values.asSequence()
111+
.filter { it.size > 1 }
112+
.forEach { sameInHierarchyConflicts.addAll(it) }
113+
}
114+
sameInHierarchyConflicts.forEach { it.generatedName += "_" + it.concept.name }
115+
116+
// replace illegal names
117+
allConcepts.flatMap { it.getOwnRoles() }.forEach {
118+
if (reservedPropertyNames.contains(it.generatedName)) {
119+
it.generatedName += getTypeSuffix(it)
120+
}
121+
}
122+
}
123+
124+
private fun getTypeSuffix(role: ProcessedRole): String {
125+
return when (role) {
126+
is ProcessedProperty -> "_property"
127+
is ProcessedReferenceLink -> "_reference"
128+
is ProcessedChildLink -> if (role.multiple) "_children" else "_child"
129+
}
130+
}
131+
132+
fun getLanguages(): List<ProcessedLanguage> {
133+
return languages
134+
}
135+
}
136+
137+
internal class ProcessedLanguage(var name: String, var uid: String?) {
138+
lateinit var languageSet: ProcessedLanguageSet
139+
private val concepts: MutableList<ProcessedConcept> = ArrayList()
140+
lateinit var simpleName2concept: Map<String, ProcessedConcept>
141+
142+
fun addConcept(concept: ProcessedConcept) {
143+
concepts.add(concept)
144+
concept.language = this
145+
}
146+
147+
fun getConcepts(): List<ProcessedConcept> = concepts
148+
149+
fun load(dataList: List<ConceptData>) {
150+
for (data in dataList) {
151+
addConcept(ProcessedConcept(data.name, data.uid, data.abstract, data.extends.map { ProcessedConceptReference(it) }.toMutableList()).also { concept ->
152+
concept.loadRoles(data)
153+
})
154+
}
155+
}
156+
157+
fun visitConceptReferences(visitor: (ProcessedConceptReference) -> Unit) {
158+
concepts.forEach { it.visitConceptReferences(visitor) }
159+
}
160+
}
161+
162+
internal class ProcessedConceptReference(var name: String) {
163+
lateinit var resolved: ProcessedConcept
164+
}
165+
166+
internal class ProcessedConcept(var name: String, var uid: String?, var abstract: Boolean, val extends: MutableList<ProcessedConceptReference>) {
167+
lateinit var language: ProcessedLanguage
168+
private val roles: MutableList<ProcessedRole> = ArrayList()
169+
170+
fun fqName() = language.name + "." + name
171+
172+
fun addRole(role: ProcessedRole) {
173+
roles.add(role)
174+
role.concept = this
175+
}
176+
177+
fun getOwnRoles(): List<ProcessedRole> = roles
178+
179+
fun getOwnAndDuplicateRoles(): List<ProcessedRole> = roles + getDuplicateSuperConcepts().flatMap { it.getOwnRoles() }
180+
181+
fun loadRoles(data: ConceptData) {
182+
data.properties.forEach { addRole(ProcessedProperty(it.name, it.uid, it.optional, it.type)) }
183+
data.children.forEach { addRole(ProcessedChildLink(it.name, it.uid, it.optional, it.multiple, ProcessedConceptReference(it.type))) }
184+
data.references.forEach { addRole(ProcessedReferenceLink(it.name, it.uid, it.optional, ProcessedConceptReference(it.type))) }
185+
}
186+
187+
fun visitConceptReferences(visitor: (ProcessedConceptReference) -> Unit) {
188+
extends.forEach { visitor(it) }
189+
roles.forEach { it.visitConceptReferences(visitor) }
190+
}
191+
192+
fun getDirectSuperConcepts(): Sequence<ProcessedConcept> = extends.asSequence().map { it.resolved }
193+
private fun getAllSuperConcepts_(): Sequence<ProcessedConcept> = getDirectSuperConcepts().flatMap { it.getAllSuperConceptsAndSelf_() }
194+
private fun getAllSuperConceptsAndSelf_(): Sequence<ProcessedConcept> = sequenceOf(this) + getAllSuperConcepts_()
195+
196+
fun getAllSuperConcepts(): Sequence<ProcessedConcept> = getAllSuperConcepts_().distinct()
197+
fun getAllSuperConceptsAndSelf(): Sequence<ProcessedConcept> = getAllSuperConceptsAndSelf_().distinct()
198+
fun getDuplicateSuperConcepts() = getAllSuperConcepts_().groupBy { it }.filter { it.value.size > 1 }.map { it.key }
199+
}
200+
201+
internal sealed class ProcessedRole(
202+
var originalName: String,
203+
var uid: String?,
204+
var optional: Boolean
205+
) {
206+
lateinit var concept: ProcessedConcept
207+
var generatedName: String = originalName
208+
209+
abstract fun visitConceptReferences(visitor: (ProcessedConceptReference) -> Unit)
210+
}
211+
212+
internal class ProcessedProperty(name: String, uid: String?, optional: Boolean, var type: PropertyType)
213+
: ProcessedRole(name, uid, optional) {
214+
override fun visitConceptReferences(visitor: (ProcessedConceptReference) -> Unit) {}
215+
}
216+
217+
internal sealed class ProcessedLink(name: String, uid: String?, optional: Boolean, var type: ProcessedConceptReference)
218+
: ProcessedRole(name, uid, optional) {
219+
override fun visitConceptReferences(visitor: (ProcessedConceptReference) -> Unit) {
220+
visitor(type)
221+
}
222+
}
223+
224+
internal class ProcessedChildLink(name: String, uid: String?, optional: Boolean, var multiple: Boolean, type: ProcessedConceptReference)
225+
: ProcessedLink(name, uid, optional, type)
226+
227+
internal class ProcessedReferenceLink(name: String, uid: String?, optional: Boolean, type: ProcessedConceptReference)
228+
: ProcessedLink(name, uid, optional, type)

0 commit comments

Comments
 (0)