Java-6-Scripting mit JRuby

Seite 2: Aufruf aus Java

Inhaltsverzeichnis

Das einfache Ruby-Script (hello1.rb)

puts "Hallo JRuby " + $world
return 42

ruft der Entwickler aus dem folgenden Java-Programm heraus auf.

public void callJRuby() {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine rubyEngine = m.getEngineByName("jruby");
if (rubyEngine==null)
throw new RuntimeException("Did not find my ruby engine");

ScriptContext context = rubyEngine.getContext();
context.setAttribute("world","Programmierer",ScriptContext.ENGINE_SCOPE);
try{
File f = new File("hello1.rb");
BufferedReader br = new BufferedReader(new FileReader(f));

rubyEngine.eval(br, context); // (1)
} catch (ScriptException e) {
e.printStackTrace();
} catch (FileNotFoundException fnfe) {
System.err.println(fnfe.getMessage());
}
}

Um das Programm ausführen zu können, muss jruby-complete-1.4.0.jar im Klassenpfad vorhanden sein und die Datei hello1.rb im aktuellen Verzeichnis liegen. Das JRuby-Script erwartet eine Variable $world als Eingabe und gibt einen Wert von "42" zurück. Im Java-Teil lässt sich zunächst eine Scripting Engine für JRuby holen. Wenn das funktioniert hat, ist als Nächstes die Ruby-Variable world mit dem Wert "Programmierer" zu setzen. Das geschieht, indem man sie in einem Script-Kontext mitgibt, dessen Gültigkeit sich auf die gesamte Engine erstreckt. Schließlich liest man das Script aus der Datei hello1.rb und ruft es auf. Auf dem Standardausgabekanal erscheint wie erwartet "Hallo JRuby Programmierer". Das funktioniert prächtig, ist aber für einfache Evaluierungen noch zu aufwendig; hierfür lässt sich das Script direkt übergeben, etwa:

      rubyEngine.eval("puts 'Hello World'");

Bisher hat man allerdings noch nicht den RĂĽckgabewert des Scripts ausgewertet. Das geschieht ĂĽber eine kleine Erweiterung an der im obigen Java-Code mit (1) bezeichneten Stelle:

            Object ret = rubyEngine.eval(br, context);
if (ret instanceof Number)
System.out.println("Die Zahl ist " + (Number)ret);
else
System.out.println("Der Text ist " + ret.toString());

Die eval()-Methode liefert ein Objekt zurück, dass dann zu "casten" ist. Da die Engine für Zahlen immer den kleinstmöglichen Typ zurückgibt, empfiehlt sich der Test und Cast auf "Number" – vorausgesetzt, dass der passende Typ nicht bereits bekannt ist.

Das gezeigte Beispiel geht davon aus, dass man das gesamte Script aufrufen möchte. In der Realität stößt man oft auf Script-Bibliotheken, bei denen man eventuell nur eine einzelne Methode aufrufen
will. Das geht ĂĽber die Invocable-Schnittstelle des javax.script-Pakets. Hier bringt man die Funktion greet zum Laufen.

  def greet(name = "World")
puts "Hello #{name}";
end

Der zugehörige Java-Code sieht wie folgt aus (Ausschnitt):

  ScriptEngine rubyEngine = m.getEngineByName("jruby");
try {
File f = new File("script.rb");
BufferedReader br = new BufferedReader(new FileReader(f));
rubyEngine.eval(br);

Invocable inv = (Invocable)rubyEngine;
inv.invokeFunction("greet"); // Aufruf ohne Parameter
inv.invokeFunction("greet","Heiko"); // Aufruf mit Parameter
}

Um die Schnittstelle nutzen zu können, ist zuerst das ganze Script mit eval() auszuwerten. Danach lässt sich die Methode invokeFunction() verwenden, um einzelne Ruby-Funktionen aufzurufen. Wie oben gezeigt ist es möglich, eine Rückgabe aus Ruby heraus zu erhalten und auszuwerten. Nach dem anfänglichen eval() kann das Script invokeFunktion() (und auch invokeMethod()) mehrfach ausführen. In der Praxis ist an der Stelle aufzupassen, wenn man mit Threads arbeitet, da eval() und invokeFunction() im gleichen Thread aufzurufen sind. Ist das nicht der Fall, bekommt man seltsame Fehler, etwa dass die aufgerufene Funktion manchmal nicht verfügbar ist.

Möchte man ein komplettes Script mehrfach nutzen, bietet es sich an, das zu kompilieren. Hierzu muss die Script Engine das Interface Compilable implementieren – das Kompilieren erfolgt über die compile()-Methode der Schnittstelle. Wie die Invocable-Schnittstelle ist die Funktion optional. Weitere Beispiele für die Verwendung der API des JSR 223 mit JRuby findet man unter hier.