December 18, 2014
Hot Topics:

More on Custom Ant Tasks

  • October 5, 2006
  • By Rob Lybarger
  • Send Email »
  • More Articles »

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.





Page 3 of 4



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel