O'Reilly logo

Java Power Tools by John Ferguson Smart

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. Setting Up a Project Using Ant

Ant in the Build Process

Ant is a popular and widely used open source build tool, written in Java. Indeed, it is probably the most widely used build tool in the Java world. It is supported by virtually all modern IDEs, and (being a Java application) runs on almost any Java-friendly platform.

In a nutshell, Ant helps you transform the source code, libraries, and other files that make up your project into a deliverable software package. And, more importantly, it helps you do this in an orderly, repeatable manner. If you design your build script well, you can also ensure that it will behave the same way on any machine. This leads the way to automatic builds, which can be run on a remote machine with little or no human intervention. In Part III, we discuss how to use Continuous Integration tools to take this process even further.

Ant is a highly flexible tool—you can make it do pretty much anything you want. However, this flexibility can come at a cost of complexity. Good Ant build scripts are written in a way that will be easy to read and maintain in a year’s time. In this chapter, I will try to show some best practices that should help make your scripts clean and readable.

Installing Ant

At the time of this writing, there were no graphical installers available, although Ant is now provided as a standard package for many Unix distributions. Nevertheless, you will often get a more complete installation if you download and install Ant manually.

Ant should run correctly on any Java-friendly platform. Ant is a pure Java tool, so it does require a (preferably recent) JDK installation to work.[*]

So, as a prerequisite for installation, you should check that your machine has a correctly installed JVM:

$ java -version
java version "1.6.0"
Java(TM) SE Runtime Environment (build 1.6.0-b105)
Java HotSpot(TM) Server VM (build 1.6.0-b105, mixed mode)

The following two sections will discuss how to install Ant on Unix and Windows environments.

Installing Ant on a Unix Machine

Download the latest stable binary release of Ant from the Ant web site,[2] and extract it into an appropriate directory. On my machine, I installed it into the /usr/share directory. I also created a convenient symbolic link to make later updates easier:

# cd /usr/share
# tar xvfz apache-ant-1.7.0-bin.tar.gz ant-1.7.0
# ln -s ant-1.7.0 Ant

Now, you should have a full Ant distribution installed on your machine:

# ls -al /usr/share/ant
lrwxrwxrwx 1 root root 9 2007-08-04 22:36 Ant -> ant-1.7.0
# ls /usr/share/ant
bin   fetch.xml   KEYS     LICENSE.dom     NOTICE
docs  get-m2.xml  lib      LICENSE.sax     README
etc   INSTALL     LICENSE  LICENSE.xerces  WHATSNEW

Once this is done, you need to set up some environment variables. Ant requires two to function. First, set the ANT_HOME variable to the directory where you have installed Ant. You also need to ensure that the JAVA_HOME variable is correctly defined, as the Ant startup script uses both of these variables. This usually goes in one of your environment initialization scripts (for example, if you are using Bash, you could place this configuration in the ~/.bashrc file if you just need to set it up for your account, or in /etc/bashrc if you want to set it up for all users on this machine):

ANT_HOME=/usr/share/ant
JAVA_HOME=/usr/lib/jvm/java
PATH=$PATH:$ANT_HOME/bin:$JAVA_HOME/bin
export PATH ANT_HOME JAVA_HOME

Now you should be able to run Ant from the command line. Make sure it works by running the following command:

$ ant -version
Apache Ant version 1.7.0 compiled on July 11 2007

Installing Ant on Windows

Installing Ant on Windows is, like for Unix, a largely manual process. Download the binary distribution from the Ant web site and unzip it to a convenient place on your disk. It is usually safer to install Ant into a short directory name, without spaces, such as C:\ant. Although in Windows you would usually do this using one of the many Windows graphical compression utilities, you can also do this using the Java jar tool if you need to, as shown in the following example:

> mkdir C:\tools
> cd C:\tools
> jar xvf apache-ant-1.7.0-bin.zip
inflated: apache-ant-1.7.0/bin/ant
inflated: apache-ant-1.7.0/bin/antRun
...

This will extract the Ant installation into a directory called C:\tools\apache-ant-1.7.0-bin. You may want to rename this to something a little more manageable, such as C:\tools\ant.

The next step involves adding the Ant bin directory (in this case, C:\tools\ant\bin) to your system path. In Windows, you do this in the “System Properties” screen, which is accessed from the control panel. From here, go to the “Advanced” tab and click “Environment Variables” (see Installing Maven on a Windows Machine” in Installing Maven). Now just add your Ant bin directory to your system PATH variable, separated from the other values by a semicolon.

You also need to add the ANT_HOME environment variable (C:\tools\ant in this case) using the “New” button in the Environmen Variables screen. You also need to ensure that the JAVA_HOME environment variable is correctly set to the directory where your JDK is installed. Ant needs both of these variables to work correctly.

Once this is done, you should be able to run Ant from the command line:

> Ant -version
Apache Ant version 1.7.0 compiled on July 11 2007

ANT_OPTS and ANT_ARGS: Some Other Useful Environment Variables

In addition to JAVA_HOME and ANT_HOME, there are two other variables used by Ant that you may occasionally find useful: ANT_OPTS and ANT_ARGS.

As with any other Java application, when you run Ant it is sometimes nice to be able to tinker with the various JVM options. The ANT_OPTS variable lets you do just that, allowing you to fine-tune memory requirements or define proxy configurations. Although Ant itself is not particularly demanding in terms of memory, some third-party tasks such as code coverage tools may be, and you may need to increase the available heap size as shown here:

export ANT_OPTS=-Xmx512M

In many organizations, you will need to access the Internet via a proxy. Another common use of the ANT_OPTS variable is to define proxy configuration details for tasks that need to access the Internet. You can do this using the http.proxyHost and http.proxyPort variables:

export ANT_OPTS="-Dhttp.proxyHost=proxy.mycompany.com -Dhttp.proxyPort=8080"

The second variable, ANT_ARGS, lets you define command-line arguments that will be passed to Ant. For example, the following configuration (attributed to Erik Hatcher in a blog entry by Matt Raible) gives color-coded output when you run Ant (green for success, red for failures, blue for log messages, and so on):

export ANT_ARGS=-logger org.apache.tools.ant.listener.AnsiColorLogger

A Gentle Introduction to Ant

Now that you have installed Ant, we can take it through its paces. Ant is an extremely powerful tool, and experienced users can really make it sing. We will start off simply, going through how to set up a small Java project with Ant.

Basic Ant Concepts

Ant build files are written in XML, using a quite readable set of tags. The overall structure of an Ant file is relatively straightforward and intuitive. An Ant build file describes how to build one (and only one) project. A build project is made up of targets and tasks.

A target is a goal that you want your build process to achieve. Such a goal might be to compile your application code, run your tests, or prepare a production-ready release package. You can (and usually do) define several targets in your build file, which you may run on different occasions.

A target can also depend on other targets. This lets you ensure that certain targets are always executed before others. For example, you might want to ensure that your unit test’s target is always run before you prepare a new release. You do this by declaring dependencies between your targets.

A task lets you define a piece of work you want done. This might be compiling some Java source code using javac, running a set of unit tests using JUnit, or generating a production-ready JAR file using the jar utility. This is where the work actually gets done. The task is the workhorse of the Ant build process and the source of a lot of its power and flexibility. Behind the scenes, Ant tasks are actually implemented as Java classes, which makes it fairly easy to extend Ant by writing new tasks. This is one of the reasons that the majority of Java tools, both open source and commercial, provide an Ant task library. Indeed, Ant comes with a rich library of built-in (or “core”) and optional tasks. Core tasks are built into Ant, and need no special configuration. Optional tasks are maintained by the Ant team and delivered with Ant, but they rely on an external library. For example, the junit task, which runs JUnit test cases, is an optional task because it requires the JUnit library to be supplied separately. Ant 1.7.0 is delivered with almost 100 core tasks and around 50 optional ones.

There is also a third type of task, referred to as “third-party” tasks. These are written and maintained outside of the Ant project, and they often provide support for other Java or non-Java tools. For example, Subversion support in Ant (see Using Subversion in Ant) is provided by Tigris, the team that maintains Subversion. The number of third-party tasks is countless.

Finally, Ant allows you to define and use a set of properties. In Ant, a property can be used as a way to make your build script simpler and easier to understand, and also to access system-level properties such as user.dir (the current working directory) or os.name (the name of the operating system). We look at this in some detail in Customizing Your Build Script Using Properties.

A Simple Ant Build File

Let’s look at a simple Ant build file. This build file will be designed to help us build a small, very simple Java project. We will build a library designed to calculate various types of taxes. This project is designed to provide an API for other projects, in the form of a JAR file called tax-calculator.jar.

Ant imposes no particular directory structure for your projects: instead, you need to define your own. Depending on your viewpoint, this can be seen as a strength or a weakness. On the positive side, it gives you the maximum degree of flexibility in structuring your project. On the other hand, the lack of a standard directory structure may make the learning curve a little harder when you switch from one project to another.

Having said that Ant requires no particular structure, there are some practices that are more common than others. In this project, we will use a directory structure that is frequently seen in Ant-based projects (though details may vary). This directory structure places the application source code in a directory called src, and it places compiled classes in a directory called build. Production classes and unit tests are placed in separate, parallel directory structures (src and test, respectively). Compiled production classes are stored in the build/classes directory, whereas compiled unit tests are placed in build/test-classes.

Finally, any libraries required by the application are stored in a directory called lib.

This directory layout is summarized in Table 1-1.

Table 1-1. Directory structure for our project
DirectoryContents
srcApplication source code
testUnit test code
libProject dependencies
buildAny files generated by the build process
build/classesCompiled Java classes
build/test-classesCompiled unit tests
distDistribution files, such as bundled JAR or WAR files

Having a clean, well-defined directory structure is important for any project, and is particularly important if you are using a tool like Ant. A clear directory layout makes the build script easier to write and understand. Separating compiled classes from source code, for example, makes it easier to do a clean recompile simply by deleting the target directory, and makes it easier to place source code (and only source code) under version control.

Using a lib directory to store your dependencies is one commonly used approach in Ant projects, though it is not the only solution to this problem. If the libraries are simply stored here with no indication of their version, it can be difficult to keep track of the exact list of libraries required by your project. To address this issue, many teams are starting to identify the version of each library using a Maven 2-like naming convention (see Project Context and Artifacts” in Declarative Builds and the Maven Project Object Model). For example, if you are using JUnit 4.4, the JAR in your lib directory would be called junit-4.4.jar. This makes it easier to know which versions of each library your application is currently using.

This is not the only way to store dependencies. Indeed, keeping them in the lib directory may mean that your JARs end up being stored in your version control system. This is not always ideal. Maven (see Chapter 2) uses a different approach, where JAR files are stored on a central repository, and dependencies are listed in the build file, instead of in one of the project directories. In Using Maven Dependencies in Ant with the Maven Tasks, we look at how to declare dependencies using Maven 2-style repositories.

Maven uses a similar standardized directory structure (see The Maven Directory Structure), which can also be used for Ant projects.

For our first example, we will be working with a simple one-class application that displays some information about the installed JVM and operating system. This remarkably useful piece of code is shown here:

package com.javapowertools.taxcalculator;

public class Main {

    public static void main(String[] args) {
        String jvm = System.getProperty("java.version");
        String osName = System.getProperty("os.name"); 
        String osVersion = System.getProperty("os.version"); 
        
        System.out.println("Running Java " + jvm 
                           + " on " + osName
                           + " (version " + osVersion + ")");
    }
}

Now let’s see how you could use Ant to build this application. At this stage, we just want to compile our code and bundle up a JAR file containing the compiled classes. You could do this with a build.xml file, along the following lines:

<?xml version="1.0" ?>
<project name="tax-calculator" default="package">

  <target name="init">
    <mkdir dir="build/classes" />                      
    <mkdir dir="dist" />                                       
  </target>

  <target name="compile" depends="init" description="Compile Java code">
    <javac srcdir="src" destdir="build/classes"/>
  </target>

  <target name="package" depends="compile" description="Generate JAR file">                                                
    <jar destfile="dist/tax-calculator.jar" basedir="build/classes"/>
  </target>

  <target name="clean" description="Deletes generated directories">
    <delete dir="build" />
    <delete dir="dist" />
  </target>

</project>

Let’s go through this build file, and see how it uses the basic concepts we discussed earlier. Like all Ant build scripts, the root element is called <project>. The main role of the <project> element is to act as a container for the other build elements such as properties, targets, and tasks. It also lets you define an (optional) default target. We will learn more about default targets later on.

Each target uses various built-in Ant tasks. The first thing we need to do in our build process is create any missing directories. You need to make sure these directories have been created before compiling your classes. So, the first target, called “init,” simply creates the build and dist directories using the mkdir task:

  <target name="init">
    <mkdir dir="build/classes" />                      
    <mkdir dir="dist" />                                       
  </target>

We don’t need to create the “build” directory before the “build/classes” directory because Ant will do this automatically for us.

Next we need to compile our classes. The “compile” target uses javac (or, more precisely, the javac Ant task) to compile the source code in the src directory and place the compiled classes in the build/classes directory:

  <target name="compile" depends="init" description="Compile Java code">
    <javac srcdir="src" destdir="build/classes"/>
  </target>

The “package” target creates a JAR file in the dist directory containing all of the compiled classes in the build/classes directory:

  <target name="package" depends="compile" description="Generate JAR file">                                                
    <jar destfile="dist/tax-calculator.jar" basedir="build/classes"/>
  </target>

Finally, the “clean” target simply deletes the generated directories using the delete task:

  <target name="clean" depends="init" description="Deletes generated directories">
    <delete dir="build" />
    <delete dir="dist" />
  </target>

Running Ant

We are now ready to run this build script. You can do so by running Ant in the root directory of your project. If you run Ant with no arguments, Ant will invoke the default target, which is “package” for this project:

$ ant
Buildfile: build.xml

init:
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/build/classes
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/dist

compile:
    [javac] Compiling 1 source file to /home/wakaleo/projects/jpt-sample-code/ant-demo
    /build/classes

package:
      [jar] Building jar: /home/wakaleo/projects/jpt-sample-code/ant-demo/dist
      /tax-calculator.jar

BUILD SUCCESSFUL
Total time: 1 second

Alternatively, you can specify the target you want to run, as shown here with the “clean” target:

$ ant clean
Buildfile: build.xml

init:

clean:
   [delete] Deleting directory /home/wakaleo/projects/jpt-sample-code/ant-demo/build
   [delete] Deleting directory /home/wakaleo/projects/jpt-sample-code/ant-demo/dist

BUILD SUCCESSFUL
Total time: 0 seconds

You can also run several targets at the same time, simply by listing them as arguments on the command line:

$ ant clean compile 
Buildfile: build.xml

clean:
   [delete] Deleting directory /home/wakaleo/projects/jpt-sample-code/ant-demo/build
   [delete] Deleting directory /home/wakaleo/projects/jpt-sample-code/ant-demo/dist

init:
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/build/classes
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/dist

compile:
    [javac] Compiling 1 source file to /home/wakaleo/projects/jpt-sample-code/ant-demo
    /build/classes

BUILD SUCCESSFUL
Total time: 2 seconds

By convention, Ant files are called build.xml, but you can specify a different build script using the -f option:

$ ant -f my-special-buildfile.xml

Finally, like many tools, you can activate a verbose mode (using the -v command-line option) to display more details about what Ant is doing. This can be useful when debugging your build scripts:

$ ant -v
Apache Ant version 1.7.0 compiled on July 11 2007
Buildfile: build.xml
Detected Java version: 1.6 in: /usr/lib/jvm/java-6-sun-1.6.0.00/jre
Detected OS: Linux
parsing buildfile /home/wakaleo/projects/java-power-tools/sample-code/ch01/ant-demo
/build.xml with URI = file:/home/wakaleo/projects/java-power-tools/sample-code/ch01
/ant-demo/build.xml
Project base dir set to: /home/wakaleo/projects/java-power-tools/sample-code/ch01
/ant-demo
...

Ant also accepts many other command-line options, which can be used to fine-tune the behavior of your build script. We will look at a few of these options later on in this chapter.

Dependencies Between Targets

Most Ant targets are not designed to be run in isolation. Before compiling the Java source code, we need to create the target directories. And we obviously need to compile the latest version of the source code before we can generate a new version of the JAR file. In other words, there is a precise order in which the targets need to be executed. Certain targets depend on others.

In Ant, you use the depends attribute to declare a target’s direct dependencies:

<target name="package" depends="compile" description="Generate JAR file">
<target name="compile" depends="init" description="Generate JAR file">

In this example, the “package” target depends on the “compile” target to compile the Java classes, which in turn depends on the “init” target to prepare the output directories. In fact, the “package” target also depends on “init,” but we don’t need to list “init” in the “package” target dependencies because it will automatically be called when the “compile” target is executed. These dependencies can be expressed as a dependency graph, as shown in Figure 1-1.

A dependency graph of Ant targets
Figure 1-1. A dependency graph of Ant targets

Some targets can have multiple dependencies. For example, in the following code the “test” target directly depends on both the “unit-test” and the “integration-test” targets.

<target name="test" depends="unit-test, integration-test" description=
"Generate JAR file">

Ant is fairly smart about how it handles dependencies. For example, if both the “unit-test” and the “integration-test” targets depend on the “compile” target, this target will only be executed once.

Documenting Your Project

Documenting build scripts is important, and Ant build files are no exception. Left unattended, they can quickly become a nightmare to understand, both for you and other developers later on.

In addition to traditional techniques such as XML comments, Ant comes with a few built-in features to help you document your build scripts. The description attribute lets you provide a short description for your targets, as shown here:

  <target name="compile" depends="init" description="Compile Java code">
    <javac srcdir="src" destdir="build/classes"/>
  </target>

It is a good idea to use the description attribute for the targets in your build script that are intended for external use. You can then use the -projecthelp command-line option to list the main build targets, complete with a brief description. With large build scripts (and Ant build scripts can become very large), this can be a valuable time-saver:

$ ant -projecthelp
Buildfile: build.xml

Main targets:

 compile  Compile Java code
 package  Generate JAR file
 clean    Deletes generated directories
Default target: package

Compiling Your Java Code in Ant

In Java development, one of the most fundamental things that any build script needs to do is compile your code. In Ant, the <javac> task provides a convenient one-stop shop for Java compilation.

Let’s look at a simple use of the <javac> task. In the example given above, we use a simple form of this task to compile the Java classes in the src/main directory, and place the compiled classes in the build/classes directory:

  <target name="compile" depends="init" description="Compile Java code">
    <javac srcdir="src" destdir="build/classes"/>
  </target>

This is equivalent to executing the javac tool as shown here:

  $ javac -nowarn -d build/classes src/main

Admittedly, you would rarely compile an application this simple in the real world. In most real applications, your application will require a set of libraries both to compile and to run. In Ant projects, these libraries are often stored (in the form of JAR files) in a directory called lib. When you compile your application, you need to tell Ant where to find these libraries.

Ant comes with some very powerful features to help you do this. Ant excels at defining and manipulating classpaths and other file path definitions. In Ant, these definitions are known as “path-like” structures, and they are used extensively in all but the most trivial build files. At its simplest, you can use the <path> tag to identify a particular library using the location attribute:

  <path id="junit.classpath" location="lib/junit.jar"/>

This path could also be defined in the same way using the path attribute. However, in addition to defining single JAR files or directories, the path attribute lets you use a more path-like construction using a combination of directory paths and a JAR file, as shown here:

  <path id="junit.classpath" path="build/classes:lib/junit.jar"/>

One of the nice things about the Ant path-handling features is that they are (like the underlying Java APIs) portable across different operating systems. Here, by convention, we use Unix-style paths containing forward slashes (“/”). For example, on a Windows machine, Ant will automatically translate the above path into “build\classes;lib\junit.jar.”

Although this certainly makes for more readable build files, the real advantages of Ant’s path-handling features come into play when you need to manipulate large sets of files. The following example creates a path definition including all the *.jar files in the lib directory:

  <path id="compile.classpath">
    <fileset dir="lib" includes="*.jar" />
  </path>

This sort of thing is exactly what you often need to define classpaths for real-world Java builds. You can use this classpath in the <javac> task using the classpathref attribute, as shown here:

  <target name="compile" depends="init" description="Compile Java code">
    <javac srcdir="src" 
           destdir="build/classes"
           classpathref="compile.classpath" />
  </target>

Alternatively, if you don’t think you will need this classpath elsewhere in your build file, you can embed the <classpath> element directly in the <javac> task:

  <target name="compile" depends="init" description="Compile Java code">
    <javac srcdir="src" destdir="build/classes">
      <classpath>
         <fileset dir="lib" includes="*.jar"/>
      </classpath>       
    </javac>
  </target>

These paths actually use a shorthand notation for convenience. You can also use embedded tags for a more readable notation, which is useful when many separate classpath elements are required. This is also convenient if you want to reuse paths defined elsewhere in the build file. The <pathelement> tag lets you define a classpath element using either a JAR file, a directory, or even a reference to another classpath element:

  <path id="test.classpath">
    <path refid="compile.classpath"/>
    <pathelement location="lib/junit.jar"/>
    <pathelement path="build/test-classes"/>
  </path>

You can also use path-like expressions within the <javac> task itself to tell Ant what classes to include or which ones should be excluded from the compilation process, using the includes and excludes attributes. For example, to compile only the classes in the com.mycompany.myapp.web package (and below), you could do this:

<javac srcdir="src" destdir="build/classes" includes="com/mycompany/myapp/web/**" />

One frequent requirement when compiling Java code is to compile for a specific Java version. For example, you may use Java 6 on your development machine, but need to deploy to an environment where only Java 1.4 is supported. You can ensure that your Java code is compatible with Java 1.4, and that none of the nice Java 5 syntactic sugar is used, by setting the source attribute to “1.4.” Similarly, you can tell Ant to generate byte-code compatible with a Java 1.4 JVM by specifying the target attribute as “1.4.”

<javac srcdir="src" destdir="build/classes" source="1.4" target="1.4" />

You may also need to indicate if you want the source code compiled with debug information. By default, this is turned off, but it can come in handy when testing and debugging your code. To turn this option on, set the debug attribute to “on” (or “true”):

<javac srcdir="src" destdir="build/classes" debug="on" />

Despite its name, the <javac> task is not tied to the standard Sun Java compiler, also called javac. In fact, fans of alternative compilers like Jikes and GCJ can use these compilers, or a number of other more exotic choices, by specifying the compiler attribute:

<javac srcdir="src" destdir="build/classes" compiler="jikes"/>

You can also define this value for the entire build file by setting the build.compiler property (see Customizing Your Build Script Using Properties).

As you can see, the <javac> task provides a powerful, flexible tool for Java compilation. Many of the techniques covered here are typical of many Ant tasks, and we will see more examples of these later on.

Customizing Your Build Script Using Properties

In development, people often speak of the DRY (Don’t Repeat Yourself) principle. This is a general principle aimed at making code more readable and easier to maintain by avoiding the duplication of data. In a Java application, for example, key values that are used in several places, or that may need to be modified easily, are stored as constants or as parameters in a configuration file. This makes the code easier to read and understand, and makes maintenance less of a headache.

In Ant, you can do the same sort of thing by using the <property> tag, which lets you declare constant values that can be used throughout the rest of your build file. Declaring a property is straightforward; you just need to provide a name and value attribute, as shown here:

<property name="javac.debug" value="on"/>

If you are referring to a directory, you can use the location attribute instead of value:

<property name="build.dir" location="build"/>

This property value will be set to the absolute filename of the build directory.

To use a property elsewhere in the build file, you just quote the name of the property surrounded by ${...}. For example, you can subsequently refer to this property anywhere in your build file as ${build.dir}. You could use the <echo> task to display the property value to the screen, as shown here:

    <target name="display-properties">
        <echo>Debug = ${javac.debug}</echo>
        <echo>Build directory (build.dir) = ${build.dir}</echo>
    </target>

Calling this target would display the value of this property on the console. On my machine, it displays the following:

$ ant display-properties
Buildfile: build.xml

display-properties:
     [echo] Build directory (build.dir) = /home/john/projects/tax-calculator/build

BUILD SUCCESSFUL
Total time: 0 seconds

Another way to do this would be to use the <echoproperties> task, which, as the name suggests, lets you display all of the current property values to the console:

    <target name="display-properties">
        <echoproperties />
    </target>

Properties can also be used to take into account environment differences on each developer’s machine. For example, you may need to store a path to a local server or to application directories, which may be different on different machines. To do this, you can store local properties in an ordinary Java properties file. The following directories would be different on Unix and Windows machines. On a Linux development box, you might have the following set of properties:

checkstyle.home = /usr/local/tools/checkstyle
pmd.home = /usr/local/tools/pmd
findbugs.home = /usr/local/tools/findbugs
cobertura.home = /usr/local/tools/cobertura

On a Windows machine, by contrast, you might have something like this:

checkstyle.home = C:\tools\checkstyle
pmd.home = C:\tools\pmd
findbugs.home = C:\tools\findbugs
cobertura.home = C:\tools\cobertura

Another common use of local properties files is to store user-specific and/or potentially sensitive details such as logins and passwords. This way, this sort of data doesn’t end up in the version control system.

Ant also comes with a set of built-in properties, such as ${basedir}, which is the absolute path of the project base directory, as well as Java system properties like ${java.home}, ${java.version}, and ${user.home}. You can also define other properties based on these properties. This can limit the need to put a lot of variables in local properties files. For example, suppose you want to use the FindBugs (see Chapter 23) library in your build process. You could define a default FindBugs installation directory as a subdirectory of the user’s home directory:

<property name="findbugs.home" value="${user.home}/.findbugs"/>

Using this definition, any user who has installed FindBugs into this directory would not need to configure their local properties file—the default property value would suffice.

One nice feature here is that you can also use properties that you define in a properties file to define other properties, as shown below:

test.server = jupiter.mycompany.com
test.server.port = 8080
test.server.url = http://${test.server}:${test.server.port}
test.server.manager.url = ${test.server.url}/manager

All team members need to be aware of where these properties should be defined. This is important, because by definition this is not something you can place under version control. One good approach is to agree on a properties file naming convention (say, use a file called local.properties in the project home directory) and to place a documented sample file under version control (called, for example, sample.local.properties). Another, possibly complementary, convention is to use a global properties file, stored in the user’s home directory, which contains properties that are shared between several projects. This file is often called “ant-global.properties” or “.ant-global.properties.

You can incorporate these properties into your build script by using the file attribute in the <property> tag:

    <property file="${user.home}/ant-global.properties"/>
    <property file="${basedir}/local.properties"/>

    <echo>checkstyle.home = ${checkstyle.home}</echo>
    <echo>pmd.home = ${pmd.home}</echo>
    <echo>findbugs.home = ${findbugs.home}</echo>
    <echo>cobertura.home = ${cobertura.home}</echo>

When you run this, the property values will be loaded and integrated into the build file:

$ ant compile
Buildfile: build.xml
     [echo] checkstyle.home = /usr/local/tools/checkstyle
     [echo] pmd.home = /usr/local/tools/pmd
     [echo] findbugs.home = /usr/local/tools/findbugs
     [echo] cobertura.home = /usr/local/tools/cobertura
     ...

You can make your build file more robust by setting up sensible default values for locally defined properties. One very powerful feature of Ant properties (which has caused a lot of confusion among inexperienced Ant users) is immutability. Once defined, Ant properties cannot be modified for the duration of the build, no matter how many <property> tags refer to them afterward. In other words, the first declaration of any property wins.

Let’s look at a practical example. Suppose you define a property called ${javac.debug} to indicate if you want to include debug information in your compiled classes. You could do this as follows:

    <property name="javac.debug" value="off"/>
    ...
    <target name="compile" depends="init" description="Compile Java code">
        <echo message="Debug: ${javac.debug}" />
        <javac srcdir="${src.dir}" 
               destdir="${build.classes.dir}" 
               classpathref="compile.classpath" 
               debug="${javac.debug}"/>
    </target>

When you run this, unsurprisingly, the ${javac.debug} property is set to “off”:

$ ant compile
Buildfile: build.xml
...
compile:
     [echo] Debug: off

BUILD SUCCESSFUL
Total time: 0 seconds

Now suppose you write a file called local.properties that you store in your project directory. This properties file contains your own personal preferences for build options such as debugging. In it, you stipulate that debugging should be on:

javac.debug = on

And now we incorporate this properties file into our build file (note that we include the properties file before the property declaration):

<property file="${basedir}/local.properties"/>
<property name="javac.debug" value="off"/>
<javac srcdir="src" destdir="build/classes" debug="${javac.debug}" />

When you run the build again, the first declaration of ${javac.debug} (in the properties file) is considered definitive, and the second (in the build file) is ignored:

$ ant compile
Buildfile: build.xml
...
compile:
     [echo] Debug: on

BUILD SUCCESSFUL
Total time: 0 seconds

The story doesn’t end there, however. You can override any property value from the command line, using the -D command-line option. For example, here we override the ${javac.debug} property value to “off.” This will take precedence over all other property declarations:

$ ant -Djavac.debug=off compile
Buildfile: build.xml

init:

compile:
     [echo] Debug: off

BUILD SUCCESSFUL
Total time: 0 seconds

Running Unit Tests in Ant

Unit testing is a fundamental part of Java development, and, applied correctly, can contribute substantially to the quality and reliability of your code. Modern coding techniques, such as test-driven development (TDD) and, more recently, behavior-driven development, rely heavily on unit testing to ensure that code is both well designed and well tested.

Unit testing is good, but automated unit testing is better. When your unit tests are integrated into your build process, you can run them automatically before each build or before committing code to your version control system. This helps to ensure that your latest changes haven’t introduced any new errors. This is also known as regression testing. It makes refactoring your code safer and easier, as you can change code without having to worry about unknowingly breaking existing code—as long as you rerun the entire set of unit tests at frequent intervals.

Let’s look at how to integrate JUnit tests into your Ant build process.

Using JUnit in Ant

JUnit (see Chapter 10) is probably the most well-known and widely used unit testing framework in the Java world. A groundbreaking piece of software in its time, JUnit 3 is still the basis of a very large range of unit testing libraries. The more recent JUnit 4 introduces annotation-based testing and some other more modern features.

You can run your JUnit tests in Ant using the <junit> task, which is what’s known as an optional task. In Ant, optional tasks are tasks that generally depend on external libraries, such as the JUnit library in this case. Ant comes bundled with the task itself, but, if you want to use the task, you need to provide the external library yourself. This is as opposed to core tasks, which are fully integrated into Ant and need no external libraries.

Historically, Ant’s JUnit integration has been a bit rough. In older versions of Ant (prior to Ant 1.7), you need to place your own copy of the junit.jar file into the $ANT_HOME/lib directory, alongside the ant-junit.jar file. As of Ant 1.7, things have improved, and you no longer need to modify your Ant installation just to run your unit tests. You still need to provide your own copy of the JUnit file (it is, after all, up to you what version of JUnit you use), but you can specify this library in the normal build classpaths, just as you would your other project dependencies.

Preparing Your Build for Automated Tests

The best way to explore how to use JUnit in Ant is to look at a practical example. In this chapter, we are going to test a simple domain class called TaxCalculator, which, rather surprisingly, calculates tax. In this particular case, we will be calculating income tax using a very simple (but nevertheless real) income tax system. The tax rates in this system are illustrated in Table 1-2.

Table 1-2. Income tax rates for our sample application
Taxable incomesTax rate for every $1 of taxable income
up to $38,00019.5 cents
$38,001 to $60,000 inclusive33 cents
$60,001 and over39 cents

In A Gentle Introduction to Ant, we discussed a recommended directory layout in which the application source code was separated from the unit tests. In this layout, the application classes are placed in the src directory, whereas the unit test classes go in the test directory. This practice will make your builds cleaner and easier to manage. The name of the directories is not particularly important (another option is to follow the Maven convention of a src/main/java directory for the Java application code, and src/test/java for Java unit tests)—the essential thing is to keep the unit tests away from the application code.

The first class we will implement for this calculator is a TaxRate class, which represents a single tax rate. It will also know how to calculate the income tax applicable for its own income bracket.

Methods such as TDD recommend writing unit tests before writing the code itself. This class is a good example of where TDD can be used effectively. So, before writing this class, let’s start off with some unit tests to figure out how the class should behave. To test this class, we will test each income bracket using a varied set of values. Rather than testing every possible value, we test a selection of key values to check for boundary conditions. For example, for an initial test, you might test the first tax bracket against $0 and $10,000:

package com.javapowertools.taxcalculator.domain;

import static org.junit.Assert.*;
import org.junit.Test;

public class TaxRateTest {

    @Test
    public void testCalculateTaxBracket1() {
        TaxRate rate = new TaxRate(0, 38000, 0.195);
        assertEquals(rate.calculateTax(0), 0);
        assertEquals(rate.calculateTax(10000), 10000 * 0.195);
    }        
}

You can now start to code. An initial class that passes these simple tests might look like this:

public class TaxRate {

    private double minimumRevenue;
    private double maxiumuRevenue;
    private double rate;

    public TaxRate(double minimumRevenue, double maxiumuRevenue, double rate) {
        super();
        this.minimumRevenue = minimumRevenue;
        this.maxiumuRevenue = maxiumuRevenue;
        this.rate = rate;
    }
    
    public double getMinimumRevenue() {
        return minimumRevenue;
    }

    public double getMaxiumuRevenue() {
        return maxiumuRevenue;
    }

    public double getRate() {
        return rate;
    }

    public double calculateTax(double totalRevenue) {
        return totalRevenue * rate;
    }        
}

This is not enough, however, so we need to add some more test cases to check the other boundary conditions:

    @Test
    public void testCalculateTaxBracket1() {
        TaxRate rate = new TaxRate(0, 38000, 0.195);
        assertEquals(rate.calculateTax(0), 0);
        assertEquals(rate.calculateTax(10000), 10000 * 0.195);
        assertEquals(rate.calculateTax(38000), 38000 * 0.195);
        assertEquals( rate.calculateTax(50000), 38000 * 0.195);
    }

Your class will now need some refactoring to take into account the maximum revenue. Once your class performs this correctly, you can progressively add new test cases to test other values, until the class does everything required of it. The full unit test class is shown here:

package com.javapowertools.taxcalculator.domain;

import static org.junit.Assert.*;
import org.junit.Test;

public class TaxRateTest {

    private static final double FIRST_TAX_BRACKET_RATE = 0.195;
    private static final double SECOND_TAX_BRACKET_RATE = 0.33;
    private static final double THIRD_TAX_BRACKET_RATE = 0.39;

    @Test
    public void testCalculateTaxBracket1() {
        TaxRate rate = new TaxRate(0, 38000, FIRST_TAX_BRACKET_RATE);
        assertEquals(0.0, rate.calculateTax(0), 0.0);
        assertEquals(10000 * FIRST_TAX_BRACKET_RATE, 
                     rate.calculateTax(10000), 0.0);
        assertEquals(38000 * FIRST_TAX_BRACKET_RATE, 
                     rate.calculateTax(38000), 0.0);
        assertEquals(38000 * FIRST_TAX_BRACKET_RATE, 
                     rate.calculateTax(50000), 0.0);
    }

    @Test
    public void testCalculateTaxBracket2() {
        TaxRate rate = new TaxRate(38000, 60000, SECOND_TAX_BRACKET_RATE);
        assertEquals(0.0, rate.calculateTax(0), 0.0);
        assertEquals(0.0, rate.calculateTax(10000), 0.0);
        assertEquals(0.0, rate.calculateTax(38000), 0);
        assertEquals(2000 * SECOND_TAX_BRACKET_RATE, 
                     rate.calculateTax(40000), 0);
        assertEquals(22000 * SECOND_TAX_BRACKET_RATE,
                     rate.calculateTax(60000), 0.0);
        assertEquals(22000 * SECOND_TAX_BRACKET_RATE,
                     rate.calculateTax(80000), 0.0);
    }    

    
    @Test
    public void testCalculateTaxBracket3() {
        TaxRate rate = new TaxRate(60000, 60000, THIRD_TAX_BRACKET_RATE);
        assertEquals(0.0, rate.calculateTax(0), 0.0);
        assertEquals(0.0, rate.calculateTax(10000), 0.0);
        assertEquals(0.0, rate.calculateTax(38000), 0);
        assertEquals(0.0, rate.calculateTax(40000), 0);
        assertEquals(0.0, rate.calculateTax(60000), 0.0);
        assertEquals(20000 * THIRD_TAX_BRACKET_RATE,
                     rate.calculateTax(80000), 0.0);        
        assertEquals(40000 * THIRD_TAX_BRACKET_RATE,
                     rate.calculateTax(100000), 0.0);                
    }        
}

Note that this is not necessarily how a tax application would be coded or tested in real life. For example, some tests are actually regrouped into a single test method for simplicity, and a real business application would probably use BigDecimals rather than doubles for more precision. However, these tests should be sufficient to verify that our class functions correctly for all the tax brackets. Let’s fast-forward to the solution—the TaxRate class is shown in full here:

public class TaxRate {

    private double minimumRevenue;
    private double maxiumuRevenue;
    private double rate;

    public TaxRate(double minimumRevenue, double maxiumuRevenue, double rate) {
        super();
        this.minimumRevenue = minimumRevenue;
        this.maxiumuRevenue = maxiumuRevenue;
        this.rate = rate;
    }
    
    public double getMinimumRevenue() {
        return minimumRevenue;
    }

    public double getMaxiumuRevenue() {
        return maxiumuRevenue;
    }

    public double getRate() {
        return rate;
    }

    private double getApplicableAmount(double totalRevenue) {
        double applicableAmount = 0.0;
        if (totalRevenue >= minimumRevenue) {
            applicableAmount = totalRevenue - minimumRevenue;
            if (maxiumuRevenue > 0) {
                if (totalRevenue > maxiumuRevenue) {
                    applicableAmount = maxiumuRevenue - minimumRevenue;
                }
            }
        }
        return applicableAmount;
    }
    
    public double calculateTax(double totalRevenue) {
        return getApplicableAmount(totalRevenue) * rate;
    }        
}

Now to the heart of the matter: running the unit tests. Before we get to the JUnit task itself, we need to do a little housekeeping. First, we define our directory structure, using property definitions for easier maintenance and clarity. As discussed, we place the application source code in the src directory (represented by the $src.dir property) and the unit tests in the test directory (the $test.dir property). Likewise, the application source code is compiled to the build/classes directory ($build.classes.dir), whereas the unit tests are compiled to build/test-classes ($test.classes.dir). The properties definitions are shown here:

<?xml version="1.0" ?>
<project name="tax-calculator" default="package">
    <property name="src.dir" location="src" />
    <property name="build.dir" location="build" />
    <property name="tests.dir" location="test" />
    <property name="build.classes.dir" location="${build.dir}/classes" />
    <property name="test.classes.dir" location="${build.dir}/test-classes" />
    <property name="lib" location="lib" />
    <property name="dist.dir" location="dist" />

Next, we need to define some paths. The first is the classpath used to compile the application classes. This simply contains all the JAR files in the lib directory:

    <path id="compile.classpath">
        <fileset dir="${lib}" includes="*.jar" />
    </path>

The second is the classpath that we will use when we compile the unit tests. Here, in addition to the libraries in the JAR file, we also need the compiled application classes that we are meant to test. To do this, we create a path with two elements: a <path> tag containing a reference to the compile classpath, and a <pathelement> tag containing the compiled application classes:

    <path id="test.compile.classpath">
      <path refid="compile.classpath"/>
      <pathelement location="${build.classes.dir}"/>
    </path>

We’re not quite done with the paths just yet. We also need to define a classpath containing all of these classes, plus the compiled unit tests. We will be needing this one when we run our unit tests:

    <path id="test.classpath">      
      <path refid="test.compile.classpath"/>
      <pathelement path="${test.classes.dir}"/>
    </path>

The next thing we need to provide in the build script is an initialization task to build the output directories, if necessary. This target simply uses the <mkdir> task to create the target directories. Note that there is no need to create the build directory before creating the build/classes directory; Ant will do that for us:

    <target name="init">
        <mkdir dir="${build.classes.dir}" />
        <mkdir dir="${test.classes.dir}" />
        <mkdir dir="${dist.dir}" />
    </target>

Now we can proceed to the compilation tasks. The <javac> task used to compile the application classes is straightforward, and simply compiles the application classes in the ${src.dir} directory into the ${build.classes.dir} directory:

    <target name="compile" depends="init" description="Compile Java code">
        <javac srcdir="${src.dir}" 
               destdir="${build.classes.dir}" 
               classpathref="compile.classpath" />
    </target>

Next we need to compile our test classes. It’s always nicer to test against the latest version of your application classes, so we make this target depend on the “compile” target. This will ensure that the application classes are compiled or recompiled, if necessary, before the unit test classes are compiled. Then we simply compile the unit test classes in the ${tests.dir} directory, using the test.compile.classpath classpath we defined above.

   <target name="compile-tests" depends="compile" description="Compile Unit Tests">
        <javac srcdir="${tests.dir}" 
               destdir="${test.classes.dir}">
               <classpath refid="test.compile.classpath"/>
        </javac>
    </target>

Using the <junit> Task

We are now (finally) ready to run our unit tests from within Ant. Our application classes are up-to-date, and our unit tests are compiled and ready to go. Let’s see how we run unit tests from within Ant.

    <target name="test" depends="compile-tests" description="Run unit tests">
        <junit printsummary="true" haltonfailure="true">
            <classpath refid="test.classpath" />
            <test name="com.javapowertools.taxcalculator.domain.TaxRateTest" />
        </junit>
    </target>

This is a fairly minimal, but usable, <junit> task configuration. It runs the TaxRateTest test case, using the test.classpath classpath that we set up earlier. The printsummary attribute tells Ant to display a list of the unit test classes being executed. Otherwise, it will run the tests, but keep the results to itself unless any of the unit tests fail. The haltonfailure attribute tells Ant to stop the build if there are test failures. The default behavior is to continue the build even if there are test failures, so you might want to generate test reports afterward (see Generating HTML Test Reports,” later in this section). When you run this target, Ant will run the unit tests and display a brief summary of the test results:

$ ant test
Buildfile: build.xml

init:

compile:

compile-tests:

test:
    [junit] Running com.javapowertools.taxcalculator.domain.TaxRateTest
    [junit] Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 0.033 sec

BUILD SUCCESSFUL
Total time: 0 seconds

If any of the tests fail, JUnit will indicate the number of failed tests and the build will fail:

$ ant test
Buildfile: build.xml

init:

compile:

compile-tests:

test:
    [junit] Running com.javapowertools.taxcalculator.domain.TaxRateTest
    [junit] Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.048 sec

BUILD FAILED
/home/wakaleo/projects/jpt-sample-code/ant-demo/build.xml:46:
Test com.javapowertools.taxcalculator.domain.TaxRateTest failed

The only problem with this is that the error message is not particularly informative. Details of what went wrong are not provided, which makes it a bit hard to debug. As you might expect, JUnit (and Ant) can do much better than that. JUnit comes with a set of formatters that can be used to display the test results in a more usable form. You can use JUnit formatters in the <junit> task by adding nested <formatter> elements to the <junit> task.

The <junit> task comes with three types of formatter. The simplest formatter is the “brief” formatter, which just provides details for any test failures. The “plain” formatter provides information on the number of tests passed and failed, and also lists the tests that actually succeeded. The third type, the “xml” formatter, is mainly used for report generation. We look at how to generate HTML test reports later in this section in Generating HTML Test Reports.”

You can add a formatter using the <formatter> element as shown here:

    <target name="test" depends="compile-tests" description="Run unit tests">
        <junit printsummary="true" haltonfailure="true">
            <classpath refid="test.classpath" />
            <formatter type="plain"/>
            <test name="com.javapowertools.taxcalculator.domain.TaxRateTest" />
        </junit>
    </target>

This will generate a text report in the working directory with a little more information about the failure:

$ ant test
...
test:
    [junit] Running com.javapowertools.taxcalculator.domain.TaxRateTest
    [junit] Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.048 sec

BUILD FAILED
/home/wakaleo/projects/jpt-sample-code/ant-demo/build.xml:46:
Test com.javapowertools.taxcalculator.domain.TaxRateTest failed

$ more TEST-com.javapowertools.taxcalculator.domain.TaxRateTest.txt 
Testsuite: com.javapowertools.taxcalculator.domain.TaxRateTest
Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.059 sec

Testcase: testCalculateTaxBracket1 took 0.016 sec
        FAILED
expected:<7410.0> but was:<5850.0>
junit.framework.AssertionFailedError: expected:<7410.0> but was:<5850.0>
        at com.javapowertools.taxcalculator.domain.TaxRateTest.
        testCalculateTaxBracket1(Unknown Source)

Testcase: testCalculateTaxBracket2 took 0.002 sec
Testcase: testCalculateTaxBracket3 took 0.008 sec

Writing test results to text files has a lot going for it, especially if you have many hundreds of unit tests. However, you may want a quick heads-up on your test failures, without having to sift through text files. A good way to do this is to have Ant write the test reports to the console instead of to a file, using the usefile attribute:

<formatter type="plain" usefile="false"/>

This will write the test results to the console, which has the advantage of making any errors stand out fairly clearly.

$ ant test
Buildfile: build.xml

init:

compile:

compile-tests:

test:
    [junit] Running com.javapowertools.taxcalculator.domain.TaxRateTest
    [junit] Testsuite: com.javapowertools.taxcalculator.domain.TaxRateTest
    [junit] Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.062 sec
    [junit] Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.062 sec
    [junit] 
    [junit] Testcase: testCalculateTaxBracket1 took 0.028 sec
    [junit]     FAILED
    [junit] expected:<7410.0> but was:<5850.0>
    [junit] junit.framework.AssertionFailedError: expected:<7410.0> but was:<5850.0>
    [junit]     at com.javapowertools.taxcalculator.domain.TaxRateTest.testCalculate
                TaxBracket1(Unknown Source)
    [junit] 
    [junit] Testcase: testCalculateTaxBracket2 took 0.001 sec
    [junit] Testcase: testCalculateTaxBracket3 took 0.006 sec

BUILD FAILED
/home/wakaleo/projects/jpt-sample-code/ant-demo/build.xml:46: 
Test com.javapowertools.taxcalculator.domain.TaxRateTest failed

Total time: 0 seconds

Or you can have the best of both worlds, and write both to a file and to the console, by specifying two formatter elements:

<formatter type="plain"/>
<formatter type="plain" usefile="false"/>

Finally, if you find the “plain” formatter a bit verbose, you can always use the “brief” formatter, shown in the following example:

<formatter type="brief" usefile="false"/>

This formatter generates a more concise output that is well-suited to console output:

$ ant test
Buildfile: build.xml

init:

compile:

compile-tests:

test:
    [junit] Running com.javapowertools.taxcalculator.domain.TaxRateTest
    [junit] Testsuite: com.javapowertools.taxcalculator.domain.TaxRateTest
    [junit] Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.062 sec
    [junit] Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.062 sec
    [junit] 
    [junit] Testcase: testCalculateTaxBracket1 took 0.028 sec
    [junit]     FAILED
    [junit] expected:<7410.0> but was:<5850.0>
    [junit] junit.framework.AssertionFailedError: expected:<7410.0> but was:<5850.0>
    [junit]     at com.javapowertools.taxcalculator.domain.TaxRateTest.
                testCalculateTaxBracket1(Unknown Source)
    [junit] 
    [junit] Testcase: testCalculateTaxBracket2 took 0.001 sec
    [junit] Testcase: testCalculateTaxBracket3 took 0.006 sec

BUILD FAILED
/home/wakaleo/projects/jpt-sample-code/ant-demo/build.xml:46: Test com.javapowertools.
taxcalculator.domain.TaxRateTest failed

Total time: 0 seconds

Running Multiple Tests

Now that we are sure that the “TaxRateCalculator” works correctly, we can proceed to writing the TaxCalculator itself. We could test this class in a traditional, JUnit 3-style approach by writing test cases for a (hopefully representative) set of values, as shown here:

public class TaxCalculatorTest extends TestCase {

    private TaxCalculator calc = null;
    
    @Override
    protected void setUp() throws Exception {
        calc = new TaxCalculator();
    }    
    public void testCalculation1() {
        assertEquals(calc.calculateIncomeTax(0), 0.0, 0.0);
    }       
    public void testCalculation2() {
        assertEquals(calc.calculateIncomeTax(10000), 1950.00, 0.0);
    }   
    public void testCalculation3() {
        assertEquals(calc.calculateIncomeTax(20000), 3900.00, 0.0);
    }    
    public void testCalculation4() {
        assertEquals(calc.calculateIncomeTax(30000), 5850.00, 0.0);
    }    
    public void testCalculation5() {
        assertEquals(calc.calculateIncomeTax(60000), 14670.00, 0.0);
    }    
    public void testCalculation6() {
        assertEquals(calc.calculateIncomeTax(100000), 30270.00, 0.0);
    }
    public void testCalculation7() {
        assertEquals(calc.calculateIncomeTax(160000), 53670.00, 0.0);
    }
    public void testCalculation8() {
        assertEquals(calc.calculateIncomeTax(200000), 69270.00, 0.0);
    }
}

Alternatively, we could use a new JUnit 4 feature called parameterized tests (see Using Parameterized Tests) to write an arguably cleaner and more maintainable test case:

@RunWith(Parameterized.class)
public class TaxCalculatorTest {

    @Parameters
    public static Collection data() {
        return Arrays.asList(new Object[][]{
                /* Income      Tax  */
                {     0.00,     0.00},
                { 10000.00,  1950.00},
                { 20000.00,  3900.00},
                { 38000.00,  7410.00},
                { 38001.00,  7410.33},
                { 40000.00,  8070.00},
                { 60000.00, 14670.00},
                {100000.00, 30270.00},
                {160000.00, 53670.00},
                {200000.00, 69270.00},
        });
    }

    private double revenue;
    private double expectedTax;
    private TaxCalculator calculator = new TaxCalculator();

    public TaxCalculatorTest(double input, double expectedTax) {
        this.revenue = input;
        this.expectedTax = expectedTax;
    }
    
    @Test public void calculateTax() {
        assertEquals(expectedTax, calculator.calculateIncomeTax(revenue), 0.0);
    }
}

One of the nice things about the <junit> task in Ant 1.7 is that the testing method doesn’t really matter—you can use JUnit 4 test cases, JUnit 3 test cases, or a mixture of the two, and the <junit> task will still run them all in a consistent manner. This is handy if you want to use the modern features of JUnit 4 where possible, while still using testing frameworks that rely on JUnit 3 for some of your tests.

Just for completeness, here is the TaxCalculator class itself:

public class TaxCalculator {

    public static final List<TaxRate> TAX_RATES 
        = new ArrayList<TaxRate>();
    
    static {
        TAX_RATES.add(new TaxRate(0, 38000, 0.195));
        TAX_RATES.add(new TaxRate(38000, 60000, 0.33));
        TAX_RATES.add(new TaxRate(60000, 0, 0.39));        
    }
    
    public TaxCalculator() {
    }
    
    public double calculateIncomeTax(double totalRevenue) {        
        double totalTax = 0.0;
        for(TaxRate rate : TAX_RATES) {
            totalTax += rate.calculateTax(totalRevenue);
        }       
        return totalTax;
    }
}

So, now we have several test classes to test. The <test> task is a fine example of an Ant task (as tasks go), and is an excellent way to get familiar with the features of JUnit in Ant. However, it is not always the most appropriate way to run tests in a real project. In a real project, you usually have more than one unit test. In fact, for a typical small- to medium-sized project, you may have hundreds. You shouldn’t have to add a new <test> task to your build file for each new unit test class you write.

Indeed, there is a much better way. The <batchtest> element lets you run multiple unit tests in one go, using Ant filesets to tell Ant which tests to run. In the following example, we use <batchtest> to run all the test classes in the test classes directory whose names end with “Test.” This naming convention is useful if you wish to include other utility or abstract classes alongside your test classes.

        <junit printsummary="true" haltonfailure="true">
            <classpath refid="test.classpath" />
            <formatter type="plain" usefile="false" />
            <batchtest>
                <fileset dir="${test.classes.dir}" includes="**/*Test.class" />
            </batchtest>
        </junit>

Of course, it is also useful to store the test results in the form of text files so that you can come back and analyze them later. However, it isn’t very tidy to generate large numbers of test reports in the project root directory. It would be much tidier to place them in their own directory. This is easy to do using the todir attribute in the <batchtest> element. In the following listing, we place all the test reports in the reports directory.

    <property name="reports.dir" location="reports" />
    ...
    <target name="init">
        <mkdir dir="${build.classes.dir}" />
        <mkdir dir="${test.classes.dir}" />
        <mkdir dir="${dist.dir}" />
        <mkdir dir="${reports.dir}" />
    </target>
    ...
    <target name="test" depends="compile-tests" description="Run unit tests">
        <junit printsummary="true" haltonfailure="true">
            <classpath refid="test.classpath" />
            <formatter type="plain" usefile="false" />
            <formatter type="plain" />
            <batchtest>
                <fileset dir="${test.classes.dir}" includes="**/*Test.class" />
            </batchtest>
        </junit>
    </target>

Running Tests in a Separate JVM

By default, the <junit> task runs unit tests in the current JVM; however, there are times when it can be useful to run your tests in a separate JVM. You can do this using the fork attribute, which spawns a new process and runs your tests in a brand new JVM. Just set the fork attribute of your <junit> task to “true,” as shown here:

        <junit printsummary="true" fork="true">
            <classpath refid="test.classpath" />
            <formatter type="plain" />
            <batchtest>
                <fileset dir="${test.classes.dir}" includes="**/*Test.class" />
            </batchtest>
        </junit>

You can also use the forkmode attribute to indicate if you want a new process for each test (using “perTest,” which is the default value), for each <batchtest> (“perBatch”), or just one for all the tests (“once”). Creating a new JVM for each test is the cleanest way to separate your tests, but it is also the slowest.

Running your <junit> task as a forked process gives you much more control over your tests. You can use the maxmemory attribute to run your tests under specific memory constraints. You can specify a timeout value to force tests to fail if they run longer than a certain number of milliseconds. And it also means that your tests can fail dramatically (for example, with an OutOfMemory exception) without breaking your build process.

Generating HTML Test Reports

When your project has many hundreds of unit tests, the generated text files can get very large, and it can be hard to sift through them all to find out exactly what went wrong.

JUnit lets you generate your test results in XML form. Now, on the surface, an XML test report is even less readable than a plain text one. However, Ant comes with the <junitreport> task, which lets you turn this raw XML data into a quite presentable HTML report. More precisely, the <junitreport> task regroups the XML test reports into a single XML document, and then applies an XSL stylesheet of your choice. The end result is much more readable and usable than the plain text equivalent, and you can easily publish your test results onto a project web site for all to see.

The first thing you need to do is generate some test results in XML format, using the “xml” formatter element. You could do this as follows:

    <target name="test" depends="compile-tests" description="Run unit tests">
        <junit printsummary="true">
            <classpath refid="test.classpath" />
            <formatter type="plain" />
            <formatter type="xml" />
            <batchtest todir="${reports.data.dir}" >
                <fileset dir="${test.classes.dir}" includes="**/*Test.class" />
            </batchtest>
        </junit>
    </target>

We have added some new directories here to make things a little tidier. The raw report data is placed in the reports/xml directory, whereas the final HTML report (which takes the form of a multipage web site) is stored in the reports/html directory. This makes it easier to clean up test data and to deploy your HTML reports.

    <property name="reports.dir" location="reports" />
    <property name="reports.data.dir" location="reports/xml" />
    <property name="reports.html.dir" location="reports/html" />

Now that we have the data, we still need to generate the report itself. We can do this using the <junitreport> task as follows:

    <target name="test.report" depends="test" description="Generate HTML unit 
     test reports">
        <junitreport todir="${reports.data.dir}">
          <fileset dir="${reports.data.dir}">
            <include name="TEST-*.xml"/>
          </fileset>
          <report format="frames" todir="${reports.html.dir}"/>
        </junitreport>
    </target>

This basically takes all the XML test reports in reports/xml and generates an HTML report using the frames-based format (the most commonly used) in the reports/html directory.

The report generated by <junitreport>
Figure 1-2. The report generated by <junitreport>

Both versions of the reports are dynamic—you can click on a package or test class for more details, and display the failed assertion and other useful details (see Figure 1-2).

Displaying detailed results for a particular test case
Figure 1-3. Displaying detailed results for a particular test case

This format is fine for most situations (see Figure 1-3), and is generated by the XSL stylesheet that comes bundled in the ant-junit.jar library. Alternatively, you may want to use the “noframes” stylesheet (just use format=“noframes”) to generate the test results in a single (big) HTML page.

In some cases, you might want to customize the stylesheets a little more. For example, you may want to add the company logo or change the color scheme to suite your project web site. You can do this without too much trouble by using the styledir attribute of the <report> element to override the default stylesheets. The first thing you need to do is copy the junit-frames.xsl and junit-noframes.xsl files into a directory of your own (conf/css, for example). You can get these files from the ant-junit.jar file, or from the etc directory in the Ant distribution ($ANT_HOME/etc). Don’t change their names, as Ant will look for stylesheets with these names in the directory you provide, and won’t accept any value other than “frames” or “noframes.” You can now tailor these stylesheets to your heart’s content. Then just specify the directory using the styledir attribute, as shown here:

    <junitreport todir="${reports.data.dir}">
        <fileset dir="${reports.data.dir}">
            <include name="TEST-*.xml"/>
        </fileset>
        <report format="frames"
                todir="${reports.html.dir}" 
                styledir="conf/css"/>
    </junitreport>

As you would expect, you need to generate your test reports after your unit tests have finished. As far as the build process goes, this has some fairly major side effects. For one thing, it means that you cannot simply let the build stop if the tests fail, so setting the haltonfailure attribute to “true” (see Using the <junit> Task,” earlier in this section) is a really bad idea. On the other hand, it is a good idea for the build to fail if there are test failures. In fact, your continuous build system may rely on it. This puts us in somewhat of a dilemma.

Fortunately, there is a solution. The <junit> task has two special attributes, failureproperty and errorproperty. These attributes name properties that will be set to “true” if a failure (or error) occurs. In fact, failureproperty is usually sufficient, as errors are treated as failures as well. This lets you proceed with the build until you decide it is time to stop, and then (and only then) stop the build with an error message. You could do this as follows (the haltonfailure attribute is just here for clarity, as the default value for this attribute is “false”):

    <target name="test" depends="compile-tests" description="Run unit tests">
        <junit printsummary="true" haltonfailure="false" failureproperty=
         "test.failures">
            <classpath refid="test.classpath" />
            <formatter type="plain" />
            <formatter type="xml" />
            <batchtest todir="${reports.data.dir}" >
                <fileset dir="${test.classes.dir}" includes="**/*Test.class" />
            </batchtest>
        </junit>
    </target>

If any failures (or errors) occur, the test.failures property will be set to “true.” Now, just after the <junitreport> task, we add a <fail> task. The <fail> task lets you force the build to fail if a particular condition is met.

    <target name="test.report" depends="test" 
            description="Generate HTML unit test reports">
        <junitreport todir="${reports.data.dir}">
          <fileset dir="${reports.data.dir}">
            <include name="TEST-*.xml"/>
          </fileset>
          <report format="noframes" todir="${reports.html.dir}"/>
        </junitreport>
        <fail if="test.failures" message="There were test failures." />
    </target>

Now, when you run the test.report target, the tests will be executed, the report generated, and then the build will fail with an appropriate error message:

$ ant test.report 
Buildfile: build.xml

init:
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/build/classes
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/build/test-classes
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/dist
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/reports/xml
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/reports/html

compile:

compile-tests:

test:
    [junit] Running com.javapowertools.taxcalculator.services.TaxCalculatorTest
    [junit] Tests run: 12, Failures: 2, Errors: 0, Time elapsed: 0.108 sec
    ...

test.report:
[junitreport] Processing /home/wakaleo/projects/jpt-sample-code/ant-demo/reports/xml/
TESTS-TestSuites.xml to /home/wakaleo/projects/jpt-sample-code/ant-demo/reports/html/
junit-noframes.html
[junitreport] Loading stylesheet jar:file:/usr/share/ant/lib/ant-junit.jar!/org
/apache/tools/ant/taskdefs/optional/junit/xsl/junit-noframes.xsl
[junitreport] Transform time: 531ms

BUILD FAILED
/home/wakaleo/projects/jpt-sample-code/ant-demo/build.xml:74: There were test failures.
Total time: 7 seconds

Using Asserts in Your Test Cases

In many examples of JUnit 4 test cases (and also TestNG test cases), the assert keyword is used as a convenient way to test application code. The assert keyword is built-in to the language, and thus needs no particular imports or dependencies. It also has a quite readable syntax. A typical assert in this context is shown here:

double tax = rate.calculateTax(100000);
assert tax > 0 : "Tax should not be zero";

Asserts are not just used for test cases: they can also be placed within application code as a way of documenting the intended behavior of your classes, and the assumptions that you have made when coding a particular method. For example, here we indicate that we are never expecting to receive a negative value for the totalRevenue parameter:

    public double calculateIncomeTax(double totalRevenue) {        
        assert totalRevenue >= 0 : "Revenue should not be negative";
        ...

The only problem with using this approach with JUnit is that, out-of-the-box, it won’t work. Assertions can be expensive operations, and by default, they are disabled at runtime. To use them, you need to activate them explicitly.

You can do this fairly easily in Ant. First of all, you need to run JUnit in a forked process (by setting the fork attribute to “true”). Then you need to add an <assertions> element to your <junit> task, containing at least one <enable> element. The following task will activate assertions for all nonsystem classes:

        <junit printsummary="true" haltonfailure="true" fork="true">
            <assertions>
                <enable/>
            </assertions>
            <classpath refid="test.classpath" />
            <formatter type="plain" />
            <batchtest todir="${reports.dir}" >
                <fileset dir="${test.classes.dir}" includes="**/*Test.class" />
            </batchtest>
        </junit>

If need be, you can narrow this down to a particular package or class, using the package and class attributes, respectively. After all, you don’t need to activate assertions for the whole JDK. Here we activate assertions uniquely for classes underneath the com.javapowertools package:

            <assertions>
                <enable package="com.javapowertools" />
            </assertions>

Now, when you run your tests, JUnit will integrate any failed assertions into the test results, albeit with a little less information in the details:

Testsuite: com.javapowertools.taxcalculator.domain.TaxRateTest
Tests run: 3, Failures: 1, Errors: 0, Time elapsed: 0.027 sec

Testcase: testCalculateTaxBracket1 took 0.005 sec
Testcase: testCalculateTaxBracket2 took 0.001 sec
Testcase: testCalculateTaxBracket3 took 0.002 sec
        FAILED
Tax should not be zero
junit.framework.AssertionFailedError: Tax should not be zero
        at com.javapowertools.taxcalculator.domain.TaxRateTest.testCalculateTaxBracket3
        (Unknown Source)

Generating Documentation with Javadoc

Technical documentation is an important part of any project, and Javadoc is one of the cornerstones of technical documentation in Java. Javadoc produces a quite decent, usable set of API documentation for your Java code, which can be a valuable aid as a communication tool, helping team members understand what other team members are doing. Of course, good Javadoc requires well-written and meaningful comments within the source code, and enforcing that is a tall order for any tool. And Javadoc documentation is by definition low-level reference material—it can be very useful for an application developer familiar with the application, but it will be of limited use for a new developer trying to learn the application architecture. Despite these reservations, Javadoc should be an integral part of any development project.

It is a good idea to generate Javadoc as part of your build process. Javadoc documentation should be generated and published alongside the compiled source code and the unit test results as part of the automatic build lifecycle. In Ant, you can do this using the <javadoc> task. This task has a lot of attributes and nested elements, but only a few are essential. Here is a simple example:

    <target name="javadoc" depends="compile,init" description="Generate JavaDocs.">        
         <javadoc sourcepath="${src.dir}"
                   destdir="${reports.javadoc}"
                   author="true"
                   version="true"
                   use="true"
                   access="private"
                   linksource="true"             
                   windowtitle="${ant.project.name} API">            
            <classpath>
                <path refid="compile.classpath" />
                <pathelement path="${build.classes.dir}" />
            </classpath>             
            <doctitle><![CDATA[<h1>${ant.project.name}</h1>]]></doctitle>
            <bottom><![CDATA[<i>Copyright &#169; 2007 All Rights Reserved.
            </i>]]></bottom>             
          </javadoc>        
    </target>

This task will generate Javadoc documentation for all the classes in the ${src.dir} directory. We needed to provide it with a classpath containing both the compiled classes and the application dependencies. We did this using an embedded <classpath> structure. We could also have defined this classpath elsewhere in the build file and referred to it using the classpathref attribute.

Most of the other attributes used here are fairly self-explanatory. The listsource attribute causes Ant to insert links in the Javadoc document to the source code in HTML form. This is similar to the Maven JXR plugin, although the formatting is less polished. The access property determines what parts of the classes should be documented. Here we document everything, from the private fields up. If you want a more succinct view, you might want to limit the javadoc to the protected or even to only the public fields and methods.

There are many other options for this task, far too many, in fact, to cover here. Most involve fine-tuning formatting details, and aren’t particularly interesting. You can limit the classes being documented by providing a list of packages in the packagenames attribute, although if you separate your test classes from your application source code, the reasons for doing so are generally more rare.

Javadoc documentation is generated in the form of a self-contained web site. This makes it easy to deploy to your local project web site, or to bundle up with your library, as most open source projects do.

In Chapter 30, we look at other tools you can use to enhance your technical documentation, all of which can be easily integrated into an Ant build script.

Packaging Your Application

Once you have compiled and tested your application, the next step is to bundle up the compiled code into a deliverable application or library. This can take different forms, depending on your project: you may have to prepare a JAR file, a WAR file, or possibly a ZIP or TAR file containing the executable code plus other files such as documentation and source code. Ant has many powerful features that can help you prepare your application for delivery. In the following sections, we will look at a few of the more interesting ones.

Generating a JAR File

The most fundamental Java packaging mechanism is the JAR file. A JAR file is essentially a ZIP file containing a hierarchy of compiled Java classes, plus some metadata. WAR and EAR files are similar, with some extra constraints on their internal directory structure and content.

The basic usage of the <jar> task is simple. Here is an example from our sample application, where we bundle the compiled classes into a JAR file:

    <property name="project.name" value="{ant.project.name}" />
    <property name="project.version" value="1.0" />
    ...
    <target name="package" depends="compile" description="Generate JAR file">
        <jar destfile="${dist.dir}/${project.name}-${project.version}.jar" basedir=
         "${build.classes.dir}"/>
    </target>

Running this will (surprise, surprise!) generate a JAR file containing your compiled classes:

$ ant clean package
Buildfile: build.xml

clean:
   [delete] Deleting directory /home/wakaleo/projects/jpt-sample-code/ant-demo/dist
   [delete] Deleting directory /home/wakaleo/projects/jpt-sample-code/ant-demo/reports

init:
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/dist
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/reports/xml
    [mkdir] Created dir: /home/wakaleo/projects/jpt-sample-code/ant-demo/reports/html

compile:

package:
      [jar] Building jar: /home/wakaleo/projects/jpt-sample-code/ant-demo/dist/
      tax-calculator-1.0.jar

BUILD SUCCESSFUL
Total time: 0 seconds

We use the Maven convention for naming JAR files here, which is to add the version number to the end of the filename. This makes it easier to identify file versions at a glance. The project name comes from the ant.project.name property, which is defined in the <project> root element. Using a different property means that developers are free to change the name of the generated JAR file by overriding this variable.[*]

If you need to deploy files from several different directories, you can use <fileset> elements to define which files to include. For example, if you also want to include files from the src/resources directory, you could do the following:

    <property name="project.name" value="{ant.project.name}" />
    <property name="project.version" value="1.0" />
    ...
    <target name="package" depends="compile" description="Generate JAR file">
        <jar destfile="${dist.dir}/${project.name}-${project.version}.jar">
            <fileset dir="${build.classes.dir}"/>
            <fileset dir="src/resources"/>
        </jar>
    </target>

If we have a look inside the JAR file generated by this task, we might notice the extra META-INF directory, which contains a file called MANIFEST.MF. This is where metadata about the version of the file is stored, along with other details such as the product and vendor names:

$ jar -tf dist/tax-calculator-1.0.jar 
META-INF/
META-INF/MANIFEST.MF
com/
com/javapowertools/
com/javapowertools/antdemo/
com/javapowertools/antdemo/domain/
com/javapowertools/antdemo/web/
com/javapowertools/antdemo/Main.class
com/javapowertools/antdemo/domain/Customer.class
com/javapowertools/antdemo/domain/TaxCalculator.class
com/javapowertools/antdemo/domain/TaxRate.class
com/javapowertools/antdemo/web/TaxCalculatorController.clas
...

By default, the MANIFEST.MF file contains very little. A sample is shown here:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0
Created-By: 1.6.0-b105 (Sun Microsystems Inc.)

However, this file is a great place to put version and build numbers and/or timestamps. Putting a version number and a build number (or timestamp) into your deployed packages is a good habit to get into—you never know when you need to work out exactly which build has just been deployed into production. In Ant, you can add extra details into the MANIFEST.MF file using the <manifest> element in the <jar> task:

    <target name="package" depends="compile" description="Generate JAR file">
        <tstamp>
            <format property="build.date" pattern="EEEE, d MMMM yyyy"/>
            <format property="build.time" pattern="hh:mm a"/>
        </tstamp>
        <jar destfile="${dist.dir}/${project.name}-${project.version}.jar"
         basedir="${build.classes.dir}" >
              <manifest>
                <attribute name="Built-By" value="${user.name}"/>
                  <attribute name="Specification-Title" value="${project.name}"/>
                <attribute name="Specification-Version" value="${project.version}"/>
                <attribute name="Specification-Vendor" value="ACME Incorporated"/>
                <attribute name="Implementation-Title" value="common"/>
                <attribute name="Implementation-Version" value="${project.version}
                 - built at ${build.time} on ${build.date} "/> 
                <attribute name="Implementation-Vendor" value="ACME Incorporated"/>
              </manifest>            
        </jar>
    </target>

Here we use the <tstamp> task to generate a timestamp corresponding to the current time. This task automatically sets three properties: DSTAMP, TSTAMP, and TODAY. The first two (DSTAMP and TSTAMP) are set to the current date and time, respectively, in a fairly machine-friendly (but not particularly readable) format (e.g., “20070820” and “2024,” respectively). The TODAY value is more readable (e.g., “August 20 2007”), but for a build date, we want something a little more precise. So, we use the nested <format> element to set some properties of our own. The deployed MANIFEST.MF file will now look something like this:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7.0
Created-By: 1.6.0-b105 (Sun Microsystems Inc.)
Built-By: wakaleo
Specification-Title: tax-calculator
Specification-Version: 1.0
Specification-Vendor: ACME Incorporated
Implementation-Title: common
Implementation-Version: 1.0 - built at 10:26 PM on Monday, 20 August  
Implementation-Vendor: ACME Incorporated

Generating a WAR File or an EAR File

Web applications are usually distributed in the form of a WAR file. WAR files can (usually) be deployed to any Java web server using a very simple deployment procedure. The exact procedure will vary from one application server to another, but it is usually something that can be done by a system administrator without a detailed understanding of the application.

A WAR file is simply a JAR file with a few extra requirements. In particular, a WAR file needs a special directory called WEB-INF, which contains application classes, libraries, and configuration files. Files placed under this directory cannot be accessed directly on the deployed web application, so this is a convenient place to put compiled classes, libraries, configuration files, and JSP pages. The basic directory structure of a WAR file is illustrated in Table 1-3.

Table 1-3. A typical WAR directory structure
DirectoryDescription
/Publicly accessible web pages
WEB-INF/Configuration files, not visible from the web site
WEB-INF/classesCompiled classes
WEB-INF/libApplication libraries

The <war> task is an extension of the <jar> task that takes into account the special structure of a WAR file. You use special nested elements to define the files to go into the WEB-INF/classes, WEB-INF/lib or WEB-INF directories.

Suppose you need to generate a WAR file for a JSP-based web application. The JSP files are stored in a directory called web. This directory also contains the WEB-INF sub-directory, where we store the web.xml file and any other configuration files we need. However, the application libraries and compiled classes will be obtained from other project directories.

You can create a WAR file from this directory structure using the <war> task, as shown here:

    <property name="web.dir" location="web" />
    <property name="dist.dir" location="dist" />

    <target name="war" depends="compile" description="Generate WAR file">
      <war destfile="${dist.dir}/${project.name}-${project.version}.war"
       webxml="${web.dir}/WEB-INF/web.xml">        
          <fileset dir="${web.dir}" />        
          <classes dir="${build.classes.dir}"/>        
          <lib dir="${lib}">
              <include name="*.jar" />
          </lib>
      </war>
    </target>

The usage of this task is similar to the <jar> task we saw previously (see Generating a JAR File,” earlier in this section), with a few additions. The most important configuration file in the WEB-INF directory is the web.xml file. As can be seen here, you use the webxml attribute to specify the location of this file.

As with the <jar> task, you can use one or more <fileset> elements to define the files you want to deploy in the root directory. In addition, the <classes> element defines the files that will be placed in the WEB-INF/classes directory. And the <lib> element defines the application libraries to be deployed in the WEB-INF/lib directory.

Like the <jar> task, the <war> task will generate a MANIFEST.MF file in the META-INF directory. And like the <jar> task, you can use the <manifest> element to add extra information into this file.

For more complex applications, a WAR file will not be enough. If you are developing an EJB-based application, you may need to deploy your application as an EAR file. An EAR file, like a WAR file, is an extension of the JAR file format. Instead of a web.xml file, every EAR file contains an application.xml file. The <ear> task, another extension of the <jar> task, is fairly easy to use. You simply specify the location of your application.xml file using the appxml attribute, and then use one or more <fileset> elements to indicate what files need to be bundled. For example, if you wanted to deploy the previous WAR file, plus a few particular JAR files (stored in a directory specified by the ${ear.lib} property), you could do the following:

    <target name="ear" depends="war" description="Generate EAR file">
      <ear destfile="${dist.dir}/${project.name}-${project.version}.ear"
           appxml="src/metadata/application.xml">        
        <fileset file="${dist.dir}/${project.name}-${project.version}.war" />        
        <fileset dir="${ear.lib}">
          <include name="*.jar" />
        </fileset>
      </ear>
    </target>

Deploying Your Application

Once you have generated a packaged version of your application, you will certainly want to deploy it. For example, if you are developing a web application, you may want to deploy it to a web server on your own machine, or to a remote server for testing. Or, if you are developing a shared library, you may copy your latest version to a local web server, where other users can consult the documentation and download the API.

Copying Files

The simplest way to deploy an application is to copy the packaged file to the target server. Of course, this will only work if the target server is hosted on the development machine or if the target server has a shared drive that can be mapped to from the development/build machine, and the current user has write access to these directories. Because this is generally the case for a local development machine, this approach is often a simple, pragmatic way to deploy (and redeploy) a web application to a locally running application server. You can copy a file to another directory by using the <copy> task, as shown here:

    <property name="tomcat.install.dir" location="${user.home}/servers/tomcat
     /apache-tomcat-5.5.23" />

    <target name="local.deploy" depends="war" description="Deploy to local 
    Tomcat instance">
        <copy file="${dist.dir}/${project.name}-${project.version}.war" 
              todir="${tomcat.install.dir}/webapps" />    
    </target>

In this example, we simply defined a property pointing to a local Tomcat installation, and used the <copy> task to copy the generated WAR file to the Tomcat webapps directory, where Tomcat will be able to pick it up and deploy it automatically. Many application servers work in the same way.

Of course, you may want to rename the WAR file on the way. Typically, you may want to strip off the version number when you deploy the web application so that users can simply access the application using the project name. You can do this using the tofile attribute instead of todir:

    <property name="tomcat.install.dir" location="${user.home}/servers/tomcat
     /apache-tomcat-5.5.23" />

    <target name="local.deploy" depends="war" description="Deploy to local 
     Tomcat instance">
        <copy file="${dist.dir}/${project.name}-${project.version}.war" 
              tofile="${tomcat.install.dir}/webapps/${project.name}.war" />    
    </target>

As you might expect, you aren’t limited to copying a single file. You can also use the <copy> task to copy sets of files, using the usual Ant path-like tags. For example, you might want to deploy the latest Javadoc to a local Apache web server. Suppose your Apache server’s web directory is /var/www/public_html, with a special subdirectory for each project. The Javadoc needs to be deployed to a directory called javadoc directly underneath the project directory. If you are running Ant on the same machine as the Apache server, you could deploy your Javadoc simply using the <copy> task, as shown here:

    <property name="web.dir" location="/var/www/public_html" />

    <target name="local.documentation" depends="javadoc"
     description="Deploy documentation to local web server">
        <copy todir="${web.dir}/${project.name}/javadoc">
            <fileset dir="${reports.javadoc"/>
        </copy>    
    </target>

The <copy> task is a powerful, flexible tool for file manipulation, and here we only cover its main features. Check out the Ant documentation for more details about what it can do.

Other Deployment Techniques

Ant provides many other ways to deploy your application. For example, the <ftp> task lets you deploy to an FTP server. And the <scp> task lets you deploy files using the widely used (Secure Copy) SCP protocol. A simple example of the <scp> task is shown here:

    <target name="remote.deploy" depends="war"
     description="Deploy to a remote integration server using SCP">
        <scp file="${dist.dir}/${project.name}-${project.version}.war"  
             todir="user@testserver:/home/integration/tomcatbase/webapps" 
             password="password"/>
    </target>

There are also many third-party libraries that can help you here. One tool worth investigating is Cargo,[*] from Codehaus. This powerful tool lets you deploy to (and manipulate in other ways) a wide range of application servers in a uniform manner. For example, using Cargo’s Ant integration, you can deploy your application to Tomcat, JBoss, Jetty, or a number of other servers with little or no modification to your build script.

Bootstrapping Your Build Scripts

As a rule, your build scripts should be as portable as possible. In an ideal world, a new user with a vanilla installation of Ant should be able to check out the project source code and use the build scripts immediately, as-is. No extra configuration should be required—no deploying JAR files in strange places, no setting up exotic configuration files, and so on.

Of course, in real-world projects, things often aren’t quite this simple. Your build file will often use nonstandard tasks that need to be installed. It is a tiresome task to hunt down and install the dozen or more Ant extensions that a real-world Ant build file will typically need, and issues may arise if different users have different versions of the extension libraries. This is clearly a good place to automate things.

One useful technique for doing this is writing bootstrap build files that download and install extra libraries that your project needs. This is fairly easy to do using standard Ant tasks such as <get>, <unzip>, and <available>. An example of a typical bootstrap script (called bootstrap-findbugs.xml), which downloads and installs the Findbugs package, is shown here:

<project name="FindBugs Bootstrap script" default="bootstrap" basedir="." >
    
  <!-- Define the environment-specific variable "findbugs.home" in this file. -->  
  <property file="${user.home}/ant-global.properties"/>
  <!-- This default values used if no properties file is present -->
  <property name="findbugs.home" value="${user.home}/.findbugs"/>
  <property name="findbugs.version" value="1.2.0"/>
  
  <echo>Installing FindBugs into ${findbugs.home}</echo>
  <property name="sourceforge.mirror"  
            value="http://optusnet.dl.sourceforge.net/sourceforge" />
    
  <available file="${findbugs.home}/findbugs.zip" property="findbugs.installed"/>
 
  <echo>Bootstrap FindBugs</echo>      
  <target name="bootstrap" unless="findbugs.installed">
    <echo>Installing FindBugs</echo>
    <mkdir dir="${findbugs.home}" />        
    <get src="${sourceforge.mirror}/findbugs/findbugs-${findbugs.version}.zip" 
         dest="${findbugs.home}/findbugs.zip" usetimestamp="true"/>
    <unzip src="${findbugs.home}/findbugs.zip" 
           dest="${findbugs.home}"/>
    <move todir="${findbugs.home}">
        <fileset dir="${findbugs.home}/findbugs-${findbugs.version}">
          <include name="**/*"/>
        </fileset>              
    </move>
    <delete dir="${findbugs.home}/findbugs-${findbugs.version}"/>
  </target>
  
</project>

This script will download and install the FindBugs library (see Chapter 23). Note how we load system-wide property values defined in the ant-global.properties file. We also use the <property> task to declare a default value for the ${svnant.home} property, in case no value has been defined in the global properties file.

Downloading files can be a long process, so we use the <available> task and the unless attribute to ensure that if the library has already been installed, the script will do nothing.

You now have a reusable bootstrap script that will download and install the FindBugs library. To use it, you call the bootstrap target in your main build script using the <ant> tag:

    <ant antfile="bootstrap-findbugs.xml"/>

Next, you need to define the FindBugs task using the <taskdef> tag:

    <taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask" >
        <classpath>
            <fileset dir="${findbugs.home}/lib">
              <include name="**/*.jar"/>
            </fileset>                      
        </classpath>
    </taskdef>

The exact implementation of a bootstrap script will obviously vary from library to library. Another real-world example of this technique is discussed in Using QALab in Ant.

You may run into difficulties with this approach if your build file is running behind a proxy server. There is some discussion of the options in the Ant documentation (see http://ant.apache.org/manual/proxy.html). The <setproxy> task is the most reliable, but needs the username and password to be set as propertites. If this is an issue, along with the limitations of the other options, it may be best to manually download a copy of the bootstrap files and install it on an HTTP or file server within the domain to remove the need to get through the proxy server.

Using Maven Dependencies in Ant with the Maven Tasks

One of the key features of Maven (see Chapter 2) is its use of a central repository to store dependencies and identify the libraries needed by an application. Maven 2 also supports transitive dependencies, a powerful concept that lets you limit the dependencies you need to declare to the strict minimum.

When you bundle and deploy an application, you not only need to include the libraries that your application requires, but you also need to include the additional libraries required by these libraries to work. So, if your application uses Hibernate, you will also need all of the libraries required by Hibernate. In real-world projects, this can amount to quite a list.

If your build framework supports transitive dependency management, you need only to declare the libraries that your application uses directly. The build tool will take care of the others. So, if your application uses Hibernate, you only need to state the exact version of Hibernate you are using, and not the other libraries that Hibernate needs. This makes your project dependencies simpler and easier to understand.

Ant does not support dependency management “out-of-the-box.” In Ant projects, all the libraries needed by an application are typically placed in a project directory—sometimes stored in the version control system, sometimes not (opinions vary on this point). This can create a number of problems because it is hard to know exactly which versions of libraries are required or currently being used. When libraries are stored in the version control system, projects can take up a lot of space (particularly if CVS is used), and they can take a long time to download. By contrast, if they are not stored in the version control system, some convention needs to be established to work out where to obtain the libraries needed for a given project.

A much better approach is to use one of several Ant extensions to manage dependencies declaratively. In this chapter, we will discuss the Maven 2.0 Ant tasks.

An alternative approach to dependency management in Ant is to use Ivy. Ivy is a powerful and flexible dependency management tool that integrates well into Ant projects, and it provides some interesting features as well as some nice reporting capabilities (http://incubator.apache.org/ivy/). Ivy can also leverage the rich public Maven repositories such as Ibiblio and Codehaus. Although we don’t have space to look at Ivy in this book, it may be worth a look if you are evaluating dependency management tools for Ant.

The Maven 2.0 Ant Tasks

One of the more powerful features of Maven is its ability to manage transitive dependencies (see Managing Transitive Dependencies). The Maven 2.0 project provides a library of Ant tasks that lets you take advantage of the powerful transitive dependency management features of Maven 2.0, along with the Maven 2.0 repositories, all from within your Ant project. It also lets you create Ant build files that integrate more smoothly with Maven projects, by enabling you to read a Maven 2.0 product object model (POM) file (see Declarative Builds and the Maven Project Object Model) directly from within your Ant build file.

Installing the Maven Ant Tasks

The Maven Ant Tasks come bundled as a simple JAR file, which you can download from the Maven web site. There are two main ways to install the tasks. The simplest way is to just place the JAR file into the Ant lib directory. Then, you simply add the appropriate namespace declaration to the Ant project file, as shown here:

<project ... xmlns:artifact="antlib:org.apache.maven.artifact.ant">

A more platform-independent way of installing the Ant tasks is to use a typedef declaration. This is useful if you don’t have access to the Ant installation, or if you don’t want to force your developers to install the Maven antlib manually onto each of their machines. You still need to have the Maven Ant Tasks jar file available somewhere, but you can now place it in some commonly available location, such as in your version control system. In the following code sample, we assume that the Maven Ant Tasks JAR file is stored in the project lib directory:

<project name="tax-calculator" default="package" xmlns:artifact="urn:maven-
artifact-ant">
   ...
   <property name="maven.antlib.version" value="2.0.7" />
   <path id="maven-ant-tasks.classpath" path="lib/maven-ant-tasks-
    ${maven.antlib.version}.jar" />
   <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri=
    "urn:maven-artifact-ant"
            classpathref="maven-ant-tasks.classpath" />

Declaring and Using Maven Dependencies in Ant

Once you have declared the antlib library, you can declare your project dependencies as you would in a Maven project. A typical list of dependencies might look like this:

    <artifact:dependencies pathId="compile.classpath">
        <dependency groupId="commons-logging" artifactId="commons-logging" version="1.1"/>
        <dependency groupId="log4j" artifactId="log4j" version="1.2.9" />
        <dependency groupId="junit" artifactId="junit" version= />
        ...
    </artifact:dependencies>

For Maven users, the dependencies will look very familiar. The groupId, artifactId, and version attributes uniquely identify each dependency within a Maven repository. As with Maven, dependencies are downloaded as required and stored in the user’s home directory, under ${user.home}/.m2/repository.

You use the pathId attribute to refer to the dependencies in other Ant tasks. For example, you could refer to these libraries in the Java compiler task as follows:

    <target name="compile" depends="init" description="Compile Java code">
        <echo message="Debug: ${javac.debug}" />
        <javac srcdir="${src.dir}" 
               destdir="${build.classes.dir}" 
               classpathref="compile.classpath" 
               debug="${javac.debug}"/>
    </target>

Or, of course, you can include the dependencies within another broader classpath variable:

    <path id="java.classpath">
        <path refid="compile.classpath" /> 
        <pathelement location="${java.classes}" />
        <pathelement location="${java.resources}" />
    </path>

Packaging the Dependencies

When you deliver your application, you generally need to bundle it up into a deployable package. This may take the form of a WAR file, an EAR file, a ZIP file, or some other kind of format. Whatever the format, you will probably want to include the project dependencies.

The Maven Ant tasks let you do this fairly easily, using the filesetId attribute. When you specify a filesetId for your dependency list, you will be able to refer to the dependency list as an ordinary fileset. The following listing shows how you can copy the project dependencies into the WEB-INF/lib directory:

    <artifact:dependencies pathId="dependency.classpath" filesetId="dependency.
fileset" useScope="runtime">
      <dependency groupId="commons-logging" artifactId="commons-logging" 
       version="1.1"/>
      <dependency groupId="javax.persistence" artifactId="persistence-api" 
       version="1.0"/>
      <dependency groupId="log4j" artifactId="log4j" version="1.2.9" />
      <dependency groupId="junit" artifactId="junit" version="3.8.1" 
              scope="test" />
      <dependency groupId="javax.servlet" artifactId="servlet-api" version="2.4" 
       scope="provided" />
      ...
    </artifact:dependencies>
    ...
    <target name="package">
        <mkdir dir="${build.dir}/WEB-INF/lib" />
        <copy todir="${build.dir}/WEB-INF/lib">
            <fileset refid="dependency.fileset" />
            <mapper type="flatten" />
        </copy>    
    </target>

The useScope attribute lets you limit the jars you want to deploy to the strict minimum. In Maven, the notion of scope lets you define which dependencies should be used at different stages in the build lifecycle (compile, test, deploy, and so on—see Dependency Scope). For example, in the listing shown here, the junit jars will not be necessary in the production environment, and the Servlet API libraries will already be provided by the application server. By declaring this dependency list to use the runtime scope, we can avoid having to bundle the junit jars (which are scoped to test) and the servlet-api jars (scoped to provided).

One limitation of this approach is that a given dependency list can only have one useScope qualifier.

Choosing Your Repositories

By default, the Maven Ant task will use the standard Maven 2 repository to resolve your project’s dependencies (http://repo1.maven.org/maven2). But you may want to use different, or additional, repositories. For example, you may want to first look in your local company Maven 2 repository. To do this, just declare a remote repository in your dependency list using the <artifact:remoteRepository> tag:

  <artifact:dependencies>
    ...
    <artifact:remoteRepository id="remote.repository" 
     url="http://repository.mycompany.com/" />
  </artifact:dependencies>

Using an Existing Maven POM File

The Maven Ant Tasks also let you lever an existing Maven POM file from within Ant. This can be useful if you need to store information, such as project and artifact names and versions in a central place (the POM file), or if you want to use the Maven build directories in the Ant build file.

You can set up a reference to your POM file using the <artifact:pom> tag, as shown here:

  <artifact:pom id="maven.project" file="pom.xml" />

From then on, you can refer to objects and fields in the Maven project structure using a JSTL-style expression language:

        <echo>Building project ${maven.project.name} version ${maven.project.version}
        </echo>    
        <echo>Application classes directory: ${maven.project.build.outputDirectory}
        </echo>    
        <echo>Test classes directory: ${maven.project.build.testOutputDirectory}
        </echo>

This would produce something along the following lines:

     [echo] Building project planestore-core version 1.0-SNAPSHOT
     [echo] Application classes directory: /home/john/projects/jpt-sample-code
           /planestore/planestore-core/target/classes
     [echo] Test classes directory: /home/john/projects/jpt-sample-code
           /planestore/planestore-core/target/test-classes

Using Ant in Eclipse

Ant is well-supported in virtually all modern Java IDEs, and Eclipse is no exception. Eclipse allows you to create a new Eclipse project using an existing Ant file, and recognizes the structure of Ant build files. The Outline view gives you a structured vision of your build file. In addition, you can execute any target directly from within Eclipse using the contextual menu (see Figure 1-4).

Using Ant in Eclipse
Figure 1-4. Using Ant in Eclipse

Using Ant in NetBeans

Ant integrates smoothly into NetBeans. Indeed, by default, NetBeans uses Ant internally to organize your project, even if you don’t ask it to. NetBeans automatically recognizes Ant build files and displays the build file targets. As in Eclipse, you can execute targets directly using the contextual menu (see Figure 1-5).

Using Ant in NetBeans
Figure 1-5. Using Ant in NetBeans

Manipulating XML with XMLTask

Contributed by: Brian Agnew

For simple text search and replace operations, the Ant <replace> task is sufficient. But in modern Java frameworks, you are more likely to need powerful XML manipulation capabilities to modify servlet descriptors, Spring configurations, and the like.

XMLTask is an Ant external task that provides powerful XML editing tools focused on creating and changing XML files as part of a build/deployment process.

What are the advantages of using XMLTask?

  • Unlike the Ant task, <replace> XMLTask gives you the ability to identify parts of an XML document using XPath, and to insert, remove and copy XML at these locations. You can use XPath to simply identify an XML element, or use more complex logic with predicates (“find me the element called ‘X’ with an attribute of ‘Y’…”).

  • XMLTask is “XML-aware.” This means that you can’t create an XML document that isn’t well-formed. XMLTask will handle character-encoding issues, whereas <replace> has no knowledge of the encoding requirements of your XML documents. For example, <replace> will allow you to insert the characters “<,” “>,” and “&” into an XML document without using the corresponding entities (“<” “>” and “&”), and thus possibly break the “well-formedness” of your document.

  • XMLTask doesn’t require you to learn or use XSLT to perform XML manipulations. It uses intuitive instructions such as insert, replace, and remove.

XMLTask is easy to use. Take a look at the XMLTask home page,[*] or download from Sourceforge.[] You don’t need to be knowledgeable about XPath to use XMLTask, but if you need an introduction, take a look at the tutorial on http://www.zvon.org.[]

Examples

Let’s look at a simple example. Imagine you have a Spring configuration that you want to modify. For instance, you may be making changes for development, test, and release versions, and want to perform insertions, replacements, and removals.

A simple XMLTask is shown below:

<project name="xmltask-demo" default="main">
  <!--xmltask.jar should be referenced via lib, or in the ${ant.home}/lib or similar 
   --> <taskdef  name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask"/>

  <!-- you may need to reference a local copy of the DTD here if your XML
         documents specify one. See below for more info -->
  <xmlcatalog id="dtd"> 
     <dtd 
        publicId="-//SPRING//DTD BEAN//EN"
        location="./spring-1.0.dtd"/> 
  </xmlcatalog> 

  <target name="main">
    <xmltask source="spring-template.xml" dest="spring.xml" preserveType="true">
      <xmlcatalog refid="dtd"/> 
      <insert path="/beans" position="under">
          <![CDATA[
            <bean id="bean-to-insert" class="com.oopsconsultancy.example.Bean1">
                <constructor-arg index="0">
                  .....
                </constructor-arg>
            </bean>
           ]]>
      </insert>
    </xmltask>
  </target>
</project>

You reference the XMLTask task as you would any external task, using <taskdef>.

In the <xmltask> task, you specify a source XML file and a destination XML file. XMLTask will read from the source XML, apply any instructions you’ve configured XMLTask with, and then write the XML to the destination file.

Each instruction identifies a set of matching XML elements (using XPath) and performs an action on each of these. For example, an <insert> instruction will perform an insert on all matching XML elements specified by its XPath (using XPath you can restrict this to the first match, the last match, and so on).

Sets of instructions are applied sequentially. So, you can specify inserts followed by replacements, followed by removals, etc.

The above example inserts a Spring bean definition under the <beans> root element in the spring-template.xml file, and writes it out to spring.xml. Suppose your spring-template.xml is an empty configuration like the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd
/spring-beans.dtd">
<beans>
</beans>

After running the <xmltask> task listed above, your spring.xml will look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd
 /spring-beans.dtd">
<beans>
   <bean id=”bean-to-insert” class=”com.oopsconsultany.example.Bean1” 
    dependency-check="default"
    lazy-init="default" singleton="true">
        <constructor-arg index=”0”>
          ......
        </constructor-arg>
   </bean>
</beans>

Note that attributes specified in the DTD with default values will be generated and inserted in the output XML (with their defaults appropriately set—e.g., “dependency-check,” “lazy-init,” “singleton”).

You don’t have to specify your XML in the Ant build. You can reference it from a file. For example, you can store the bean definition in a file called development-bean.xml and use the code below:

<insert path="/beans" position="under" file="development-bean.xml">

to insert the contents of development-bean.xml into your Spring configuration.

So far this is relatively straightforward, but you can perform more complex manipulations. For example, if you want to modify the login details for a Spring-configured data source bean that looks like:

<bean id="ExampleDataSource" class="org.apache.commons.dbcp.BasicDataSource" 
destroy-method="close">
    <property name="driverClassName" ref="db-driver-name"/>
    <property name="url" value="...."/>
    <property name="username" value=""/>
    <property name="password" value=""/>
</bean>

You can use <replace> operations to insert the username and password from properties ${dev.username} and ${dev.password}:

<xmltask source="spring-template.xml" dest="spring.xml" preserveType="true">
  <replace path="/beans/bean[@id='ExampleDataSource']/property[@name='username']/@value"
   withText="${dev.username}"/>
  <replace path="/beans/bean[@id='ExampleDataSource']/property[@name='password']/@value"
   withText="${dev.password}"/>
</xmltask>

Note that in this example you’re using XPath to specify which bean to change (ExampleDataSource) by using a predicate. The XPath expression says “find the bean with a given id, and find the property under that with a given name,” allowing you to change attributes for particular elements.

You can remove XML as well. For instance, you may want to remove all your test beans:

<remove path=”/beans/bean[contains(@id, ‘Test’)]”/>

This removes all beans that have “Test” in their id from your Spring configuration.

DTDs and XMLTask

In the above example we’ve specified that a local version of the DTD. XMLTask needs access to a DTD, if specified in the source document, to perform entity replacements. If you have a direct connection to the Internet then XMLTask (and other tools) may get the DTD transparently. However, if you don’t have a direct connection to the Internet, or if speed is an issue, you’ll need to specify a local copy (or tell XMLTask that one isn’t available).

This is straightforward. Simply specify an Ant <xmlcatalog>. For example, the code below specifies a Servlet DTD and a local copy:

<xmlcatalog id="dtd"> 
   <dtd publicId="-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
      location="./servlet-2.3.dtd"
   /> 
</xmlcatalog>

which specifies a local copy of the DTD (servlet-2.3.dtd) with the given public ID.

And then reference this within the <xmltask> invocation:

<xmltask 
   source="src/web.xml" dest="target/web.xml" 
   preserveType=”true”>
  <xmlcatalog refid="dtd"/> 
    .....

What DTD should your output document use? This depends on how you’re manipulating your source document. In most cases, the target document will match the source document’s DTD. In this scenario, you can tell XMLTask to generate a DTD instruction in your target document that matches that of your source document:

<xmltask 
   source="src/web.xml" dest="target/web.xml" 
   preserveType=”true”>

In other scenarios (you may be creating a document from scratch or heavily changing the source document), you’ll want to specify the DTD public and system identifiers:

<!-- we're creating a 2.3 web.xml document from scratch --> 
<xmltask 
   source="src/web.xml" dest="target/web.xml" 
   public="-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
   system="http://java.sun.com/dtd/web-app_2_3.dtd"> 

Driving Ant via XMLTask

You can use XMLTask to read an XML file, and use this to call different Ant targets for each occurrence of a specified XML element. Thus, you can drive parts of your build via an external configuration file. This file could represent (for example) environments that you want to build, classes that you need to test, cataloges of files you need to process, etc.

For instance, imagine you have a set of test classes that you need to run. These are encapsulated in one configuration XML:

<environments>
   <env name="Test Scenario 1" enabled="true">
     <class>com.oopsconsultancy.example.TestScenario1</class>
     <db>database1</db>
    <results>development/test/scenario1.txt</results>
   </env>
   <env name="Test Scenario 2" enabled="true">
     <class>com.oopsconsultancy.example.TestScenario2</class>
     <db>database2</db>
    <results>development/test/test_data_2.txt</results>
   </env>
</environments>

Each environment has a test class, a test database, and a results text file.

You can use XMLTask to iterate over this file, and execute each test class to perform the appropriate tests:

<!-- XMLTask only needs a source here, since it’s only reading -->
<xmltask source="environments.xml">
   <call path="/environments/env[@enabled='true']" target=”execute-tests">
      <param name="class" path="class/text()"/>
      <param name="db" path="db/text()" default="devDb"/>
      <param name="results" path="results/text()"/>
   </call>
</xmltask>

<target name="execute-tests">
   <echo>Running ${class} against ${db}, results in ${results}</echo>
   <!-- run the appropriate tests -->
</target>

For each XML element identified by /environments/env (where enabled is “true”), XMLTask will call the Ant target “execute-tests”. Properties are set for each Ant target called using the contents of the XML file being read. Each time it calls “execute-tests,” it will set the ${class} property to the class specified for that XML element, the ${db} property to the database specified for that element, and the ${results} property to the results file required.

If you run the above, you’ll see:

Running com.oopsconsultancy.example.TestScenario1 against database1, results in
 development/test/scenario1.txt
Running com.oopsconsultancy.example.TestScenario2 against database2, results in
 development/test/test_data_2.txt

Other Tricks

Changing encodings

You can trivially change the character encoding of an XML file:

<xmltask source="windows-encoded.xml" dest="16bit-unicode-encoded.xml"
encoding="UnicodeBig"/>

(UnicodeBig is the encoding code for 16-bit Unicode encoding (big-endian). See http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html for supported encodings). This will convert your XML document to a 16-bit Unicode-encoded document on output. Note that you don’t have to define any instructions, since XMLTask is simply reading the document in and writing it out.

Maintaining documents with comments

You can use XMLTask to uncomment sections of your XML files. This means you can maintain one configuration file with multiple commented sections, and simply uncomment the section you require at deployment time. For example:

<configurations>
  <!--
  <configuration env="dev">
  ….
  </configuration>
  -->
  <!--
  <configuration env="test">
  ….
  </configuration>
  -->
  <!--
  <configuration env="prod">
  ….
  </configuration>
  -->
</configurations>

<!-- 
<xmltask source="source.xml" dest="dest.xml" >
  <uncomment path="/configurations/comment()[2]"/>
 ...
</xmltask>

This enables the second commented block (beware: XPath indexes elements from element one on, not zero!). So each of your deployed documents will have the same sections present, but only one needs to be uncommented. This can make your life a lot easier when you have to compare different deployed versions and the differences between them.

Conclusion

Using XMLTask, you can maintain, create, and modify XML files with a tool that is much more powerful than the standard <replace> or file creation tasks, and yet not have to worry about using XSLT. We’ve not covered many of its functions here. See the home page (http://www.oopsconsultancy.com/software/xmltask) for more information and examples. A mailing list (subscription only) is also available (https://lists.sourceforge.net/lists/listinfo/xmltask-users).



[*] The Ant team recommends Java 1.5 or later, mentioning that less tasks will work correctly with older versions of Java.

[*] I first came across this technique in the excellent Ant In Action (Manning Publications), by Steve Loughran and Erik Hatcher.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required