I’ve been experimenting with the (much improved) Java interop in BoxLang and wanted to see how much I could simplify my cfmljure
project. Here’s what I ended up with:
My Application.bx
file:
import java.lang.Class;
import java.lang.reflect.Array;
import java.lang.Thread;
class {
this.javaSettings = {
loadPaths: systemExecute("clojure","-Spath").output.split(":")
};
function onApplicationStart() {
var urls = this.javaSettings.loadPaths.map(p =>
new java:java.io.File(p).toURI().toURL()
);
// replace the classloader with all the necessary URLs added:
var myThread = Thread::currentThread();
var classloader = new java:java.net.URLClassLoader(
urls.toArray( Array::newInstance( Class::forName("java.net.URL"), urls.size() ) ),
myThread.getContextClassLoader()
);
myThread.setContextClassLoader( classloader );
var stringType = Class::forName("java.lang.String");
var objectType = Class::forName("java.lang.Object");
// install clojure.java.api.Clojure:
var clj = classloader.loadClass("clojure.java.api.Clojure");
// Object var(Object ns, Object name):
var _var = clj.getMethod("var", [objectType, objectType]);
// Object read(String s):
var _read = clj.getMethod("read", [stringType]);
// BoxLang wrapper:
application.clj = {
var: (ns, name) => _var.invoke(null, [ns, name]),
read: (s) => _read.invoke(null, [s])
};
// expose for debugging only:
application.paths = this.javaSettings.loadPaths
application.urls = urls;
}
}
And the index.bxs
file:
// verify classpath from Clojure:
for (p in application.urls) echo(p & char(10))
for (x in application.clj.read("[1 2 3]")) echo(x & char(10))
plus = application.clj.var("clojure.core", "+")
echo( "(+ 1 2) = " & plus.invoke(1, 2) & char(10) )
Running this (with Beta 20):
sean@sean-win11-desk:~/boxlang$ bin/boxlang index.bxs
file:/home/sean/boxlang/src
file:/home/sean/.m2/repository/org/clojure/clojure/1.12.0/clojure-1.12.0.jar
file:/home/sean/.m2/repository/org/clojure/core.specs.alpha/0.4.74/core.specs.alpha-0.4.74.jar
file:/home/sean/.m2/repository/org/clojure/spec.alpha/0.5.238/spec.alpha-0.5.238.jar
1
2
3
(+ 1 2) = 3
I had hoped that just setting the classpath from the result of clojure -Spath
would be sufficient to provide the correct classpath for Clojure execution, but I wasn’t able to get it to work (I think this is due to some static initialization that Clojure does).
If it had worked, index.bxs
would look more like this:
import clojure.java.api.Clojure;
for (x in Clojure::read("[1 2 3]")) echo (x & char(10))
plus = Clojure::var("clojure.core", "+")
echo( "(+ 1 2) = " & plus.invoke(1, 2) & char(10) )
Note: this doesn’t fully replicate
cfmljure
because it doesn’t have theonMissingMethod()
adapter for auto-requiring/installing Clojure namespaces on first invocation but for basic interop, it’s already pretty good – and better than the original CFML code.