Hey guys, nobody ever responded to my message...
Do you really think that my usage of these internal Nashorn APIs is so
unwarranted?
I tried to prove that some useful libraries need to use Nashorn APIs
that some of you insist should not be made public.
On 07/06/2015 09:10 PM, Tal Liron wrote:
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.