You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by bo...@apache.org on 2003/04/01 13:35:07 UTC

cvs commit: ant/proposal/sandbox/antlib/src/main/org/apache/tools/ant IntrospectionHelper.java Project.java

bodewig     2003/04/01 03:35:07

  Modified:    proposal/sandbox/antlib/src/main/org/apache/tools/ant
                        IntrospectionHelper.java Project.java
  Log:
  Sync with main code line
  
  Revision  Changes    Path
  1.4       +451 -182  ant/proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java
  
  Index: IntrospectionHelper.java
  ===================================================================
  RCS file: /home/cvs/ant/proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- IntrospectionHelper.java	7 Mar 2003 11:22:58 -0000	1.3
  +++ IntrospectionHelper.java	1 Apr 2003 11:35:06 -0000	1.4
  @@ -1,7 +1,7 @@
   /*
    * The Apache Software License, Version 1.1
    *
  - * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
  + * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
    * reserved.
    *
    * Redistribution and use in source and binary forms, with or without
  @@ -23,7 +23,7 @@
    *    Alternately, this acknowlegement may appear in the software itself,
    *    if and wherever such third-party acknowlegements normally appear.
    *
  - * 4. The names "The Jakarta Project", "Ant", and "Apache Software
  + * 4. The names "Ant" and "Apache Software
    *    Foundation" must not be used to endorse or promote products derived
    *    from this software without prior written permission. For written
    *    permission, please contact apache@apache.org.
  @@ -54,16 +54,15 @@
   
   package org.apache.tools.ant;
   
  -import org.apache.tools.ant.types.Path;
  -import org.apache.tools.ant.types.EnumeratedAttribute;
  -
  -import java.lang.reflect.Method;
  -import java.lang.reflect.InvocationTargetException;
  -import java.lang.reflect.Constructor;
   import java.io.File;
  +import java.lang.reflect.Constructor;
  +import java.lang.reflect.InvocationTargetException;
  +import java.lang.reflect.Method;
   import java.util.Enumeration;
   import java.util.Hashtable;
   import java.util.Locale;
  +import org.apache.tools.ant.types.EnumeratedAttribute;
  +import org.apache.tools.ant.types.Path;
   
   /**
    * Helper class that collects the methods a task or nested element
  @@ -75,45 +74,125 @@
   public class IntrospectionHelper implements BuildListener {
   
       /**
  -     * holds the types of the attributes that could be set.
  +     * Map from attribute names to attribute types 
  +     * (String to Class).
        */
       private Hashtable attributeTypes;
   
       /**
  -     * holds the attribute setter methods.
  +     * Map from attribute names to attribute setter methods 
  +     * (String to AttributeSetter).
        */
       private Hashtable attributeSetters;
   
       /**
  -     * Holds the types of nested elements that could be created.
  +     * Map from attribute names to nested types 
  +     * (String to Class).
        */
       private Hashtable nestedTypes;
   
       /**
  -     * Holds methods to create nested elements.
  +     * Map from attribute names to methods to create nested types 
  +     * (String to NestedCreator).
        */
       private Hashtable nestedCreators;
   
       /**
  -     * Holds methods to store configured nested elements.
  +     * Map from attribute names to methods to store configured nested types 
  +     * (String to NestedStorer).
        */
       private Hashtable nestedStorers;
   
       /**
  -     * The method to add PCDATA stuff.
  +     * The method to invoke to add PCDATA.
        */
       private Method addText = null;
   
       /**
  -     * The Class that's been introspected.
  +     * The class introspected by this instance.
        */
       private Class bean;
   
       /**
  -     * instances we've already created
  +     * Helper instances we've already created (Class to IntrospectionHelper).
        */
       private static Hashtable helpers = new Hashtable();
   
  +    /** 
  +     * Map from primitive types to wrapper classes for use in 
  +     * createAttributeSetter (Class to Class). Note that char 
  +     * and boolean are in here even though they get special treatment
  +     * - this way we only need to test for the wrapper class.
  +     */
  +    private static final Hashtable PRIMITIVE_TYPE_MAP = new Hashtable(8);
  +
  +    // Set up PRIMITIVE_TYPE_MAP
  +    static {
  +        Class[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE, 
  +                              Short.TYPE, Integer.TYPE, Long.TYPE, 
  +                              Float.TYPE, Double.TYPE};
  +        Class[] wrappers = {Boolean.class, Byte.class, Character.class, 
  +                            Short.class, Integer.class, Long.class, 
  +                            Float.class, Double.class};
  +        for (int i = 0; i < primitives.length; i++) {
  +            PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]);
  +        }
  +    }
  +
  +    // XXX: (Jon Skeet) The documentation below doesn't draw a clear 
  +    // distinction between addConfigured and add. It's obvious what the
  +    // code *here* does (addConfigured sets both a creator method which
  +    // calls a no-arg constructor and a storer method which calls the
  +    // method we're looking at, whlie add just sets a creator method
  +    // which calls the method we're looking at) but it's not at all
  +    // obvious what the difference in actual *effect* will be later
  +    // on. I can't see any mention of addConfiguredXXX in "Developing
  +    // with Ant" (at least in the version on the web site). Someone
  +    // who understands should update this documentation 
  +    // (and preferably the manual too) at some stage.
  +    /**
  +     * Sole constructor, which is private to ensure that all 
  +     * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
  +     * Introspects the given class for bean-like methods.
  +     * Each method is examined in turn, and the following rules are applied:
  +     * <p>
  +     * <ul>
  +     * <li>If the method is <code>Task.setLocation(Location)</code>, 
  +     * <code>Task.setTaskType(String)</code>
  +     * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These 
  +     * methods are handled differently elsewhere.
  +     * <li><code>void addText(String)</code> is recognised as the method for
  +     * adding PCDATA to a bean.
  +     * <li><code>void setFoo(Bar)</code> is recognised as a method for 
  +     * setting the value of attribute <code>foo</code>, so long as 
  +     * <code>Bar</code> is non-void and is not an array type. Non-String 
  +     * parameter types always overload String parameter types, but that is
  +     * the only guarantee made in terms of priority.
  +     * <li><code>Foo createBar()</code> is recognised as a method for
  +     * creating a nested element called <code>bar</code> of type 
  +     * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
  +     * array type.
  +     * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
  +     * method for storing a pre-configured element called 
  +     * <code>foo</code> and of type <code>Bar</code>, so long as
  +     * <code>Bar</code> is not an array, primitive or String type. 
  +     * <code>Bar</code> must have an accessible constructor taking no 
  +     * arguments.
  +     * <li><code>void addFoo(Bar)</code> is recognised as a
  +     * method for storing an element called <code>foobar</code> 
  +     * and of type <code>Baz</code>, so long as
  +     * <code>Baz</code> is not an array, primitive or String type. 
  +     * <code>Baz</code> must have an accessible constructor taking no 
  +     * arguments.
  +     * </ul>
  +     * Note that only one method is retained to create/set/addConfigured/add 
  +     * any element or attribute.
  +     * 
  +     * @param bean The bean type to introspect. 
  +     *             Must not be <code>null</code>.
  +     * 
  +     * @see #getHelper(Class)
  +     */
       private IntrospectionHelper(final Class bean) {
           attributeTypes = new Hashtable();
           attributeSetters = new Hashtable();
  @@ -124,7 +203,7 @@
           this.bean = bean;
   
           Method[] methods = bean.getMethods();
  -        for (int i=0; i<methods.length; i++) {
  +        for (int i = 0; i < methods.length; i++) {
               final Method m = methods[i];
               final String name = m.getName();
               Class returnType = m.getReturnType();
  @@ -132,23 +211,16 @@
   
               // not really user settable properties on tasks
               if (org.apache.tools.ant.Task.class.isAssignableFrom(bean)
  -                && args.length == 1 &&
  -                (
  -                 (
  -                  "setLocation".equals(name) && org.apache.tools.ant.Location.class.equals(args[0])
  -                  ) || (
  -                   "setTaskType".equals(name) && java.lang.String.class.equals(args[0])
  -                  )
  -                 )) {
  +                 && args.length == 1 && isHiddenSetMethod(name, args[0])) {
                   continue;
               }
   
               // hide addTask for TaskContainers
  -//              if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean)
  -//                  && args.length == 1 && "addTask".equals(name)
  -//                  && org.apache.tools.ant.Task.class.equals(args[0])) {
  -//                  continue;
  -//              }
  +            if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean)
  +                && args.length == 1 && "addTask".equals(name)
  +                && org.apache.tools.ant.Task.class.equals(args[0])) {
  +                continue;
  +            }
   
   
               if ("addText".equals(name)
  @@ -185,7 +257,7 @@
                           particular order.
                       */
                   }
  -                AttributeSetter as = createAttributeSetter(m, args[0]);
  +                AttributeSetter as = createAttributeSetter(m, args[0], propName);
                   if (as != null) {
                       attributeTypes.put(propName, args[0]);
                       attributeSetters.put(propName, as);
  @@ -225,9 +297,7 @@
                       nestedCreators.put(propName, new NestedCreator() {
   
                               public Object create(Object parent)
  -                                throws InvocationTargetException, 
  -				       IllegalAccessException, 
  -				       InstantiationException {
  +                                throws InvocationTargetException, IllegalAccessException, InstantiationException {
   
                                   Object o = c.newInstance(new Object[] {});
                                   return o;
  @@ -237,9 +307,7 @@
                       nestedStorers.put(propName, new NestedStorer() {
   
                               public void store(Object parent, Object child)
  -                                throws InvocationTargetException, 
  -				       IllegalAccessException, 
  -				       InstantiationException {
  +                                throws InvocationTargetException, IllegalAccessException, InstantiationException {
   
                                   m.invoke(parent, new Object[] {child});
                               }
  @@ -262,9 +330,7 @@
                       nestedCreators.put(propName, new NestedCreator() {
   
                               public Object create(Object parent)
  -                                throws InvocationTargetException, 
  -				       IllegalAccessException, 
  -				       InstantiationException {
  +                                throws InvocationTargetException, IllegalAccessException, InstantiationException {
   
                                   Object o = c.newInstance(new Object[] {});
                                   m.invoke(parent, new Object[] {o});
  @@ -279,8 +345,36 @@
           }
       }
   
  -    /**
  -     * Factory method for helper objects.
  +    /** 
  +     * Certain set methods are part of the Ant core interface to tasks and 
  +     * therefore not to be considered for introspection
  +     *
  +     * @param name the name of the set method
  +     * @param type the type of the set method's parameter 
  +     * @return true if the given set method is to be hidden.
  +     */
  +    private boolean isHiddenSetMethod(String name, Class type) {
  +        if ("setLocation".equals(name) 
  +             && org.apache.tools.ant.Location.class.equals(type)) {
  +            return true;
  +        }
  +        
  +        if  ("setTaskType".equals(name) 
  +             && java.lang.String.class.equals(type)) {
  +            return true;
  +        }
  +        
  +        return false;
  +    }
  +    
  +    /**
  +     * Returns a helper for the given class, either from the cache
  +     * or by creating a new instance.
  +     * 
  +     * @param c The class for which a helper is required.
  +     *          Must not be <code>null</code>.
  +     * 
  +     * @return a helper for the specified class
        */
       public static synchronized IntrospectionHelper getHelper(Class c) {
           IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
  @@ -292,17 +386,63 @@
       }
   
       /**
  -     * Sets the named attribute.
  +     * Returns a helper for the given class, either from the cache
  +     * or by creating a new instance.
  +     *
  +     * The method will make sure the helper will be cleaned up at the end of
  +     * the project, and only one instance will be created for each class.
  +     *
  +     * @param c The class for which a helper is required.
  +     *          Must not be <code>null</code>.
  +     *
  +     * @return a helper for the specified class
  +     */
  +    public static synchronized IntrospectionHelper getHelper(Project p, Class c)
  +    {
  +        IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
  +        if (ih == null) {
  +            ih = new IntrospectionHelper(c);
  +            helpers.put(c, ih);
  +            // Cleanup at end of project
  +            p.addBuildListener(ih);
  +        }
  +        return ih;
  +    }
  +
  +    /**
  +     * Sets the named attribute in the given element, which is part of the 
  +     * given project.
  +     * 
  +     * @param p The project containing the element. This is used when files 
  +     *          need to be resolved. Must not be <code>null</code>.
  +     * @param element The element to set the attribute in. Must not be 
  +     *                <code>null</code>.
  +     * @param attributeName The name of the attribute to set. Must not be
  +     *                      <code>null</code>.
  +     * @param value The value to set the attribute to. This may be interpreted
  +     *              or converted to the necessary type if the setter method
  +     *              doesn't just take a string. Must not be <code>null</code>.
  +     * 
  +     * @exception BuildException if the introspected class doesn't support 
  +     *                           the given attribute, or if the setting 
  +     *                           method fails.
        */
       public void setAttribute(Project p, Object element, String attributeName,
  -                             String value)
  -        throws BuildException {
  -        AttributeSetter as = (AttributeSetter) attributeSetters.get(attributeName);
  +                             String value) throws BuildException {
  +        AttributeSetter as
  +            = (AttributeSetter) attributeSetters.get(attributeName);
           if (as == null) {
  -            String msg = getElementName(p, element) +
  -            //String msg = "Class " + element.getClass().getName() +
  -                " doesn't support the \"" + attributeName + "\" attribute.";
  -            throw new BuildException(msg);
  +            if (element instanceof DynamicConfigurator) {
  +                DynamicConfigurator dc = (DynamicConfigurator) element;
  +                dc.setDynamicAttribute(attributeName, value);
  +                return;
  +            }
  +            else {
  +                String msg = getElementName(p, element) +
  +                    " doesn't support the \"" + attributeName +
  +                    "\" attribute.";
  +                throw new BuildException(msg);
  +            }
           }
           try {
               as.set(p, element, value);
  @@ -319,18 +459,32 @@
       }
   
       /**
  -     * Adds PCDATA areas.
  +     * Adds PCDATA to an element, using the element's 
  +     * <code>void addText(String)</code> method, if it has one. If no
  +     * such method is present, a BuildException is thrown if the 
  +     * given text contains non-whitespace.
  +     * 
  +     * @param project The project which the element is part of. 
  +     *                Must not be <code>null</code>.
  +     * @param element The element to add the text to. 
  +     *                Must not be <code>null</code>.
  +     * @param text    The text to add.
  +     *                Must not be <code>null</code>.
  +     * 
  +     * @exception BuildException if non-whitespace text is provided and no
  +     *                           method is available to handle it, or if
  +     *                           the handling method fails.
        */
  -    public void addText(Project project, Object element, String text) {
  +    public void addText(Project project, Object element, String text) 
  +        throws BuildException {
           if (addText == null) {
               // Element doesn't handle text content
  -            if ( text.trim().length() == 0 ) {
  +            if (text.trim().length() == 0) {
                   // Only whitespace - ignore
                   return;
  -            }
  -            else {
  +            } else {
                   // Not whitespace - fail
  -                String msg = getElementName(project, element) +
  +                String msg = project.getElementName(element) +
                       " doesn't support nested text data.";
                   throw new BuildException(msg);
               }
  @@ -350,25 +504,46 @@
       }
   
       /**
  -     * Creates a named nested element.
  -     */
  -    public Object createElement(Project project, Object element, String elementName)
  -        throws BuildException {
  -	
  +     * Creates a named nested element. Depending on the results of the
  +     * initial introspection, either a method in the given parent instance
  +     * or a simple no-arg constructor is used to create an instance of the
  +     * specified element type.
  +     * 
  +     * @param project Project to which the parent object belongs.
  +     *                Must not be <code>null</code>. If the resulting
  +     *                object is an instance of ProjectComponent, its
  +     *                Project reference is set to this parameter value.
  +     * @param parent  Parent object used to create the instance.
  +     *                Must not be <code>null</code>.
  +     * @param elementName Name of the element to create an instance of.
  +     *                    Must not be <code>null</code>.
  +     * 
  +     * @return an instance of the specified element type
  +     * 
  +     * @exception BuildException if no method is available to create the
  +     *                           element instance, or if the creating method
  +     *                           fails.
  +     */
  +    public Object createElement(Project project, Object parent, 
  +        String elementName) throws BuildException {
  +        NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
  +        if (nc == null && parent instanceof DynamicConfigurator) {
  +            DynamicConfigurator dc = (DynamicConfigurator) parent;
  +            Object nestedElement = dc.createDynamicElement(elementName);
  +            if (nestedElement != null) {
  +                if (nestedElement instanceof ProjectComponent) {
  +                    ((ProjectComponent) nestedElement).setProject(project);
  +                }
  +                return nestedElement;
  +            }
  +        }
  +        if (nc == null) {
  +            String msg = project.getElementName(parent) +
  +                " doesn't support the nested \"" + elementName + "\" element.";
  +            throw new BuildException(msg);
  +        }
           try {
  -	    // First check if there are any roles supported by this class
  -	    Object nestedElement = project.createInRole(element, elementName);
  -	    if (nestedElement == null) {
  -		NestedCreator nc = 
  -		    (NestedCreator) nestedCreators.get(elementName);
  -		if (nc == null) {
  -		    String msg = getElementName(project, element) +
  -			" doesn't support the nested \"" + elementName + 
  -			"\" element.";
  -		    throw new BuildException(msg);
  -		}
  -		nestedElement = nc.create(element);
  -	    }
  +            Object nestedElement = nc.create(parent);
               if (nestedElement instanceof ProjectComponent) {
                   ((ProjectComponent) nestedElement).setProject(project);
               }
  @@ -389,19 +564,48 @@
       }
   
       /**
  -     * Creates a named nested element.
  +     * Indicate if this element supports a nested element of the 
  +     * given name.
  +     *
  +     * @param elementName the name of the nested element being checked
  +     *
  +     * @return true if the given nested element is supported
  +     */
  +    public boolean supportsNestedElement(String elementName) {
  +        return nestedCreators.containsKey(elementName);
  +    }
  +    
  +    /**
  +     * Stores a named nested element using a storage method determined
  +     * by the initial introspection. If no appropriate storage method
  +     * is available, this method returns immediately.
  +     * 
  +     * @param project Ignored in this implementation. 
  +     *                May be <code>null</code>.
  +     * 
  +     * @param parent  Parent instance to store the child in. 
  +     *                Must not be <code>null</code>.
  +     * 
  +     * @param child   Child instance to store in the parent.
  +     *                Should not be <code>null</code>.
  +     * 
  +     * @param elementName  Name of the child element to store. 
  +     *                     May be <code>null</code>, in which case
  +     *                     this method returns immediately.
  +     * 
  +     * @exception BuildException if the storage method fails.
        */
  -    public void storeElement(Project project, Object element, Object child, String elementName)
  -        throws BuildException {
  +    public void storeElement(Project project, Object parent, Object child, 
  +        String elementName) throws BuildException {
           if (elementName == null) {
               return;
           }
  -        NestedStorer ns = (NestedStorer)nestedStorers.get(elementName);
  +        NestedStorer ns = (NestedStorer) nestedStorers.get(elementName);
           if (ns == null) {
               return;
           }
           try {
  -            ns.store(element, child);
  +            ns.store(parent, child);
           } catch (IllegalAccessException ie) {
               // impossible as getMethods should only return public methods
               throw new BuildException(ie);
  @@ -418,7 +622,16 @@
       }
   
       /**
  -     * returns the type of a named nested element.
  +     * Returns the type of a named nested element.
  +     * 
  +     * @param elementName The name of the element to find the type of.
  +     *                    Must not be <code>null</code>.
  +     * 
  +     * @return the type of the nested element with the specified name.
  +     *         This will never be <code>null</code>.
  +     * 
  +     * @exception BuildException if the introspected class does not
  +     *                           support the named nested element.
        */
       public Class getElementType(String elementName)
           throws BuildException {
  @@ -432,7 +645,16 @@
       }
   
       /**
  -     * returns the type of a named attribute.
  +     * Returns the type of a named attribute.
  +     * 
  +     * @param attributeName The name of the attribute to find the type of.
  +     *                      Must not be <code>null</code>.
  +     * 
  +     * @return the type of the attribute with the specified name.
  +     *         This will never be <code>null</code>.
  +     * 
  +     * @exception BuildException if the introspected class does not
  +     *                           support the named attribute.
        */
       public Class getAttributeType(String attributeName)
           throws BuildException {
  @@ -446,35 +668,77 @@
       }
   
       /**
  -     * Does the introspected class support PCDATA?
  +     * Returns whether or not the introspected class supports PCDATA.
  +     * 
  +     * @return whether or not the introspected class supports PCDATA.
        */
       public boolean supportsCharacters() {
           return addText != null;
       }
   
       /**
  -     * Return all attribues supported by the introspected class.
  +     * Returns an enumeration of the names of the attributes supported 
  +     * by the introspected class.
  +     * 
  +     * @return an enumeration of the names of the attributes supported
  +     *         by the introspected class.
        */
       public Enumeration getAttributes() {
           return attributeSetters.keys();
       }
   
       /**
  -     * Return all nested elements supported by the introspected class.
  +     * Returns an enumeration of the names of the nested elements supported 
  +     * by the introspected class.
  +     * 
  +     * @return an enumeration of the names of the nested elements supported
  +     *         by the introspected class.
        */
       public Enumeration getNestedElements() {
           return nestedTypes.keys();
       }
   
       /**
  -     * Create a proper implementation of AttributeSetter for the given
  -     * attribute type.
  +     * Creates an implementation of AttributeSetter for the given
  +     * attribute type. Conversions (where necessary) are automatically
  +     * made for the following types:
  +     * <ul>
  +     * <li>String (left as it is)
  +     * <li>Character/char (first character is used)
  +     * <li>Boolean/boolean 
  +     * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used)
  +     * <li>Class (Class.forName is used)
  +     * <li>File (resolved relative to the appropriate project)
  +     * <li>Path (resolve relative to the appropriate project)
  +     * <li>EnumeratedAttribute (uses its own 
  +     * {@link EnumeratedAttribute#setValue(String) setValue} method)
  +     * <li>Other primitive types (wrapper classes are used with constructors 
  +     * taking String)
  +     * </ul>
  +     * 
  +     * If none of the above covers the given parameters, a constructor for the 
  +     * appropriate class taking a String parameter is used if it is available.
  +     * 
  +     * @param m The method to invoke on the bean when the setter is invoked.
  +     *          Must not be <code>null</code>.
  +     * @param arg The type of the single argument of the bean's method.
  +     *            Must not be <code>null</code>.
  +     * @param attrName the name of the attribute for which the setter is being
  +     *                 created.
  +     * 
  +     * @return an appropriate AttributeSetter instance, or <code>null</code>
  +     *         if no appropriate conversion is available.
        */
       private AttributeSetter createAttributeSetter(final Method m,
  -                                                  final Class arg) {
  +                                                  Class arg, 
  +                                                  final String attrName) {
  +        // use wrappers for primitive classes, e.g. int and 
  +        // Integer are treated identically
  +        final Class reflectedArg = PRIMITIVE_TYPE_MAP.containsKey (arg) 
  +            ? (Class) PRIMITIVE_TYPE_MAP.get(arg) : arg;
   
           // simplest case - setAttribute expects String
  -        if (java.lang.String.class.equals(arg)) {
  +        if (java.lang.String.class.equals(reflectedArg)) {
               return new AttributeSetter() {
                       public void set(Project p, Object parent, String value)
                           throws InvocationTargetException, IllegalAccessException {
  @@ -482,69 +746,23 @@
                       }
                   };
   
  -        // now for the primitive types, use their wrappers
  -        } else if (java.lang.Character.class.equals(arg)
  -                   || java.lang.Character.TYPE.equals(arg)) {
  +        // char and Character get special treatment - take the first character
  +        } else if (java.lang.Character.class.equals(reflectedArg)) {
               return new AttributeSetter() {
                       public void set(Project p, Object parent, String value)
                           throws InvocationTargetException, IllegalAccessException {
  +                        if (value.length() == 0) {
  +                            throw new BuildException("The value \"\" is not a " 
  +                                + "legal value for attribute \"" 
  +                                + attrName + "\"");
  +                        }
                           m.invoke(parent, new Character[] {new Character(value.charAt(0))});
                       }
   
                   };
  -        } else if (java.lang.Byte.TYPE.equals(arg)) {
  -            return new AttributeSetter() {
  -                    public void set(Project p, Object parent, String value)
  -                        throws InvocationTargetException, IllegalAccessException {
  -                        m.invoke(parent, new Byte[] {new Byte(value)});
  -                    }
  -
  -                };
  -        } else if (java.lang.Short.TYPE.equals(arg)) {
  -            return new AttributeSetter() {
  -                    public void set(Project p, Object parent, String value)
  -                        throws InvocationTargetException, IllegalAccessException {
  -                        m.invoke(parent, new Short[] {new Short(value)});
  -                    }
  -
  -                };
  -        } else if (java.lang.Integer.TYPE.equals(arg)) {
  -            return new AttributeSetter() {
  -                    public void set(Project p, Object parent, String value)
  -                        throws InvocationTargetException, IllegalAccessException {
  -                        m.invoke(parent, new Integer[] {new Integer(value)});
  -                    }
  -
  -                };
  -        } else if (java.lang.Long.TYPE.equals(arg)) {
  -            return new AttributeSetter() {
  -                    public void set(Project p, Object parent, String value)
  -                        throws InvocationTargetException, IllegalAccessException {
  -                        m.invoke(parent, new Long[] {new Long(value)});
  -                    }
  -
  -                };
  -        } else if (java.lang.Float.TYPE.equals(arg)) {
  -            return new AttributeSetter() {
  -                    public void set(Project p, Object parent, String value)
  -                        throws InvocationTargetException, IllegalAccessException {
  -                        m.invoke(parent, new Float[] {new Float(value)});
  -                    }
  -
  -                };
  -        } else if (java.lang.Double.TYPE.equals(arg)) {
  -            return new AttributeSetter() {
  -                    public void set(Project p, Object parent, String value)
  -                        throws InvocationTargetException, IllegalAccessException {
  -                        m.invoke(parent, new Double[] {new Double(value)});
  -                    }
  -
  -                };
  -
  -        // boolean gets an extra treatment, because we have a nice method
  -        // in Project
  -        } else if (java.lang.Boolean.class.equals(arg)
  -                   || java.lang.Boolean.TYPE.equals(arg)) {
  +        // boolean and Boolean get special treatment because we 
  +        // have a nice method in Project
  +        } else if (java.lang.Boolean.class.equals(reflectedArg)) {
               return new AttributeSetter() {
                       public void set(Project p, Object parent, String value)
                           throws InvocationTargetException, IllegalAccessException {
  @@ -555,7 +773,7 @@
                   };
   
           // Class doesn't have a String constructor but a decent factory method
  -        } else if (java.lang.Class.class.equals(arg)) {
  +        } else if (java.lang.Class.class.equals(reflectedArg)) {
               return new AttributeSetter() {
                       public void set(Project p, Object parent, String value)
                           throws InvocationTargetException, IllegalAccessException, BuildException {
  @@ -568,7 +786,7 @@
                   };
   
           // resolve relative paths through Project
  -        } else if (java.io.File.class.equals(arg)) {
  +        } else if (java.io.File.class.equals(reflectedArg)) {
               return new AttributeSetter() {
                       public void set(Project p, Object parent, String value)
                           throws InvocationTargetException, IllegalAccessException {
  @@ -578,7 +796,7 @@
                   };
   
           // resolve relative paths through Project
  -        } else if (org.apache.tools.ant.types.Path.class.equals(arg)) {
  +        } else if (org.apache.tools.ant.types.Path.class.equals(reflectedArg)) {
               return new AttributeSetter() {
                       public void set(Project p, Object parent, String value)
                           throws InvocationTargetException, IllegalAccessException {
  @@ -588,12 +806,13 @@
                   };
   
           // EnumeratedAttributes have their own helper class
  -        } else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(arg)) {
  +        } else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
               return new AttributeSetter() {
                       public void set(Project p, Object parent, String value)
                           throws InvocationTargetException, IllegalAccessException, BuildException {
                           try {
  -                            org.apache.tools.ant.types.EnumeratedAttribute ea = (org.apache.tools.ant.types.EnumeratedAttribute)arg.newInstance();
  +                            org.apache.tools.ant.types.EnumeratedAttribute ea = 
  +                                (org.apache.tools.ant.types.EnumeratedAttribute) reflectedArg.newInstance();
                               ea.setValue(value);
                               m.invoke(parent, new EnumeratedAttribute[] {ea});
                           } catch (InstantiationException ie) {
  @@ -603,11 +822,13 @@
                   };
   
           // worst case. look for a public String constructor and use it
  +        // This is used (deliberately) for all primitives/wrappers other than 
  +        // char and boolean
           } else {
   
               try {
                   final Constructor c =
  -                    arg.getConstructor(new Class[] {java.lang.String.class});
  +                    reflectedArg.getConstructor(new Class[] {java.lang.String.class});
   
                   return new AttributeSetter() {
                           public void set(Project p, Object parent,
  @@ -632,63 +853,76 @@
           return null;
       }
   
  -    protected String getElementName(Project project, Object element)
  -    {
  -        Hashtable elements = project.getTaskDefinitions();
  -        String typeName = "task";
  -        if (!elements.contains( element.getClass() ))
  -        {
  -            elements = project.getDataTypeDefinitions();
  -            typeName = "data type";
  -            if (!elements.contains( element.getClass() ))
  -            {
  -                elements = null;
  -            }
  -        }
  -
  -        if (elements != null)
  -        {
  -            Enumeration e = elements.keys();
  -            while (e.hasMoreElements())
  -            {
  -                String elementName = (String) e.nextElement();
  -                Class elementClass = (Class) elements.get( elementName );
  -                if ( element.getClass().equals( elementClass ) )
  -                {
  -                    return "The <" + elementName + "> " + typeName;
  -                }
  -            }
  -        }
  -
  -        return "Class " + element.getClass().getName();
  +    /**
  +     * Returns a description of the type of the given element in
  +     * relation to a given project. This is used for logging purposes
  +     * when the element is asked to cope with some data it has no
  +     * way of handling.
  +     * 
  +     * @param project The project the element is defined in. 
  +     *                Must not be <code>null</code>.
  +     * 
  +     * @param element The element to describe.
  +     *                Must not be <code>null</code>.
  +     * 
  +     * @return a description of the element type
  +     */
  +    protected String getElementName(Project project, Object element) {
  +        return project.getElementName(element);
       }
   
       /**
  -     * extract the name of a property from a method name - subtracting
  -     * a given prefix.
  +     * Extracts the name of a property from a method name by subtracting
  +     * a given prefix and converting into lower case. It is up to calling
  +     * code to make sure the method name does actually begin with the
  +     * specified prefix - no checking is done in this method.
  +     * 
  +     * @param methodName The name of the method in question.
  +     *                   Must not be <code>null</code>.
  +     * @param prefix     The prefix to remove.
  +     *                   Must not be <code>null</code>.
  +     * 
  +     * @return the lower-cased method name with the prefix removed.
        */
       private String getPropertyName(String methodName, String prefix) {
           int start = prefix.length();
           return methodName.substring(start).toLowerCase(Locale.US);
       }
   
  +    /**
  +     * Internal interface used to create nested elements. Not documented 
  +     * in detail for reasons of source code readability.
  +     */
       private interface NestedCreator {
           Object create(Object parent)
               throws InvocationTargetException, IllegalAccessException, InstantiationException;
       }
   
  +    /**
  +     * Internal interface used to storing nested elements. Not documented 
  +     * in detail for reasons of source code readability.
  +     */
       private interface NestedStorer {
           void store(Object parent, Object child)
               throws InvocationTargetException, IllegalAccessException, InstantiationException;
       }
   
  +    /**
  +     * Internal interface used to setting element attributes. Not documented 
  +     * in detail for reasons of source code readability.
  +     */
       private interface AttributeSetter {
           void set(Project p, Object parent, String value)
               throws InvocationTargetException, IllegalAccessException,
                      BuildException;
       }
   
  -    public void buildStarted(BuildEvent event) {}
  +    /**
  +     * Clears all storage used by this class, including the static cache of 
  +     * helpers.
  +     * 
  +     * @param event Ignored in this implementation.
  +     */
       public void buildFinished(BuildEvent event) {
           attributeTypes.clear();
           attributeSetters.clear();
  @@ -698,9 +932,44 @@
           helpers.clear();
       }
   
  +    /**
  +     * Empty implementation to satisfy the BuildListener interface.
  +     * @param event Ignored in this implementation.
  +     */
  +    public void buildStarted(BuildEvent event) {}
  +    
  +    /**
  +     * Empty implementation to satisfy the BuildListener interface.
  +     *
  +     * @param event Ignored in this implementation.
  +     */
       public void targetStarted(BuildEvent event) {}
  +    
  +    /**
  +     * Empty implementation to satisfy the BuildListener interface.
  +     *
  +     * @param event Ignored in this implementation.
  +     */
       public void targetFinished(BuildEvent event) {}
  +    
  +    /**
  +     * Empty implementation to satisfy the BuildListener interface.
  +     *
  +     * @param event Ignored in this implementation.
  +     */
       public void taskStarted(BuildEvent event) {}
  +    
  +    /**
  +     * Empty implementation to satisfy the BuildListener interface.
  +     *
  +     * @param event Ignored in this implementation.
  +     */
       public void taskFinished(BuildEvent event) {}
  +    
  +    /**
  +     * Empty implementation to satisfy the BuildListener interface.
  +     *
  +     * @param event Ignored in this implementation.
  +     */
       public void messageLogged(BuildEvent event) {}
   }
  
  
  
  1.5       +409 -33   ant/proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java
  
  Index: Project.java
  ===================================================================
  RCS file: /home/cvs/ant/proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- Project.java	3 Mar 2002 12:37:13 -0000	1.4
  +++ Project.java	1 Apr 2003 11:35:06 -0000	1.5
  @@ -1,7 +1,7 @@
   /*
    *  The Apache Software License, Version 1.1
    *
  - *  Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
  + *  Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
    *  reserved.
    *
    *  Redistribution and use in source and binary forms, with or without
  @@ -61,7 +61,6 @@
   import java.util.Properties;
   import java.util.Enumeration;
   import java.util.Stack;
  -import java.lang.reflect.Modifier;
   import java.lang.reflect.Method;
   import java.lang.reflect.InvocationTargetException;
   
  @@ -70,8 +69,10 @@
   import org.apache.tools.ant.types.FilterSet;
   import org.apache.tools.ant.types.FilterSetCollection;
   import org.apache.tools.ant.util.FileUtils;
  +import org.apache.tools.ant.util.LazyHashtable;
   import org.apache.tools.ant.types.Path;
   import org.apache.tools.ant.taskdefs.Antlib;
  +import org.apache.tools.ant.input.InputHandler;
   
   /**
    *  Central representation of an Ant project. This class defines a Ant project
  @@ -165,13 +166,16 @@
   
       private String name;
       private String description;
  +    /** Map of references within the project (paths etc) (String to Object). */
   
       private Hashtable properties = new Hashtable();
       private Hashtable userProperties = new Hashtable();
  -    private Hashtable references = new Hashtable();
  +    private Hashtable references = new AntRefTable(this);
       private String defaultTarget;
  -    //    private Hashtable dataClassDefinitions = new Hashtable();
  -    //    private Hashtable taskClassDefinitions = new Hashtable();
  +    /** Map from data type names to implementing classes (String to Class). */
  +    private Hashtable dataClassDefinitions = new AntTaskTable(this, false);
  +    /** Map from task names to implementing classes (String to Class). */
  +    private Hashtable taskClassDefinitions = new AntTaskTable(this, true);
       private Hashtable createdTasks = new Hashtable();
       private Hashtable targets = new Hashtable();
       private FilterSet globalFilterSet = new FilterSet();
  @@ -185,11 +189,13 @@
        */
       private ClassLoader coreLoader = null;
   
  -    /**
  -     *  Records the latest task on a thread
  -     */
  +    /** Records the latest task to be executed on a thread (Thread to Task). */
       private Hashtable threadTasks = new Hashtable();
   
  +    /** Records the latest task to be executed on a thread Group. */
  +    private Hashtable threadGroupTasks = new Hashtable();
  +
  +
       /**
        *  Store symbol tables
        */
  @@ -219,6 +225,48 @@
           }
       }
   
  +    /**
  +     * Called to handle any input requests.
  +     */
  +    private InputHandler inputHandler = null;
  +
  +    /**
  +     * The default input stream used to read any input
  +     */
  +    private InputStream defaultInputStream = null;
  +
  +    /**
  +     * Sets the input handler
  +     *
  +     * @param handler the InputHandler instance to use for gathering input.
  +     */
  +    public void setInputHandler(InputHandler handler) {
  +        inputHandler = handler;
  +    }
  +
  +    /**
  +     * Set the default System input stream. Normally this stream is set to
  +     * System.in. This inputStream is used when no task inptu redirection is
  +     * being performed.
  +     *
  +     * @param defaultInputStream the default input stream to use when input
  +     *        is reuested.
  +     * @since Ant 1.6
  +     */
  +    public void setDefaultInputStream(InputStream defaultInputStream) {
  +        this.defaultInputStream = defaultInputStream;
  +    }
  +
  +    /**
  +     * Retrieves the current input handler.
  +     *
  +     * @return the InputHandler instance currently in place for the project
  +     *         instance.
  +     */
  +    public InputHandler getInputHandler() {
  +        return inputHandler;
  +    }
  +
       private FileUtils fileUtils;
   
   
  @@ -302,12 +350,17 @@
   
       private void autoLoadDefinitions() {
           DirectoryScanner ds = new DirectoryScanner();
  -        ds.setBasedir(new File(getProperty("ant.home"),"autolib"));
  -        ds.scan();
  -        String dirs[] = ds.getIncludedDirectories();
  -        for (int i = 0; i < dirs.length; i++) {
  -            autoLoad(ds.getBasedir(), dirs[i]);
  -        }
  +        try {
  +            File autolib=new File(getProperty("ant.home"),"autolib");
  +            log("scanning the autolib directory "+autolib.toString(),MSG_DEBUG);
  +            ds.setBasedir(autolib);
  +            ds.scan();
  +            String dirs[] = ds.getIncludedDirectories();
  +            for (int i = 0; i < dirs.length; i++) {
  +                autoLoad(ds.getBasedir(), dirs[i]);
  +            }
  +        } catch (Exception e) {}
  +
       }
   
       private void autoLoad(File base, String dir) {
  @@ -343,21 +396,59 @@
   
   
       /**
  -     *  Initialise the project. This involves setting the default task
  -     *  definitions and loading the system properties.
  +     * Initialises the project.
        *
  -     *@exception  BuildException  Description of the Exception
  +     * This involves setting the default task definitions and loading the
  +     * system properties.
  +     *
  +     * @exception BuildException if the default task list cannot be loaded
        */
       public void init() throws BuildException {
           setJavaVersionProperty();
  -        setSystemProperties();
           if (!isRoleDefined(TASK_ROLE)) {
               // Top project, need to load the core definitions
               loadDefinitions();
           }
  +        String defs = "/org/apache/tools/ant/taskdefs/defaults.properties";
  +
  +        try {
  +            Properties props = new Properties();
  +            InputStream in = this.getClass().getResourceAsStream(defs);
  +            if (in == null) {
  +                throw new BuildException("Can't load default task list");
  +            }
  +            props.load(in);
  +            in.close();
  +            ((AntTaskTable) taskClassDefinitions).addDefinitions(props);
  +
  +
  +        } catch (IOException ioe) {
  +            throw new BuildException("Can't load default task list");
  +        }
  +
  +        String dataDefs = "/org/apache/tools/ant/types/defaults.properties";
  +
  +        try {
  +            Properties props = new Properties();
  +            InputStream in = this.getClass().getResourceAsStream(dataDefs);
  +            if (in == null) {
  +                throw new BuildException("Can't load default datatype list");
  +            }
  +            props.load(in);
  +            in.close();
  +
  +            ((AntTaskTable) dataClassDefinitions).addDefinitions(props);
  +
  +
  +        } catch (IOException ioe) {
  +            throw new BuildException("Can't load default datatype list");
  +        }
  +
  +        setSystemProperties();
       }
   
   
  +
       /**
        *  Sets the CoreLoader to the default of the Project object
        */
  @@ -1223,13 +1314,16 @@
   
   
       /**
  -     *  Description of the Method
  -     *
  -     *@param  line     Description of the Parameter
  -     *@param  isError  Description of the Parameter
  +     * Demultiplexes output so that each task receives the appropriate
  +     * messages. If the current thread is not currently executing a task,
  +     * the message is logged directly.
  +     *
  +     * @param line Message to handle. Should not be <code>null</code>.
  +     * @param isError Whether the text represents an error (<code>true</code>)
  +     *        or information (<code>false</code>).
        */
       public void demuxOutput(String line, boolean isError) {
  -        Task task = (Task) threadTasks.get(Thread.currentThread());
  +        Task task = getThreadTask(Thread.currentThread());
           if (task == null) {
               fireMessageLogged(this, line, isError ? MSG_ERR : MSG_INFO);
           } else {
  @@ -1241,6 +1335,75 @@
           }
       }
   
  +    /**
  +     * Read data from the default input stream. If no default has been
  +     * specified, System.in is used.
  +     *
  +     * @param buffer the buffer into which data is to be read.
  +     * @param offset the offset into the buffer at which data is stored.
  +     * @param length the amount of data to read
  +     *
  +     * @return the number of bytes read
  +     *
  +     * @exception IOException if the data cannot be read
  +     * @since Ant 1.6
  +     */
  +    public int defaultInput(byte[] buffer, int offset, int length)
  +        throws IOException {
  +        if (defaultInputStream != null) {
  +            return defaultInputStream.read(buffer, offset, length);
  +        } else {
  +            return System.in.read(buffer, offset, length);
  +        }
  +    }
  +
  +    /**
  +     * Demux an input request to the correct task.
  +     *
  +     * @param buffer the buffer into which data is to be read.
  +     * @param offset the offset into the buffer at which data is stored.
  +     * @param length the amount of data to read
  +     *
  +     * @return the number of bytes read
  +     *
  +     * @exception IOException if the data cannot be read
  +     * @since Ant 1.6
  +     */
  +    public int demuxInput(byte[] buffer, int offset, int length)
  +        throws IOException {
  +        Task task = getThreadTask(Thread.currentThread());
  +        if (task == null) {
  +            return defaultInput(buffer, offset, length);
  +        } else {
  +            return task.handleInput(buffer, offset, length);
  +        }
  +    }
  +
  +    /**
  +     * Demultiplexes flush operation so that each task receives the appropriate
  +     * messages. If the current thread is not currently executing a task,
  +     * the message is logged directly.
  +     *
  +     * @since Ant 1.5.2
  +     *
  +     * @param line Message to handle. Should not be <code>null</code>.
  +     * @param isError Whether the text represents an error (<code>true</code>)
  +     *        or information (<code>false</code>).
  +     */
  +    public void demuxFlush(String line, boolean isError) {
  +        Task task = getThreadTask(Thread.currentThread());
  +        if (task == null) {
  +            fireMessageLogged(this, line, isError ? MSG_ERR : MSG_INFO);
  +        } else {
  +            if (isError) {
  +                task.handleErrorFlush(line);
  +            } else {
  +                task.handleFlush(line);
  +            }
  +        }
  +    }
  +
  +
   
       /**
        *  execute the targets and any targets it depends on
  @@ -1646,21 +1809,35 @@
   
   
       /**
  -     *  Adds a feature to the Reference attribute of the Project object
  +     * Adds a reference to the project.
        *
  -     *@param  name   The feature to be added to the Reference attribute
  -     *@param  value  The feature to be added to the Reference attribute
  +     * @param name The name of the reference. Must not be <code>null</code>.
  +     * @param value The value of the reference. Must not be <code>null</code>.
        */
       public void addReference(String name, Object value) {
  -        Object o = references.get(name);
  -        if (null != o && o != value
  -                 && (!(o instanceof RoleAdapter)
  -                 || ((RoleAdapter) o).getProxy() != value)) {
  -            log("Overriding previous definition of reference to " + name,
  +        synchronized (references) {
  +            Object old = ((AntRefTable) references).getReal(name);
  +            if (old == value) {
  +                // no warning, this is not changing anything
  +                return;
  +            }
  +            if (old != null && !(old instanceof UnknownElement)) {
  +                log("Overriding previous definition of reference to " + name,
                       MSG_WARN);
  +            }
  +
  +            String valueAsString = "";
  +            try {
  +                valueAsString = value.toString();
  +            } catch (Throwable t) {
  +                log("Caught exception (" + t.getClass().getName() + ")"
  +                    + " while expanding " + name + ": " + t.getMessage(),
  +                    MSG_WARN);
  +            }
  +            log("Adding reference: " + name + " -> " + valueAsString,
  +                MSG_DEBUG);
  +            references.put(name, value);
           }
  -        log("Adding reference: " + name + " -> " + value, MSG_DEBUG);
  -        references.put(name, value);
       }
   
   
  @@ -1684,6 +1861,45 @@
   
   
       /**
  +     * Returns a description of the type of the given element, with
  +     * special handling for instances of tasks and data types.
  +     * <p>
  +     * This is useful for logging purposes.
  +     *
  +     * @param element The element to describe.
  +     *                Must not be <code>null</code>.
  +     *
  +     * @return a description of the element type
  +     *
  +     * @since 1.95, Ant 1.5
  +     */
  +    public String getElementName(Object element) {
  +        Hashtable elements = taskClassDefinitions;
  +        Class elementClass = element.getClass();
  +        String typeName = "task";
  +        if (!elements.contains(elementClass)) {
  +            elements = dataClassDefinitions;
  +            typeName = "data type";
  +            if (!elements.contains(elementClass)) {
  +                elements = null;
  +            }
  +        }
  +
  +        if (elements != null) {
  +            Enumeration e = elements.keys();
  +            while (e.hasMoreElements()) {
  +                String name = (String) e.nextElement();
  +                Class clazz = (Class) elements.get(name);
  +                if (elementClass.equals(clazz)) {
  +                    return "The <" + name + "> " + typeName;
  +                }
  +            }
  +        }
  +
  +        return "Class " + elementClass.getName();
  +    }
  +
  +    /**
        *  send build started event to the listeners
        */
       protected void fireBuildStarted() {
  @@ -1827,6 +2043,166 @@
       protected void fireMessageLogged(Task task, String message, int priority) {
           BuildEvent event = new BuildEvent(task);
           fireMessageLoggedEvent(event, message, priority);
  +    }
  +    /**
  +     * Register a task as the current task for a thread.
  +     * If the task is null, the thread's entry is removed.
  +     *
  +     * @param thread the thread on which the task is registered.
  +     * @param task the task to be registered.
  +     * @since Ant 1.5
  +     */
  +    public synchronized void registerThreadTask(Thread thread, Task task) {
  +        if (task != null) {
  +            threadTasks.put(thread, task);
  +            threadGroupTasks.put(thread.getThreadGroup(), task);
  +        } else {
  +            threadTasks.remove(thread);
  +            threadGroupTasks.remove(thread.getThreadGroup());
  +        }
  +    }
  +
  +    /**
  +     * Get the current task assopciated with a thread, if any
  +     *
  +     * @param thread the thread for which the task is required.
  +     * @return the task which is currently registered for the given thread or
  +     *         null if no task is registered.
  +     */
  +    public Task getThreadTask(Thread thread) {
  +        Task task = (Task) threadTasks.get(thread);
  +        if (task == null) {
  +            ThreadGroup group = thread.getThreadGroup();
  +            while (task == null && group != null) {
  +                task = (Task) threadGroupTasks.get(group);
  +                group = group.getParent();
  +            }
  +        }
  +        return task;
  +    }
  +
  +
  +    // Should move to a separate public class - and have API to add
  +    // listeners, etc.
  +    private static class AntRefTable extends Hashtable {
  +        Project project;
  +        public AntRefTable(Project project) {
  +            super();
  +            this.project = project;
  +        }
  +
  +        /** Returns the unmodified original object.
  +         * This method should be called internally to
  +         * get the 'real' object.
  +         * The normal get method will do the replacement
  +         * of UnknownElement ( this is similar with the JDNI
  +         * refs behavior )
  +         */
  +        public Object getReal(Object key) {
  +            return super.get(key);
  +        }
  +
  +        /** Get method for the reference table.
  +         *  It can be used to hook dynamic references and to modify
  +         * some references on the fly - for example for delayed
  +         * evaluation.
  +         *
  +         * It is important to make sure that the processing that is
  +         * done inside is not calling get indirectly.
  +         *
  +         * @param key
  +         * @return
  +         */
  +        public Object get(Object key) {
  +            //System.out.println("AntRefTable.get " + key);
  +            Object o = super.get(key);
  +            if (o instanceof UnknownElement) {
  +                // Make sure that
  +                ((UnknownElement) o).maybeConfigure();
  +                o = ((UnknownElement) o).getTask();
  +            }
  +            return o;
  +        }
  +    }
  +
  +    private static class AntTaskTable extends LazyHashtable {
  +        Project project;
  +        Properties props;
  +        boolean tasks = false;
  +
  +        public AntTaskTable(Project p, boolean tasks) {
  +            this.project = p;
  +            this.tasks = tasks;
  +        }
  +
  +        public void addDefinitions(Properties props) {
  +            this.props = props;
  +        }
  +
  +        protected void initAll() {
  +            if (initAllDone ) return;
  +            project.log("InitAll", Project.MSG_DEBUG);
  +            if (props==null ) return;
  +            Enumeration enum = props.propertyNames();
  +            while (enum.hasMoreElements()) {
  +                String key = (String) enum.nextElement();
  +                Class taskClass=getTask( key );
  +                if (taskClass!=null ) {
  +                    // This will call a get() and a put()
  +                    if (tasks )
  +                        project.addTaskDefinition(key, taskClass);
  +                    else
  +                        project.addDataTypeDefinition(key, taskClass );
  +                }
  +            }
  +            initAllDone=true;
  +        }
  +
  +        protected Class getTask(String key) {
  +            if (props==null ) return null; // for tasks loaded before init()
  +            String value=props.getProperty(key);
  +            if (value==null) {
  +                //project.log( "No class name for " + key, Project.MSG_VERBOSE );
  +                return null;
  +            }
  +            try {
  +                Class taskClass=null;
  +                if (project.getCoreLoader() != null &&
  +                    !("only".equals(project.getProperty("build.sysclasspath")))) {
  +                    try {
  +                        taskClass=project.getCoreLoader().loadClass(value);
  +                        if (taskClass != null ) return taskClass;
  +                    } catch( Exception ex ) {
  +                    }
  +                }
  +                taskClass = Class.forName(value);
  +                return taskClass;
  +            } catch (NoClassDefFoundError ncdfe) {
  +                project.log("Could not load a dependent class ("
  +                        + ncdfe.getMessage() + ") for task " + key, Project.MSG_DEBUG);
  +            } catch (ClassNotFoundException cnfe) {
  +                project.log("Could not load class (" + value
  +                        + ") for task " + key, Project.MSG_DEBUG);
  +            }
  +            return null;
  +        }
  +
  +        // Hashtable implementation
  +        public Object get( Object key ) {
  +            Object orig=super.get( key );
  +            if (orig!= null ) return orig;
  +            if (! (key instanceof String) ) return null;
  +            project.log("Get task " + key, Project.MSG_DEBUG );
  +            Object taskClass=getTask( (String) key);
  +            if (taskClass != null)
  +                super.put( key, taskClass );
  +            return taskClass;
  +        }
  +
  +        public boolean containsKey(Object key) {
  +            return get(key) != null;
  +        }
  +
       }
   
   }