diff --git a/WHATSNEW b/WHATSNEW index cf7e1ea04..8af756e45 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -61,6 +61,8 @@ Other changes: * Enable to choose the regexp implementation without system property. Bugzilla Report 15390. +* Expose objects and methods in IntrospectionHelper. Bugzilla Report 30794. + Fixed bugs: ----------- diff --git a/src/main/org/apache/tools/ant/IntrospectionHelper.java b/src/main/org/apache/tools/ant/IntrospectionHelper.java index 9c38d98d2..be8051f2d 100644 --- a/src/main/org/apache/tools/ant/IntrospectionHelper.java +++ b/src/main/org/apache/tools/ant/IntrospectionHelper.java @@ -22,10 +22,12 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Locale; +import java.util.Map; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.taskdefs.PreSetDef; @@ -34,7 +36,6 @@ import org.apache.tools.ant.taskdefs.PreSetDef; * holds to set attributes, create nested elements or hold PCDATA * elements. * The class is final as it has a private constructor. - * */ public final class IntrospectionHelper implements BuildListener { @@ -63,9 +64,9 @@ public final class IntrospectionHelper implements BuildListener { private Hashtable nestedCreators; /** - * Vector of methods matching add[Configured](Class) pattern + * Vector of methods matching add[Configured](Class) pattern. */ - private List addTypeMethods; + private List addTypeMethods; /** * The method to invoke to add PCDATA. @@ -188,8 +189,7 @@ public final class IntrospectionHelper implements BuildListener { } // hide addTask for TaskContainers - if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean) - && args.length == 1 && "addTask".equals(name) + if (isContainer() && args.length == 1 && "addTask".equals(name) && org.apache.tools.ant.Task.class.equals(args[0])) { continue; } @@ -246,28 +246,13 @@ public final class IntrospectionHelper implements BuildListener { // 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 Object getRealObject() { - return null; - } - - public Class getElementClass() { - return null; - } - - public Object create( + nestedCreators.put(propName, new NestedCreator(m) { + Object create( Project project, Object parent, Object ignore) throws InvocationTargetException, IllegalAccessException { return m.invoke(parent, new Object[] {}); } - - public void store(Object parent, Object child) { - } }); } } else if (name.startsWith("addConfigured") @@ -290,21 +275,16 @@ public final class IntrospectionHelper implements BuildListener { final Constructor c = constructor; String propName = getPropertyName(name, "addConfigured"); nestedTypes.put(propName, args[0]); - nestedCreators.put(propName, new NestedCreator() { - - public boolean isPolyMorphic() { + nestedCreators.put(propName, new NestedCreator(m) { + boolean isPolyMorphic() { return true; } - public Object getRealObject() { - return null; - } - - public Class getElementClass() { + Class getElementClass() { return c.getDeclaringClass(); } - public Object create( + Object create( Project project, Object parent, Object child) throws InvocationTargetException, IllegalAccessException, InstantiationException { @@ -323,7 +303,7 @@ public final class IntrospectionHelper implements BuildListener { return child; } - public void store(Object parent, Object child) + void store(Object parent, Object child) throws InvocationTargetException, IllegalAccessException, InstantiationException { m.invoke(parent, new Object[] {child}); @@ -353,21 +333,16 @@ public final class IntrospectionHelper implements BuildListener { final Constructor c = constructor; String propName = getPropertyName(name, "add"); nestedTypes.put(propName, args[0]); - nestedCreators.put(propName, new NestedCreator() { - - public boolean isPolyMorphic() { + nestedCreators.put(propName, new NestedCreator(m) { + boolean isPolyMorphic() { return true; } - public Object getRealObject() { - return null; - } - - public Class getElementClass() { + Class getElementClass() { return c.getDeclaringClass(); } - public Object create( + Object create( Project project, Object parent, Object child) throws InvocationTargetException, IllegalAccessException, InstantiationException { @@ -386,12 +361,6 @@ public final class IntrospectionHelper implements BuildListener { m.invoke(parent, new Object[] {child}); return child; } - public void store(Object parent, Object child) - throws InvocationTargetException, - IllegalAccessException, InstantiationException { - - } - }); } catch (NoSuchMethodException nse) { // ignore @@ -453,7 +422,7 @@ public final class IntrospectionHelper implements BuildListener { * * @return a helper for the specified class */ - public static synchronized IntrospectionHelper getHelper(Project p, + public static synchronized IntrospectionHelper getHelper(Project p, Class c) { IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c); if (ih == null) { @@ -529,7 +498,7 @@ public final class IntrospectionHelper implements BuildListener { throw new BuildException(t); } } - + /** * Adds PCDATA to an element, using the element's @@ -619,24 +588,11 @@ public final class IntrospectionHelper implements BuildListener { (child == null ? "" : child.getNamespace()), name, qName); if (nestedElement != null) { - nc = new NestedCreator() { - public boolean isPolyMorphic() { - return false; - } - public Class getElementClass() { - return null; - } - - public Object getRealObject() { - return null; - } - - public Object create( + nc = new NestedCreator(null) { + Object create( Project project, Object parent, Object ignore) { return nestedElement; } - public void store(Object parent, Object child) { - } }; } } @@ -645,24 +601,11 @@ public final class IntrospectionHelper implements BuildListener { final Object nestedElement = dc.createDynamicElement(name.toLowerCase(Locale.US)); if (nestedElement != null) { - nc = new NestedCreator() { - public boolean isPolyMorphic() { - return false; - } - public Class getElementClass() { - return null; - } - - public Object getRealObject() { - return null; - } - - public Object create( + nc = new NestedCreator(null) { + Object create( Project project, Object parent, Object ignore) { return nestedElement; } - public void store(Object parent, Object child) { - } }; } } @@ -730,7 +673,6 @@ public final class IntrospectionHelper implements BuildListener { * @param ue The unknown element associated with the element. * @return a creator object to create and store the element instance. */ - public Creator getElementCreator( Project project, String parentUri, Object parent, String elementName, UnknownElement ue) { @@ -740,7 +682,37 @@ public final class IntrospectionHelper implements BuildListener { } /** - * Indicate if this element supports a nested element of the + * Indicates whether the introspected class is a dynamic one, + * supporting arbitrary nested elements and/or attributes. + * + * @return true if the introspected class is dynamic; + * false otherwise. + * @since Ant 1.6.3 + * + * @see DynamicElement + * @see DynamicElementNS + */ + public boolean isDynamic() { + return DynamicElement.class.isAssignableFrom(bean) + || DynamicElementNS.class.isAssignableFrom(bean); + } + + /** + * Indicates whether the introspected class is a task container, + * supporting arbitrary nested tasks/types. + * + * @return true if the introspected class is a container; + * false otherwise. + * @since Ant 1.6.3 + * + * @see TaskContainer + */ + public boolean isContainer() { + return TaskContainer.class.isAssignableFrom(bean); + } + + /** + * Indicates if this element supports a nested element of the * given name. * * @param elementName the name of the nested element being checked @@ -749,8 +721,7 @@ public final class IntrospectionHelper implements BuildListener { */ public boolean supportsNestedElement(String elementName) { return nestedCreators.containsKey(elementName.toLowerCase(Locale.US)) - || DynamicElement.class.isAssignableFrom(bean) - || DynamicElementNS.class.isAssignableFrom(bean) + || isDynamic() || addTypeMethods.size() != 0; } @@ -776,8 +747,7 @@ public final class IntrospectionHelper implements BuildListener { return ( nestedCreators.containsKey(name.toLowerCase(Locale.US)) && (uri.equals(parentUri) || uri.equals(""))) - || DynamicElement.class.isAssignableFrom(bean) - || DynamicElementNS.class.isAssignableFrom(bean) + || isDynamic() || addTypeMethods.size() != 0; } @@ -875,6 +845,72 @@ public final class IntrospectionHelper implements BuildListener { return at; } + /** + * Returns the addText method when the introspected + * class supports nested text. + * + * @return the method on this introspected class that adds nested text. + * Cannot be null. + * @throws BuildException if the introspected class does not + * support the nested text. + * @since Ant 1.6.3 + */ + public Method getAddTextMethod() + throws BuildException { + if (!supportsCharacters()) { + String msg = "Class " + bean.getName() + + " doesn't support nested text data."; + throw new BuildException(msg); + } + return addText; + } + + /** + * Returns the adder or creator method of a named nested element. + * + * @param elementName The name of the attribute to find the setter + * method of. Must not be null. + * @return the method on this introspected class that adds or creates this + * nested element. Can be null when the introspected + * class is a dynamic configurator! + * @throws BuildException if the introspected class does not + * support the named nested element. + * @since Ant 1.6.3 + */ + public Method getElementMethod(String elementName) + throws BuildException { + Object creator = nestedCreators.get(elementName); + if (creator == null) { + String msg = "Class " + bean.getName() + + " doesn't support the nested \"" + elementName + + "\" element."; + throw new BuildException(msg); + } + return ((NestedCreator) creator).method; + } + + /** + * Returns the setter method of a named attribute. + * + * @param attributeName The name of the attribute to find the setter + * method of. Must not be null. + * @return the method on this introspected class that sets this attribute. + * This will never be null. + * @throws BuildException if the introspected class does not + * support the named attribute. + * @since Ant 1.6.3 + */ + public Method getAttributeMethod(String attributeName) + throws BuildException { + Object setter = attributeSetters.get(attributeName); + if (setter == null) { + String msg = "Class " + bean.getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException(msg); + } + return ((AttributeSetter) setter).method; + } + /** * Returns whether or not the introspected class supports PCDATA. * @@ -890,22 +926,78 @@ public final class IntrospectionHelper implements BuildListener { * * @return an enumeration of the names of the attributes supported * by the introspected class. + * @see #getAttributeMap */ public Enumeration getAttributes() { return attributeSetters.keys(); } + /** + * Returns a read-only map of attributes supported + * by the introspected class. + * + * @return an attribute name to attribute Class + * unmodifiable map. Can be empty, but never null. + * @since Ant 1.6.3 + */ + public Map getAttributeMap() { + if (attributeTypes.size() < 1) { + return Collections.EMPTY_MAP; + } + return Collections.unmodifiableMap(attributeTypes); + } + /** * Returns an enumeration of the names of the nested elements supported * by the introspected class. * * @return an enumeration of the names of the nested elements supported * by the introspected class. + * @see #getNestedElementMap */ public Enumeration getNestedElements() { return nestedTypes.keys(); } + /** + * Returns a read-only map of nested elements supported + * by the introspected class. + * + * @return a nested-element name to nested-element Class + * unmodifiable map. Can be empty, but never null. + * @since Ant 1.6.3 + */ + public Map getNestedElementMap() { + if (nestedTypes.size() < 1) { + return Collections.EMPTY_MAP; + } + return Collections.unmodifiableMap(nestedTypes); + } + + /** + * Returns a read-only list of extension points supported + * by the introspected class. + *

+ * A task/type or nested element with void methods named add() + * or addConfigured(), taking a single class or interface + * argument, supports extensions point. This method returns the list of + * all these void add[Configured](type) methods. + * + * @return a list of void, single argument add() or addConfigured() + * Methods of all supported extension points. + * These methods are sorted such that if the argument type of a + * method derives from another type also an argument of a method + * of this list, the method with the most derived argument will + * always appear first. Can be empty, but never null. + * @since Ant 1.6.3 + */ + public List getExtensionPoints() { + if (addTypeMethods.size() < 1) { + return Collections.EMPTY_LIST; + } + return Collections.unmodifiableList(addTypeMethods); + } + /** * Creates an implementation of AttributeSetter for the given * attribute type. Conversions (where necessary) are automatically @@ -947,7 +1039,7 @@ public final class IntrospectionHelper implements BuildListener { // simplest case - setAttribute expects String if (java.lang.String.class.equals(reflectedArg)) { - return new AttributeSetter() { + return new AttributeSetter(m) { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new String[] {value}); @@ -956,7 +1048,7 @@ public final class IntrospectionHelper implements BuildListener { // char and Character get special treatment - take the first character } else if (java.lang.Character.class.equals(reflectedArg)) { - return new AttributeSetter() { + return new AttributeSetter(m) { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { if (value.length() == 0) { @@ -971,7 +1063,7 @@ public final class IntrospectionHelper implements BuildListener { // boolean and Boolean get special treatment because we // have a nice method in Project } else if (java.lang.Boolean.class.equals(reflectedArg)) { - return new AttributeSetter() { + return new AttributeSetter(m) { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, @@ -983,7 +1075,7 @@ public final class IntrospectionHelper implements BuildListener { // Class doesn't have a String constructor but a decent factory method } else if (java.lang.Class.class.equals(reflectedArg)) { - return new AttributeSetter() { + return new AttributeSetter(m) { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException, BuildException { try { @@ -996,7 +1088,7 @@ public final class IntrospectionHelper implements BuildListener { // resolve relative paths through Project } else if (java.io.File.class.equals(reflectedArg)) { - return new AttributeSetter() { + return new AttributeSetter(m) { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException { m.invoke(parent, new File[] {p.resolveFile(value)}); @@ -1006,7 +1098,7 @@ public final class IntrospectionHelper implements BuildListener { // EnumeratedAttributes have their own helper class } else if (EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) { - return new AttributeSetter() { + return new AttributeSetter(m) { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException, BuildException { try { @@ -1045,7 +1137,7 @@ public final class IntrospectionHelper implements BuildListener { final boolean finalIncludeProject = includeProject; final Constructor finalConstructor = c; - return new AttributeSetter() { + return new AttributeSetter(m) { public void set(Project p, Object parent, String value) throws InvocationTargetException, IllegalAccessException, BuildException { try { @@ -1235,25 +1327,45 @@ public final class IntrospectionHelper implements BuildListener { * Internal interface used to create nested elements. Not documented * in detail for reasons of source code readability. */ - private interface NestedCreator { - boolean isPolyMorphic(); - Class getElementClass(); - Object getRealObject(); - Object create(Project project, Object parent, Object child) - throws InvocationTargetException, IllegalAccessException, InstantiationException; + private abstract class NestedCreator { + Method method; // the method called to add/create the nested element + NestedCreator(Method m) { + this.method = m; + } + boolean isPolyMorphic() { + return false; + } + Class getElementClass() { + return null; + } + Object getRealObject() { + return null; + } + abstract Object create(Project project, Object parent, Object child) + throws InvocationTargetException, + IllegalAccessException, + InstantiationException; void store(Object parent, Object child) - throws InvocationTargetException, IllegalAccessException, InstantiationException; + throws InvocationTargetException, + IllegalAccessException, + InstantiationException { + // DO NOTHING + } } - /** * Internal interface used to setting element attributes. Not documented * in detail for reasons of source code readability. */ - private interface AttributeSetter { - void set(Project p, Object parent, String value) - throws InvocationTargetException, IllegalAccessException, - BuildException; + private abstract class AttributeSetter { + Method method; // the method called to set the attribute + AttributeSetter(Method m) { + this.method = m; + } + abstract void set(Project p, Object parent, String value) + throws InvocationTargetException, + IllegalAccessException, + BuildException; } /** @@ -1318,7 +1430,6 @@ public final class IntrospectionHelper implements BuildListener { public void messageLogged(BuildEvent event) { } - /** * */ @@ -1353,15 +1464,8 @@ public final class IntrospectionHelper implements BuildListener { final Object nestedObject = addedObject; final Object realObject = rObject; - return new NestedCreator() { - public boolean isPolyMorphic() { - return false; - } - - public Class getElementClass() { - return null; - } - public Object create(Project project, Object parent, Object ignore) + return new NestedCreator(method) { + Object create(Project project, Object parent, Object ignore) throws InvocationTargetException, IllegalAccessException { if (!method.getName().endsWith("Configured")) { method.invoke(parent, new Object[]{realObject}); @@ -1369,11 +1473,11 @@ public final class IntrospectionHelper implements BuildListener { return nestedObject; } - public Object getRealObject() { + Object getRealObject() { return realObject; } - public void store(Object parent, Object child) + void store(Object parent, Object child) throws InvocationTargetException, IllegalAccessException, InstantiationException { if (method.getName().endsWith("Configured")) { @@ -1389,7 +1493,6 @@ public final class IntrospectionHelper implements BuildListener { * ordered so that the more derived classes * are first. */ - private void insertAddTypeMethod(Method method) { Class argClass = method.getParameterTypes()[0]; for (int c = 0; c < addTypeMethods.size(); ++c) { @@ -1406,10 +1509,9 @@ public final class IntrospectionHelper implements BuildListener { addTypeMethods.add(method); } - /** * Search the list of methods to find the first method - * that has a parameter that accepts the nested element object + * that has a parameter that accepts the nested element object. */ private Method findMatchingMethod(Class paramClass, List methods) { Class matchedClass = null; diff --git a/src/testcases/org/apache/tools/ant/IntrospectionHelperTest.java b/src/testcases/org/apache/tools/ant/IntrospectionHelperTest.java index 95f1a3e66..a17c50d27 100644 --- a/src/testcases/org/apache/tools/ant/IntrospectionHelperTest.java +++ b/src/testcases/org/apache/tools/ant/IntrospectionHelperTest.java @@ -20,7 +20,15 @@ package org.apache.tools.ant; import junit.framework.TestCase; import junit.framework.AssertionFailedError; import java.io.File; -import java.util.*; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; import org.apache.tools.ant.taskdefs.condition.Os; /** @@ -31,6 +39,7 @@ import org.apache.tools.ant.taskdefs.condition.Os; public class IntrospectionHelperTest extends TestCase { private Project p; + private IntrospectionHelper ih; private static final String projectBasedir = File.separator; public IntrospectionHelperTest(String name) { @@ -40,17 +49,18 @@ public class IntrospectionHelperTest extends TestCase { public void setUp() { p = new Project(); p.setBasedir(projectBasedir); + ih = IntrospectionHelper.getHelper(getClass()); } - public void testAddText() throws BuildException { - IntrospectionHelper ih = IntrospectionHelper.getHelper(java.lang.String.class); - try { - ih.addText(p, "", "test"); - fail("String doesn\'t support addText"); - } catch (BuildException be) { - } + public void testIsDynamic() { + assertTrue("Not dynamic", false == ih.isDynamic()); + } - ih = IntrospectionHelper.getHelper(getClass()); + public void testIsContainer() { + assertTrue("Not a container", false == ih.isContainer()); + } + + public void testAddText() throws BuildException { ih.addText(p, this, "test"); try { ih.addText(p, this, "test2"); @@ -58,14 +68,31 @@ public class IntrospectionHelperTest extends TestCase { } catch (BuildException be) { assertTrue(be.getException() instanceof AssertionFailedError); } + + ih = IntrospectionHelper.getHelper(String.class); + try { + ih.addText(p, "", "test"); + fail("String doesn\'t support addText"); + } catch (BuildException be) { + } + } + + public void testGetAddTextMethod() { + Method m = ih.getAddTextMethod(); + assertMethod(m, "addText", String.class, "test", "bing!"); + + ih = IntrospectionHelper.getHelper(String.class); + try { + m = ih.getAddTextMethod(); + } catch (BuildException e) {} } public void testSupportsCharacters() { - IntrospectionHelper ih = IntrospectionHelper.getHelper(java.lang.String.class); - assertTrue("String doesn\'t support addText", !ih.supportsCharacters()); - ih = IntrospectionHelper.getHelper(getClass()); assertTrue("IntrospectionHelperTest supports addText", - ih.supportsCharacters()); + ih.supportsCharacters()); + + ih = IntrospectionHelper.getHelper(String.class); + assertTrue("String doesn\'t support addText", !ih.supportsCharacters()); } public void addText(String text) { @@ -73,7 +100,6 @@ public class IntrospectionHelperTest extends TestCase { } public void testElementCreators() throws BuildException { - IntrospectionHelper ih = IntrospectionHelper.getHelper(getClass()); try { ih.getElementType("one"); fail("don't have element type one"); @@ -99,7 +125,7 @@ public class IntrospectionHelperTest extends TestCase { fail("createFive returns primitive type"); } catch (BuildException be) { } - assertEquals(java.lang.String.class, ih.getElementType("six")); + assertEquals(String.class, ih.getElementType("six")); assertEquals("test", ih.createElement(p, this, "six")); try { @@ -132,7 +158,7 @@ public class IntrospectionHelperTest extends TestCase { fail("no primitive constructor for java.lang.Class"); } catch (BuildException be) { } - assertEquals(java.lang.StringBuffer.class, ih.getElementType("thirteen")); + assertEquals(StringBuffer.class, ih.getElementType("thirteen")); assertEquals("test", ih.createElement(p, this, "thirteen").toString()); try { @@ -150,23 +176,69 @@ public class IntrospectionHelperTest extends TestCase { } } + private Map getExpectedNestedElements() { + Map elemMap = new Hashtable(); + elemMap.put("six", String.class); + elemMap.put("thirteen", StringBuffer.class); + elemMap.put("fourteen", StringBuffer.class); + elemMap.put("fifteen", StringBuffer.class); + return elemMap; + } + public void testGetNestedElements() { - Hashtable h = new Hashtable(); - h.put("six", java.lang.String.class); - h.put("thirteen", java.lang.StringBuffer.class); - h.put("fourteen", java.lang.StringBuffer.class); - h.put("fifteen", java.lang.StringBuffer.class); - IntrospectionHelper ih = IntrospectionHelper.getHelper(getClass()); + Map elemMap = getExpectedNestedElements(); Enumeration e = ih.getNestedElements(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); - Class expect = (Class) h.get(name); + Class expect = (Class) elemMap.get(name); assertNotNull("Support for "+name+" in IntrospectioNHelperTest?", expect); assertEquals("Return type of "+name, expect, ih.getElementType(name)); - h.remove(name); + elemMap.remove(name); + } + assertTrue("Found all", elemMap.isEmpty()); + } + + public void testGetNestedElementMap() { + Map elemMap = getExpectedNestedElements(); + Map actualMap = ih.getNestedElementMap(); + for (Iterator i = actualMap.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + String elemName = (String) entry.getKey(); + Class elemClass = (Class) elemMap.get(elemName); + assertNotNull("Support for " + elemName + + " in IntrospectionHelperTest?", elemClass); + assertEquals("Type of " + elemName, elemClass, entry.getValue()); + elemMap.remove(elemName); + } + assertTrue("Found all", elemMap.isEmpty()); + + // Check it's a read-only map. + try { + actualMap.clear(); + } catch (UnsupportedOperationException e) {} + } + + public void testGetElementMethod() { + assertElemMethod("six", "createSix", String.class, null); + assertElemMethod("thirteen", "addThirteen", null, StringBuffer.class); + assertElemMethod("fourteen", "addFourteen", null, StringBuffer.class); + assertElemMethod("fifteen", "createFifteen", StringBuffer.class, null); + } + + private void assertElemMethod(String elemName, String methodName, + Class returnType, Class methodArg) { + Method m = ih.getElementMethod(elemName); + assertEquals("Method name", methodName, m.getName()); + Class expectedReturnType = (returnType == null)? Void.TYPE: returnType; + assertEquals("Return type", expectedReturnType, m.getReturnType()); + Class[] args = m.getParameterTypes(); + if (methodArg != null) { + assertEquals("Arg Count", 1, args.length); + assertEquals("Arg Type", methodArg, args[0]); + } else { + assertEquals("Arg Count", 0, args.length); } - assertTrue("Found all", h.isEmpty()); } public Object createTwo(String s) { @@ -214,7 +286,6 @@ public class IntrospectionHelperTest extends TestCase { } public void testAttributeSetters() throws BuildException { - IntrospectionHelper ih = IntrospectionHelper.getHelper(getClass()); try { ih.setAttribute(p, this, "one", "test"); fail("setOne doesn't exist"); @@ -344,21 +415,21 @@ public class IntrospectionHelperTest extends TestCase { } } - public void testGetAttributes() { - Hashtable h = new Hashtable(); - h.put("seven", java.lang.String.class); - h.put("eight", java.lang.Integer.TYPE); - h.put("nine", java.lang.Integer.class); - h.put("ten", java.io.File.class); - h.put("eleven", java.lang.Boolean.TYPE); - h.put("twelve", java.lang.Boolean.class); - h.put("thirteen", java.lang.Class.class); - h.put("fourteen", java.lang.StringBuffer.class); - h.put("fifteen", java.lang.Character.TYPE); - h.put("sixteen", java.lang.Character.class); - h.put("seventeen", java.lang.Byte.TYPE); - h.put("eightteen", java.lang.Short.TYPE); - h.put("nineteen", java.lang.Double.TYPE); + private Map getExpectedAttributes() { + Map attrMap = new Hashtable(); + attrMap.put("seven", String.class); + attrMap.put("eight", Integer.TYPE); + attrMap.put("nine", Integer.class); + attrMap.put("ten", File.class); + attrMap.put("eleven", Boolean.TYPE); + attrMap.put("twelve", Boolean.class); + attrMap.put("thirteen", Class.class); + attrMap.put("fourteen", StringBuffer.class); + attrMap.put("fifteen", Character.TYPE); + attrMap.put("sixteen", Character.class); + attrMap.put("seventeen", Byte.TYPE); + attrMap.put("eightteen", Short.TYPE); + attrMap.put("nineteen", Double.TYPE); /* * JUnit 3.7 adds a getName method to TestCase - so we now @@ -367,20 +438,85 @@ public class IntrospectionHelperTest extends TestCase { * * Simply add it here and remove it after the tests. */ - h.put("name", java.lang.String.class); + attrMap.put("name", String.class); + + return attrMap; + } - IntrospectionHelper ih = IntrospectionHelper.getHelper(getClass()); + public void testGetAttributes() { + Map attrMap = getExpectedAttributes(); Enumeration e = ih.getAttributes(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); - Class expect = (Class) h.get(name); + Class expect = (Class) attrMap.get(name); assertNotNull("Support for "+name+" in IntrospectionHelperTest?", expect); assertEquals("Type of "+name, expect, ih.getAttributeType(name)); - h.remove(name); + attrMap.remove(name); } - h.remove("name"); - assertTrue("Found all", h.isEmpty()); + attrMap.remove("name"); + assertTrue("Found all", attrMap.isEmpty()); + } + + public void testGetAttributeMap() { + Map attrMap = getExpectedAttributes(); + Map actualMap = ih.getAttributeMap(); + for (Iterator i = actualMap.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + String attrName = (String) entry.getKey(); + Class attrClass = (Class) attrMap.get(attrName); + assertNotNull("Support for " + attrName + + " in IntrospectionHelperTest?", attrClass); + assertEquals("Type of " + attrName, attrClass, entry.getValue()); + attrMap.remove(attrName); + } + attrMap.remove("name"); + assertTrue("Found all", attrMap.isEmpty()); + + // Check it's a read-only map. + try { + actualMap.clear(); + } catch (UnsupportedOperationException e) {} + } + + public void testGetAttributeMethod() { + assertAttrMethod("seven", "setSeven", String.class, + "2", "3"); + assertAttrMethod("eight", "setEight", Integer.TYPE, + new Integer(2), new Integer(3)); + assertAttrMethod("nine", "setNine", Integer.class, + new Integer(2), new Integer(3)); + assertAttrMethod("ten", "setTen", File.class, + new File(projectBasedir + 2), new File("toto")); + assertAttrMethod("eleven", "setEleven", Boolean.TYPE, + Boolean.FALSE, Boolean.TRUE); + assertAttrMethod("twelve", "setTwelve", Boolean.class, + Boolean.FALSE, Boolean.TRUE); + assertAttrMethod("thirteen", "setThirteen", Class.class, + Project.class, Map.class); + assertAttrMethod("fourteen", "setFourteen", StringBuffer.class, + new StringBuffer("2"), new StringBuffer("3")); + assertAttrMethod("fifteen", "setFifteen", Character.TYPE, + new Character('a'), new Character('b')); + assertAttrMethod("sixteen", "setSixteen", Character.class, + new Character('a'), new Character('b')); + assertAttrMethod("seventeen", "setSeventeen", Byte.TYPE, + new Byte((byte)17), new Byte((byte)10)); + assertAttrMethod("eightteen", "setEightteen", Short.TYPE, + new Short((short)18), new Short((short)10)); + assertAttrMethod("nineteen", "setNineteen", Double.TYPE, + new Double(19), new Double((short)10)); + + try { + assertAttrMethod("onehundred", null, null, null, null); + fail("Should have raised a BuildException!"); + } catch (BuildException e) {} + } + + private void assertAttrMethod(String attrName, String methodName, + Class methodArg, Object arg, Object badArg) { + Method m = ih.getAttributeMethod(attrName); + assertMethod(m, methodName, methodArg, arg, badArg); } public int setTwo(String s) { @@ -408,12 +544,14 @@ public class IntrospectionHelperTest extends TestCase { } public void setTen(File f) { + String path = f.getAbsolutePath(); if (Os.isFamily("unix") || Os.isFamily("openvms")) { - assertEquals(projectBasedir+"2", f.getAbsolutePath()); + assertEquals(projectBasedir+"2", path); } else if (Os.isFamily("netware")) { - assertEquals(projectBasedir+"2", f.getAbsolutePath().toLowerCase(Locale.US)); + assertEquals(projectBasedir+"2", path.toLowerCase(Locale.US)); } else { - assertEquals(":"+projectBasedir+"2", f.getAbsolutePath().toLowerCase(Locale.US).substring(1)); + assertEquals(":"+projectBasedir+"2", + path.toLowerCase(Locale.US).substring(1)); } } @@ -453,4 +591,82 @@ public class IntrospectionHelperTest extends TestCase { assertEquals(19, d, 1e-6); } -}// IntrospectionHelperTest + public void testGetExtensionPoints() { + List extensions = ih.getExtensionPoints(); + assertEquals("extension count", 3, extensions.size()); + + assertExtMethod(extensions.get(0), "add", Number.class, + new Integer(2), new Integer(3)); + + // addConfigured(Hashtable) should come before addConfigured(Map) + assertExtMethod(extensions.get(1), "addConfigured", Hashtable.class, + makeTable("key", "value"), makeTable("1", "2")); + + assertExtMethod(extensions.get(2), "addConfigured", Map.class, + Collections.EMPTY_MAP, makeTable("1", "2")); + } + + private void assertExtMethod(Object mo, String methodName, Class methodArg, + Object arg, Object badArg) { + assertMethod((Method) mo, methodName, methodArg, arg, badArg); + } + + private void assertMethod(Method m, String methodName, Class methodArg, + Object arg, Object badArg) { + assertEquals("Method name", methodName, m.getName()); + assertEquals("Return type", Void.TYPE, m.getReturnType()); + Class[] args = m.getParameterTypes(); + assertEquals("Arg Count", 1, args.length); + assertEquals("Arg Type", methodArg, args[0]); + + try { + m.invoke(this, new Object[] { arg }); + } catch (IllegalAccessException e) { + throw new BuildException(e); + } catch (InvocationTargetException e) { + throw new BuildException(e); + } + + try { + m.invoke(this, new Object[] { badArg }); + fail("Should have raised an assertion exception"); + } catch (IllegalAccessException e) { + throw new BuildException(e); + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + assertTrue(t instanceof junit.framework.AssertionFailedError); + } + } + + public List add(List l) { + // INVALID extension point + return null; + } + + public void add(Number n) { + // Valid extension point + assertEquals(2, n.intValue()); + } + + public void add(List l, int i) { + // INVALID extension point + } + + public void addConfigured(Map m) { + // Valid extension point + assertTrue(Collections.EMPTY_MAP == m); + } + + public void addConfigured(Hashtable h) { + // Valid extension point, more derived than Map above, but *after* it! + assertEquals(makeTable("key", "value"), h); + } + + private Hashtable makeTable(Object key, Object value) { + Hashtable table = new Hashtable(); + table.put(key, value); + return table; + } + +} // IntrospectionHelperTest +