Dustin's Pages

Monday, April 20, 2009

JavaScript in Java

The recent JavaLobby post The Top 10 Unused Features in Java has been extremely popular. At the time of this writing, it is the top ranked post in the DZone Top Links category. In addition a reply to it has been posted as well. There are many interesting observations about underutilized features in Java in both blogs posts and I agree with some more than others. However, item that really caught my attention was the assertion that Java SE 6 is one of the most unused Java features.

I really enjoy working with Java SE 6 and have written about or blogged on Java SE 6 features several times in the past. In this blog posting, I intend to demonstrate a portion of Java SE 6's ability to host execute JavaScript code.

Most Java developers and JavaScript developers understand that besides the four letters "J-A-V-A," JavaScript and Java have very little in common other than some C-like heritage. Still, it can be useful at times to run a scripting language from within Java code and Java SE 6 allows this.

The code shown above generates output like that shown in the next screen snapshot.

As this image demonstrates, the Mozilla Rhino JavaScript engine is included with Sun's Java SE 6. We also see some "common names" that are associated with this particular engine. Any of these names can be used to lookup this engine. In later examples in this post, I will be using the common name "js" for this lookup.

The next code sample will take advantage of the provided Rhino JavaScript engine to execute some JavaScript code from Java code. In this case, we'll be taking advantage of JavaScript's toExponential function.

The code above directly invokes JavaScript using the ScriptEngine.eval(String) method to evaluate the provided String containing JavaScript syntax. Before invocation of the eval method, two parameters are "passed in" (bound) to the JavaScript code via ScriptEngine.put(String,Object) calls. The result object of the executed JavaScript is accessed in the Java code using a ScriptEngine.get(String) call.

To demonstrate the above code using the toExponential function, I'll use the following "client" code.

When the above code is run against the writeNumberAsExponential method shown earlier and JavaScript is employed, the output appears similar to that shown in the next screen snapshot.

This example is enough to demonstrate how easy it is to invoke JavaScript functionality from within Java SE 6. However, this could be implemented even more generically as the next two examples will demonstrate. The first example shows invocation of relatively arbitrary JavaScript with no parameters passed/bound and the second example demonstrates invocation of relatively arbitrary JavaScript with parameters passed/bound.

A relatively arbitrary JavaScript string can be processed with code similar to that shown next.

/** * Process the passed-in JavaScript script that should include an assignment * to a variable with the name prescribed by the provided nameOfOutput and * may include parameters prescribed by inputParameters. * * @param javaScriptCodeToProcess The String containing JavaScript code to * be evaluated. This String is not checked for any type of validity and * might possibly lead to the throwing of a ScriptException, which would * be logged. * @param nameOfOutput The name of the output variable associated with the * provided JavaScript script. * @param inputParameters Optional map of parameter names to parameter values * that might be employed in the provided JavaScript script. This map * may be null if no input parameters are expected in the script. */ public static Object processArbitraryJavaScript( final String javaScriptCodeToProcess, final String nameOfOutput, final Map<String, Object> inputParameters) { Object result = null; final ScriptEngine engine = manager.getEngineByName("js"); try { if (inputParameters != null) { for (final Map.Entry<String,Object> parameter : inputParameters.entrySet()) { engine.put(parameter.getKey(), parameter.getValue()); } } engine.eval(javaScriptCodeToProcess); result = engine.get(nameOfOutput); } catch (ScriptException scriptException) { LOGGER.severe( "ScriptException encountered trying to write arbitrary JavaScript '" + javaScriptCodeToProcess + "': " + scriptException.toString()); } return result; }

The code above provides quite a bit of flexibility in terms of the JavaScript that can be processed. This is probably not the best idea for production code, but does make it easier to demonstrate use of various JavaScript features within Java.

The first example to use this relatively arbitrary JavaScript processing takes advantage of JavaScript's Date object. The sample code is shown next.

This code specifies that a JavaScript Date should be retrieved (which will be the current date) and that month, date of month, and full year should be extracted from that instantiated Date. The output for this appears next.

The last example worked on an arbitrary JavaScript String but did not use any parameters. The next example demonstrates providing of parameters to this arbitrary JavaScript String processing as it demonstrates use of JavaScript's pow function. The code for this example is listed next.

The output from running this example is shown in the following screen snapshot.

For my final example of this blog posting, I demonstrate the standard toString() output of the ScriptException declared in some of the previous examples. The ScriptEngine.eval method throws this checked exception if there is an error in executing/evaluating the provided script. This method also throws a NullPointerException if the provided String is null. The code used to force a script error is shown next.

This code provides a nonsensical script (in terms of JavaScript syntax), but that is exactly what is needed to demonstrate the ScriptException.toString(), which is called as part of the exception handling in the method shown above for handling an arbitrary JavaScript String. When the code is executed, we see the exception information as shown in the next image.

The portion of the output that comes from ScriptException.toString() is the portion that states: "javax.script.ScriptException: sun.org.mozilla.javascript.internal.EvaluatorException: missing ; before statement (<Unknown source>#1) in <Unknown source> at line number 1."

The ScriptException contains the file name, line number, and column number of the exception, which is especially helpful if a file with JavaScript code is provided for evaluation.

Conclusion

Java SE 6 makes it simple to use JavaScript within Java code. Other scripting engines can also be associated with Java, but it is handy to have one provided out-of-the-box with Mozilla Rhino.

Complete Code and Output Screen Snapshot

For completeness, I am including the complete code listing in one place here and the resultant output after that.

/** * Process the passed-in JavaScript script that should include an assignment * to a variable with the name prescribed by the provided nameOfOutput and * may include parameters prescribed by inputParameters. * * @param javaScriptCodeToProcess The String containing JavaScript code to * be evaluated. This String is not checked for any type of validity and * might possibly lead to the throwing of a ScriptException, which would * be logged. * @param nameOfOutput The name of the output variable associated with the * provided JavaScript script. * @param inputParameters Optional map of parameter names to parameter values * that might be employed in the provided JavaScript script. This map * may be null if no input parameters are expected in the script. */ public static Object processArbitraryJavaScript( final String javaScriptCodeToProcess, final String nameOfOutput, final Map<String, Object> inputParameters) { Object result = null; final ScriptEngine engine = manager.getEngineByName("js"); try { if (inputParameters != null) { for (final Map.Entry<String,Object> parameter : inputParameters.entrySet()) { engine.put(parameter.getKey(), parameter.getValue()); } } engine.eval(javaScriptCodeToProcess); result = engine.get(nameOfOutput); } catch (ScriptException scriptException) { LOGGER.severe( "ScriptException encountered trying to write arbitrary JavaScript '" + javaScriptCodeToProcess + "': " + scriptException.toString()); } return result; }

Only with the Sun release of Java SE version 6 comes a version of Rhine JavaScript bundled. Other implementations of Java 6 may differ: for instance, the version of Java 6 supplied by Apple for Mac OS X does NOT include a JavaScript engine by default.

Thanks for pointing out that Rhino JavaScript engine is not necessarily delivered with all implementations of Java, though all implementations of Java do (or should) support the ability to associate scripting language engines with them via JSR 223 (Scripting for the Java Platform). I had intended to address this, but got caught up in the hurry to post the blog entry. It is beneficial to have this explicitly called out.