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