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