Tuesday, February 15, 2011

AntBuilder: Groovy Meets Ant

One of convenient classes that Groovy provides is the AntBuilder class, whose purpose is best described in the Javadoc-based documentation: "Allows Ant tasks to be used with GroovyMarkup." The AntBuilder class provides a strong association between the Groovy programming language and the well-known and ubiquitous Ant build tool.

The Ant Integration with Groovy section of the Groovy User Guide describes three common situations in which Ant ("the predominant build environment for Java projects") and Groovy ("a leading Scripting language for the JVM") are used in conjunction with one another. The first listed approach is when developers supplement their Java-building Ant build.xml scripts with Groovy using the specific to Groovy <groovy> tag or the general to Ant <script> tag and embed Groovy code inside these tag's bodies.

The second listed approach for integrating Groovy and Ant is to use Ant to compile Groovy code. The groovyc Ant task (<groovyc>) can be used to build Groovy code with the groovyc joint compiler. Although it can be desirable to run Groovy scripts and other code without explicit compilation, there are several advantages to compiling Groovy with groovyc and this groovyc Ant task makes this even easier to do.

The third listed approach for integrating Groovy and Ant is use of AntBuilder and is the focus of this blog post. Groovy's DSL capabilities are leveraged by AntBuilder to provide a "fluent" Groovy markup representation of the Ant tasks most of us are familiar with from Ant XML files.

There are advantages to being able to apply one's Ant knowledge from within Groovy code. First, many of us have used Ant for years and have anywhere from a passing familiarity to deep knowledge of the available Ant tasks. This is an advantage both in terms of developing the Groovy script code and in terms of others reading and maintaining the code. Even a relatively inexperienced Groovy developer who has some Ant knowledge will benefit when using AntBuilder in Groovy scripts.

The second advantage of being able to invoke Ant functionality easily from Groovy code is that Ant has gained significant functionality over its (relatively) long existence. Ant is no longer used explicitly for simple compilation and JAR/WAR/EAR assembly. It has functionality for file copying and naming, using Java mail, and a wide breadth of other functionality. All of Ant's richness is available to the Groovy developer via AntBuilder. In some cases, it may be easier (more concise) for the Groovy developer to use Ant's predefined task functionality than to write the equivalent functionality in Groovy.

Even when the task is to build Java projects, a developer may choose to perform the build via Groovy scripts rather than via Ant and its XML directly. That is another example of an advantage offered by Groovy+Ant integration encapsulated in AntBuilder. The developer can write Groovy scripts with conditionals, loops, and other full language constructs while still taking advantage of Ant's deep and broad building functionality. In other words, with AntBuilder, the developer can benefit from all of Ant's features without using XML.

AntBuilder makes use of and is illustrative of Groovy's metaobject facilities and the ability to build Domain Specific Languages (DSLs) in Groovy. Martin Fowler describes what a domain specific language is: "a computer language that's targeted to a particular kind of problem, rather than a general purpose language that's aimed at any kind of software problem." I have illustrated another Groovy DSL (MarkupBuilder) in the post GroovySql and MarkupBuilder: SQL-to-XML. Like MarkupBuilder, AntBuilder extends the abstract base class BuilderSupport (as do SwingBuilder and NodeBuilder).

The Groovy builders are particularly well suited for generating hierarchical data output. Therefore, it's no surprise that builders are useful for building markup (XML and HTML for example) and even for helping with Swing. See How Builders Work for a basic introduction into using Groovy builders and Practical Groovy: Mark it up with Groovy Builders for greater details.

AntBuilder makes it easy to harness the functionality of Ant from within Groovy code. I'll use of an example of using AntBuilder to begin my explanation of how it works and is used. Ant's support for file operations has been proven perhaps millions of times by now. The next code listing demonstrates Groovy code that uses AntBuilder to take advantage of Ant's file copying functionality. AntBuilder is not the only way to copy files in Groovy and two other ways (copying delegated to Windows/DOS operating system and copying using Groovy GDK) are also included in the example.

demoGroovyFileCopying.groovy
#!/usr/bin/env groovy

/*
 * demoGroovyFileCopying.groovy
 *
 * Demonstrates copying files in Groovy with three different mechanisms:
 *   1. AntBuilder
 *   2. Operating System copying with String.execute()
 *   3. Groovy File.write(File.text)
 */
 
 // AntBuilder instance used throughout this script
 AntBuilder antBuilder = new AntBuilder()
 
 demonstrateFileCopying(args[0], "target", antBuilder)
 
 /**
  * Demonstrate file copying in Groovy. Generated target files will be copies of
  * the file represented by the provided sourceFilePathAndName and the generated
  * files have names that start with the targetFileNameBase and have the type of
  * Groovy copying appended to the base in the file name.
  *
  * @param sourceFilePathAndName Path and file name of source file to be copied.
  * @param targetFileNameBase Base portion of name of all target files created
  *    as copies of the source file.
  */
 def void demonstrateFileCopying(
    final String sourceFilePathAndName,
    final String targetFileNameBase,
    final AntBuilder ant)
 {
    // File copying with AntBuilder
    def targetFileFromAntCopyName = targetFileNameBase + "_ant"
    def targetFileFromAntCopy = ant.copy(file: sourceFilePathAndName,
                                         tofile: targetFileFromAntCopyName)

    // File copying with Windows/DOS operating system and GDK String.execute()
    //   (use 'copy' in DOS and need 'cmd /c')
    "cmd /c copy ${sourceFilePathAndName} ${targetFileNameBase+'_dos'}".execute()

    // File copying with Groovy GDK File functionality
    def sourceFile = new File(sourceFilePathAndName)
    new File(targetFileNameBase + '_groovy').write(sourceFile.text)
 }

The next screen snapshot demonstrates the directory before and after this script is executed and includes the output showing the Ant functionality for file copy being invoked.


It could be argued that for file copying, using the Groovy approach with the GDK File class is the easiest approach. However, a Groovy developer not familiar with the Groovy approach might be more comfortable with the AntBuilder approach. Either approach is obviously preferable in most cases to the operating system dependent approach.

The AntBuilder approach might be easier if specialized functionality related to the copying needed to be performed that is supported by the Ant copy task but might not be so readily applied directly in Groovy. Suppose that I had a file that I not only wanted to copy, but wanted to replace tokens inside the source file with something else in the copied target file. Ant shines here and therefore so does AntBuilder. To illustrate this, I use a simple source file as shown in the next listing.

tokenSource.txt
Hello, my name is @NAME@ and I like Ant and Groovy.

With the above source file in place with a token delimited with the @ character, AntBuilder is again demonstrated in the following example.

copyFileAndReplaceNameToken.groovy
#!/usr/bin/env groovy

/*
 * demoGroovyFileCopyingAndTokenReplacing.groovy
 *
 * Demonstrates copying file with Groovy and AntBuilder and replacing @NAME@
 * token in source file in the copied file.
 */
 
// AntBuilder instance used throughout this script
def ant = new AntBuilder()
def sourceFilePathAndName = args[0]
def targetFileFromAntCopyName =  "target_antToken"
def targetFileFromAntCopy =
   ant.copy(file: sourceFilePathAndName,
            tofile: targetFileFromAntCopyName)
            {
               filterset()
               {
                  filter(token: "NAME", value: "Dustin")
               }
            }

The above Groovy code uses AntBuilder to not only copy the file, but to also replace the token @NAME@ with "Dustin". This output is shown in the next screen snapshot.


As the last example shows, AntBuilder makes it easy to leverage Ant's wide set of functionality from Groovy without a hint of XML. A developer familiar with Ant's XML structure is likely to be able to map Ant XML to the Groovy markup. Builders work so well because they rely on conventions and AntBuilder is no different. The convention is that XML element names are nested within other XML element names by placing them within curly braces of the outer elements. Attributes in the XML map to name/value pairs separated by colons within the parentheses containing arguments to the names associated with XML elements. In other words, the above Groovy script for copying the file with the filter is equivalent to the XML below in an Ant build script (where "args[0]" corresponds to the String passed in as the first argument to the Groovy script).

  <copy file="args[0]" tofile="target_antToken">
    <filterset>
      <filter token="NAME" value="Dustin"/>
    </filterset>
  </copy>

It is relatively easy to see the mapping of the above XML to the Groovy markup the way I indented it. Of course, I could have chosen to use less whitespace to make the code appear even more concise, but at the arguable cost of being less readable.

To apply core Ant tasks (such as copy used above) within Groovy using AntBuilder, nothing has to be placed on the script's classpath because Ant is included in the Groovy distribution. However, any optional Ant tasks that have external library dependencies require those external libraries to be placed on the classpath.

Conclusion

AntBuilder provides powerful functionality for the Groovy developer. While it can certainly be used in building code, its providing of Ant's breadth and depth of features can be useful in all sorts of contexts in which Groovy might be used besides simply builds. Appropriate use of AntBuilder can reduce the necessary Groovy code required for certain operations when Ant already does the heavy lifting. AntBuilder can also serve as a crutch to the developer who knows Ant better than Groovy but wants to write Groovy scripts.

No comments: