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