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

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