Thank you for your reply, Sundar.

Unfortunately, your suggestions are insufficient.

In the BSON translator, I do not have access to the script engine at all. It might not even be created: after all, the BSON translator can be invoked by Nashorn directly, not in JSR-223 mode.

Likewise, you have not addressed much more of what I write about: access to Script, Source, Context, etc.

I understand that you do not want to have to support internal APIs. I merely wanted to prove to you that there is a cost in doing so: many powerful things can only be achieved by access to those APIs. This means that various 3rd party products might break with newer versions of Nashorn.

This is not the end of the world: many libraries, of course, break dependent products, even in regards to public APIs.

Still, I hope you can read my original email in depth and think more about what I'm trying to do. Perhaps you will agree that some of those APIs could benefit from being public, or suggest other solutions for me.

On my end, I will continue to maintain my code as is, even if it uses internal APIs, because it allows me to use Nashorn for some very cool projects.

-Tal

On 10/15/2015 09:56 PM, Sundararajan Athijegannathan wrote:
Hi Tal Liron,

Sorry about missing reply for this email. I somehow remember replying similar nashorn internal usage email. It is difficult maintain all of jdk.nashorn.internal.* between versions. Also, with security manager around, jdk.nashorn.internal.* are not accessible without explicit 'package access' RuntimePermission. In addition, with jdk9, nashorn module exports only jdk.nashorn.api.* packages. i.e., even without any security manager, user code won't be able to access nashorn internal implementation classes. It is possible to use jdk.nashorn.api.scripting.JSObject to get constructors and create objecs.

JSObject numberConstructor = (JSObject) engine.eval("Number");
JSObject numberObj = numberConstructor.newObject();

Is it not possible to work with these? Please check out latest 8u-dev code. Mostly explicit wrap/unwrap won't be required from user's code.

Thanks,
-Sundar

On 10/15/2015 10:21 PM, Tal Liron wrote:
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.




Reply via email to