Access variable of ScriptContext using Nashorn JavaScript Engine (Java 8) - javascript

I used the following code with the Rhino JavaScript engine in Java:
#Test
public void testRhino() throws ScriptException {
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName("rhino");
final String raw = "I am the raw value injected";
final ScriptContext ctx = new SimpleScriptContext();
ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);
String script = "var result = 'I am a result';";
script += "java.lang.System.out.println(raw);";
script += "'I am a returned value';";
final Object res = engine.eval(script, ctx);
System.out.println(ctx.getAttribute("result"));
System.out.println(res);
}
The output of the script (using Rhino) is:
I am the raw value injected
I am a result
I am a returned value
Within the Nashorn JavaScript engine, I get no value for the result:
#Test
public void testNashorn() throws ScriptException {
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName("nashorn");
final String raw = "I am the raw value injected";
final ScriptContext ctx = new SimpleScriptContext();
ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);
String script = "var result = 'I am a result';";
script += "java.lang.System.out.println(raw);";
script += "'I am a returned value';";
final Object res = engine.eval(script, ctx);
System.out.println(ctx.getAttribute("result"));
System.out.println(res);
}
returns
I am the raw value injected
null
I am a returned value
How can I access the value of the result variable of the ScriptContext using the nashorn engine?

If you use ScriptEngine.createEngine API to create ENGINE_SCOPE Bindings, it'll work as expected:
import javax.script.*;
public class Main {
public static void main(String[] args) throws Exception {
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName("nashorn");
final String raw = "I am the raw value injected";
final ScriptContext ctx = new SimpleScriptContext();
// **This is the inserted line**
ctx.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
ctx.setAttribute("raw", raw, ScriptContext.ENGINE_SCOPE);
String script = "var result = 'I am a result';";
script += "java.lang.System.out.println(raw);";
script += "'I am a returned value';";
final Object res = engine.eval(script, ctx);
System.out.println(ctx.getAttribute("result"));
System.out.println(res);
}
}

Nashorn treats the Bindings stored in ScriptContext as "read-only".
Any attempt to set a variable stored in a Bindings object (or to create a new variable) will result in a new variable being created in nashorn.global which shadows the Bindings parameter by that name.
You can use the engine to "evaluate" the variable, using this code:
System.out.println( engine.eval("result", ctx) );
This is, however, quite ugly. "result" is first compiled into a script, and then that script is evaluated, to return the variable's value. Fine for testing, but perhaps a little too inefficient for a general solution.
A better, but perhaps more fragile method, is to extract the "nashorn.global" variable, and query it for the desired value.
Bindings nashorn_global = (Bindings) ctx.getAttribute("nashorn.global");
System.out.println( nashorn_global.get("result") );
See also my hack/answer in Capturing Nashorn's Global Variables for automated way of moving nashorn.global values back to a Map<String,Object> after evaluating a script.

Related

How to remove element from Java Map from within Nashorn JavaScript

I have a Java HashMap which I have passed to the script engine. I would like to remove entries as they are processed because I'm reporting invalid keys later. The apparent usual method for removing entries (delete testMap['key'];) has no effect.
How do I make this test pass?
#Test
public void mapDelete() throws ScriptException{
Map<String,String> map = new HashMap<>(1);
map.put("key","value");
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
engine.getContext().getBindings(ScriptContext.GLOBAL_SCOPE).put ("testMap", map);
engine.eval("delete testMap['key'];");
Assert.assertEquals (0, map.size());
}
If you know that you have an HashMap, you can use its Map API within Nashorn, i.e.:
#Test
public void mapDelete() throws ScriptException {
Map<String,String> map = new HashMap<>(1);
map.put("key","value");
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
engine.getContext().getBindings(ScriptContext.GLOBAL_SCOPE).put ("testMap", map);
engine.eval("testMap.remove('key');");
Assert.assertEquals (0, map.size());
}

What is the use of overriding hasMember() inside JSObject, AbstractJSObject?

I understand that this helper method can be called from a Java code to check if your Java/JS Object has a property you are looking for
but i would like to know if this is called by the Nashorn Engine while we use this JSObject/AbstractJSObject implementation in a JavaScript code.
I am aware of the fact that doing a . inside JavaScript will in turn invoke the Java method .getMember()
If "in" operator in used in JavaScript (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in) on a JSObject instance, Nashorn will call hasMember method on that JSObject.
Example code:
import javax.script.*;
import jdk.nashorn.api.scripting.*;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
e.put("obj", new AbstractJSObject() {
#Override
public boolean hasMember(String name) {
System.out.println("hasMember called for " + name);
return false;
}
});
// in operator triggers hasMember call on JSObject instance
e.eval("if ('foo' in obj) print('yes')");
}
}
The output from the above program looks like:
hasMember called for foo

Is it possible to import javascript files to a java ScriptEngine

I am using nashorn java ScriptEngine. I would like to evaluate a script which includes other scripts. I know I can use the load directive directly in the javascript itself, but I would prefer to import or load it directly from the java code instanciating the scriptEngine.
Is there a way to do this ? Something like :
void evaluateScript(String scriptName, String dependency) {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine jsEngine = factory.getEngineByName("nashorn");
jsEngine.load(depency); // does not exist.
jsEngine.eval();
}
I see the "load" function does not exist. How could I achieve this?
Thanks
Actually I found the answer myself: as mentioned in the comment, it is possible to call several eval with different scripts, same engine, and the engine will keep the evaluated scripts in its context. So here is my code:
public void executeScript(String scriptName, String[] dependencies) {
try {
FileReader script = new FileReader(scriptName);
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine jsEngine = factory.getEngineByName("nashorn");
if(dependencies != null) {
for (String dependency : dependencies) {
FileReader dependencyFile = new FileReader(dependency);
jsEngine.eval(dependencyFile);
}
}
jsEngine.eval(script);
}
}
I can define functions in my dependencies and use them in the script of name scriptName.
javax.script.ScriptEngine has many "eval" methods - there are java.io.Reader accepting eval methods like this -> eval
You can pass a java.io.FileReader or a jdk.nashorn.api.scripting.URLReader to load script from a file or a URL.

Nashorn does not find function when eval with bindings

There is a strange difference when I evaluate a nashorn script with or without bindings: without bindings there is no problem invoking a function, but with the bindings the function is not found. Here is an example:
public class SimpleNashornTester {
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
ScriptEngine jsEngine = new ScriptEngineManager().getEngineByName("nashorn");
Compilable jsCompilable = (Compilable) jsEngine;
Invocable jsInvocable = (Invocable) jsEngine;
ScriptContext scriptCtxt = new SimpleScriptContext();
Bindings engineScope = scriptCtxt.getBindings(ScriptContext.ENGINE_SCOPE);
CompiledScript jsScript = jsCompilable.compile("function definition() {print(\"Hello\")}");
jsScript.eval(engineScope); // no error with jsScript.eval() !
jsInvocable.invokeFunction("definition", new Object[] {});
}
}
This yields the error:
Exception in thread "main" java.lang.NoSuchMethodException: No such function definition
at jdk.nashorn.api.scripting.ScriptObjectMirror.callMember(ScriptObjectMirror.java:204)
Without the parameter engineScope in the evaluation of the script the function is found and invoked. Can anybody explain this difference? How can I use bindings without getting an error?
You're using a new ScriptContext and it's associated ENGINE_SCOPE Bindings to compile the script. invokeFunction/invokeMethod use the default ScriptContext (and it's associated ENGINE_SCOPE Bindings) to search the function. Each different ENGINE_SCOPE Bindings is associated with its own ECMAScript global object (and it's own ECMAScript global objects).
So, you can fix your program by
changing the default context to be the new context before invoke:
// change the default ScriptContext
jsEngine.setContext(scriptCtxt);
jsInvocable.invokeFunction("definition", new Object[] {});
Use the default ScriptContext for compiled script as well. As in:
ScriptContext scriptCtxt = jsEngine.getContext(); // new SimpleScriptContext();

Equivalent of Ruby's "require" in Javascript

How do you "require" another file into the existing file in Javascript? Is there anything similar to Ruby's "require" or "load"?
> Note: I'm using JS in server (Rhino)
Reason: I just need to access the methods in the other JS files.
Update: This works only when executing it from cmd line. When I try to call it programatically it fails. Here's my code: http://pastie.org/1240495
To use the load function in js embedded from Java, you must first expose it in on the scripting context. There's probably a way to do it from Java, but you can do it using js as well.
Disclaimer: this solution uses source code taken from an Apache-licensed project I have been working on. You can see the original source file here.
This js file sets up your global variables, and lives in a file named setupglobals.js:
var shell = org.mozilla.javascript.tools.shell.Main;
var args = ["-e","var a='STRING';"];
shell.exec(args);
var shellGlobal = shell.global;
//grab functions from shell global and place in current global
load=shellGlobal.load;
print=shellGlobal.print;
defineClass=shellGlobal.defineClass;
deserialize=shellGlobal.deserialize;
doctest=shellGlobal.doctest;
gc=shellGlobal.gc;
help=shellGlobal.help;
loadClass=shellGlobal.loadClass;
quit=shellGlobal.quit;
readFile=shellGlobal.readFile;
readUrl=shellGlobal.readUrl;
runCommand=shellGlobal.runCommand;
seal=shellGlobal.seal;
serialize=shellGlobal.serialize;
spawn=shellGlobal.spawn;
sync=shellGlobal.sync;
toint32=shellGlobal.toint32;
version=shellGlobal.version;
environment=shellGlobal.environment;
Here is your original Java host file, now augmented to evaluate setupglobals.js before any other scripts:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import org.mozilla.javascript.*;
public class RhinoRunner {
public static void main(String args[]) throws IOException
{
BufferedReader script = new BufferedReader(new FileReader("setupglobals.js"));
BufferedReader script2 = new BufferedReader(new FileReader("example.js"));
Context context = Context.enter();
try {
ScriptableObject scope = context.initStandardObjects();
context.evaluateReader(scope, script, "script", 1, null);
context.evaluateReader(scope, script2, "script2", 1, null);
Function fct = (Function)scope.get("abc", scope);
Object result = fct.call(context, scope, scope, new Object[] {2, 3});
System.out.println(Context.jsToJava(result, int.class));
}
finally
{
Context.exit();
}
}
}
Here is your example.js, now augmented to use the global load function to load the file hello.js:
function abc(x,y)
{
return x+y
}
load("hello.js")
And finally, here is hello.js:
print("hello world!")
When executed, RhinoRunner prints the following:
hello world!
5
In Rhino shell, you can should be able to use load(), which is a predefined global method:
load([filename, ...])
Load JavaScript source files named by string arguments. If multiple arguments are given, each file is read in and executed in turn.

Categories

Resources