From 56dfc3c4751e165bc6ca8387c3797bc72575eb72 Mon Sep 17 00:00:00 2001 From: Federico Tomassetti Date: Fri, 4 Aug 2023 10:49:51 +0200 Subject: [PATCH] refactor LionWebLanguageConverter --- .../lionweb/LionWebLanguageConverter.kt | 162 ++++++++++-------- .../kolasu/lionweb/LionWebModelConverter.kt | 8 +- .../KolasuLanguageAssociatedToLionWebTest.kt | 8 +- .../lionweb/LionWebLanguageConverterTest.kt | 2 +- 4 files changed, 101 insertions(+), 79 deletions(-) diff --git a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverter.kt b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverter.kt index 4e6193d9d..e5c4acebf 100644 --- a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverter.kt +++ b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverter.kt @@ -23,13 +23,16 @@ import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.full.createType +typealias LWLanguage = Language +typealias EnumKClass = KClass> + /** * This class is able to convert between Kolasu and LionWeb languages, tracking the mapping. */ class LionWebLanguageConverter { private val astClassesAndClassifiers = BiMap, Classifier<*>>() - private val classesAndEnumerations = BiMap>, Enumeration>() - private val languages = BiMap() + private val classesAndEnumerations = BiMap() + private val languages = BiMap() init { val starLasuKLanguage = KolasuLanguage(StarLasuLWLanguage.name) @@ -38,29 +41,8 @@ class LionWebLanguageConverter { registerMapping(Named::class, StarLasuLWLanguage.Named) } - fun getKolasuClassesToConceptsMapping(): Map, Classifier<*>> { - return astClassesAndClassifiers.asToBsMap - } - - fun getConceptsToKolasuClassesMapping(): Map, KClass<*>> { - return astClassesAndClassifiers.bsToAsMap - } - - fun getEnumerationsToKolasuClassesMapping(): Map> { - return classesAndEnumerations.bsToAsMap - } - - fun knownLWLanguages(): Set { - return languages.bs - } - - fun correspondingLanguage(kolasuLanguage: KolasuLanguage): Language { - return languages.byA(kolasuLanguage) - ?: throw java.lang.IllegalArgumentException("Unknown Kolasu Language $kolasuLanguage") - } - - fun export(kolasuLanguage: KolasuLanguage): Language { - val lionwebLanguage = Language() + fun exportToLionWeb(kolasuLanguage: KolasuLanguage): LWLanguage { + val lionwebLanguage = LWLanguage() lionwebLanguage.version = "1" lionwebLanguage.name = kolasuLanguage.qualifiedName lionwebLanguage.key = kolasuLanguage.qualifiedName.replace('.', '-') @@ -92,7 +74,7 @@ class LionWebLanguageConverter { val superInterfaces = astClass.supertypes.map { it.classifier as KClass<*> } .filter { it.java.isInterface } superInterfaces.filter { it.isMarkedAsNodeType() }.forEach { - conceptInterface.addExtendedInterface(toConceptInterface(it)) + conceptInterface.addExtendedInterface(correspondingConceptInterface(it)) } } else { val concept = featuresContainer as Concept @@ -105,7 +87,7 @@ class LionWebLanguageConverter { } val interfaces = astClass.supertypes.map { it.classifier as KClass<*> }.filter { it.java.isInterface } interfaces.filter { it.isMarkedAsNodeType() }.forEach { - concept.addImplementedInterface(toConceptInterface(it)) + concept.addImplementedInterface(correspondingConceptInterface(it)) } } astClass.features().forEach { @@ -142,49 +124,13 @@ class LionWebLanguageConverter { return lionwebLanguage } - private fun toLWDataType(kType: KType, lionwebLanguage: Language): DataType<*> { - return when (kType) { - Int::class.createType() -> LionCoreBuiltins.getInteger() - Long::class.createType() -> LionCoreBuiltins.getInteger() - String::class.createType() -> LionCoreBuiltins.getString() - Boolean::class.createType() -> LionCoreBuiltins.getBoolean() - else -> { - val kClass = kType.classifier as KClass<*> - val isEnum = kClass.supertypes.any { it.classifier == Enum::class } - if (isEnum) { - val enumeration = classesAndEnumerations.byA(kClass as KClass>) - if (enumeration == null) { - val newEnumeration = Enumeration(lionwebLanguage, kClass.simpleName) - lionwebLanguage.addElement(newEnumeration) - classesAndEnumerations.associate(kClass, newEnumeration) - return newEnumeration - } else { - return enumeration - } - } else { - throw UnsupportedOperationException("KType: $kType") - } - } - } - } - - fun toConceptInterface(kClass: KClass<*>): ConceptInterface { - return toLWClassifier(kClass) as ConceptInterface - } - - fun toConcept(kClass: KClass<*>): Concept { - return toLWClassifier(kClass) as Concept - } - - fun matchingKClass(concept: Concept): KClass<*>? { - return this.astClassesAndClassifiers.bsToAsMap.entries.find { - it.key.key == concept.key && - it.key.language!!.id == concept.language!!.id && - it.key.language!!.version == concept.language!!.version - }?.value - } - - fun importLanguages(lwLanguage: Language, kolasuLanguage: KolasuLanguage) { + /** + * Importing a LionWeb language as a Kolasu language requires the generation of classes, to be performed + * separately. Once that is done we associate the Kolasu language defined by those classes to a certain + * LionWeb language, so that we can import LionWeb models by instantiating the corresponding classes in the + * Kolasu language. + */ + fun associateLanguages(lwLanguage: LWLanguage, kolasuLanguage: KolasuLanguage) { this.languages.associate(kolasuLanguage, lwLanguage) kolasuLanguage.astClasses.forEach { astClass -> var classifier: Classifier<*>? = null @@ -212,6 +158,56 @@ class LionWebLanguageConverter { } } + fun knownLWLanguages(): Set { + return languages.bs + } + + fun knownKolasuLanguages(): Set { + return languages.`as` + } + + fun correspondingLanguage(kolasuLanguage: KolasuLanguage): LWLanguage { + return languages.byA(kolasuLanguage) + ?: throw java.lang.IllegalArgumentException("Unknown Kolasu Language $kolasuLanguage") + } + + fun correspondingLanguage(lwLanguage: LWLanguage): KolasuLanguage { + return languages.byB(lwLanguage) + ?: throw java.lang.IllegalArgumentException("Unknown LionWeb Language $lwLanguage") + } + + fun getKolasuClassesToClassifiersMapping(): Map, Classifier<*>> { + return astClassesAndClassifiers.asToBsMap + } + + fun getClassifiersToKolasuClassesMapping(): Map, KClass<*>> { + return astClassesAndClassifiers.bsToAsMap + } + + fun getEnumerationsToKolasuClassesMapping(): Map { + return classesAndEnumerations.bsToAsMap + } + + fun getKolasuClassesToEnumerationsMapping(): Map { + return classesAndEnumerations.asToBsMap + } + + fun correspondingConceptInterface(kClass: KClass<*>): ConceptInterface { + return toLWClassifier(kClass) as ConceptInterface + } + + fun correspondingConcept(kClass: KClass<*>): Concept { + return toLWClassifier(kClass) as Concept + } + + fun correspondingKolasuClass(classifier: Classifier<*>): KClass<*>? { + return this.astClassesAndClassifiers.bsToAsMap.entries.find { + it.key.key == classifier.key && + it.key.language!!.id == classifier.language!!.id && + it.key.language!!.version == classifier.language!!.version + }?.value + } + private fun registerMapping(kolasuClass: KClass<*>, featuresContainer: Classifier<*>) { astClassesAndClassifiers.associate(kolasuClass, featuresContainer) } @@ -219,4 +215,30 @@ class LionWebLanguageConverter { private fun toLWClassifier(kClass: KClass<*>): Classifier<*> { return astClassesAndClassifiers.byA(kClass) ?: throw IllegalArgumentException("Unknown KClass $kClass") } + + private fun toLWDataType(kType: KType, lionwebLanguage: LWLanguage): DataType<*> { + return when (kType) { + Int::class.createType() -> LionCoreBuiltins.getInteger() + Long::class.createType() -> LionCoreBuiltins.getInteger() + String::class.createType() -> LionCoreBuiltins.getString() + Boolean::class.createType() -> LionCoreBuiltins.getBoolean() + else -> { + val kClass = kType.classifier as KClass<*> + val isEnum = kClass.supertypes.any { it.classifier == Enum::class } + if (isEnum) { + val enumeration = classesAndEnumerations.byA(kClass as EnumKClass) + if (enumeration == null) { + val newEnumeration = Enumeration(lionwebLanguage, kClass.simpleName) + lionwebLanguage.addElement(newEnumeration) + classesAndEnumerations.associate(kClass, newEnumeration) + return newEnumeration + } else { + return enumeration + } + } else { + throw UnsupportedOperationException("KType: $kType") + } + } + } + } } diff --git a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt index 424610bc8..54174b585 100644 --- a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt +++ b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt @@ -52,11 +52,11 @@ class LionWebModelConverter { } fun recordLanguage(kolasuLanguage: KolasuLanguage): Language { - return languageExporter.export(kolasuLanguage) + return languageExporter.exportToLionWeb(kolasuLanguage) } fun importLanguages(lwLanguage: Language, kolasuLanguage: KolasuLanguage) { - this.languageExporter.importLanguages(lwLanguage, kolasuLanguage) + this.languageExporter.associateLanguages(lwLanguage, kolasuLanguage) } fun export(kolasuTree: com.strumenta.kolasu.model.Node): Node { @@ -221,7 +221,7 @@ class LionWebModelConverter { fun import(lwTree: Node): com.strumenta.kolasu.model.Node { val referencesPostponer = ReferencesPostponer() lwTree.thisAndAllDescendants().reversed().forEach { lwNode -> - val kClass = languageExporter.matchingKClass(lwNode.concept) + val kClass = languageExporter.correspondingKolasuClass(lwNode.concept) ?: throw RuntimeException("We do not have StarLasu AST class for LIonWeb Concept ${lwNode.concept}") val kNode: com.strumenta.kolasu.model.Node = instantiate(kClass, lwNode, referencesPostponer) registerMapping(kNode, lwNode) @@ -235,7 +235,7 @@ class LionWebModelConverter { } private fun findConcept(kNode: com.strumenta.kolasu.model.Node): Concept { - return languageExporter.toConcept(kNode.javaClass.kotlin) + return languageExporter.correspondingConcept(kNode.javaClass.kotlin) } private fun nodeID(kNode: com.strumenta.kolasu.model.Node): String { diff --git a/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/KolasuLanguageAssociatedToLionWebTest.kt b/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/KolasuLanguageAssociatedToLionWebTest.kt index 881757b3e..190432324 100644 --- a/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/KolasuLanguageAssociatedToLionWebTest.kt +++ b/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/KolasuLanguageAssociatedToLionWebTest.kt @@ -63,9 +63,9 @@ class KolasuLanguageAssociatedToLionWebTest { addClass(LWRoot::class) } val lie = LionWebLanguageConverter() - lie.importLanguages(lwLang, kolasuLanguage) - assertEquals(LWRoot::class, lie.matchingKClass(lwRoot)) - assertEquals(LWNodeA::class, lie.matchingKClass(lwNodeA)) - assertEquals(LWNodeB::class, lie.matchingKClass(lwNodeB)) + lie.associateLanguages(lwLang, kolasuLanguage) + assertEquals(LWRoot::class, lie.correspondingKolasuClass(lwRoot)) + assertEquals(LWNodeA::class, lie.correspondingKolasuClass(lwNodeA)) + assertEquals(LWNodeB::class, lie.correspondingKolasuClass(lwNodeB)) } } diff --git a/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverterTest.kt b/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverterTest.kt index 9f9e2b042..265eb7caf 100644 --- a/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverterTest.kt +++ b/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebLanguageConverterTest.kt @@ -37,7 +37,7 @@ class LionWebLanguageConverterTest { addClass(SimpleRoot::class) } assertEquals(5, kLanguage.astClasses.size) - val lwLanguage = LionWebLanguageConverter().export(kLanguage) + val lwLanguage = LionWebLanguageConverter().exportToLionWeb(kLanguage) assertEquals("1", lwLanguage.version) assertEquals(5, lwLanguage.elements.size)