From ba434a6c30a5ebdfca8dc55853bcb3d84a94582a Mon Sep 17 00:00:00 2001
From: Rico Bergmann <rico.bergmann1@tu-dresden.de>
Date: Tue, 15 Jan 2019 13:17:36 +0100
Subject: [PATCH] #8 - Add basic exception handling to IO operations

Closes #8.
---
 .../model_sync/generator/Generator.scala      | 85 ++++++++++++-------
 .../generator/env/FilesCompiler.scala         | 30 ++++---
 .../generator/env/JarPackager.scala           | 78 +++++++++--------
 .../generator/io/SModelFSWriter.scala         | 64 ++++++++------
 4 files changed, 158 insertions(+), 99 deletions(-)

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 9160439..acf4fd9 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
@@ -4,7 +4,7 @@ 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.SModelFSWriter
+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
@@ -13,6 +13,7 @@ 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 scala.io.Source
 
@@ -53,36 +54,56 @@ object Generator extends App {
     ).text("Remove the generated .scala files and only keep the compiled .class files")
   }
 
-  parser.parse(args, GeneratorConfig()) match {
-    case Some(config) =>
-
-      val modelJson = Source.fromFile(config.getModelFile).getLines.mkString
-      implicit val jsonFormats: Formats = DefaultFormats
-      val modelCfg = parse(modelJson).extract[ModelConfig]
-
-      val ecoreModel = loadEcore(config.source)
-      val sModel = new SModelGenerator convert ecoreModel
-      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))
-      }
-    case None =>
+  try {
+    parser.parse(args, GeneratorConfig()) match {
+      case Some(config) =>
+
+        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")
+
+        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.
@@ -113,3 +134,5 @@ object Generator extends App {
   }
 
 }
+
+class EcoreLoadException(cause: Exception) extends RuntimeException(cause)
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/env/FilesCompiler.scala b/src/main/scala/org/rosi_project/model_sync/generator/env/FilesCompiler.scala
index 29a00c4..516f285 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/env/FilesCompiler.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/env/FilesCompiler.scala
@@ -17,16 +17,26 @@ class FilesCompiler(outDir: File) {
     */
   def run(filesToCompile: List[File]): Unit = {
     // see: https://stackoverflow.com/a/20323371/5161760
-    val out = new PrintWriter(System.out)
-    val compilationSettings: Settings = new GenericRunnerSettings(out.println)
-    // just re-use the whole classpath
-    compilationSettings.classpath.value = JClassPath.adaptClassPathToOSEnv(JClassPath.fetchCurrentClassPath).distinct.mkString(File.pathSeparator)
-    println(s"Using classpath ${compilationSettings.classpath.value}")
-    compilationSettings.outdir.value = outDir.toAbsolute.toString
-    val reporter = new ConsoleReporter(compilationSettings)
-    val compiler = new Global(compilationSettings, reporter)
-    val runner = new compiler.Run
-    runner.compile(filesToCompile.map(_.toAbsolute.toString))
+    try {
+      val out = new PrintWriter(System.out)
+      val compilationSettings: Settings = new GenericRunnerSettings(out.println)
+      // just re-use the whole classpath
+      compilationSettings.classpath.value = JClassPath.adaptClassPathToOSEnv(JClassPath.fetchCurrentClassPath).distinct.mkString(File.pathSeparator)
+      println(s"Using classpath ${compilationSettings.classpath.value}")
+      compilationSettings.outdir.value = outDir.toAbsolute.toString
+      val reporter = new ConsoleReporter(compilationSettings)
+      val compiler = new Global(compilationSettings, reporter)
+      val runner = new compiler.Run
+      runner.compile(filesToCompile.map(_.toAbsolute.toString))
+    } catch {
+      case e: Exception => throw new CompilationException(e)
+    }
   }
 
 }
+
+/** Exception to indicate that the compilation process failed.
+  *
+  * @param cause the causing exception
+  */
+class CompilationException(cause: Exception) extends RuntimeException(cause)
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/env/JarPackager.scala b/src/main/scala/org/rosi_project/model_sync/generator/env/JarPackager.scala
index 86f7a64..3d1478f 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/env/JarPackager.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/env/JarPackager.scala
@@ -21,49 +21,59 @@ class JarPackager(inputDir: File, outputDir: File, jarName: String = "model.jar"
     */
   def run(): Unit = {
     // 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 + jarName), mf)
-    var filesToAdd: List[java.io.File] = inputDir.jfile.listFiles.toList
+    try {
+      val mf = new Manifest
+      mf.getMainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0")
+      val targetJar = new JarOutputStream(new FileOutputStream(outputDir + File.separator + jarName), mf)
+      var filesToAdd: List[java.io.File] = inputDir.jfile.listFiles.toList
 
-    while (filesToAdd.nonEmpty) {
-      val file = filesToAdd.head
-      var fname = file.getAbsolutePath.replace(inputDir.jfile.getAbsolutePath + File.separator, "").replaceAll("\\\\", "/")
-      println(s"Adding to JAR: $fname")
-      if (file.isDirectory) {
-        if (!fname.endsWith("/")) {
-          fname += "/"
-        }
-        filesToAdd ++= file.listFiles.toList
+      while (filesToAdd.nonEmpty) {
+        val file = filesToAdd.head
+        var fname = file.getAbsolutePath.replace(inputDir.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 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
+          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.write(buffer, 0, bytesRead)
           }
+          targetJar.closeEntry()
+          in.close()
         }
-        targetJar.closeEntry()
-        in.close()
+
+        filesToAdd = filesToAdd.tail
       }
 
-      filesToAdd = filesToAdd.tail
+      targetJar.close()
+    } catch {
+      case e: Exception => throw new JarPackaginException(e)
     }
-
-    targetJar.close()
   }
 
 }
+
+/** Exception to indicate that the packaging process failed.
+  *
+  * @param cause the causing exception
+  */
+class JarPackaginException(cause: Exception) extends RuntimeException(cause)
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 ec68ac7..5a42da3 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
@@ -1,20 +1,13 @@
 package org.rosi_project.model_sync.generator.io
 
-import java.io.{BufferedInputStream, FileInputStream, FileOutputStream, PrintWriter}
+import java.nio.file.{Files, StandardCopyOption}
 import java.{io => jio}
-import java.net.URLClassLoader
-import java.nio.file.{CopyOption, Files, StandardCopyOption}
-import java.util.jar.{Attributes, JarEntry, JarOutputStream, Manifest}
 
 import org.rosi_project.model_sync.generator.ModelConfig
 import org.rosi_project.model_sync.generator.acr_model._
 import org.rosi_project.model_sync.generator.env.{FilesCompiler, JarPackager}
 
-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
+import scala.reflect.io.{Directory, File, Path}
 
 /** The `FSWriter` writes a [[SModel]] as a single compiled ''JAR'' file to the File System.
   *
@@ -40,12 +33,19 @@ class SModelFSWriter(
   println(s"Output dir is $outputDir")
 
   println("... Copying model images")
-  private val images = collectAllModelImages(modelCfg, currentDir)
-  private val imagesTargetDir = new jio.File(workingDir.toAbsolute.toString() + File.separator + "res")
-  imagesTargetDir.mkdirs()
-  images.foreach(img => {
-    Files.copy(img.toPath, imagesTargetDir.toPath.resolve(img.getName), StandardCopyOption.REPLACE_EXISTING)
-  })
+  private var images: List[jio.File] = _
+  private var imagesTargetDir: jio.File = _
+  try {
+    images = collectAllModelImages(modelCfg, currentDir)
+    imagesTargetDir = new jio.File(workingDir.toAbsolute.toString() + File.separator + "res")
+    imagesTargetDir.mkdirs()
+    images.foreach(img => {
+      Files.copy(img.toPath, imagesTargetDir.toPath.resolve(img.getName), StandardCopyOption.REPLACE_EXISTING)
+    })
+  } catch {
+    case e: Exception => throw new ModelImagePreparationException(e)
+  }
+
 
   override def visit(sModel: SModel): Unit = {
     println(s"... Wrote files (sources) $sFilesToCompile")
@@ -69,15 +69,19 @@ class SModelFSWriter(
   }
 
   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
+    try {
+      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
+    } catch {
+      case e: Exception => throw new ClassWritingException(e)
+    }
   }
 
   override def visit(sAttr: SAttribute): Unit = {
@@ -125,3 +129,15 @@ class SModelFSWriter(
   }
 
 }
+
+/** Exception to indicate that the model images may not be copied into the JAR.
+  *
+  * @param cause the causing exception
+  */
+class ModelImagePreparationException(cause: Exception) extends RuntimeException(cause)
+
+/** Exception to indicate that a class may not be written.
+  *
+  * @param cause the causing exception
+  */
+class ClassWritingException(cause: Exception) extends RuntimeException(cause)
-- 
GitLab