Scala Code dynamisch von Java aus aufrufen.


Hier mal ein kleines Beispiel wie man von Java aus Scala “scripts” dynamisch kompiliert und mit Argumenten ausführt. (Testumgebung ist Ubuntu Linux 8.10 / Eclipse IDE mit Scala Plugin). Das “Script” wird hierbei von dem in dem Scala Interpreter integrierten Compiler kompiliert und nicht etwa über eine IDE Erweiterung wie das Scala Eclipse Plugin.

Das wird u.a. dadurch forciert, dass die scala Files in einem separaten Ordner src-scripts liegen, der sich nicht im Build-Path befindet. Dadurch vermeiden wir, dass das .scala File vom
Scala Builder kompiliert wird.

scala-ide-structure

Um das Beispiel ausführen zu können braucht man die aktuelle Scala Version 2.7.3

Sinn der Übung ist zu zeigen, wie man scala als Scriptsprache in eine Java Anwendung einbetten kann. Die nächste Aufgabe ist nun, den über diesen Weg aufgerufenen Scala Code auch debuggen zu können. Stay tuned :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package de.tutorials;
package de.tutorials;
 
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
 
import scala.List;
import scala.tools.nsc.Interpreter;
import scala.tools.nsc.Settings;
import scala.tools.nsc.util.SourceFile;
 
public class Main {
	public static void main(String[] args) throws Exception {
		executeScript(new File("src-script/scripts/Script.scala"),new HashMap<String, String>(){{put("a","1"); put("b","2");put("c","3");}},"scripts.Script.run(args)");
	}
 
	private static void executeScript(File scriptFile,Object args,String command) throws Exception {
		Settings settings = new Settings();
		File classFileCacheDirectory = new File("/tmp/scala/");
		if (!classFileCacheDirectory.exists()) {
			classFileCacheDirectory.mkdir();
		}
 
		Interpreter interpreter = new ExtendedInterpreter(settings,	classFileCacheDirectory);
		addToClassPath((URLClassLoader) Thread.currentThread().getContextClassLoader(), classFileCacheDirectory);
 
		Object[] context = { scriptFile, null };
		interpreter.bind("context", "Array[Object]", context);
		interpreter.interpret("context(1)=List(new scala.tools.nsc.util.BatchSourceFile(new scala.tools.nsc.io.PlainFile(context(0).asInstanceOf[java.io.File])))");
		interpreter.compileSources((List<SourceFile>) context[1]);
		interpreter.setContextClassLoader();
 
		interpreter.bind("args", "Object", args);
 
		interpreter.interpret(command);
	}
 
	private static Method addURLMethod;
	static {
		try {
			addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL",	URL.class);
			addURLMethod.setAccessible(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
	private static void addToClassPath(URLClassLoader classLoader, File classFileCacheDirectory) {
		try {
			addURLMethod.invoke(classLoader, classFileCacheDirectory.toURI().toURL());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Unser erweiterter Scala Interpreter, der die on-the-fly generierten .class Files auf dem Filesystem ablegt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package de.tutorials
import scala.tools.nsc._
import scala.tools.nsc.reporters._
import io.PlainFile
import java.io.File
 
class ExtendedInterpreter(override val settings: Settings, val classFileCacheDirectory: File) extends Interpreter(settings) {
 
  override protected def newCompiler(settings: Settings, reporter: Reporter) = {
    val comp = new scala.tools.nsc.Global(settings, reporter)
    comp.genJVM.outputDir = new PlainFile(classFileCacheDirectory);
    comp
  }
}

Hier unser Beispiel-Script

1
2
3
4
5
6
7
8
9
package scripts
 
object Script{
    def run(args:Object){
    	println("Hello World!")
    	println("got args: " + args)
    	println("Bye Bye")
    }
}

Ausgabe:

context: Array[java.lang.Object] = Array(src-script/scripts/Script.scala, null)
args: java.lang.Object = {b=2, c=3, a=1}
Hello World!
got args: {b=2, c=3, a=1}
Bye Bye

Dabei werden von Scala eine ganze Menge von "zwischen-Klassen" erzeugt:
tom@ubuntu:/tmp/scala$ tree
.
|-- RequestResult$line0$object$.class
|-- RequestResult$line0$object.class
|-- RequestResult$line1$object$.class
|-- RequestResult$line1$object.class
|-- RequestResult$line2$object$.class
|-- RequestResult$line2$object.class
|-- RequestResult$line3$object$.class
|-- RequestResult$line3$object.class
|-- binder0$.class
|-- binder0.class
|-- binder1$.class
|-- binder1.class
|-- line0$object$$iw$$iw$.class
|-- line0$object$$iw$.class
|-- line0$object$.class
|-- line0$object.class
|-- line1$object$$iw$$iw$.class
|-- line1$object$$iw$.class
|-- line1$object$.class
|-- line1$object.class
|-- line2$object$$iw$$iw$.class
|-- line2$object$$iw$.class
|-- line2$object$.class
|-- line2$object.class
|-- line3$object$$iw$$iw$.class
|-- line3$object$$iw$.class
|-- line3$object$.class
|-- line3$object.class
`-- scripts
    |-- Script$.class
    `-- Script.class

1 directory, 30 files

Beispielcode: detutorialsscalainterpreter

  1. #1 von Germar am 4. August 2010 - 11:35

    Hallo,

    super Artikel! Hat mir sehr geholfen.
    Das Feld “comp.genJVM.outputDir” scheint es allerdings in Version 2.8 nicht mehr zu geben.
    Weisst Du wie ich outputDir jetzt setzen kann?

    Gruß,

    Germar

(wird nicht veröffentlicht)

Comment Spam Protection by WP-SpamFree