I think the most robust and straightforward way to do this is to rely on the fact that both Rhino and Nashorn create per-thread execution contexts to do their work.
Nashorn's can be a bit more complicated to access because of the module (JPMS) changes introduced in Java 9, which makes it more difficult for scripts to access some of the classes involved.
Here's some code I use for this, in the SLIME codebase (which similarly supports both engines, and JDK 8/11 as of this writing). In each case, the engine provides a running
method that returns the engine-specific context if we are running inside that engine (so for simple detection, it could be wrapped inside a call to Boolean
or whatever). It is tested but as it's copy-paste out of context, it contains a bit of extraneous information, apologies for that:
Rhino (in this implementation you must call isPresent()
and ensure it returns true
before calling running()
, although you could improve that):
var rhino = (
function(global) {
return {
isPresent: function() {
return typeof(global.Packages.org.mozilla.javascript.Context.getCurrentContext) == "function";
},
running: function() {
return global.Packages.org.mozilla.javascript.Context.getCurrentContext();
}
}
}
)(this);
Nashorn (relies on Packages
, provided by the Mozilla compatibility layer, mostly to improve static type-checking, but could easily be rewritten to use the Java
equivalents for Nashorn):
var nashorn = (
function() {
// TODO A little bit of this logic is duplicated in loader/jrunscript/nashorn.js; we could make this method
// available there somehow, perhaps, although it might be tricky getting things organized between
// bootstrapping Nashorn in the loader and loading the launcher bootstrap script
var Context = Packages.jdk.nashorn.internal.runtime.Context;
var $getContext;
if (typeof(Context) == "function") {
try {
// TODO is there any way to avoid repeating the class name?
$getContext = Packages.java.lang.Class.forName("jdk.nashorn.internal.runtime.Context").getMethod("getContext");
} catch (e) {
// do nothing; $getContext will remain undefined
}
}
var isPresent = function() {
if (!new Packages.javax.script.ScriptEngineManager().getEngineByName("nashorn")) {
$api.debug("Nashorn not detected via javax.script; removing.");
return false;
}
if (typeof(Context.getContext) != "function" && !$getContext) {
$api.debug("jdk.nashorn.internal.runtime.Context.getContext not accessible; removing Nashorn.")
return false;
}
return true;
}
return {
isPresent: isPresent,
running: function() {
if ($getContext) {
try {
return $getContext.invoke(null);
} catch (e) {
return null;
}
} else {
return null;
}
}
}
}
)();
Sure, the Nashorn implementation uses non-public APIs. But I still think this is better, and more straightforward, than relying on what are essentially side effects (existence of various cherry-picked APIs that one or the other engine might provide). These techniques are potentially breakable if either engine seeks to improve compatibility with the other.