diff --git a/.gitignore b/.gitignore index b871d0c43df10dfe199ccccedb14a2cf78bc460b..e6431cf9d44258e2ba620f7e1f7f79ff0770c85a 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,4 @@ local.properties .idea/ .classpath .project +output/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..d32bc548305c52a170e059020262ab0e062d32d6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "lib/ModelSyncProvider"] + path = lib/ModelSyncProvider + url = https://git-st.inf.tu-dresden.de/cwerner/role_model_synchronization_provider.git +[submodule "lib/SCROLL"] + path = lib/SCROLL + url = https://github.com/portux/SCROLL.git diff --git a/assets/ttc17.ecore b/assets/ttc17.ecore new file mode 100644 index 0000000000000000000000000000000000000000..7f2edd41690a950461bf0e6137bcc912bf591e12 --- /dev/null +++ b/assets/ttc17.ecore @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="ttc17" nsURI="http://www.example.org/ttc17" nsPrefix="ttc17"> + <eClassifiers xsi:type="ecore:EClass" name="Person"> + <eStructuralFeatures xsi:type="ecore:EAttribute" name="fullName" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/> + </eClassifiers> + <eClassifiers xsi:type="ecore:EClass" name="Male" eSuperTypes="#//Person"/> + <eClassifiers xsi:type="ecore:EClass" name="Female" eSuperTypes="#//Person"/> +</ecore:EPackage> diff --git a/build.sbt b/build.sbt index d6e9bab9286d4ced4c8aeb33089254555c8c6a36..a2f6a4623878302e208f347d1f3cbe4226024a9e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,21 +1,23 @@ +import sbt.Keys.{libraryDependencies, scalacOptions, version} + val emfcommonVersion = "2.12.0" val emfecoreVersion = "2.12.0" val scrollVersion = "1.6" -name := "CodeGenerator" - -version := "0.1" - -scalaVersion := "2.12.6" - -libraryDependencies ++= Seq( - "org.scala-lang" % "scala-reflect" % scalaVersion.value, - "org.scala-lang" % "scala-compiler" % scalaVersion.value, - "org.eclipse.emf" % "org.eclipse.emf.common" % emfcommonVersion, - "org.eclipse.emf" % "org.eclipse.emf.ecore" % emfecoreVersion, - "com.github.max-leuthaeuser" %% "scroll" % scrollVersion -) +val syncProvider = RootProject(file("lib/ModelSyncProvider")) -scalacOptions ++= Seq( - "-language:implicitConversions" -) +lazy val generator = (project in file(".")) + .settings( + name := "CodeGenerator", + version := "0.1", + scalaVersion := "2.12.6", + libraryDependencies ++= Seq( + "org.scala-lang" % "scala-reflect" % scalaVersion.value, + "org.scala-lang" % "scala-compiler" % scalaVersion.value, + "org.eclipse.emf" % "org.eclipse.emf.common" % emfcommonVersion, + "org.eclipse.emf" % "org.eclipse.emf.ecore" % emfecoreVersion, + ), + scalacOptions ++= Seq( + "-language:implicitConversions" + ) + ).dependsOn(syncProvider) diff --git a/lib/ModelSyncProvider b/lib/ModelSyncProvider new file mode 160000 index 0000000000000000000000000000000000000000..ace8bdf5799dd89bd66d01782237123172bb0ec0 --- /dev/null +++ b/lib/ModelSyncProvider @@ -0,0 +1 @@ +Subproject commit ace8bdf5799dd89bd66d01782237123172bb0ec0 diff --git a/project/plugins.sbt b/project/plugins.sbt index a4e6fcc87c3d3c84b38d39cf4ec86abd88e39c99..45a6f02d1880ea4c9bff889a194ab87adf467611 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1,2 @@ addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4") +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") diff --git a/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala b/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala index 14c46b7a2856d9481eabc0641a7967e8d2440a65..409478bf65cba4749aa1326c253fe8a610464b00 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala @@ -1,7 +1,14 @@ package org.rosi_project.model_sync.generator import org.eclipse.emf.ecore._ +import org.eclipse.emf.ecore.resource.Resource import org.rosi_project.model_sync.generator.conversion.SModelGenerator +import org.rosi_project.model_sync.generator.io.SModelFSWriter +import org.rosi_project.model_sync.generator.io.SModelFSWriterTest.{model, personClass} +import org.rosi_project.model_sync.generator.sync_model.{GetterSetterGeneratingVisitor, SModel, SyncEnhancingVisitor} +import scroll.internal.ecore.ECoreImporter + +import scala.reflect.io.File /** @@ -11,23 +18,29 @@ object Generator extends App { val ecoreModel = loadEcore() - SModelGenerator.convert(ecoreModel) + val sModel = SModelGenerator.convert(ecoreModel) - def loadEcore(): EPackage = { - val factory: EcoreFactory = EcoreFactory.eINSTANCE - val ecorePackage: EcorePackage = EcorePackage.eINSTANCE - val ePackage: EPackage = factory.createEPackage() + prepareModel(sModel) + + sModel.accept(new SModelFSWriter(workingDir = File("output/raw"), keepClassFiles = true)) - val ePerson = factory.createEClass() - ePerson.setName("Person") - ePackage.getEClassifiers().add(ePerson) + def loadEcore(): EPackage = { + val importer = new ECoreImporter { + def lm(): Resource = loadModel() + } + importer.path = "assets/ttc17.ecore" + val res = importer.lm() + println(s"We done: $res") + + res.getContents.toArray(new Array[EObject](0)).toList.find(_.isInstanceOf[EPackage]).map((p: EObject) => p.asInstanceOf[EPackage]).orNull + } - val eName: EAttribute = factory.createEAttribute() - eName.setName("name") - eName.setEType(ecorePackage.getEString) - ePerson.getEStructuralFeatures.add(eName) + def prepareModel(sModel: SModel): Unit = { + val getterSetterVisitor = new GetterSetterGeneratingVisitor + val syncNotificationVisitor = new SyncEnhancingVisitor - ePackage + sModel.accept(getterSetterVisitor) + sModel.accept(syncNotificationVisitor) } } diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala index cb9a73a6b1d68b2d27f903f2cf3f7c41f0bf2e13..e7782503ab25181285604e4773959fb64f47d26c 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala @@ -7,12 +7,16 @@ import org.rosi_project.model_sync.generator.sync_model.SModelVisitor * @author Rico Bergmann */ class SClass(val name: String, - val attributes: Seq[SAttribute], + var attributes: Seq[SAttribute] = Seq.empty, val sPackage: String = "", var parent: STypedElement = null, var methods: Seq[SMethod] = Seq.empty) extends STypedElement { + private var constructorStatements: Seq[SMethodStatement] = Seq.empty + + def getAdditionalConstructorStatements: Seq[SMethodStatement] = constructorStatements + def isDefaultPackage: Boolean = sPackage == "" def isRootClass: Boolean = parent == null @@ -50,8 +54,12 @@ class SClass(val name: String, def addMethod(m: SMethod): Unit = methods = methods :+ m + def augmentConstructor(statement: SMethodStatement): Unit = constructorStatements = constructorStatements :+ statement + def setParent(parent: STypedElement): Unit = this.parent = parent + def setAttributes(attrs: Seq[SAttribute]): Unit = this.attributes = attrs + override def getName: String = name override def getPackage: String = sPackage diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/EmfTypeTranslator.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/EmfTypeTranslator.scala new file mode 100644 index 0000000000000000000000000000000000000000..463f272e655c8ea6a88542a28058d32b6e517a99 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/EmfTypeTranslator.scala @@ -0,0 +1,17 @@ +package org.rosi_project.model_sync.generator.conversion + +import org.eclipse.emf.ecore.EDataType +import org.rosi_project.model_sync.generator.acr.SType + +/** + * @author Rico Bergmann + */ +object EmfTypeTranslator { + + private val typeMap: Map[String, SType] = Map( + "EString" -> SType("String") + ) + + def getSClassFromEmf(dataType: EDataType): Option[SType] = typeMap.get(dataType.getName) + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SClassConverter.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SClassConverter.scala new file mode 100644 index 0000000000000000000000000000000000000000..e56125f6373cc455ab0abe0cdc99087818d90271 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SClassConverter.scala @@ -0,0 +1,59 @@ +package org.rosi_project.model_sync.generator.conversion + +import org.eclipse.emf.ecore.{EAttribute, EClass} +import org.rosi_project.model_sync.generator.acr.{SAttribute, SClass, STypedElement} +import org.rosi_project.model_sync.generator.sync_model.STypeRegistry + +/** + * @author Rico Bergmann + */ +class SClassConverter extends Converter[EClass, SClass] { + + override def convert(source: EClass): SClass = { + var attrs: List[SAttribute] = List.empty + + (source.getEAttributes: List[EAttribute]).foreach(eAttr => { + val attrType: STypedElement = STypeRegistry.query(eAttr.getEAttributeType.getName, eAttr.getEAttributeType.getEPackage.getName).getOrElse { + val newAttr = EmfTypeTranslator.getSClassFromEmf(eAttr.getEAttributeType).getOrElse(new SClass(eAttr.getEAttributeType.getName, sPackage = eAttr.getEAttributeType.getEPackage.getName)) + STypeRegistry.addType(newAttr) + } + attrs ::= SAttribute(eAttr.getName, attrType) + }) + + val parents: List[EClass] = source.getESuperTypes.toArray(new Array[EClass](0)).toList + + if (violatesSingleInheritance(parents)) { + throw new UnconvertibleEmfException(source.getEPackage, s"For class: $source") + } + + val parent: STypedElement = parents.headOption.map((p: EClass) => { + STypeRegistry.queryForName(p.getName).getOrElse { + val parentSClass: SClass = new SClass(p.getName, sPackage = p.getEPackage.getName) + STypeRegistry.addType(parentSClass) + parentSClass + } + }).orNull + + + // TODO add methods to SClass + + val currentClass: Option[STypedElement] = STypeRegistry.query(source.getName, source.getEPackage.getName) + + currentClass.map { + case clazz: SClass => + clazz.attributes = attrs + clazz.parent = parent + clazz + case sType => + sys.error(s"sType should have been a class: $sType") + }.getOrElse { + val createdClass: SClass = new SClass(source.getName, attrs, source.getEPackage.getName, parent) + STypeRegistry.addType(createdClass) + createdClass + } + + } + + private def violatesSingleInheritance(parents: List[EClass]): Boolean = parents.length > 1 + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala index b87387fc2e5b989bc0a874117d83422d49ac4adc..170416c884b40133348cf258857a49482b9a6620 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala @@ -17,18 +17,21 @@ object SModelGenerator extends Converter[EPackage, SModel] { val model = new SimpleSModel(packageName) - contents.foreach{ + contents.foreach { case ec: EClass => + model.addModelClass(new SClassConverter convert ec) + STypeRegistry.addType(SType(ec.getName, packageName)) println(s"Found class $ec") case et: EGenericType=> - STypeRegistry.addType(SType(et.eClass().getName, packageName)) + //STypeRegistry.addType(SType(et.eClass().getName, packageName)) println(s"Found type $et") case obj => println(s"Found something else: $obj") } println(s"The TypeRegistry:\n${STypeRegistry.allTypes}") + println(s"The model:\n$model") model } diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGeneratorTest.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGeneratorTest.scala new file mode 100644 index 0000000000000000000000000000000000000000..fc8987d44499a7a352b0974bb2785e3180d569d1 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGeneratorTest.scala @@ -0,0 +1,21 @@ +package org.rosi_project.model_sync.generator.conversion + +import org.eclipse.emf.ecore.{EObject, EPackage} +import org.eclipse.emf.ecore.resource.Resource +import scroll.internal.ecore.ECoreImporter + +/** + * @author Rico Bergmann + */ +object SModelGeneratorTest extends App { + + val importer = new ECoreImporter { + def lm(): Resource = loadModel() + } + importer.path = "assets/ttc17.ecore" + val res = importer.lm() + println(s"We done: $res") + + res.getContents.toArray(new Array[EObject](0)).toList.find(_.isInstanceOf[EPackage]).foreach(e => SModelGenerator.convert(e.asInstanceOf[EPackage])) + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/UnconvertibleEmfException.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/UnconvertibleEmfException.scala new file mode 100644 index 0000000000000000000000000000000000000000..86b2857b79291e4c0dbd528d26b2fd0593d1bbab --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/UnconvertibleEmfException.scala @@ -0,0 +1,12 @@ +package org.rosi_project.model_sync.generator.conversion + +import org.eclipse.emf.ecore.EPackage + +/** + * @author Rico Bergmann + */ +class UnconvertibleEmfException(val model: EPackage, val message: String = "") extends RuntimeException(message) { + + override def toString: String = s"UnconvertibleException: $model ${if (message.isEmpty) "" else s"($message)"}" + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/package.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/package.scala new file mode 100644 index 0000000000000000000000000000000000000000..436c3f61beeb2d87c1b321eb4c5aa7e08fb7748d --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/package.scala @@ -0,0 +1,20 @@ +package org.rosi_project.model_sync.generator + +import org.eclipse.emf.common.util.EList + +/** + * @author Rico Bergmann + */ +package object conversion { + + implicit def elist2slist[T](elst: EList[T]): List[T] = { + if (elst == null) { + null + } else { + var res: List[T] = List() + elst.forEach(elem => res = res :+ elem) + res + } + } + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/EmfImporter.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/EmfImporter.scala new file mode 100644 index 0000000000000000000000000000000000000000..f1eb7eedd5a6e49943aeee56be84d3b3811661f0 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/io/EmfImporter.scala @@ -0,0 +1,8 @@ +package org.rosi_project.model_sync.generator.io + +/** + * @author Rico Bergmann + */ +class EmfImporter { + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala index dcf13963d0891e641e8a506833db018ead54cc80..748a7e69fdccac4297551d3be4db75fd9097c191 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala @@ -19,6 +19,8 @@ class SClassWriter(val modelClass: SClass) { | |class $clazzFixture { | + | ${modelClass.getAdditionalConstructorStatements.map(_.content).mkString("\n")} + | | ${modelClass.methods.map(stringifyMethod).mkString("\n")} | |} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala index 296252ddd98d677a901644f4a139af52d5166da2..133c07a5f4acc140b62dc38b581adac661f37bf8 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala @@ -21,9 +21,6 @@ class SModelFSWriter( outputDir: Directory = File("output").toDirectory, keepClassFiles: Boolean = false) extends SModelVisitor { - // TODO enable different output dirs - // TODO enable retaining the .scala files - workingDir.jfile.mkdirs() outputDir.jfile.mkdirs() diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala index 7c212dc0a1433d1dabbbb01c6147ff346eec5da6..b2bd7883623e3770b89e44c7cb66e7bd0315b950 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala @@ -11,4 +11,6 @@ trait SModel extends SModelElement { def getAllClasses: Set[SClass] + override def toString: String = s"SModel: pckg=$getPackageName, classes=$getAllClasses" + } diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala index 16ede166272d498deceaa9f3899be28db89cc55a..a0be35f80cb61e92ae776de81fb86d87bcc6892b 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala @@ -11,8 +11,11 @@ object STypeRegistry { registerDefaultTypes() - def addType(theType: STypedElement): Unit = { - registeredTypes += theType + def addType(theType: STypedElement): STypedElement = { + if (!registeredTypes.contains(theType)) { + registeredTypes += theType + } + theType } def query(name: String, sPackage: String): Option[STypedElement] = { diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala index 6f27ff100d99ebad0adf330f3551b1703255ffe0..8306d15a07554312bd98df0d67fc34e3dc53e27b 100644 --- a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala @@ -1,6 +1,6 @@ package org.rosi_project.model_sync.generator.sync_model import org.rosi_project.model_sync.generator.acr._ -import scroll.internal.Compartment +import org.rosi_project.model_sync.sync.PlayerSync /** * @author Rico Bergmann @@ -12,14 +12,12 @@ class SyncEnhancingVisitor extends SModelVisitor { } override def visit(sClass: SClass): Unit = { - // TODO extend PlayerSync and call setup method in constructor - sClass.getInheritanceHierarchy.reverse.headOption.foreach{ - case root: SClass => root.setParent(SyncEnhancingVisitor.COMPARTMENT_STYPE) + case root: SClass => root.setParent(SyncEnhancingVisitor.PLAYER_SYNC_STYPE) case tp => sys.error(s"May not enhance $tp as it is a type and not a class") } - println(s"Class hierarchy for $sClass : ${sClass.getInheritanceHierarchy}") + sClass.augmentConstructor(SyncEnhancingVisitor.PLAYER_SYNC_INIT) } @@ -37,7 +35,7 @@ class SyncEnhancingVisitor extends SModelVisitor { private def extractSetterAttr(sMethod: SMethod): Option[String] = { sMethod.name match { - case SyncEnhancingVisitor.SetterRegex(attrName) => + case SyncEnhancingVisitor.Setter(attrName) => Option(attrName) case _ => None @@ -49,11 +47,11 @@ class SyncEnhancingVisitor extends SModelVisitor { object SyncEnhancingVisitor { - private val SetterRegex = """set([A-Z][a-zA-z0-9]*)""".r + private val Setter = """set([A-Z][a-zA-z0-9]*)""".r - private val COMPARTMENT_CLASS = classOf[Compartment] + private val PLAYER_SYNC_CLASS = classOf[PlayerSync] + private val PLAYER_SYNC_STYPE = SType(PLAYER_SYNC_CLASS.getSimpleName, PLAYER_SYNC_CLASS.getPackage.getName) - // TODO enhance with player sync instead of compartment! (where to get the class from?) - private val COMPARTMENT_STYPE = SType(COMPARTMENT_CLASS.getSimpleName, COMPARTMENT_CLASS.getPackage.getName) + private val PLAYER_SYNC_INIT = SMethodStatement("buildClass()") }