Browse Source

Sync with main code line

git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@274356 13f79535-47bb-0310-9956-ffa450edef68
master
Stefan Bodewig 22 years ago
parent
commit
5cdfabc038
2 changed files with 857 additions and 212 deletions
  1. +449
    -180
      proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java
  2. +408
    -32
      proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java

+ 449
- 180
proposal/sandbox/antlib/src/main/org/apache/tools/ant/IntrospectionHelper.java View File

@@ -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:
* <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)
*/
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; i<methods.length; i++) {
for (int i = 0; i < methods.length; i++) {
final Method m = methods[i];
final String name = m.getName();
Class returnType = m.getReturnType();
@@ -132,23 +211,16 @@ public class IntrospectionHelper implements BuildListener {

// not really user settable properties on tasks
if (org.apache.tools.ant.Task.class.isAssignableFrom(bean)
&& args.length == 1 &&
(
(
"setLocation".equals(name) && org.apache.tools.ant.Location.class.equals(args[0])
) || (
"setTaskType".equals(name) && java.lang.String.class.equals(args[0])
)
)) {
&& args.length == 1 && isHiddenSetMethod(name, args[0])) {
continue;
}

// hide addTask for TaskContainers
// if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean)
// && args.length == 1 && "addTask".equals(name)
// && org.apache.tools.ant.Task.class.equals(args[0])) {
// continue;
// }
if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean)
&& args.length == 1 && "addTask".equals(name)
&& org.apache.tools.ant.Task.class.equals(args[0])) {
continue;
}


if ("addText".equals(name)
@@ -185,7 +257,7 @@ public class IntrospectionHelper implements BuildListener {
particular order.
*/
}
AttributeSetter as = createAttributeSetter(m, args[0]);
AttributeSetter as = createAttributeSetter(m, args[0], propName);
if (as != null) {
attributeTypes.put(propName, args[0]);
attributeSetters.put(propName, as);
@@ -225,9 +297,7 @@ public class IntrospectionHelper implements BuildListener {
nestedCreators.put(propName, new NestedCreator() {

public Object create(Object parent)
throws InvocationTargetException,
IllegalAccessException,
InstantiationException {
throws InvocationTargetException, IllegalAccessException, InstantiationException {

Object o = c.newInstance(new Object[] {});
return o;
@@ -237,9 +307,7 @@ public class IntrospectionHelper implements BuildListener {
nestedStorers.put(propName, new NestedStorer() {

public void store(Object parent, Object child)
throws InvocationTargetException,
IllegalAccessException,
InstantiationException {
throws InvocationTargetException, IllegalAccessException, InstantiationException {

m.invoke(parent, new Object[] {child});
}
@@ -262,9 +330,7 @@ public class IntrospectionHelper implements BuildListener {
nestedCreators.put(propName, new NestedCreator() {

public Object create(Object parent)
throws InvocationTargetException,
IllegalAccessException,
InstantiationException {
throws InvocationTargetException, IllegalAccessException, InstantiationException {

Object o = c.newInstance(new Object[] {});
m.invoke(parent, new Object[] {o});
@@ -279,8 +345,36 @@ public class IntrospectionHelper implements BuildListener {
}
}

/**
* Certain set methods are part of the Ant core interface to tasks and
* therefore not to be considered for introspection
*
* @param name the name of the set method
* @param type the type of the set method's parameter
* @return true if the given set method is to be hidden.
*/
private boolean isHiddenSetMethod(String name, Class type) {
if ("setLocation".equals(name)
&& org.apache.tools.ant.Location.class.equals(type)) {
return true;
}
if ("setTaskType".equals(name)
&& java.lang.String.class.equals(type)) {
return true;
}
return false;
}
/**
* 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);
@@ -292,17 +386,63 @@ public class IntrospectionHelper implements BuildListener {
}

/**
* Sets the named attribute.
* Returns a helper for the given class, either from the cache
* or by creating a new instance.
*
* The method will make sure the helper will be cleaned up at the end of
* the project, and only one instance will be created for each class.
*
* @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(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 <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>.
*
* @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
* <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>.
*
* @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 <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
*
* @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 <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.
*
* @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 <code>null</code>.
*
* @return the type of the nested element with the specified name.
* This will never be <code>null</code>.
*
* @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 <code>null</code>.
*
* @return the type of the attribute with the specified name.
* This will never be <code>null</code>.
*
* @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:
* <ul>
* <li>String (left as it is)
* <li>Character/char (first character is used)
* <li>Boolean/boolean
* ({@link 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
* {@link 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 of the bean's method.
* Must not be <code>null</code>.
* @param attrName the name of the attribute for which the setter is being
* created.
*
* @return an appropriate AttributeSetter instance, or <code>null</code>
* 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 <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) {
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 <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.
*
* @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) {}
}

+ 408
- 32
proposal/sandbox/antlib/src/main/org/apache/tools/ant/Project.java View File

@@ -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 <code>null</code>.
* @param isError Whether the text represents an error (<code>true</code>)
* or information (<code>false</code>).
*/
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 <code>null</code>.
* @param isError Whether the text represents an error (<code>true</code>)
* or information (<code>false</code>).
*/
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 <code>null</code>.
* @param value The value of the reference. Must not be <code>null</code>.
*/
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.
* <p>
* This is useful for logging purposes.
*
* @param element The element to describe.
* Must not be <code>null</code>.
*
* @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;
}

}

}

Loading…
Cancel
Save