Developer API docs
Tal Liron
tal.liron at threecrickets.com
Tue Jul 7 02:10:46 UTC 2015
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.
More information about the nashorn-dev
mailing list