Browse Source

Rewritten the introspection part of ProjectHelper.


			
			master
		
Stefan Bodewig 25 years ago
parent
commit
b6ec4af9dd
6 changed files with 588 additions and 98 deletions
  1. +16
    -3
      docs/index.html
  2. +7
    -0
      src/main/org/apache/tools/ant/BuildException.java
  3. +482
    -0
      src/main/org/apache/tools/ant/IntrospectionHelper.java
  4. +54
    -79
      src/main/org/apache/tools/ant/ProjectHelper.java
  5. +1
    -1
      src/main/org/apache/tools/ant/taskdefs/Ant.java
  6. +28
    -15
      src/main/org/apache/tools/ant/taskdefs/Patch.java

+ 16
- 3
docs/index.html View File

@@ -3143,11 +3143,24 @@ output.
<p>It is very easy to write your own task:</p>
<ol>
<li>Create a Java class that extends <code>org.apache.tools.ant.Task</code>.</li>
<li>For each attribute, write a setter method. The setter method must be a <code>public
void</code> method that takes one <code>String</code> as an argument. The
<li>For each attribute, write a setter method. The setter method must be a
<code>public void</code> method that takes a single argument. The
name of the method must begin with &quot;set&quot;, followed by the
attribute name, with the first character in uppercase, and the rest in
lowercase.</li>
lowercase. The type of the attribute can be <code>String</code>, any
primitive type, <code>Class</code>, <code>File</code> (in which case the
value of the attribute is interpreted relative to the project's basedir)
or any other type that has a constructor with a single <code>String</code>
argument</li>
<li>If the task should support character data, write a <code>public void
addText(String)</code> method.</li>
<li>For each nested element, write a create or add method. A create method
must be a <code>public</code> method that takes no arguments and returns
an Object type. The name of the create method must begin with
&quot;create&quot;, followed by the element name. An add method must be
a <code>public void</code> method that takes a single argument of an
Object type with a no argument constructor. The name of the add method
must begin with &quot;add&quot;, followed by the element name.
<li>Write a <code>public void execute</code> method, with no arguments, that
throws a <code>BuildException</code>. This method implements the task
itself.</li>


+ 7
- 0
src/main/org/apache/tools/ant/BuildException.java View File

@@ -152,4 +152,11 @@ public class BuildException extends RuntimeException {
public void setLocation(Location location) {
this.location = location;
}

/**
* Returns the file location where the error occured.
*/
public Location getLocation() {
return location;
}
}

+ 482
- 0
src/main/org/apache/tools/ant/IntrospectionHelper.java View File

@@ -0,0 +1,482 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", 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.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/

package org.apache.tools.ant;

import java.lang.reflect.*;
import java.io.File;
import java.util.*;

/**
* Helper class that collects the methods a task or nested element
* holds to set attributes, create nested elements or hold PCDATA
* elements.
*
* @author Stefan Bodewig <a href="mailto:stefan.bodewig@megabit.net">stefan.bodewig@megabit.net</a>
*/
public class IntrospectionHelper {

/**
* holds the types of the attributes that could be set.
*/
private Hashtable attributeTypes;

/**
* holds the attribute setter methods.
*/
private Hashtable attributeSetters;

/**
* Holds the types of nested elements that could be created.
*/
private Hashtable nestedTypes;

/**
* Holds methods to create nested elements.
*/
private Hashtable nestedCreators;

/**
* The method to add PCDATA stuff.
*/
private Method addText = null;

/**
* The Class that's been introspected.
*/
private Class bean;

/**
* instances we've already created
*/
private static Hashtable helpers = new Hashtable();

private IntrospectionHelper(final Class bean) {
attributeTypes = new Hashtable();
attributeSetters = new Hashtable();
nestedTypes = new Hashtable();
nestedCreators = new Hashtable();
this.bean = bean;

Method[] methods = bean.getMethods();
for (int i=0; i<methods.length; i++) {
final Method m = methods[i];
final String name = m.getName();
Class returnType = m.getReturnType();
Class[] args = m.getParameterTypes();

if ("setLocation".equals(name) || "setDescription".equals(name)) {
continue;
}
if ("addText".equals(name)
&& java.lang.Void.TYPE.equals(returnType)
&& args.length == 1
&& java.lang.String.class.equals(args[0])) {

addText = methods[i];

} else if (name.startsWith("set")
&& java.lang.Void.TYPE.equals(returnType)
&& args.length == 1
&& !args[0].isArray()) {

String propName = getPropertyName(name, "set");
AttributeSetter as = createAttributeSetter(m, args[0]);
if (as != null) {
attributeTypes.put(propName, args[0]);
attributeSetters.put(propName, as);
}

} else if (name.startsWith("create")
&& !returnType.isArray()
&& !returnType.isPrimitive()
&& args.length == 0) {

String propName = getPropertyName(name, "create");
nestedTypes.put(propName, returnType);
nestedCreators.put(propName, new NestedCreator() {

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

return m.invoke(parent, new Object[] {});
}

});
} else if (name.startsWith("add")
&& java.lang.Void.TYPE.equals(returnType)
&& args.length == 1
&& !args[0].isArray()
&& !args[0].isPrimitive()) {
try {
final Constructor c =
args[0].getConstructor(new Class[] {});
String propName = getPropertyName(name, "add");
nestedTypes.put(propName, args[0]);
nestedCreators.put(propName, new NestedCreator() {

public Object create(Object parent)
throws InvocationTargetException, IllegalAccessException, InstantiationException {
Object o = c.newInstance(new Object[] {});
m.invoke(parent, new Object[] {o});
return o;
}

});
} catch (NoSuchMethodException nse) {
}
}
}
}
/**
* Factory method for helper objects.
*/
public synchronized static IntrospectionHelper getHelper(Class c) {
IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
if (ih == null) {
ih = new IntrospectionHelper(c);
helpers.put(c, ih);
}
return ih;
}

/**
* Sets the named attribute.
*/
public void setAttribute(Project p, Object element, String attributeName,
String value)
throws BuildException {
AttributeSetter as = (AttributeSetter) attributeSetters.get(attributeName);
if (as == null) {
String msg = "Class " + element.getClass() +
" doesn't support the \"" + attributeName + "\" attribute";
throw new BuildException(msg);
}
try {
as.set(p, element, value);
} catch (IllegalAccessException ie) {
// impossible as getMethods should only return public methods
throw new BuildException(ie);
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t instanceof BuildException) {
throw (BuildException) t;
}
throw new BuildException(t);
}
}

/**
* Adds PCDATA areas.
*/
public void addText(Object element, String text) {
if (addText == null) {
String msg = "Class " + element.getClass() +
" doesn't support nested text elements";
throw new BuildException(msg);
}
try {
addText.invoke(element, new String[] {text});
} catch (IllegalAccessException ie) {
// impossible as getMethods should only return public methods
throw new BuildException(ie);
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t instanceof BuildException) {
throw (BuildException) t;
}
throw new BuildException(t);
}
}

/**
* Creates a named nested element.
*/
public Object createElement(Object element, String elementName)
throws BuildException {
NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
if (nc == null) {
String msg = "Class " + element.getClass() +
" doesn't support the nested \"" + elementName + "\" element";
throw new BuildException(msg);
}
try {
return nc.create(element);
} catch (IllegalAccessException ie) {
// impossible as getMethods should only return public methods
throw new BuildException(ie);
} catch (InstantiationException ine) {
// impossible as getMethods should only return public methods
throw new BuildException(ine);
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t instanceof BuildException) {
throw (BuildException) t;
}
throw new BuildException(t);
}
}

/**
* returns the type of a named nested element.
*/
public Class getElementType(String elementName)
throws BuildException {
Class nt = (Class) nestedTypes.get(elementName);
if (nt == null) {
String msg = "Class " + bean +
" doesn't support the nested \"" + elementName + "\" element";
throw new BuildException(msg);
}
return nt;
}

/**
* returns the type of a named attribute.
*/
public Class getAttributeType(String attributeName)
throws BuildException {
Class at = (Class) attributeTypes.get(attributeName);
if (at == null) {
String msg = "Class " + bean +
" doesn't support the \"" + attributeName + "\" attribute";
throw new BuildException(msg);
}
return at;
}

/**
* Does the introspected class support PCDATA?
*/
public boolean supportsCharacters() {
return addText != null;
}

/**
* Return all attribues supported by the introspected class.
*/
public Enumeration getAttributes() {
return attributeSetters.keys();
}

/**
* Return all nested elements supported by the introspected class.
*/
public Enumeration getNestedElements() {
return nestedTypes.keys();
}

/**
* Create a proper implementation of AttributeSetter for the given
* attribute type.
*/
private AttributeSetter createAttributeSetter(final Method m,
final Class arg) {

// simplest case - setAttribute expects String
if (java.lang.String.class.equals(arg)) {
return new AttributeSetter() {
public void set(Project p, Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new String[] {value});
}
};

// now for the primitive types, use their wrappers
} else if (java.lang.Character.class.equals(arg)
|| java.lang.Character.TYPE.equals(arg)) {
return new AttributeSetter() {
public void set(Project p, Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
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)) {
return new AttributeSetter() {
public void set(Project p, Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent,
new Boolean[] {new Boolean(Project.toBoolean(value))});
}

};

// Class doesn't have a String constructor but a decent factory method
} else if (java.lang.Class.class.equals(arg)) {
return new AttributeSetter() {
public void set(Project p, Object parent, String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
try {
m.invoke(parent, new Class[] {Class.forName(value)});
} catch (ClassNotFoundException ce) {
throw new BuildException(ce);
}
}
};

// resolve relative paths through Project
} else if (java.io.File.class.equals(arg)) {
return new AttributeSetter() {
public void set(Project p, Object parent, String value)
throws InvocationTargetException, IllegalAccessException {
m.invoke(parent, new File[] {p.resolveFile(value)});
}

};

// worst case. look for a public String constructor and use it
} else {

try {
final Constructor c =
arg.getConstructor(new Class[] {java.lang.String.class});

return new AttributeSetter() {
public void set(Project p, Object parent,
String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
try {
m.invoke(parent, new Object[] {c.newInstance(new String[] {value})});
} catch (InstantiationException ie) {
throw new BuildException(ie);
}
}
};
} catch (NoSuchMethodException nme) {
}
}
return null;
}

/**
* extract the name of a property from a method name - subtracting
* a given prefix.
*/
private String getPropertyName(String methodName, String prefix) {
int start = prefix.length();
return methodName.substring(start).toLowerCase();
}

private interface NestedCreator {
public Object create(Object parent)
throws InvocationTargetException, IllegalAccessException, InstantiationException;
}
private interface AttributeSetter {
public void set(Project p, Object parent, String value)
throws InvocationTargetException, IllegalAccessException,
BuildException;
}
}

+ 54
- 79
src/main/org/apache/tools/ant/ProjectHelper.java View File

@@ -54,9 +54,7 @@

package org.apache.tools.ant;

import java.beans.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import org.xml.sax.*;
import org.w3c.dom.*;
@@ -109,10 +107,24 @@ public class ProjectHelper {
catch(SAXParseException exc) {
Location location =
new Location(buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber());
throw new BuildException(exc.getMessage(), exc.getException(), location);

Throwable t = exc.getException();
if (t instanceof BuildException) {
BuildException be = (BuildException) t;
if (be.getLocation() == Location.UNKNOWN_LOCATION) {
be.setLocation(location);
}
throw be;
}
throw new BuildException(exc.getMessage(), t, location);
}
catch(SAXException exc) {
throw new BuildException(exc.getMessage(), exc.getException());
Throwable t = exc.getException();
if (t instanceof BuildException) {
throw (BuildException) t;
}
throw new BuildException(exc.getMessage(), t);
}
catch(FileNotFoundException exc) {
throw new BuildException("File \"" + buildFile.toString() + "\" not found");
@@ -342,15 +354,13 @@ public class ProjectHelper {
String text = new String(buf, start, end).trim();
if (text.length() == 0) return;

IntrospectionHelper ih =
IntrospectionHelper.getHelper(task.getClass());

try {
Method addProp = task.getClass().getMethod("addText", new Class[]{String.class});
Object child = addProp.invoke(task, new Object[] {text});
} catch(NoSuchMethodException exc) {
throw new SAXParseException(task.getClass() + " does not support nested text elements", locator);
} catch(InvocationTargetException exc) {
throw new SAXParseException("Error invoking \"addText\" method", locator, exc);
} catch(IllegalAccessException exc) {
throw new SAXParseException("Unable to access \"addText\" method", locator, exc);
ih.addText(task, text);
} catch (BuildException exc) {
throw new SAXParseException(exc.getMessage(), locator, exc);
}
}

@@ -376,19 +386,28 @@ public class ProjectHelper {

public void init(String propType, AttributeList attrs) throws SAXParseException {
Class targetClass = target.getClass();
String methodName = "create" + Character.toUpperCase(propType.charAt(0)) + propType.substring(1);
IntrospectionHelper ih =
IntrospectionHelper.getHelper(targetClass);

try {
Method addProp = targetClass.getMethod(methodName, new Class[]{});
child = addProp.invoke(target, new Object[] {});
child = ih.createElement(target, propType.toLowerCase());
configure(child, attrs);
} catch(NoSuchMethodException exc) {
throw new SAXParseException(targetClass + " does not support nested " + propType + " properties", locator);
} catch(InvocationTargetException exc) {
throw new SAXParseException(exc.getMessage(), locator);
} catch(IllegalAccessException exc) {
throw new SAXParseException(exc.getMessage(), locator);
} catch (BuildException exc) {
throw new SAXParseException(exc.getMessage(), locator, exc);
}
}

public void characters(char[] buf, int start, int end) throws SAXParseException {
String text = new String(buf, start, end).trim();
if (text.length() == 0) return;

IntrospectionHelper ih =
IntrospectionHelper.getHelper(child.getClass());

try {
ih.addText(child, text);
} catch (BuildException exc) {
throw new SAXParseException(exc.getMessage(), locator, exc);
}
}

@@ -401,70 +420,26 @@ public class ProjectHelper {
if( target instanceof TaskAdapter )
target=((TaskAdapter)target).getProxy();

// XXX
// instead of doing this introspection each time around, I
// should have a helper class to keep this info around for
// each kind of class

Hashtable propertySetters = new Hashtable();
BeanInfo beanInfo;
try {
beanInfo = Introspector.getBeanInfo(target.getClass());
} catch (IntrospectionException ie) {
String msg = "Can't introspect class: " + target.getClass();
throw new BuildException(msg);
}

PropertyDescriptor[] pda = beanInfo.getPropertyDescriptors();
for (int i = 0; i < pda.length; i++) {
PropertyDescriptor pd = pda[i];
String property = pd.getName();
Method setMethod = pd.getWriteMethod();
if (setMethod != null) {

// make sure that there's only 1 param and that it
// takes a String object, all other setMethods need
// to get screened out

Class[] ma =setMethod.getParameterTypes();
if (ma.length == 1) {
Class c = ma[0];
if (c.getName().equals("java.lang.String")) {
propertySetters.put(property, setMethod);
}
}
}
}
IntrospectionHelper ih =
IntrospectionHelper.getHelper(target.getClass());

for (int i = 0; i < attrs.getLength(); i++) {
// reflect these into the target
String value=replaceProperties(attrs.getValue(i),
project.getProperties() );
try {
ih.setAttribute(project, target,
attrs.getName(i).toLowerCase(), value);

Method setMethod = (Method)propertySetters.get(attrs.getName(i));
if (setMethod == null) {
} catch (BuildException be) {
if (attrs.getName(i).equals("id")) {
project.addReference(attrs.getValue(i), target);
continue;
} else {
be.setLocation(new Location(buildFile.toString(),
locator.getLineNumber(),
locator.getColumnNumber()));
throw be;
}

String msg = "Class " + target.getClass() +
" doesn't support the \"" + attrs.getName(i) + "\" property";
throw new BuildException(msg);
}

String value=replaceProperties(attrs.getValue(i), project.getProperties() );
try {
setMethod.invoke(target, new String[] {value});
} catch (IllegalAccessException iae) {
String msg = "Error setting value for attrib: " +
attrs.getName(i);
iae.printStackTrace();
throw new BuildException(msg);
} catch (InvocationTargetException ie) {
String msg = "Error setting value for attrib: " +
attrs.getName(i) + " in " + target.getClass().getName();
ie.printStackTrace();
ie.getTargetException().printStackTrace();
throw new BuildException(msg);
}
}
}


+ 1
- 1
src/main/org/apache/tools/ant/taskdefs/Ant.java View File

@@ -162,7 +162,7 @@ public class Ant extends Task {
}

// XXX replace with createProperty!!
public Task createProperty() {
public Property createProperty() {
Property p=(Property)p1.createTask("property");
p.setUserProperty(true);
properties.addElement( p );


+ 28
- 15
src/main/org/apache/tools/ant/taskdefs/Patch.java View File

@@ -75,29 +75,29 @@ public class Patch extends Exec {
/**
* The file to patch.
*/
public void setOriginalfile(String file) {
originalFile = project.resolveFile(file);
public void setOriginalfile(File file) {
originalFile = file;
}

/**
* The file containing the diff output.
*/
public void setPatchfile(String file) {
patchFile = project.resolveFile(file);
public void setPatchfile(File file) {
patchFile = file;
}

/**
* Shall patch write backups.
*/
public void setBackups(String backups) {
backup = Project.toBoolean(backups);
public void setBackups(boolean backups) {
backup = backups;
}

/**
* Ignore whitespace differences.
*/
public void setIgnorewhitespace(String ignore) {
ignoreWhitespace = Project.toBoolean(ignore);
public void setIgnorewhitespace(boolean ignore) {
ignoreWhitespace = ignore;
}

/**
@@ -106,28 +106,41 @@ public class Patch extends Exec {
*
* <p>patch's <i>-p</i> option.
*/
public void setStrip(String num) {
strip = Integer.parseInt(num);
public void setStrip(int num) throws BuildException {
if (strip < 0) {
throw new BuildException("strip has to be >= 0", location);
}
strip = num;
}

/**
* Work silently unless an error occurs.
*/
public void setQuiet(String q) {
quiet = Project.toBoolean(q);
public void setQuiet(boolean q) {
quiet = q;
}

/**
* Assume patch was created with old and new files swapped.
*/
public void setReverse(String r) {
reverse = Project.toBoolean(r);
public void setReverse(boolean r) {
reverse = r;
}

public final void setCommand(String command) throws BuildException {
throw new BuildException("Cannot set attribute command in patch task",
location);
}

public void execute() throws BuildException {
if (patchFile == null) {
throw new BuildException("patchfile argument is required");
throw new BuildException("patchfile argument is required",
location);
}
if (!patchFile.exists()) {
throw new BuildException("patchfile "+patchFile+" doesn\'t exist",
location);
}
StringBuffer command = new StringBuffer("patch -i "+patchFile+" ");



Loading…
Cancel
Save