Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 981   Methods: 37
NCLOC: 444   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
IntrospectionHelper.java 86.9% 85.4% 78.4% 85%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 
 55   
 package org.apache.tools.ant;
 56   
 
 57   
 import java.io.File;
 58   
 import java.lang.reflect.Constructor;
 59   
 import java.lang.reflect.InvocationTargetException;
 60   
 import java.lang.reflect.Method;
 61   
 import java.util.Enumeration;
 62   
 import java.util.Hashtable;
 63   
 import java.util.Locale;
 64   
 import org.apache.tools.ant.types.EnumeratedAttribute;
 65   
 import org.apache.tools.ant.types.Path;
 66   
 
 67   
 /**
 68   
  * Helper class that collects the methods a task or nested element
 69   
  * holds to set attributes, create nested elements or hold PCDATA
 70   
  * elements.
 71   
  *
 72   
  * @author Stefan Bodewig
 73   
  */
 74   
 public class IntrospectionHelper implements BuildListener {
 75   
 
 76   
     /**
 77   
      * Map from attribute names to attribute types 
 78   
      * (String to Class).
 79   
      */
 80   
     private Hashtable attributeTypes;
 81   
 
 82   
     /**
 83   
      * Map from attribute names to attribute setter methods 
 84   
      * (String to AttributeSetter).
 85   
      */
 86   
     private Hashtable attributeSetters;
 87   
 
 88   
     /**
 89   
      * Map from attribute names to nested types 
 90   
      * (String to Class).
 91   
      */
 92   
     private Hashtable nestedTypes;
 93   
 
 94   
     /**
 95   
      * Map from attribute names to methods to create nested types 
 96   
      * (String to NestedCreator).
 97   
      */
 98   
     private Hashtable nestedCreators;
 99   
 
 100   
     /**
 101   
      * Map from attribute names to methods to store configured nested types 
 102   
      * (String to NestedStorer).
 103   
      */
 104   
     private Hashtable nestedStorers;
 105   
 
 106   
     /**
 107   
      * The method to invoke to add PCDATA.
 108   
      */
 109   
     private Method addText = null;
 110   
 
 111   
     /**
 112   
      * The class introspected by this instance.
 113   
      */
 114   
     private Class bean;
 115   
 
 116   
     /**
 117   
      * Helper instances we've already created (Class to IntrospectionHelper).
 118   
      */
 119   
     private static Hashtable helpers = new Hashtable();
 120   
 
 121   
     /** 
 122   
      * Map from primitive types to wrapper classes for use in 
 123   
      * createAttributeSetter (Class to Class). Note that char 
 124   
      * and boolean are in here even though they get special treatment
 125   
      * - this way we only need to test for the wrapper class.
 126   
      */
 127   
     private static final Hashtable PRIMITIVE_TYPE_MAP = new Hashtable(8);
 128   
 
 129   
     // Set up PRIMITIVE_TYPE_MAP
 130   
     static {
 131  1
         Class[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE, 
 132   
                               Short.TYPE, Integer.TYPE, Long.TYPE, 
 133   
                               Float.TYPE, Double.TYPE};
 134  1
         Class[] wrappers = {Boolean.class, Byte.class, Character.class, 
 135   
                             Short.class, Integer.class, Long.class, 
 136   
                             Float.class, Double.class};
 137  1
         for (int i = 0; i < primitives.length; i++) {
 138  8
             PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]);
 139   
         }
 140   
     }
 141   
 
 142   
     // XXX: (Jon Skeet) The documentation below doesn't draw a clear 
 143   
     // distinction between addConfigured and add. It's obvious what the
 144   
     // code *here* does (addConfigured sets both a creator method which
 145   
     // calls a no-arg constructor and a storer method which calls the
 146   
     // method we're looking at, whlie add just sets a creator method
 147   
     // which calls the method we're looking at) but it's not at all
 148   
     // obvious what the difference in actual *effect* will be later
 149   
     // on. I can't see any mention of addConfiguredXXX in "Developing
 150   
     // with Ant" (at least in the version on the web site). Someone
 151   
     // who understands should update this documentation 
 152   
     // (and preferably the manual too) at some stage.
 153   
     /**
 154   
      * Sole constructor, which is private to ensure that all 
 155   
      * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
 156   
      * Introspects the given class for bean-like methods.
 157   
      * Each method is examined in turn, and the following rules are applied:
 158   
      * <p>
 159   
      * <ul>
 160   
      * <li>If the method is <code>Task.setLocation(Location)</code>, 
 161   
      * <code>Task.setTaskType(String)</code>
 162   
      * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These 
 163   
      * methods are handled differently elsewhere.
 164   
      * <li><code>void addText(String)</code> is recognised as the method for
 165   
      * adding PCDATA to a bean.
 166   
      * <li><code>void setFoo(Bar)</code> is recognised as a method for 
 167   
      * setting the value of attribute <code>foo</code>, so long as 
 168   
      * <code>Bar</code> is non-void and is not an array type. Non-String 
 169   
      * parameter types always overload String parameter types, but that is
 170   
      * the only guarantee made in terms of priority.
 171   
      * <li><code>Foo createBar()</code> is recognised as a method for
 172   
      * creating a nested element called <code>bar</code> of type 
 173   
      * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
 174   
      * array type.
 175   
      * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
 176   
      * method for storing a pre-configured element called 
 177   
      * <code>foo</code> and of type <code>Bar</code>, so long as
 178   
      * <code>Bar</code> is not an array, primitive or String type. 
 179   
      * <code>Bar</code> must have an accessible constructor taking no 
 180   
      * arguments.
 181   
      * <li><code>void addFoo(Bar)</code> is recognised as a
 182   
      * method for storing an element called <code>foobar</code> 
 183   
      * and of type <code>Baz</code>, so long as
 184   
      * <code>Baz</code> is not an array, primitive or String type. 
 185   
      * <code>Baz</code> must have an accessible constructor taking no 
 186   
      * arguments.
 187   
      * </ul>
 188   
      * Note that only one method is retained to create/set/addConfigured/add 
 189   
      * any element or attribute.
 190   
      * 
 191   
      * @param bean The bean type to introspect. 
 192   
      *             Must not be <code>null</code>.
 193   
      * 
 194   
      * @see #getHelper(Class)
 195   
      */
 196  144
     private IntrospectionHelper(final Class bean) {
 197  144
         attributeTypes = new Hashtable();
 198  144
         attributeSetters = new Hashtable();
 199  144
         nestedTypes = new Hashtable();
 200  144
         nestedCreators = new Hashtable();
 201  144
         nestedStorers = new Hashtable();
 202   
 
 203  144
         this.bean = bean;
 204   
 
 205  144
         Method[] methods = bean.getMethods();
 206  144
         for (int i = 0; i < methods.length; i++) {
 207  4935
             final Method m = methods[i];
 208  4935
             final String name = m.getName();
 209  4935
             Class returnType = m.getReturnType();
 210  4935
             Class[] args = m.getParameterTypes();
 211   
 
 212   
             // not really user settable properties on tasks
 213  4935
             if (org.apache.tools.ant.Task.class.isAssignableFrom(bean)
 214   
                  && args.length == 1 && isHiddenSetMethod(name, args[0])) {
 215  138
                 continue;
 216   
             }
 217   
 
 218   
             // hide addTask for TaskContainers
 219  4797
             if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean)
 220   
                 && args.length == 1 && "addTask".equals(name)
 221   
                 && org.apache.tools.ant.Task.class.equals(args[0])) {
 222  5
                 continue;
 223   
             }
 224   
 
 225   
 
 226  4792
             if ("addText".equals(name)
 227   
                 && java.lang.Void.TYPE.equals(returnType)
 228   
                 && args.length == 1
 229   
                 && java.lang.String.class.equals(args[0])) {
 230   
 
 231  8
                 addText = methods[i];
 232   
 
 233  4784
             } else if (name.startsWith("set")
 234   
                        && java.lang.Void.TYPE.equals(returnType)
 235   
                        && args.length == 1
 236   
                        && !args[0].isArray()) {
 237   
 
 238  1148
                 String propName = getPropertyName(name, "set");
 239  1148
                 if (attributeSetters.get(propName) != null) {
 240  11
                     if (java.lang.String.class.equals(args[0])) {
 241   
                         /*
 242   
                             Ignore method m, as there is an overloaded
 243   
                             form of this method that takes in a
 244   
                             non-string argument, which gains higher
 245   
                             priority.
 246   
                         */
 247  0
                         continue;
 248   
                     }
 249   
                     /*
 250   
                         If the argument is not a String, and if there
 251   
                         is an overloaded form of this method already defined,
 252   
                         we just override that with the new one.
 253   
                         This mechanism does not guarantee any specific order
 254   
                         in which the methods will be selected: so any code
 255   
                         that depends on the order in which "set" methods have
 256   
                         been defined, is not guaranteed to be selected in any
 257   
                         particular order.
 258   
                     */
 259   
                 }
 260  1148
                 AttributeSetter as = createAttributeSetter(m, args[0], propName);
 261  1148
                 if (as != null) {
 262  896
                     attributeTypes.put(propName, args[0]);
 263  896
                     attributeSetters.put(propName, as);
 264   
                 }
 265   
 
 266  3636
             } else if (name.startsWith("create")
 267   
                        && !returnType.isArray()
 268   
                        && !returnType.isPrimitive()
 269   
                        && args.length == 0) {
 270   
 
 271  152
                 String propName = getPropertyName(name, "create");
 272  152
                 nestedTypes.put(propName, returnType);
 273  152
                 nestedCreators.put(propName, new NestedCreator() {
 274   
 
 275  341
                         public Object create(Object parent)
 276   
                             throws InvocationTargetException,
 277   
                             IllegalAccessException {
 278   
 
 279  341
                             return m.invoke(parent, new Object[] {});
 280   
                         }
 281   
 
 282   
                     });
 283  152
                 nestedStorers.remove(propName);
 284   
 
 285  3484
             } else if (name.startsWith("addConfigured")
 286   
                        && java.lang.Void.TYPE.equals(returnType)
 287   
                        && args.length == 1
 288   
                        && !java.lang.String.class.equals(args[0])
 289   
                        && !args[0].isArray()
 290   
                        && !args[0].isPrimitive()) {
 291   
 
 292  16
                 try {
 293  16
                     final Constructor c =
 294   
                         args[0].getConstructor(new Class[] {});
 295  16
                     String propName = getPropertyName(name, "addConfigured");
 296  16
                     nestedTypes.put(propName, args[0]);
 297  16
                     nestedCreators.put(propName, new NestedCreator() {
 298   
 
 299  72
                             public Object create(Object parent)
 300   
                                 throws InvocationTargetException, IllegalAccessException, InstantiationException {
 301   
 
 302  72
                                 Object o = c.newInstance(new Object[] {});
 303  72
                                 return o;
 304   
                             }
 305   
 
 306   
                         });
 307  16
                     nestedStorers.put(propName, new NestedStorer() {
 308   
 
 309  66
                             public void store(Object parent, Object child)
 310   
                                 throws InvocationTargetException, IllegalAccessException, InstantiationException {
 311   
 
 312  66
                                 m.invoke(parent, new Object[] {child});
 313   
                             }
 314   
 
 315   
                         });
 316   
                 } catch (NoSuchMethodException nse) {
 317   
                 }
 318  3468
             } else if (name.startsWith("add")
 319   
                        && java.lang.Void.TYPE.equals(returnType)
 320   
                        && args.length == 1
 321   
                        && !java.lang.String.class.equals(args[0])
 322   
                        && !args[0].isArray()
 323   
                        && !args[0].isPrimitive()) {
 324   
 
 325  442
                 try {
 326  442
                     final Constructor c =
 327   
                         args[0].getConstructor(new Class[] {});
 328  439
                     String propName = getPropertyName(name, "add");
 329  439
                     nestedTypes.put(propName, args[0]);
 330  439
                     nestedCreators.put(propName, new NestedCreator() {
 331   
 
 332  438
                             public Object create(Object parent)
 333   
                                 throws InvocationTargetException, IllegalAccessException, InstantiationException {
 334   
 
 335  438
                                 Object o = c.newInstance(new Object[] {});
 336  438
                                 m.invoke(parent, new Object[] {o});
 337  436
                                 return o;
 338   
                             }
 339   
 
 340   
                         });
 341  439
                     nestedStorers.remove(name);
 342   
                 } catch (NoSuchMethodException nse) {
 343   
                 }
 344   
             }
 345   
         }
 346   
     }
 347   
 
 348   
     /** 
 349   
      * Certain set methods are part of the Ant core interface to tasks and 
 350   
      * therefore not to be considered for introspection
 351   
      *
 352   
      * @param name the name of the set method
 353   
      * @param type the type of the set method's parameter 
 354   
      * @return true if the given set method is to be hidden.
 355   
      */
 356  1540
     private boolean isHiddenSetMethod(String name, Class type) {
 357  1540
         if ("setLocation".equals(name) 
 358   
              && org.apache.tools.ant.Location.class.equals(type)) {
 359  69
             return true;
 360   
         }
 361   
         
 362  1471
         if  ("setTaskType".equals(name) 
 363   
              && java.lang.String.class.equals(type)) {
 364  69
             return true;
 365   
         }
 366   
         
 367  1402
         return false;
 368   
     }
 369   
     
 370   
     /**
 371   
      * Returns a helper for the given class, either from the cache
 372   
      * or by creating a new instance.
 373   
      * 
 374   
      * @param c The class for which a helper is required.
 375   
      *          Must not be <code>null</code>.
 376   
      * 
 377   
      * @return a helper for the specified class
 378   
      */
 379  5606
     public static synchronized IntrospectionHelper getHelper(Class c) {
 380  5606
         IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
 381  5606
         if (ih == null) {
 382  144
             ih = new IntrospectionHelper(c);
 383  144
             helpers.put(c, ih);
 384   
         }
 385  5606
         return ih;
 386   
     }
 387   
 
 388   
     /**
 389   
      * Returns a helper for the given class, either from the cache
 390   
      * or by creating a new instance.
 391   
      *
 392   
      * The method will make sure the helper will be cleaned up at the end of
 393   
      * the project, and only one instance will be created for each class.
 394   
      *
 395   
      * @param c The class for which a helper is required.
 396   
      *          Must not be <code>null</code>.
 397   
      *
 398   
      * @return a helper for the specified class
 399   
      */
 400  4084
     public static synchronized IntrospectionHelper getHelper(Project p, Class c)
 401   
     {
 402  4084
         IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
 403  4084
         if (ih == null) {
 404  0
             ih = new IntrospectionHelper(c);
 405  0
             helpers.put(c, ih);
 406   
             // Cleanup at end of project
 407  0
             p.addBuildListener(ih);
 408   
         }
 409  4084
         return ih;
 410   
     }
 411   
 
 412   
     /**
 413   
      * Sets the named attribute in the given element, which is part of the 
 414   
      * given project.
 415   
      * 
 416   
      * @param p The project containing the element. This is used when files 
 417   
      *          need to be resolved. Must not be <code>null</code>.
 418   
      * @param element The element to set the attribute in. Must not be 
 419   
      *                <code>null</code>.
 420   
      * @param attributeName The name of the attribute to set. Must not be
 421   
      *                      <code>null</code>.
 422   
      * @param value The value to set the attribute to. This may be interpreted
 423   
      *              or converted to the necessary type if the setter method
 424   
      *              doesn't just take a string. Must not be <code>null</code>.
 425   
      * 
 426   
      * @exception BuildException if the introspected class doesn't support 
 427   
      *                           the given attribute, or if the setting 
 428   
      *                           method fails.
 429   
      */
 430  5789
     public void setAttribute(Project p, Object element, String attributeName,
 431   
                              String value) throws BuildException {
 432  5789
         AttributeSetter as
 433   
             = (AttributeSetter) attributeSetters.get(attributeName);
 434  5789
         if (as == null) {
 435  183
             if (element instanceof DynamicConfigurator) {
 436  6
                 DynamicConfigurator dc = (DynamicConfigurator) element;
 437  6
                 dc.setDynamicAttribute(attributeName, value);
 438  6
                 return;
 439   
             } else {
 440  177
                 String msg = getElementName(p, element) +
 441   
                     " doesn't support the \"" + attributeName +
 442   
                     "\" attribute.";
 443  177
                 throw new BuildException(msg);
 444   
             }
 445   
         }
 446  5606
         try {
 447  5606
             as.set(p, element, value);
 448   
         } catch (IllegalAccessException ie) {
 449   
             // impossible as getMethods should only return public methods
 450  0
             throw new BuildException(ie);
 451   
         } catch (InvocationTargetException ite) {
 452  19
             Throwable t = ite.getTargetException();
 453  19
             if (t instanceof BuildException) {
 454  3
                 throw (BuildException) t;
 455   
             }
 456  16
             throw new BuildException(t);
 457   
         }
 458   
     }
 459   
 
 460   
     /**
 461   
      * Adds PCDATA to an element, using the element's 
 462   
      * <code>void addText(String)</code> method, if it has one. If no
 463   
      * such method is present, a BuildException is thrown if the 
 464   
      * given text contains non-whitespace.
 465   
      * 
 466   
      * @param project The project which the element is part of. 
 467   
      *                Must not be <code>null</code>.
 468   
      * @param element The element to add the text to. 
 469   
      *                Must not be <code>null</code>.
 470   
      * @param text    The text to add.
 471   
      *                Must not be <code>null</code>.
 472   
      * 
 473   
      * @exception BuildException if non-whitespace text is provided and no
 474   
      *                           method is available to handle it, or if
 475   
      *                           the handling method fails.
 476   
      */
 477  676
     public void addText(Project project, Object element, String text) 
 478   
         throws BuildException {
 479  676
         if (addText == null) {
 480   
             // Element doesn't handle text content
 481  578
             if (text.trim().length() == 0) {
 482   
                 // Only whitespace - ignore
 483  577
                 return;
 484   
             } else {
 485   
                 // Not whitespace - fail
 486  1
                 String msg = project.getElementName(element) +
 487   
                     " doesn't support nested text data.";
 488  1
                 throw new BuildException(msg);
 489   
             }
 490   
         }
 491  98
         try {
 492  98
             addText.invoke(element, new String[] {text});
 493   
         } catch (IllegalAccessException ie) {
 494   
             // impossible as getMethods should only return public methods
 495  0
             throw new BuildException(ie);
 496   
         } catch (InvocationTargetException ite) {
 497  1
             Throwable t = ite.getTargetException();
 498  1
             if (t instanceof BuildException) {
 499  0
                 throw (BuildException) t;
 500   
             }
 501  1
             throw new BuildException(t);
 502   
         }
 503   
     }
 504   
 
 505  0
     public void throwNotSupported(Project project, Object parent, 
 506   
         String elementName) {
 507  0
         String msg = project.getElementName(parent) +
 508   
             " doesn't support the nested \"" + elementName + "\" element.";
 509  0
         throw new BuildException(msg);
 510   
     }        
 511   
     
 512   
     /**
 513   
      * Creates a named nested element. Depending on the results of the
 514   
      * initial introspection, either a method in the given parent instance
 515   
      * or a simple no-arg constructor is used to create an instance of the
 516   
      * specified element type.
 517   
      * 
 518   
      * @param project Project to which the parent object belongs.
 519   
      *                Must not be <code>null</code>. If the resulting
 520   
      *                object is an instance of ProjectComponent, its
 521   
      *                Project reference is set to this parameter value.
 522   
      * @param parent  Parent object used to create the instance.
 523   
      *                Must not be <code>null</code>.
 524   
      * @param elementName Name of the element to create an instance of.
 525   
      *                    Must not be <code>null</code>.
 526   
      * 
 527   
      * @return an instance of the specified element type
 528   
      * 
 529   
      * @exception BuildException if no method is available to create the
 530   
      *                           element instance, or if the creating method
 531   
      *                           fails.
 532   
      */
 533  853
     public Object createElement(Project project, Object parent, 
 534   
         String elementName) throws BuildException {
 535  853
         NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
 536  853
         if (nc == null && parent instanceof DynamicConfigurator) {
 537  2
             DynamicConfigurator dc = (DynamicConfigurator) parent;
 538  2
             Object nestedElement = dc.createDynamicElement(elementName);
 539  2
             if (nestedElement != null) {
 540  2
                 if (nestedElement instanceof ProjectComponent) {
 541  0
                     ((ProjectComponent) nestedElement).setProject(project);
 542   
                 }
 543  2
                 return nestedElement;
 544   
             }
 545   
         }
 546  851
         if (nc == null) {
 547  0
             throwNotSupported(project, parent, elementName);
 548   
         }
 549  851
         try {
 550  851
             Object nestedElement = nc.create(parent);
 551  849
             if (nestedElement instanceof ProjectComponent) {
 552  432
                 ((ProjectComponent) nestedElement).setProject(project);
 553   
             }
 554  849
             return nestedElement;
 555   
         } catch (IllegalAccessException ie) {
 556   
             // impossible as getMethods should only return public methods
 557  0
             throw new BuildException(ie);
 558   
         } catch (InstantiationException ine) {
 559   
             // impossible as getMethods should only return public methods
 560  0
             throw new BuildException(ine);
 561   
         } catch (InvocationTargetException ite) {
 562  2
             Throwable t = ite.getTargetException();
 563  2
             if (t instanceof BuildException) {
 564  0
                 throw (BuildException) t;
 565   
             }
 566  2
             throw new BuildException(t);
 567   
         }
 568   
     }
 569   
 
 570   
     /**
 571   
      * Indicate if this element supports a nested element of the 
 572   
      * given name.
 573   
      *
 574   
      * @param elementName the name of the nested element being checked
 575   
      *
 576   
      * @return true if the given nested element is supported
 577   
      */
 578  1866
     public boolean supportsNestedElement(String elementName) {
 579  1866
         return nestedCreators.containsKey(elementName) ||
 580   
             DynamicConfigurator.class.isAssignableFrom(bean);
 581   
     }
 582   
     
 583   
     /**
 584   
      * Stores a named nested element using a storage method determined
 585   
      * by the initial introspection. If no appropriate storage method
 586   
      * is available, this method returns immediately.
 587   
      * 
 588   
      * @param project Ignored in this implementation. 
 589   
      *                May be <code>null</code>.
 590   
      * 
 591   
      * @param parent  Parent instance to store the child in. 
 592   
      *                Must not be <code>null</code>.
 593   
      * 
 594   
      * @param child   Child instance to store in the parent.
 595   
      *                Should not be <code>null</code>.
 596   
      * 
 597   
      * @param elementName  Name of the child element to store. 
 598   
      *                     May be <code>null</code>, in which case
 599   
      *                     this method returns immediately.
 600   
      * 
 601   
      * @exception BuildException if the storage method fails.
 602   
      */
 603  842
     public void storeElement(Project project, Object parent, Object child, 
 604   
         String elementName) throws BuildException {
 605  842
         if (elementName == null) {
 606  0
             return;
 607   
         }
 608  842
         NestedStorer ns = (NestedStorer) nestedStorers.get(elementName);
 609  842
         if (ns == null) {
 610  776
             return;
 611   
         }
 612  66
         try {
 613  66
             ns.store(parent, child);
 614   
         } catch (IllegalAccessException ie) {
 615   
             // impossible as getMethods should only return public methods
 616  0
             throw new BuildException(ie);
 617   
         } catch (InstantiationException ine) {
 618   
             // impossible as getMethods should only return public methods
 619  0
             throw new BuildException(ine);
 620   
         } catch (InvocationTargetException ite) {
 621  5
             Throwable t = ite.getTargetException();
 622  5
             if (t instanceof BuildException) {
 623  4
                 throw (BuildException) t;
 624   
             }
 625  1
             throw new BuildException(t);
 626   
         }
 627   
     }
 628   
 
 629   
     /**
 630   
      * Returns the type of a named nested element.
 631   
      * 
 632   
      * @param elementName The name of the element to find the type of.
 633   
      *                    Must not be <code>null</code>.
 634   
      * 
 635   
      * @return the type of the nested element with the specified name.
 636   
      *         This will never be <code>null</code>.
 637   
      * 
 638   
      * @exception BuildException if the introspected class does not
 639   
      *                           support the named nested element.
 640   
      */
 641  17
     public Class getElementType(String elementName)
 642   
         throws BuildException {
 643  17
         Class nt = (Class) nestedTypes.get(elementName);
 644  17
         if (nt == null) {
 645  11
             String msg = "Class " + bean.getName() +
 646   
                 " doesn't support the nested \"" + elementName + "\" element.";
 647  11
             throw new BuildException(msg);
 648   
         }
 649  6
         return nt;
 650   
     }
 651   
 
 652   
     /**
 653   
      * Returns the type of a named attribute.
 654   
      * 
 655   
      * @param attributeName The name of the attribute to find the type of.
 656   
      *                      Must not be <code>null</code>.
 657   
      * 
 658   
      * @return the type of the attribute with the specified name.
 659   
      *         This will never be <code>null</code>.
 660   
      * 
 661   
      * @exception BuildException if the introspected class does not
 662   
      *                           support the named attribute.
 663   
      */
 664  14
     public Class getAttributeType(String attributeName)
 665   
         throws BuildException {
 666  14
         Class at = (Class) attributeTypes.get(attributeName);
 667  14
         if (at == null) {
 668  0
             String msg = "Class " + bean.getName() +
 669   
                 " doesn't support the \"" + attributeName + "\" attribute.";
 670  0
             throw new BuildException(msg);
 671   
         }
 672  14
         return at;
 673   
     }
 674   
 
 675   
     /**
 676   
      * Returns whether or not the introspected class supports PCDATA.
 677   
      * 
 678   
      * @return whether or not the introspected class supports PCDATA.
 679   
      */
 680  2
     public boolean supportsCharacters() {
 681  2
         return addText != null;
 682   
     }
 683   
 
 684   
     /**
 685   
      * Returns an enumeration of the names of the attributes supported 
 686   
      * by the introspected class.
 687   
      * 
 688   
      * @return an enumeration of the names of the attributes supported
 689   
      *         by the introspected class.
 690   
      */
 691  1
     public Enumeration getAttributes() {
 692  1
         return attributeSetters.keys();
 693   
     }
 694   
 
 695   
     /**
 696   
      * Returns an enumeration of the names of the nested elements supported 
 697   
      * by the introspected class.
 698   
      * 
 699   
      * @return an enumeration of the names of the nested elements supported
 700   
      *         by the introspected class.
 701   
      */
 702  1
     public Enumeration getNestedElements() {
 703  1
         return nestedTypes.keys();
 704   
     }
 705   
 
 706   
     /**
 707   
      * Creates an implementation of AttributeSetter for the given
 708   
      * attribute type. Conversions (where necessary) are automatically
 709   
      * made for the following types:
 710   
      * <ul>
 711   
      * <li>String (left as it is)
 712   
      * <li>Character/char (first character is used)
 713   
      * <li>Boolean/boolean 
 714   
      * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used)
 715   
      * <li>Class (Class.forName is used)
 716   
      * <li>File (resolved relative to the appropriate project)
 717   
      * <li>Path (resolve relative to the appropriate project)
 718   
      * <li>EnumeratedAttribute (uses its own 
 719   
      * {@link EnumeratedAttribute#setValue(String) setValue} method)
 720   
      * <li>Other primitive types (wrapper classes are used with constructors 
 721   
      * taking String)
 722   
      * </ul>
 723   
      * 
 724   
      * If none of the above covers the given parameters, a constructor for the 
 725   
      * appropriate class taking a String parameter is used if it is available.
 726   
      * 
 727   
      * @param m The method to invoke on the bean when the setter is invoked.
 728   
      *          Must not be <code>null</code>.
 729   
      * @param arg The type of the single argument of the bean's method.
 730   
      *            Must not be <code>null</code>.
 731   
      * @param attrName the name of the attribute for which the setter is being
 732   
      *                 created.
 733   
      * 
 734   
      * @return an appropriate AttributeSetter instance, or <code>null</code>
 735   
      *         if no appropriate conversion is available.
 736   
      */
 737  1148
     private AttributeSetter createAttributeSetter(final Method m,
 738   
                                                   Class arg, 
 739   
                                                   final String attrName) {
 740   
         // use wrappers for primitive classes, e.g. int and 
 741   
         // Integer are treated identically
 742  1148
         final Class reflectedArg = PRIMITIVE_TYPE_MAP.containsKey (arg) 
 743   
             ? (Class) PRIMITIVE_TYPE_MAP.get(arg) : arg;
 744   
 
 745   
         // simplest case - setAttribute expects String
 746  1148
         if (java.lang.String.class.equals(reflectedArg)) {
 747  421
             return new AttributeSetter() {
 748  2380
                     public void set(Project p, Object parent, String value)
 749   
                         throws InvocationTargetException, IllegalAccessException {
 750  2380
                         m.invoke(parent, new String[] {value});
 751   
                     }
 752   
                 };
 753   
 
 754   
         // char and Character get special treatment - take the first character
 755  727
         } else if (java.lang.Character.class.equals(reflectedArg)) {
 756  4
             return new AttributeSetter() {
 757  4
                     public void set(Project p, Object parent, String value)
 758   
                         throws InvocationTargetException, IllegalAccessException {
 759  4
                         if (value.length() == 0) {
 760  0
                             throw new BuildException("The value \"\" is not a " 
 761   
                                 + "legal value for attribute \"" 
 762   
                                 + attrName + "\"");
 763   
                         }
 764  4
                         m.invoke(parent, new Character[] {new Character(value.charAt(0))});
 765   
                     }
 766   
 
 767   
                 };
 768   
         // boolean and Boolean get special treatment because we 
 769   
         // have a nice method in Project
 770  723
         } else if (java.lang.Boolean.class.equals(reflectedArg)) {
 771  181
             return new AttributeSetter() {
 772  223
                     public void set(Project p, Object parent, String value)
 773   
                         throws InvocationTargetException, IllegalAccessException {
 774  223
                         m.invoke(parent,
 775   
                                  new Boolean[] {new Boolean(Project.toBoolean(value))});
 776   
                     }
 777   
 
 778   
                 };
 779   
 
 780   
         // Class doesn't have a String constructor but a decent factory method
 781  542
         } else if (java.lang.Class.class.equals(reflectedArg)) {
 782  1
             return new AttributeSetter() {
 783  3
                     public void set(Project p, Object parent, String value)
 784   
                         throws InvocationTargetException, IllegalAccessException, BuildException {
 785  3
                         try {
 786  3
                             m.invoke(parent, new Class[] {Class.forName(value)});
 787   
                         } catch (ClassNotFoundException ce) {
 788  1
                             throw new BuildException(ce);
 789   
                         }
 790   
                     }
 791   
                 };
 792   
 
 793   
         // resolve relative paths through Project
 794  541
         } else if (java.io.File.class.equals(reflectedArg)) {
 795  166
             return new AttributeSetter() {
 796  2606
                     public void set(Project p, Object parent, String value)
 797   
                         throws InvocationTargetException, IllegalAccessException {
 798  2606
                         m.invoke(parent, new File[] {p.resolveFile(value)});
 799   
                     }
 800   
 
 801   
                 };
 802   
 
 803   
         // resolve relative paths through Project
 804  375
         } else if (org.apache.tools.ant.types.Path.class.equals(reflectedArg)) {
 805  24
             return new AttributeSetter() {
 806  68
                     public void set(Project p, Object parent, String value)
 807   
                         throws InvocationTargetException, IllegalAccessException {
 808  68
                         m.invoke(parent, new Path[] {new Path(p, value)});
 809   
                     }
 810   
 
 811   
                 };
 812   
 
 813   
         // EnumeratedAttributes have their own helper class
 814  351
         } else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
 815  28
             return new AttributeSetter() {
 816  106
                     public void set(Project p, Object parent, String value)
 817   
                         throws InvocationTargetException, IllegalAccessException, BuildException {
 818  106
                         try {
 819  106
                             org.apache.tools.ant.types.EnumeratedAttribute ea = 
 820   
                                 (org.apache.tools.ant.types.EnumeratedAttribute) reflectedArg.newInstance();
 821  106
                             ea.setValue(value);
 822  102
                             m.invoke(parent, new EnumeratedAttribute[] {ea});
 823   
                         } catch (InstantiationException ie) {
 824  0
                             throw new BuildException(ie);
 825   
                         }
 826   
                     }
 827   
                 };
 828   
 
 829   
         // worst case. look for a public String constructor and use it
 830   
         // This is used (deliberately) for all primitives/wrappers other than 
 831   
         // char and boolean
 832   
         } else {
 833   
 
 834  323
             try {
 835  323
                 final Constructor c =
 836   
                     reflectedArg.getConstructor(new Class[] {java.lang.String.class});
 837   
 
 838  71
                 return new AttributeSetter() {
 839  216
                         public void set(Project p, Object parent,
 840   
                                         String value)
 841   
                             throws InvocationTargetException, IllegalAccessException, BuildException {
 842  216
                             try {
 843  216
                                 Object attribute = c.newInstance(new String[] {value});
 844  213
                                 if (attribute instanceof ProjectComponent) {
 845  0
                                     ((ProjectComponent) attribute).setProject(p);
 846   
                                 }
 847  213
                                 m.invoke(parent, new Object[] {attribute});
 848   
                             } catch (InstantiationException ie) {
 849  0
                                 throw new BuildException(ie);
 850   
                             }
 851   
                         }
 852   
                     };
 853   
 
 854   
             } catch (NoSuchMethodException nme) {
 855   
             }
 856   
         }
 857   
 
 858  252
         return null;
 859   
     }
 860   
 
 861   
     /**
 862   
      * Returns a description of the type of the given element in
 863   
      * relation to a given project. This is used for logging purposes
 864   
      * when the element is asked to cope with some data it has no
 865   
      * way of handling.
 866   
      * 
 867   
      * @param project The project the element is defined in. 
 868   
      *                Must not be <code>null</code>.
 869   
      * 
 870   
      * @param element The element to describe.
 871   
      *                Must not be <code>null</code>.
 872   
      * 
 873   
      * @return a description of the element type
 874   
      */
 875  177
     protected String getElementName(Project project, Object element) {
 876  177
         return project.getElementName(element);
 877   
     }
 878   
 
 879   
     /**
 880   
      * Extracts the name of a property from a method name by subtracting
 881   
      * a given prefix and converting into lower case. It is up to calling
 882   
      * code to make sure the method name does actually begin with the
 883   
      * specified prefix - no checking is done in this method.
 884   
      * 
 885   
      * @param methodName The name of the method in question.
 886   
      *                   Must not be <code>null</code>.
 887   
      * @param prefix     The prefix to remove.
 888   
      *                   Must not be <code>null</code>.
 889   
      * 
 890   
      * @return the lower-cased method name with the prefix removed.
 891   
      */
 892  1755
     private String getPropertyName(String methodName, String prefix) {
 893  1755
         int start = prefix.length();
 894  1755
         return methodName.substring(start).toLowerCase(Locale.US);
 895   
     }
 896   
 
 897   
     /**
 898   
      * Internal interface used to create nested elements. Not documented 
 899   
      * in detail for reasons of source code readability.
 900   
      */
 901   
     private interface NestedCreator {
 902   
         Object create(Object parent)
 903   
             throws InvocationTargetException, IllegalAccessException, InstantiationException;
 904   
     }
 905   
 
 906   
     /**
 907   
      * Internal interface used to storing nested elements. Not documented 
 908   
      * in detail for reasons of source code readability.
 909   
      */
 910   
     private interface NestedStorer {
 911   
         void store(Object parent, Object child)
 912   
             throws InvocationTargetException, IllegalAccessException, InstantiationException;
 913   
     }
 914   
 
 915   
     /**
 916   
      * Internal interface used to setting element attributes. Not documented 
 917   
      * in detail for reasons of source code readability.
 918   
      */
 919   
     private interface AttributeSetter {
 920   
         void set(Project p, Object parent, String value)
 921   
             throws InvocationTargetException, IllegalAccessException,
 922   
                    BuildException;
 923   
     }
 924   
 
 925   
     /**
 926   
      * Clears all storage used by this class, including the static cache of 
 927   
      * helpers.
 928   
      * 
 929   
      * @param event Ignored in this implementation.
 930   
      */
 931  0
     public void buildFinished(BuildEvent event) {
 932  0
         attributeTypes.clear();
 933  0
         attributeSetters.clear();
 934  0
         nestedTypes.clear();
 935  0
         nestedCreators.clear();
 936  0
         addText = null;
 937  0
         helpers.clear();
 938   
     }
 939   
 
 940   
     /**
 941   
      * Empty implementation to satisfy the BuildListener interface.
 942   
      * @param event Ignored in this implementation.
 943   
      */
 944  0
     public void buildStarted(BuildEvent event) {}
 945   
     
 946   
     /**
 947   
      * Empty implementation to satisfy the BuildListener interface.
 948   
      *
 949   
      * @param event Ignored in this implementation.
 950   
      */
 951  0
     public void targetStarted(BuildEvent event) {}
 952   
     
 953   
     /**
 954   
      * Empty implementation to satisfy the BuildListener interface.
 955   
      *
 956   
      * @param event Ignored in this implementation.
 957   
      */
 958  0
     public void targetFinished(BuildEvent event) {}
 959   
     
 960   
     /**
 961   
      * Empty implementation to satisfy the BuildListener interface.
 962   
      *
 963   
      * @param event Ignored in this implementation.
 964   
      */
 965  0
     public void taskStarted(BuildEvent event) {}
 966   
     
 967   
     /**
 968   
      * Empty implementation to satisfy the BuildListener interface.
 969   
      *
 970   
      * @param event Ignored in this implementation.
 971   
      */
 972  0
     public void taskFinished(BuildEvent event) {}
 973   
     
 974   
     /**
 975   
      * Empty implementation to satisfy the BuildListener interface.
 976   
      *
 977   
      * @param event Ignored in this implementation.
 978   
      */
 979  0
     public void messageLogged(BuildEvent event) {}
 980   
 }
 981