Just the one class today, 'cos it was a biggie :)

Notes:

Requested clarification in constructor documentation and manual
(addConfigured)

Changed a couple of parameter names for clarity (element->parent in both
cases, IIRC).

Why does storeElement return on null elementName / NestedStorer,
where other methods will throw an exception?

Commented on implementation of createAttributeSetter - can be made
much simpler (although slightly slower).

getElementName could be made static, which would add some clarity
(IMO).

The various one-method interfaces haven't been documented in detail, as
they
ended up reducing code readability.

Jon
Index: src/main/org/apache/tools/ant/IntrospectionHelper.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-ant/src/main/org/apache/tools/ant/IntrospectionHelper.java,v
retrieving revision 1.32
diff -u -r1.32 IntrospectionHelper.java
--- src/main/org/apache/tools/ant/IntrospectionHelper.java      10 Jan 2002 
11:21:19 -0000      1.32
+++ src/main/org/apache/tools/ant/IntrospectionHelper.java      19 Feb 2002 
16:32:27 -0000
@@ -1,7 +1,7 @@
 /*
  * The Apache Software License, Version 1.1
  *
- * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
+ * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
  * reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -75,45 +75,104 @@
 public class IntrospectionHelper implements BuildListener {
 
     /**
-     * holds the types of the attributes that could be set.
+     * Map from attribute names to attribute types 
+     * (String->Class).
      */
     private Hashtable attributeTypes;
 
     /**
-     * holds the attribute setter methods.
+     * Map from attribute names to attribute setter methods 
+     * (String->AttributeSetter).
      */
     private Hashtable attributeSetters;
 
     /**
-     * Holds the types of nested elements that could be created.
+     * Map from attribute names to nested types 
+     * (String->Class).
      */
     private Hashtable nestedTypes;
 
     /**
-     * Holds methods to create nested elements.
+     * Map from attribute names to methods to create nested types 
+     * (String->NestedCreator).
      */
     private Hashtable nestedCreators;
 
     /**
-     * Holds methods to store configured nested elements.
+     * Map from attribute names to methods to store configured nested types 
+     * (String->NestedStorer).
      */
     private Hashtable nestedStorers;
 
     /**
-     * The method to add PCDATA stuff.
+     * The method to invoke to add PCDATA.
      */
     private Method addText = null;
 
     /**
-     * The Class that's been introspected.
+     * The class introspected by this instance.
      */
     private Class bean;
 
     /**
-     * instances we've already created
+     * Helper instances we've already created (Class->IntrospectionHelper).
      */
     private static Hashtable helpers = new Hashtable();
 
+    /**
+     * Sole constructor, which is private to ensure that all 
+     * IntrospectionHelpers are created via [EMAIL PROTECTED] 
#getHelper(Class) getHelper}.
+     * Introspects the given class for bean-like methods.
+     * Each method is examined in turn, and the following rules are applied:
+     * <p>
+     * <ul>
+     * <li>If the method is <code>Task.setLocation(Location)</code>, 
+     * <code>Task.setTaskType(String)</code>
+     * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These 
+     * methods are handled differently elsewhere.
+     * <li><code>void addText(String)</code> is recognised as the method for
+     * adding PCDATA to a bean.
+     * <li><code>void setFoo(Bar)</code> is recognised as a method for 
+     * setting the value of attribute <code>foo</code>, so long as 
+     * <code>Bar</code> is non-void and is not an array type. Non-String 
+     * parameter types always overload String parameter types, but that is
+     * the only guarantee made in terms of priority.
+     * <li><code>Foo createBar()</code> is recognised as a method for
+     * creating a nested element called <code>bar</code> of type 
+     * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
+     * array type.
+     * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
+     * method for storing a pre-configured element called 
+     * <code>foo</code> and of type <code>Bar</code>, so long as
+     * <code>Bar</code> is not an array, primitive or String type. 
+     * <code>Bar</code> must have an accessible constructor taking no 
+     * arguments.
+     * <li><code>void addFoo(Bar)</code> is recognised as a
+     * method for storing an element called <code>foobar</code> 
+     * and of type <code>Baz</code>, so long as
+     * <code>Baz</code> is not an array, primitive or String type. 
+     * <code>Baz</code> must have an accessible constructor taking no 
+     * arguments.
+     * </ul>
+     * Note that only one method is retained to create/set/addConfigured/add 
+     * any element or attribute.
+     * 
+     * @param bean The bean type to introspect. 
+     *             Must not be <code>null</code>.
+     * 
+     * @see #getHelper(Class)
+     */
+    // XXX: (Jon Skeet) The above documentation doesn't draw a clear 
+    // distinction between addConfigured and add. It's obvious what the
+    // code *here* does (addConfigured sets both a creator method which
+    // calls a no-arg constructor and a storer method which calls the
+    // method we're looking at, whlie add just sets a creator method
+    // which calls the method we're looking at) but it's not at all
+    // obvious what the difference in actual *effect* will be later
+    // on. I can't see any mention of addConfiguredXXX in "Developing
+    // with Ant" (at least in the version on the web site). Someone
+    // who understands should update this documentation 
+    // (and preferably the manual too) at some stage.
     private IntrospectionHelper(final Class bean) {
         attributeTypes = new Hashtable();
         attributeSetters = new Hashtable();
@@ -274,7 +333,13 @@
     }
 
     /**
-     * Factory method for helper objects.
+     * Returns a helper for the given class, either from the cache
+     * or by creating a new instance.
+     * 
+     * @param c The class for which a helper is required.
+     *          Must not be <code>null</code>.
+     * 
+     * @return a helper for the specified class
      */
     public static synchronized IntrospectionHelper getHelper(Class c) {
         IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
@@ -286,7 +351,18 @@
     }
 
     /**
-     * Sets the named attribute.
+     * Sets the named attribute in the given element, which is part of the 
+     * given project.
+     * 
+     * @param p The project containing the element. This is used when files 
+     *          need to be resolved. Must not be <code>null</code>.
+     * @param element The element to set the attribute in. Must not be 
+     *                <code>null</code>.
+     * @param attributeName The name of the attribute to set. Must not be
+     *                      <code>null</code>.
+     * @param value The value to set the attribute to. This may be interpreted
+     *              or converted to the necessary type if the setter method
+     *              doesn't just take a string. Must not be <code>null</code>.
      */
     public void setAttribute(Project p, Object element, String attributeName,
                              String value)
@@ -313,7 +389,21 @@
     }
 
     /**
-     * Adds PCDATA areas.
+     * Adds PCDATA to an element, using the element's 
+     * <code>void addText(String)</code> method, if it has one. If no
+     * such method is present, a BuildException is thrown if the 
+     * given text contains non-whitespace.
+     * 
+     * @param project The project which the element is part of. 
+     *                Must not be <code>null</code>.
+     * @param element The element to add the text to. 
+     *                Must not be <code>null</code>.
+     * @param text    The text to add.
+     *                Must not be <code>null</code>.
+     * 
+     * @throws BuildException if non-whitespace text is provided and no
+     *                        method is available to handle it, or if
+     *                        the handling method fails.
      */
     public void addText(Project project, Object element, String text) {
         if (addText == null) {
@@ -344,18 +434,36 @@
     }
 
     /**
-     * Creates a named nested element.
+     * Creates a named nested element. Depending on the results of the
+     * initial introspection, either a method in the given parent instance
+     * or a simple no-arg constructor is used to create an instance of the
+     * specified element type.
+     * 
+     * @param project Project to which the parent object belongs.
+     *                Must not be <code>null</code>. If the resulting
+     *                object is an instance of ProjectComponent, its
+     *                Project reference is set to this parameter value.
+     * @param parent  Parent object used to create the instance.
+     *                Must not be <code>null</code>.
+     * @param elementName Name of the element to create an instance of.
+     *                    Must not be <code>null</code>.
+     * 
+     * @return an instance of the specified element type
+     * 
+     * @throws BuildException if no method is available to create the 
+     *                        element instance, or if the creating method
+     *                        fails.
      */
-    public Object createElement(Project project, Object element, String 
elementName)
+    public Object createElement(Project project, Object parent, String 
elementName)
         throws BuildException {
         NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
         if (nc == null) {
-            String msg = getElementName(project, element) +
+            String msg = getElementName(project, parent) +
                 " doesn't support the nested \"" + elementName + "\" element.";
             throw new BuildException(msg);
         }
         try {
-            Object nestedElement = nc.create(element);
+            Object nestedElement = nc.create(parent);
             if (nestedElement instanceof ProjectComponent) {
                 ((ProjectComponent) nestedElement).setProject(project);
             }
@@ -376,9 +484,26 @@
     }
 
     /**
-     * Creates a named nested element.
+     * Stores a named nested element using a storage method determined
+     * by the initial introspection. If no appropriate storage method
+     * is available, this method returns immediately.
+     * 
+     * @param project Ignored in this implementation. 
+     *                May be <code>null</code>.
+     * 
+     * @param parent  Parent instance to store the child in. 
+     *                Must not be <code>null</code>.
+     * 
+     * @param child   Child instance to store in the parent.
+     *                Should not be <code>null</code>.
+     * 
+     * @param elementName  Name of the child element to store. 
+     *                     May be <code>null</code>, in which case
+     *                     this method returns immediately.
+     * 
+     * @throws BuildException if the storage method fails.
      */
-    public void storeElement(Project project, Object element, Object child, 
String elementName)
+    public void storeElement(Project project, Object parent, Object child, 
String elementName)
         throws BuildException {
         if (elementName == null) {
             return;
@@ -388,7 +513,7 @@
             return;
         }
         try {
-            ns.store(element, child);
+            ns.store(parent, child);
         } catch (IllegalAccessException ie) {
             // impossible as getMethods should only return public methods
             throw new BuildException(ie);
@@ -405,7 +530,16 @@
     }
 
     /**
-     * returns the type of a named nested element.
+     * Returns the type of a named nested element.
+     * 
+     * @param elementName The name of the element to find the type of.
+     *                    Must not be <code>null</code>.
+     * 
+     * @return the type of the nested element with the specified name.
+     *         This will never be <code>null</code>.
+     * 
+     * @throws BuildException if the introspected class does not
+     *                        support the named nested element.
      */
     public Class getElementType(String elementName)
         throws BuildException {
@@ -419,7 +553,16 @@
     }
 
     /**
-     * returns the type of a named attribute.
+     * Returns the type of a named attribute.
+     * 
+     * @param attributeName The name of the attribute to find the type of.
+     *                      Must not be <code>null</code>.
+     * 
+     * @return the type of the attribute with the specified name.
+     *         This will never be <code>null</code>.
+     * 
+     * @throws BuildException if the introspected class does not
+     *                        support the named attribute.
      */
     public Class getAttributeType(String attributeName)
         throws BuildException {
@@ -433,30 +576,73 @@
     }
 
     /**
-     * Does the introspected class support PCDATA?
+     * Returns whether or not the introspected class supports PCDATA.
+     * 
+     * @return whether or not the introspected class supports PCDATA.
      */
     public boolean supportsCharacters() {
         return addText != null;
     }
 
     /**
-     * Return all attribues supported by the introspected class.
+     * Returns an enumeration of the names of the attributes supported 
+     * by the introspected class.
+     * 
+     * @return an enumeration of the names of the attributes supported
+     *         by the introspected class.
      */
     public Enumeration getAttributes() {
         return attributeSetters.keys();
     }
 
     /**
-     * Return all nested elements supported by the introspected class.
+     * Returns an enumeration of the names of the nested elements supported 
+     * by the introspected class.
+     * 
+     * @return an enumeration of the names of the nested elements supported
+     *         by the introspected class.
      */
     public Enumeration getNestedElements() {
         return nestedTypes.keys();
     }
 
     /**
-     * Create a proper implementation of AttributeSetter for the given
-     * attribute type.
-     */
+     * Creates an implementation of AttributeSetter for the given
+     * attribute type. Conversions (where necessary) are automatically
+     * made for the following types:
+     * <ul>
+     * <li>String (left as it is)
+     * <li>Character/char (first character is used)
+     * <li>Boolean/boolean 
+     * ([EMAIL PROTECTED] Project#toBoolean(String) Project.toBoolean(String)} 
is used)
+     * <li>Class (Class.forName is used)
+     * <li>File (resolved relative to the appropriate project)
+     * <li>Path (resolve relative to the appropriate project)
+     * <li>EnumeratedAttribute (uses its own 
+     * [EMAIL PROTECTED] EnumeratedAttribute#setValue(String) setValue} method)
+     * <li>Other primitive types (wrapper classes are used with constructors 
+     * taking String)
+     * </ul>
+     * 
+     * If none of the above covers the given parameters, a constructor for the 
+     * appropriate class taking a String parameter is used if it is available.
+     * 
+     * @param m The method to invoke on the bean when the setter is invoked.
+     *          Must not be <code>null</code>.
+     * @param arg The type of the single argument passed to the bean's method 
+     *            when the returned setter is invoked.
+     * 
+     * @return an appropriate AttributeSetter instance, or <code>null</code>
+     *         if no appropriate conversion is available.
+     */
+    // XXX: (Jon Skeet) This method implementation could be made much simpler
+    // using a mapping from Integer.TYPE->Integer.class etc, so
+    // that when a primitive type is given, the wrapper is just
+    // automatically used. This would then fall through to the "worst case"
+    // where a string constructor is used - but this is what happens
+    // for Integer etc anyway. There would be a slight speed difference (due
+    // to using reflection where it's currently not being used), but the
+    // size of the code for this method would be reduced by 50 lines :)
     private AttributeSetter createAttributeSetter(final Method m,
                                                   final Class arg) {
 
@@ -619,6 +805,20 @@
         return null;
     }
 
+    /**
+     * Returns a description of the type of the given element in
+     * relation to a given project. This is used for logging purposes
+     * when the element is asked to cope with some data it has no
+     * way of handling.
+     * 
+     * @param project The project the element is defined in. 
+     *                Must not be <code>null</code>.
+     * 
+     * @param element The element to describe.
+     *                Must not be <code>null</code>.
+     * 
+     * @return a description of the element type
+     */
     protected String getElementName(Project project, Object element)
     {
         Hashtable elements = project.getTaskDefinitions();
@@ -651,31 +851,55 @@
     }
 
     /**
-     * extract the name of a property from a method name - subtracting
-     * a given prefix.
+     * Extracts the name of a property from a method name by subtracting
+     * a given prefix and converting into lower case. It is up to calling
+     * code to make sure the method name does actually begin with the
+     * specified prefix - no checking is done in this method.
+     * 
+     * @param methodName The name of the method in question.
+     *                   Must not be <code>null</code>.
+     * @param prefix     The prefix to remove.
+     *                   Must not be <code>null</code>.
+     * 
+     * @return the lower-cased method name with the prefix removed.
      */
     private String getPropertyName(String methodName, String prefix) {
         int start = prefix.length();
         return methodName.substring(start).toLowerCase(Locale.US);
     }
 
+    /**
+     * Internal interface used to create nested elements. Not documented 
+     * in detail for reasons of source code readability.
+     */
     private interface NestedCreator {
         Object create(Object parent)
             throws InvocationTargetException, IllegalAccessException, 
InstantiationException;
     }
 
+    /**
+     * Internal interface used to storing nested elements. Not documented 
+     * in detail for reasons of source code readability.
+     */
     private interface NestedStorer {
         void store(Object parent, Object child)
             throws InvocationTargetException, IllegalAccessException, 
InstantiationException;
     }
 
+    /**
+     * Internal interface used to setting element attributes. Not documented 
+     * in detail for reasons of source code readability.
+     */
     private interface AttributeSetter {
         void set(Project p, Object parent, String value)
             throws InvocationTargetException, IllegalAccessException,
                    BuildException;
     }
 
-    public void buildStarted(BuildEvent event) {}
+    /**
+     * Clears all storage used by this class, including the static cache of 
+     * helpers.
+     */
     public void buildFinished(BuildEvent event) {
         attributeTypes.clear();
         attributeSetters.clear();
@@ -685,9 +909,16 @@
         helpers.clear();
     }
 
+    /** Empty implementation to satisfy the BuildListener interface. */
+    public void buildStarted(BuildEvent event) {}
+    /** Empty implementation to satisfy the BuildListener interface. */
     public void targetStarted(BuildEvent event) {}
+    /** Empty implementation to satisfy the BuildListener interface. */
     public void targetFinished(BuildEvent event) {}
+    /** Empty implementation to satisfy the BuildListener interface. */
     public void taskStarted(BuildEvent event) {}
+    /** Empty implementation to satisfy the BuildListener interface. */
     public void taskFinished(BuildEvent event) {}
+    /** Empty implementation to satisfy the BuildListener interface. */
     public void messageLogged(BuildEvent event) {}
 }
--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to