Save This Page
Home » apache-ant-1.8.1 » org.apache.tools » ant » [javadoc | source]
    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one or more
    3    *  contributor license agreements.  See the NOTICE file distributed with
    4    *  this work for additional information regarding copyright ownership.
    5    *  The ASF licenses this file to You under the Apache License, Version 2.0
    6    *  (the "License"); you may not use this file except in compliance with
    7    *  the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    *  Unless required by applicable law or agreed to in writing, software
   12    *  distributed under the License is distributed on an "AS IS" BASIS,
   13    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    *  See the License for the specific language governing permissions and
   15    *  limitations under the License.
   16    *
   17    */
   18   package org.apache.tools.ant;
   19   
   20   import java.lang.reflect.Constructor;
   21   import java.lang.reflect.InvocationTargetException;
   22   import java.lang.reflect.Method;
   23   import java.util.ArrayList;
   24   import java.util.Collections;
   25   import java.util.Enumeration;
   26   import java.util.Hashtable;
   27   import java.util.HashMap;
   28   import java.util.List;
   29   import java.util.Locale;
   30   import java.util.Map;
   31   import org.apache.tools.ant.types.EnumeratedAttribute;
   32   import org.apache.tools.ant.types.Resource;
   33   import org.apache.tools.ant.types.resources.FileProvider;
   34   import org.apache.tools.ant.types.resources.FileResource;
   35   import org.apache.tools.ant.taskdefs.PreSetDef;
   36   import org.apache.tools.ant.util.StringUtils;
   37   
   38   /**
   39    * Helper class that collects the methods a task or nested element
   40    * holds to set attributes, create nested elements or hold PCDATA
   41    * elements.
   42    *
   43    * It contains hashtables containing classes that use introspection
   44    * to handle all the invocation of the project-component specific methods.
   45    *
   46    * This class is somewhat complex, as it implements the O/X mapping between
   47    * Ant XML and Java class instances. This is not the best place for someone new
   48    * to Ant to start contributing to the codebase, as a change here can break the
   49    * entire system in interesting ways. Always run a full test of Ant before checking
   50    * in/submitting changes to this file.
   51    *
   52    * The class is final and has a private constructor.
   53    * To get an instance for a specific (class,project) combination,
   54    * use {@link #getHelper(Project,Class)}.
   55    * This may return an existing version, or a new one
   56    * ...do not make any assumptions about its uniqueness, or its validity after the Project
   57    * instance has finished its build.
   58    *
   59    */
   60   public final class IntrospectionHelper {
   61   
   62       /**
   63        * Helper instances we've already created (Class.getName() to IntrospectionHelper).
   64        */
   65       private static final Map HELPERS = new Hashtable();
   66   
   67       /**
   68        * Map from primitive types to wrapper classes for use in
   69        * createAttributeSetter (Class to Class). Note that char
   70        * and boolean are in here even though they get special treatment
   71        * - this way we only need to test for the wrapper class.
   72        */
   73       private static final Map PRIMITIVE_TYPE_MAP = new HashMap(8);
   74   
   75       // Set up PRIMITIVE_TYPE_MAP
   76       static {
   77           Class[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE,
   78                                 Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE};
   79           Class[] wrappers = {Boolean.class, Byte.class, Character.class, Short.class,
   80                               Integer.class, Long.class, Float.class, Double.class};
   81           for (int i = 0; i < primitives.length; i++) {
   82               PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]);
   83           }
   84       }
   85   
   86       private static final int MAX_REPORT_NESTED_TEXT = 20;
   87       private static final String ELLIPSIS = "...";
   88   
   89       /**
   90        * Map from attribute names to attribute types
   91        * (String to Class).
   92        */
   93       private final Hashtable attributeTypes = new Hashtable();
   94   
   95       /**
   96        * Map from attribute names to attribute setter methods
   97        * (String to AttributeSetter).
   98        */
   99       private final Hashtable attributeSetters = new Hashtable();
  100   
  101       /**
  102        * Map from attribute names to nested types
  103        * (String to Class).
  104        */
  105       private final Hashtable nestedTypes = new Hashtable();
  106   
  107       /**
  108        * Map from attribute names to methods to create nested types
  109        * (String to NestedCreator).
  110        */
  111       private final Hashtable nestedCreators = new Hashtable();
  112   
  113       /**
  114        * Vector of methods matching add[Configured](Class) pattern.
  115        */
  116       private final List addTypeMethods = new ArrayList();
  117   
  118       /**
  119        * The method to invoke to add PCDATA.
  120        */
  121       private final Method addText;
  122   
  123       /**
  124        * The class introspected by this instance.
  125        */
  126       private final Class bean;
  127   
  128       /**
  129        * Sole constructor, which is private to ensure that all
  130        * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
  131        * Introspects the given class for bean-like methods.
  132        * Each method is examined in turn, and the following rules are applied:
  133        * <p>
  134        * <ul>
  135        * <li>If the method is <code>Task.setLocation(Location)</code>,
  136        * <code>Task.setTaskType(String)</code>
  137        * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These
  138        * methods are handled differently elsewhere.
  139        * <li><code>void addText(String)</code> is recognised as the method for
  140        * adding PCDATA to a bean.
  141        * <li><code>void setFoo(Bar)</code> is recognised as a method for
  142        * setting the value of attribute <code>foo</code>, so long as
  143        * <code>Bar</code> is non-void and is not an array type.
  144        * As of Ant 1.8, a Resource or FileProvider parameter overrides a java.io.File parameter;
  145        * in practice the only effect of this is to allow objects rendered from
  146        * the 1.8 PropertyHelper implementation to be used as Resource parameters,
  147        * since Resources set from Strings are resolved as project-relative files
  148        * to preserve backward compatibility.  Beyond this, non-String
  149        * parameter types always overload String parameter types; these are
  150        * the only guarantees made in terms of priority.
  151        * <li><code>Foo createBar()</code> is recognised as a method for
  152        * creating a nested element called <code>bar</code> of type
  153        * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
  154        * array type.
  155        * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
  156        * method for storing a pre-configured element called
  157        * <code>foo</code> and of type <code>Bar</code>, so long as
  158        * <code>Bar</code> is not an array, primitive or String type.
  159        * <code>Bar</code> must have an accessible constructor taking no
  160        * arguments.
  161        * <li><code>void addFoo(Bar)</code> is recognised as a method for storing
  162        * an element called <code>foo</code> and of type <code>Bar</code>, so
  163        * long as <code>Bar</code> is not an array, primitive or String type.
  164        * <code>Bar</code> must have an accessible constructor taking no
  165        * arguments. This is distinct from the 'addConfigured' idiom in that
  166        * the nested element is added to the parent immediately after it is
  167        * constructed; in practice this means that <code>addFoo(Bar)</code> should
  168        * do little or nothing with its argument besides storing it for later use.
  169        * </ul>
  170        * Note that only one method is retained to create/set/addConfigured/add
  171        * any element or attribute.
  172        *
  173        * @param bean The bean type to introspect.
  174        *             Must not be <code>null</code>.
  175        *
  176        * @see #getHelper(Class)
  177        */
  178       private IntrospectionHelper(final Class bean) {
  179           this.bean = bean;
  180           Method[] methods = bean.getMethods();
  181           Method addTextMethod = null;
  182           for (int i = 0; i < methods.length; i++) {
  183               final Method m = methods[i];
  184               final String name = m.getName();
  185               Class returnType = m.getReturnType();
  186               Class[] args = m.getParameterTypes();
  187   
  188               // check of add[Configured](Class) pattern
  189               if (args.length == 1 && java.lang.Void.TYPE.equals(returnType)
  190                       && ("add".equals(name) || "addConfigured".equals(name))) {
  191                   insertAddTypeMethod(m);
  192                   continue;
  193               }
  194               // not really user settable properties on tasks/project components
  195               if (org.apache.tools.ant.ProjectComponent.class.isAssignableFrom(bean)
  196                       && args.length == 1 && isHiddenSetMethod(name, args[0])) {
  197                   continue;
  198               }
  199               // hide addTask for TaskContainers
  200               if (isContainer() && args.length == 1 && "addTask".equals(name)
  201                       && org.apache.tools.ant.Task.class.equals(args[0])) {
  202                   continue;
  203               }
  204               if ("addText".equals(name) && java.lang.Void.TYPE.equals(returnType)
  205                       && args.length == 1 && java.lang.String.class.equals(args[0])) {
  206                   addTextMethod = methods[i];
  207               } else if (name.startsWith("set") && java.lang.Void.TYPE.equals(returnType)
  208                       && args.length == 1 && !args[0].isArray()) {
  209                   String propName = getPropertyName(name, "set");
  210                   AttributeSetter as = (AttributeSetter) attributeSetters.get(propName);
  211                   if (as != null) {
  212                       if (java.lang.String.class.equals(args[0])) {
  213                           /*
  214                               Ignore method m, as there is an overloaded
  215                               form of this method that takes in a
  216                               non-string argument, which gains higher
  217                               priority.
  218                           */
  219                           continue;
  220                       }
  221                       if (java.io.File.class.equals(args[0])) {
  222                           // Ant Resources/FileProviders override java.io.File
  223                           if (Resource.class.equals(as.type) || FileProvider.class.equals(as.type)) {
  224                               continue;
  225                           }
  226                       }
  227                       /*
  228                           In cases other than those just explicitly covered,
  229                           we just override that with the new one.
  230                           This mechanism does not guarantee any specific order
  231                           in which the methods will be selected: so any code
  232                           that depends on the order in which "set" methods have
  233                           been defined, is not guaranteed to be selected in any
  234                           particular order.
  235                       */
  236                   }
  237                   as = createAttributeSetter(m, args[0], propName);
  238                   if (as != null) {
  239                       attributeTypes.put(propName, args[0]);
  240                       attributeSetters.put(propName, as);
  241                   }
  242               } else if (name.startsWith("create") && !returnType.isArray()
  243                       && !returnType.isPrimitive() && args.length == 0) {
  244   
  245                   String propName = getPropertyName(name, "create");
  246                   // Check if a create of this property is already present
  247                   // add takes preference over create for CB purposes
  248                   if (nestedCreators.get(propName) == null) {
  249                       nestedTypes.put(propName, returnType);
  250                       nestedCreators.put(propName, new CreateNestedCreator(m));
  251                   }
  252               } else if (name.startsWith("addConfigured")
  253                       && java.lang.Void.TYPE.equals(returnType) && args.length == 1
  254                       && !java.lang.String.class.equals(args[0])
  255                       && !args[0].isArray() && !args[0].isPrimitive()) {
  256                   try {
  257                       Constructor constructor = null;
  258                       try {
  259                           constructor = args[0].getConstructor(new Class[] {});
  260                       } catch (NoSuchMethodException ex) {
  261                           constructor = args[0].getConstructor(new Class[] {Project.class});
  262                       }
  263                       String propName = getPropertyName(name, "addConfigured");
  264                       nestedTypes.put(propName, args[0]);
  265                       nestedCreators.put(propName, new AddNestedCreator(m,
  266                           constructor, AddNestedCreator.ADD_CONFIGURED));
  267                   } catch (NoSuchMethodException nse) {
  268                       // ignore
  269                   }
  270               } else if (name.startsWith("add")
  271                       && java.lang.Void.TYPE.equals(returnType) && args.length == 1
  272                       && !java.lang.String.class.equals(args[0])
  273                       && !args[0].isArray() && !args[0].isPrimitive()) {
  274                   try {
  275                       Constructor constructor = null;
  276                       try {
  277                           constructor = args[0].getConstructor(new Class[] {});
  278                       } catch (NoSuchMethodException ex) {
  279                           constructor = args[0].getConstructor(new Class[] {Project.class});
  280                       }
  281                       String propName = getPropertyName(name, "add");
  282                       if (nestedTypes.get(propName) != null) {
  283                           /*
  284                            *  Ignore this method as there is an addConfigured
  285                            *  form of this method that has a higher
  286                            *  priority
  287                            */
  288                           continue;
  289                       }
  290                       nestedTypes.put(propName, args[0]);
  291                       nestedCreators.put(propName, new AddNestedCreator(m,
  292                               constructor, AddNestedCreator.ADD));
  293                   } catch (NoSuchMethodException nse) {
  294                       // ignore
  295                   }
  296               }
  297           }
  298           addText = addTextMethod;
  299       }
  300   
  301       /**
  302        * Certain set methods are part of the Ant core interface to tasks and
  303        * therefore not to be considered for introspection
  304        *
  305        * @param name the name of the set method
  306        * @param type the type of the set method's parameter
  307        * @return true if the given set method is to be hidden.
  308        */
  309       private boolean isHiddenSetMethod(String name, Class type) {
  310           if ("setLocation".equals(name) && org.apache.tools.ant.Location.class.equals(type)) {
  311               return true;
  312           }
  313           if ("setTaskType".equals(name) && java.lang.String.class.equals(type)) {
  314               return true;
  315           }
  316           return false;
  317       }
  318   
  319       /**
  320        * Returns a helper for the given class, either from the cache
  321        * or by creating a new instance.
  322        *
  323        * @param c The class for which a helper is required.
  324        *          Must not be <code>null</code>.
  325        *
  326        * @return a helper for the specified class
  327        */
  328       public static synchronized IntrospectionHelper getHelper(Class c) {
  329           return getHelper(null, c);
  330       }
  331   
  332       /**
  333        * Returns a helper for the given class, either from the cache
  334        * or by creating a new instance.
  335        *
  336        * The method will make sure the helper will be cleaned up at the end of
  337        * the project, and only one instance will be created for each class.
  338        *
  339        * @param p the project instance. Can be null, in which case the helper is not cached.
  340        * @param c The class for which a helper is required.
  341        *          Must not be <code>null</code>.
  342        *
  343        * @return a helper for the specified class
  344        */
  345       public static IntrospectionHelper getHelper(Project p, Class c) {
  346           IntrospectionHelper ih = (IntrospectionHelper) HELPERS.get(c.getName());
  347           // If a helper cannot be found, or if the helper is for another
  348           // classloader, create a new IH
  349           if (ih == null || ih.bean != c) {
  350               ih = new IntrospectionHelper(c);
  351               if (p != null) {
  352                   // #30162: do *not* cache this if there is no project, as we
  353                   // cannot guarantee that the cache will be cleared.
  354                   HELPERS.put(c.getName(), ih);
  355               }
  356           }
  357           return ih;
  358       }
  359   
  360       /**
  361        * Sets the named attribute in the given element, which is part of the
  362        * given project.
  363        *
  364        * @param p The project containing the element. This is used when files
  365        *          need to be resolved. Must not be <code>null</code>.
  366        * @param element The element to set the attribute in. Must not be
  367        *                <code>null</code>.
  368        * @param attributeName The name of the attribute to set. Must not be
  369        *                      <code>null</code>.
  370        * @param value The value to set the attribute to. This may be interpreted
  371        *              or converted to the necessary type if the setter method
  372        *              doesn't accept an object of the supplied type.
  373        *
  374        * @exception BuildException if the introspected class doesn't support
  375        *                           the given attribute, or if the setting
  376        *                           method fails.
  377        */
  378       public void setAttribute(Project p, Object element, String attributeName,
  379               Object value) throws BuildException {
  380           AttributeSetter as = (AttributeSetter) attributeSetters.get(
  381                   attributeName.toLowerCase(Locale.ENGLISH));
  382           if (as == null && value != null) {
  383               if (element instanceof DynamicAttributeNS) {
  384                   DynamicAttributeNS dc = (DynamicAttributeNS) element;
  385                   String uriPlusPrefix = ProjectHelper.extractUriFromComponentName(attributeName);
  386                   String uri = ProjectHelper.extractUriFromComponentName(uriPlusPrefix);
  387                   String localName = ProjectHelper.extractNameFromComponentName(attributeName);
  388                   String qName = "".equals(uri) ? localName : uri + ":" + localName;
  389                   dc.setDynamicAttribute(uri, localName, qName, value.toString());
  390                   return;
  391               }
  392               if (element instanceof DynamicAttribute) {
  393                   DynamicAttribute dc = (DynamicAttribute) element;
  394                   dc.setDynamicAttribute(attributeName.toLowerCase(Locale.ENGLISH), value.toString());
  395                   return;
  396               }
  397               if (attributeName.indexOf(':') >= 0) {
  398                   return; // Ignore attribute from unknown uri's
  399               }
  400               String msg = getElementName(p, element)
  401                       + " doesn't support the \"" + attributeName + "\" attribute.";
  402               throw new UnsupportedAttributeException(msg, attributeName);
  403           }
  404           try {
  405               as.setObject(p, element, value);
  406           } catch (IllegalAccessException ie) {
  407               // impossible as getMethods should only return public methods
  408               throw new BuildException(ie);
  409           } catch (InvocationTargetException ite) {
  410               throw extractBuildException(ite);
  411           }
  412       }
  413   
  414       /**
  415        * Sets the named attribute in the given element, which is part of the
  416        * given project.
  417        *
  418        * @param p The project containing the element. This is used when files
  419        *          need to be resolved. Must not be <code>null</code>.
  420        * @param element The element to set the attribute in. Must not be
  421        *                <code>null</code>.
  422        * @param attributeName The name of the attribute to set. Must not be
  423        *                      <code>null</code>.
  424        * @param value The value to set the attribute to. This may be interpreted
  425        *              or converted to the necessary type if the setter method
  426        *              doesn't just take a string. Must not be <code>null</code>.
  427        *
  428        * @exception BuildException if the introspected class doesn't support
  429        *                           the given attribute, or if the setting
  430        *                           method fails.
  431        */
  432       public void setAttribute(Project p, Object element, String attributeName,
  433                                String value) throws BuildException {
  434           setAttribute(p, element, attributeName, (Object) value);
  435       }
  436   
  437       /**
  438        * Adds PCDATA to an element, using the element's
  439        * <code>void addText(String)</code> method, if it has one. If no
  440        * such method is present, a BuildException is thrown if the
  441        * given text contains non-whitespace.
  442        *
  443        * @param project The project which the element is part of.
  444        *                Must not be <code>null</code>.
  445        * @param element The element to add the text to.
  446        *                Must not be <code>null</code>.
  447        * @param text    The text to add.
  448        *                Must not be <code>null</code>.
  449        *
  450        * @exception BuildException if non-whitespace text is provided and no
  451        *                           method is available to handle it, or if
  452        *                           the handling method fails.
  453        */
  454       public void addText(Project project, Object element, String text)
  455           throws BuildException {
  456           if (addText == null) {
  457               text = text.trim();
  458               // Element doesn't handle text content
  459               if (text.length() == 0) {
  460                   // Only whitespace - ignore
  461                   return;
  462               }
  463               // Not whitespace - fail
  464               throw new BuildException(project.getElementName(element)
  465                       + " doesn't support nested text data (\"" + condenseText(text) + "\").");
  466           }
  467           try {
  468               addText.invoke(element, new Object[] {text});
  469           } catch (IllegalAccessException ie) {
  470               // impossible as getMethods should only return public methods
  471               throw new BuildException(ie);
  472           } catch (InvocationTargetException ite) {
  473               throw extractBuildException(ite);
  474           }
  475       }
  476   
  477       /**
  478        * part of the error message created by {@link #throwNotSupported
  479        * throwNotSupported}.
  480        * @since Ant 1.8.0
  481        */
  482       protected static final String NOT_SUPPORTED_CHILD_PREFIX =
  483           " doesn't support the nested \"";
  484   
  485       /**
  486        * part of the error message created by {@link #throwNotSupported
  487        * throwNotSupported}.
  488        * @since Ant 1.8.0
  489        */
  490       protected static final String NOT_SUPPORTED_CHILD_POSTFIX = "\" element.";
  491   
  492       /**
  493        * Utility method to throw a NotSupported exception
  494        *
  495        * @param project the Project instance.
  496        * @param parent the object which doesn't support a requested element
  497        * @param elementName the name of the Element which is trying to be created.
  498        */
  499       public void throwNotSupported(Project project, Object parent, String elementName) {
  500           String msg = project.getElementName(parent)
  501               + NOT_SUPPORTED_CHILD_PREFIX + elementName
  502               + NOT_SUPPORTED_CHILD_POSTFIX;
  503           throw new UnsupportedElementException(msg, elementName);
  504       }
  505   
  506       /**
  507        * Get the specific NestedCreator for a given project/parent/element combination
  508        * @param project ant project
  509        * @param parentUri URI of the parent.
  510        * @param parent the parent class
  511        * @param elementName element to work with. This can contain
  512        *  a URI,localname tuple of of the form uri:localname
  513        * @param child the bit of XML to work with
  514        * @return a nested creator that can handle the child elements.
  515        * @throws BuildException if the parent does not support child elements of that name
  516        */
  517       private NestedCreator getNestedCreator(
  518           Project project, String parentUri, Object parent,
  519           String elementName, UnknownElement child) throws BuildException {
  520   
  521           String uri = ProjectHelper.extractUriFromComponentName(elementName);
  522           String name = ProjectHelper.extractNameFromComponentName(elementName);
  523   
  524           if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
  525               uri = "";
  526           }
  527           if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
  528               parentUri = "";
  529           }
  530           NestedCreator nc = null;
  531           if (uri.equals(parentUri) || uri.length() == 0) {
  532               nc = (NestedCreator) nestedCreators.get(name.toLowerCase(Locale.ENGLISH));
  533           }
  534           if (nc == null) {
  535               nc = createAddTypeCreator(project, parent, elementName);
  536           }
  537           if (nc == null &&
  538               (parent instanceof DynamicElementNS
  539                || parent instanceof DynamicElement)
  540               ) {
  541               String qName = child == null ? name : child.getQName();
  542               final Object nestedElement =
  543                   createDynamicElement(parent,
  544                                        child == null ? "" : child.getNamespace(),
  545                                        name, qName);
  546               if (nestedElement != null) {
  547                   nc = new NestedCreator(null) {
  548                       Object create(Project project, Object parent, Object ignore) {
  549                           return nestedElement;
  550                       }
  551                   };
  552               }
  553           }
  554           if (nc == null) {
  555               throwNotSupported(project, parent, elementName);
  556           }
  557           return nc;
  558       }
  559   
  560       /**
  561        * Invokes the "correct" createDynamicElement method on parent in
  562        * order to obtain a child element by name.
  563        *
  564        * @since Ant 1.8.0.
  565        */
  566       private Object createDynamicElement(Object parent, String ns,
  567                                           String localName, String qName) {
  568           Object nestedElement = null;
  569           if (parent instanceof DynamicElementNS) {
  570               DynamicElementNS dc = (DynamicElementNS) parent;
  571               nestedElement = dc.createDynamicElement(ns, localName, qName);
  572           }
  573           if (nestedElement == null && parent instanceof DynamicElement) {
  574               DynamicElement dc = (DynamicElement) parent;
  575               nestedElement =
  576                   dc.createDynamicElement(localName.toLowerCase(Locale.ENGLISH));
  577           }
  578           return nestedElement;
  579       }
  580   
  581       /**
  582        * Creates a named nested element. Depending on the results of the
  583        * initial introspection, either a method in the given parent instance
  584        * or a simple no-arg constructor is used to create an instance of the
  585        * specified element type.
  586        *
  587        * @param project Project to which the parent object belongs.
  588        *                Must not be <code>null</code>. If the resulting
  589        *                object is an instance of ProjectComponent, its
  590        *                Project reference is set to this parameter value.
  591        * @param parent  Parent object used to create the instance.
  592        *                Must not be <code>null</code>.
  593        * @param elementName Name of the element to create an instance of.
  594        *                    Must not be <code>null</code>.
  595        *
  596        * @return an instance of the specified element type
  597        * @deprecated since 1.6.x.
  598        *             This is not a namespace aware method.
  599        *
  600        * @exception BuildException if no method is available to create the
  601        *                           element instance, or if the creating method fails.
  602        */
  603       public Object createElement(Project project, Object parent, String elementName)
  604               throws BuildException {
  605           NestedCreator nc = getNestedCreator(project, "", parent, elementName, null);
  606           try {
  607               Object nestedElement = nc.create(project, parent, null);
  608               if (project != null) {
  609                   project.setProjectReference(nestedElement);
  610               }
  611               return nestedElement;
  612           } catch (IllegalAccessException ie) {
  613               // impossible as getMethods should only return public methods
  614               throw new BuildException(ie);
  615           } catch (InstantiationException ine) {
  616               // impossible as getMethods should only return public methods
  617               throw new BuildException(ine);
  618           } catch (InvocationTargetException ite) {
  619               throw extractBuildException(ite);
  620           }
  621       }
  622   
  623       /**
  624        * returns an object that creates and stores an object
  625        * for an element of a parent.
  626        *
  627        * @param project      Project to which the parent object belongs.
  628        * @param parentUri    The namespace uri of the parent object.
  629        * @param parent       Parent object used to create the creator object to
  630        *                     create and store and instance of a subelement.
  631        * @param elementName  Name of the element to create an instance of.
  632        * @param ue           The unknown element associated with the element.
  633        * @return a creator object to create and store the element instance.
  634        */
  635       public Creator getElementCreator(
  636           Project project, String parentUri, Object parent, String elementName, UnknownElement ue) {
  637           NestedCreator nc = getNestedCreator(project, parentUri, parent, elementName, ue);
  638           return new Creator(project, parent, nc);
  639       }
  640   
  641       /**
  642        * Indicates whether the introspected class is a dynamic one,
  643        * supporting arbitrary nested elements and/or attributes.
  644        *
  645        * @return <code>true<code> if the introspected class is dynamic;
  646        *         <code>false<code> otherwise.
  647        * @since Ant 1.6.3
  648        *
  649        * @see DynamicElement
  650        * @see DynamicElementNS
  651        */
  652       public boolean isDynamic() {
  653           return DynamicElement.class.isAssignableFrom(bean)
  654                   || DynamicElementNS.class.isAssignableFrom(bean);
  655       }
  656   
  657       /**
  658        * Indicates whether the introspected class is a task container,
  659        * supporting arbitrary nested tasks/types.
  660        *
  661        * @return <code>true<code> if the introspected class is a container;
  662        *         <code>false<code> otherwise.
  663        * @since Ant 1.6.3
  664        *
  665        * @see TaskContainer
  666        */
  667       public boolean isContainer() {
  668           return TaskContainer.class.isAssignableFrom(bean);
  669       }
  670   
  671       /**
  672        * Indicates if this element supports a nested element of the
  673        * given name.
  674        *
  675        * @param elementName the name of the nested element being checked
  676        *
  677        * @return true if the given nested element is supported
  678        */
  679       public boolean supportsNestedElement(String elementName) {
  680           return supportsNestedElement("", elementName);
  681       }
  682   
  683       /**
  684        * Indicate if this element supports a nested element of the
  685        * given name.
  686        *
  687        * <p>Note that this method will always return true if the
  688        * introspected class is {@link #isDynamic dynamic} or contains a
  689        * method named "add" with void return type and a single argument.
  690        * To ge a more thorough answer, use the four-arg version of this
  691        * method instead.</p>
  692        *
  693        * @param parentUri   the uri of the parent
  694        * @param elementName the name of the nested element being checked
  695        *
  696        * @return true if the given nested element is supported
  697        */
  698       public boolean supportsNestedElement(String parentUri, String elementName) {
  699           if (isDynamic() || addTypeMethods.size() > 0) {
  700               return true;
  701           }
  702           return supportsReflectElement(parentUri, elementName);
  703       }
  704   
  705       /**
  706        * Indicate if this element supports a nested element of the
  707        * given name.
  708        *
  709        * <p>Note that this method will always return true if the
  710        * introspected class is {@link #isDynamic dynamic}, so be
  711        * prepared to catch an exception about unsupported children when
  712        * calling {@link #getElementCreator getElementCreator}.</p>
  713        *
  714        * @param parentUri   the uri of the parent
  715        * @param elementName the name of the nested element being checked
  716        * @param project currently executing project instance
  717        * @param parent the parent element
  718        *
  719        * @return true if the given nested element is supported
  720        * @since Ant 1.8.0.
  721        */
  722       public boolean supportsNestedElement(String parentUri, String elementName,
  723                                            Project project, Object parent) {
  724           if (addTypeMethods.size() > 0
  725               && createAddTypeCreator(project, parent, elementName) != null) {
  726               return true;
  727           }
  728           return isDynamic() || supportsReflectElement(parentUri, elementName);
  729       }
  730   
  731       /**
  732        * Check if this element supports a nested element from reflection.
  733        *
  734        * @param parentUri   the uri of the parent
  735        * @param elementName the name of the nested element being checked
  736        *
  737        * @return true if the given nested element is supported
  738        * @since Ant 1.8.0
  739        */
  740       public boolean supportsReflectElement(
  741           String parentUri, String elementName) {
  742           String name = ProjectHelper.extractNameFromComponentName(elementName);
  743           if (!nestedCreators.containsKey(name.toLowerCase(Locale.ENGLISH))) {
  744               return false;
  745           }
  746           String uri = ProjectHelper.extractUriFromComponentName(elementName);
  747           if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
  748               uri = "";
  749           }
  750           if ("".equals(uri)) {
  751               return true;
  752           }
  753           if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
  754               parentUri = "";
  755           }
  756           return uri.equals(parentUri);
  757       }
  758   
  759       /**
  760        * Stores a named nested element using a storage method determined
  761        * by the initial introspection. If no appropriate storage method
  762        * is available, this method returns immediately.
  763        *
  764        * @param project Ignored in this implementation.
  765        *                May be <code>null</code>.
  766        *
  767        * @param parent  Parent instance to store the child in.
  768        *                Must not be <code>null</code>.
  769        *
  770        * @param child   Child instance to store in the parent.
  771        *                Should not be <code>null</code>.
  772        *
  773        * @param elementName  Name of the child element to store.
  774        *                     May be <code>null</code>, in which case
  775        *                     this method returns immediately.
  776        *
  777        * @exception BuildException if the storage method fails.
  778        */
  779       public void storeElement(Project project, Object parent, Object child,
  780           String elementName) throws BuildException {
  781           if (elementName == null) {
  782               return;
  783           }
  784           NestedCreator ns = (NestedCreator) nestedCreators.get(elementName.toLowerCase(Locale.ENGLISH));
  785           if (ns == null) {
  786               return;
  787           }
  788           try {
  789               ns.store(parent, child);
  790           } catch (IllegalAccessException ie) {
  791               // impossible as getMethods should only return public methods
  792               throw new BuildException(ie);
  793           } catch (InstantiationException ine) {
  794               // impossible as getMethods should only return public methods
  795               throw new BuildException(ine);
  796           } catch (InvocationTargetException ite) {
  797               throw extractBuildException(ite);
  798           }
  799       }
  800   
  801       /**
  802        * Helper method to extract the inner fault from an {@link InvocationTargetException}, and turn
  803        * it into a BuildException. If it is already a BuildException, it is type cast and returned; if
  804        * not a new BuildException is created containing the child as nested text.
  805        * @param ite
  806        * @return the nested exception
  807        */
  808       private static BuildException extractBuildException(InvocationTargetException ite) {
  809           Throwable t = ite.getTargetException();
  810           if (t instanceof BuildException) {
  811               return (BuildException) t;
  812           }
  813           return new BuildException(t);
  814       }
  815   
  816       /**
  817        * Returns the type of a named nested element.
  818        *
  819        * @param elementName The name of the element to find the type of.
  820        *                    Must not be <code>null</code>.
  821        *
  822        * @return the type of the nested element with the specified name.
  823        *         This will never be <code>null</code>.
  824        *
  825        * @exception BuildException if the introspected class does not
  826        *                           support the named nested element.
  827        */
  828       public Class getElementType(String elementName) throws BuildException {
  829           Class nt = (Class) nestedTypes.get(elementName);
  830           if (nt == null) {
  831               throw new UnsupportedElementException("Class "
  832                       + bean.getName() + " doesn't support the nested \""
  833                       + elementName + "\" element.", elementName);
  834           }
  835           return nt;
  836       }
  837   
  838       /**
  839        * Returns the type of a named attribute.
  840        *
  841        * @param attributeName The name of the attribute to find the type of.
  842        *                      Must not be <code>null</code>.
  843        *
  844        * @return the type of the attribute with the specified name.
  845        *         This will never be <code>null</code>.
  846        *
  847        * @exception BuildException if the introspected class does not
  848        *                           support the named attribute.
  849        */
  850       public Class getAttributeType(String attributeName) throws BuildException {
  851           Class at = (Class) attributeTypes.get(attributeName);
  852           if (at == null) {
  853               throw new UnsupportedAttributeException("Class "
  854                       + bean.getName() + " doesn't support the \""
  855                       + attributeName + "\" attribute.", attributeName);
  856           }
  857           return at;
  858       }
  859   
  860       /**
  861        * Returns the addText method when the introspected
  862        * class supports nested text.
  863        *
  864        * @return the method on this introspected class that adds nested text.
  865        *         Cannot be <code>null</code>.
  866        * @throws BuildException if the introspected class does not
  867        *         support the nested text.
  868        * @since Ant 1.6.3
  869        */
  870       public Method getAddTextMethod() throws BuildException {
  871           if (!supportsCharacters()) {
  872               throw new BuildException("Class " + bean.getName()
  873                       + " doesn't support nested text data.");
  874           }
  875           return addText;
  876       }
  877   
  878       /**
  879        * Returns the adder or creator method of a named nested element.
  880        *
  881        * @param  elementName The name of the attribute to find the setter
  882        *         method of. Must not be <code>null</code>.
  883        * @return the method on this introspected class that adds or creates this
  884        *         nested element. Can be <code>null</code> when the introspected
  885        *         class is a dynamic configurator!
  886        * @throws BuildException if the introspected class does not
  887        *         support the named nested element.
  888        * @since Ant 1.6.3
  889        */
  890       public Method getElementMethod(String elementName) throws BuildException {
  891           Object creator = nestedCreators.get(elementName);
  892           if (creator == null) {
  893               throw new UnsupportedElementException("Class "
  894                       + bean.getName() + " doesn't support the nested \""
  895                       + elementName + "\" element.", elementName);
  896           }
  897           return ((NestedCreator) creator).method;
  898       }
  899   
  900       /**
  901        * Returns the setter method of a named attribute.
  902        *
  903        * @param  attributeName The name of the attribute to find the setter
  904        *         method of. Must not be <code>null</code>.
  905        * @return the method on this introspected class that sets this attribute.
  906        *         This will never be <code>null</code>.
  907        * @throws BuildException if the introspected class does not
  908        *         support the named attribute.
  909        * @since Ant 1.6.3
  910        */
  911       public Method getAttributeMethod(String attributeName) throws BuildException {
  912           Object setter = attributeSetters.get(attributeName);
  913           if (setter == null) {
  914               throw new UnsupportedAttributeException("Class "
  915                       + bean.getName() + " doesn't support the \""
  916                       + attributeName + "\" attribute.", attributeName);
  917           }
  918           return ((AttributeSetter) setter).method;
  919       }
  920   
  921       /**
  922        * Returns whether or not the introspected class supports PCDATA.
  923        *
  924        * @return whether or not the introspected class supports PCDATA.
  925        */
  926       public boolean supportsCharacters() {
  927           return addText != null;
  928       }
  929   
  930       /**
  931        * Returns an enumeration of the names of the attributes supported by the introspected class.
  932        *
  933        * @return an enumeration of the names of the attributes supported by the introspected class.
  934        * @see #getAttributeMap
  935        */
  936       public Enumeration getAttributes() {
  937           return attributeSetters.keys();
  938       }
  939   
  940       /**
  941        * Returns a read-only map of attributes supported by the introspected class.
  942        *
  943        * @return an attribute name to attribute <code>Class</code>
  944        *         unmodifiable map. Can be empty, but never <code>null</code>.
  945        * @since Ant 1.6.3
  946        */
  947       public Map getAttributeMap() {
  948           return attributeTypes.isEmpty()
  949               ? Collections.EMPTY_MAP : Collections.unmodifiableMap(attributeTypes);
  950       }
  951   
  952       /**
  953        * Returns an enumeration of the names of the nested elements supported
  954        * by the introspected class.
  955        *
  956        * @return an enumeration of the names of the nested elements supported
  957        *         by the introspected class.
  958        * @see #getNestedElementMap
  959        */
  960       public Enumeration getNestedElements() {
  961           return nestedTypes.keys();
  962       }
  963   
  964       /**
  965        * Returns a read-only map of nested elements supported
  966        * by the introspected class.
  967        *
  968        * @return a nested-element name to nested-element <code>Class</code>
  969        *         unmodifiable map. Can be empty, but never <code>null</code>.
  970        * @since Ant 1.6.3
  971        */
  972       public Map getNestedElementMap() {
  973           return nestedTypes.isEmpty()
  974               ? Collections.EMPTY_MAP : Collections.unmodifiableMap(nestedTypes);
  975       }
  976   
  977       /**
  978        * Returns a read-only list of extension points supported
  979        * by the introspected class.
  980        * <p>
  981        * A task/type or nested element with void methods named <code>add()<code>
  982        * or <code>addConfigured()</code>, taking a single class or interface
  983        * argument, supports extensions point. This method returns the list of
  984        * all these <em>void add[Configured](type)</em> methods.
  985        *
  986        * @return a list of void, single argument add() or addConfigured()
  987        *         <code>Method<code>s of all supported extension points.
  988        *         These methods are sorted such that if the argument type of a
  989        *         method derives from another type also an argument of a method
  990        *         of this list, the method with the most derived argument will
  991        *         always appear first. Can be empty, but never <code>null</code>.
  992        * @since Ant 1.6.3
  993        */
  994       public List getExtensionPoints() {
  995           return addTypeMethods.isEmpty()
  996                   ? Collections.EMPTY_LIST : Collections.unmodifiableList(addTypeMethods);
  997       }
  998   
  999       /**
 1000        * Creates an implementation of AttributeSetter for the given
 1001        * attribute type. Conversions (where necessary) are automatically
 1002        * made for the following types:
 1003        * <ul>
 1004        * <li>String (left as it is)
 1005        * <li>Character/char (first character is used)
 1006        * <li>Boolean/boolean
 1007        * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used)
 1008        * <li>Class (Class.forName is used)
 1009        * <li>File (resolved relative to the appropriate project)
 1010        * <li>Path (resolve relative to the appropriate project)
 1011        * <li>Resource (resolved as a FileResource relative to the appropriate project)
 1012        * <li>FileProvider (resolved as a FileResource relative to the appropriate project)
 1013        * <li>EnumeratedAttribute (uses its own
 1014        * {@link EnumeratedAttribute#setValue(String) setValue} method)
 1015        * <li>Other primitive types (wrapper classes are used with constructors
 1016        * taking String)
 1017        * </ul>
 1018        *
 1019        * If none of the above covers the given parameters, a constructor for the
 1020        * appropriate class taking a String parameter is used if it is available.
 1021        *
 1022        * @param m The method to invoke on the bean when the setter is invoked.
 1023        *          Must not be <code>null</code>.
 1024        * @param arg The type of the single argument of the bean's method.
 1025        *            Must not be <code>null</code>.
 1026        * @param attrName the name of the attribute for which the setter is being
 1027        *                 created.
 1028        *
 1029        * @return an appropriate AttributeSetter instance, or <code>null</code>
 1030        *         if no appropriate conversion is available.
 1031        */
 1032       private AttributeSetter createAttributeSetter(final Method m,
 1033                                                     Class arg,
 1034                                                     final String attrName) {
 1035           // use wrappers for primitive classes, e.g. int and
 1036           // Integer are treated identically
 1037           final Class reflectedArg = PRIMITIVE_TYPE_MAP.containsKey(arg)
 1038               ? (Class) PRIMITIVE_TYPE_MAP.get(arg) : arg;
 1039   
 1040           // Object.class - it gets handled differently by AttributeSetter
 1041           if (java.lang.Object.class == reflectedArg) {
 1042               return new AttributeSetter(m, arg) {
 1043                   public void set(Project p, Object parent, String value)
 1044                           throws InvocationTargetException,
 1045                       IllegalAccessException {
 1046                       throw new BuildException(
 1047                           "Internal ant problem - this should not get called");
 1048                   }
 1049               };
 1050           }
 1051           // simplest case - setAttribute expects String
 1052           if (java.lang.String.class.equals(reflectedArg)) {
 1053               return new AttributeSetter(m, arg) {
 1054                   public void set(Project p, Object parent, String value)
 1055                           throws InvocationTargetException, IllegalAccessException {
 1056                       m.invoke(parent, (Object[]) new String[] {value});
 1057                   }
 1058               };
 1059           }
 1060           // char and Character get special treatment - take the first character
 1061           if (java.lang.Character.class.equals(reflectedArg)) {
 1062               return new AttributeSetter(m, arg) {
 1063                   public void set(Project p, Object parent, String value)
 1064                           throws InvocationTargetException, IllegalAccessException {
 1065                       if (value.length() == 0) {
 1066                           throw new BuildException("The value \"\" is not a "
 1067                                   + "legal value for attribute \"" + attrName + "\"");
 1068                       }
 1069                       m.invoke(parent, (Object[]) new Character[] {new Character(value.charAt(0))});
 1070                   }
 1071               };
 1072           }
 1073           // boolean and Boolean get special treatment because we have a nice method in Project
 1074           if (java.lang.Boolean.class.equals(reflectedArg)) {
 1075               return new AttributeSetter(m, arg) {
 1076                   public void set(Project p, Object parent, String value)
 1077                           throws InvocationTargetException, IllegalAccessException {
 1078                       m.invoke(parent, (Object[]) new Boolean[] {
 1079                               Project.toBoolean(value) ? Boolean.TRUE : Boolean.FALSE });
 1080                   }
 1081               };
 1082           }
 1083           // Class doesn't have a String constructor but a decent factory method
 1084           if (java.lang.Class.class.equals(reflectedArg)) {
 1085               return new AttributeSetter(m, arg) {
 1086                   public void set(Project p, Object parent, String value)
 1087                           throws InvocationTargetException, IllegalAccessException, BuildException {
 1088                       try {
 1089                           m.invoke(parent, new Object[] {Class.forName(value)});
 1090                       } catch (ClassNotFoundException ce) {
 1091                           throw new BuildException(ce);
 1092                       }
 1093                   }
 1094               };
 1095           }
 1096           // resolve relative paths through Project
 1097           if (java.io.File.class.equals(reflectedArg)) {
 1098               return new AttributeSetter(m, arg) {
 1099                   public void set(Project p, Object parent, String value)
 1100                           throws InvocationTargetException, IllegalAccessException {
 1101                       m.invoke(parent, new Object[] {p.resolveFile(value)});
 1102                   }
 1103               };
 1104           }
 1105           // resolve Resources/FileProviders as FileResources relative to Project:
 1106           if (Resource.class.equals(reflectedArg) || FileProvider.class.equals(reflectedArg)) {
 1107               return new AttributeSetter(m, arg) {
 1108                   void set(Project p, Object parent, String value) throws InvocationTargetException,
 1109                           IllegalAccessException, BuildException {
 1110                       m.invoke(parent, new Object[] { new FileResource(p, p.resolveFile(value)) });
 1111                   };
 1112               };
 1113           }
 1114           // EnumeratedAttributes have their own helper class
 1115           if (EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
 1116               return new AttributeSetter(m, arg) {
 1117                   public void set(Project p, Object parent, String value)
 1118                           throws InvocationTargetException, IllegalAccessException, BuildException {
 1119                       try {
 1120                           EnumeratedAttribute ea = (EnumeratedAttribute) reflectedArg.newInstance();
 1121                           ea.setValue(value);
 1122                           m.invoke(parent, new Object[] {ea});
 1123                       } catch (InstantiationException ie) {
 1124                           throw new BuildException(ie);
 1125                       }
 1126                   }
 1127               };
 1128           }
 1129   
 1130           AttributeSetter setter = getEnumSetter(reflectedArg, m, arg);
 1131           if (setter != null) {
 1132               return setter;
 1133           }
 1134   
 1135           if (java.lang.Long.class.equals(reflectedArg)) {
 1136               return new AttributeSetter(m, arg) {
 1137                   public void set(Project p, Object parent, String value)
 1138                           throws InvocationTargetException, IllegalAccessException, BuildException {
 1139                       try {
 1140                           m.invoke(parent, new Object[] {
 1141                                   new Long(StringUtils.parseHumanSizes(value)) });
 1142                       } catch (NumberFormatException e) {
 1143                           throw new BuildException("Can't assign non-numeric"
 1144                                                    + " value '" + value + "' to"
 1145                                                    + " attribute " + attrName);
 1146                       } catch (InvocationTargetException e) {
 1147                           throw e;
 1148                       } catch (IllegalAccessException e) {
 1149                           throw e;
 1150                       } catch (Exception e) {
 1151                           throw new BuildException(e);
 1152                       }
 1153                   }
 1154               };
 1155           }
 1156           // worst case. look for a public String constructor and use it
 1157           // also supports new Whatever(Project, String) as for Path or Reference
 1158           // This is used (deliberately) for all primitives/wrappers other than
 1159           // char, boolean, and long.
 1160           boolean includeProject;
 1161           Constructor c;
 1162           try {
 1163               // First try with Project.
 1164               c = reflectedArg.getConstructor(new Class[] {Project.class, String.class});
 1165               includeProject = true;
 1166           } catch (NoSuchMethodException nme) {
 1167               // OK, try without.
 1168               try {
 1169                   c = reflectedArg.getConstructor(new Class[] {String.class});
 1170                   includeProject = false;
 1171               } catch (NoSuchMethodException nme2) {
 1172                   // Well, no matching constructor.
 1173                   return null;
 1174               }
 1175           }
 1176           final boolean finalIncludeProject = includeProject;
 1177           final Constructor finalConstructor = c;
 1178   
 1179           return new AttributeSetter(m, arg) {
 1180               public void set(Project p, Object parent, String value)
 1181                       throws InvocationTargetException, IllegalAccessException, BuildException {
 1182                   try {
 1183                       Object[] args = finalIncludeProject
 1184                               ? new Object[] {p, value} : new Object[] {value};
 1185   
 1186                       Object attribute = finalConstructor.newInstance(args);
 1187                       if (p != null) {
 1188                           p.setProjectReference(attribute);
 1189                       }
 1190                       m.invoke(parent, new Object[] {attribute});
 1191                   } catch (InvocationTargetException e) {
 1192                       Throwable cause = e.getCause();
 1193                       if (cause instanceof IllegalArgumentException) {
 1194                           throw new BuildException("Can't assign value '" + value
 1195                                                    + "' to attribute " + attrName
 1196                                                    + ", reason: "
 1197                                                    + cause.getClass()
 1198                                                    + " with message '"
 1199                                                    + cause.getMessage() + "'");
 1200                       }
 1201                       throw e;
 1202                   } catch (InstantiationException ie) {
 1203                       throw new BuildException(ie);
 1204                   }
 1205               }
 1206           };
 1207       }
 1208   
 1209       private AttributeSetter getEnumSetter(
 1210           final Class reflectedArg, final Method m, Class arg) {
 1211           Class enumClass = null;
 1212           try {
 1213               enumClass = Class.forName("java.lang.Enum");
 1214           } catch (ClassNotFoundException e) {
 1215               //ignore
 1216           }
 1217           if (enumClass != null && enumClass.isAssignableFrom(reflectedArg)) {
 1218               return new AttributeSetter(m, arg) {
 1219                   public void set(Project p, Object parent, String value)
 1220                       throws InvocationTargetException, IllegalAccessException,
 1221                       BuildException {
 1222                       try {
 1223                           m.invoke(
 1224                               parent, new Object[] {
 1225                                   reflectedArg.getMethod(
 1226                                       "valueOf", new Class[] {String.class}).
 1227                                   invoke(null, new Object[] {value})});
 1228                       } catch (InvocationTargetException x) {
 1229                           //there is specific logic here for the value
 1230                           // being out of the allowed set of enumerations.
 1231                           if (x.getTargetException() instanceof IllegalArgumentException) {
 1232                               throw new BuildException(
 1233                                   "'" + value + "' is not a permitted value for "
 1234                                   + reflectedArg.getName());
 1235                           }
 1236                           //only if the exception is not an IllegalArgument do we
 1237                           // request the
 1238                           //BuildException via extractBuildException():
 1239                           throw extractBuildException(x);
 1240                       } catch (Exception x) {
 1241                           //any other failure of invoke() to work.
 1242                           throw new BuildException(x);
 1243                       }
 1244                   }
 1245               };
 1246           }
 1247           return null;
 1248       }
 1249   
 1250       /**
 1251        * Returns a description of the type of the given element in
 1252        * relation to a given project. This is used for logging purposes
 1253        * when the element is asked to cope with some data it has no way of handling.
 1254        *
 1255        * @param project The project the element is defined in. Must not be <code>null</code>.
 1256        *
 1257        * @param element The element to describe. Must not be <code>null</code>.
 1258        *
 1259        * @return a description of the element type
 1260        */
 1261       private String getElementName(Project project, Object element) {
 1262           return project.getElementName(element);
 1263       }
 1264   
 1265       /**
 1266        * Extracts the name of a property from a method name by subtracting
 1267        * a given prefix and converting into lower case. It is up to calling
 1268        * code to make sure the method name does actually begin with the
 1269        * specified prefix - no checking is done in this method.
 1270        *
 1271        * @param methodName The name of the method in question. Must not be <code>null</code>.
 1272        * @param prefix     The prefix to remove. Must not be <code>null</code>.
 1273        *
 1274        * @return the lower-cased method name with the prefix removed.
 1275        */
 1276       private static String getPropertyName(String methodName, String prefix) {
 1277           return methodName.substring(prefix.length()).toLowerCase(Locale.ENGLISH);
 1278       }
 1279   
 1280       /**
 1281        * creator - allows use of create/store external
 1282        * to IntrospectionHelper.
 1283        * The class is final as it has a private constructor.
 1284        */
 1285       public static final class Creator {
 1286           private NestedCreator nestedCreator;
 1287           private Object parent;
 1288           private Project project;
 1289           private Object nestedObject;
 1290           private String polyType;
 1291   
 1292           /**
 1293            * Creates a new Creator instance.
 1294            * This object is given to the UnknownElement to create
 1295            * objects for sub-elements. UnknownElement calls
 1296            * create to create an object, the object then gets
 1297            * configured and then UnknownElement calls store.
 1298            * SetPolyType may be used to override the type used
 1299            * to create the object with. SetPolyType gets called before create.
 1300            *
 1301            * @param project the current project
 1302            * @param parent  the parent object to create the object in
 1303            * @param nestedCreator the nested creator object to use
 1304            */
 1305           private Creator(Project project, Object parent, NestedCreator nestedCreator) {
 1306               this.project = project;
 1307               this.parent = parent;
 1308               this.nestedCreator = nestedCreator;
 1309           }
 1310   
 1311           /**
 1312            * Used to override the class used to create the object.
 1313            *
 1314            * @param polyType a ant component type name
 1315            */
 1316           public void setPolyType(String polyType) {
 1317               this.polyType = polyType;
 1318           }
 1319   
 1320           /**
 1321            * Create an object using this creator, which is determined by introspection.
 1322            *
 1323            * @return the created object
 1324            */
 1325           public Object create() {
 1326               if (polyType != null) {
 1327                   if (!nestedCreator.isPolyMorphic()) {
 1328                       throw new BuildException(
 1329                               "Not allowed to use the polymorphic form for this element");
 1330                   }
 1331                   ComponentHelper helper = ComponentHelper.getComponentHelper(project);
 1332                   nestedObject = helper.createComponent(polyType);
 1333                   if (nestedObject == null) {
 1334                       throw new BuildException("Unable to create object of type " + polyType);
 1335                   }
 1336               }
 1337               try {
 1338                   nestedObject = nestedCreator.create(project, parent, nestedObject);
 1339                   if (project != null) {
 1340                       project.setProjectReference(nestedObject);
 1341                   }
 1342                   return nestedObject;
 1343               } catch (IllegalAccessException ex) {
 1344                   throw new BuildException(ex);
 1345               } catch (InstantiationException ex) {
 1346                   throw new BuildException(ex);
 1347               } catch (IllegalArgumentException ex) {
 1348                   if (polyType == null) {
 1349                       throw ex;
 1350                   }
 1351                   throw new BuildException("Invalid type used " + polyType);
 1352               } catch (InvocationTargetException ex) {
 1353                   throw extractBuildException(ex);
 1354               }
 1355           }
 1356   
 1357           /**
 1358            * @return the real object (used currently only for presetdef).
 1359            */
 1360           public Object getRealObject() {
 1361               return nestedCreator.getRealObject();
 1362           }
 1363   
 1364           /**
 1365            * Stores the nested element object using a storage method determined by introspection.
 1366            *
 1367            */
 1368           public void store() {
 1369               try {
 1370                   nestedCreator.store(parent, nestedObject);
 1371               } catch (IllegalAccessException ex) {
 1372                   throw new BuildException(ex);
 1373               } catch (InstantiationException ex) {
 1374                   throw new BuildException(ex);
 1375               } catch (IllegalArgumentException ex) {
 1376                   if (polyType == null) {
 1377                       throw ex;
 1378                   }
 1379                   throw new BuildException("Invalid type used " + polyType);
 1380               } catch (InvocationTargetException ex) {
 1381                   throw extractBuildException(ex);
 1382               }
 1383           }
 1384       }
 1385   
 1386       /**
 1387        * Internal interface used to create nested elements. Not documented
 1388        * in detail for reasons of source code readability.
 1389        */
 1390       private abstract static class NestedCreator {
 1391           private Method method; // the method called to add/create the nested element
 1392   
 1393           protected NestedCreator(Method m) {
 1394               method = m;
 1395           }
 1396           Method getMethod() {
 1397               return method;
 1398           }
 1399           boolean isPolyMorphic() {
 1400               return false;
 1401           }
 1402           Object getRealObject() {
 1403               return null;
 1404           }
 1405           abstract Object create(Project project, Object parent, Object child)
 1406                   throws InvocationTargetException, IllegalAccessException, InstantiationException;
 1407   
 1408           void store(Object parent, Object child)
 1409                    throws InvocationTargetException, IllegalAccessException, InstantiationException {
 1410               // DO NOTHING
 1411           }
 1412       }
 1413   
 1414       private static class CreateNestedCreator extends NestedCreator {
 1415           CreateNestedCreator(Method m) {
 1416               super(m);
 1417           }
 1418   
 1419           Object create(Project project, Object parent, Object ignore)
 1420                   throws InvocationTargetException, IllegalAccessException {
 1421               return getMethod().invoke(parent, new Object[] {});
 1422           }
 1423       }
 1424   
 1425       /** Version to use for addXXX and addConfiguredXXX */
 1426       private static class AddNestedCreator extends NestedCreator {
 1427   
 1428           static final int ADD = 1;
 1429           static final int ADD_CONFIGURED = 2;
 1430   
 1431           private Constructor constructor;
 1432           private int behavior; // ADD or ADD_CONFIGURED
 1433   
 1434           AddNestedCreator(Method m, Constructor c, int behavior) {
 1435               super(m);
 1436               this.constructor = c;
 1437               this.behavior = behavior;
 1438           }
 1439   
 1440           boolean isPolyMorphic() {
 1441               return true;
 1442           }
 1443   
 1444           Object create(Project project, Object parent, Object child)
 1445                   throws InvocationTargetException, IllegalAccessException, InstantiationException {
 1446               if (child == null) {
 1447                   child = constructor.newInstance(
 1448                           constructor.getParameterTypes().length == 0
 1449                                   ? new Object[] {} : new Object[] {project});
 1450               }
 1451               if (child instanceof PreSetDef.PreSetDefinition) {
 1452                   child = ((PreSetDef.PreSetDefinition) child).createObject(project);
 1453               }
 1454               if (behavior == ADD) {
 1455                   istore(parent, child);
 1456               }
 1457               return child;
 1458           }
 1459   
 1460           void store(Object parent, Object child)
 1461                   throws InvocationTargetException, IllegalAccessException, InstantiationException {
 1462               if (behavior == ADD_CONFIGURED) {
 1463                   istore(parent, child);
 1464               }
 1465           }
 1466   
 1467           private void istore(Object parent, Object child)
 1468                   throws InvocationTargetException, IllegalAccessException, InstantiationException {
 1469               getMethod().invoke(parent, new Object[] {child});
 1470           }
 1471       }
 1472   
 1473       /**
 1474        * Internal interface used to setting element attributes. Not documented
 1475        * in detail for reasons of source code readability.
 1476        */
 1477       private abstract static class AttributeSetter {
 1478           private Method method; // the method called to set the attribute
 1479           private Class type;
 1480           protected AttributeSetter(Method m, Class type) {
 1481               method = m;
 1482               this.type = type;
 1483           }
 1484           void setObject(Project p, Object parent, Object value)
 1485                   throws InvocationTargetException, IllegalAccessException, BuildException {
 1486               if (type != null) {
 1487                   Class useType = type;
 1488                   if (type.isPrimitive()) {
 1489                       if (value == null) {
 1490                           throw new BuildException(
 1491                               "Attempt to set primitive "
 1492                               + getPropertyName(method.getName(), "set")
 1493                               + " to null on " + parent);
 1494                       }
 1495                       useType = (Class) PRIMITIVE_TYPE_MAP.get(type);
 1496                   }
 1497                   if (value == null || useType.isInstance(value)) {
 1498                       method.invoke(parent, new Object[] {value});
 1499                       return;
 1500                   }
 1501               }
 1502               set(p, parent, value.toString());
 1503           }
 1504           abstract void set(Project p, Object parent, String value)
 1505                   throws InvocationTargetException, IllegalAccessException, BuildException;
 1506       }
 1507   
 1508       /**
 1509        * Clears the static cache of on build finished.
 1510        */
 1511       public static void clearCache() {
 1512           HELPERS.clear();
 1513       }
 1514   
 1515       /**
 1516        * Create a NestedCreator for the given element.
 1517        * @param project owning project
 1518        * @param parent Parent object used to create the instance.
 1519        * @param elementName name of the element
 1520        * @return a nested creator, or null if there is no component of the given name, or it
 1521        *        has no matching add type methods
 1522        * @throws BuildException
 1523        */
 1524       private NestedCreator createAddTypeCreator(
 1525               Project project, Object parent, String elementName) throws BuildException {
 1526           if (addTypeMethods.size() == 0) {
 1527               return null;
 1528           }
 1529           ComponentHelper helper = ComponentHelper.getComponentHelper(project);
 1530   
 1531           MethodAndObject restricted =  createRestricted(
 1532               helper, elementName, addTypeMethods);
 1533           MethodAndObject topLevel = createTopLevel(
 1534               helper, elementName, addTypeMethods);
 1535   
 1536           if (restricted == null && topLevel == null) {
 1537               return null;
 1538           }
 1539   
 1540           if (restricted != null && topLevel != null) {
 1541               throw new BuildException(
 1542                   "ambiguous: type and component definitions for "
 1543                   + elementName);
 1544           }
 1545   
 1546           MethodAndObject methodAndObject
 1547               = restricted != null ? restricted : topLevel;
 1548   
 1549           Object rObject = methodAndObject.object;
 1550           if (methodAndObject.object instanceof PreSetDef.PreSetDefinition) {
 1551               rObject = ((PreSetDef.PreSetDefinition) methodAndObject.object)
 1552                   .createObject(project);
 1553           }
 1554           final Object nestedObject = methodAndObject.object;
 1555           final Object realObject = rObject;
 1556   
 1557           return new NestedCreator(methodAndObject.method) {
 1558               Object create(Project project, Object parent, Object ignore)
 1559                       throws InvocationTargetException, IllegalAccessException {
 1560                   if (!getMethod().getName().endsWith("Configured")) {
 1561                       getMethod().invoke(parent, new Object[] {realObject});
 1562                   }
 1563                   return nestedObject;
 1564               }
 1565   
 1566               Object getRealObject() {
 1567                   return realObject;
 1568               }
 1569   
 1570               void store(Object parent, Object child) throws InvocationTargetException,
 1571                       IllegalAccessException, InstantiationException {
 1572                   if (getMethod().getName().endsWith("Configured")) {
 1573                       getMethod().invoke(parent, new Object[] {realObject});
 1574                   }
 1575               }
 1576           };
 1577       }
 1578   
 1579       /**
 1580        * Inserts an add or addConfigured method into
 1581        * the addTypeMethods array. The array is
 1582        * ordered so that the more derived classes are first.
 1583        * If both add and addConfigured are present, the addConfigured will take priority.
 1584        * @param method the <code>Method</code> to insert.
 1585        */
 1586       private void insertAddTypeMethod(Method method) {
 1587           Class argClass = method.getParameterTypes()[0];
 1588           for (int c = 0; c < addTypeMethods.size(); ++c) {
 1589               Method current = (Method) addTypeMethods.get(c);
 1590               if (current.getParameterTypes()[0].equals(argClass)) {
 1591                   if (method.getName().equals("addConfigured")) {
 1592                       // add configured replaces the add method
 1593                       addTypeMethods.set(c, method);
 1594                   }
 1595                   return; // Already present
 1596               }
 1597               if (current.getParameterTypes()[0].isAssignableFrom(argClass)) {
 1598                   addTypeMethods.add(c, method);
 1599                   return; // higher derived
 1600               }
 1601           }
 1602           addTypeMethods.add(method);
 1603       }
 1604   
 1605       /**
 1606        * Search the list of methods to find the first method
 1607        * that has a parameter that accepts the nested element object.
 1608        * @param paramClass the <code>Class</code> type to search for.
 1609        * @param methods the <code>List</code> of methods to search.
 1610        * @return a matching <code>Method</code>; null if none found.
 1611        */
 1612       private Method findMatchingMethod(Class paramClass, List methods) {
 1613           if (paramClass == null) {
 1614               return null;
 1615           }
 1616           Class matchedClass = null;
 1617           Method matchedMethod = null;
 1618   
 1619           for (int i = 0; i < methods.size(); ++i) {
 1620               Method method = (Method) methods.get(i);
 1621               Class  methodClass = method.getParameterTypes()[0];
 1622               if (methodClass.isAssignableFrom(paramClass)) {
 1623                   if (matchedClass == null) {
 1624                       matchedClass = methodClass;
 1625                       matchedMethod = method;
 1626                   } else if (!methodClass.isAssignableFrom(matchedClass)) {
 1627                       throw new BuildException("ambiguous: types " + matchedClass.getName() + " and "
 1628                               + methodClass.getName() + " match " + paramClass.getName());
 1629                   }
 1630               }
 1631           }
 1632           return matchedMethod;
 1633       }
 1634   
 1635       private String condenseText(final String text) {
 1636           if (text.length() <= MAX_REPORT_NESTED_TEXT) {
 1637               return text;
 1638           }
 1639           int ends = (MAX_REPORT_NESTED_TEXT - ELLIPSIS.length()) / 2;
 1640           return new StringBuffer(text).replace(ends, text.length() - ends, ELLIPSIS).toString();
 1641       }
 1642   
 1643   
 1644       private class MethodAndObject {
 1645           private Method method;
 1646           private Object object;
 1647           public MethodAndObject(Method method, Object object) {
 1648               this.method = method;
 1649               this.object = object;
 1650           }
 1651       }
 1652   
 1653       /**
 1654        *
 1655        */
 1656       private AntTypeDefinition findRestrictedDefinition(
 1657           ComponentHelper helper, String componentName, List methods) {
 1658           AntTypeDefinition definition = null;
 1659           Class matchedDefinitionClass = null;
 1660   
 1661           List definitions = helper.getRestrictedDefinitions(componentName);
 1662           if (definitions == null) {
 1663               return null;
 1664           }
 1665           synchronized (definitions) {
 1666               for (int i = 0; i < definitions.size(); ++i) {
 1667                   AntTypeDefinition d = (AntTypeDefinition) definitions.get(i);
 1668                   Class exposedClass = d.getExposedClass(helper.getProject());
 1669                   if (exposedClass == null) {
 1670                       continue;
 1671                   }
 1672                   Method method  = findMatchingMethod(exposedClass, methods);
 1673                   if (method == null) {
 1674                       continue;
 1675                   }
 1676                   if (matchedDefinitionClass != null) {
 1677                       throw new BuildException(
 1678                           "ambiguous: restricted definitions for "
 1679                           + componentName + " "
 1680                           + matchedDefinitionClass + " and " + exposedClass);
 1681                   }
 1682                   matchedDefinitionClass = exposedClass;
 1683                   definition = d;
 1684               }
 1685           }
 1686           return definition;
 1687       }
 1688   
 1689       private MethodAndObject createRestricted(
 1690           ComponentHelper helper, String elementName, List addTypeMethods) {
 1691   
 1692           Project project = helper.getProject();
 1693   
 1694           AntTypeDefinition restrictedDefinition =
 1695               findRestrictedDefinition(helper, elementName, addTypeMethods);
 1696   
 1697           if (restrictedDefinition == null) {
 1698               return null;
 1699           }
 1700   
 1701           Method addMethod = findMatchingMethod(
 1702               restrictedDefinition.getExposedClass(project), addTypeMethods);
 1703           if (addMethod == null) {
 1704               throw new BuildException(
 1705                   "Ant Internal Error - contract mismatch for "
 1706                   + elementName);
 1707           }
 1708           Object addedObject = restrictedDefinition.create(project);
 1709           if (addedObject == null) {
 1710               throw new BuildException(
 1711                   "Failed to create object " + elementName
 1712                   + " of type " + restrictedDefinition.getTypeClass(project));
 1713           }
 1714           return new MethodAndObject(addMethod, addedObject);
 1715       }
 1716   
 1717       private MethodAndObject createTopLevel(
 1718           ComponentHelper helper, String elementName, List methods) {
 1719           Class clazz = helper.getComponentClass(elementName);
 1720           if (clazz == null) {
 1721               return null;
 1722           }
 1723           Method addMethod = findMatchingMethod(clazz, addTypeMethods);
 1724           if (addMethod == null) {
 1725               return null;
 1726           }
 1727           Object addedObject = helper.createComponent(elementName);
 1728           return new MethodAndObject(addMethod, addedObject);
 1729       }
 1730   
 1731   }

Save This Page
Home » apache-ant-1.8.1 » org.apache.tools » ant » [javadoc | source]