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 0000000000000000000000000000000000000000..9f6af4d784e94a44459d35eb17e5c33159c54a02 --- /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 0000000000000000000000000000000000000000..6bd33b84fa74494b769718936aa11b759e4c5bea --- /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 acf4fd9eec7adfc4923adca5bd88bd402ecee93b..69c91044992b22cb5d4733834f2136c4360e7409 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 0000000000000000000000000000000000000000..130f2527eae224b7fed30dcf4295e0b1ed7fa876 --- /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) + } + +}