Reputation: 27276
I am trying to use Clojure as a scripting language from a host Java program. The idea being that the end user will be able to write Clojure scripting code that will call a domain-specific Java API. At runtime, the host Java program will evaluate the end-user's Clojure script (which will in turn call the domain APIs). So I started with a dead-simple prototype to explore the terrain.
package a.problem.domain;
public class Domain {
public Domain() { }
public String defaultMsg() {
return "default";
}
public String passBackMsg(String s) {
return s;
}
}
(end user's Clojure script hard-coded for simplicity)
String script = "(do "+
" (import '(a.problem.domain Domain)) "+
" (.defaultMsg (Domain.)) "+
") ";
System.out.println(RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script)));
(code snippet taken from here)
So far so good.
However I couldn't find a way to invoke the second method (the one that requires an argument). Instead I resorted to dynamically generating the Clojure script at runtime and replacing a placeholder with a literal that represents the result of calling the domain method passBackMsg. Obviously, this is unsatisfactory and doesn't go very far (what if I want to pass a java.sql.Connection to my Clojure script ?).
So, how do I invoke the passBackMsg method from the host Java program?
When I try the following:
String script = "(ns foo) "+
"(import '(a.problem.domain Domain)) "+
"(defn numberToString [s] ( "+
" (.passBackMsg (Domain.) s) "+
")) ";
RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script)); // line-A
System.out.println(RT.var("foo", "numberToString").invoke(33)); // line-B
I get:
java.lang.IllegalStateException: Can't change/establish root binding of: *ns* with set
... on line-A. When I try without the ns and with:
RT.var("user", "numberToString").invoke(33)
("user" being a wild guess as I don't see a var method without a namespace argument)
I get an:
java.lang.IllegalStateException: Attempting to call unbound fn: #'user/numberToString"
... on line-B.
Upvotes: 7
Views: 1120
Reputation: 33637
Try this:
String script = "(do "+
" (import '(a.problem.domain Domain)) "+
" (fn [s] " +
" (.passBackMsg (Domain.) s) "+
")) ";
IFn fn = (IFn)RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script));
fn.invoke("hello");
UPDATED: Below sample code works fine:
package hello_clj;
import clojure.lang.RT;
import clojure.lang.IFn;
public class Main {
public String passBackMsg(String s) {
return s;
}
public static void main(String[] args) {
String script = "(do (import 'hello_clj.Main) (fn [s] " +
"(.passBackMsg (Main.) s) ))";
IFn fn = (IFn)RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script));
System.out.print(fn.invoke("Hello"));
}
}
Upvotes: 4