After much head scratching, I came up with a solution to my own little Ant
extension problem (about which I complained about a few months back).
Basically, I first had to use Parametrizable to enable the extension point,
like other Ant types:

    <buildpath ident="buildpath"
               resolverClassname="....TahoeBuildPathResolver">
      <param name="destdir" value="${destdir}" />
      <param name="dependencies" value="${dependencies}" />
    </buildpath>

The solution was the introduction of a DynamicTag class which uses both
DynamicConfigurator and UnknownElement. I can now define my extension point
(which implements an interface/extension point only my datatype cares about)
using a <typedef>, and then use it (and configure it as any other task) like
so:

    <buildpath id="tahoebuildpath">
      <resolver>
        <tahoeresolver destdir="${destdir}"
                  dependencies="${dependencies}" 
      </resolver>
    </buildpath>

BuildPath.java adds a nested creator:

    /**
     * Creates a nested resolver element to contain a user-defined
     * resolver dynamically parameterized thanks to DynamicTag.
     */
    public Object createResolver() {
        return _dynaResolver = new DynamicTag(BuildPathResolver.class);
    }

And uses it like so when declared:

        if (_dynaResolver != null) {
            BuildPathResolver resolver;
            resolver = (BuildPathResolver)_dynaResolver.getTag(0);
            return resolver.getBuildPath();
        }

DynamicTag does the magic, i.e. ensure it contains as many nested elements
as specified (default to requiring exactly one element), and also ensures
that these elements are of the correct type (i.e. assigneable to the
specified class/interface).

This always the extension point to be configured like any other Ant
task/type, including proper conversion to File, int, etc...

My only concern is the lack of namespaces (not the XML type) for all the
declarations of tasks/types/custom extension points. I initially coded a
role manager class, but would have had to modify Ant core in many places to
use it, so reverted to subverting the existing type system of Ant.

This extension point mechanism could be used in place of Peter's patch to
enable custom selector/mappers/etc... I believe.

Let me all know what you think of it. --DD

// vim:ts=4:sw=4
package com.lgc.buildmagic;

import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;

import org.apache.tools.ant.Task;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.UnknownElement;
import org.apache.tools.ant.RuntimeConfigurable;
import org.apache.tools.ant.DynamicConfigurator;

/**
 * Tag allowing creation of arbitrary bean/type/tasks registered with Ant
 * using either a taskdef or typedef.
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Dominique Devienne</a>
 * @version Mar 2003 - Copyright (c) 2002, Landmark Graphics Corp.
 */
public class DynamicTag
             extends Task
             implements DynamicConfigurator {

    /** List of tags actually instantiated. */
    private List _tags;

    /** Class(es) this/these dynamic tag(s) must be compatible with. */
    private List _types;

    /** Minimum number of dynamic tags to create. Defaults to 1. */
    private int _atLeast = 1;

    /** Maximum number of dynamic tags to create. Defaults to 1. */
    private int _atMost = 1;

    /**
     * Instantiates a new dynamic tag that accepts only one arbitrary
Object.
     */
    public DynamicTag() {
        this(null);
    }

    /**
     * Instantiates a new dynamic tag that accepts only one given Object
     * assignable to the given required type.
     *
     * @param  requiredType the class this dynamic tag's only child must
     *         be compatible with, in the [EMAIL PROTECTED] 
Class#isAssignableWith}
     *         sense. Can be <code>null</code>.
     */
    public DynamicTag(Class requiredType) {
        if (requiredType != null) {
            _types = new ArrayList(1);
            _types.add(requiredType);
        }
    }

    /**
     * Instantiates a new dynamic tag that accepts Objects
     * assignable to the given required types.
     *
     * @param  requiredTypes the classes this dynamic tag's only child must
     *         be compatible with, in the [EMAIL PROTECTED] 
Class#isAssignableWith}
     *         sense. Can be <code>null</code>.
     * @param  atLeast the minimum number of beans/types/tasks this
     *         dynamic element should contain. Must not be negative,
     *         or strictly greater than <code>atMost</code>.
     *         Use <code>0</code> (zero) for no lower bound.
     * @param  atMost the maximum number of beans/types/tasks this
     *         dynamic element should contain. Must not be negative, zero,
     *         or strictly smaller than <code>atLeast</code>.
     *         Use [EMAIL PROTECTED] Integer#MAX_INT} for no upper bound.
     */
    public DynamicTag(Class[] requiredTypes, int atLeast, int atMost) {
        if (atLeast < 0 || atLeast > atMost || atMost == 0) {
            throw new IllegalArgumentException("Invalid min/max: " +
                                               atLeast + '/' + atMost);
        }
        if (requiredTypes != null && requiredTypes.length > 0) {
            _types = Arrays.asList(requiredTypes);
        }
        _atLeast = atLeast;
        _atMost = atMost;
    }

    // Implements DynamicConfigurator#setDynamicAttribute
    public void setDynamicAttribute(String name, String value)
                throws BuildException {
        throw new BuildException("Unknown attribute: " + name);
    }

    // Implements DynamicConfigurator#createDynamicElement
    public Object createDynamicElement(String name)
                  throws BuildException {
        return new MyUnknownElement(name);
    }

    /**
     * Gets the instantiated bean/type/task at the given.
     *
     * @param  index the index requested.
     * @throws IndexOutOfBoundsException if no tags were instantiated;
     *         or the given <code>index</code> is invalid.
     */
    public Object getTag(int index) {
        if (_tags == null) {
            throw new IndexOutOfBoundsException("no tags");
        }
        assert _tags.size() >= _atLeast;
        assert _tags.size() <= _atMost;
        return _tags.get(index);
    }

    /**
     * Gets all the tags instantiated within this dynamic tag.
     *
     * @return the unmodifiable, and possibly empty, list of tags.
     */
    public List getTags() {
        if (_tags == null) {
            return Collections.EMPTY_LIST;
        }
        return Collections.unmodifiableList(_tags);
    }

    /**
     * Checks to see whether the instantiated bean/type/tag is valid.
     * <p>
     * This particular implementation checks the instantiated bean/type/tag
     * is assigment-compatible with the classes provided as arguments to
     * this dynamic tag's constructors.
     * <p>
     * Derived classes should augment instead of bypass the checks performed
     * by this method, by first calling
<code>super.assertValidTag(tag);</code>
     *
     * @param  tag the bean/type/tag to check.
     * @throws BuildException if the bean/type/tag is invalid.
     */
    protected void assertValidTag(Object tag)
                   throws BuildException {
        if (_types == null || _types.size() < 1) {
            return;
        }
        Class tagClass = tag.getClass();
        for (int i = 0; i < _types.size(); ++i) {
            Class type = (Class)_types.get(i);
            if (!type.isAssignableFrom(tagClass)) {
                throw new BuildException(tagClass +
                                         " not assignment-compatible with "
+
                                         type);
            }
        }
    }

    /**
     * Adds the given instantiated bean/type/task to this dynamic tag.
     */
    private void addTag(Object realThing) {
        assertValidTag(realThing);
        if (_tags == null) {
            _tags = new ArrayList(Math.min(16, _atMost));
        }
        _tags.add(realThing);
    }

    // Intercept maybe configure to check min/max bounds.
    public void maybeConfigure()
                throws BuildException {
        super.maybeConfigure();

        if (_atLeast > 0 && (_tags == null || _tags.size() < 1)) {
            throw new BuildException("Too few elements: <" + _atLeast);
        }
        if (_tags.size() > _atMost) {
            throw new BuildException("Too many elements: >" + _atMost);
        }
    }

    /**
     * UnknownElement extension to intercept the bean/type/task
     * actually instantiated by UnknownElement.
     *
     * @author <a href="mailto:[EMAIL PROTECTED]">Dominique Devienne</a>
     * @version Mar 2003 - Copyright (c) 2002, Landmark Graphics Corp.
     */
    private class MyUnknownElement 
                  extends UnknownElement {

        private MyUnknownElement(String name) {
            super(name);
        }

        protected Object makeObject(UnknownElement ue, RuntimeConfigurable
w) {
            Object realThing = super.makeObject(ue, w);
            DynamicTag.this.addTag(realThing);
            return realThing;
        }

    } // END class DynamicTag.MyUnknownElement

} // END class DynamicTag


-----Original Message-----
From: peter reilly [mailto:[EMAIL PROTECTED] 
Sent: Wednesday, March 26, 2003 12:25 PM
To: Ant Developers List
Subject: Re: Artima SuiteRunner Task

I would include filters, mappers, conditions and selectors to
the list.

A relatively simple mod to the core ant makes this
possible (bugzilla 17199) basically get ConditionBase.java,
AbstractFileSet, FilterChain implement DynamicConfigurator.
and get UnknownElement (bugzilla 18312) call setProject
earlier on created children.
Mappers are a little different, I implemented a new
attribute to handle this.

It this is done, then non ant core projects may implement
their own whacky types.

(like this to convert to upper case:
<filterchain>
    <tokenfilter>
         <scriptfilter language="beanshell">
             self.setToken(self.getToken().toUpperCase())
         </scriptfilter>
     </tokenfilter>
</filterchain>


Peter

On Wednesday 26 March 2003 17:46, Costin Manolache wrote:
> Dominique Devienne wrote:
> > Hummmm, not totally. If the AntLib also uses types, you need another
> > <typedef>, which should also probably needs a loaderref. Since you now
> > use twice the classpath, if needs to be outside and refid'd.
>
> In ant1.6 the difference between tasks and types is very small. It would
be
> trivial to load both of them at once.
>
> In any case, I don't quite agree with Stefan: the simpler solution is:
>
>  <path id="..." />
>
>  <taskdef resource=".. " classpathref=".." />
>
> I would love to completely remove the different treatment of types -
> i.e. a task is just like a type with an execute() method, and nothing
else,
> and <taskdef> loades both kinds.
>
> That would be simpler than the current syntax that also require you to
> do a:
>  <typedef resource="..." classpathref="..." />
>
> Costin
>
> > And what about the <junit> task? I'd like to not have setup my classpath
> > outside of Ant and build.xml, and avoid having to dump everything in
> > AntLib.
> >
> > I believe it can and should be easier and more flexible. --DD
> >
> > -----Original Message-----
> > From: Stefan Bodewig [mailto:[EMAIL PROTECTED]
> > Sent: Wednesday, March 26, 2003 10:27 AM
> > To: [EMAIL PROTECTED]
> > Subject: Re: Artima SuiteRunner Task
> >
> > On Wed, 26 Mar 2003, Dominique Devienne <[EMAIL PROTECTED]> wrote:
> >> That said (one more ;-), if Ant ever comes up with an easier way to
> >> integrate third party tasks
> >
> > Easier than <taskdef resource="..."><classpath .../></taskdef>?
> >
> > Almost impossible.
> >
> > Stefan
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to