Hi, I am on holidays, but have followed
this thread.

I have been playing with DynamicTag and it is
quite neat, but there are some issues with it..
I have made some modifications (well rewrite...)

1) DynamicTag uses UnknownElement. This part of ant code is
   undergoing a number of changes at the moment, and currently
   DynamicTag does not work with cvs ant.

   I extracted the stuff that unknownelement does and placed
   them in a method, this is compatible with apr12 ant cvs and with
   ant 1.5.x (tested with 1.5.2).

2) I would like to have dynamic tag behavior on the the current
   task as well as nested elements.

3) I would like to use reflection in the same way as ant uses
   reflection for setters and creators.

4) I would like to support multiple dynamic types in the same class.

I initially considered using only datatypes, but on consideration
it does make sense to have tasks as well. The problems with this
are:
   a) tasks that do not extend ant.task get wrapped by ant.taskadapter
      which may not be what is expected
   b) tasks have a more complex life cycle that datatypes (init,
      perform, execute methods)

Attached are two files : DynamicElementHelper and DynamicElementTask.

Tasks and Datatypes/Sub-elements extend DynamicElementTask. 
The signature dynamicElement(Type) is used to indicate the type that
this method will handle.
DynamicElementTask implements DynamicConfigurator, and calls
DynamicElementHelper, which looks up the tag name in the
projects tasks and datatypes, if found that, the method
uses reflection to see if the calling object has a matching
dynamicElement method for the type.

DynamicElementHelper is written so that if this idea
is considered useful, it can be used by IntrospectionHelper,
It that case, there would be no need for DynamicElementTask.


To do the original example:
    <buildpath id="tahoebuildpath">
        <tahoeresolver destdir="${destdir}"
                  dependencies="${dependencies}" 
    </buildpath>

BuildPath.java extends DynamicElementTask and adds a dynamic element
handler:

  // Creates a nested resolver
  public void dynamicElement(BuildPathResolver resolver) {
     if (this.resolver != null)
         throw new BuildException("resolver already defined");
     this.resolver = resolver;
  }

And the xml filters:

<target name="test" depends="-init" >
   <xmlchain toDir="./build/chain" extension=".xxx">
     <fileset refid="workOnStuf" />
     <filter1 attr1="v1" attr2="v2">
       <mysubelement .../>
     </filter1>
     <xincludefilter .../>
     <xmlfilter refid="other-filter"/>
   </xmlchain>
</target>

and in xml chain:

public void dynamicElement(XmlFilter filter) {
    filters.addElement(filter)
}

One may also nest the custom filters in the same way as
dynamictag by writing a new class 

public class CustomFilters extends DynamicElementTask {
   private Vector filters = new Vector();
   public void dynamicElement(XmlFilter filter) {
      filters.addElement(filter);
   }
   public Vector getFilters() { return filters;}
}

and in XmlChain:
  public void addCustomFilters(CustomFilters customFilters) {
    this.customFilters = customFilters;
  }

Cheers,

Peter

Dominique Devienne wrote:
>>Two comments:

>>1) DynamicTag is fully Ant 1.5.x compatible. No need for 1.6. Just use
it
>>along side your own classes, and you're good to go.

>>2) DynamicTag *relies* on <taskdef> or <typedef> (you can declare your
>>custom extension either way), which takes care of all the
classloading,
>>already has all the special stuff like loaderref. Any enhancement to
these
>>tasks automatically benefit DynamicTag.


package net.sf.antcontrib.util;

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

import java.util.Vector;

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

/**
 * <p>
 * This class is used to help in handling dynamic elements.
 * The idea is to allow easy custom extensions
 * and to extend the traditional ant bean reflection to call setters
 * methods, or add/create methods, with all the magic type conversion
 * it does.
 * </p>
 * <p>
 * The dynamic element classes will be defined by &lt;typedef/&gt;
 * or <b>(to be decided)</b> by &lt;taskdef/&gt;
 * </p>
 * <p>
 * User classes (tasks or datatypes) have methods
 * <code>dynamicElement(class or interface)</code>
 * </p>
 * <p>
 * This class is currently used by DynamicElementTask, but
 * may in the future be used by ant core IntrospectionHelper.
 * </p>
 * <p>
 * An example: Suppose one had a task buildpath that resolved
 * paths using custom resolvers that implement a BuildPathResolver
 * interface.
 * </p>
 * <pre>
 *  &lt;typedef name="tahoeresolver"
 *            classname="acme.resolvers.TahoeResolver"
 *            classpath="acme.classpath"/&gt;
 *   &lt;buildpath id="tahoebuildpath"&gt;
 *     &lt;tahoeresolver destdir="${destdir}"
 *                    dependencies="${dependencies}" 
 *   &lt;/buildpath&gt;
 * </pre>
 *
 * BuildPath would look something like this.
 * <pre>
 * public class BuildPathTask
 *    extends DynamicElementTask
 * {
 *     ...
 *     BuildPathResolver resolver;
 *     public void dynamicElement(BuildPathResolver resolver) {
 *         if (resolver != null)
 *             throw new BuildException();
 *         resolver = resolver;
 *     }
 *     ....
 *     public void execute() {
 *        if (resolver == null)
 *           throw new BuildException();
 *        buildPath = resolver.getBuildPath();
 *     }
 * }
 * </pre>
 * <p>
 * <b>Note:</b>
 * </p>
 * <p>x
 * createDynamicMethod should possible throw different
 * exceptions for the various error conditions:
 * <dl>
 *  <li>name not found</li>
 *  <li>name found but no matching dynamic element method</li>
 * </dl>
 * At the moment the method simply returns null for these
 * conditions.
 * </p>
 *
 * @author Peter Reilly
 */
public class DynamicElementHelper {
    private Vector nestedDynamicMethods = new Vector();

    /**
     * Constructor for DynamicElementHelper.
     *
     * @param clazz the class to reflect over
     */
    public DynamicElementHelper(Class clazz) {
        extractDynamicElements(clazz.getMethods());
    }

    /**
     * Create a dynamic nested element from either the
     * task definitions or the data types defined in
     * the project.
     * This method also invokes the correct <code>dynamicelement</code>
     * method in object.
     * @param project the project the task or datatype is in
     * @param object  the task or data type instance
     * @param elementName the xml tag
     * @return the created object.
     */
    public Object createDynamicElement(
        Project project, Object object, String elementName)
    {
        if (project == null) {
            throw new BuildException(
                "Project is null for dynamicElementHelper");
        }

        if (nestedDynamicMethods.size() == 0)
            return null;
        
        // is element in task definitions
        Class elementClass =
            (Class) project.getTaskDefinitions().get(elementName);

        boolean isTask = (elementClass != null);

        if (elementClass != null) {
            if (! (Task.class.isAssignableFrom(elementClass))) {
                elementClass = TaskAdapter.class;
            }
        } else {
            // is element in data type definitions
            elementClass =
                (Class) project.getDataTypeDefinitions().get(elementName);
        }
        
        if (elementClass == null) {
            return null;
        }

        Method method = getMatchingMethod(elementClass);

        if (method == null)
            return null;

        Object nestedObject = (isTask
                               ? project.createTask(elementName)
                               : project.createDataType(elementName));
        if (nestedObject == null)
            return null;

        // invoke the dynamic element method
        try {
            method.invoke(object, new Object[]{nestedObject});
        }
        catch (InvocationTargetException ex) {
            Throwable realException = ex.getTargetException();
            if (realException instanceof BuildException)
                throw (BuildException) realException;
        }
        catch (Throwable t) {
            throw new BuildException(t);
        }

        // If this is a task call the init method on it
        if (isTask) {
            ((Task) nestedObject).init();
        }
        return nestedObject;
    }
    

    /**
     * Search the array of methods to find method names
     * of "dynamicElement" with one parameter. Sort the
     * methods so that derived classes are placed before
     * their parents.
     */
    private void extractDynamicElements(Method[] methods) {
      loop:
        for (int i = 0; i < methods.length; ++i) {
            Method method = methods[i];
            Class[] args = method.getParameterTypes();
            if (args.length != 1)
                continue loop;
            if (! method.getName().equals("dynamicElement"))
                continue loop;
            for (int m = 0; m < nestedDynamicMethods.size(); ++m) {
                Method current = (Method) nestedDynamicMethods.elementAt(m);
                if (current.getParameterTypes()[0].isAssignableFrom(
                        method.getParameterTypes()[0]))
                {
                    nestedDynamicMethods.insertElementAt(method, m);
                    continue loop;
                }
            }
            nestedDynamicMethods.addElement(method);
        }
    }

    /**
     * Search the list of methods to find the first method
     * that has a parameter that accepts the dynamic element object
     */
    private Method getMatchingMethod(Class paramClass) {
        for (int i = 0; i < nestedDynamicMethods.size(); ++i) {
            Method method = (Method) nestedDynamicMethods.elementAt(i);
            if (method.getParameterTypes()[0].isAssignableFrom(paramClass))
                return method;
        }
        return null;
    }
}

package net.sf.antcontrib.util;

import java.util.Vector;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

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

import org.apache.tools.ant.taskdefs.condition.Condition;

/**
 * Abstract class to use DynamicElementHelper to
 * use refection to find methods called dynamicElement.
 * This class implements DynamicConfiguator.
 * The class may be used as a basis for a task or a
 * type.
 */
public abstract class DynamicElementTask
    extends Task
    implements DynamicConfigurator
{
    
    private DynamicElementHelper dynamicElementHelper = null;

    public Object createDynamicElement(String name) {
        if (dynamicElementHelper == null) {
            dynamicElementHelper = new DynamicElementHelper(
                this.getClass());
        }
        Object ret = dynamicElementHelper.createDynamicElement(
            getProject(), this, name);
        if (ret == null)
            throw new BuildException("Unknown Element " + name);
        return ret;
    }
    
    public void setDynamicAttribute(String name, String value) {
        throw new BuildException("Unknown attribute " + name);
    }
}

Reply via email to