Wednesday, December 24, 2008

Scripting in JDK6 (JSR 223) Part 2

Its Lunch Time!

After programmer 2 shown programmer 1 how to deal with JSR 223 or JDK6 scripting ( part 1) programmer 2 went for lunch as he didn’t eat anything since yesterday and left programmer 1 to meet his fate.

Programmer 1 began to apply what he learned from programmer 2 and stuff went so bad, after 30 minutes programmer 2 came back.

Programmer 2: Man, how is everything going? (While he was just about to finish his last bite from the sandwich)

Programmer 1: man the performance sucks, it is really really slow and that won’t be good thing, it is bottleneck now

Programmer 2: ok let me see what did you do. (Swallowed his last bite hardly)

The Crime!:

What programmer 1 wrote was the following:

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName("javascript");
engine.put("x",1);
for(int i=0;i<100;i++){

engine.eval("function xReturner(){" +" x=x*10; return x;};xReturner();");

}

As we can see programmer 1 did nothing wrong , he just used what he learned but this code is a disaster, it will make performance issue , in our case we are using JavaScript which is interpreted with each request to the eval method ,this mean that every hit to this method and then calling the eval will do the following : read the file(if we are reading from file) , evaluate and then execute , oh man that so bad if we have a very complicated logic resides in a very big file or even a big function this will cause performance issue, lucky enough that there is something to over come this.

The Solution! :

There is an interface shipped with the script package named Compilable, from its name we can conclude that it might be used to compile our script, and yup that’s right it compiles our script so that we don’t need to evaluate and read it before executing it every time we need to invoke the script, this can be achieved as shown in the following code:

ScriptEngine engine = manager.getEngineByName("javascript");
if(engine instanceof Compilable)

{

Compilable compiledEng=(Compilable) engine;

CompiledScript script= compiledEng.compile("function xReturner(){" +" x=x*10; return x;};xReturner();");
for(int i=0;i<100;i++){

engine.put("x",i);

Object result =script.eval();}

}

else{

engine.eval("function xReturner(){" +" x=x*10; return x;};xReturner();");

}

First we check to see if this engine is implementing Compilable interface or no (this is an optional interface so some engines might not implement it but in our case as we are using JS engine shipped with JDK which implements this interface) so if our engine implements this interface then everything will be smooth else we don’t have any other way except the normal eval method.

Ok so we will just check if it implements the Compilable interface so we will invoke the compile method of the engine which will return CompiledScript which we are going to use and invoke our eval method on it and get the result.

By invoking these two scripts on programmers 1 machine we got these results:

Case 1 (no compiling): 931 ms

Case 2(with compiling) 661 ms

*Oh yea the machine that was used is a little bit slow (very slow, in fact used for performance testing for sure :p)

After solving the disaster programmer 1 was very happy by this performance tip.

Programmer 1: oh man thanks a lot for this tip, but I have another thing in mind, does JSR 223 enable me to invoke a method of my choice from a list of methods?

Programmer 2: oh that’s nice questions, let me show you how.

The Invocable!

Yup as you figured out it is the same thing as Compliable an interface that is optionally implemented by the engine you are using.

It enables you to invoke a method by name and also pass parameters to this method (we won’t need any binding here as all the stuff is handled by concrete implementation of this interface).

Before we dig deep in it and see sample code we need to mention that there are two methods that are used for this purpose and they are “invokeMethod”,”invokeFunction” lol it has the same meaning but if you give it another thought you would figure out that the names is correctly used (Function is usually used to address functions in non Object oriented or in other words flat structure and Method are used to address methods in object oriented language) so what are the difference between them ?

The difference between them is as follow:

invokeFunction: used to invoke methods on the top level which means methods that doesn’t reside inside a class (as in Ruby for example)

invokeMethod: used to invoke methods inside a class or module

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");

engine.eval("function getWelcomeMessage(name){return 'hello '+name;};");
Object[] params = {"Wolrd"};
Invocable invEngine=(Invocable)engine;
System.out.println(invEngine.invokeFunction("getWelcomeMessage",params[0]));

As we can see in the previous code we just got instance from our engine and cast it to invocable and then call the invokeFunction method which will take the function we want to invoke and also the parameters we want to pass, nothing else we can say about the invokeFunction on the other side invokeMethod as we said it can be applied for JRuby, well as people say nothing is better than a nice simple example ;)

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("jruby");
Object obj=engine.eval(new FileReader("c:\\JRubySample.rb"));
Invocable invEngine=(Invocable)engine;
Object[] params = {"Wolrd"};

System.out.println(invEngine.invokeFunction("show",params[0]));;

System.out.println(invEngine.invokeMethod(obj,"show",params[0]));

As we can see above the code, it is the same as we did before but here we changed our scripting engine to be JRuby and not JavaScript as we used to do but here we read from a file named JRubySample which has the following contents:

# Class Scripting sample
class SampleClass

def show(message)
puts "hello "+message
end
end

def show(message)
puts "hello "+message +" outside"
end
sample = SampleClass.new

return sample;

we can see that we have two methods with the name show, one is a top level (flat structure) and the other reside in a class so as we mentioned before the invoke function will work on top level functions so after invoking the invokeFunction we will get the value “hello world outside” on the other side we said that invokeMethod works on methods that resides in a class.

How can we decide which class we want to invoke its method?

This is determined in the first parameter of the invokeMethod as we can see in our JRuby sample we are returning an object from the class SampleClass and then this object is returned by the eval method and then we pass it to the invokeMethod which will take this object, look for show method in it and then invoke it so we will get this message “hello world”.

Nice, but is that the only thing Invocable interface can do? Nope it can do more than this.

Its Creation Time!

The title sounds strange (all titles in fact), well it is not that strange when you know what is it all about.

The other nice feature about Invocable interface is : with the usage of getInterface method you can create interface implementation from the methods in the script you are evaluating ,this mean that getInterface method returns an implementation of a specific java interface and the methods in this interface is implemented by the script being evaluated(the script has functions that have the same signature as the one in the java interface), so when I get the object returned by getInterface and invoke a method on it , it will just run the logic that were written in the script method, in the code below we can see that happens :

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");

engine.eval("function getAccountName(){var name='Account 1'; return name;};");
Invocable invEngine=(Invocable)engine;
AccountOperations operation=invEngine.getInterface(AccountOperations.class);
System.out.println(operation.getAccountName());

As we can see in the previous code sample we are going to evaluate the script which has one method named getAccountName (returns a string that represents the account name)
The new thing here is that we are using the getInterface method which would return an interface implementation to us, as we can see it takes the Interface that we want to get implementation for (it will return an implementation of that interface) after that we will call getAccountName method (the only method inside this simple interface) on the instance returned by the getInterface method and that’s it!

One last thing:

Ok I feel guilty as I didn’t mention something which is:” we can use java objects in JavaScript engine that is shipped with JDK6”

Oh yea you are talking about binding right? nope we are not talking about the bindings here I mean it literally, what I mean is that I can use java objects inside the script I am writing lets take a look at the following example :

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine egine = manager.getEngineByExtension("js");egine.eval("importPackage(javax.swing);var optionPane =JOptionPane.showMessageDialog(null, 'Hello!');");

As we can see we used this time getEngineByExtension instead of getEngineByName (both will do the same thing , no reason why I changed it here but as we said before I can use getEngineByExtension if we are parsing scripts for example so we don’t know what script we have now so we will just get engine instance based on its extension ) after that we actually imported java objects and used it in our script engine and when we call the eval method we will get a message box with Hello message

Back To Our Programmers:

Programmer 1: oh man that’s awesome thanks a lot. I owe you one, I want to do anything for you, tell me what can i do?
Programmer 2: it is ok, no need for that
Programmer 1: no no I insist
Programmer 2: well if you are, invite me on dinner.
Programmer 1: ok consider it done.


And in the restaurant programmer 1 forgot that he doesn’t have enough money and programmer 2 had to pay for it.

Despite this sad ending for programmer 2, programmer 1 were able to finish the task on time, thanks to JSR 223 which enabled him to do so and lets not forget programmer 2 as well

Conclusion:

JSR 223 is really nice feature shipped with JDK 6

*We saw that JSR 223 enables us to pass data from java to JavaScript and vice versa.

*Also enable us to invoke scripts not only that but also enables us to return implementation of a specific interface when the script we are invoking have methods as the interface we want to get implementation for.

*it also enable us to use java objects in the script as in the JavaScript engine “rhino“ shipped with the JDK 6




7 comments:

Donovan said...

In the code example you showed for the Compilable interface , this :

engine.put("x",i);

should be in the for loop and not outside , right ?

Ahmed Ali said...

Donovan,

sorry for this, yes it can be in the for loop or you can replace the i with a static value (1 for example)

my bad , i will fix it now :) thanks for hgihglighting it

Shubha said...

how do I execute one of the methods in my script with varying parameter values for type_element, click_element for multiple iteration.

myscript.js
-----------
function method1(){
}

function execute(){
abc.type(type_input);
abc.click(click_element);

}

Ahmed Ali said...

Shubha,

As i understood from your question , you will just need to bind these values to your scriptengine and to bind these values you will need to do something like that : engine.put("arrList", arrList); arrList is an arrayLisr in your script just use the binded values (loop over them for example) or extract the data you need.

Hope that would answer your question, please feel free to correct me if i didn’t get your question right :)

Thanks,
Ahmed Ali

Ketan said...

Can i do following for improving performance while invoking functions/methods.

compile a script using Compilable interface --> ignore the returned CompiledScript --> invoke a function/method in the script using invocable interface.

Will it improve the performance or it is equivalent to using the Invocable interface directly.

Thnx in advance

Ahmed Ali said...

Hello Katen,

As for your question , No it won't do any improvement and please note that invokeMethod calls method on a script object compiled during a previous script execution , hope this would answer your question :)

Thanks,
Ahmed

indu said...

hey
i have a javascript code in a website which has a form and post value in that form.I need to invoke that script from java.What should i do?