Browse Source

This commit implements:

- addConfigured(Type) to introspection rules
    - ant-type magic polymorhic attribute
    - allow types that have Project as a constructor to
      be used in addName(Type) and addConfiguredName(Type) methods
    - allow addName and addConfiguredName methods to have higher
      presedence that createName() methods.

PR: 19897


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@274906 13f79535-47bb-0310-9956-ffa450edef68
master
Peter Reilly 22 years ago
parent
commit
5460755b44
5 changed files with 506 additions and 101 deletions
  1. +45
    -0
      src/etc/testcases/types/poly.xml
  2. +299
    -83
      src/main/org/apache/tools/ant/IntrospectionHelper.java
  3. +42
    -6
      src/main/org/apache/tools/ant/RuntimeConfigurable.java
  4. +15
    -12
      src/main/org/apache/tools/ant/UnknownElement.java
  5. +105
    -0
      src/testcases/org/apache/tools/ant/types/PolyTest.java

+ 45
- 0
src/etc/testcases/types/poly.xml View File

@@ -0,0 +1,45 @@
<project name="test" basedir=".">
<property name="c" value="org.apache.tools.ant.types.PolyTest"/>
<path id="test-c">
<pathelement location="../../../../build/testcases" />
<pathelement path="${java.class.path}" />
</path>

<target name="init">
<typedef loaderref="poly" classpathref="test-c"
name = "myfileset" classname="${c}$MyFileSet"/>

<typedef loaderref="poly" classpathref="test-c"
name = "mypath" classname="${c}$MyPath"/>

<typedef loaderref="poly" classpathref="test-c"
name = "mytask" classname="${c}$MyTask"/>
</target>

<target name="fileset" depends="init">
<mytask>
<fileset dir="."/>
</mytask>
</target>

<target name="fileset-ant-type" depends="init">
<mytask>
<fileset ant-type="myfileset" dir="."/>
</mytask>
</target>

<target name="path" depends="init">
<mytask>
<path path="."/>
</mytask>
</target>

<target name="path-ant-type" depends="init">
<mytask>
<path ant-type="mypath" path="."/>
</mytask>
</target>

</project>

+ 299
- 83
src/main/org/apache/tools/ant/IntrospectionHelper.java View File

@@ -70,10 +70,12 @@ import org.apache.tools.ant.types.Path;
* Helper class that collects the methods a task or nested element
* holds to set attributes, create nested elements or hold PCDATA
* elements.
* The class is final as it has a private constructor.
*
* @author Stefan Bodewig
* @author Peter Reilly
*/
public class IntrospectionHelper implements BuildListener {
public final class IntrospectionHelper implements BuildListener {

/**
* Map from attribute names to attribute types
@@ -205,7 +207,6 @@ public class IntrospectionHelper implements BuildListener {
attributeSetters = new Hashtable();
nestedTypes = new Hashtable();
nestedCreators = new Hashtable();
nestedStorers = new Hashtable();
addTypeMethods = new ArrayList();

this.bean = bean;
@@ -220,7 +221,7 @@ public class IntrospectionHelper implements BuildListener {
// check of add[Configured](Class) pattern
if (args.length == 1
&& java.lang.Void.TYPE.equals(returnType)
&& (name.equals("add") /*|| name.equals("addConfigured")*/)) {
&& (name.equals("add") || name.equals("addConfigured"))) {
insertAddTypeMethod(m);
continue;
}
@@ -286,19 +287,31 @@ public class IntrospectionHelper implements BuildListener {
&& args.length == 0) {

String propName = getPropertyName(name, "create");
nestedTypes.put(propName, returnType);
nestedCreators.put(propName, new NestedCreator() {
// Check if a create of this property is already present
// add takes preference over create for CB purposes
if (nestedCreators.get(propName) == null) {
nestedTypes.put(propName, returnType);
nestedCreators.put(propName, new NestedCreator() {

public boolean isPolyMorphic() {
return false;
}

public Class getElementClass() {
return null;
}

public Object create(Object parent)
public Object create(
Project project, Object parent, Object ignore)
throws InvocationTargetException,
IllegalAccessException {

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

public void store(Object parent, Object child) {
}
});
nestedStorers.remove(propName);

}
} else if (name.startsWith("addConfigured")
&& java.lang.Void.TYPE.equals(returnType)
&& args.length == 1
@@ -307,24 +320,45 @@ public class IntrospectionHelper implements BuildListener {
&& !args[0].isPrimitive()) {

try {
final Constructor c =
args[0].getConstructor(new Class[] {});
Constructor constructor = null;
try {
constructor =
args[0].getConstructor(new Class[] {});
} catch (NoSuchMethodException ex) {
constructor =
args[0].getConstructor(new Class[] {
Project.class});
}
final Constructor c = constructor;
String propName = getPropertyName(name, "addConfigured");
nestedTypes.put(propName, args[0]);
nestedCreators.put(propName, new NestedCreator() {

public Object create(Object parent)
throws InvocationTargetException, IllegalAccessException, InstantiationException {
public boolean isPolyMorphic() {
return true;
}

Object o = c.newInstance(new Object[] {});
return o;
public Class getElementClass() {
return c.getDeclaringClass();
}

});
nestedStorers.put(propName, new NestedStorer() {
public Object create(
Project project, Object parent, Object child)
throws InvocationTargetException,
IllegalAccessException, InstantiationException {
if (child != null) {
return child;
} else if (c.getParameterTypes().length == 0) {
return c.newInstance(new Object[] {});
} else {
return c.newInstance(new Object[] {
project});
}
}

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

m.invoke(parent, new Object[] {child});
}
@@ -341,22 +375,50 @@ public class IntrospectionHelper implements BuildListener {
&& !args[0].isPrimitive()) {

try {
final Constructor c =
args[0].getConstructor(new Class[] {});
Constructor constructor = null;
try {
constructor =
args[0].getConstructor(new Class[] {});
} catch (NoSuchMethodException ex) {
constructor =
args[0].getConstructor(new Class[] {
Project.class});
}
final Constructor c = constructor;
String propName = getPropertyName(name, "add");
nestedTypes.put(propName, args[0]);
nestedCreators.put(propName, new NestedCreator() {

public Object create(Object parent)
throws InvocationTargetException, IllegalAccessException, InstantiationException {
public boolean isPolyMorphic() {
return true;
}

public Class getElementClass() {
return c.getDeclaringClass();
}

public Object create(
Project project, Object parent, Object child)
throws InvocationTargetException,
IllegalAccessException, InstantiationException {
if (child != null) {
// ignore
} else if (c.getParameterTypes().length == 0) {
child = c.newInstance(new Object[] {});
} else {
child = c.newInstance(new Object[] {
project});
}
m.invoke(parent, new Object[] {child});
return child;
}
public void store(Object parent, Object child)
throws InvocationTargetException,
IllegalAccessException, InstantiationException {

Object o = c.newInstance(new Object[] {});
m.invoke(parent, new Object[] {o});
return o;
}

});
nestedStorers.remove(name);
} catch (NoSuchMethodException nse) {
// ignore
}
@@ -535,6 +597,40 @@ public class IntrospectionHelper implements BuildListener {
throw new BuildException(msg);
}

private NestedCreator getNestedCreator(Project project, Object parent,
String elementName) throws BuildException {

NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
if (nc == null) {
nc = createAddTypeCreator(project, parent, elementName);
}
if (nc == null && parent instanceof DynamicConfigurator) {
DynamicConfigurator dc = (DynamicConfigurator) parent;
final Object nestedElement = dc.createDynamicElement(elementName);
if (nestedElement != null) {
nc = new NestedCreator() {
public boolean isPolyMorphic() {
return false;
}
public Class getElementClass() {
return null;
}

public Object create(
Project project, Object parent, Object ignore) {
return nestedElement;
}
public void store(Object parent, Object child) {
}
};
}
}
if (nc == null) {
throwNotSupported(project, parent, elementName);
}
return nc;
}

/**
* Creates a named nested element. Depending on the results of the
* initial introspection, either a method in the given parent instance
@@ -558,32 +654,9 @@ public class IntrospectionHelper implements BuildListener {
*/
public Object createElement(Project project, Object parent,
String elementName) throws BuildException {
NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
if (nc == null && addTypeMethods.size() > 0) {
Object nestedElement = createAddTypeElement(
project, parent, elementName);
if (nestedElement != null) {
if (project != null) {
project.setProjectReference(nestedElement);
}
return nestedElement;
}
}
if (nc == null && parent instanceof DynamicConfigurator) {
DynamicConfigurator dc = (DynamicConfigurator) parent;
Object nestedElement = dc.createDynamicElement(elementName);
if (nestedElement != null) {
if (project != null) {
project.setProjectReference(nestedElement);
}
return nestedElement;
}
}
if (nc == null) {
throwNotSupported(project, parent, elementName);
}
NestedCreator nc = getNestedCreator(project, parent, elementName);
try {
Object nestedElement = nc.create(parent);
Object nestedElement = nc.create(project, parent, null);
if (project != null) {
project.setProjectReference(nestedElement);
}
@@ -603,6 +676,23 @@ public class IntrospectionHelper implements BuildListener {
}
}

/**
* returns an object that creates and stores an object
* for an element of a parent.
*
* @param project Project to which the parent object belongs.
* @param parent Parent object used to create the creator object to
* create and store and instance of a subelement.
* @param elementName Name of the element to create an instance of.
* @return a creator object to create and store the element instance.
*/

public Creator getElementCreator(
Project project, Object parent, String elementName) {
NestedCreator nc = getNestedCreator(project, parent, elementName);
return new Creator(project, parent, nc);
}

/**
* Indicate if this element supports a nested element of the
* given name.
@@ -642,7 +732,7 @@ public class IntrospectionHelper implements BuildListener {
if (elementName == null) {
return;
}
NestedStorer ns = (NestedStorer) nestedStorers.get(elementName);
NestedCreator ns = (NestedCreator) nestedCreators.get(elementName);
if (ns == null) {
return;
}
@@ -876,7 +966,8 @@ public class IntrospectionHelper implements BuildListener {
return new AttributeSetter() {
public void set(Project p, Object parent,
String value)
throws InvocationTargetException, IllegalAccessException, BuildException {
throws InvocationTargetException,
IllegalAccessException, BuildException {
try {
Object attribute = c.newInstance(new String[] {value});
if (p != null) {
@@ -934,23 +1025,138 @@ public class IntrospectionHelper implements BuildListener {
}

/**
* Internal interface used to create nested elements. Not documented
* in detail for reasons of source code readability.
* creator - allows use of create/store external
* to IntrospectionHelper.
* The class is final as it has a private constructor.
*/
private interface NestedCreator {
Object create(Object parent)
throws InvocationTargetException, IllegalAccessException, InstantiationException;
public static final class Creator {
private NestedCreator nestedCreator;
private Object parent;
private Project project;
private Object nestedObject;
private String polyType;

/**
* Creates a new Creator instance.
* This object is given to the UnknownElement to create
* objects for sub-elements. UnknownElement calls
* create to create an object, the object then gets
* configured and then UnknownElement calls store.
* SetPolyType may be used to override the type used
* to create the object with. SetPolyType gets called
* before create.
*
* @param project the current project
* @param parent the parent object to create the object in
* @param nestedCreator the nested creator object to use
*/
private Creator(
Project project, Object parent, NestedCreator nestedCreator) {
this.project = project;
this.parent = parent;
this.nestedCreator = nestedCreator;
}

/**
* Used to override the class used to create the object.
*
* @param polyType a ant component type name
*/
public void setPolyType(String polyType) {
this.polyType = polyType;
}

/**
* Create an object using this creator, which is determined
* by introspection.
*
* @return the created object
*/
public Object create() {
if (polyType != null) {
if (!nestedCreator.isPolyMorphic()) {
throw new BuildException(
"Not allowed to use the polymorhic form"
+ " for this element");
}
Class elementClass = nestedCreator.getElementClass();
ComponentHelper helper =
ComponentHelper.getComponentHelper(project);
nestedObject = ComponentHelper.getComponentHelper(project)
.createComponent(polyType);
if (nestedObject == null) {
throw new BuildException(
"Unable to create object of type " + polyType);
}
}
try {
nestedObject = nestedCreator.create(
project, parent, nestedObject);
if (project != null) {
project.setProjectReference(nestedObject);
}
return nestedObject;
} catch (IllegalAccessException ex) {
throw new BuildException(ex);
} catch (InstantiationException ex) {
throw new BuildException(ex);
} catch (IllegalArgumentException ex) {
if (polyType != null) {
throw new BuildException(
"Invalid type used " + polyType);
}
throw ex;
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
if (t instanceof BuildException) {
throw (BuildException) t;
}
throw new BuildException(t);
}
}

/**
* Stores the nested elemtnt object using a storage method
* detimined by introspection.
*
*/
public void store() {
try {
nestedCreator.store(parent, nestedObject);
} catch (IllegalAccessException ex) {
throw new BuildException(ex);
} catch (InstantiationException ex) {
throw new BuildException(ex);
} catch (IllegalArgumentException ex) {
if (polyType != null) {
throw new BuildException(
"Invalid type used " + polyType);
}
throw ex;
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
if (t instanceof BuildException) {
throw (BuildException) t;
}
throw new BuildException(t);
}
}
}

/**
* Internal interface used to storing nested elements. Not documented
* Internal interface used to create nested elements. Not documented
* in detail for reasons of source code readability.
*/
private interface NestedStorer {
private interface NestedCreator {
boolean isPolyMorphic();
Class getElementClass();
Object create(Project project, Object parent, Object child)
throws InvocationTargetException, IllegalAccessException, InstantiationException;
void store(Object parent, Object child)
throws InvocationTargetException, IllegalAccessException, InstantiationException;
}


/**
* Internal interface used to setting element attributes. Not documented
* in detail for reasons of source code readability.
@@ -1023,21 +1229,20 @@ public class IntrospectionHelper implements BuildListener {
public void messageLogged(BuildEvent event) {
}


/**
* Check if the parent accepts a typed nested element
* and if so, create the object, call the parents
* addmethod.
* This method is part of the initial support
* for add(Type) and addConfigured(Type).
* AddConfigured(Type) will be done later.
*
*/

private Object createAddTypeElement(
Project project, Object parent, String elementName) {
private NestedCreator createAddTypeCreator(
Project project, Object parent, String elementName)
throws BuildException {
if (addTypeMethods.size() == 0) {
return null;
}
ComponentHelper helper = ComponentHelper.getComponentHelper(project);

Object addedObject = null;
Method addMethod = null;

Class clazz = helper.getComponentClass(elementName);
if (clazz == null) {
return null;
@@ -1050,21 +1255,32 @@ public class IntrospectionHelper implements BuildListener {
if (addedObject == null) {
return null;
}
final Method method = addMethod;
final Object nestedObject = addedObject;

try {
addMethod.invoke(parent, new Object[] {addedObject});
} catch (IllegalAccessException ex) {
throw new BuildException(ex);
} catch (InvocationTargetException ex) {
Throwable t = ex.getTargetException();
if (t instanceof BuildException) {
throw (BuildException) t;
return new NestedCreator() {
public boolean isPolyMorphic() {
return false;
}
throw new BuildException(t);
} catch (Throwable t) {
throw new BuildException(t);
}
return addedObject;

public Class getElementClass() {
return null;
}
public Object create(Project project, Object parent, Object ignore)
throws InvocationTargetException, IllegalAccessException {
if (!method.getName().endsWith("Configured")) {
method.invoke(parent, new Object[]{nestedObject});
}
return nestedObject;
}
public void store(Object parent, Object child)
throws InvocationTargetException, IllegalAccessException,
InstantiationException {
if (method.getName().endsWith("Configured")) {
method.invoke(parent, new Object[]{nestedObject});
}
}
};
}

/**


+ 42
- 6
src/main/org/apache/tools/ant/RuntimeConfigurable.java View File

@@ -77,6 +77,9 @@ import org.xml.sax.helpers.AttributeListImpl;
*/
public class RuntimeConfigurable implements Serializable {

/** Polymorphic attribute (May be XML NS attribute later) */
private static final String ANT_TYPE = "ant-type";
/** Name of the element to configure. */
private String elementTag = null;

@@ -88,7 +91,10 @@ public class RuntimeConfigurable implements Serializable {
*/
private transient Object wrappedObject = null;

/**
/** the creator used to make the wrapped object */
private transient IntrospectionHelper.Creator creator;

/**
* @deprecated
* XML attributes for the element.
*/
@@ -112,6 +118,9 @@ public class RuntimeConfigurable implements Serializable {
/** Indicates if the wrapped object has been configured */
private boolean proxyConfigured = false;

/** the polymorphic type */
private String polyType = null;
/**
* Sole constructor creating a wrapper for the specified object.
*
@@ -139,6 +148,16 @@ public class RuntimeConfigurable implements Serializable {
proxyConfigured = false;
}

/**
* Sets the creator of the element to be configured
* used to store the element in the parent;
*
* @param creator the creator object
*/
void setCreator(IntrospectionHelper.Creator creator) {
this.creator = creator;
}
/**
* Get the object for which this RuntimeConfigurable holds the configuration
* information
@@ -149,6 +168,13 @@ public class RuntimeConfigurable implements Serializable {
return wrappedObject;
}

/**
* get the polymorphic type for this element
*/
public String getPolyType() {
return polyType;
}

/**
* Sets the attributes for the wrapped element.
*
@@ -170,12 +196,16 @@ public class RuntimeConfigurable implements Serializable {
* @param value the attribute's value.
*/
public void setAttribute(String name, String value) {
if (attributeNames == null) {
attributeNames = new ArrayList();
attributeMap = new HashMap();
if (name.equalsIgnoreCase(ANT_TYPE)) {
this.polyType = value;
} else {
if (attributeNames == null) {
attributeNames = new ArrayList();
attributeMap = new HashMap();
}
attributeNames.add(name);
attributeMap.put(name, value);
}
attributeNames.add(name);
attributeMap.put(name, value);
}

/** Return the attribute map.
@@ -389,6 +419,12 @@ public class RuntimeConfigurable implements Serializable {
childTask.setRuntimeConfigurableWrapper(child);
}

if ((child.creator != null) && configureChildren) {
child.maybeConfigure(p);
child.creator.store();
continue;
}
/*
* backwards compatibility - element names of nested
* elements have been all lower-case in Ant, except for


+ 15
- 12
src/main/org/apache/tools/ant/UnknownElement.java View File

@@ -312,17 +312,13 @@ public class UnknownElement extends Task {
Class parentClass = parent.getClass();
IntrospectionHelper ih = IntrospectionHelper.getHelper(parentClass);


if (children != null) {
Iterator it = children.iterator();
for (int i = 0; it.hasNext(); i++) {
RuntimeConfigurable childWrapper = parentWrapper.getChild(i);
UnknownElement child = (UnknownElement) it.next();

// backwards compatibility - element names of nested
// elements have been all lower-case in Ant, except for
// TaskContainers
if (!handleChild(ih, parent, child,
child.getTag().toLowerCase(Locale.US),
if (!handleChild(ih, parent, child,
childWrapper)) {
if (!(parent instanceof TaskContainer)) {
ih.throwNotSupported(getProject(), parent,
@@ -475,17 +471,24 @@ public class UnknownElement extends Task {
*/
private boolean handleChild(IntrospectionHelper ih,
Object parent, UnknownElement child,
String childTag,
RuntimeConfigurable childWrapper) {
if (ih.supportsNestedElement(childTag)) {
Object realChild
= ih.createElement(getProject(), parent, childTag);
// backwards compatibility - element names of nested
// elements have been all lower-case in Ant, except for
// TaskContainers
String childName = child.getTag().toLowerCase(Locale.US);
if (ih.supportsNestedElement(childName)) {
IntrospectionHelper.Creator creator =
ih.getElementCreator(getProject(), parent, childName);
creator.setPolyType(childWrapper.getPolyType());
Object realChild=creator.create();
childWrapper.setCreator(creator);
childWrapper.setProxy(realChild);
if (realChild instanceof Task) {
Task childTask = (Task) realChild;
childTask.setRuntimeConfigurableWrapper(childWrapper);
childTask.setTaskName(childTag);
childTask.setTaskType(childTag);
childTask.setTaskName(childName);
childTask.setTaskType(childName);
childTask.setLocation(child.getLocation());
}
child.handleChildren(realChild, childWrapper);
return true;


+ 105
- 0
src/testcases/org/apache/tools/ant/types/PolyTest.java View File

@@ -0,0 +1,105 @@
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 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 "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.types;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildFileTest;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.condition.Condition;

public class PolyTest extends BuildFileTest {

public PolyTest(String name) {
super(name);
}

public void setUp() {
configureProject("src/etc/testcases/types/poly.xml");
}

public void testFileSet() {
expectLogContaining("fileset", "types.FileSet");
}
public void testFileSetAntType() {
expectLogContaining("fileset-ant-type", "types.PolyTest$MyFileSet");
}

public void testPath() {
expectLogContaining("path", "types.Path");
}
public void testPathAntType() {
expectLogContaining("path-ant-type", "types.PolyTest$MyPath");
}

public static class MyFileSet extends FileSet {}

public static class MyPath extends Path {
public MyPath(Project project) {
super(project);
}
}

public static class MyTask extends Task {
public void addPath(Path path) {
log("class of path is " + path.getClass());
}
public void addFileset(FileSet fileset) {
log("class of fileset is " + fileset.getClass());
}
}
}

Loading…
Cancel
Save