JavaScript in Java
JavaScript in Java
COMMENTS
The recent JavaLobby post The Top 10 Unused Features in Javahas 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 aboutor 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 javax.script package was introduced with Java SE 6 and includes classes, interfaces, and a checked exception related to use of scripting engines within Java. This blog posting will focus on ScriptEngineFactory, ScriptEngineManager, ScriptEngine, and ScriptException.
One of the first things one might want to do is to determine which scripting engines are already available. The next snippet of code shows how easy this is to do with Java SE 6.
final ScriptEngineManager manager = new ScriptEngineManager();
for (final ScriptEngineFactory scriptEngine : manager.getEngineFactories())
{
System.out.println(
scriptEngine.getEngineName() + " ("
+ scriptEngine.getEngineVersion() + ")" );
System.out.println(
"\tLanguage: " + scriptEngine.getLanguageName() + "("
+ scriptEngine.getLanguageVersion() + ")" );
System.out.println("\tCommon Names/Aliases: ");
for (final String engineAlias : scriptEngine.getNames())
{
System.out.println(engineAlias + " ");
}
}
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.
/**
* Write number in exponential form.
*
* @param numberToWriteInExponentialForm The number to be represented in
* exponential form.
* @param numberDecimalPlaces The number of decimal places to be used in the
* exponential representation.
*/
public static void writeNumberAsExponential(
final Number numberToWriteInExponentialForm,
final int numberDecimalPlaces)
{
final ScriptEngine engine = manager.getEngineByName("js");
try
{
engine.put("inputNumber", numberToWriteInExponentialForm);
engine.put("decimalPlaces", numberDecimalPlaces);
engine.eval("var outputNumber = inputNumber.toExponential(decimalPlaces);");
final String exponentialNumber = (String) engine.get("outputNumber");
System.out.println("Number: " + exponentialNumber);
}
catch (ScriptException scriptException)
{
LOGGER.severe(
"ScriptException encountered trying to write exponential: "
+ scriptException.toString());
}
}
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.final int sourceNumber = 675456;
writeNumberAsExponential(sourceNumber, 1, System.out);
writeNumberAsExponential(sourceNumber, 2, System.out);
writeNumberAsExponential(sourceNumber, 3, System.out);
writeNumberAsExponential(sourceNumber, 4, System.out);
writeNumberAsExponential(sourceNumber, 5, System.out);
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.
System.out.println(
"Today's Date: "
+ processArbitraryJavaScript(
"var date = new Date(); var month = (date.getMonth()+1).toFixed(0)",
"month",
null) + "/"
+ processArbitraryJavaScript(
"var date = new Date(); var day = date.getDate().toFixed(0)",
"day",
null) + "/"
+ processArbitraryJavaScript(
"var date = new Date(); var year = date.getFullYear().toFixed(0)",
"year",
null) );
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.
final Map<String, Object> exponentParameters = new HashMap<String, Object>();
exponentParameters.put("base", 2);
exponentParameters.put("exponent", 5);
System.out.println(
"2 to the 5 is: "
+ processArbitraryJavaScript(
"var answer = Math.pow(base,exponent)",
"answer",
exponentParameters) );
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. /**
* Intentionally cause script handling error to show the type of information
* that a ScriptException includes.
*/
public static void testScriptExceptionHandling()
{
System.out.println(processArbitraryJavaScript("Garbage In", "none", null));
}
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.
JavaScriptInJavaExample.java