Nashorn access non-static Java method

2.8k Views Asked by At

In Java 7 (1.7), I could access a Java method from JavaScript by running this:

ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval("importClass(net.apocalypselabs.symat.Functions);");
jse.eval("SyMAT_Functions = new net.apocalypselabs.symat.Functions();");

String input = "notify(\"Foo\");"; // This is user input

jse.eval("with(SyMAT_Functions){ "+input+" }");

Which would run the notify() function from the Functions java class:

public class Functions {
    private Object someObjectThatCannotBeStatic;
    public void notify(Object message) {
        JOptionPane.showMessageDialog(null, message.toString());
    }
    /* Lots more functions in here, several working with the same non-static variable */
}

How do I access the Functions class in Java 1.8 with the Nashorn engine? My goal is to run different code for the first snippet if the user has Java 1.8, while still allowing people with 1.7 to use the app.

I've tried http://www.doublecloud.org/2014/04/java-8-new-features-nashorn-javascript-engine/ , https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html , and How to instantiate a Java class in JavaScript using Nashorn? without luck. None of them seem to allow me the same thing as Java 1.7 did, instead assuming I only want to access static functions and objects.

The most common error I get:

I start with...

ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval("var SyMAT_Functions;with (new JavaImporter(Packages.net.apocalypselabs.symat)) {"
                    + "SyMAT_Functions = new Functions();}");

...then...

jse.eval("with(SyMAT_Functions){ "+input+" }");

...spits out...

TypeError: Cannot apply "with" to non script object in <eval> at line number 1
2

There are 2 best solutions below

0
On BEST ANSWER

I decided to compile and bundle the "old" Rhino interpreter with my application instead of using Nashorn.

https://wiki.openjdk.java.net/display/Nashorn/Using+Rhino+JSR-223+engine+with+JDK8

1
On

I was able to reproduce. First of all, Nashorn doesn't try to make it difficult to use Java objects (non-static or otherwise) in general. I have used it in other projects and not had any major issue converting from Rhino in Java 7 beyond what is covered in the migration guide. However, the issue here appears to deal with the use of the with statement which is "not recommended" and is even disallowed in strict mode of ECMAScript 5.1, both according to MDN.

Meanwhile, I found a thread on the Nashorn-dev mailing list discussing a similar case. The relevant part of the response was:

Nashorn allows only script objects (i.e., objects created by a JS constructor or JS object literal expression) as scope expression for "with" statement. Arbitrary objects . . . can not be used as 'scope' expression for 'with'.

In jdk9, support has been added to support script objects mirror other script engines or other globals (which are instances of ScriptObjectMirror).

It's not the most elegant solution but, without using JDK 9, I was able to get your intended use of with to function by writing a proxy object inside the Javascript to mirror the public API of the Java class:

package com.example;

import javax.script.*;

public class StackOverflow27120811
{
    public static void main(String... args) throws Exception {
        ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
        jse.eval(
            "var real = new Packages.com.example.StackOverflow27120811(); " +
            "var proxy = { doSomething: function(str) { return real.doSomething(str); } }; "
        );
        jse.eval("with (proxy) { doSomething(\"hello, world\"); } ");
    }

    public void doSomething(String foo) {
        System.out.println(foo);
    }
}

Attila Szegedi pointed out the non-standard Nashorn Object.bindProperties function. While it can't be expected to work with anything but the Nashorn engine, it does eliminate the complexity of re-declaring all of the public API inside the proxy object. Using this approach, the first jse.eval(...) call can be replaced by:

jse.eval(
    "var real = new Packages.com.example.StackOverflow27120811(); " +
    "var proxy = { }; " +
    "Object.bindProperties(proxy, real); " // Nashorn-only feature
);