Creating custom ANT plugin or Task in Java

ANT is very powerful and one of the most used tool in Java and other platforms like Salesforce. In Salesforce, ANT is officially used to deploy changes on instances using Metadata API. It is also integral part of Continuous Integration. Even though, there are hundreds of libraries and ANT Tasks are available to use, however there are many scenarios where we need to create our own custom Task.

I am using many custom ANT tasks in my projects and its very easy to create. For example, I have created automated data load process with custom ANT task, which is almost as powerful as of any paid ETL tool.

In this blog post, We will go through creation of very simple ANT plugin. We will create a custom ANT Task with nested tags , attributes and then print it on console. It shows that, we can read attribute passed in build.xml. if we can read attribute, then writing any custom logic is not a big deal.

We are planning to build below ANT task

<DemoTask description="Sample Attribute at Task level"> 
	<DemoEntry path="c:\\FilePath\\File.txt" isFolderPath="false" />
	<DemoEntry path="c:\\Folder" isFolderPath="true" />
</DemoTask>

Below image shows working of custom ANT plugin.

Create custom ANT Task in Java
Create custom ANT Task in Java

Task name would be DemoTask. It will have attribute named description. Multiple child tags by name DemoEntry is used to showcase how we can read child tags in custom ANT task.

We need to add ANT library in class-path of our project, so that we can reuse its library.

First step is to create a simple POJO (Plain Old Java Object) class to represent nested tag DemoEntry. This class will have properties with setter and getters. In this case, we need two properties – “path” and “isFolderPath” as shown in below class.

DemoEntry.java

/*
 * @Author  :   Jitendra Zaa
 * @Date    :   03/18/2016
 */
package com.jitendrazaa;
 
import java.util.Objects;

public class DemoEntry {
 
    String path ;    
    Boolean isFolderPath ;

    public DemoEntry(){}
    
    public DemoEntry(String path, Boolean isFolderPath) {
        this.path = path;
        this.isFolderPath = isFolderPath;
    } 

    public String getPath() {
        return path;
    }

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

    public Boolean getIsFolderPath() {
        return isFolderPath;
    }

    public void setIsFolderPath(Boolean isFolderPath) {
        this.isFolderPath = isFolderPath;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 71 * hash + Objects.hashCode(this.path);
        hash = 71 * hash + Objects.hashCode(this.isFolderPath);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final DemoEntry other = (DemoEntry) obj;
        if (!Objects.equals(this.path, other.path)) {
            return false;
        }
        if (!Objects.equals(this.isFolderPath, other.isFolderPath)) {
            return false;
        }
        return true;
    } 
}

Its time to create class which will act as custom ANT Task.
DemoTask.java

/*
 * @Author  :   Jitendra Zaa
 * @Date    :   03/18/2016
 */
package com.jitendrazaa;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Iterator;

import org.apache.tools.ant.Task;

public class DemoTask extends Task {

    HashSet<DemoEntry> demoEntryList = new HashSet<DemoEntry>();
    String description ;
    
    /**
     * print Stacktrace from Exception
     * @param e
     * @return 
     */
    private String getExceptionMessage(Exception e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }

    /**
     * Create Method needed by Ant reflection
     *
     * @return
     */
    public DemoEntry createDemoEntry() {
        DemoEntry item = new DemoEntry();
        demoEntryList.add(item);
        return item;
    }

    /**
     * We cannot use original "demoEntryList" because it is created with blank
     * value and using java reflection values are populated. Hashset method is
     * computed during constructor and any change in value after constructor
     * will not change Hashset.
     *
     * @return HashSet<DemoEntry>
     */
    private HashSet<DemoEntr> cloneIgnoreSet() {
        HashSet<DemoEntry> newSet = new HashSet<DemoEntry>();
        for (Iterator<DemoEntry> it = demoEntryList.iterator(); it.hasNext();) {
            DemoEntry item = it.next();
            DemoEntry newItem = new DemoEntry(item.getPath(), item.getIsFolderPath());
            newSet.add(newItem);
            log(" ------- Reading sub tag -------- ");
            log("Path - "+item.getPath());
            log("Is Folder Path - "+item.getIsFolderPath());
        }
        return newSet;
    }

    public void execute() {
        try {
            log("Description - "+getDescription());
            //Read set of tags from ANT Build script
            HashSet&lt;DemoEntry&gt; setDemo = cloneIgnoreSet();
        } catch (Exception e) {
            log(getExceptionMessage(e));
        }
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

}

This custom ANT task class needs to extend org.apache.tools.ant.Task available in ANT library. Method createDemoEntry will be called by ANT to initialize nested tags. Method getDescription and setDescription is used to get and set attribute named Description. Remaining part of code is well documented.

We can build above classes as a jar file either from Eclipse, Netbeans or Java command.

To test this custom ANT task, lets create build.properties file. It is not necessary to create this in our example, however there is no harm in following best practices. This file has complete path of custom jar file.

build.properties

pluginFileName=ANT_Plugin.jar

Build.xml

<project name="Demo ANT Plugin" default="Demo" basedir=".">  
    <property file="build.properties"/> 
	<target name="Demo">  
		<taskdef name="DemoTask" classname="com.jitendrazaa.DemoTask" classpath="${pluginFileName}"/>
		<DemoTask description="Sample Attribute at Task level"> 
            <DemoEntry path="c:\\FilePath\\File.txt" isFolderPath="false" />
            <DemoEntry path="c:\\Folder" isFolderPath="true" />
		</DemoTask>
	</target>     
</project>  

We can run above build file by either using command ANT or ANT Demo. Demo image above shows final output and custom ANT task in action. Complete source code is available on my Github account.

Posted

in

by

Tags:


Related Posts

Comments

One response to “Creating custom ANT plugin or Task in Java”

  1. Jagan Challa Avatar
    Jagan Challa

    ANT is so old. Salesforce should upgrade it’s tooling. Gradle should be used instead.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Jitendra Zaa

Subscribe now to keep reading and get access to the full archive.

Continue Reading