How to eval code that uses InterfaceStability annotation (that fails with "illegal cyclic reference involving class InterfaceStability")?
Asked Answered
N

1

3

I want to dynamically generate some kafka stream code when the program is running, but I get an exception when I compile the following code for kafka stream with scala toolbox eval:

val toolbox = runtimeMirror(getClass.getClassLoader).mkToolBox()
val code =
  """
    |import org.apache.kafka.streams.scala.kstream.KStream
    |import org.apache.kafka.streams.scala.StreamsBuilder
    |
    |class ClassA {
    |  def createStream(): KStream[String, String] = {
    |    import org.apache.kafka.streams.scala.ImplicitConversions._
    |    import org.apache.kafka.streams.scala.Serdes._
    |    new StreamsBuilder().stream("TestTopic")
    |  }
    |}
    |scala.reflect.classTag[ClassA].runtimeClass
  """.stripMargin
toolbox.eval(toolbox.parse(code))

errors:

reflective compilation has failed:

illegal cyclic reference involving class InterfaceStability
scala.tools.reflect.ToolBoxError: reflective compilation has failed:

illegal cyclic reference involving class InterfaceStability
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:331)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.wrapInPackageAndCompile(ToolBoxFactory.scala:213)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:267)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.$anonfun$compile$13(ToolBoxFactory.scala:444)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.apply(ToolBoxFactory.scala:370)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:437)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:459)

It seems that the kafka InterfaceStability class annotated itself:

package org.apache.kafka.common.annotation;

@InterfaceStability.Evolving
public class InterfaceStability {
...
}

Scala version:"2.12.8"

kafkaVersion: "2.1.0"

Sbt version : "1.2.7"

Nobody answered 30/12, 2018 at 8:40 Comment(1)
#16539291Benham
N
3

After switching to the api below the scala.tools.nsc package, I can compile successfully.

refer to https://eknet.org/main/dev/runtimecompilescala.html [broken link]

import scala.reflect.internal.util.{AbstractFileClassLoader, BatchSourceFile}
import scala.reflect.io.{AbstractFile, VirtualDirectory}
import scala.reflect.runtime
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.nsc.{Global, Settings}
import scala.collection.mutable
import java.security.MessageDigest
import java.math.BigInteger

class Compiler(targetDir: Option[File]) {

  val target = targetDir match {
    case Some(dir) => AbstractFile.getDirectory(dir)
    case None => new VirtualDirectory("(memory)", None)
  }

  val classCache = mutable.Map[String, Class[_]]()

  private val settings = new Settings()
  settings.deprecation.value = true // enable detailed deprecation warnings
  settings.unchecked.value = true // enable detailed unchecked warnings
  settings.outputDirs.setSingleOutput(target)
  settings.usejavacp.value = true

  private val global = new Global(settings)
  private lazy val run = new global.Run

  val classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader)

  /**Compiles the code as a class into the class loader of this compiler.
   *
   * @param code
   * @return
   */
  def compile(code: String) = {
    val className = classNameForCode(code)
    findClass(className).getOrElse {
      val sourceFiles = List(new BatchSourceFile("(inline)", wrapCodeInClass(className, code)))
      run.compileSources(sourceFiles)
      findClass(className).get
    }
  }

  /** Compiles the source string into the class loader and
   * evaluates it.
   *
   * @param code
   * @tparam T
   * @return
   */
  def eval[T](code: String): T = {
    val cls = compile(code)
    cls.getConstructor().newInstance().asInstanceOf[() => Any].apply().asInstanceOf[T]
  }

  def findClass(className: String): Option[Class[_]] = {
    synchronized {
      classCache.get(className).orElse {
        try {
          val cls = classLoader.loadClass(className)
          classCache(className) = cls
          Some(cls)
        } catch {
          case e: ClassNotFoundException => None
        }
      }
    }
  }

  protected def classNameForCode(code: String): String = {
    val digest = MessageDigest.getInstance("SHA-1").digest(code.getBytes)
    "sha"+new BigInteger(1, digest).toString(16)
  }

  /*
  * Wrap source code in a new class with an apply method.
  */
  private def wrapCodeInClass(className: String, code: String) = {
    "class " + className + " extends (() => Any) {\n" +
      "  def apply() = {\n" +
      code + "\n" +
      "  }\n" +
      "}\n"
  }
}
Nobody answered 2/1, 2019 at 7:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.