From d859f286772c1151cf77093ad12d07c40e80100a Mon Sep 17 00:00:00 2001
From: Rico Bergmann <rico.bergmann1@tu-dresden.de>
Date: Tue, 15 Jan 2019 13:52:37 +0100
Subject: [PATCH] #6 - Refactor Generator to delegate to services

The CLI and the generation workflow are two different components now.
Furthermore the generator will now use dedicated services to load an
ecore model as well as prepare the SModel instead of carrying these
functionalities out by itself.

Closes #6.
---
 .../model_sync/generator/CLIGenerator.scala   |  50 +++++
 .../model_sync/generator/EcoreLoader.scala    |  29 +++
 .../model_sync/generator/Generator.scala      | 172 ++++++------------
 .../SModelSyncPreparationService.scala        |  26 +++
 4 files changed, 159 insertions(+), 118 deletions(-)
 create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/CLIGenerator.scala
 create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/EcoreLoader.scala
 create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/SModelSyncPreparationService.scala

diff --git a/src/main/scala/org/rosi_project/model_sync/generator/CLIGenerator.scala b/src/main/scala/org/rosi_project/model_sync/generator/CLIGenerator.scala
new file mode 100644
index 0000000..9f6af4d
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/CLIGenerator.scala
@@ -0,0 +1,50 @@
+package org.rosi_project.model_sync.generator
+
+import java.{io => jio}
+
+import scopt.OptionParser
+
+/** Main entry into the application. It is intended to run as a console application and should be
+  * called like the following: `generator [-o DIR] [-c] ECORE` with the following options:
+  *
+  *  - `-o | --outdir DIR` will write the generated `model.jar` to the directory `DIR`
+  *  - `-c | --cleanup` will remove the generated `.scala` files and only keep the `.class` in the
+  *   generated `.jar` file
+  *  - `ECORE` should specify the ecore (XML) files which contains the model to create the scala
+  *   files for
+  *
+  * @author Rico Bergmann
+  */
+object CLIGenerator extends App {
+
+  val parser = new OptionParser[GeneratorConfig](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[jio.File]('w', "workdir").optional().action( (dir, conf) =>
+      conf.copy(workDir = dir)
+    ).text("The directory to place the generated .scala files in (temp dir by default)")
+
+    opt[jio.File]('m', "model").optional().action( (mj, conf) =>
+      conf.copy(modelFile = mj.getAbsolutePath)
+    ).text("The description of the model's components (mapped to the name of the ecore with .sync.json extension 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, GeneratorConfig()) match {
+    case Some(config) =>
+      new Generator(config).run()
+    case None =>
+  }
+
+}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/EcoreLoader.scala b/src/main/scala/org/rosi_project/model_sync/generator/EcoreLoader.scala
new file mode 100644
index 0000000..6bd33b8
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/EcoreLoader.scala
@@ -0,0 +1,29 @@
+package org.rosi_project.model_sync.generator
+
+import org.eclipse.emf.ecore.{EObject, EPackage}
+import scroll.internal.ecore.ECoreImporter
+
+/** Simple service to load an ECORE model from a file.
+  *
+  * @author Rico Bergmann
+  */
+class EcoreLoader extends ECoreImporter {
+
+  /** Fetches an ecore model from XML.
+    *
+    * @param path where to find the model
+    * @return the model described by the XML
+    */
+  def loadEcore(path: String = "assets/ttc17.ecore"): EPackage = {
+    this.path = path
+    val res = loadModel()
+    res.getContents.toArray(new Array[EObject](0)).toList.find(_.isInstanceOf[EPackage]).map((p: EObject) => p.asInstanceOf[EPackage]).orNull
+  }
+
+}
+
+/** Exception to indicate that the model images may not be copied into the JAR.
+  *
+  * @param cause the causing exception
+  */
+class EcoreLoadException(cause: Exception) extends RuntimeException(cause)
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 acf4fd9..69c9104 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
@@ -2,137 +2,73 @@ package org.rosi_project.model_sync.generator
 
 import net.liftweb.json._
 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.{ClassWritingException, ModelImagePreparationException, SModelFSWriter}
-import org.rosi_project.model_sync.generator.sync.{GetterSetterGeneratingVisitor, SyncEnhancingVisitor}
-import scopt.OptionParser
-import scroll.internal.ecore.ECoreImporter
-
-import scala.reflect.io.{Directory, File}
-import java.{io => jio}
-
-import org.rosi_project.model_sync.generator.acr_model.SModel
 import org.rosi_project.model_sync.generator.env.{CompilationException, JarPackaginException}
+import org.rosi_project.model_sync.generator.io.{ClassWritingException, ModelImagePreparationException, SModelFSWriter}
 
 import scala.io.Source
+import scala.reflect.io.File
 
-/** Main entry into the application. It is intended to run as a console application and should be
-  * called like the following: `generator [-o DIR] [-c] ECORE` with the following options:
+/** The `Generator` runs the whole workflow of generating a JAR of compiled Scala source files
+  * based on an ECORE file of some sync model.
   *
-  *  - `-o | --outdir DIR` will write the generated `model.jar` to the directory `DIR`
-  *  - `-c | --cleanup` will remove the generated `.scala` files and only keep the `.class` in the
-  *   generated `.jar` file
-  *  - `ECORE` should specify the ecore (XML) files which contains the model to create the scala
-  *   files for
+  * @param config the configuration that tells the generator where to locate the necessary files.
   *
   * @author Rico Bergmann
   */
-object Generator extends App {
-
-  val parser = new OptionParser[GeneratorConfig](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[jio.File]('w', "workdir").optional().action( (dir, conf) =>
-      conf.copy(workDir = dir)
-    ).text("The directory to place the generated .scala files in (temp dir by default)")
-
-    opt[jio.File]('m', "model").optional().action( (mj, conf) =>
-      conf.copy(modelFile = mj.getAbsolutePath)
-    ).text("The description of the model's components (mapped to the name of the ecore with .sync.json extension 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")
-  }
-
-  try {
-    parser.parse(args, GeneratorConfig()) match {
-      case Some(config) =>
+class Generator(config: GeneratorConfig) {
 
-        val modelJson = Source.fromFile(config.getModelFile).getLines.mkString
-        implicit val jsonFormats: Formats = DefaultFormats
-        val modelCfg = parse(modelJson).extract[ModelConfig]
-
-        var ecoreModel: EPackage = null
-        try {
-          ecoreModel = loadEcore(config.source)
-        }  catch {
-          case e: Exception => throw new EcoreLoadException(e)
-        }
-
-        val sModel = new SModelGenerator convert ecoreModel
-        prepareModel(sModel, modelCfg)
-        // write the model and create the JAR
-        println("... Writing model")
+  /**
+    * Starts the generation.
+    */
+  def run(): Unit = {
+    try {
+      val modelJson = Source.fromFile(config.getModelFile).getLines.mkString
+      implicit val jsonFormats: Formats = DefaultFormats
+      val modelCfg = parse(modelJson).extract[ModelConfig]
+
+      var ecoreModel: EPackage = null
+      try {
+        ecoreModel = new EcoreLoader loadEcore config.source
+      }  catch {
+        case e: Exception => throw new EcoreLoadException(e)
+      }
+
+      val sModel = new SModelGenerator convert ecoreModel
+      new SModelSyncPreparationService prepareModel(sModel, modelCfg)
+      // write the model and create the JAR
+      println("... Writing model")
+
+      if (config.hasWorkDir) {
+        sModel.accept(
+          new SModelFSWriter(
+            outputDir = File(config.outDir).toDirectory,
+            workingDir = File(config.workDir),
+            keepClassFiles = !config.cleanUp,
+            modelCfg = modelCfg,
+            currentDir = config.getModelPath))
+      } else {
+        sModel.accept(
+          new SModelFSWriter(
+            outputDir = File(config.outDir).toDirectory,
+            keepClassFiles = !config.cleanUp,
+            modelCfg = modelCfg,
+            currentDir = config.getModelPath))
+      }
 
-        if (config.hasWorkDir) {
-          sModel.accept(
-            new SModelFSWriter(
-              outputDir = File(config.outDir).toDirectory,
-              workingDir = File(config.workDir),
-              keepClassFiles = !config.cleanUp,
-              modelCfg = modelCfg,
-              currentDir = config.getModelPath))
-        } else {
-          sModel.accept(
-            new SModelFSWriter(
-              outputDir = File(config.outDir).toDirectory,
-              keepClassFiles = !config.cleanUp,
-              modelCfg = modelCfg,
-              currentDir = config.getModelPath))
-        }
-      case None =>
     }
-  }
-  catch {
-    case ele: EcoreLoadException =>
-      println(s"** ERROR ** could not load ecore model: $ele")
-    case mipe: ModelImagePreparationException =>
-      println(s"** ERROR ** could not prepare model images: $mipe")
-    case cwe: ClassWritingException =>
-      println(s"** ERROR ** could not write classes: $cwe")
-    case ce: CompilationException =>
-      println(s"** ERROR ** could not compile classes: $ce")
-    case jpe: JarPackaginException =>
-      println(s"** ERROR ** could not package JAR: $jpe")
-  }
-
-  /** Fetches an ecore model from XML.
-    *
-    * @param path where to find the model
-    * @return the model described by the XML
-    */
-  def loadEcore(path: String = "assets/ttc17.ecore"): EPackage = {
-    val importer = new ECoreImporter {
-      def lm(): Resource = loadModel()
+    catch {
+      case ele: EcoreLoadException =>
+        println(s"** ERROR ** could not load ecore model: $ele")
+      case mipe: ModelImagePreparationException =>
+        println(s"** ERROR ** could not prepare model images: $mipe")
+      case cwe: ClassWritingException =>
+        println(s"** ERROR ** could not write classes: $cwe")
+      case ce: CompilationException =>
+        println(s"** ERROR ** could not compile classes: $ce")
+      case jpe: JarPackaginException =>
+        println(s"** ERROR ** could not package JAR: $jpe")
     }
-    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
-  }
-
-  /** Augments a [[SModel]] with the necessary methods and statements to make it usable in a
-    * synchronization context.
-    *
-    * @param sModel the model to augment
-    */
-  def prepareModel(sModel: SModel, modelCfg: ModelConfig): Unit = {
-    val getterSetterVisitor = new GetterSetterGeneratingVisitor
-    val syncNotificationVisitor = new SyncEnhancingVisitor(modelCfg)
-
-    sModel.accept(getterSetterVisitor)
-    sModel.accept(syncNotificationVisitor)
   }
 
 }
-
-class EcoreLoadException(cause: Exception) extends RuntimeException(cause)
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/SModelSyncPreparationService.scala b/src/main/scala/org/rosi_project/model_sync/generator/SModelSyncPreparationService.scala
new file mode 100644
index 0000000..130f252
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/SModelSyncPreparationService.scala
@@ -0,0 +1,26 @@
+package org.rosi_project.model_sync.generator
+
+import org.rosi_project.model_sync.generator.acr_model.SModel
+import org.rosi_project.model_sync.generator.sync.{GetterSetterGeneratingVisitor, SyncEnhancingVisitor}
+
+/** Simple service to perform all the necessary adaptions of an [[SModel]] to make applicable for
+  * synchronization.
+  *
+  * @author Rico Bergmann
+  */
+class SModelSyncPreparationService {
+
+  /** Augments a [[SModel]] with the necessary methods and statements to make it usable in a
+    * synchronization context.
+    *
+    * @param sModel the model to augment
+    */
+  def prepareModel(sModel: SModel, modelCfg: ModelConfig): Unit = {
+    val getterSetterVisitor = new GetterSetterGeneratingVisitor
+    val syncNotificationVisitor = new SyncEnhancingVisitor(modelCfg)
+
+    sModel.accept(getterSetterVisitor)
+    sModel.accept(syncNotificationVisitor)
+  }
+
+}
-- 
GitLab