Hi Atilla (and Sundar and everyone else, really),

You asked which Nashorn APIs I'm using that are not documented. I will reply in full detail.

For the BSON/JSON codecs, the most important thing is to access the NativeBoolean, NativeNumber, NativeArray, ConsString, Undefined, etc., classes. This allows the codecs to check for these classes incoming, and also to easily create instances of them using their constructor() static method.

ScriptObject has no constructor(), so I use Global.newEmptyInstance(). (By the way: NativeDate and NativeArray name the method construct() instead of constructor(). I assume this is a consistency mistake.)

But I also need to access their data. This means get()/set() for NativeScriptObject and NativeArray, getOwnKeys(), getArray() (which means I need access to the ArrayData class), and also getTime() for NativeData. NativeRegExp is a bit harder, but I use get("source"), get("multiline"), etc.

(In Rhino, some of these classes are actually private! This required an awkward workaround: I do a string equality check on the classname. That's neither efficient nor portable, though more "dynamic", I guess. Also, for these classes I can use Context.newObject() to create instances by JS name, for example "RegExp". Then I can do ScriptableObject.callMethod() to access their internals. So, there are workarounds to not being to able to access them.)

ScriptObjectMirror is awkward. Though it's stable and documented, the issue with unwrap() is that it needs a global. Documented, but of course unclear what to do! For me, this actually means calling Context.getGlobal(), which is not publicly documented. (Another issue is that, of course, unwrap won't work for other globals. This has created difficulties in some threaded environments in which multiple globals are used intentionally. More on that below.)

So much for BSON/JSON. The Scripturian implementation of Nashorn is much more complex. As you may know, Scripturian is a rethinking of and alternative to JSR-223, so it has to do much of the same work.

Scripturian works by purposely creating different global instances for each "execution context" (which *can* be a thread, but doesn't have to be: it's an abstraction over, well, execution contexts). I use Context.createGlobal(), and set it via Context.setGlobal().

We then need to compile scripts in the Context, so I use Source.sourceFor() and Context.compileScript(), which returns a ScriptFunction, so I also need access to that. Compilations errors are via Context.getErrorManager(), so I need access to ErrorManager. To run the scripts, I use ScriptRuntime.apply(). A small fix I need to add is that Nashorn's NativeArray does not support java.util.List, so if an array is returned from apply(), I call NativeJava.to() to get list access. Thats's just a bit friendlier for users of Scripturian, who are otherwise agnostic about implementation specifics.

There's an important issue here: remember, there might be many different globals, but of course I want them all to use the same code cache, which is stored in the Context. So, I use one singleton Context and switch globals via Context.setGlobal(). To create a Context, I also need access to Options. A limitation in Nashorn is that I can't change stdout and stderr after I create the Context (Scripturian allows different onces per ExecutionContext), so my workaround is to use a custom Writer wrapper class that underneath delegates to the correct "real" Writers (I use the same mechanism for a few other Scripturian language engines, too).

I grumbled here before that I have no programmatic access to the code cache. Behind the scenes, ScriptFunction might retrieve from the cache instead of being compiled. I can control the cache base location via the "nashorn.persistent.code.cache" system property, but it's unfortunate that I can't control the structure and naming the way I can with other languages supported by Scripturian. In particular, the problem is that it's a global property for the whole JVM, whereas compilation and caching location is ideally controlled per ExecutionContext in Scripturian. This makes Nashorn support in Scripturian a bit more limited.

Finally, for errors during execution, I use NashornException (documented!) to extract specific information into Scripturian's more generic ExecutionException.

Small extras: I use Version.version() and NashornScriptEngineFactory.getLanguageVersion() to get version data.

I think that's everything! Of course, I also had to "reverse engineer" much of this (=read a lot of code) and work around many quirks (and big differences to Rhino's implementation) before streamlining it down to just these few classes. (I tried to work around the caching limitations, but gave up due to its complexity. Also, I think some of the key classes I would need are private.) I did my best not to delve to much into internals, but I hope I made it clear to you that it would have been impossible to achieve all the above goals without it.

-Tal

On 07/06/2015 04:18 AM, Attila Szegedi wrote:
What APIs are you using, BTW? Just curious if I can suggest an alternative approach, or even consider if something you use should be publicly supported.

Reply via email to