Saturday, December 13, 2008

Scripting in JDK6 (JSR 223) Part 1

Introduction:

For sure most of us (mm guess so) have heard about the Scripting provided in Java 6 (Mustang) or JSR 223, 1st time I saw that (just saw the title) I thought that the Java guys will enable us to compile and run JavaScript scripts and that’s the end, well nope that wasn’t what Scripting in java 6 about but actually it is about enabling scripting languages to access the java platform and get the advantages of using java and the java programmers to use such scripting languages engines and get advantages from that (as we will see ).after I got it I was thinking : oh man if the team who implemented the Scripting engine were living in the dark age and they came up with such idea they will be accused of The practice of witchcraft but thanks God we are open-minded now and they will live and the good thing is that they will continue to practice witchcraft :D
So for now java guys will stay alive and continue to do their voodoo stuff and we will be looking at this nice scripting engine.

A sad story (life before Scripting engine):

Programmer 1 is reading documents of an approved change request (while listening to Bon Jovi’s Have a nice Day) which was as follow:

“We need to change our Java Scripts in the client side and move it to server side and also our JRuby scripts to be converted into java classes and we need this by tomorrow”

Programmer 1:” Man, I’m going to have a bad day, how will I do this (develop, test and deploy) in one day this is insane, And that task is assigned to only me :S I don’t know JRuby and I am not that good at JavaScript, well better to move on and get some tutorials”

And in order to stick to the deadline programmer 1 had to get some advanced JavaScript, JRuby tutorials and didn’t have his lunch break L and he had to work till late hours in the next day morning in order to meet the deadline but as expected from programmer 1(that’s why they hired him) he completed his job on time and didn’t complain but he experienced a very bad day. TA DA end of story and it is one sad story, now lets rewind the tape and see how will the story end if programmer 1 had someone else to help him and to show him what is JSR 223.

A happy story (life with Scripting engine):

Same thing as previous (programmer 1 is reading CR document) and he got shocked

Programmer 1: man I’m gonna have a bad day, how will I do this (develop, test and deploy) in one day, this is insane.

A voice came from nowhere and said: what is going on? (That was programmer 2)

Programmer 1: man, check this change request we don’t have such time, we will have to work so hard to finish it in this time

Programmer 2: let me see… mmm well we could make a workaround and give them a build that everything is converted into java classes in no time.

Programmer 1: ok tell me more.

Programmer 2: we will give them what they want, which is all scripts are called and manipulated by java classes and called from server side (in JavaScript case for example) and by using the script engine we wont need to rewrite JRuby script or JavaScript in java we wont even need to know the logic or get any JRuby or JavaScript developer we will just call use the same logic and return the results and TA DA everything is going fine

Programmer 1: huh, seems to me that something hit you on the head, ok how we are going to do this?

Programmer 2: by using the scripting engine shipped with java 6

Programmer 1: huh !

Programmer 2: ok seems to me that you don’t understand a word, I will show you, just give me a paper

The Details:

What we need to know that the main class that we will need to interact with is ScriptEngineManager which will give us access to ScriptEngine which is the engine to any scripting language we want (in our case JavaScript and JRuby) by default JavaScript engine is shipped with JDK6 (Mozilla Rhino) but if we want other script engine we should download its engine implementation (there are some engines for, JRuby, PHP, Python, Groovy,…) ok lets see some JavaScript in action

In Action:

try {

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval("var out='hi';var date=new Date();");
}

catch (ScriptException scrpEx)
{
scrpEx.printStackTrace();
}

By analyzing the previous code we can see:

Line 3 we make an instance from the ScriptEngineManager which is the entry point or in other words we use it to gain access to our script engine like line 4 shows , we note here that we got the scriptEngine by specifying the name of the engine ,we can also get our engine by specifying MimeType or Extension and that’s by calling getEngineByMimeType or getEngineByExtension instead of calling getEngineByName ,this can be useful if we want to get a scriptEgnine instance based on a file extension or something like that.

ScriptEngine instance we got is an instance of JavaScript engine which will apply all JavaScript rules to the script written or evaluated on this engine.

After getting the script engine in line 5 we just invoked a method called eval on this engine, this method evaluates the script giving for this method here as we can see we have supplied the script as a normal string we can supply it from a file for example but for the sake of simplicity we will just invoke the eval method on a string.

After invoking the eval method on the previous script, the rules for JavaScript will be applied while parsing this script, if anything went wrong an exception will be thrown and the type of the exception that will be thrown is ScriptException as line 7 shows.

When Stuff Goes Wrong:

What if we changed the parsed line demonstrated in previous code to be something that JavaScript wont recognize, what will show up? A parsing exception will be thrown indicating that this is not correct, something like the exception below (after changing var to vear)

javax.script.ScriptException: sun.org.mozilla.javascript.internal.EvaluatorException: missing ; before statement (#1) in at line number 1


As we can see the exception will give you the line number that caused this exception with the column number and also the file name (in our case we are not using any file so we got Unknown Source)

Evaluating…:


Great now that we have known the basics of the scripting what else can I do with that? It doesn’t give me enough control yet, for example I can’t pass or get variable from java to JavaScript and vice versa, who said so? JSR 223 enables use to pass inputs and receive outputs, in the previous sample we’ve just shown how to acquire a ScriptEngine which is our entry to our selected script language but as we all know that this won’t be enough , we want to deal with the script itself in other words we need to pass variables to the script and get some output from that script to be manipulated by java. Fortunately JSR223 enables us to do this in an easy smooth way; the listing below shows us how we can get the return value from JS:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
System.out.println(engine.eval("var date=new Date(); date.getMonth();"));

The previous code just gets engine instance and after that it evaluates the expression and here eval will evaluate the expression against JS rules and then invoke this script which gives us back the value of the month but we are just printing the value, what if we wanted to take this value and use it somewhere else?

That’s easy we will just need to modify the last line to be like this:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
Double month=(Double)engine.eval("var date=new Date(); date.getMonth();");


Cross Worlds (the direct way):

And now we can do whatever we want on the resulted month value.

Ok now we have seen that we can get back the date from the script by evaluating it but what if I wanted to get a specific variable value? Or assigning a specific value to a variable?

What we didn’t mention is the eval method has more than overload (the one we talked about is either pass a string or read the script form a file)

There is something called ScriptContext which enables us to define the scope of Biding Objects (we will see what does that mean shortly).

Bindings, this is what we have been looking for, bindings enables you to pass java objects to the script and also get script variable to java world.

The ScriptContext can either be GLOBAL_SCOPE or ENGINE_SCOPE which means that every object that we are going to bind in case of GLOBAL_SCOPE will be shared for all engine script and ENGINE_SCOPE will mean that it will be only visible for this engine.

Let’s look for some code now:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("a",1);
bindings.put("b",5);
Object a = bindings.get("a");
Object b = bindings.get("b");
System.out.println("a = " + a);
System.out.println("b = " + b);
Object result = engine.eval("c = a + b;");
System.out.println("a + b = " + result);

As we can see in the previous code we got the bindings of the engine and we’ve set its scope to be engine scope (only this engine).

After that we are calling bindings put method (which will allow us to pass java object to the script) bind the variable name “a” a value of 1 and variable “b” a value of 5so when evaluating the script, a will have value of 1 and b will have value of 5.

After that we are just getting the value of “a” and “b” back to the java world (we are just printing it but we could do anything with it and this is how we can get variable value from the script) and evaluating the script which will get us a result of 6

Cross Worlds (the indirect way):

We could have achieved the same functionality by using the indirect way as below:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.put("a", 1);
engine.put("b", 5);
Object result = engine.eval("c=a + b;");
System.out.println("a + b = " + result);
System.out.println("a + b = " + engine.get("c"));

We just called put method on the engine which indirectly does the bindings for us same as the previous code and the get method also will get us the variable named “c” to our java world.

Conclusion:

For now we have seen how to make a simple application and call a script, evaluate it and get its return and even pass any input to what ever we want in part two we will look into more interesting stuff, so stay tuned ;)

2 comments:

Amego said...

but you still have to know how to write script..you didn't make the life of programmer one happier, he still needs to get his hands dirty :D..

nice feature man..keep the good work

Ahmed Ali said...

lol yea but he wont go into details like he will do when he implements it from scratch ;)

Thanks for your comment