Thursday, May 21, 2009

Groovy in Java with JSR 223

I previously blogged on using JavaScript in Java. The same Java Specification Request that allows for JavaScript in Java, JSR 223 ("Scripting for the Java Platform"), allows for including other scripting languages in Java. In this blog posting, I'll briefly look at embedding Groovy code in a Java application via JSR 223 support.

Sun's Java SE 6 includes the Rhino JavaScript engine, but does not include Groovy by default. Fortunately, Groovy 1.6 (I'm using 1.6.2) provides built-in JSR 223 support that makes it really easy to embed Groovy within Java. For example, if my Java application with embedded Groovy is called GroovyInJavaExample, is a class located in the dustin.examples package, and is built into a JAR named ScriptInJava.jar and if Groovy is installed in C:\Program Files\Groovy\Groovy-1.6.2\, I can run the Java+Groovy code as follows:


java -cp dist\ScriptInJava.jar;"C:\Program Files\Groovy\Groovy-1.6.2\lib\*" dustin.examples.GroovyInJavaExample


By including the necessary JARs from the Groovy distribution, the JSR 223 support for Groovy in Java is automatically available.

The code listing for GroovyInJavaExample.java is shown next. It is similar to the example I used in the JavaScript in Java blog example I used previously.


package dustin.examples;

import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

/**
* This example demonstrates using Groovy within Java SE 6.
*/
public class GroovyInJavaExample
{
private final static ScriptEngineManager manager = new ScriptEngineManager();

/** Using java.util.logging. */
private final static Logger LOGGER = Logger.getLogger(
GroovyInJavaExample.class.getName());

private final static String NEW_LINE = System.getProperty("line.separator");

private final static String HEADER_SEPARATOR =
"=======================================================================";

/**
* Write the key information about the provided Script Engine Factories to
* the provided OutputStream.
*
* @param scriptEngineFactories Script Engine Factories for which key data
* should be written to the OutputStream.
* @param out OutputStream to which to write Script Engine Factory details.
*/
public static void writeScriptEngineFactoriesToOutputStream(
final List<ScriptEngineFactory> scriptEngineFactories,
final OutputStream out)
{
printHeader("Available Script Engines", out);
try
{
out.write(NEW_LINE.getBytes());
for (final ScriptEngineFactory scriptEngine : scriptEngineFactories)
{
out.write(
( scriptEngine.getEngineName() + " ("
+ scriptEngine.getEngineVersion() + ")" + NEW_LINE).getBytes());
out.write(
( "\tLanguage: " + scriptEngine.getLanguageName() + "("
+ scriptEngine.getLanguageVersion() + ")" + NEW_LINE).getBytes());
out.write("\tCommon Names/Aliases: ".getBytes());
for (final String engineAlias : scriptEngine.getNames())
{
out.write((engineAlias + " ").getBytes());
}
out.write(NEW_LINE.getBytes());
}
out.write(NEW_LINE.getBytes());
}
catch (IOException ioEx)
{
LOGGER.severe(
"Could not write to provided OutputStream: "
+ ioEx.toString());
}
}

/**
* Show availability of scripting engines supported in this environment.
*/
public static void testSupportedScriptingEngines()
{
writeScriptEngineFactoriesToOutputStream(
manager.getEngineFactories(), System.out);
}

/**
* Process the passed-in Groovy 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 groovyCodeToProcess The String containing Groovy 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 Groovy script.
* @param inputParameters Optional map of parameter names to parameter values
* that might be employed in the provided Groovy script. This map may be
* null if no input parameters are expected in the script.
*/
public static Object processArbitraryGroovy(
final String groovyCodeToProcess,
final String nameOfOutput,
final Map<String, Object> inputParameters)
{
Object result = null;
final ScriptEngine engine = manager.getEngineByName("groovy");
try
{
if (inputParameters != null)
{
for (final Map.Entry<String,Object> parameter :
inputParameters.entrySet())
{
engine.put(parameter.getKey(), parameter.getValue());
}
}
engine.eval(groovyCodeToProcess);
result = engine.get(nameOfOutput);
}
catch (ScriptException scriptException)
{
LOGGER.severe(
"ScriptException encountered trying to write arbitrary Groovy '"
+ groovyCodeToProcess + "': "
+ scriptException.toString());
}
return result;
}

/**
* Write passed-in headerMessage text to provided OutputStream using clear
* header demarcation.
*
* @param headerMessage Text to be written to header.
* @param out OutputStream to which header should be written.
*/
private static void printHeader(
final String headerMessage, final OutputStream out)
{
try
{
out.write((NEW_LINE + HEADER_SEPARATOR + NEW_LINE).getBytes());
out.write((headerMessage + NEW_LINE).getBytes());
out.write((HEADER_SEPARATOR + NEW_LINE).getBytes());
}
catch (IOException ioEx)
{
LOGGER.warning(
"Not able to write header with text '"
+ headerMessage
+ " out to provided OutputStream: " + ioEx.toString());
System.out.println(HEADER_SEPARATOR);
System.out.println(headerMessage);
System.out.println(HEADER_SEPARATOR);
}
}

/**
* Demonstrate execution of an arbitrary JavaScript script within Java that
* does NOT include parameters.
*/
public static void testArbitraryJavaScriptStringEvaluationWithoutParameters()
{
printHeader(
"Display x character consecutively repeated (no parameters).", System.out);
System.out.println(
processArbitraryGroovy("newString = 'x'.multiply(50)", "newString", null));
}

/**
* Demonstrate execution of an arbritrary JavaScript script within Java
* that includes parameters.
*/
public static void testArbitraryJavaScriptStringEvaluationWithParameters()
{
printHeader(
"Display y character consecutively repeated (parameters)",
System.out);
final Map<String, Object> characterRepetitionParameters =
new HashMap<String, Object>();
characterRepetitionParameters.put("character", "y");
characterRepetitionParameters.put("number", 50);
System.out.println(
"New character string is: "
+ processArbitraryGroovy(
"newString = character.multiply(number)",
"newString",
characterRepetitionParameters)
+ NEW_LINE);
}

/**
* Intentionally cause script handling error to show the type of information
* that a ScriptException includes.
*/
public static void testScriptExceptionHandling()
{
printHeader(
"Intentional Script Error to Demonstrate ScriptException", System.out);
System.out.println(
NEW_LINE + processArbitraryGroovy("Garbage In", "none", null));
}

/**
* Main executable for demonstrating running of script code within Java.
*/
public static void main(final String[] arguments)
{
testSupportedScriptingEngines();
testArbitraryJavaScriptStringEvaluationWithoutParameters();
testArbitraryJavaScriptStringEvaluationWithParameters();
testScriptExceptionHandling();
}
}


When the above code is run, the output is shown in the next screen snapshot.



From this output, we see that the Groovy Scripting Engine is available in addition to the Rhino JavaScript Engine that was available by default with the Sun SDK. We can also see that the two aliases for getting a handle on this scripting engine from within Java code are "groovy" and "Groovy".

The output shown above also shows that using Groovy's .multiply operator method on a String literal works just fine as the "x" and "y" characters are repeated the provided number of times. The examples show how to call arbitrary Groovy code without and with parameters. The final example in the above output also shows the ScriptException encountered when a bad piece of Groovy code is provided to the engine.

Conclusion

Although Groovy is not provided out-of-the-box with the Sun SDK like the Rhino JavaScript engine is, this blog posting has attempted to show that Groovy's built-in support for JSR 223 makes it easy to embed Groovy with Java in a JSR 223 compliant syntax. Because Groovy really is Java, there are other ways to integrate the two, but it is nice to know that JSR 223 syntax can be used with Groovy easily and similarly to JavaScript in Java. Consistency is often a highly desirable goal.

No comments: