Hello Sundar,
Your suggestion works too. Would it be the recommended (more
future-proof) approach, as opposed to setting the context classloader
when instantiating the ScriptEngine instance? I'm guessing that it is.
Is there any way to make it even neater, by moving the ".static" logic
from the JavaScript side into the "JavaClass" function, so that the
JavaScript code can just invoke the JavaClass("className") function
without needing to add ".static" after each usage (to avoid typing,
and to avoid side effects when forgotten)?
Thanks,
Christopher
On 16 February 2015 at 04:38, A. Sundararajan
<[email protected]
<mailto:[email protected]>> wrote:
Actually, you don't need to modify the thread context class
loader. You can just expose Java Class objects via a script
"function" exposed to scripts. You can implement "script function"
as a Java lambda. Then, you can use "static" property to make Java
type object from it (as Attila mentioned in his email).
Example:
import javax.script.*;
import java.util.function.*;
class Main {
public static void main(String[] ar) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
e.put("JavaClass", (Function<String, Class>)
s -> {
try {
// replace this whatever Class finding logic here
// say, using your own class loader(s) based search
return Class.forName(s);
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
});
// Using "static" property on Class object to make a
// JS Java type object from a Java Class object
e.eval("var System = JavaClass('java.lang.System').static");
// call static method on that Java type
e.eval("System.out.println('hello world')");
}
}
This avoids any class loader setting or even changing existing API.
-Sundar
On Sunday 15 February 2015 08:16 PM, Christopher Brown wrote:
Hello,
Setting the context classloader when instantiating the script
engine, as
you suggested, solved my problem (note: the
ScriptEngineManager had to be
instantiated before changing the context classloader, as
otherwise the
"nashorn" engine couldn't be found). Here's the code:
ScriptEngineManager scriptEngineManager = new
ScriptEngineManager();
thread.setContextClassLoader(MyApiInterface.class.getClassLoader());
ScriptEngine engine =
scriptEngineManager.getEngineByName("nashorn");
thread.setContextClassLoader(contextClassLoader);
Will this technique supported in the future (all foreseeable
versions), or
is it an implementation side-effect that is subject to change?
Following on from your reply, making Java.type(...) accept an
actual Class
instance would be great. Also, a variant, such as:
Java.type(className, classLoaderOrArrayOfClassLoaders)
...would be great.
Thank you for your very helpful answer!
--
Christopher
On 11 February 2015 at 09:56, Attila Szegedi
<[email protected] <mailto:[email protected]>>
wrote:
Java.type will only resolve names against script engine's
"app loader"
(which is whatever was the thread context loader at the
time engine
instance was created) and additionally from the path
passed in as
"-classpath" command line option (to get an engine with
extra command line
options programmatically you'll need to use
NashornScriptEngineFactory
class directly instead of only relying on javax.script.*
APIs; actually you
can pass explicit "app loader" with some of
NashornScriptEngineFactory
factory methods too).
In absence of this, you can do the following: if you pass
in a Class
object representing your interface (let's call it
ForeignInterface) into
the runtime somehow (e.g. through Bindings):
bindings.put("foreignInterfaceClass",
ForeignInterface.class)
then you can use "foreignInterfaceClass.static" to
represent the static
aspect of the type[1] in the script. Thereafter,
var ForeignInterface = foreignInterfaceClass.static;
var adapter = Java.extend(ForeignInterface ...
should work, with the caveat that Nashorn's own classes
must be visible
through the class loader for ForeignInterface, as Nashorn
will essentially
be loading an adapter class that will have an "implements
ForeignInterface"
clause but it will also rely on some Nashorn internals in
its bytecode, so
it must be able to resolve symbolic bytecode references
both to
ForeignInterface and to some Nashorn classes. If your
class loader has the
extension class loader (which is where Nashorn lives)
somewhere in its
parent chain, you're good. Otherwise, if you have some
more involved class
loader topology, then I'm sorry to say that you're out of
luck[2] :-(
As for your additional questions:
#1 you can decide to not implement all methods of the
interface (in fact,
you could choose to implement none of them :-) ). If the
underlying script
object provides no implementation for an invoked method,
then interface
default method is invoked if one is provided by the
interface, otherwise
java.lang.UnsupportedOperationException is thrown.
#2 yes, javax.script has no explicit cleanup mechanism for
script engines.
It gets GC'd if it's unreachable. If you need resource
cleanup tied to it
not being reachable anymore, you'll need to use
PhantomReference.
HTH,
Attila.
---
[1] the one you would in Java simply call
"ForeignInterface", except in
Java it's not a reified object, but purely a lexical
language construct. In
Nashorn, static types are reified, and you can think of
".static" as being
the inverse operation of ".class"; .class on a static type
gives you a
runtime type (java.lang.Class) object, while .static on a
j.l.Class object
gives you back the static type which is used as both the
constructor
function for the type and a namespace for static
fields/properties/methods
(just as in Java). This whole discussion about static
types makes me want
to extend the Java.extend so that it accepts either a
static type or a
runtime j.l.Class object in its arguments, though.
[2] The truth is actually somewhat more complex; when
Nashorn defines the
adapter class, it does so in a small new class loader, and
it needs to find
a suitable parent for this class loader: one that sees the
class being
extended (if there's one), all the interface(s) being
implemented, and the
Nashorn classes themselves. This can get tricky if you're
implementing
multiple interfaces coming from multiple class loaders.
Nashorn will choose
a class loader from among all involved classes' and
interfaces' class
loaders that can see all the classes (the "maximum
visibility class
loader") by probing Class.forName from one loader with a
name of a class
from another loader and seeing if it resolves to the same
class. If it
finds one, it uses that as the parent loader for the
adapter's loader. If
it can't find one, you get a TypeError at runtime. You
know I have a fun
job when some days I wake up thinking "I need to write an
algorithm for
finding a maximum in a lattice of ClassLoader objects
partially ordered by
can-see-classes-in-the-other relation".
On Feb 6, 2015, at 10:05 AM, Christopher Brown <
[email protected]
<mailto:[email protected]>> wrote:
Hello,
Is it possible to use the Java.type() then the
Java.extend() syntax
described here:
https://wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions
...with interface types loaded from a classloader
other than the JRE
classloader and the main application classloader.
As a simple example, suppose in the main() method of
an application, I
create a URL classloader to load arbitrary JAR files
containing interface
definitions, and then I create an instance of the
Nashorn scripting
engine. Is it possible to make these types visible to
the scripting
engine, such that I could (in a loaded script) use
Java.type() then
Java.extend() to create an implementation of such a
dynamically-loaded
interface, and return it from the script when invoking
a given function?
I'm guessing that it's not possible, because I've not
seen this
documented
anywhere. I would assume that it might be possible
via the "context
classloader" of the current thread, but I'd prefer not
to rely on
undocumented features (supposing that it's the case).
The most flexible
approach would be to pass a classloader (or a
map/array of classloaders)
into the binding, and pass it as a second parameter to
the Java.type()
function (especially if in a more complicated example,
multiple URL
classloaders were loaded and defined types with the
same fully-qualified
names).
Additional question #1: would the implementation of
the interface in
Nashorn require that I implement default methods, or
could they be
invoked
in the script without defining them?
Additional question #2: how should resources added to
a binding, and more
generally, resources used by a ScriptEngine instance,
be cleaned up when
the engine instance is no longer required? For
example, if adding
classloaders is (or can be made) possible, I can't see
any way to
explicitly "close" the script engine... I'm assuming
the only way to
clean
things up is to de-reference the engine and wait for
it to be
garbage-collected.
Thanks,
Christopher