0

I want to pass a scala file containing a case class so my application compiles this case class during run time and start using it.

The main reason why I am doing this is because I want to avoid rebuilding my code every time the case class changes. So would be better to pass it as a parameter (in case you are wondered, the operations with this case class are generic so it is not required any rework in the transformations)

I was using these post1, post2 and post3 as references. So far my application looks like this:

import scala.io.Source
import scala.reflect.runtime.universe
import scala.tools.reflect._

object TestCompile {
  def main(args: Array[String]): Unit = {

    val path = "C:\\myWorkspace\\entity\\TestClass.scala"
    val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
    val src = Source.fromFile(path).mkString.stripMargin
    val clazz = tb.compile(tb.parse(src))().asInstanceOf[Class[_]]

  }
}

The file TestClass.scala is like this:

case class TestClass(
         val value : String,
         val timeStamp : Long,
         val rowKey : String,
         val columnFamily : String
)

But I am getting an exception in

val clazz = tb.compile(tb.parse(src))().asInstanceOf[Class[_]]

Exception:

Exception in thread "main" scala.tools.reflect.ToolBoxError: reflective compilation has failed: cannot initialize the compiler due to java.lang.VerifyError: scala/tools/reflect/ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$api$.liftedTree1$1(ToolBoxFactory.scala:344) at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$api$.compiler$lzycompute(ToolBoxFactory.scala:330) at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$api$.compiler(ToolBoxFactory.scala:329) at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.liftedTree2$1(ToolBoxFactory.scala:356) at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.apply(ToolBoxFactory.scala:354) at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.parse(ToolBoxFactory.scala:413) at TestCompile$.main(App.scala:17) at TestCompile.main(App.scala) Caused by: java.lang.VerifyError: scala/tools/reflect/ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$api$.liftedTree1$1(ToolBoxFactory.scala:334)

Below is the dependency I am using, however I tried with other versions always getting the same error:

<dependency>
  <groupId>org.scala-lang</groupId>
  <artifactId>scala-reflect</artifactId>
  <version>2.11.6</version>
</dependency>

What am I doing wrong?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Ignacio Alorre
  • 7,307
  • 8
  • 57
  • 94

3 Answers3

1

I can't reproduce VerifyError.

I have

java.lang.ClassCastException: scala.runtime.BoxedUnit cannot be cast to java.lang.Class

in the line val clazz = tb.compile(tb.parse(src))().asInstanceOf[Class[_]].

tb.compile(tb.parse(src)) has type () => Any, so tb.compile(tb.parse(src))() has type Any.

https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/reflect/ToolBox.scala#L129

Remove .asInstanceOf[Class[_]].

Also see Causes of getting a java.lang.VerifyError

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
1

I got some time to test this today. The main problem I think is that the last expected line scala.reflect.classTag[TestClass].runtimeClass is missing in TestClass.scala.

One other important thing to remember is that scala-compiler.jar should also be in your class path.

Once I resolved that, I ran into another problem with Java/Scala object conversion. You have two options, make the timeStamp field, either a String or Java.lang.Long. Since the String conversion is trivial, I've given below example for Java.lang.Long.

With the above changes, my TestClass.scala looks like below:

case class TestClass(
                      value : String,
                      timeStamp : java.lang.Long,
                      rowKey : String,
                      columnFamily : String
                    ) {}

scala.reflect.classTag[TestClass].runtimeClass

I copied it to /tmp/ directory for testing.

Testing

My TestCompile.scala looks like below:

import scala.io.Source
import scala.reflect.runtime.universe
import scala.tools.reflect._

object TestCompile {
  def main(args: Array[String]): Unit = {

    val path = "/tmp/TestClass.scala"
    val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
    val src = Source.fromFile(path).mkString.stripMargin
    println(src)
    val clazz = tb.compile(tb.parse(src))().asInstanceOf[Class[_]]

    val ctor = clazz.getDeclaredConstructors()(0)
    val instance = ctor.newInstance("My value", new java.lang.Long(1234567890L), "Row1", "Column1")
    println(instance.toString)

  }
}

Output

TestClass(My value,1234567890,Row1,Column1)
vsnyc
  • 2,117
  • 22
  • 35
0

Because you lost dependency Add those dependency to your pom.xml

<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-library</artifactId>
    <version>2.11.8</version>
</dependency>
<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-reflect</artifactId>
    <version>2.11.8</version>
</dependency>
<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-compiler</artifactId>
    <version>2.11.8</version>
</dependency>

enter image description here

Josef
  • 2,869
  • 2
  • 22
  • 23
Barca
  • 1
  • 1