String expression interpolation support in java scripting engines - java

I'm using groovy/nashorn as java engine but not able to interpolate Strings.
jdk.nashorn.api.scripting.NashornScriptEngine scriptEngine =(NashornScriptEngine) factory.getEngineByName("nashorn");
ScriptContext context = scriptEngine.getContext();
Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("x","Guest");
engine.eval("Hello, ${x}",context);
But I'm getting javax.script.ScriptException.
Is Sttring interpolation supported?
Thanks

There are two things worth mentioning:
if you want to evaluate Groovy script, you might need to use
new ScriptEngineManager().getEngineByExtension("groovy");
the script passed to engine.eval() method has to be a valid Groovy code. The script code you passed to the eval method is not a valid Groovy code - you expect to interpolate a string, but you didn't put it inside the double quotes.
Consider the following example:
ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("groovy");
ScriptContext context = engine.getContext();
Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("x","Guest");
Object result = engine.eval("\"Hello, ${x}\"", context);
System.out.println(result);
The output:
Hello, Guest
Alternatively, you may pass a Groovy script that prints the interpolated script. In this case the code may look like this:
ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("groovy");
ScriptContext context = engine.getContext();
Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("x","Guest");
engine.eval("println \"Hello, ${x}\"", context);
It produces the same output, but does not assign Hello, Guest to a variable.

Related

Questions about Nashorn - JSObject

I want to create a JSObject and fill it with properties (kind of like a HashMap), but without casting the result of an eval("({})"), because I would think that constantly evaluating such a thing would really have an impact on performance. Is there a way?
If you want to use script objects like Maps, you might as well use java.util.HashMap instances from Nashorn scripts! In addition to supporting the usual java method calls, Nashorn's linker special cases java.util.Map instances and supports keys-as-property names idiom. This is more efficient than using a Javascript object as a Map.
$ jjs
jjs> var m = new java.util.HashMap
jjs> m.foo = "bar"
bar
jjs> m.foo
bar
jjs> m.get("foo")
bar
jjs> m.put("js", "nashorn")
null
jjs> m.js
nashorn
But, if you insist on using JS-object-as-map, then you can do the eval and cast to JSObject you mentioned. You may want to measure the perf. hit (which is assumed!) before making any further changes!
You can also get hold of JS "Object" constructor object and 'cache' it for repeated object creation from Java code. You can then call newObject() method on it to create a new empty object.
import javax.script.*;
import jdk.nashorn.api.scripting.JSObject;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
// get JS "Object" constructor object
JSObject objConstructor = (JSObject)e.eval("Object");
// call 'newObject' on Object constructor
JSObject jsObj = (JSObject) objConstructor.newObject();
// fill properties of the new empty script object
jsObj.setMember("foo", "bar");
// expose the new JS object as global var!
e.put("obj", jsObj);
// print the object as a JSON string
e.eval("print(JSON.stringify(obj))");
}
}
Note that the above scheme works for any user-defined constructor function as well. For eg. if you want to create objects using specific user defined constructor function, you just have to replace
JSObject objConstructor = (JSObject)e.eval("Object");
with
JSObject objConstructor = (JSObject)e.eval("MyConstructorFunc");
(assuming you've eval'ed code to define MyConstructorFunc function earlier).
The rest of the code is same as above.

Matching Javascript RegEx using Nashorn

How would I evaluate input with a ECMA 262 compliant regular expression?
After some reading I found out the Java 8's javascript engine nashorn can help me to do that. How can I use nashorn script engine to match a regular expression?
Assume you have a file regex.js containing a method used to determine if a given String matches a given regular expression:
var check = function(regex, str) {
return new RegExp(regex).test(str);
};
You can evaluate this function using the Nashorn script engine, and call it with the following code:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("regex.js"));
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("check", "\\d+", "1");
System.out.println(result);
In order to call a function, you first have to cast the script engine to Invocable. This interface is implemented by the NashornScriptEngine and defines a method invokeFunction to call a JavaScript function for a given name. The rest of the method arguments are then directly passed as arguments to the JavaScript method.
Note that it is also possible to do this without an external JavaScript file. It is also possible to avoid the cast to Invocable by asking the script engine to evalute a String. This is the same code with those two ideas:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("function check(regex, str) { return new RegExp(regex).test(str); }");
Object result = engine.eval("check('\\\\d+', 1);");
System.out.println(result);
When passing variables from Java to JavaScript, you have to be very careful about the proper escaping of characters: the rule are not the same for the two languages.
Perhaps something like this:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine se = manager.getEngineByName("nashorn");
se.put("str", "the quick brown fox");
se.put("rgx", "/quick/");
Object result = se.eval("str.matches(rgx).length");
System.out.println(result);
You need to run with Java8 to get the "nashorn" engine. The argument to se.eval(String) is the javascript. I kept it simple for this example. You can do plenty other stuff in terms of regex. See here.
Using Java 8 you can use the Nashorn JavaScript engine via the package javax.script using the nashorn identifier.
Here's an example:
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine nashorn = engineManager.getEngineByName("nashorn");
String ecmaScript = //YOUR SCRIPT HERE
//add any additional variables here
Object returnVal = nashorn.eval(ecmaScript);
SOURCE: http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html
You'll want to get familar with the ScriptEngine class and you'll need to do quite a bit of testing and trial & error to handle the data that's returned since the returned data won't be type safe. The debugger will be your best friend during this process.

Passing json data from javascript to Java

I'm trying to pass json data from javascript to java, like below. The idea is to pass the data to the java code as a javascript object (and not a a string which I know can be done ). I've tried the code below without success - idea was to use NativeJson.stringify to convert from a javascript object to a Java string, however this results in an Undefined instance instead of the expected string. Any idea on this could be achieved much appreciated.
in javascript file "test.js"
parser.fct ( {"abc":123,"def":456} )
in java
//1.binds javascript calls to Java
...
ScriptEngine engine = new ScriptEngine...
Bindings bindings = engine.createBindings();
bindings.put("parser", new Parser());
engine.eval("test.js");
//2. in Parser class
public void fct(Object obj){
Context ctx = Context.enter();
ScriptableObject scope = ctx.initStandardObjects();
Object json = NativeJSON.stringify(ctx, scope, obj, null,null);
//json returned is of type Undefined
}
Could not find a way to use NativeJSON.stringify(). I ended up building the json string "by hand", i.e Iterating on the property ids of the native javascript object and adding these properties to a java Map.
The Map is then transformed into a json object using new JSONObject(map);
link to source code
https://gist.github.com/eleco/c2bc77dca9ecc844a924

Static analysis of Javascript with Java

I need to do a static analysis of Javascript files using Java. Here I need to check whether the Javascript file has any function calls to document.write() or reference to properties like innerHTML etc. Can I use javax.script.* package to achieve this? or Which Java api do I need to use for Parsing? Also can you provide examples for the same?
You can't statically analyze Javascript in the way you intend because Javascript is not a statically typed language.
You can check for document.write() but what if my code was this:
var whatever = document; whatever.write()
Or do you want to reject any function named write() even if it didn't write to the document?
Furthermore, Javascript has an eval function so you could always do:
var m = "ment";
eval("docu" + m + ".wri" + "te('hahahaha')");`.
How are you going to check for that?
Similarly, property access can be done in many ways.
Imagine this piece of code:
var x = document.children[0];
x.innerHTML = ...;
x["inner" + "HTML"] = ...;
var y = "inner";
x[y + "HTML"] = ...;
You're not going to be able to detect all those variants, and the hundreds more variants that you could make, using static analysis.

Create a string with the result of an expression and the expression that originated the value. Is it possible?

Like
String r = SomeThing.toExecString("new Object().toString()");
And when executed the value of r would be:
"new Object().toString() = java.lang.Object#c5e3974"
Is this even possible at all? Would it need a bunch of reflection? A built in compiler maybe?
ScriptEngine engine = new ScriptEngineManager().getEngineByName("beanshell");
Object result = engine.eval("new Object().toString();");
System.out.println(result);
You may get close to what you want using BeanShell. I ran the above code with Java 6 with BeanShell 2.0b4 and the JSR 223-based bsh-engine.jar engine on the classpath.
There is a great post here:
Generating Static Proxy Classes - http://www.javaspecialists.eu/archive/Issue180.html
Part one is enough for what you asked, I think
Don't know if you really wanted this. But your problem would be solved with this method:
String toExecString( String code ) {
return String.format(
"\"%s\" = %s#%x",
code,
code.getClass().getName(),
code.hashCode()
);
}

Categories

Resources