Ant Sequential Tasks

This article will show you how to implement an Ant task which consume a sequence of sub-task and call them with a parameter of the result from the execution.

As an example we can consider a task taking a path to a directory as a parameter, fetching files in the directory and running a sequence of sub-task for each file

The task will be implemented as a Java library


Considering the example, we will have in our Ant script something like:

<fetch-files path="/Windows" suffix=".exe">
    <sequential>
        <echo>My file print: @{file}</echo>

        <antcall target="process-file">
            <param name="filename" value="@{file}" />
        </antcall>
    </sequential>
</fetch-files>

This should process all the .exe files in the /Windows directory and print the name of the file before processing. 

Implementation in Java 

Let's create a new Maven project, all we need is a dependency to the Ant API:

<dependency>
    <groupId>org.apache.ant</groupId>
    <artifactId>ant</artifactId>
    <version>1.9.7</version>
</dependency>

Sequential Task 

First of all we will create an abstract class which allows us to put the <sequential> part into the task:

abstract class SequentialTask extends Task {

    private MacroDef macroDef;
    private Target owningTarget;

    abstract String getAttributeName();

    @Override
    public void setOwningTarget(Target owningTarget) {
        this.owningTarget = owningTarget;
    }

    public Object createSequential() {
        macroDef = new MacroDef();
        macroDef.setProject(getProject());

        MacroDef.Attribute attribute = new MacroDef.Attribute();
        attribute.setName(getAttributeName());
        macroDef.addConfiguredAttribute(attribute);

        return macroDef.createSequential();
    }

    void executeSequential(String attrValue) {
        MacroInstance instance = new MacroInstance();
        instance.setProject(getProject());
        instance.setOwningTarget(owningTarget);
        instance.setMacroDef(macroDef);
        instance.setDynamicAttribute(getAttributeName(), attrValue);
        instance.execute();
    }
}

All the task exending the class must define the declared getAttributeName() method which returns the name of the attribute ("file" in our example).

Then we cann call the method executeSequential(String attrValue) for each item in the task result.

Fetching Files Task 

Our concrete task implementation could look like:

public class FetchFilesTask extends SequentialTask {

    private static final String ATTR_NAME = "file";

    private String path;
    private String suffix;

    public void setPath(String path) {
        this.path = path;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    @Override
    String getAttributeName() {
        return ATTR_NAME;
    }

    @Override
    public void execute() { 
        if (path == null || path.trim().isEmpty()) {
            throw new BuildException("Parameter 'path' must be specified.");
        }

        List<String> filesList = getFilesList(Paths.get(path), suffix);

        for (String file : filesList) {
            executeSequential(file.toString());
        }
    }

    List<String> getFilesList(Path sourcePath, String suffix) {
        final List<String> toReturn = new ArrayList<>();

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourcePath)) {
            for (Path file: stream) {
                if (Files.isRegularFile(file)) {
                    if (suffix == null || file.toString().endsWith(suffix)) {
                        toReturn.add(file.toString());
                    }
                }
            }
        }
        catch (IOException | DirectoryIteratorException e) {
            throw new BuildException("Error by reading '" + sourcePath + "'.", e);
        }
        return toReturn;
    }
}

The class extends the SequentialTask, defines its abstract method and use the executeSequential method.

Additionaly defines the class two task parameter path and suffix by the defining the getters for them. 

Using the task in an Ant script

To call the task in an Ant script as showned in the example we have to provide the JAR library and define the task by its class:

<taskdef name="fetch-files" classname="cz.net21.ttulka.ant.FetchFilesTask" classpath="AntTasks-1.0.jar" />

You can download the compiled JAR library or whole project sources.

Have fun!