This article is a continuation of the article “ Introduction to Custom Ant Tasks.” Reading through the previous article is highly recommended because I am just going to pick up where I left off last time. For this reason, my examples in this article will start numbering at five (5). I am assuming you have a moderate level of experience with Ant on your platform-of-choice. (I use Windows 2000 during the day and Mac OS X when I’m at home.) Also, my code examples use some of the newer Java syntax bits such as enhanced-for and generics.
I left off in the first article just shy of mentioning nested elements. My reason for deferring the topic was that I wanted enough material and explanations to be valuable. However, before we get into the actual mechanics of nested elements, let’s take a moment to cover custom types.
Topics in this article
- Types versus Tasks
- Custom Condition Types
- Handling Specific Nested Types
- Handling Arbitrary Nested Types
- Task with Nested Tasks
- Nesting Tasks and Conditions
- Custom Selectors
- XML Namespaces
Types versus Tasks
Types are anything that lack an execute() method. This means that Ant will not attempt to call the execute() method when it encounters them. Instead, the parent element will interact with the type via some appropriate method calls which are entirely up to the developer to provide. This being the case, types serve as auxillary helpers for other tasks, usually as nested child elements. You may note that Ant does provide the org.apache.tools.ant.types.DataType class from which many of Ant’s own types derive. However, it is perfectly fine for your own tasks to (implicitly) derive directly from java.lang.Object. (Also, although I have yet to personally verify this, I suspect a publicly accessible inner class might also work, provided the typedef is correctly set.)
Ant maintains a distinction between tasks and types to the point that you declare types with a typedef task instead of a taskdef task. For the sake of the example, this means you will list your custom types in a second properties file.
Aside from the setter methods to support attributes in the build file, other methods you provide are at your discretion. Indeed, the whole reason for the type to exist might be limited to the support of exactly one other task or type, or it might be a more generic and flexible type that you use across multiple tasks or types. Ant, for example, has the fairly specific arg element underneath the exec task and the fairly generic fileset element which is supported throughout many Ant tasks which need to deal with files. There are even a few special-purpose categories of types in Ant, the condition type (not to be confused with the <condition> task itself which contains nested condition types) being notable.
Custom Condition Types
Ant has a task called condition, but the various elements which nest underneath it are condition types. Some examples familiar to Ant script writers include isset (for properties) and available (for files and resources) as well as the logicals and and or and the negation not. It may happen that you need some logic to happen based on a condition which is either exceedingly cumbersome to write with the standard condition types, or which is just not possible. Writing a custom condition type is almost identical to writing a custom task:
- Implement org.apache.tools.ant.taskdef.condition.Condition interface
- Implement public boolean eval() method
- Provide standard instance variables and setter methods for attribute support, if needed
Remember when I said the parent element interacts with a type element via some appropriate method? For a condition type, this is the role of the eval() method which returns a boolean value.
Now, once your Java source code is written, create a properties file to map your Java class to an Ant element name. Do not list your custom type in the same properties file you are using for custom tasks; they need to be referenced by separate typedef and taskdef task statements, respectively.
Example 5: A Numerical “Greater Than” Condition
The standard conditions do not support checking numerical-based comparisons such as greater than, less than, or equal to. This is how a numerical greater than condition would work:
//NumGreaterThanCondition.java package org.roblybarger; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.taskdefs.condition.Condition; public class NumGreaterThan implements Condition { private double arg1; private double arg2; public double getArg1() { return arg1; } public void setArg1(double arg1) { this.arg1 = arg1; } public double getArg2() { return arg2; } public void setArg2(double arg2) { this.arg2 = arg2; } public boolean eval() throws BuildException { return (arg1>arg2); } }
Create MyAntTypes.properties (if necessary) and add this entry:
numgt=org.roblybarger.NumGreaterThanConditionUpdate your jar file, and then use an Ant script similar to this to put the custom condition to use:
<project name="ex5" default="demo" basedir="."> <typedef resource="MyAntTypes.properties" classpath="myant.jar"/> <target name="demo"> <property name="valueA" value="10.34"/> <property name="valueB" value="9.52"/> <fail message="${valueA} is NOT greater than ${valueB}"> <condition> <not> <numgt arg1="${valueA}" arg2="${valueB}"/> </not> </condition> </fail> <echo message="valueA is greater than valueB"/> </target> </project>
Something like this might be useful if you wanted to verify that you have sufficient disk space before attempting to copy a large file. (The OS-specific details of computing available disk space are left as an exercise to the reader.) You also might employ custom conditions elsewhere to perform sanity checks or enforce business logic rules at various places in your build. Providing a collection of small, focused conditions is better than one monolithic condition: The smaller condition types can be reused more easily, and overall maintenance is reduced to minor updates your ant script should your business rules change slightly.
Handling Specific Nested Types
To support nesting a given type element under your task, you must know the Java class name that corresponds to that type element. Let us consider this hypothetical case: You are writing code for a Java class called "CustomTask". You want to support a nested element <blah> in your Ant script, and that the supporting Java class is named FooTypeClass. (I am intentionally mismatching the element name and the class name to improve clarity.) You must provide one (but only one) of the following three methods to get a reference to the FooTypeClass object:
- public FooTypeClass createBlah() {...}
You assume responsibility to instantiate the FooTypeClass object and pass the reference to Ant. - public void addBlah(FooTypeClass ftc) {...}
Ant instantiates the FooTypeClass object and passes the reference via this method; then, it configures the object with its attributes and nested elements, if any. - public void addConfiguredBlah(FooTypeClass ftc) {...}
Ant instantiates the FooTypeClass object and configures it with its attributes and nested elements, if any, and then passes the reference via this method.
Personally, I use the second option, but this is mainly out of laziness. (Larry Wall, creator of the Perl language, cites laziness as the first of a programmer's three great virtues.) More practically, there may be situations where you should use one form instead of the others, but—usually—it doesn't matter.
Note that what does matter is that the exact name of the method you provide to support your custom element should match the name of the nested element and it should take as argument whose type is that of the supporting Java class for that nested element. (A more generic alternative will be shown below.) Also note that these methods aren't inherited by a super class or mandated by an interface. Ant "just knows" to call a method of one of these forms when you nest its associated element under yours.
The Ant manual strongly cautions to only provide one of the above three methods to support a given nested element class:
"What happens if you use more than one of the options? Only one of the methods will be called, but we don't know which, this depends on the implementation of your Java virtual machine."
Example 6: A rot13 task with arbitrary number of nested fileset elements
This shows a custom task that interacts with the standard fileset element. For any matching file that the fileset selects, you will read the file and write a new file whose contents are rot13-translated. The exercise is two-fold: to show how to handle some nested element in your custom task and also to show how to work with a fileset element (which could really be slightly more intuitive, in my opinion).
For those unfamiliar, rot13 is the highly simplistic way of pseudo-scrambling text: alphabet characters in the set [a-m] are replaced with alphabet characters in the set [n-z], respectively and vice-versa. (Case is generally preserved, and numbers and symbols are generally left alone.) Caution: do not mistake rot13 for any form of encryption.
To handle the <fileset> element, you first quickly look through the Ant API (included with the standard documentation in your download) and deduce that this standard type is supported by the org.apache.tools.ant.types.FileSet class. Because I am choosing to allow multiple <fileset> elements to be nested, I will store the FileSet objects into a Vector instance variable. Note I am omitting some of the more mundane details here; feel free to refer to Example Six in the zip file accompanying this article for a full copy of the code.
//Rot13Converter.java package org.roblybarger; // ... imports here ... public class Rot13Converter extends Task { String extension = null; Vector<FileSet> filesets = new Vector<FileSet>(); /* ...init, getter/setter... */ public void addFileSet(FileSet fileset) { if (!filesets.contains(fileset)) { filesets.add(fileset); } } public void execute() { /* ...attribute validation... */ int filesProcessed = 0; DirectoryScanner ds; for (FileSet fileset : filesets) { ds = fileset.getDirectoryScanner(getProject()); File dir = ds.getBaseDir(); String[] filesInSet = ds.getIncludedFiles(); for (String filename : filesInSet) { File file = new File(dir,filename); /* ...handle the matching file... */ filesProcessed++; } } log("Done. "+filesProcessed+" file(s) processed."); } /* ...method(s) called by execute, above... */ }
Remember that this is a custom task because you want the execute() method to fire, so add an entry in your task-based properties file:
rot13=org.roblybarger.Rot13Converter
Update your jar file, then use something of this form in your Ant script:
<project name="ex6" default="demo" basedir="."> <taskdef resource="MyAntTasks.properties" classpath="mytasks.jar"/> <target name="demo"> <rot13 extension=".rot13"> <fileset dir="${basedir}" includes="build.*.xml"/> </rot13> </target> </project>
In the real world, I have put a custom task with nested filesets to more practical use in order to generate an XML summary file with a listing of files that result from an automated nightly build process. (This file listing contributes to post-build verification and summation procedures.)
Handling Arbitrary Nested Types
The fact that one must know the exact Java class name in the above section might not sit well with some people. Notably, if you want to have conditional support, you can't very well be expected to have an addXXX method for every possible conditional type, right? After all, you would be required to maintain this through all of Ant's future upgrades.
Fortunately, there is a way to add types to your type class in a more generic fashion. Ant supports these methods:
public void add(SomeInterface obj) { ... } public void addConfigured(SomeInterface obj) { ... }
This is probably due as much to polymorphism in Java as it is intended design of Ant. This also carries with it the proviso that you have, for example, a common interface to work from, and also requires that all your concrete implementing type classes are declared in the typedef statement. (That is, all implementing type classes are listed in your types properties file and referenced by the typedef statement.) Unfortunately, this requirement also applies to the standard condition types, and you cannot simply re-typedef them with the same name, either.
For example, custom conditions that implement the Condition interface can be nested under your custom task (or custom type) by providing an add(Condition c) method. Unfortunately, the isset built-in condition type won't work as-is. You would need to redeclare the built-in condition types with slightly modified names—is.set or core.isset, for example—but then you're back to the maintenance headache.
However, in the case of condition types, such hacks aren't necessary. The Ant team has provided the ConditionBase type class, and this class provides working addXXX methods for all the standard Ant tasks as well as a working add(Condition c) method. (The implication here is that they will do their due diligence to keep ConditionBase in sync with the built-in condition types.) Note that ConditionBase also includes these rather helpful methods:
protected int countConditions() { ... } protected java.util.Enumeration getConditions() { ... }
You are forced into subclassing in this case, but you are not likely to ever need to be a task and type at the same time. (If you think you will, keep reading.)
Example 7: An XOR custom condition type with arbitrary nested conditions
Consider you want to create an xor conditional type to round out the standard set of conditionals Ant provides. This means you need to implement the Condition interface and create an eval() method. You also want to nest two (and only two) arbitrary conditions under your xor condition. This means you should subclass from the ConditionBase class as mentioned above.
//XORCondition.java package org.roblybarger; import java.util.Enumeration; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.taskdefs.condition.Condition; import org.apache.tools.ant.taskdefs.condition.ConditionBase; public class XORCondition extends ConditionBase implements Condition { public boolean eval() throws BuildException { if (countConditions()<2) { throw new BuildException("Two nested conditions are required."); } else if (countConditions()>2) { throw new BuildException("Only two nested conditions allowed."); } else { Enumeration<Condition> conditions = getConditions(); boolean b1 = conditions.nextElement().eval(); boolean b2 = conditions.nextElement().eval(); return b1 ^ b2; } } }
Add an entry in the MyAntTypes.properties file:
xor=org.roblybarger.XORCondition
Update your jar file, and then try it with an Ant file such as this:
<project name="ex7" default="demo" basedir="."> <typedef resource="MyAntTypes.properties" classpath="mytasks.jar"/> <target name="demo"> <property name="propA" value="true"/> <property name="propB" value="false"/> <condition property="xor_check"> <xor> <isset property="propA"/> <isset property="propB"/> </xor> </condition> <!-- gimmick: force a false value if not set to true above --> <condition property="xor_check" value="false"> <not> <isset property="xor_check"/> </not> </condition> <echo message="property value: ${xor_check}"/> </target> </project>
The second condition block isn't really necessary except to force an explicit value of false into the property if the first condition block didn't set it to true. This is only being done so that the echo task actually prints something meaningful regardless of the effect of the xor operation.
Important: Recall that Ant's general behavior is to only set a property if it will get a true value. However, be cautioned to also recall that Ant's behavior in things such as if and unless checks in target attributes only look for the specified property being set, not actually being true. On the other hand, you are free to use explicit iftrue standard conditions for your own purposes. Combined with the if task from the ant-contrib project, you can generate some amazingly spidery ant files. (Weigh the two-edged sword of job-security carefully here.)
Task with Nested Tasks
It is probably not as likely that you will need a task that can have other tasks nested underneath it as it is that you will need to nest types under it. (The standard Ant task set has the sequential and parallel tasks that permit nested tasks, and the ant-contrib project's if task also does this.) Still, the capability exists in two flavors: you want to deal with specific tasks, or you want to deal with any task generically. The first option—handling nested tasks of a specific, known name/class—is handled in a manner very similar to handling specific nested types. If you want to support a nested task with an element name of foo which is backed by a Java class named MyFooTask, provide one of these two methods to store the reference passed to you from Ant:
public void addFoo(MyFooTask t) { ... } public void addConfiguredFoo(MyFooTask t) { ... }
The difference between these two methods, again, is whether Ant passes you the object reference before (first method) or after (second method) processing the element's own attributes and nested elements.
The second option involves handling tasks generically. To accomodate this, your class should implement the org.apache.tools.ant.TaskContainer interface, which requires you to provide an implementation for one method:
public class YourCustomTask extends TaskContainer { ... public void addTask(Task t) { ... } ... }
You will probably want to simply add the generic task object reference to some collection or array and deal with it later:
Vector<Task> tasks; public void init() { tasks = new Vector<Task>(); } public void addTask(Task t) { tasks.add(t); } ...
Of course, when your own task's execute() method is called, you will want to cause your nested tasks to run their own execute method. Although you might be able to directly call the execute() method of some tasks, other tasks may require addition pre-execution processing before they are finally useful. Therefore, the preferred way is to call the perform() method:
public void execute() { ... for(Task t : tasks) { t.perform(); } ... }
Granted, your real-world task will probably have a set of nested types to process as well, and you may need to add error checking and attribute value validation (among other things) before calling certain of the tasks nested underneath your own task.
Nesting Tasks and Conditions: Task versus ConditionBase
One final wrinkle that might surface is the case of needing to write a task that supports arbitrary condition types. Naturally, single-inheritance being what it is, you cannot subclass both Task and ConditionBase at the same time. On the other hand, a quick view of the documentation for the "if" task (provided in the ant-contrib package) indicates it is doing exactly this feat. A quick peek at the source code for the "if" task, however, indicates that it subclasses ConditionBase (and implements TaskContainer to allow any nested tasks). Yet, it provides an execute() method and functions apparently like any other first-class task. (For that matter, the standard condition task itself is a ConditionBase derivative that behaves like a task.)
The conclusion is that Ant has a little-known (at least little-publicized) secret: Your task doesn't exactly have to subclass ant's own "Task" class to work... simply having the execute() method, plus being taskdef'ed, is enough to work. Still, you do lose out on a bit of functionality that the Task class would provide you if you were to subclass from it, so weigh the effects of this carefully. (The condition and if tasks don't do anything that really requires such functionality.) You can still get and set project-level properties by virtue of ConditionBase also being a subclass of Ant's ProjectComponent class.
However, the general spirit of Ant's design indicates you may want to consider a different approach: Have your main task class subclass from Task to provide the execute() method and then provide a nested condition type which subclasses from ConditionBase to handle the conditions. Example:
<project ...> <taskdef name="mytask" ... /> <typedef name="mycondition" ... /> <mytask ...> <mycondition> <!-- standard conditionals here --> </mycondition> <!-- standard or custom tasks nested here --> </mytask> </project>
What's Left?
Custom selectors
Another special-purpose type that Ant exposes to customizations is the selector type used in, for example, a fileset element. You can write your own logic to determine whether a given file encountered in the filesystem (as a fileset is being populated) should be included in the fileset's entries. To do this, implement the org.apache.tools.ant.types.selectors.FileSelector interface and provide an implementation for isSelected:
public class MyCustomSelector implements FileSelector { ... public boolean isSelected(File basedir, String filename, File file) { ... } ... }
This is a type, so declare this via a typedef statement. And of course, you may provide for attributes and other nested elements as with any custom type. Although I will not show an example here, the Ant manual shows a quick example of a selector that returns true only if the filename ends in ".java", but the selection logic can be as complex as you need it to be.
Much like the ConditionBase class, there is an analogous org.apache.tools.ant.types.selectors.BaseSelectorContainer class that you might want to look into, should you wish to write a custom task or type that allows arbitrary nested standard selector elements.
XML Namespaces
The more recent versions of Ant (the 1.6 branch) support the concept of XML namespaces. A namespace in XML is conceptually similar to namespaces in programming toolsets (like Java's concept "package"). The standard version of Ant includes the <property> element, so if you also create a custom task or type with that element name, you'll collide against the standard version. With an XML namespace prefix, you could say <acme:property> and get your version instead.
Ant gives you a few different ways to get a namespace prefix to work in your build files, but I will show just this one version that puts the "xmlns" attributes in the root element (that is, the <project> element) where XML geeks are likely to look for them. The only other thing you need to do is to match the value of the URI you mention in the xmlns attribute in a uri attribute in your taskdef and typedef elements:
<project xmlns_acme="http://ant.acme.com/custom" ...> <taskdef uri="http://ant.acme.com/custom" ... /> <typedef uri="http://ant.acme.com/custom" ... /> <acme:mytask ...> <acme:mycondition> <isset property="some_property /> </acme:mycondition> <delete file="some_file.properties" /> <acme:mytype ... /> </acme:mytask> </project>
Although many publicly available projects tend to use a web address (URL) as their URI, it can be any string in any format you want... just be sure they match. (If you make your custom ant code available in bid for fame and fortune, pick a stable URI and don't change it.) A URI value of INHOUSE_CODE_BY_TOM_DICK_AND_HARRY is just as functional (to Ant, anyway) as the URI in the above example.
Download the Code
You can download the code file that accompanies the article here.
Next Time
Custom Ant event loggers and Ant in your apps
You also can make use of the built-in Ant task code in your own Java applications and projects without having to invoke Ant on the command line. You also can hijack the default logging facilities to create Ant logging output in any custom format you need. I will cover these two options in a future article. Stay tuned.
Resources
About the Author
Rob Lybarger is a software engineer who has been using Java for nearly a decade and using Ant since its earliest days. He has used various versions of Windows, various distributions of Linux, and, most recently, Mac OS X. Although he knows a wide array of software languages, the cross-platform nature of Java and Ant has proven to be a valuable combination for his personal and professional efforts.