Programming your own Selectors

Selector Programming API

Want to define your own selectors? It's easy!

First, pick the type of selector that you want to define. There are three types, and a recipe for each one follows. Chances are you'll want to work with the first one, Custom Selectors.

  1. Custom Selectors

    This is the category that Ant provides specifically for you to define your own Selectors. Anywhere you want to use your selector you use the <custom> element and specify the class name of your selector within it. See the Custom Selectors section of the Selector page for details. The <custom> element can be used anywhere the core selectors can be used. It can be contained within Selector Containers, for example.

    To create a new Custom Selector, you have to create a class that implements org.apache.tools.ant.types.selectors.ExtendFileSelector. The easiest way to do that is through the convenience base class org.apache.tools.ant.types.selectors.BaseExtendSelector, which provides all of the methods for supporting <param> tags. First, override the isSelected() method, and optionally the verifySettings() method. If your custom selector requires parameters to be set, you can also override the setParameters() method and interpret the parameters that are passed in any way you like. Several of the core selectors demonstrate how to do that because they can also be used as custom selectors.

  2. Core Selectors

    These are the selectors used by Ant itself. To implement one of these, you will have to alter some of the classes contained within Ant.

  3. Selector Containers

    Got an idea for a new Selector Container? Creating a new one is no problem:

Testing Selectors

For a robust component (and selectors are (Project)Components) tests are necessary. For testing Tasks we use JUnit TestCases - more specific org.apache.tools.ant.BuildFileTest extends junit.framework.TestCase. Some of its features like configure the (test) project by reading its buildfile and execute targets we need for selector tests also. Therefore we use that BuildFileTest. But testing selectors requires some more work: having a set of files, instantiate and configure the selector, check the selection work and more. Because we usually extend BaseExtendSelector its features have to be tested also (e.g. setError()).

That's why we have a base class for doing our selector tests: org.apache.tools.ant.types.selectors.BaseSelectorTest.

This class extends TestCase and therefore can included in the set of Ant's unit tests. It holds an instance of preconfigured BuildFileTest. Configuration is done by parsing the src/etc/testcases/types/selectors.xml. BaseSelectorTest then gives us helper methods for handling multiple selections.

Because the term "testcase" or "testenvironment" are so often used, this special testenvironment got a new name: bed. Like you initialize the test environment by calling setUp() and cleaning by calling tearDown() (or like to make your bed before go sleeping) you have to do that work with your bed by calling makeBed() respecitive cleanupBed().

A usual test scenario is

  1. make the bed
  2. instantiate the selector
  3. configure the selector
  4. let the selector do some work
  5. verify the work
  6. clean the bed

For common way of instantiation you have to override the getInstance() simply by returning a new object of your selector. For easier "selection and verification work" BaseSelectorTest provides the method performTests() which iterates over all files (and directories) in the String array filenames and checks whether the given selector returns the expected result. If an error occurred (especially the selector does not return the expected result) the test fails and the failing filenames are logged.

An example test would be:

package org.apache.tools.ant.types.selectors;

public class MySelectorTest extends BaseSelectorTest {

    public MySelectorTest(String name) {
        super(name);
    }

    public BaseSelector getInstance() {
        return new MySelector();
    }

    public void testCase1() {
        try {
            // initialize test environment 'bed'
            makeBed();

            // Configure the selector
            MySelector s = (MySelector)getSelector();
            s.addParam("key1", "value1");
            s.addParam("key2", "value2");
            s.setXX(true);
            s.setYY("a value");

            // do the tests
            performTests(s, "FTTTTTTTTTTT");  // First is not selected - rest is

        } finally {
            // cleanup the environment
            cleanupBed();
        }
    }
}
    
As an example of an error JUnit could log
    [junit]     FAILED
    [junit] Error for files: .;copy.filterset.filtered;tar/gz/asf-logo.gif.tar.gz
    [junit] expected:<FTTTFTTTF...> but was:<TTTTTTTTT...>
    [junit] junit.framework.ComparisonFailure: Error for files: .;copy.filterset.filtered;tar/gz/asf-logo.gif.tar.gz
    [junit] expected:<FTTTFTTTF...> but was:<TTTTTTTTT...>
    [junit]     at junit.framework.Assert.assertEquals(Assert.java:81)
    [junit]     at org.apache.tools.ant.types.selectors.BaseSelectorTest.performTest(BaseSelectorTest.java:194)
    

Described above the test class should provide a getInstance() method. But that isn't used here. The used getSelector() method is implemented in the base class and gives an instance of an Ant Project to the selector. This is usually done inside normal build file runs, but not inside this special environment, so this method gives the selector the ability to use its own Project object (getProject()), for example for logging.

Logging

During development and maybe later you sometimes need the output of information. Therefore Logging is needed. Because the selector extends BaseExtendSelector or directly BaseSelector it is an Ant DataType and therefore a ProjectComponent.
That means that you have access to the project object and its logging capability. ProjectComponent itself provides log methods which will do the access to the project instance. Logging is therefore done simply with:

        log( "message" );
    
or
        log( "message" , loglevel );
    
where the loglevel is one of the values