Submitted by: Jose Alberto Fernandez <j_a_fernandez@yahoo.com> git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271407 13f79535-47bb-0310-9956-ffa450edef68master
@@ -0,0 +1,2 @@ | |||
build | |||
dist |
@@ -6,6 +6,7 @@ | |||
<property name='build' location='build' /> | |||
<property name='dist' location='dist' /> | |||
<property name='classes' location='${build}/classes' /> | |||
<property name='testcases' location='src/testcases' /> | |||
<property name="debug" value="true" /> | |||
<property name="deprecation" value="false" /> | |||
@@ -19,6 +20,8 @@ | |||
<fileset dir='${orig-classes}'> | |||
<include name='**' /> | |||
<exclude name='org/apache/tools/ant/Project.class' /> | |||
<exclude name='org/apache/tools/ant/ProjectHelper.class' /> | |||
<exclude name='org/apache/tools/ant/IntrospectionHelper.class' /> | |||
<exclude name='org/apache/tools/ant/TaskAdapter.class' /> | |||
<exclude name='org/apache/tools/ant/taskdefs/Ant.class' /> | |||
</fileset> | |||
@@ -54,6 +57,12 @@ | |||
<delete dir='${build}' /> | |||
</target> | |||
<target name='test'> | |||
<ant dir='${testcases}' inheritAll='false'/> | |||
<ant dir='${testcases}' | |||
antfile='${testcases}/case.xml' inheritAll='false'/> | |||
</target> | |||
<target name='cleanall' depends='clean'> | |||
<delete dir='${dist}' /> | |||
</target> |
@@ -0,0 +1,706 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000-2001 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", "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. | |||
* | |||
* 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 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.util.Enumeration; | |||
import java.util.Hashtable; | |||
import java.util.Locale; | |||
/** | |||
* Helper class that collects the methods a task or nested element | |||
* holds to set attributes, create nested elements or hold PCDATA | |||
* elements. | |||
* | |||
* @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a> | |||
*/ | |||
public class IntrospectionHelper implements BuildListener { | |||
/** | |||
* 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; | |||
/** | |||
* Holds methods to store configured nested elements. | |||
*/ | |||
private Hashtable nestedStorers; | |||
/** | |||
* 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(); | |||
nestedStorers = 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(); | |||
// 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]) | |||
) | |||
)) { | |||
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 ("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"); | |||
if (attributeSetters.get(propName) != null) { | |||
if (java.lang.String.class.equals(args[0])) { | |||
/* | |||
Ignore method m, as there is an overloaded | |||
form of this method that takes in a | |||
non-string argument, which gains higher | |||
priority. | |||
*/ | |||
continue; | |||
} | |||
/* | |||
If the argument is not a String, and if there | |||
is an overloaded form of this method already defined, | |||
we just override that with the new one. | |||
This mechanism does not guarantee any specific order | |||
in which the methods will be selected: so any code | |||
that depends on the order in which "set" methods have | |||
been defined, is not guaranteed to be selected in any | |||
particular order. | |||
*/ | |||
} | |||
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[] {}); | |||
} | |||
}); | |||
nestedStorers.remove(propName); | |||
} else if (name.startsWith("addConfigured") | |||
&& java.lang.Void.TYPE.equals(returnType) | |||
&& args.length == 1 | |||
&& !java.lang.String.class.equals(args[0]) | |||
&& !args[0].isArray() | |||
&& !args[0].isPrimitive()) { | |||
try { | |||
final Constructor c = | |||
args[0].getConstructor(new Class[] {}); | |||
String propName = getPropertyName(name, "addConfigured"); | |||
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[] {}); | |||
return o; | |||
} | |||
}); | |||
nestedStorers.put(propName, new NestedStorer() { | |||
public void store(Object parent, Object child) | |||
throws InvocationTargetException, | |||
IllegalAccessException, | |||
InstantiationException { | |||
m.invoke(parent, new Object[] {child}); | |||
} | |||
}); | |||
} catch (NoSuchMethodException nse) { | |||
} | |||
} else if (name.startsWith("add") | |||
&& java.lang.Void.TYPE.equals(returnType) | |||
&& args.length == 1 | |||
&& !java.lang.String.class.equals(args[0]) | |||
&& !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; | |||
} | |||
}); | |||
nestedStorers.remove(name); | |||
} catch (NoSuchMethodException nse) { | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Factory method for helper objects. | |||
*/ | |||
public static synchronized 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 = getElementName(p, element) + | |||
//String msg = "Class " + element.getClass().getName() + | |||
" 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(Project project, Object element, String text) { | |||
if (addText == null) { | |||
// Element doesn't handle text content | |||
if ( text.trim().length() == 0 ) { | |||
// Only whitespace - ignore | |||
return; | |||
} | |||
else { | |||
// Not whitespace - fail | |||
String msg = getElementName(project, element) + | |||
" doesn't support nested text data."; | |||
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(Project project, Object element, String elementName) | |||
throws BuildException { | |||
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); | |||
} | |||
if (nestedElement instanceof ProjectComponent) { | |||
((ProjectComponent) nestedElement).setProject(project); | |||
} | |||
return nestedElement; | |||
} 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); | |||
} | |||
} | |||
/** | |||
* Creates a named nested element. | |||
*/ | |||
public void storeElement(Project project, Object element, Object child, String elementName) | |||
throws BuildException { | |||
if (elementName == null) { | |||
return; | |||
} | |||
NestedStorer ns = (NestedStorer)nestedStorers.get(elementName); | |||
if (ns == null) { | |||
return; | |||
} | |||
try { | |||
ns.store(element, child); | |||
} 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.getName() + | |||
" 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.getName() + | |||
" 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)}); | |||
} | |||
}; | |||
// resolve relative paths through Project | |||
} else if (org.apache.tools.ant.types.Path.class.equals(arg)) { | |||
return new AttributeSetter() { | |||
public void set(Project p, Object parent, String value) | |||
throws InvocationTargetException, IllegalAccessException { | |||
m.invoke(parent, new Path[] {new Path(p, value)}); | |||
} | |||
}; | |||
// EnumeratedAttributes have their own helper class | |||
} else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(arg)) { | |||
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(); | |||
ea.setValue(value); | |||
m.invoke(parent, new EnumeratedAttribute[] {ea}); | |||
} catch (InstantiationException ie) { | |||
throw new BuildException(ie); | |||
} | |||
} | |||
}; | |||
// 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 { | |||
Object attribute = c.newInstance(new String[] {value}); | |||
if (attribute instanceof ProjectComponent) { | |||
((ProjectComponent) attribute).setProject(p); | |||
} | |||
m.invoke(parent, new Object[] {attribute}); | |||
} catch (InstantiationException ie) { | |||
throw new BuildException(ie); | |||
} | |||
} | |||
}; | |||
} catch (NoSuchMethodException nme) { | |||
} | |||
} | |||
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(); | |||
} | |||
/** | |||
* 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(Locale.US); | |||
} | |||
private interface NestedCreator { | |||
Object create(Object parent) | |||
throws InvocationTargetException, IllegalAccessException, InstantiationException; | |||
} | |||
private interface NestedStorer { | |||
void store(Object parent, Object child) | |||
throws InvocationTargetException, IllegalAccessException, InstantiationException; | |||
} | |||
private interface AttributeSetter { | |||
void set(Project p, Object parent, String value) | |||
throws InvocationTargetException, IllegalAccessException, | |||
BuildException; | |||
} | |||
public void buildStarted(BuildEvent event) {} | |||
public void buildFinished(BuildEvent event) { | |||
attributeTypes.clear(); | |||
attributeSetters.clear(); | |||
nestedTypes.clear(); | |||
nestedCreators.clear(); | |||
addText = null; | |||
helpers.clear(); | |||
} | |||
public void targetStarted(BuildEvent event) {} | |||
public void targetFinished(BuildEvent event) {} | |||
public void taskStarted(BuildEvent event) {} | |||
public void taskFinished(BuildEvent event) {} | |||
public void messageLogged(BuildEvent event) {} | |||
} |
@@ -63,12 +63,15 @@ 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; | |||
import org.apache.tools.ant.types.DataTypeAdapterTask; | |||
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.types.Path; | |||
/** | |||
* Central representation of an Ant project. This class defines a | |||
@@ -90,6 +93,9 @@ public class Project { | |||
public final static int MSG_VERBOSE = 3; | |||
public final static int MSG_DEBUG = 4; | |||
public final static String TASK_ROLE = "task"; | |||
public final static String DATATYPE_ROLE = "datatype"; | |||
// private set of constants to represent the state | |||
// of a DFS of the Target dependencies | |||
private final static String VISITING = "VISITING"; | |||
@@ -165,30 +171,26 @@ public class Project { | |||
fileUtils = FileUtils.newFileUtils(); | |||
symbols = new SymbolTable(); | |||
symbols.setProject(this); | |||
loadDefinitions(); | |||
} | |||
/** | |||
* create a new ant project that inherits from caler project | |||
* @param p the calling project | |||
*/ | |||
public Project(Project p) { | |||
private Project(Project p) { | |||
fileUtils = FileUtils.newFileUtils(); | |||
symbols = new SymbolTable(p); | |||
symbols = new SymbolTable(p.getSymbols()); | |||
symbols.setProject(this); | |||
} | |||
/** | |||
* Initialise the project. | |||
* | |||
* This involves setting the default task definitions and loading the | |||
* system properties. | |||
* Loads the core definitions into the Root project. | |||
*/ | |||
public void init() throws BuildException { | |||
setJavaVersionProperty(); | |||
// Initialize simbol table just in case | |||
symbols.addRole("task", TaskContainer.class, TaskAdapter.class); | |||
symbols.addRole("datatype", TaskContainer.class, | |||
private void loadDefinitions() { | |||
// Initialize symbol table just in case | |||
symbols.addRole(TASK_ROLE, TaskContainer.class, TaskAdapter.class); | |||
symbols.addRole(DATATYPE_ROLE, TaskContainer.class, | |||
DataTypeAdapterTask.class); | |||
String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; | |||
@@ -248,7 +250,23 @@ public class Project { | |||
} catch (IOException ioe) { | |||
throw new BuildException("Can't load default datatype list"); | |||
} | |||
} | |||
/** | |||
* Creates a subproject of the current project. | |||
*/ | |||
public Project createSubProject() { | |||
return new Project(this); | |||
} | |||
/** | |||
* Initialise the project. | |||
* | |||
* This involves setting the default task definitions and loading the | |||
* system properties. | |||
*/ | |||
public void init() throws BuildException { | |||
setJavaVersionProperty(); | |||
setSystemProperties(); | |||
} | |||
@@ -275,7 +293,7 @@ public class Project { | |||
/** | |||
* Get the symbols associated with this project. | |||
*/ | |||
public SymbolTable getSymbols() { | |||
private SymbolTable getSymbols() { // Package protected on purpose | |||
return symbols; | |||
} | |||
@@ -618,6 +636,46 @@ public class Project { | |||
} | |||
} | |||
public ClassLoader addToLoader(String loader, Path path) { | |||
return symbols.addToLoader(loader, path); | |||
} | |||
public boolean addRoleDefinition(String role, | |||
Class roleClass, Class adapter) | |||
{ | |||
return symbols.addRole(role, roleClass, adapter); | |||
} | |||
/** | |||
* test for a role name being in use already | |||
* | |||
* @param name the name to test | |||
* @return true if it is a task or a datatype | |||
*/ | |||
public boolean isRoleDefined(String name) { | |||
return (symbols.getRole(name) != null); | |||
} | |||
public void addDefinitionOnRole(String role, | |||
String type, Class clz) | |||
{ | |||
Class old = symbols.add(role, type, clz); | |||
// Special management for Tasks | |||
if (TASK_ROLE.equals(role) && null != old && !old.equals(clz)) { | |||
invalidateCreatedTasks(type); | |||
} | |||
} | |||
/** | |||
* test for a name being in use already on this role | |||
* | |||
* @param name the name to test | |||
* @return true if it is a task or a datatype | |||
*/ | |||
public boolean isDefinedOnRole(String role, String name) { | |||
return (symbols.get(role, name) != null); | |||
} | |||
/** | |||
* add a new task definition, complain if there is an overwrite attempt | |||
* @param taskName name of the task | |||
@@ -627,21 +685,14 @@ public class Project { | |||
*/ | |||
public void addTaskDefinition(String taskName, Class taskClass) | |||
throws BuildException { | |||
Class old = symbols.add("task", taskName, taskClass); | |||
if (null != old && !old.equals(taskClass)) { | |||
invalidateCreatedTasks(taskName); | |||
} | |||
String msg = | |||
" +User task: " + taskName + " " + taskClass.getName(); | |||
log(msg, MSG_DEBUG); | |||
checkTaskClass(taskClass); | |||
addDefinitionOnRole(TASK_ROLE, taskName, taskClass); | |||
} | |||
/** | |||
* Checks a class, whether it is suitable for serving as ant task. | |||
* @throws BuildException and logs as Project.MSG_ERR for | |||
* conditions, that will cause the task execution to fail. | |||
* @deprecated this is done now when added to SymbolTable | |||
*/ | |||
public void checkTaskClass(final Class taskClass) throws BuildException { | |||
if( !Task.class.isAssignableFrom(taskClass) ) { | |||
@@ -653,7 +704,7 @@ public class Project { | |||
* get the current task definition hashtable | |||
*/ | |||
public Hashtable getTaskDefinitions() { | |||
return symbols.getTaskDefinitions(); | |||
return symbols.getDefinitions(TASK_ROLE); | |||
} | |||
/** | |||
@@ -662,18 +713,14 @@ public class Project { | |||
* @param typeClass full datatype classname | |||
*/ | |||
public void addDataTypeDefinition(String typeName, Class typeClass) { | |||
symbols.add("datatype", typeName, typeClass); | |||
String msg = | |||
" +User datatype: " + typeName + " " + typeClass.getName(); | |||
log(msg, MSG_DEBUG); | |||
addDefinitionOnRole(DATATYPE_ROLE, typeName, typeClass); | |||
} | |||
/** | |||
* get the current task definition hashtable | |||
*/ | |||
public Hashtable getDataTypeDefinitions() { | |||
return symbols.getDataTypeDefinitions(); | |||
return symbols.getDefinitions(DATATYPE_ROLE); | |||
} | |||
/** | |||
@@ -701,7 +748,7 @@ public class Project { | |||
* in the project. | |||
* @see Project#addOrReplaceTarget to replace existing Targets. | |||
*/ | |||
public void addTarget(String targetName, Target target) | |||
public void addTarget(String targetName, Target target) | |||
throws BuildException { | |||
if (targets.get(targetName) != null) { | |||
throw new BuildException("Duplicate target: `"+targetName+"'"); | |||
@@ -737,6 +784,88 @@ public class Project { | |||
return targets; | |||
} | |||
/** | |||
* Create a new element instance on a Role | |||
* @param role name of the role to use | |||
* @param type name of the element to create | |||
* @return null if element unknown on this role | |||
*/ | |||
public Object createForRole(String role, String type) { | |||
SymbolTable.Factory f = symbols.get(role, type); | |||
if (f == null) return null; | |||
try { | |||
Object o = f.create(this); | |||
// Do special book keeping for ProjectComponents | |||
if ( o instanceof ProjectComponent ) { | |||
((ProjectComponent)o).setProject(this); | |||
if (o instanceof Task) { | |||
Task task = (Task) o; | |||
task.setTaskType(type); | |||
// set default value, can be changed by the user | |||
task.setTaskName(type); | |||
addCreatedTask(type, task); | |||
} | |||
} | |||
String msg = " +" + role + ": " + type; | |||
log (msg, MSG_DEBUG); | |||
return o; | |||
} | |||
catch (Throwable t) { | |||
String msg = "Could not create " + role + " of type: " | |||
+ type + " due to " + t; | |||
throw new BuildException(msg, t); | |||
} | |||
} | |||
/** | |||
* | |||
*/ | |||
public Object createInRole(Object container, String type) { | |||
Class clz = container.getClass(); | |||
String roles[] = symbols.findRoles(clz); | |||
Object theOne = null; | |||
Method add = null; | |||
for(int i = 0; i < roles.length; i++) { | |||
Object o = createForRole(roles[i], type); | |||
if (o != null) { | |||
if (theOne != null) { | |||
String msg = "Element " + type + | |||
" is ambiguous for container " + clz.getName(); | |||
if (theOne instanceof RoleAdapter) | |||
theOne = ((RoleAdapter)theOne).getProxy(); | |||
if (o instanceof RoleAdapter) | |||
o = ((RoleAdapter)o).getProxy(); | |||
log(msg, MSG_ERR); | |||
log("cannot distinguish between " + | |||
theOne.getClass().getName() + | |||
" and " + o.getClass().getName(), MSG_ERR); | |||
throw new BuildException(msg); | |||
} | |||
theOne = o; | |||
add = symbols.getRole(roles[i]).getInterfaceMethod(); | |||
} | |||
} | |||
if (theOne != null) { | |||
try { | |||
add.invoke(container, new Object[]{theOne}); | |||
} | |||
catch(InvocationTargetException ite) { | |||
if (ite.getTargetException() instanceof BuildException) { | |||
throw (BuildException)ite.getTargetException(); | |||
} | |||
throw new BuildException(ite.getTargetException()); | |||
} | |||
catch(Exception e) { | |||
throw new BuildException(e); | |||
} | |||
} | |||
return theOne; | |||
} | |||
/** | |||
* create a new task instance | |||
* @param taskType name of the task | |||
@@ -744,39 +873,7 @@ public class Project { | |||
* @return null if the task name is unknown | |||
*/ | |||
public Task createTask(String taskType) throws BuildException { | |||
Class c = symbols.get("task", taskType); | |||
if (c == null) { | |||
return null; | |||
} | |||
try { | |||
Object o = c.newInstance(); | |||
Task task = null; | |||
if( o instanceof Task ) { | |||
task=(Task)o; | |||
} else { | |||
// "Generic" Bean - use the setter pattern | |||
// and an Adapter | |||
TaskAdapter taskA=new TaskAdapter(); | |||
taskA.setProxy( o ); | |||
task=taskA; | |||
} | |||
task.setProject(this); | |||
task.setTaskType(taskType); | |||
// set default value, can be changed by the user | |||
task.setTaskName(taskType); | |||
String msg = " +Task: " + taskType; | |||
log (msg, MSG_DEBUG); | |||
addCreatedTask(taskType, task); | |||
return task; | |||
} catch (Throwable t) { | |||
String msg = "Could not create task of type: " | |||
+ taskType + " due to " + t; | |||
throw new BuildException(msg, t); | |||
} | |||
return (Task) createForRole(TASK_ROLE, taskType); | |||
} | |||
/** | |||
@@ -820,47 +917,11 @@ public class Project { | |||
* @return null if the datatype name is unknown | |||
*/ | |||
public Object createDataType(String typeName) throws BuildException { | |||
Class c = symbols.get("datatype", typeName); | |||
if (c == null) { | |||
return null; | |||
} | |||
try { | |||
java.lang.reflect.Constructor ctor = null; | |||
boolean noArg = false; | |||
// DataType can have a "no arg" constructor or take a single | |||
// Project argument. | |||
try { | |||
ctor = c.getConstructor(new Class[0]); | |||
noArg = true; | |||
} catch (NoSuchMethodException nse) { | |||
ctor = c.getConstructor(new Class[] {Project.class}); | |||
noArg = false; | |||
} | |||
Object o = null; | |||
if (noArg) { | |||
o = ctor.newInstance(new Object[0]); | |||
} else { | |||
o = ctor.newInstance(new Object[] {this}); | |||
} | |||
if (o instanceof ProjectComponent) { | |||
((ProjectComponent)o).setProject(this); | |||
} | |||
String msg = " +DataType: " + typeName; | |||
log (msg, MSG_DEBUG); | |||
return o; | |||
} catch (java.lang.reflect.InvocationTargetException ite) { | |||
Throwable t = ite.getTargetException(); | |||
String msg = "Could not create datatype of type: " | |||
+ typeName + " due to " + t; | |||
throw new BuildException(msg, t); | |||
} catch (Throwable t) { | |||
String msg = "Could not create datatype of type: " | |||
+ typeName + " due to " + t; | |||
throw new BuildException(msg, t); | |||
} | |||
// This is to make the function backward compatible | |||
// Since we know if it returning an adapter for it | |||
DataTypeAdapterTask dt = | |||
(DataTypeAdapterTask) createForRole(DATATYPE_ROLE, typeName); | |||
return (dt != null? dt.getProxy() : null); | |||
} | |||
/** | |||
@@ -1227,7 +1288,10 @@ public class Project { | |||
} | |||
public void addReference(String name, Object value) { | |||
if (null != references.get(name)) { | |||
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, | |||
MSG_WARN); | |||
} | |||
@@ -0,0 +1,797 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 2000-2002 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", "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. | |||
* | |||
* 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.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.FileNotFoundException; | |||
import java.io.IOException; | |||
import java.util.Hashtable; | |||
import java.util.Vector; | |||
import java.util.Enumeration; | |||
import java.util.Locale; | |||
import org.xml.sax.Locator; | |||
import org.xml.sax.InputSource; | |||
import org.xml.sax.HandlerBase; | |||
import org.xml.sax.SAXParseException; | |||
import org.xml.sax.SAXException; | |||
import org.xml.sax.DocumentHandler; | |||
import org.xml.sax.AttributeList; | |||
import javax.xml.parsers.SAXParserFactory; | |||
import javax.xml.parsers.SAXParser; | |||
import javax.xml.parsers.ParserConfigurationException; | |||
/** | |||
* Configures a Project (complete with Targets and Tasks) based on | |||
* a XML build file. | |||
* | |||
* @author duncan@x180.com | |||
*/ | |||
public class ProjectHelper { | |||
private static SAXParserFactory parserFactory = null; | |||
private org.xml.sax.Parser parser; | |||
private Project project; | |||
private File buildFile; | |||
private File buildFileParent; | |||
private Locator locator; | |||
/** | |||
* Configures the Project with the contents of the specified XML file. | |||
*/ | |||
public static void configureProject(Project project, File buildFile) throws BuildException { | |||
new ProjectHelper(project, buildFile).parse(); | |||
} | |||
/** | |||
* Constructs a new Ant parser for the specified XML file. | |||
*/ | |||
private ProjectHelper(Project project, File buildFile) { | |||
this.project = project; | |||
this.buildFile = new File(buildFile.getAbsolutePath()); | |||
buildFileParent = new File(this.buildFile.getParent()); | |||
} | |||
/** | |||
* Parses the project file. | |||
*/ | |||
private void parse() throws BuildException { | |||
FileInputStream inputStream = null; | |||
InputSource inputSource = null; | |||
try { | |||
SAXParser saxParser = getParserFactory().newSAXParser(); | |||
parser = saxParser.getParser(); | |||
String uri = "file:" + buildFile.getAbsolutePath().replace('\\', '/'); | |||
for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#')) { | |||
uri = uri.substring(0, index) + "%23" + uri.substring(index+1); | |||
} | |||
inputStream = new FileInputStream(buildFile); | |||
inputSource = new InputSource(inputStream); | |||
inputSource.setSystemId(uri); | |||
project.log("parsing buildfile " + buildFile + " with URI = " + uri, Project.MSG_VERBOSE); | |||
saxParser.parse(inputSource, new RootHandler()); | |||
} | |||
catch(ParserConfigurationException exc) { | |||
throw new BuildException("Parser has not been configured correctly", exc); | |||
} | |||
catch(SAXParseException exc) { | |||
Location location = | |||
new Location(buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber()); | |||
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) { | |||
Throwable t = exc.getException(); | |||
if (t instanceof BuildException) { | |||
throw (BuildException) t; | |||
} | |||
throw new BuildException(exc.getMessage(), t); | |||
} | |||
catch(FileNotFoundException exc) { | |||
throw new BuildException(exc); | |||
} | |||
catch(IOException exc) { | |||
throw new BuildException("Error reading project file", exc); | |||
} | |||
finally { | |||
if (inputStream != null) { | |||
try { | |||
inputStream.close(); | |||
} | |||
catch (IOException ioe) { | |||
// ignore this | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* The common superclass for all sax event handlers in Ant. Basically | |||
* throws an exception in each method, so subclasses should override | |||
* what they can handle. | |||
* | |||
* Each type of xml element (task, target, etc) in ant will | |||
* have its own subclass of AbstractHandler. | |||
* | |||
* In the constructor, this class takes over the handling of sax | |||
* events from the parent handler, and returns | |||
* control back to the parent in the endElement method. | |||
*/ | |||
private class AbstractHandler extends HandlerBase { | |||
protected DocumentHandler parentHandler; | |||
public AbstractHandler(DocumentHandler parentHandler) { | |||
this.parentHandler = parentHandler; | |||
// Start handling SAX events | |||
parser.setDocumentHandler(this); | |||
} | |||
public void startElement(String tag, AttributeList attrs) throws SAXParseException { | |||
throw new SAXParseException("Unexpected element \"" + tag + "\"", locator); | |||
} | |||
public void characters(char[] buf, int start, int end) throws SAXParseException { | |||
String s = new String(buf, start, end).trim(); | |||
if (s.length() > 0) { | |||
throw new SAXParseException("Unexpected text \"" + s + "\"", locator); | |||
} | |||
} | |||
/** | |||
* Called when this element and all elements nested into it have been | |||
* handled. | |||
*/ | |||
protected void finished() {} | |||
public void endElement(String name) throws SAXException { | |||
finished(); | |||
// Let parent resume handling SAX events | |||
parser.setDocumentHandler(parentHandler); | |||
} | |||
} | |||
/** | |||
* Handler for the root element. It's only child must be the "project" element. | |||
*/ | |||
private class RootHandler extends HandlerBase { | |||
/** | |||
* resolve file: URIs as relative to the build file. | |||
*/ | |||
public InputSource resolveEntity(String publicId, | |||
String systemId) { | |||
project.log("resolving systemId: " + systemId, Project.MSG_VERBOSE); | |||
if (systemId.startsWith("file:")) { | |||
String path = systemId.substring(5); | |||
int index = path.indexOf("file:"); | |||
// we only have to handle these for backward compatibility | |||
// since they are in the FAQ. | |||
while (index != -1) { | |||
path = path.substring(0, index) + path.substring(index + 5); | |||
index = path.indexOf("file:"); | |||
} | |||
String entitySystemId = path; | |||
index = path.indexOf("%23"); | |||
// convert these to # | |||
while (index != -1) { | |||
path = path.substring(0, index) + "#" + path.substring(index + 3); | |||
index = path.indexOf("%23"); | |||
} | |||
File file = new File(path); | |||
if (!file.isAbsolute()) { | |||
file = new File(buildFileParent, path); | |||
} | |||
try { | |||
InputSource inputSource = new InputSource(new FileInputStream(file)); | |||
inputSource.setSystemId("file:" + entitySystemId); | |||
return inputSource; | |||
} catch (FileNotFoundException fne) { | |||
project.log(file.getAbsolutePath()+" could not be found", | |||
Project.MSG_WARN); | |||
} | |||
} | |||
// use default if not file or file not found | |||
return null; | |||
} | |||
public void startElement(String tag, AttributeList attrs) throws SAXParseException { | |||
if (tag.equals("project")) { | |||
new ProjectHandler(this).init(tag, attrs); | |||
} else { | |||
throw new SAXParseException("Config file is not of expected XML type", locator); | |||
} | |||
} | |||
public void setDocumentLocator(Locator locator) { | |||
ProjectHelper.this.locator = locator; | |||
} | |||
} | |||
/** | |||
* Handler for the top level "project" element. | |||
*/ | |||
private class ProjectHandler extends AbstractHandler { | |||
public ProjectHandler(DocumentHandler parentHandler) { | |||
super(parentHandler); | |||
} | |||
public void init(String tag, AttributeList attrs) throws SAXParseException { | |||
String def = null; | |||
String name = null; | |||
String id = null; | |||
String baseDir = null; | |||
for (int i = 0; i < attrs.getLength(); i++) { | |||
String key = attrs.getName(i); | |||
String value = attrs.getValue(i); | |||
if (key.equals("default")) { | |||
def = value; | |||
} else if (key.equals("name")) { | |||
name = value; | |||
} else if (key.equals("id")) { | |||
id = value; | |||
} else if (key.equals("basedir")) { | |||
baseDir = value; | |||
} else { | |||
throw new SAXParseException("Unexpected attribute \"" + attrs.getName(i) + "\"", locator); | |||
} | |||
} | |||
if (def == null) { | |||
throw new SAXParseException("The default attribute of project is required", | |||
locator); | |||
} | |||
project.setDefaultTarget(def); | |||
if (name != null) { | |||
project.setName(name); | |||
project.addReference(name, project); | |||
} | |||
if (id != null) { | |||
project.addReference(id, project); | |||
} | |||
if (project.getProperty("basedir") != null) { | |||
project.setBasedir(project.getProperty("basedir")); | |||
} else { | |||
if (baseDir == null) { | |||
project.setBasedir(buildFileParent.getAbsolutePath()); | |||
} else { | |||
// check whether the user has specified an absolute path | |||
if ((new File(baseDir)).isAbsolute()) { | |||
project.setBasedir(baseDir); | |||
} else { | |||
project.setBaseDir(project.resolveFile(baseDir, buildFileParent)); | |||
} | |||
} | |||
} | |||
} | |||
public void startElement(String name, AttributeList attrs) throws SAXParseException { | |||
if (name.equals("taskdef")) { | |||
handleTopTask(name, attrs); | |||
} else if (name.equals("typedef")) { | |||
handleTopTask(name, attrs); | |||
} else if (name.equals("antlib")) { | |||
handleTopTask(name, attrs); | |||
} else if (name.equals("property")) { | |||
handleTopTask(name, attrs); | |||
} else if (name.equals("target")) { | |||
handleTarget(name, attrs); | |||
} else if (project.isDefinedOnRole(Project.DATATYPE_ROLE, name)) { | |||
handleTopTask(name, attrs); | |||
} else { | |||
throw new SAXParseException("Unexpected element \"" + name + "\"", locator); | |||
} | |||
} | |||
private void handleTopTask(String name, AttributeList attrs) | |||
throws SAXParseException { | |||
InmediateTarget target = new InmediateTarget(name); | |||
(new TaskHandler(this, target, null, target)).init(name, attrs); | |||
} | |||
private void handleTarget(String tag, AttributeList attrs) throws SAXParseException { | |||
new TargetHandler(this).init(tag, attrs); | |||
} | |||
} | |||
/** | |||
* Handler for "target" elements. | |||
*/ | |||
private class TargetHandler extends AbstractHandler { | |||
private Target target; | |||
public TargetHandler(DocumentHandler parentHandler) { | |||
super(parentHandler); | |||
} | |||
public void init(String tag, AttributeList attrs) throws SAXParseException { | |||
String name = null; | |||
String depends = ""; | |||
String ifCond = null; | |||
String unlessCond = null; | |||
String id = null; | |||
String description = null; | |||
for (int i = 0; i < attrs.getLength(); i++) { | |||
String key = attrs.getName(i); | |||
String value = attrs.getValue(i); | |||
if (key.equals("name")) { | |||
name = value; | |||
} else if (key.equals("depends")) { | |||
depends = value; | |||
} else if (key.equals("if")) { | |||
ifCond = value; | |||
} else if (key.equals("unless")) { | |||
unlessCond = value; | |||
} else if (key.equals("id")) { | |||
id = value; | |||
} else if (key.equals("description")) { | |||
description = value; | |||
} else { | |||
throw new SAXParseException("Unexpected attribute \"" + key + "\"", locator); | |||
} | |||
} | |||
if (name == null) { | |||
throw new SAXParseException("target element appears without a name attribute", locator); | |||
} | |||
target = new Target(); | |||
target.setName(name); | |||
target.setIf(ifCond); | |||
target.setUnless(unlessCond); | |||
target.setDescription(description); | |||
project.addTarget(name, target); | |||
if (id != null && !id.equals("")) { | |||
project.addReference(id, target); | |||
} | |||
// take care of dependencies | |||
if (depends.length() > 0) { | |||
target.setDepends(depends); | |||
} | |||
} | |||
public void startElement(String name, AttributeList attrs) throws SAXParseException { | |||
new TaskHandler(this, target, null, target).init(name, attrs); | |||
} | |||
} | |||
/** | |||
* Handler for all task elements. | |||
*/ | |||
private class TaskHandler extends AbstractHandler { | |||
private Target target; | |||
private TaskContainer container; | |||
private Task task; | |||
private RuntimeConfigurable parentWrapper; | |||
private RuntimeConfigurable wrapper = null; | |||
public TaskHandler(DocumentHandler parentHandler, TaskContainer container, RuntimeConfigurable parentWrapper, Target target) { | |||
super(parentHandler); | |||
this.container = container; | |||
this.parentWrapper = parentWrapper; | |||
this.target = target; | |||
} | |||
public void init(String tag, AttributeList attrs) throws SAXParseException { | |||
try { | |||
task = (Task)project.createInRole(container, tag); | |||
} catch (BuildException e) { | |||
// swallow here, will be thrown again in | |||
// UnknownElement.maybeConfigure if the problem persists. | |||
} | |||
if (task == null) { | |||
task = new UnknownElement(tag); | |||
task.setProject(project); | |||
task.setTaskType(tag); | |||
task.setTaskName(tag); | |||
container.addTask(task); | |||
} | |||
task.setLocation(new Location(buildFile.toString(), | |||
locator.getLineNumber(), | |||
locator.getColumnNumber())); | |||
configureId(task, attrs); | |||
task.setOwningTarget(target); | |||
task.init(); | |||
wrapper = task.getRuntimeConfigurableWrapper(); | |||
wrapper.setAttributes(attrs); | |||
if (parentWrapper != null) { | |||
parentWrapper.addChild(wrapper); | |||
} | |||
} | |||
protected void finished() { | |||
if (container instanceof InmediateTarget) { | |||
((InmediateTarget)container).execute(); | |||
} | |||
} | |||
public void characters(char[] buf, int start, int end) throws SAXParseException { | |||
if (wrapper == null) { | |||
try { | |||
addText(project, task, buf, start, end); | |||
} catch (BuildException exc) { | |||
throw new SAXParseException(exc.getMessage(), locator, exc); | |||
} | |||
} else { | |||
wrapper.addText(buf, start, end); | |||
} | |||
} | |||
public void startElement(String name, AttributeList attrs) throws SAXParseException { | |||
if (task instanceof TaskContainer) { | |||
// task can contain other tasks - no other nested elements possible | |||
new TaskHandler(this, (TaskContainer)task, wrapper, target).init(name, attrs); | |||
} | |||
else { | |||
new NestedElementHandler(this, task, wrapper, target).init(name, attrs); | |||
} | |||
} | |||
} | |||
/** | |||
* Handler for all nested properties. | |||
*/ | |||
private class NestedElementHandler extends AbstractHandler { | |||
private Object parent; | |||
private Object child; | |||
private RuntimeConfigurable parentWrapper; | |||
private RuntimeConfigurable childWrapper = null; | |||
private Target target; | |||
public NestedElementHandler(DocumentHandler parentHandler, | |||
Object parent, | |||
RuntimeConfigurable parentWrapper, | |||
Target target) { | |||
super(parentHandler); | |||
if (parent instanceof RoleAdapter) { | |||
this.parent = ((RoleAdapter) parent).getProxy(); | |||
} else { | |||
this.parent = parent; | |||
} | |||
this.parentWrapper = parentWrapper; | |||
this.target = target; | |||
} | |||
public void init(String propType, AttributeList attrs) throws SAXParseException { | |||
Class parentClass = parent.getClass(); | |||
IntrospectionHelper ih = | |||
IntrospectionHelper.getHelper(parentClass); | |||
try { | |||
String elementName = propType.toLowerCase(Locale.US); | |||
if (parent instanceof UnknownElement) { | |||
UnknownElement uc = new UnknownElement(elementName); | |||
uc.setProject(project); | |||
((UnknownElement) parent).addChild(uc); | |||
// Set this parameters just in case is a Task | |||
uc.setTaskType(elementName); | |||
uc.setTaskName(elementName); | |||
child = uc; | |||
} else { | |||
child = ih.createElement(project, parent, elementName); | |||
} | |||
configureId(child, attrs); | |||
if (parentWrapper != null) { | |||
childWrapper = new RuntimeConfigurable(child, propType); | |||
childWrapper.setAttributes(attrs); | |||
parentWrapper.addChild(childWrapper); | |||
} else { | |||
configure(child, attrs, project); | |||
ih.storeElement(project, parent, child, elementName); | |||
} | |||
} catch (BuildException exc) { | |||
throw new SAXParseException(exc.getMessage(), locator, exc); | |||
} | |||
} | |||
public void characters(char[] buf, int start, int end) throws SAXParseException { | |||
if (parentWrapper == null) { | |||
try { | |||
addText(project, child, buf, start, end); | |||
} catch (BuildException exc) { | |||
throw new SAXParseException(exc.getMessage(), locator, exc); | |||
} | |||
} else { | |||
childWrapper.addText(buf, start, end); | |||
} | |||
} | |||
public void startElement(String name, AttributeList attrs) throws SAXParseException { | |||
if (child instanceof TaskContainer) { | |||
// taskcontainer nested element can contain other tasks - no other | |||
// nested elements possible | |||
new TaskHandler(this, (TaskContainer)child, childWrapper, target).init(name, attrs); | |||
} | |||
else { | |||
new NestedElementHandler(this, child, childWrapper, target).init(name, attrs); | |||
} | |||
} | |||
} | |||
/** | |||
* Special target type for top level Tasks and Datatypes. | |||
* This will allow eliminating special cases. | |||
*/ | |||
private class InmediateTarget extends Target { | |||
/** | |||
* Create a target for a top level task or datatype. | |||
* @param name the name of the task to be run on this target. | |||
*/ | |||
InmediateTarget(String name) { | |||
super(); | |||
setProject(project); | |||
setName("Top level " + name); | |||
} | |||
} | |||
public static void configure(Object target, AttributeList attrs, | |||
Project project) throws BuildException { | |||
if( target instanceof RoleAdapter ) { | |||
target=((RoleAdapter)target).getProxy(); | |||
} | |||
IntrospectionHelper ih = | |||
IntrospectionHelper.getHelper(target.getClass()); | |||
project.addBuildListener(ih); | |||
for (int i = 0; i < attrs.getLength(); i++) { | |||
// reflect these into the target | |||
String value=replaceProperties(project, attrs.getValue(i), | |||
project.getProperties() ); | |||
try { | |||
ih.setAttribute(project, target, | |||
attrs.getName(i).toLowerCase(Locale.US), value); | |||
} catch (BuildException be) { | |||
// id attribute must be set externally | |||
if (!attrs.getName(i).equals("id")) { | |||
throw be; | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Adds the content of #PCDATA sections to an element. | |||
*/ | |||
public static void addText(Project project, Object target, char[] buf, int start, int end) | |||
throws BuildException { | |||
addText(project, target, new String(buf, start, end)); | |||
} | |||
/** | |||
* Adds the content of #PCDATA sections to an element. | |||
*/ | |||
public static void addText(Project project, Object target, String text) | |||
throws BuildException { | |||
if (text == null ) { | |||
return; | |||
} | |||
if(target instanceof RoleAdapter) { | |||
target = ((RoleAdapter) target).getProxy(); | |||
} | |||
IntrospectionHelper.getHelper(target.getClass()).addText(project, target, text); | |||
} | |||
/** | |||
* Stores a configured child element into its parent object | |||
*/ | |||
public static void storeChild(Project project, Object parent, Object child, String tag) { | |||
IntrospectionHelper ih = IntrospectionHelper.getHelper(parent.getClass()); | |||
ih.storeElement(project, parent, child, tag); | |||
} | |||
/** | |||
* Replace ${} style constructions in the given value with the string value of | |||
* the corresponding data types. | |||
* | |||
* @param value the string to be scanned for property references. | |||
* @since 1.5 | |||
*/ | |||
public static String replaceProperties(Project project, String value) | |||
throws BuildException { | |||
return project.replaceProperties(value); | |||
} | |||
/** | |||
* Replace ${} style constructions in the given value with the string value of | |||
* the corresponding data types. | |||
* | |||
* @param value the string to be scanned for property references. | |||
*/ | |||
public static String replaceProperties(Project project, String value, Hashtable keys) | |||
throws BuildException { | |||
if (value == null) { | |||
return null; | |||
} | |||
Vector fragments = new Vector(); | |||
Vector propertyRefs = new Vector(); | |||
parsePropertyString(value, fragments, propertyRefs); | |||
StringBuffer sb = new StringBuffer(); | |||
Enumeration i = fragments.elements(); | |||
Enumeration j = propertyRefs.elements(); | |||
while (i.hasMoreElements()) { | |||
String fragment = (String)i.nextElement(); | |||
if (fragment == null) { | |||
String propertyName = (String)j.nextElement(); | |||
if (!keys.containsKey(propertyName)) { | |||
project.log("Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE); | |||
} | |||
fragment = (keys.containsKey(propertyName)) ? (String) keys.get(propertyName) | |||
: "${" + propertyName + "}"; | |||
} | |||
sb.append(fragment); | |||
} | |||
return sb.toString(); | |||
} | |||
/** | |||
* This method will parse a string containing ${value} style | |||
* property values into two lists. The first list is a collection | |||
* of text fragments, while the other is a set of string property names | |||
* null entries in the first list indicate a property reference from the | |||
* second list. | |||
*/ | |||
public static void parsePropertyString(String value, Vector fragments, Vector propertyRefs) | |||
throws BuildException { | |||
int prev = 0; | |||
int pos; | |||
while ((pos = value.indexOf("$", prev)) >= 0) { | |||
if (pos > 0) { | |||
fragments.addElement(value.substring(prev, pos)); | |||
} | |||
if( pos == (value.length() - 1)) { | |||
fragments.addElement("$"); | |||
prev = pos + 1; | |||
} | |||
else if (value.charAt(pos + 1) != '{' ) { | |||
fragments.addElement(value.substring(pos + 1, pos + 2)); | |||
prev = pos + 2; | |||
} else { | |||
int endName = value.indexOf('}', pos); | |||
if (endName < 0) { | |||
throw new BuildException("Syntax error in property: " | |||
+ value ); | |||
} | |||
String propertyName = value.substring(pos + 2, endName); | |||
fragments.addElement(null); | |||
propertyRefs.addElement(propertyName); | |||
prev = endName + 1; | |||
} | |||
} | |||
if (prev < value.length()) { | |||
fragments.addElement(value.substring(prev)); | |||
} | |||
} | |||
private static SAXParserFactory getParserFactory() { | |||
if (parserFactory == null) { | |||
parserFactory = SAXParserFactory.newInstance(); | |||
} | |||
return parserFactory; | |||
} | |||
/** | |||
* Scan AttributeList for the id attribute and maybe add a | |||
* reference to project. | |||
* | |||
* <p>Moved out of {@link #configure configure} to make it happen | |||
* at parser time.</p> | |||
*/ | |||
private void configureId(Object target, AttributeList attr) { | |||
String id = attr.getValue("id"); | |||
if (id != null) { | |||
if( target instanceof RoleAdapter ) { | |||
((RoleAdapter)target).setId(id); | |||
} | |||
project.addReference(id, target); | |||
} | |||
} | |||
} |
@@ -55,6 +55,11 @@ package org.apache.tools.ant; | |||
public interface RoleAdapter { | |||
/** | |||
* Obtain the id in case it is needed. | |||
*/ | |||
public void setId(String id); | |||
/** | |||
* Set the object being adapted. | |||
* @param o the object being adapted | |||
@@ -54,6 +54,7 @@ | |||
package org.apache.tools.ant; | |||
import java.lang.reflect.InvocationTargetException; | |||
import java.lang.reflect.Constructor; | |||
import java.lang.reflect.Method; | |||
import java.lang.reflect.Modifier; | |||
import java.util.*; | |||
@@ -96,8 +97,8 @@ public class SymbolTable { | |||
* from that defined in the calling Project. | |||
* @param p the calling project | |||
*/ | |||
public SymbolTable(Project p) { | |||
parentTable = p.getSymbols(); | |||
public SymbolTable(SymbolTable st) { | |||
parentTable = st; | |||
} | |||
/** | |||
@@ -108,6 +109,54 @@ public class SymbolTable { | |||
this.project = p; | |||
} | |||
/** | |||
* Get the specified loader for the project. | |||
* @param name the name of the loader | |||
* @return the corresponding ANT classloader | |||
*/ | |||
private AntClassLoader getLoader(String name) { | |||
AntClassLoader cl = (AntClassLoader) loaders.get(name); | |||
if (cl == null && parentTable != null) { | |||
return parentTable.getLoader(name); | |||
} | |||
return cl; | |||
} | |||
/** | |||
* Add the specified class-path to a loader. | |||
* If the loader is defined in an ancestor project then a new | |||
* classloader inheritin from the one already existing | |||
* will be created, otherwise the path willbe added to the existing | |||
* ClassLoader. | |||
* @param name the name of the loader to use. | |||
* @param clspath the path to be added to the classloader | |||
*/ | |||
public ClassLoader addToLoader(String name, Path clspath) { | |||
// Find if the loader is already defined in the current project | |||
AntClassLoader cl = (AntClassLoader) loaders.get(name); | |||
if (cl == null) { | |||
// Is it inherited from the calling project | |||
if (parentTable != null) { | |||
cl = parentTable.getLoader(name); | |||
} | |||
cl = new AntClassLoader(cl, project, clspath, true); | |||
loaders.put(name, cl); | |||
} | |||
else { | |||
// Add additional path to the existing definition | |||
String[] pathElements = clspath.list(); | |||
for (int i = 0; i < pathElements.length; ++i) { | |||
try { | |||
cl.addPathElement(pathElements[i]); | |||
} | |||
catch (BuildException e) { | |||
// ignore path elements invalid relative to the project | |||
} | |||
} | |||
} | |||
return cl; | |||
} | |||
/** | |||
* Find all the roles supported by a Class | |||
* on this symbol table. | |||
@@ -133,13 +182,13 @@ public class SymbolTable { | |||
list.addElement(role); | |||
} | |||
} | |||
if (parentTable != null) findRoles(clz, list); | |||
if (parentTable != null) parentTable.findRoles(clz, list); | |||
} | |||
/** | |||
* Get the Role definition | |||
* @param role the name of the role | |||
* @return the method used to support objects on this role | |||
* @return the Role description | |||
*/ | |||
public Role getRole(String role) { | |||
Role r = (Role) roles.get(role); | |||
@@ -171,112 +220,6 @@ public class SymbolTable { | |||
return (old != null); | |||
} | |||
/** | |||
* Verify if the interface is valid. | |||
* @param clz the interface to validate | |||
* @return the method defined by the interface | |||
*/ | |||
private Method validInterface(Class clz) { | |||
Method m[] = clz.getDeclaredMethods(); | |||
if (m.length == 1 | |||
&& java.lang.Void.TYPE.equals(m[0].getReturnType())) { | |||
Class args[] = m[0].getParameterTypes(); | |||
if (args.length == 1 | |||
&& !java.lang.String.class.equals(args[0]) | |||
&& !args[0].isArray() | |||
&& !args[0].isPrimitive()) { | |||
return m[0]; | |||
} | |||
else { | |||
throw new BuildException("Invalid role interface method in: " | |||
+ clz.getName()); | |||
} | |||
} | |||
else { | |||
throw new BuildException("More than one method on role interface"); | |||
} | |||
} | |||
/** | |||
* Verify if the adapter is valid with respect to the interface. | |||
* @param clz the class adapter to validate | |||
* @param mtd the method whose only argument must match | |||
* @return the static method to use for validating adaptees | |||
*/ | |||
private Method validAdapter(Class clz, Method mtd) { | |||
if (clz == null) return null; | |||
checkClass(clz); | |||
if (!mtd.getParameterTypes()[0].isAssignableFrom(clz)) { | |||
String msg = "Adapter " + clz.getName() + | |||
" is incompatible with role interface " + | |||
mtd.getDeclaringClass().getName(); | |||
throw new BuildException(msg); | |||
} | |||
String msg = "Class " + clz.getName() + " is not an adapter: "; | |||
if (!RoleAdapter.class.isAssignableFrom(clz)) { | |||
throw new BuildException(msg + "does not implement RoleAdapter"); | |||
} | |||
try { | |||
Method chk = clz.getMethod("checkClass", CHECK_ADAPTER_PARAMS); | |||
if (!Modifier.isStatic(chk.getModifiers())) { | |||
throw new BuildException(msg + "checkClass() is not static"); | |||
} | |||
return chk; | |||
} | |||
catch(NoSuchMethodException nme){ | |||
throw new BuildException(msg + "checkClass() not found", nme); | |||
} | |||
} | |||
/** | |||
* Get the specified loader for the project. | |||
* @param name the name of the loader | |||
* @return the corresponding ANT classloader | |||
*/ | |||
private AntClassLoader getLoader(String name) { | |||
AntClassLoader cl = (AntClassLoader) loaders.get(name); | |||
if (cl == null && parentTable != null) { | |||
return parentTable.getLoader(name); | |||
} | |||
return cl; | |||
} | |||
/** | |||
* Add the specified class-path to a loader. | |||
* If the loader is defined in an ancestor project then a new | |||
* classloader inheritin from the one already existing | |||
* will be created, otherwise the path willbe added to the existing | |||
* ClassLoader. | |||
* @param name the name of the loader to use. | |||
* @param clspath the path to be added to the classloader | |||
*/ | |||
public ClassLoader addToLoader(String name, Path clspath) { | |||
// Find if the loader is already defined in the current project | |||
AntClassLoader cl = (AntClassLoader) loaders.get(name); | |||
if (cl == null) { | |||
// Is it inherited from the calling project | |||
if (parentTable != null) { | |||
cl = parentTable.getLoader(name); | |||
} | |||
cl = new AntClassLoader(cl, project, clspath, true); | |||
loaders.put(name, cl); | |||
} | |||
else { | |||
// Add additional path to the existing definition | |||
String[] pathElements = clspath.list(); | |||
for (int i = 0; i < pathElements.length; ++i) { | |||
try { | |||
cl.addPathElement(pathElements[i]); | |||
} | |||
catch (BuildException e) { | |||
// ignore path elements invalid relative to the project | |||
} | |||
} | |||
} | |||
return cl; | |||
} | |||
/** | |||
* Add a new type of element to a role. | |||
* @param role the role for this Class. | |||
@@ -291,13 +234,13 @@ public class SymbolTable { | |||
throw new BuildException("Unknown role: " + role); | |||
} | |||
// Check if it is already defined | |||
Class old = get(role, name); | |||
Factory old = get(role, name); | |||
if (old != null) { | |||
if (old.equals(clz)) { | |||
if (old.getOriginalClass().equals(clz)) { | |||
project.log("Ignoring override for "+ role + " " + name | |||
+ ", it is already defined by the same class.", | |||
project.MSG_VERBOSE); | |||
return old; | |||
return old.getOriginalClass(); | |||
} | |||
else { | |||
project.log("Trying to override old definition of " + | |||
@@ -305,26 +248,33 @@ public class SymbolTable { | |||
project.MSG_WARN); | |||
} | |||
} | |||
checkClass(clz); | |||
Factory f = checkClass(clz); | |||
// Check that the Class is compatible with the role definition | |||
r.verifyAdaptability(role, clz); | |||
f = r.verifyAdaptability(role, f); | |||
// Record the new type | |||
Hashtable defTable = (Hashtable)defs.get(role); | |||
if (defTable == null) { | |||
defTable = new Hashtable(); | |||
defs.put(role, defTable); | |||
} | |||
defTable.put(name, clz); | |||
return old; | |||
defTable.put(name, f); | |||
String msg = | |||
" +User " + role + ": " + name + " " + clz.getName(); | |||
project.log(msg, project.MSG_DEBUG); | |||
return (old != null ? old.getOriginalClass() : null); | |||
} | |||
/** | |||
* Checks a class, whether it is suitable for serving in ANT. | |||
* @return the factory to use when instantiating the class | |||
* @throws BuildException and logs as Project.MSG_ERR for | |||
* conditions, that will cause execution to fail. | |||
*/ | |||
void checkClass(final Class clz) | |||
Factory checkClass(final Class clz) // Package on purpose | |||
throws BuildException { | |||
if (clz == null) return null; | |||
if(!Modifier.isPublic(clz.getModifiers())) { | |||
final String message = clz + " is not public"; | |||
project.log(message, Project.MSG_ERR); | |||
@@ -342,8 +292,37 @@ public class SymbolTable { | |||
// getConstructor finds public constructors only. | |||
try { | |||
clz.getConstructor(new Class[0]); | |||
return new Factory(){ | |||
public Object create(Project p) { | |||
try { | |||
return clz.newInstance(); | |||
} | |||
catch(Exception e) { | |||
throw new BuildException(e); | |||
} | |||
} | |||
public Class getOriginalClass() { | |||
return clz; | |||
} | |||
}; | |||
} catch (NoSuchMethodException nse) { | |||
clz.getConstructor(new Class[] {Project.class}); | |||
final Constructor c = | |||
clz.getConstructor(new Class[] {Project.class}); | |||
return new Factory(){ | |||
public Object create(Project p) { | |||
try { | |||
return c.newInstance(new Object[]{p}); | |||
} | |||
catch(Exception e) { | |||
throw new BuildException(e); | |||
} | |||
} | |||
public Class getOriginalClass() { | |||
return clz; | |||
} | |||
}; | |||
} | |||
} catch(NoSuchMethodException e) { | |||
final String message = | |||
@@ -359,11 +338,11 @@ public class SymbolTable { | |||
* @param name the name of the element to sea | |||
* @return the Class implementation | |||
*/ | |||
public Class get(String role, String name) { | |||
public Factory get(String role, String name) { | |||
Hashtable defTable = (Hashtable)defs.get(role); | |||
if (defTable != null) { | |||
Class clz = (Class)defTable.get(name); | |||
if (clz != null) return clz; | |||
Factory f = (Factory)defTable.get(name); | |||
if (f != null) return f; | |||
} | |||
if (parentTable != null) { | |||
return parentTable.get(role, name); | |||
@@ -372,19 +351,12 @@ public class SymbolTable { | |||
} | |||
/** | |||
* Get a Hashtable that is usable for manipulating Tasks, | |||
* Get a Hashtable that is usable for manipulating elements on Role. | |||
* @param role the role of the elements in the table | |||
* @return a Hashtable that delegates to the Symbol table. | |||
*/ | |||
public Hashtable getTaskDefinitions() { | |||
return new SymbolHashtable("task"); | |||
} | |||
/** | |||
* Get a Hashtable that is usable for manipulating Datatypes, | |||
* @return a Hashtable that delegates to the Symbol table. | |||
*/ | |||
public Hashtable getDataTypeDefinitions() { | |||
return new SymbolHashtable("datatype"); | |||
Hashtable getDefinitions(String role) { // package scope on purpose | |||
return new SymbolHashtable(role); | |||
} | |||
/** | |||
@@ -402,16 +374,43 @@ public class SymbolTable { | |||
} | |||
public synchronized Object get(Object key) { | |||
return SymbolTable.this.get(role, (String)key); | |||
Factory f = SymbolTable.this.get(role, (String)key); | |||
return (f == null? null : f.getOriginalClass()); | |||
} | |||
} | |||
/** | |||
* Factory for creating ANT objects. | |||
* Class objects are not instanciated directly but through a Factory | |||
* which is able to resolve issues such as proxys and such. | |||
*/ | |||
public static interface Factory { | |||
/** | |||
* Creates an object for the Role | |||
* @param the project in which it is created | |||
* @return the instantiated object with a proxy if necessary | |||
*/ | |||
public Object create(Project p); | |||
/** | |||
* Creates an object for the Role, adapted if necessary | |||
* for a particular interface. | |||
*/ | |||
// public Object adaptFor(Class clz, Project p, Object o); | |||
/** | |||
* The original class of the object without proxy. | |||
*/ | |||
public Class getOriginalClass(); | |||
} | |||
/** | |||
* The definition of a role | |||
*/ | |||
public class Role { | |||
private Method interfaceMethod; | |||
private Method adapterVerifier; | |||
private Factory adapterFactory; | |||
/** | |||
* Creates a new Role object | |||
@@ -420,6 +419,7 @@ public class SymbolTable { | |||
*/ | |||
Role(Class roleClz, Class adapterClz) { | |||
interfaceMethod = validInterface(roleClz); | |||
adapterFactory = checkClass(adapterClz); | |||
adapterVerifier = validAdapter(adapterClz, interfaceMethod); | |||
} | |||
@@ -433,12 +433,11 @@ public class SymbolTable { | |||
/** | |||
* Instantiate a new adapter for this role. | |||
*/ | |||
public RoleAdapter createAdapter() { | |||
if (adapterVerifier == null) return null; | |||
public RoleAdapter createAdapter(Project p) { | |||
if (adapterFactory == null) return null; | |||
try { | |||
return (RoleAdapter) | |||
adapterVerifier.getDeclaringClass().newInstance(); | |||
return (RoleAdapter) adapterFactory.create(p); | |||
} | |||
catch(BuildException be) { | |||
throw be; | |||
@@ -451,11 +450,12 @@ public class SymbolTable { | |||
/** | |||
* Verify if the class can be adapted to use by the role | |||
* @param role the name of the role to verify | |||
* @param clz the class to verify | |||
* @param f the factory for the class to verify | |||
*/ | |||
public void verifyAdaptability(String role, Class clz) { | |||
public Factory verifyAdaptability(String role, final Factory f) { | |||
final Class clz = f.getOriginalClass(); | |||
if (interfaceMethod.getParameterTypes()[0].isAssignableFrom(clz)) { | |||
return; | |||
return f; | |||
} | |||
if (adapterVerifier == null) { | |||
String msg = "Class " + clz.getName() + | |||
@@ -464,8 +464,18 @@ public class SymbolTable { | |||
} | |||
try { | |||
try { | |||
adapterVerifier.invoke(null, | |||
new Object[]{clz, project}); | |||
adapterVerifier.invoke(null, new Object[]{clz, project}); | |||
return new Factory(){ | |||
public Object create(Project p) { | |||
RoleAdapter ra = createAdapter(p); | |||
ra.setProxy(f.create(p)); | |||
return ra; | |||
} | |||
public Class getOriginalClass() { | |||
return clz; | |||
} | |||
}; | |||
} | |||
catch (InvocationTargetException ite) { | |||
throw ite.getTargetException(); | |||
@@ -487,5 +497,63 @@ public class SymbolTable { | |||
public boolean isImplementedBy(Class clz) { | |||
return interfaceMethod.getDeclaringClass().isAssignableFrom(clz); | |||
} | |||
/** | |||
* Verify if the interface is valid. | |||
* @param clz the interface to validate | |||
* @return the method defined by the interface | |||
*/ | |||
private Method validInterface(Class clz) { | |||
Method m[] = clz.getDeclaredMethods(); | |||
if (m.length == 1 | |||
&& java.lang.Void.TYPE.equals(m[0].getReturnType())) { | |||
Class args[] = m[0].getParameterTypes(); | |||
if (args.length == 1 | |||
&& !java.lang.String.class.equals(args[0]) | |||
&& !args[0].isArray() | |||
&& !args[0].isPrimitive()) { | |||
return m[0]; | |||
} | |||
else { | |||
throw new BuildException("Invalid role interface method in: " | |||
+ clz.getName()); | |||
} | |||
} | |||
else { | |||
throw new BuildException("More than one method on role interface"); | |||
} | |||
} | |||
/** | |||
* Verify if the adapter is valid with respect to the interface. | |||
* @param clz the class adapter to validate | |||
* @param mtd the method whose only argument must match | |||
* @return the static method to use for validating adaptees | |||
*/ | |||
private Method validAdapter(Class clz, Method mtd) { | |||
if (clz == null) return null; | |||
if (!mtd.getParameterTypes()[0].isAssignableFrom(clz)) { | |||
String msg = "Adapter " + clz.getName() + | |||
" is incompatible with role interface " + | |||
mtd.getDeclaringClass().getName(); | |||
throw new BuildException(msg); | |||
} | |||
String msg = "Class " + clz.getName() + " is not an adapter: "; | |||
if (!RoleAdapter.class.isAssignableFrom(clz)) { | |||
throw new BuildException(msg + "does not implement RoleAdapter"); | |||
} | |||
try { | |||
Method chk = clz.getMethod("checkClass", CHECK_ADAPTER_PARAMS); | |||
if (!Modifier.isStatic(chk.getModifiers())) { | |||
throw new BuildException(msg + "checkClass() is not static"); | |||
} | |||
return chk; | |||
} | |||
catch(NoSuchMethodException nme){ | |||
throw new BuildException(msg + "checkClass() not found", nme); | |||
} | |||
} | |||
} | |||
} |
@@ -165,4 +165,5 @@ public class TaskAdapter extends Task implements RoleAdapter { | |||
return this.proxy ; | |||
} | |||
public void setId(String id) {} | |||
} |
@@ -138,10 +138,8 @@ public class Ant extends Task { | |||
} | |||
public void init() { | |||
newProject = new Project(project); | |||
newProject = project.createSubProject(); | |||
newProject.setJavaVersionProperty(); | |||
// newProject.addTaskDefinition("property", | |||
// (Class)project.getTaskDefinitions().get("property")); | |||
} | |||
private void reinit() { | |||
@@ -185,26 +183,6 @@ public class Ant extends Task { | |||
} | |||
} | |||
// Hashtable taskdefs = project.getTaskDefinitions(); | |||
// Enumeration et = taskdefs.keys(); | |||
// while (et.hasMoreElements()) { | |||
// String taskName = (String) et.nextElement(); | |||
// if (taskName.equals("property")) { | |||
// // we have already added this taskdef in #init | |||
// continue; | |||
// } | |||
// Class taskClass = (Class) taskdefs.get(taskName); | |||
// newProject.addTaskDefinition(taskName, taskClass); | |||
// } | |||
// Hashtable typedefs = project.getDataTypeDefinitions(); | |||
// Enumeration e = typedefs.keys(); | |||
// while (e.hasMoreElements()) { | |||
// String typeName = (String) e.nextElement(); | |||
// Class typeClass = (Class) typedefs.get(typeName); | |||
// newProject.addDataTypeDefinition(typeName, typeClass); | |||
// } | |||
// set user-defined or all properties from calling project | |||
Hashtable prop1; | |||
if (inheritAll) { | |||
@@ -418,7 +418,7 @@ public class Antlib extends Task { | |||
if (classpath != null) { | |||
clspath.append(classpath); | |||
} | |||
return project.getSymbols().addToLoader(loaderId, clspath); | |||
return project.addToLoader(loaderId, clspath); | |||
} | |||
@@ -505,8 +505,6 @@ public class Antlib extends Task { | |||
private int level = 0; | |||
private SymbolTable symbols = null; | |||
private String name = null; | |||
private String className = null; | |||
private String adapter = null; | |||
@@ -520,7 +518,6 @@ public class Antlib extends Task { | |||
AntLibraryHandler(ClassLoader classloader, Properties als) { | |||
this.classloader = classloader; | |||
this.aliasMap = als; | |||
this.symbols = project.getSymbols(); | |||
} | |||
/** | |||
@@ -591,15 +588,15 @@ public class Antlib extends Task { | |||
try { | |||
if ("role".equals(tag)) { | |||
if (isRoleInUse(name)) { | |||
if (project.isRoleDefined(name)) { | |||
String msg = "Cannot override role: " + name; | |||
log(msg, Project.MSG_WARN); | |||
return; | |||
} | |||
// Defining a new role | |||
symbols.addRole(name, loadClass(className), | |||
(adapter == null? | |||
null : loadClass(adapter))); | |||
project.addRoleDefinition(name, loadClass(className), | |||
(adapter == null? | |||
null : loadClass(adapter))); | |||
return; | |||
} | |||
@@ -610,12 +607,12 @@ public class Antlib extends Task { | |||
name = alias; | |||
} | |||
//catch an attempted override of an existing name | |||
if (!override && isInUse(tag, name)) { | |||
if (!override && project.isDefinedOnRole(tag, name)) { | |||
String msg = "Cannot override " + tag + ": " + name; | |||
log(msg, Project.MSG_WARN); | |||
return; | |||
} | |||
symbols.add(tag, name, loadClass(className)); | |||
project.addDefinitionOnRole(tag, name, loadClass(className)); | |||
} | |||
catch(BuildException be) { | |||
throw new SAXParseException(be.getMessage(), locator, be); | |||
@@ -651,26 +648,6 @@ public class Antlib extends Task { | |||
} | |||
} | |||
/** | |||
* test for a name being in use already on this role | |||
* | |||
* @param name the name to test | |||
* @return true if it is a task or a datatype | |||
*/ | |||
private boolean isInUse(String role, String name) { | |||
return (symbols.get(role, name) != null); | |||
} | |||
/** | |||
* test for a role name being in use already | |||
* | |||
* @param name the name to test | |||
* @return true if it is a task or a datatype | |||
*/ | |||
private boolean isRoleInUse(String name) { | |||
return (symbols.getRole(name) != null); | |||
} | |||
//end inner class AntLibraryHandler | |||
} | |||
@@ -66,6 +66,7 @@ import org.apache.tools.ant.*; | |||
public class DataTypeAdapterTask extends Task implements RoleAdapter { | |||
Object proxy; | |||
String id = null; | |||
/** | |||
* Checks a class, whether it is suitable to be adapted. | |||
@@ -83,14 +84,27 @@ public class DataTypeAdapterTask extends Task implements RoleAdapter { | |||
* Do the execution. | |||
*/ | |||
public void execute() throws BuildException { | |||
if (id != null) { | |||
// Need to re-register this reference | |||
// The container has register the Adapter instead | |||
project.addReference(id, proxy); | |||
} | |||
} | |||
/** | |||
* Propagate configuration of Project | |||
*/ | |||
public void setProject(Project p) { | |||
super.setProject(p); | |||
// Check to see if the DataType has a setProject method to set | |||
if (proxy instanceof ProjectComponent) { | |||
((ProjectComponent)proxy).setProject(project); | |||
((ProjectComponent)proxy).setProject(p); | |||
return; | |||
} | |||
// This may not be needed | |||
// We are trying to set project even it is was not declared | |||
// We are trying to set project even if is was not declared | |||
// just like TaskAdapter does for beans, this is not done | |||
// by the original code | |||
Method setProjectM = null; | |||
@@ -99,7 +113,7 @@ public class DataTypeAdapterTask extends Task implements RoleAdapter { | |||
setProjectM = | |||
c.getMethod( "setProject", new Class[] {Project.class}); | |||
if(setProjectM != null) { | |||
setProjectM.invoke(proxy, new Object[] {project}); | |||
setProjectM.invoke(proxy, new Object[] {p}); | |||
} | |||
} catch (NoSuchMethodException e) { | |||
// ignore this if the class being used as a task does not have | |||
@@ -122,4 +136,8 @@ public class DataTypeAdapterTask extends Task implements RoleAdapter { | |||
return this.proxy ; | |||
} | |||
public void setId(String id) { | |||
log("Setting adapter id to: " + id, Project.MSG_DEBUG); | |||
this.id = id; | |||
} | |||
} |
@@ -0,0 +1,30 @@ | |||
<?xml version="1.0"?> | |||
<project name="local" default="libs" > | |||
<taskdef name="antjar" classname="org.apache.tools.ant.taskdefs.Antjar" /> | |||
<property name="src" location="." /> | |||
<property name="classes" location="../../build/testcases" /> | |||
<property name="contrib" location="../../build/case_contrib.jar" /> | |||
<target name="libs" depends="compile" > | |||
<antjar destfile="${contrib}" antxml="${src}/case-antlib.xml" > | |||
<fileset dir="${classes}" > | |||
<include name="org/**" /> | |||
</fileset> | |||
</antjar> | |||
</target> | |||
<target name="compile" > | |||
<mkdir dir="${classes}" /> | |||
<javac srcdir="${src}" destdir="${classes}" > | |||
<include name="org/**/*.java" /> | |||
</javac> | |||
</target> | |||
<target name="clean" > | |||
<delete dir="${classes}" /> | |||
<delete file='${contrib}'/> | |||
</target> | |||
</project> |
@@ -0,0 +1,5 @@ | |||
<?xml version="1.0" ?> | |||
<antlib version="1.0" > | |||
<task name="case" class="org.apache.ant.contrib.Case" /> | |||
</antlib> |
@@ -0,0 +1,39 @@ | |||
<?xml version="1.0"?> | |||
<project name="case-test" default="test" basedir="."> | |||
<property name="value" value="task.xml" /> | |||
<target name="init"> | |||
<taskdef name="antlib" classname="org.apache.tools.ant.taskdefs.Antlib" /> | |||
<antlib file="../../build/case_contrib.jar" /> | |||
</target> | |||
<target name="test" depends="init,case,test1,test2,test3"> | |||
<echo message="Value=${value}" /> | |||
</target> | |||
<target name="case" > | |||
<case property="value" > | |||
<when value="task.xml" property="value.xml" /> | |||
</case> | |||
<case property="location" > | |||
<when value="loc" property="location.fail" /> | |||
<when value="" property="location.fail" /> | |||
<else property="location.unset" /> | |||
</case> | |||
</target> | |||
<target name="test1" if="value.xml"> | |||
<echo message="Value equals to itself" /> | |||
</target> | |||
<target name="test2" if="location.fail"> | |||
<fail message="Location passed" /> | |||
</target> | |||
<target name="test3" if="location.unset"> | |||
<echo message="Location does not exists" /> | |||
</target> | |||
</project> |
@@ -0,0 +1,169 @@ | |||
/* | |||
* The Apache Software License, Version 1.1 | |||
* | |||
* Copyright (c) 1999 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.ant.contrib; | |||
import java.io.*; | |||
import java.util.*; | |||
import org.apache.tools.ant.*; | |||
import org.apache.tools.ant.types.*; | |||
/** | |||
* Will set one of the given properties depending on the result of testing | |||
* the value of another property. | |||
* | |||
* <!ELEMENT case (when*, else) > | |||
* <!ATTLIST case property CDATA #REQUIRED > The name of the property to test | |||
* <!ELEMENT when EMPTY > | |||
* <!ATTLIST when value CDATA #REQUIRED > The value to compare and set prop. | |||
* <!ATTLIST when property CDATA #REQUIRED > The name of the property to set | |||
* <!ELEMENT else EMPTY > | |||
* <!ATTLIST else property CDATA #REQUIRED > The name of the property to set otherwise | |||
* <!ATTLIST else value CDATA #IMPLIED > The value to set; default "true". | |||
* | |||
* @author Jose Alberto Fernandez <a href="mailto:jfernandez@viquity.com">jfernandez@viquity.com</a> | |||
*/ | |||
public class Case extends Task { | |||
public class When { | |||
private String property; | |||
private String value; | |||
public void setProperty(String name) { | |||
property = name; | |||
} | |||
public String getProperty() { | |||
return property; | |||
} | |||
public void setValue(String val) { | |||
value = val; | |||
} | |||
public String getValue() { | |||
return value; | |||
} | |||
public boolean tryCase(String caseValue) throws BuildException { | |||
if (property == null) | |||
throw new BuildException("Property attribute is mandatory"); | |||
if (value == null) | |||
throw new BuildException("Value attribute is mandatory"); | |||
if (!value.equals(caseValue)) return false; | |||
if (getProject().getProperty(property) == null) { | |||
getProject().setProperty(property, value); | |||
} else { | |||
log("Override ignored for " + property, Project.MSG_VERBOSE); | |||
} | |||
return true; | |||
} | |||
public void doElse() throws BuildException { | |||
if (property == null) | |||
throw new BuildException("Property attribute is mandatory"); | |||
String elseValue = (value == null) ? "true" : value; | |||
if (getProject().getProperty(property) == null) { | |||
getProject().setProperty(property, elseValue); | |||
} else { | |||
log("Override ignored for " + property, Project.MSG_VERBOSE); | |||
} | |||
} | |||
} | |||
private String caseProperty; | |||
private Vector whenList = new Vector(); | |||
private When elseCase = null; | |||
public When createWhen() throws BuildException { | |||
When w = new When(); | |||
whenList.addElement(w); | |||
return w; | |||
} | |||
public When createElse() throws BuildException { | |||
if (elseCase != null) | |||
throw new BuildException("Only one else element allowed per case"); | |||
return (elseCase = new When()); | |||
} | |||
public void setProperty(String property) { | |||
this.caseProperty = property; | |||
} | |||
public void execute() throws BuildException { | |||
if (caseProperty == null) { | |||
throw new BuildException("property attribute is required", | |||
location); | |||
} | |||
String caseValue = getProject().getProperty(caseProperty); | |||
for (Enumeration e = whenList.elements(); e.hasMoreElements(); ) { | |||
When w = (When)e.nextElement(); | |||
if (w.tryCase(caseValue)) return; | |||
} | |||
if (elseCase != null) | |||
elseCase.doElse(); | |||
} | |||
} |