Skip to content

Instantly share code, notes, and snippets.

@metasim
Last active November 6, 2017 10:02
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save metasim/7492509 to your computer and use it in GitHub Desktop.
Save metasim/7492509 to your computer and use it in GitHub Desktop.
Demo code for defining a scala class dynamically (as string) and load it into Java.
package eri;
import scala.collection.Iterator;
import scala.collection.JavaConversions;
import scala.collection.Seq;
import scala.reflect.internal.util.BatchSourceFile;
import scala.reflect.io.AbstractFile;
import scala.runtime.AbstractFunction1;
import scala.runtime.BoxedUnit;
import scala.tools.nsc.GenericRunnerSettings;
import scala.tools.nsc.interpreter.AbstractFileClassLoader;
import scala.tools.nsc.interpreter.IMain;
import scala.tools.nsc.settings.MutableSettings;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
public class ScalaInterpreterExample {
public interface MustConform {
int doIt();
}
private static class ErrorHandler extends AbstractFunction1<String, BoxedUnit> {
@Override
public BoxedUnit apply(String message) {
System.err.println("Interpreter error: " + message);
return BoxedUnit.UNIT;
}
}
private static File createSource() throws IOException {
File source = File.createTempFile("code-", ".scala");
try (PrintWriter out = new PrintWriter(source)) {
out.println("import eri.ScalaInterpreterExample.MustConform");
out.println("case class FooBar(name: String, value: Int) extends MustConform {");
out.println(" def doIt: Int = 34");
out.println("}");
}
return source;
}
public static void main(String[] args) {
try {
// Setup the compiler/interpreter
GenericRunnerSettings settings = new GenericRunnerSettings(new ErrorHandler());
// In scala this is settings.usejavacp.value = true;
// It it through this setting that the compiled code is able to reference the
// `MustConform` interface. The runtime classpath leaks into the compiler classpath, but
// we're OK with that in this use case.
((MutableSettings.BooleanSetting) settings.usejavacp()).v_$eq(true);
IMain interpreter = new IMain(settings);
// Create and prepare source to compile.
File source = createSource();
// Java->Scala conversion nastiness.
Iterable sources = Collections.singletonList(new BatchSourceFile(AbstractFile.getFile(source.toString())));
Seq seq = JavaConversions.asScalaIterable(sources).toSeq();
// Compile source file(s).
interpreter.compileSources(seq);
// Find out what was compiled.
Collection<Class<? extends MustConform>> classes = compiledConformers(interpreter);
for(Class<? extends MustConform> c : classes) {
// Interface doesn't enforce a constructor signature. This is just to have it more
// interesting than the default ctor.
Constructor<? extends MustConform> ctor = c.getConstructor(String.class, int.class);
MustConform instance = ctor.newInstance("Wow", 44);
System.out.println(instance);
System.out.println("Did it: " + instance.doIt());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// This is a bit hackish, as we're going through the AbstractFileClassLoader mapping from AbstractFile
// (a virtualized file) to the compiled class byte code (which isn't directly accessible, as far as I can tell).
private static Collection<Class<? extends MustConform>> compiledConformers(IMain interpreter) {
Collection<Class<? extends MustConform>> retval = new LinkedList<>();
AbstractFileClassLoader interpreterClassLoader = interpreter.getInterpreterClassLoader();
AbstractFile root = interpreterClassLoader.root();
Iterator<AbstractFile> it = root.iterator();
while(it.hasNext()) {
AbstractFile classFile = it.next();
String name = classFile.name().replace(".class", "");
Class<?> clazz = interpreterClassLoader.findClass(name);
if(MustConform.class.isAssignableFrom(clazz)) {
System.out.println("Found conforming class: " + name);
retval.add((Class<? extends MustConform>) clazz);
}
}
return retval;
}
}
@metasim
Copy link
Author

metasim commented Nov 15, 2013

build.scala looks like this:

import sbt._
import sbt.Keys._

object Build extends Build {
    lazy val info = Seq(
        name := "interpreter-example",
        organization := "eri",
        version := "0.1-SNAPSHOT",
        scalaVersion := "2.10.3"
    )

    lazy val repos = Seq(
    )

    lazy val libs = Seq(
      "org.scala-lang" % "scala-compiler" % "2.10.3"
    )

    lazy val projectDef = Project(id = "interpreter-example", base = file(".")).
        settings(info: _*).
        settings(resolvers ++= repos,
        libraryDependencies ++= libs,
        autoAPIMappings := true,
        javacOptions in(Compile, test) ++= Seq("-source", "1.6", "-target", "1.6"),
        scalacOptions in(Compile, doc) ++= Seq("-groups", "-implicits", "-diagrams"))

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment