Ant write advanced Task

write an advanced apache ant task, that perform operations with file set and relative paths

  1. ant write advanced task
    1. Ant Task using filesets
      1. skeleton
      2. validate() and execute()
      3. test
    2. Relativize filepaths
      1. add property
      2. modify validate()
      3. modify the execute()
    3. download code and test
    4. References

ant, task, apache, advanced task, filesets

ant write advanced task

Here we show how to write an advanced ant task that search inside a FileSet , and that can return found files giving their path, relative to a given base path.

  • First we write and test a simple task using filesets
  • then we improve the task by managing relative paths.

At the end you can download all source code as a zipped project.

Ant Task using filesets

Suppose you want to write a task that search for a filename, over a large set of files.
Ant provides <fileset/> to represent a collection of files.
So, to follow standards, your task should looks like the following.

<my-advanced-task find="foobar" outproperty="found.list">
	<fileset dir="input" includes="**/*" />
</my-advanced-task>

in my idea the <my-advanced-task/> search for files whose contain “foobar” in the name, and write the list of matching files into the “found.list” ant property.

skeleton

Ant provides a common way of bundling files: the fileset. To build the java class modeling the task we write a skeleton class like following

/** My advanced ant task that manage filesets */
public class MyAdvancedTask extends Task{
	private String find;
	private String outproperty;
	private Vector filesets = new Vector();
	
	public void setFind(String find){
		this.find = find;
	}
	
	public void setOutproperty(String outproperty){
		this.outproperty = outproperty;
	}
	
	public void addFileset(FileSet fileset){
		filesets.add(fileset);
	}
}

Note, the Vector filesets, allow to add more than one fileset to your task.

validate() and execute()

Before to execute() we need a validate() method that checks preconditions, and if some of them are not respected will throw a BuildException

	/** Validate passed parameters */
	public void validate(){
		if( find==null) throw new BuildException("find string not set");
		if( outproperty==null) throw new BuildException("out property not set");
		if( filesets.size()<1) throw new BuildException("fileset not set");
	}

Now we can write the execute() method, that make a search on the Vector of filesets added.

	/** executes search operation on filesets.*/
	@Override
	public void execute(){
		validate();
		List matchingFiles = new ArrayList();
		
		// we iterate over one or more <fileset> elements 
		for(Iterator it = filesets.iterator(); it.hasNext(); ){
			FileSet fs = (FileSet) it.next();
			
			// refer the result of fileset via the directory scanner
			DirectoryScanner ds = fs.getDirectoryScanner(getProject());
			String[] includedFiles = ds.getIncludedFiles();
			for( int i=0; i < includedFiles.length; i++ ){
				
				// get a platform-independent representation of file
				String filename = includedFiles[i].replace('\\','/');
				filename = filename.substring(filename.lastIndexOf("/")+1);
				
				// try to match each filename with the "find" expression
				if( filename.contains( find ) ){
					File base = ds.getBasedir();
					File found = new File(base,includedFiles[i]);
					
					// add the full path to the list 
					matchingFiles.add( found.getAbsolutePath() );	
					
				}
			}
		}
		
		// write the list of matches on the given property  
		String outString = "[]";
		if( matchingFiles.size() > 0 ){
			outString = Arrays.toString( matchingFiles.toArray() );
		}
		getProject().setNewProperty(outproperty, outString );
	}
	

Following comments you see main points of execution:

  • validate() input data

test

Suppose we have an input directory tree like the following image

To test the above task we write a small buildfile

<project name="MyAdvancedTask" default="test">
	
	<target name="use.init">
		<taskdef name="my-advanced-task" classname="tut.ant.task.advanced.MyAdvancedTask" classpath="${dir.lib.generated}/${ant.project.name}.jar"/>
	</target>
	
	<target name="use" depends="build,use.init">
		<!-- suppose the "input" folder contains files to search -->
<my-advanced-task find="foobar" outproperty="found.list">
	<fileset dir="input" includes="**/*" />
</my-advanced-task>
		<!-- output found matching files -->
		<echo>${found.list}</echo>
	</target>
	
</project>

The resulting output follows

<code>use.init:
use:
     [echo] [E:\my\work\space\tut.java-ant\input\Dir-1\Dir-1.1\Dir-1.1.1\foobar.xml, E:\my\work\space\tut.java-ant\input\Dir-1\Dir-1.2\hell.foobar, E:\my\work\space\tut.java-ant\input\Dir-2\foobar.txt]

The ouput looks correct, but verbous. In next section we make a small change to relativise paths in output

Relativize filepaths

Now we add a small improvement to the avdanced task. We want that the output file list is relativized with respect to a given basepath.

Finally, we want to call the task with following signatire

<my-advanced-task find="foobar" basepath="input" outproperty="found.list">
	<fileset dir="input" includes="**/*" />
</my-advanced-task> 

Then we need to:

  • add a property basedir to the task
  • add a condition to the validate() method
  • modify the execute() method by relativizing output

add property

We add a property and its setters.

	private File basepath;
	/** set the relative basepath, to use when output filenames */
	public void setBasepath(File basepath) {
		this.basepath = basepath;
	}

Since the property is a file, ant convert automagically the given string to a file

modify validate()

now the validate() needs a check on the condition: if basepath is defined, and does not match a directory, so throw a build exception

	public void validate(){
		if( basepath!=null && ! basepath.isDirectory() ) throw new BuildException("basepath defined, but is not a valid directory");
		// .. other conditions 
	}

modify the execute()

The execute method now, take into account the new basepath and, if it is defined, use the method File.toURI().relativize(.) to get relative path. If the basepath is not defined, we want the same old behaviour.

So, simply we remove the old line of code:

	// .. the rest of code remains the same
	matchingFiles.add( found.getAbsolutePath() );	

and replace it with following snippet

	// if given a basepath the file path is relativized
	if( basepath!=null ){
		matchingFiles.add( basepath.toURI().relativize(found.toURI()).getPath() );	
	} else {
		matchingFiles.add( found.getAbsolutePath() );	
	}

Note, we mantain the old behaviour if the basepath is not defined.

download code and test

You can download and test the code easily:

  • download archive tut.java-ant.zip, then unzip to a folder
  • from command prompt, move to folder, and invoke ant -buildfile build.java-ant.xml use
  • look at the full process of build and use of the advanced task

References