Combining Groovy and Java
Everybody refactors (I hope). But what if your standard refactoring just isn’t good enough? Take the next step in refactoring into Groovy code and see how easy it is to integrate Groovy into your existing Java projects.
The challenge
While working on a project lately, I encountered (something like) the following code:
// The result list ArrayList result = new ArrayList(); // First, we create a document based on a URL URL url = new URL(configLocation); URLConnection connection = url.getConnection(); InputStream in = connection.getInputStream(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(inputStream); // .. then, do some XPath queries XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpath = xPathFactory.newXPath(); NodeList nodeList = (NodeList) xpath.evaluate("/user/address", document, XPathConstants.NODESET); // And loop through the results for (int i = 0; i < nodeList.getLength(); i++) { final Node node = nodeList.item(i); XPath xpath = XPathFactory.newInstance().newXPath(); // Read parameters from XML file using XPath String name = (String) xpath.evaluate("name", node, XPathConstants.STRING); result.add(name); }
First, this code tries to read from a URL to retrieve an XML file. Then it tries to execute some XPath queries to retrieve a NodeList, and for each item in the NodeList it adds an item to a result List. I omitted the error handling, because that would make the code example even longer.
As you might agree, the above code is quite hard to read, and gives a lot of overhead in trying to accomplish the task at hand.
While this code could probably be optimized and shorted considerably, I thought this code was ugly enough to be replaced completely. I considered my options, and I decided it would be a nice exercise for using a bit of Groovy to do the XML parsing for me, and replace the current Java code by Groovy code while maintaining the same level of functionality.
For those totally unfamiliar with Groovy, I suggest checking the Groovy website. It contains a lot of documentation and code examples. In short, Groovy is a Dynamic Scripting Language. It’s not a port, like JRuby, but has been specifically designed for working with Java.
To replace the code, I had to make sure it would:
- Provide the same level of functionality afterward
- Have less overhead
- Would integrate into our build system (Maven 2)
The solution
So, let’s begin at point 1. We need a bit of Groovy code to load a url, parse some XML, and build some Java objects with it. The following code does just that:
package groovy import com.xebia.domain.Address import com.xebia.domain.AddressDownloader class GroovyAddressDownloader implements AddressDownloader { // implement the method just like in java public List download() { // define the location of the source url def url = "https://www.foobar.com/address.xml" // use the automatically imported groovy.util.XmlParser // to parse the source def xmlFeed = new XmlParser().parse(url); // create a new list def list = [] // loop through all the xml elements using dynamic methods xmlFeed.address.each {item -> // retrieve the content of the root/address/name element def value = item.name.text() // create a new address using a dynamic constructor // and add it to the list list << new Address(name: value); } return list } }
As you can see, even with the above example consisting of 50% comments, it’s still shorter and more complete than the Java version. The Groovy code is very consistent and provides almost no overhead in parsing the XML. The above example uses quite some Groovy magic, like closures, dynamic methods, shorthand method, etc.
Getting it to work
When I type the following in my favorite IDE:
import groovy.GroovyAddressDownloader; AddressDownloader addressDownloader = new GroovyAddressDownloader(); List addresses = addressDownloader.download();
And press CTRL+SHIFT+F10 (Run), it works! IntelliJ 7.0 supports Groovy (and Grails) out of the box, and does a very good job at providing a lot of the functionality Java developers are used to these days (syntax highlighting, code completion, refactoring, etc).
Integrating it with Maven
But we’re not there yet! We don’t use IntelliJ as our build, but we use Maven 2. Fortunately for us, a the beginning of December a new version of the Groovy Maven Plugin was released. This plugin makes it easy to compile Java, Groovy or a combination of both in one project. All we need to do for this is edit our Maven pom.xml to include the Groovy plugin, and change the settings.xml to include the codehaus repository.
The following lines of XML need to be added to the pom.xml:
org.codehaus.mojo.groovy groovy-maven-plugin generateStubs compile
A small explanation from the Groovy Maven Plugin:
It works by adding an extra goal execution to generate stubs during the generate-sources and generate-test-sources phases. These stubs are created from your Groovy source files and translated into minimal Java sources (containing JavaDoc, classes, fields, methods, etc. but omitting the implementation details. This allows Maven’s maven-compiler-plugin to be invoked as normal to compile Java sources and the Groovy Java stubs, thus allowing the compilation to resolve classes. After the normal Java compile, the Groovy compiler kicks in and then re-generates the real classes into places and everything is happy happy. In short: this makes sure your Java code can use the Groovy code, and vice verse.
After adding the final lines of XML to our settings (found in your home directory/.m2):
codehaus Codehaus https://repository.codehaus.org/ false true Codehaus https://repository.codehaus.org/ false true Codehaus
You should be all set to go. Running mvn install
should compile both your Java as well as your Groovy classes, and if everything went allright, you should see something like this in the Maven output:
[INFO] [groovy:generateStubs {execution: default}]
[INFO] Generated 1 Java stub
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to D:\Projects\GroovyDemot\target\classes
[INFO] [groovy:compile {execution: default}]
[INFO] Compiled 2 Groovy source files
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
Conclusion
It isn’t hard to get Groovy working in combination with your existing Java project. So download Groovy, read the docs and have a go at it!