3

I am trying to port my java code to pure scala, so would appreciate any help in this regard.

The code below works by first translating my business logic into java code. Here I'm using freemarker template to do template based code generation. After file creation I then use the java comiler to compile the code and create a jar file, which is persisted in a temporary directory

I am currently using the javax.tools.* package that provides runtime compilation. What is the Scala equiavalent to that approach? I'd like to generate pure Scala code using freemarker templates and then run Scala compilation to create a jar file.

Below is sample Java code I am using to make this happen.

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObjects);
    StringBuilder builder = new StringBuilder();
    builder.append(service.getConfig().getProp("coreLib"));
    builder.append(";" +result.getCodeContext().getOmClasspath());
    builder.append(";" +jarBuilder.toString());
    builder.append(";" +service.getConfig().getProp("tempCodeGen"));
    String[] compileOptions = new String[]{"-d", result.getCodeContext().getOmClasspath(),"-cp",builder.toString()} ;
    Iterable<String> compilationOptionss = Arrays.asList(compileOptions);
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
    CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, compilationOptionss, null, compilationUnits) ;
    boolean status = compilerTask.call();
fresskoma
  • 25,481
  • 10
  • 85
  • 128
GammaVega
  • 779
  • 1
  • 7
  • 23

4 Answers4

3

Here's some methods from my own code to compile a project and package it into a jar. Its very far from polished, or properly commented, but hopefully it will indicate where you need to start. I don't think you need to use String Builder as this is not performance critical:

def buildAll(name: String, projDir: String, mainClass: String = ""): Unit =
{
  import scala.tools.nsc.{Settings,Global}
  val relSrc : List[String] = List()
  val maniVersion = "None"
  def targDir: String = projDir + "/targ"
  def srcDir: String = projDir + "/src"
  def srcDirs: List[String] = srcDir :: relSrc

  import java.io._
  val sings = new scala.tools.nsc.Settings     
  new File(targDir).mkdir 
  sings.outputDirs.setSingleOutput(targDir.toString)     
  val comp = new Global(sings)      
  val crun: comp.Run  = new comp.Run
  def getList(fName: String): List[String] =
  {
     println("starting getList " + fName)
     val file = new File(fName)
     if (file.isDirectory) file.listFiles.toList.flatMap(i => getList(fName + "/" + i.getName))
     else List(fName)
  }  

  crun.compile(srcDirs.flatMap(i => getList(i))) 
  import sys.process._
  ("cp -r /sdat/projects/ScalaLibs/scala " + targDir + "/scala").!

  import java.util.jar._
  val manif = new Manifest
  val mf = manif.getMainAttributes
  mf.put(Attributes.Name.MANIFEST_VERSION, maniVersion)
  if (mainClass != "") mf.put(Attributes.Name.MAIN_CLASS, mainClass)
  val jarName = name + ".jar"
  val jarOut: JarOutputStream = new JarOutputStream(new FileOutputStream(projDir + "/" + jarName), manif)  
  AddAllToJar(targDir, jarOut)      
  jarOut.close   
}   

def addToJar(jarOut: JarOutputStream, file: File, reldir: String): Unit =
{
  val fName = reldir + file.getName         
  val fNameMod = if (file.isDirectory) fName + "/" else fName
  val entry = new JarEntry(fNameMod)
  entry.setTime(file.lastModified)         
  jarOut.putNextEntry(entry)
  if (file.isDirectory)
  {
     jarOut.closeEntry
     file.listFiles.foreach(i => addToJar(jarOut, i, fName + "/"))
  }
  else
  {         
     var buf = new Array[Byte](1024)
     val in = new FileInputStream(file)
     Stream.continually(in.read(buf)).takeWhile(_ != -1).foreach(jarOut.write(buf, 0, _))
     in.close
     jarOut.closeEntry()
  }         
}
def AddAllToJar(targDir: String, jarOut: JarOutputStream): Unit =
  new java.io.File(targDir).listFiles.foreach(i => addToJar(jarOut, i, ""))

You need to add the Scala Compiler to the build path. The Scala compiler takes a list of sourcefiles and produces the compiled class files in the directory set in the output directory. Getting to grips with the full capabilities of the compiler is a major task though.

Rich Oliver
  • 6,001
  • 4
  • 34
  • 57
1

And when using scala you don't need to use any free-marker similar tools. Since scala version 2.10 there is string interpolation feature.

val traitName = "MyTrait"
val packageName = "my.pack"
val typeParams = List("A", "B", "C")

s"""
  |package ${packageName}
  |
  |trait ${traitName}[${typeParams.mkString(",")}] {
  |  ${typeParams.map(t => s"val ${t.toLowerCase()}: ${t}")}
  |}
  |
""".stripMargin

will yield:

package my.pack

trait MyTrait[A,B,C] {
  List(val a: A, val b: B, val c: C)
}

No dependencies needed :)

pawel.panasewicz
  • 1,831
  • 16
  • 27
  • This is a good approach, however in my case the template content is stored in a file and string interpolation is a compile time feature, so technically it is not possible to achieve unless i am missing something. See below code val c2 =Source.fromFile("C:/templates/datadomain.ftl").getLines.mkString content of datadomain.ftl - package om.${data.id +"_" +data.version}; – GammaVega Oct 21 '13 at 15:35
  • 1
    Please note that with quasiquotes (http://docs.scala-lang.org/overviews/macros/quasiquotes.html) there's no longer a need in using error-prone string manipulation to assemble Scala code. – Eugene Burmako Oct 21 '13 at 22:34
1

If you would like to compile generated code in runtime, the simplest solution is twitter eval utility

Ilya Klyuchnikov
  • 3,149
  • 3
  • 18
  • 9
0

See the suggestion here - Generating a class from string and instantiating it in Scala 2.10

Or the Eval class from twitter's util library - https://github.com/twitter/util/blob/master/util-eval/src/main/scala/com/twitter/util/Eval.scala

Community
  • 1
  • 1
Rotem Hermon
  • 2,127
  • 15
  • 19
  • AFAIK, Eval does not allow you to *generate* code, e.g. physically build code and bundle it as jar, so it is not what op asks. Not quite sure about first one. – om-nom-nom Oct 21 '13 at 14:42
  • When provided with the standard "-d" option, toolboxes will dump code into a specified directory: https://github.com/scala/scala/blob/371bad2fe657d7fd065a47dc38fa5ae1fea882a7/src/compiler/scala/tools/reflect/ToolBoxFactory.scala#L322 – Eugene Burmako Oct 21 '13 at 22:31