July 30, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

More on Custom Ant Tasks

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

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:

  1. public FooTypeClass createBlah() {...}
    You assume responsibility to instantiate the FooTypeClass object and pass the reference to Ant.
  2. 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.
  3. 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.





Page 2 of 4



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel