diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e6431cf9d44258e2ba620f7e1f7f79ff0770c85a --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +#### joe made this: http://goel.io/joe + +#### scala #### +*.class +*.log + + +#### sbt #### +# Simple Build Tool +# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control + +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +.history +.cache +.lib/ + + +#### jetbrains #### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + + +#### java #### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +#### eclipse #### + +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Custom (user defined) insertions +.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 new file mode 100644 index 0000000000000000000000000000000000000000..1660dd3eaef67463e4e9a0d01f8ce0040ae053a9 --- /dev/null +++ b/build.sbt @@ -0,0 +1,38 @@ +import sbt.Keys.{libraryDependencies, scalacOptions, version} + +val emfcommonVersion = "2.12.0" +val emfecoreVersion = "2.12.0" +val scrollVersion = "1.6" +val scoptVersion = "3.7.0" + +val syncProvider = RootProject(file("lib/ModelSyncProvider")) + +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, + "com.github.scopt" %% "scopt" % scoptVersion + ), + scalacOptions ++= Seq( + "-language:implicitConversions" + ), + mainClass in assembly := Some("org.rosi_project.model_sync.generator.Generator"), + assemblyMergeStrategy in assembly := { + case "MANIFEST.MF" => MergeStrategy.first + case "plugin.xml" => MergeStrategy.discard + case "plugin.properties" => MergeStrategy.discard + case "generated_package.exsd" => MergeStrategy.discard + case "dynamic_package.exsd" => MergeStrategy.discard + case PathList("schema", ps @ _ *) if ps.lastOption.exists(_.endsWith("generated_package.exsd")) => MergeStrategy.discard + case PathList("schema", ps @ _ *) if ps.lastOption.exists(_.endsWith("dynamic_package.exsd")) => MergeStrategy.discard + case x => + val oldStrategy = (assemblyMergeStrategy in assembly).value + oldStrategy(x) + } + ).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/lib/SCROLL b/lib/SCROLL new file mode 160000 index 0000000000000000000000000000000000000000..623765816af6d27387efb82dfb890bc4318c58a2 --- /dev/null +++ b/lib/SCROLL @@ -0,0 +1 @@ +Subproject commit 623765816af6d27387efb82dfb890bc4318c58a2 diff --git a/project/assembly.sbt b/project/assembly.sbt new file mode 100644 index 0000000000000000000000000000000000000000..d95475f16ffa0d9a0670795c8296b31fa24c76ec --- /dev/null +++ b/project/assembly.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.7") diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..8db5ca22266ea113c2b39c92dddb73c83e61ffee --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.2.1 \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000000000000000000000000000000000000..45a6f02d1880ea4c9bff889a194ab87adf467611 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +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/Config.scala b/src/main/scala/org/rosi_project/model_sync/generator/Config.scala new file mode 100644 index 0000000000000000000000000000000000000000..94f73bf979dc26e67368bd26ed582aaf8d3fba7b --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/Config.scala @@ -0,0 +1,10 @@ +package org.rosi_project.model_sync.generator + +import java.io.File + +/** + * @author Rico Bergmann + */ +case class Config(source: String = "", cleanUp: Boolean = false, outDir: File = new File(System.getProperty("user.dir"))) { + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..80a73165cf5b4295139a1b73e6d0aeb29b016cce --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala @@ -0,0 +1,67 @@ +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.sync_model.{GetterSetterGeneratingVisitor, SModel, SyncEnhancingVisitor} +import scopt.OptionParser +import scroll.internal.ecore.ECoreImporter + +import scala.reflect.io.File + +import java.{io => jio} + +/** + * @author Rico Bergmann + */ +object Generator extends App { + + // TODO CLI parser (scopt) here + // gen -c(leanup) -o [outdir] ECOREFILE + + val parser = new OptionParser[Config](programName="modelgen") { + head("modelgen", "0.1") + + arg[jio.File]("ecore").required().action( (ef, conf) => + conf.copy(source = ef.getAbsolutePath) + ).text("The ecore (XML) file of the model") + + opt[jio.File]('o', "outdir").optional().action( (dir, conf) => + conf.copy(outDir = dir) + ).text("The directory to place the generated model in (current dir by default)") + + opt[Unit]('c', "cleanup").optional().action( (_, conf) => + conf.copy(cleanUp = true) + ).text("Remove the generated .scala files and only keep the compiled .class files") + } + + parser.parse(args, Config()) match { + case Some(config) => + val ecoreModel = loadEcore(config.source) + val sModel = SModelGenerator.convert(ecoreModel) + prepareModel(sModel) + sModel.accept(new SModelFSWriter(outputDir = File(config.outDir).toDirectory, keepClassFiles = !config.cleanUp)) + case None => + } + + + + def loadEcore(path: String = "assets/ttc17.ecore"): EPackage = { + val importer = new ECoreImporter { + def lm(): Resource = loadModel() + } + importer.path = path + val res = importer.lm() + res.getContents.toArray(new Array[EObject](0)).toList.find(_.isInstanceOf[EPackage]).map((p: EObject) => p.asInstanceOf[EPackage]).orNull + } + + def prepareModel(sModel: SModel): Unit = { + val getterSetterVisitor = new GetterSetterGeneratingVisitor + val syncNotificationVisitor = new SyncEnhancingVisitor + + sModel.accept(getterSetterVisitor) + sModel.accept(syncNotificationVisitor) + } + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SAttribute.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SAttribute.scala new file mode 100644 index 0000000000000000000000000000000000000000..ca51f699e8c8c53e1c0ebee4b6a07914300ce560 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SAttribute.scala @@ -0,0 +1,13 @@ +package org.rosi_project.model_sync.generator.acr +import org.rosi_project.model_sync.generator.sync_model.SModelVisitor + +/** + * @author Rico Bergmann + */ +case class SAttribute(name: String, attrType: STypedElement) extends SModelElement { + + def getType: String = attrType.getName + + override def accept(visitor: SModelVisitor): Unit = visitor.visit(this) + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..e7782503ab25181285604e4773959fb64f47d26c --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala @@ -0,0 +1,87 @@ +package org.rosi_project.model_sync.generator.acr + +import org.rosi_project.model_sync.generator.sync_model.SModelVisitor + +/** Abstraction of a model element class which should be synced. + * + * @author Rico Bergmann + */ +class SClass(val name: String, + 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 + + def collectImports: Set[String] = { + val parentImport: List[String] = if (isRootClass) List() else includeImportIfNecessary(parent.getPackage, parent.getName) + + val attrTypeImports: List[String] = + attributes + .map(attr => includeImportIfNecessary(attr.attrType.getPackage, attr.getType)) + .fold(List())((l1, l2) => l1 ++ l2) + + val methodResultImports: List[String] = + methods + .map(_.result) + .map(res => includeImportIfNecessary(res.getPackage, res.getName)) + .fold(List())((l1, l2) => l1 ++ l2) + + val methodParamImports: List[String] = + methods + .map(_.params) + .fold(List())((l1, l2) => l1 ++ l2) + .map(param => includeImportIfNecessary(param.paramType.getPackage, param.paramType.getName)) + .fold(List())((l1, l2) => l1 ++ l2) + + val methodImplImports: List[String] = + methods + .map(_.implementationUsedTypes.toList) + .fold(List())((l1, l2) => l1 ++ l2) + .map(typ => includeImportIfNecessary(typ.getName, typ.getPackage)) + .fold(List())((l1, l2) => l1 ++ l2) + + (parentImport ++ attrTypeImports ++ methodResultImports ++ methodParamImports ++ methodImplImports).toSet + } + + 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 + + override def getConstructorParameters: Seq[SMethodParameter] = { + val ownParams = attributes.map(attr => SMethodParameter(attr.name, attr.attrType)) + val parentParams = if (isRootClass) List() else parent.getConstructorParameters + ownParams ++ parentParams + } + + override def getInheritanceHierarchy: Seq[STypedElement] = if (isRootClass) List(this) else this +: parent.getInheritanceHierarchy + + override def accept(visitor: SModelVisitor): Unit = { + attributes.foreach(_.accept(visitor)) + methods.foreach(_.accept(visitor)) + visitor.visit(this) + } + + private def includeImportIfNecessary(sPackage: String, sClass: String): List[String] = { + if (sPackage != this.sPackage && sPackage != "") List(s"$sPackage.$sClass") else List() + } + + override def toString: String = s"$name" + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SGetter.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SGetter.scala new file mode 100644 index 0000000000000000000000000000000000000000..160e39b243d656d910a0fa16f63243f7e27a0bbf --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SGetter.scala @@ -0,0 +1,15 @@ +package org.rosi_project.model_sync.generator.acr + +import org.rosi_project.model_sync.generator.support.StringUtils + + +/** + * @author Rico Bergmann + */ +class SGetter(attr: SAttribute) extends SMethod( + name = s"get${StringUtils.firstLetterToUpperCase(attr.name)}", + result = attr.attrType, + params = Seq.empty, + implementation = Seq(SMethodStatement(attr.name))) { + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethod.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethod.scala new file mode 100644 index 0000000000000000000000000000000000000000..7bf7e1d8ad7b031eaeaac23a8f069fba44c0ea95 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethod.scala @@ -0,0 +1,27 @@ +package org.rosi_project.model_sync.generator.acr + +import org.rosi_project.model_sync.generator.sync_model.SModelVisitor + +// TODO use a simple DSL to represent the method body + +/** + * @author Rico Bergmann + */ +class SMethod(val name: String, + val result: STypedElement, + val params: Seq[SMethodParameter], + var implementation: Seq[SMethodStatement], + var implementationUsedTypes: Set[STypedElement] = Set.empty) + extends SModelElement { + + def getResultType: String = result.getName + + def updateImplementation(impl: Seq[SMethodStatement]): Unit = this.implementation = impl + + def augmentImplementation(statement: SMethodStatement): Unit = this.implementation = this.implementation :+ statement + + override def accept(visitor: SModelVisitor): Unit = visitor.visit(this) + + override def toString: String = s"$name" + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethodParameter.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethodParameter.scala new file mode 100644 index 0000000000000000000000000000000000000000..5951f01975dc466b906912e8fdb58ae9df6ff73e --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethodParameter.scala @@ -0,0 +1,10 @@ +package org.rosi_project.model_sync.generator.acr + +/** + * @author Rico Bergmann + */ +case class SMethodParameter(name: String, paramType: STypedElement) { + + def getType: String = paramType.getName + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethodStatement.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethodStatement.scala new file mode 100644 index 0000000000000000000000000000000000000000..5bfa5f74cd5065bded80b46fc3e7a36532877495 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SMethodStatement.scala @@ -0,0 +1,8 @@ +package org.rosi_project.model_sync.generator.acr + +/** + * @author Rico Bergmann + */ +case class SMethodStatement(content: String) { + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SModelElement.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SModelElement.scala new file mode 100644 index 0000000000000000000000000000000000000000..193fa526dd59a0bf60c0c6e407e5dc5a69e602e5 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SModelElement.scala @@ -0,0 +1,12 @@ +package org.rosi_project.model_sync.generator.acr + +import org.rosi_project.model_sync.generator.sync_model.SModelVisitor + +/** + * @author Rico Bergmann + */ +trait SModelElement { + + def accept(visitor: SModelVisitor) + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SSetter.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SSetter.scala new file mode 100644 index 0000000000000000000000000000000000000000..09e65ecba2dd354a6994b2256b1ae45dfd8647d4 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SSetter.scala @@ -0,0 +1,14 @@ +package org.rosi_project.model_sync.generator.acr + +import org.rosi_project.model_sync.generator.support.StringUtils + +/** + * @author Rico Bergmann + */ +class SSetter(attr: SAttribute) extends SMethod( + name = s"set${StringUtils.firstLetterToUpperCase(attr.name)}", + result = SType.UNIT, + params = Seq(SMethodParameter(attr.name.head.toString, attr.attrType)), + implementation = Seq(SMethodStatement(s"${attr.name} = ${attr.name.head}"))) { + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SType.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SType.scala new file mode 100644 index 0000000000000000000000000000000000000000..a7de086a5d0f6b397d2fa676cf666a746d943520 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SType.scala @@ -0,0 +1,21 @@ +package org.rosi_project.model_sync.generator.acr +import org.rosi_project.model_sync.generator.sync_model.SModelVisitor + +/** + * @author Rico Bergmann + */ +case class SType(name: String, sPackage: String = "") extends STypedElement { + + override def getName: String = name + + override def getPackage: String = sPackage + + override def accept(visitor: SModelVisitor): Unit = visitor.visit(this) + +} + +object SType { + + val UNIT = SType("Unit") + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/STypedElement.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/STypedElement.scala new file mode 100644 index 0000000000000000000000000000000000000000..faa239e919c3a20b214b5a256c9e7d829f00f529 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/STypedElement.scala @@ -0,0 +1,16 @@ +package org.rosi_project.model_sync.generator.acr + +/** + * @author Rico Bergmann + */ +trait STypedElement extends SModelElement { + + def getName: String + + def getPackage: String + + def getConstructorParameters: Seq[SMethodParameter] = Seq.empty + + def getInheritanceHierarchy: Seq[STypedElement] = Seq.empty + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/package.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/package.scala new file mode 100644 index 0000000000000000000000000000000000000000..62fc1c074a125a94d79a8438c0b8217ca4c70701 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/package.scala @@ -0,0 +1,8 @@ +package org.rosi_project.model_sync.generator + +/** Abstract class representation of the Scala code to generate + * @author Rico Bergmann + */ +package object acr { + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/Converter.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/Converter.scala new file mode 100644 index 0000000000000000000000000000000000000000..776643f8ef3be5d7049d4cf74898ecf15e58e87e --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/Converter.scala @@ -0,0 +1,10 @@ +package org.rosi_project.model_sync.generator.conversion + +/** + * @author Rico Bergmann + */ +trait Converter[S, T] { + + def convert(source: S): T + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..170416c884b40133348cf258857a49482b9a6620 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala @@ -0,0 +1,39 @@ +package org.rosi_project.model_sync.generator.conversion + +import org.eclipse.emf.ecore.{EClass, EGenericType, EPackage} +import org.rosi_project.model_sync.generator.acr.SType +import org.rosi_project.model_sync.generator.sync_model.{SModel, STypeRegistry, SimpleSModel} + +import scala.collection.JavaConverters._ + +/** + * @author Rico Bergmann + */ +object SModelGenerator extends Converter[EPackage, SModel] { + + override def convert(source: EPackage): SModel = { + val packageName = if (source.getName != null) source.getName else "" + val contents = source.eAllContents().asScala + + val model = new SimpleSModel(packageName) + + 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)) + 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..a10220112001e3e74c695edeb35fe8bc2edf3512 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..748a7e69fdccac4297551d3be4db75fd9097c191 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala @@ -0,0 +1,70 @@ +package org.rosi_project.model_sync.generator.io + +import org.rosi_project.model_sync.generator.acr.{SAttribute, SMethod, SClass} + +/** + * @author Rico Bergmann + */ +class SClassWriter(val modelClass: SClass) { + + private val pckg: String = if (modelClass.isDefaultPackage) "" else s"package ${modelClass.sPackage}" + private val imports: Seq[String] = modelClass.collectImports.toSeq + + private val clazzFixture: String = generateClassFixture + + def stringify: String = { + s"""$pckg + | + |${imports.map(i => s"import $i").mkString("\n")} + | + |class $clazzFixture { + | + | ${modelClass.getAdditionalConstructorStatements.map(_.content).mkString("\n")} + | + | ${modelClass.methods.map(stringifyMethod).mkString("\n")} + | + |} + """.stripMargin + } + + protected def generateGetter(attr: SAttribute): String = + s"def get${firstLetterToUpperCase(attr.name)}(): ${attr.getType} = ${attr.name}" + + protected def generateSetter(attr: SAttribute): String = { + s"""def set${firstLetterToUpperCase(attr.name)}(${attr.name.head}: ${attr.getType}): Unit = { + | this.${attr.name} = ${attr.name.head} + |} + """.stripMargin + } + + protected def stringifyMethod(m: SMethod): String = { + s"""def ${m.name}(${m.params.map(param => s"${param.name}: ${param.getType}").mkString(", ")}): ${m.getResultType} = { + | ${m.implementation.map(_.content).mkString("\n")} + |} + """.stripMargin + } + + protected def generateClassFixture: String = { + var params: List[String] = modelClass.attributes.map(attr => s"var ${attr.name}: ${attr.getType}").toList + val parentConstructorParams: String = + if (modelClass.isRootClass) + "" + else + modelClass.parent.getConstructorParameters.map(param => s"${param.name}: ${param.getType}").mkString(", ") + + val parentConstructor: String = if (modelClass.isRootClass) "" else s"(${modelClass.parent.getConstructorParameters.map(_.name).toList.mkString(", ")})" + + if (parentConstructorParams != "") + params ::= parentConstructorParams + + val constructor = s"(${params.mkString(", ")})" + var baseFixture = s"${modelClass.getName}$constructor" + + if (modelClass.isRootClass) baseFixture else s"$baseFixture extends ${modelClass.parent.getName}$parentConstructor" + } + + private def firstLetterToUpperCase(str: String): String = { + str.head.toUpper + str.tail + } + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriterTest.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriterTest.scala new file mode 100644 index 0000000000000000000000000000000000000000..ecd08594aa7bbbc833bfd3cce000799776201c36 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriterTest.scala @@ -0,0 +1,26 @@ +package org.rosi_project.model_sync.generator.io + +import org.rosi_project.model_sync.generator.acr._ +import org.rosi_project.model_sync.generator.sync_model.{GetterSetterGeneratingVisitor, SyncEnhancingVisitor} + +/** + * @author Rico Bergmann + */ +object SClassWriterTest/* extends App */{ + + val stringType = SType("String") + val attrs = Seq(SAttribute("name", stringType)) + val sayHelloMethod = new SMethod("sayHello", stringType, Seq.empty, Seq(SMethodStatement("s\"Hello $name\""))) + val modelClass = new SClass("Person", attrs, methods = Seq(sayHelloMethod)) + + val getterSetterVisitor = new GetterSetterGeneratingVisitor + modelClass.accept(getterSetterVisitor) + + val syncNotificationVisitor = new SyncEnhancingVisitor + modelClass.accept(syncNotificationVisitor) + + val writer = new SClassWriter(modelClass) + + println(writer.stringify) + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..133c07a5f4acc140b62dc38b581adac661f37bf8 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala @@ -0,0 +1,170 @@ +package org.rosi_project.model_sync.generator.io + +import java.io.{ BufferedInputStream, FileInputStream, FileOutputStream, PrintWriter } +import java.net.URLClassLoader +import java.nio.file.Files +import java.util.jar.{ Attributes, JarEntry, JarOutputStream, Manifest } + +import org.rosi_project.model_sync.generator.acr.{ SAttribute, SClass, SMethod, SType } +import org.rosi_project.model_sync.generator.sync_model.{ SModel, SModelVisitor } + +import scala.reflect.io.{ File, Path } +import scala.tools.nsc.reporters.ConsoleReporter +import scala.tools.nsc.{ GenericRunnerSettings, Global, Settings } +import scala.util.control.Breaks._ +import scala.reflect.io.Directory + +/** @author Rico Bergmann + */ +class SModelFSWriter( + workingDir: File = File(Files.createTempDirectory("model").toFile), + outputDir: Directory = File("output").toDirectory, + keepClassFiles: Boolean = false) extends SModelVisitor { + + workingDir.jfile.mkdirs() + outputDir.jfile.mkdirs() + + private var sFilesToCompile: List[File] = List.empty + + println(s"... Temp dir (sources) is $workingDir") + + override def visit(sModel: SModel): Unit = { + println(s"... Wrote files (sources) $sFilesToCompile") + + println("... Starting compilation") + // see: https://stackoverflow.com/a/20323371/5161760 + val out = new PrintWriter(System.out) + val compilationSettings: Settings = new GenericRunnerSettings(out.println) + // TODO do we want to reuse the whole classpath? Or just the parts we _actually_ need + compilationSettings.classpath.value = adaptClassPathToOSEnv(fetchCurrentClassPath).distinct.mkString(File.pathSeparator) + println(s"Using classpath ${compilationSettings.classpath.value}") + compilationSettings.outdir.value = workingDir.toAbsolute.toString + val reporter = new ConsoleReporter(compilationSettings) + val compiler = new Global(compilationSettings, reporter) + val runner = new compiler.Run + runner.compile(sFilesToCompile.map(_.toAbsolute.toString)) + println("... Compilation done") + + if (!keepClassFiles) { + println("... Cleaning up") + sFilesToCompile.foreach { + _.delete + } + } else { + println("... No clean-up requested") + } + + println("... Generating JAR") + // see: https://stackoverflow.com/a/1281295/5161760 + val mf = new Manifest + mf.getMainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0") + val targetJar = new JarOutputStream(new FileOutputStream(outputDir + File.separator + "model.jar"), mf) + var filesToAdd: List[java.io.File] = workingDir.jfile.listFiles.toList + + while (filesToAdd.nonEmpty) { + val file = filesToAdd.head + var fname = file.getAbsolutePath.replace(workingDir.jfile.getAbsolutePath + File.separator, "").replaceAll("\\\\", "/") + println(s"Adding to JAR: $fname") + if (file.isDirectory) { + if (!fname.endsWith("/")) { + fname += "/" + } + filesToAdd ++= file.listFiles.toList + + val entry = new JarEntry(fname) + entry.setTime(file.lastModified) + targetJar.putNextEntry(entry) + targetJar.closeEntry() + } else { + val entry = new JarEntry(fname) + entry.setTime(file.lastModified) + targetJar.putNextEntry(entry) + + val in = new BufferedInputStream(new FileInputStream(file)) + var buffer = new Array[Byte](1024) + breakable { + while (true) { + val bytesRead = in.read(buffer) + if (bytesRead == -1) { + break + } + targetJar.write(buffer, 0, bytesRead) + } + } + targetJar.closeEntry() + in.close() + } + + filesToAdd = filesToAdd.tail + } + + targetJar.close() + println("... Done") + } + + override def visit(sClass: SClass): Unit = { + println(s"Writing class $sClass") + val classNameWithPath = workingDir.toAbsolute.toString() + File.separator + pckg2Path(sClass.getPackage) + File.separator + s"${sClass.name}.scala" + val writer = new SClassWriter(sClass) + + val classFile = File(classNameWithPath) + + classFile.jfile.getParentFile.mkdirs() + classFile.writeAll(writer.stringify) + sFilesToCompile ::= classFile + } + + override def visit(sAttr: SAttribute): Unit = { + // pass + } + + override def visit(sMethod: SMethod): Unit = { + // pass + } + + override def visit(sType: SType): Unit = { + // pass + } + + private def pckg2Path(pckg: String): Path = Path(pckg.replace(".", File.separator)) + + private def fetchCurrentClassPath: List[String] = { + val ctxLoader = Thread.currentThread().getContextClassLoader + ctxLoader match { + case urlCL: URLClassLoader => + urlCL.getURLs.toList.map { + _.toString + } + case wrappedCL => + wrappedCL.getParent match { + case urlCL: URLClassLoader => + urlCL.getURLs.toList.map { + _.toString + } + case something => sys.error(s"Could not unwrap class loader: $something") + } + } + } + + private def adaptClassPathToOSEnv(cp: List[String]): List[String] = { + sys.props("os.name").toLowerCase match { + case win if win contains "windows" => + cp.map(entry => { + val WinUrl = """file:/(.*)""".r + entry match { + case WinUrl(path) => path + case p => p + } + }).map(_.replaceAll("/", "\\\\").replaceAll("%20", " ")) + case unix => + val UnixUrl = """file:(.*)""".r + cp.map(entry => { + entry match { + case UnixUrl(path) => path + case p => p + } + }) + } + } + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriterTest.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriterTest.scala new file mode 100644 index 0000000000000000000000000000000000000000..461367d73a1eb5fa3f9a65a831690811fea2d185 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriterTest.scala @@ -0,0 +1,27 @@ +package org.rosi_project.model_sync.generator.io + +import org.rosi_project.model_sync.generator.acr._ +import org.rosi_project.model_sync.generator.sync_model.{ GetterSetterGeneratingVisitor, SimpleSModel, SyncEnhancingVisitor } +import scala.reflect.io.File + +/** @author Rico Bergmann + */ +object SModelFSWriterTest/* extends App */{ + + val model = new SimpleSModel("org.foo") + + val stringType = SType("String") + + val personAttrs = Seq(SAttribute("name", stringType)) + val personSayHelloMethod = new SMethod("sayHello", stringType, Seq.empty, Seq(SMethodStatement("s\"Hello $name\""))) + val personClass = new SClass("Person", personAttrs, methods = Seq(personSayHelloMethod), sPackage = "foo") + + val getterSetterVisitor = new GetterSetterGeneratingVisitor + val syncNotificationVisitor = new SyncEnhancingVisitor + + model.addModelClass(personClass) + + model.accept(getterSetterVisitor) + model.accept(syncNotificationVisitor) + model.accept(new SModelFSWriter(workingDir = File("output/raw"), keepClassFiles = true)) +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/support/Assert.scala b/src/main/scala/org/rosi_project/model_sync/generator/support/Assert.scala new file mode 100644 index 0000000000000000000000000000000000000000..abf3f38aa107c6b2887cc286bc062e93fe89025b --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/support/Assert.scala @@ -0,0 +1,16 @@ +package org.rosi_project.model_sync.generator.support + +/** + * @author Rico Bergmann + */ +object Assert { + + def notNull(obj: AnyRef, msg: String): Unit = isTrue(obj != null, msg) + + def isTrue(cond: Boolean, msg: String): Unit = if (!cond) { + throw new AssertionError(msg) + } + + def isFalse(cond: Boolean, msg: String): Unit = isTrue(!cond, msg) + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/support/GenericRegistry.scala b/src/main/scala/org/rosi_project/model_sync/generator/support/GenericRegistry.scala new file mode 100644 index 0000000000000000000000000000000000000000..81889257cfceb9effc6b20a56ad4aaa60729f803 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/support/GenericRegistry.scala @@ -0,0 +1,10 @@ +package org.rosi_project.model_sync.generator.support + +/** + * @author Rico Bergmann + */ +class GenericRegistry[T](val unique: Boolean = true) { + + + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/support/StringUtils.scala b/src/main/scala/org/rosi_project/model_sync/generator/support/StringUtils.scala new file mode 100644 index 0000000000000000000000000000000000000000..338834dd93d4afe054adfd8edad772e7f38bb521 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/support/StringUtils.scala @@ -0,0 +1,12 @@ +package org.rosi_project.model_sync.generator.support + +/** + * @author Rico Bergmann + */ +object StringUtils { + + def firstLetterToUpperCase(str: String): String = + str.headOption.map(h => h.toUpper + str.tail).getOrElse("") + + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/GetterSetterGeneratingVisitor.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/GetterSetterGeneratingVisitor.scala new file mode 100644 index 0000000000000000000000000000000000000000..11207a6861fef44b0b06eca25c42f8ac279282d8 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/GetterSetterGeneratingVisitor.scala @@ -0,0 +1,34 @@ +package org.rosi_project.model_sync.generator.sync_model +import org.rosi_project.model_sync.generator.acr._ + +/** + * @author Rico Bergmann + */ +class GetterSetterGeneratingVisitor extends SModelVisitor { + + override def visit(sModel: SModel): Unit = { + // pass + } + + override def visit(sClass: SClass): Unit = { + sClass.attributes.foreach(attr => { + val getter = new SGetter(attr) + val setter = new SSetter(attr) + sClass.addMethod(getter) + sClass.addMethod(setter) + }) + + } + + override def visit(sAttr: SAttribute): Unit = { + // pass + } + + override def visit(sMethod: SMethod): Unit = { + // pass + } + + override def visit(sType: SType): Unit = { + // pass + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..b2bd7883623e3770b89e44c7cb66e7bd0315b950 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala @@ -0,0 +1,16 @@ +package org.rosi_project.model_sync.generator.sync_model + +import org.rosi_project.model_sync.generator.acr.{SClass, SModelElement} + +/** + * @author Rico Bergmann + */ +trait SModel extends SModelElement { + + def getPackageName: String + + 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/SModelVisitor.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModelVisitor.scala new file mode 100644 index 0000000000000000000000000000000000000000..0d1dd128e214e33c2044bdd2554745c1ad6faed3 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModelVisitor.scala @@ -0,0 +1,22 @@ +package org.rosi_project.model_sync.generator.sync_model + +import org.rosi_project.model_sync.generator.acr.{SAttribute, SMethod, SClass, SType} + +// TODO fix cyclic imports with classes from acr package + +/** + * @author Rico Bergmann + */ +trait SModelVisitor { + + def visit(sModel: SModel): Unit + + def visit(sClass: SClass): Unit + + def visit(sAttr: SAttribute): Unit + + def visit(sMethod: SMethod): Unit + + def visit(sType: SType): Unit + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..a0be35f80cb61e92ae776de81fb86d87bcc6892b --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala @@ -0,0 +1,49 @@ +package org.rosi_project.model_sync.generator.sync_model + +import org.rosi_project.model_sync.generator.acr.{SType, STypedElement} + +/** + * @author Rico Bergmann + */ +object STypeRegistry { + + private var registeredTypes: Set[STypedElement] = Set() + + registerDefaultTypes() + + def addType(theType: STypedElement): STypedElement = { + if (!registeredTypes.contains(theType)) { + registeredTypes += theType + } + theType + } + + def query(name: String, sPackage: String): Option[STypedElement] = { + registeredTypes.find(t => t.getName == name && t.getPackage == sPackage) + } + + def queryForName(name: String): Option[STypedElement] = { + registeredTypes.find(_.getName == name) + } + + def allTypes: Set[STypedElement] = registeredTypes + + private def registerDefaultTypes(): Unit = { + registeredTypes ++= Seq( + SType("Boolean"), + SType("Byte"), + SType("Short"), + SType("Integer"), + SType("Long"), + SType("Float"), + SType("Double"), + SType("String"), + SType("Set"), + SType("List"), + SType("Map") + ) + } + + override def toString: String = s"Registry: $registeredTypes" + +} diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SimpleSModel.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SimpleSModel.scala new file mode 100644 index 0000000000000000000000000000000000000000..4bb43ec9bba28a9fdb01342d230f1f54898ebf14 --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SimpleSModel.scala @@ -0,0 +1,22 @@ +package org.rosi_project.model_sync.generator.sync_model +import org.rosi_project.model_sync.generator.acr.SClass + +/** + * @author Rico Bergmann + */ +class SimpleSModel(val sPackage: String = "") extends SModel { + + var sClasses: Set[SClass] = Set.empty + + def addModelClass(mClass: SClass): Unit = sClasses += mClass + + override def getPackageName: String = sPackage + + override def getAllClasses: Set[SClass] = sClasses + + override def accept(visitor: SModelVisitor): Unit = { + sClasses.foreach(_.accept(visitor)) + visitor.visit(this) + } + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..8306d15a07554312bd98df0d67fc34e3dc53e27b --- /dev/null +++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala @@ -0,0 +1,57 @@ +package org.rosi_project.model_sync.generator.sync_model +import org.rosi_project.model_sync.generator.acr._ +import org.rosi_project.model_sync.sync.PlayerSync + +/** + * @author Rico Bergmann + */ +class SyncEnhancingVisitor extends SModelVisitor { + + override def visit(sModel: SModel): Unit = { + // pass + } + + override def visit(sClass: SClass): Unit = { + sClass.getInheritanceHierarchy.reverse.headOption.foreach{ + 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") + } + + sClass.augmentConstructor(SyncEnhancingVisitor.PLAYER_SYNC_INIT) + + } + + override def visit(sAttr: SAttribute): Unit = { + // pass + } + + override def visit(sMethod: SMethod): Unit = { + extractSetterAttr(sMethod).foreach(attr => sMethod.augmentImplementation(SMethodStatement(s"+this change$attr ()"))) + } + + override def visit(sType: SType): Unit = { + // pass + } + + private def extractSetterAttr(sMethod: SMethod): Option[String] = { + sMethod.name match { + case SyncEnhancingVisitor.Setter(attrName) => + Option(attrName) + case _ => + None + } + + } + +} + +object SyncEnhancingVisitor { + + private val Setter = """set([A-Z][a-zA-z0-9]*)""".r + + private val PLAYER_SYNC_CLASS = classOf[PlayerSync] + private val PLAYER_SYNC_STYPE = SType(PLAYER_SYNC_CLASS.getSimpleName, PLAYER_SYNC_CLASS.getPackage.getName) + + private val PLAYER_SYNC_INIT = SMethodStatement("buildClass()") + +}