diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/IntrospectionHelper.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/IntrospectionHelper.java | 1745 |
1 files changed, 1745 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/IntrospectionHelper.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/IntrospectionHelper.java new file mode 100644 index 00000000..30086563 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/IntrospectionHelper.java @@ -0,0 +1,1745 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant; + +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.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.tools.ant.taskdefs.PreSetDef; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.resources.FileProvider; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.util.StringUtils; + +/** + * Helper class that collects the methods a task or nested element + * holds to set attributes, create nested elements or hold PCDATA + * elements. + * + * It contains hashtables containing classes that use introspection + * to handle all the invocation of the project-component specific methods. + * + * This class is somewhat complex, as it implements the O/X mapping between + * Ant XML and Java class instances. This is not the best place for someone new + * to Ant to start contributing to the codebase, as a change here can break the + * entire system in interesting ways. Always run a full test of Ant before checking + * in/submitting changes to this file. + * + * The class is final and has a private constructor. + * To get an instance for a specific (class,project) combination, + * use {@link #getHelper(Project,Class)}. + * This may return an existing version, or a new one + * ...do not make any assumptions about its uniqueness, or its validity after the Project + * instance has finished its build. + * + */ +public final class IntrospectionHelper { + + /** + * Helper instances we've already created (Class.getName() to IntrospectionHelper). + */ + private static final Map<String, IntrospectionHelper> HELPERS = new Hashtable<String, IntrospectionHelper>(); + + /** + * Map from primitive types to wrapper classes for use in + * createAttributeSetter (Class to Class). Note that char + * and boolean are in here even though they get special treatment + * - this way we only need to test for the wrapper class. + */ + private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPE_MAP = new HashMap<Class<?>, Class<?>>(8); + + // Set up PRIMITIVE_TYPE_MAP + static { + final Class<?>[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE, + Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE}; + final Class<?>[] wrappers = {Boolean.class, Byte.class, Character.class, Short.class, + Integer.class, Long.class, Float.class, Double.class}; + for (int i = 0; i < primitives.length; i++) { + PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]); + } + } + + private static final int MAX_REPORT_NESTED_TEXT = 20; + private static final String ELLIPSIS = "..."; + + /** + * Map from attribute names to attribute types + * (String to Class). + */ + private final Hashtable<String, Class<?>> attributeTypes = new Hashtable<String, Class<?>>(); + + /** + * Map from attribute names to attribute setter methods + * (String to AttributeSetter). + */ + private final Hashtable<String, AttributeSetter> attributeSetters = new Hashtable<String, AttributeSetter>(); + + /** + * Map from attribute names to nested types + * (String to Class). + */ + private final Hashtable<String, Class<?>> nestedTypes = new Hashtable<String, Class<?>>(); + + /** + * Map from attribute names to methods to create nested types + * (String to NestedCreator). + */ + private final Hashtable<String, NestedCreator> nestedCreators = new Hashtable<String, NestedCreator>(); + + /** + * Vector of methods matching add[Configured](Class) pattern. + */ + private final List<Method> addTypeMethods = new ArrayList<Method>(); + + /** + * The method to invoke to add PCDATA. + */ + private final Method addText; + + /** + * The class introspected by this instance. + */ + private final Class<?> bean; + + /** + * Sole constructor, which is private to ensure that all + * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}. + * Introspects the given class for bean-like methods. + * Each method is examined in turn, and the following rules are applied: + * <p> + * <ul> + * <li>If the method is <code>Task.setLocation(Location)</code>, + * <code>Task.setTaskType(String)</code> + * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These + * methods are handled differently elsewhere. + * <li><code>void addText(String)</code> is recognised as the method for + * adding PCDATA to a bean. + * <li><code>void setFoo(Bar)</code> is recognised as a method for + * setting the value of attribute <code>foo</code>, so long as + * <code>Bar</code> is non-void and is not an array type. + * As of Ant 1.8, a Resource or FileProvider parameter overrides a java.io.File parameter; + * in practice the only effect of this is to allow objects rendered from + * the 1.8 PropertyHelper implementation to be used as Resource parameters, + * since Resources set from Strings are resolved as project-relative files + * to preserve backward compatibility. Beyond this, non-String + * parameter types always overload String parameter types; these are + * the only guarantees made in terms of priority. + * <li><code>Foo createBar()</code> is recognised as a method for + * creating a nested element called <code>bar</code> of type + * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or + * array type. + * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a + * method for storing a pre-configured element called + * <code>foo</code> and of type <code>Bar</code>, so long as + * <code>Bar</code> is not an array, primitive or String type. + * <code>Bar</code> must have an accessible constructor taking no + * arguments. + * <li><code>void addFoo(Bar)</code> is recognised as a method for storing + * an element called <code>foo</code> and of type <code>Bar</code>, so + * long as <code>Bar</code> is not an array, primitive or String type. + * <code>Bar</code> must have an accessible constructor taking no + * arguments. This is distinct from the 'addConfigured' idiom in that + * the nested element is added to the parent immediately after it is + * constructed; in practice this means that <code>addFoo(Bar)</code> should + * do little or nothing with its argument besides storing it for later use. + * </ul> + * Note that only one method is retained to create/set/addConfigured/add + * any element or attribute. + * + * @param bean The bean type to introspect. + * Must not be <code>null</code>. + * + * @see #getHelper(Class) + */ + private IntrospectionHelper(final Class<?> bean) { + this.bean = bean; + final Method[] methods = bean.getMethods(); + Method addTextMethod = null; + for (int i = 0; i < methods.length; i++) { + final Method m = methods[i]; + final String name = m.getName(); + final Class<?> returnType = m.getReturnType(); + final Class<?>[] args = m.getParameterTypes(); + + // check of add[Configured](Class) pattern + if (args.length == 1 && java.lang.Void.TYPE.equals(returnType) + && ("add".equals(name) || "addConfigured".equals(name))) { + insertAddTypeMethod(m); + continue; + } + // not really user settable properties on tasks/project components + if (org.apache.tools.ant.ProjectComponent.class.isAssignableFrom(bean) + && args.length == 1 && isHiddenSetMethod(name, args[0])) { + continue; + } + // hide addTask for TaskContainers + if (isContainer() && 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])) { + addTextMethod = methods[i]; + } else if (name.startsWith("set") && java.lang.Void.TYPE.equals(returnType) + && args.length == 1 && !args[0].isArray()) { + final String propName = getPropertyName(name, "set"); + AttributeSetter as = attributeSetters.get(propName); + if (as != 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 (java.io.File.class.equals(args[0])) { + // Ant Resources/FileProviders override java.io.File + if (Resource.class.equals(as.type) || FileProvider.class.equals(as.type)) { + continue; + } + } + /* + In cases other than those just explicitly covered, + 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. + */ + } + as = createAttributeSetter(m, args[0], propName); + if (as != null) { + attributeTypes.put(propName, args[0]); + attributeSetters.put(propName, as); + } + } else if (name.startsWith("create") && !returnType.isArray() + && !returnType.isPrimitive() && args.length == 0) { + + final String propName = getPropertyName(name, "create"); + // 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 CreateNestedCreator(m)); + } + } 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 { + Constructor<?> constructor = null; + try { + constructor = args[0].getConstructor(); + } catch (final NoSuchMethodException ex) { + constructor = args[0].getConstructor(Project.class); + } + final String propName = getPropertyName(name, "addConfigured"); + nestedTypes.put(propName, args[0]); + nestedCreators.put(propName, new AddNestedCreator(m, + constructor, AddNestedCreator.ADD_CONFIGURED)); + } catch (final NoSuchMethodException nse) { + // ignore + } + } 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 { + Constructor<?> constructor = null; + try { + constructor = args[0].getConstructor(); + } catch (final NoSuchMethodException ex) { + constructor = args[0].getConstructor(Project.class); + } + final String propName = getPropertyName(name, "add"); + if (nestedTypes.get(propName) != null) { + /* + * Ignore this method as there is an addConfigured + * form of this method that has a higher + * priority + */ + continue; + } + nestedTypes.put(propName, args[0]); + nestedCreators.put(propName, new AddNestedCreator(m, + constructor, AddNestedCreator.ADD)); + } catch (final NoSuchMethodException nse) { + // ignore + } + } + } + addText = addTextMethod; + } + + /** + * Certain set methods are part of the Ant core interface to tasks and + * therefore not to be considered for introspection + * + * @param name the name of the set method + * @param type the type of the set method's parameter + * @return true if the given set method is to be hidden. + */ + private boolean isHiddenSetMethod(final String name, final Class<?> type) { + if ("setLocation".equals(name) && org.apache.tools.ant.Location.class.equals(type)) { + return true; + } + if ("setTaskType".equals(name) && java.lang.String.class.equals(type)) { + return true; + } + return false; + } + + /** + * Returns a helper for the given class, either from the cache + * or by creating a new instance. + * + * @param c The class for which a helper is required. + * Must not be <code>null</code>. + * + * @return a helper for the specified class + */ + public static synchronized IntrospectionHelper getHelper(final Class<?> c) { + return getHelper(null, c); + } + + /** + * Returns a helper for the given class, either from the cache + * or by creating a new instance. + * + * The method will make sure the helper will be cleaned up at the end of + * the project, and only one instance will be created for each class. + * + * @param p the project instance. Can be null, in which case the helper is not cached. + * @param c The class for which a helper is required. + * Must not be <code>null</code>. + * + * @return a helper for the specified class + */ + public static synchronized IntrospectionHelper getHelper(final Project p, final Class<?> c) { + IntrospectionHelper ih = HELPERS.get(c.getName()); + // If a helper cannot be found, or if the helper is for another + // classloader, create a new IH + if (ih == null || ih.bean != c) { + ih = new IntrospectionHelper(c); + if (p != null) { + // #30162: do *not* cache this if there is no project, as we + // cannot guarantee that the cache will be cleared. + HELPERS.put(c.getName(), ih); + } + } + return ih; + } + + /** + * Sets the named attribute in the given element, which is part of the + * given project. + * + * @param p The project containing the element. This is used when files + * need to be resolved. Must not be <code>null</code>. + * @param element The element to set the attribute in. Must not be + * <code>null</code>. + * @param attributeName The name of the attribute to set. Must not be + * <code>null</code>. + * @param value The value to set the attribute to. This may be interpreted + * or converted to the necessary type if the setter method + * doesn't accept an object of the supplied type. + * + * @exception BuildException if the introspected class doesn't support + * the given attribute, or if the setting + * method fails. + */ + public void setAttribute(final Project p, final Object element, final String attributeName, + final Object value) throws BuildException { + final AttributeSetter as = attributeSetters.get( + attributeName.toLowerCase(Locale.ENGLISH)); + if (as == null && value != null) { + if (element instanceof DynamicAttributeNS) { + final DynamicAttributeNS dc = (DynamicAttributeNS) element; + final String uriPlusPrefix = ProjectHelper.extractUriFromComponentName(attributeName); + final String uri = ProjectHelper.extractUriFromComponentName(uriPlusPrefix); + final String localName = ProjectHelper.extractNameFromComponentName(attributeName); + final String qName = "".equals(uri) ? localName : uri + ":" + localName; + dc.setDynamicAttribute(uri, localName, qName, value.toString()); + return; + } + if (element instanceof DynamicObjectAttribute) { + final DynamicObjectAttribute dc = (DynamicObjectAttribute) element; + dc.setDynamicAttribute(attributeName.toLowerCase(Locale.ENGLISH), value); + return; + } + if (element instanceof DynamicAttribute) { + final DynamicAttribute dc = (DynamicAttribute) element; + dc.setDynamicAttribute(attributeName.toLowerCase(Locale.ENGLISH), value.toString()); + return; + } + if (attributeName.indexOf(':') >= 0) { + return; // Ignore attribute from unknown uri's + } + final String msg = getElementName(p, element) + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new UnsupportedAttributeException(msg, attributeName); + } + try { + as.setObject(p, element, value); + } catch (final IllegalAccessException ie) { + // impossible as getMethods should only return public methods + throw new BuildException(ie); + } catch (final InvocationTargetException ite) { + throw extractBuildException(ite); + } + } + + /** + * Sets the named attribute in the given element, which is part of the + * given project. + * + * @param p The project containing the element. This is used when files + * need to be resolved. Must not be <code>null</code>. + * @param element The element to set the attribute in. Must not be + * <code>null</code>. + * @param attributeName The name of the attribute to set. Must not be + * <code>null</code>. + * @param value The value to set the attribute to. This may be interpreted + * or converted to the necessary type if the setter method + * doesn't just take a string. Must not be <code>null</code>. + * + * @exception BuildException if the introspected class doesn't support + * the given attribute, or if the setting + * method fails. + */ + public void setAttribute(final Project p, final Object element, final String attributeName, + final String value) throws BuildException { + setAttribute(p, element, attributeName, (Object) value); + } + + /** + * Adds PCDATA to an element, using the element's + * <code>void addText(String)</code> method, if it has one. If no + * such method is present, a BuildException is thrown if the + * given text contains non-whitespace. + * + * @param project The project which the element is part of. + * Must not be <code>null</code>. + * @param element The element to add the text to. + * Must not be <code>null</code>. + * @param text The text to add. + * Must not be <code>null</code>. + * + * @exception BuildException if non-whitespace text is provided and no + * method is available to handle it, or if + * the handling method fails. + */ + public void addText(final Project project, final Object element, String text) + throws BuildException { + if (addText == null) { + text = text.trim(); + // Element doesn't handle text content + if (text.length() == 0) { + // Only whitespace - ignore + return; + } + // Not whitespace - fail + throw new BuildException(project.getElementName(element) + + " doesn't support nested text data (\"" + condenseText(text) + "\")."); + } + try { + addText.invoke(element, new Object[] {text}); + } catch (final IllegalAccessException ie) { + // impossible as getMethods should only return public methods + throw new BuildException(ie); + } catch (final InvocationTargetException ite) { + throw extractBuildException(ite); + } + } + + /** + * part of the error message created by {@link #throwNotSupported + * throwNotSupported}. + * @since Ant 1.8.0 + */ + protected static final String NOT_SUPPORTED_CHILD_PREFIX = + " doesn't support the nested \""; + + /** + * part of the error message created by {@link #throwNotSupported + * throwNotSupported}. + * @since Ant 1.8.0 + */ + protected static final String NOT_SUPPORTED_CHILD_POSTFIX = "\" element."; + + /** + * Utility method to throw a NotSupported exception + * + * @param project the Project instance. + * @param parent the object which doesn't support a requested element + * @param elementName the name of the Element which is trying to be created. + */ + public void throwNotSupported(final Project project, final Object parent, final String elementName) { + final String msg = project.getElementName(parent) + + NOT_SUPPORTED_CHILD_PREFIX + elementName + + NOT_SUPPORTED_CHILD_POSTFIX; + throw new UnsupportedElementException(msg, elementName); + } + + /** + * Get the specific NestedCreator for a given project/parent/element combination + * @param project ant project + * @param parentUri URI of the parent. + * @param parent the parent class + * @param elementName element to work with. This can contain + * a URI,localname tuple of of the form uri:localname + * @param child the bit of XML to work with + * @return a nested creator that can handle the child elements. + * @throws BuildException if the parent does not support child elements of that name + */ + private NestedCreator getNestedCreator( + final Project project, String parentUri, final Object parent, + final String elementName, final UnknownElement child) throws BuildException { + + String uri = ProjectHelper.extractUriFromComponentName(elementName); + final String name = ProjectHelper.extractNameFromComponentName(elementName); + + if (uri.equals(ProjectHelper.ANT_CORE_URI)) { + uri = ""; + } + if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) { + parentUri = ""; + } + NestedCreator nc = null; + if (uri.equals(parentUri) || uri.length() == 0) { + nc = nestedCreators.get(name.toLowerCase(Locale.ENGLISH)); + } + if (nc == null) { + nc = createAddTypeCreator(project, parent, elementName); + } + if (nc == null && + (parent instanceof DynamicElementNS + || parent instanceof DynamicElement) + ) { + final String qName = child == null ? name : child.getQName(); + final Object nestedElement = + createDynamicElement(parent, + child == null ? "" : child.getNamespace(), + name, qName); + if (nestedElement != null) { + nc = new NestedCreator(null) { + @Override + Object create(final Project project, final Object parent, final Object ignore) { + return nestedElement; + } + }; + } + } + if (nc == null) { + throwNotSupported(project, parent, elementName); + } + return nc; + } + + /** + * Invokes the "correct" createDynamicElement method on parent in + * order to obtain a child element by name. + * + * @since Ant 1.8.0. + */ + private Object createDynamicElement(final Object parent, final String ns, + final String localName, final String qName) { + Object nestedElement = null; + if (parent instanceof DynamicElementNS) { + final DynamicElementNS dc = (DynamicElementNS) parent; + nestedElement = dc.createDynamicElement(ns, localName, qName); + } + if (nestedElement == null && parent instanceof DynamicElement) { + final DynamicElement dc = (DynamicElement) parent; + nestedElement = + dc.createDynamicElement(localName.toLowerCase(Locale.ENGLISH)); + } + return nestedElement; + } + + /** + * Creates a named nested element. Depending on the results of the + * initial introspection, either a method in the given parent instance + * or a simple no-arg constructor is used to create an instance of the + * specified element type. + * + * @param project Project to which the parent object belongs. + * Must not be <code>null</code>. If the resulting + * object is an instance of ProjectComponent, its + * Project reference is set to this parameter value. + * @param parent Parent object used to create the instance. + * Must not be <code>null</code>. + * @param elementName Name of the element to create an instance of. + * Must not be <code>null</code>. + * + * @return an instance of the specified element type + * @deprecated since 1.6.x. + * This is not a namespace aware method. + * + * @exception BuildException if no method is available to create the + * element instance, or if the creating method fails. + */ + @Deprecated + public Object createElement(final Project project, final Object parent, final String elementName) + throws BuildException { + final NestedCreator nc = getNestedCreator(project, "", parent, elementName, null); + try { + final Object nestedElement = nc.create(project, parent, null); + if (project != null) { + project.setProjectReference(nestedElement); + } + return nestedElement; + } catch (final IllegalAccessException ie) { + // impossible as getMethods should only return public methods + throw new BuildException(ie); + } catch (final InstantiationException ine) { + // impossible as getMethods should only return public methods + throw new BuildException(ine); + } catch (final InvocationTargetException ite) { + throw extractBuildException(ite); + } + } + + /** + * 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 parentUri The namespace uri of the parent object. + * @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. + * @param ue The unknown element associated with the element. + * @return a creator object to create and store the element instance. + */ + public Creator getElementCreator( + final Project project, final String parentUri, final Object parent, final String elementName, final UnknownElement ue) { + final NestedCreator nc = getNestedCreator(project, parentUri, parent, elementName, ue); + return new Creator(project, parent, nc); + } + + /** + * Indicates whether the introspected class is a dynamic one, + * supporting arbitrary nested elements and/or attributes. + * + * @return <div><code>true</code> if the introspected class is dynamic; + * <code>false</code> otherwise.</div> + * @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 <code>true</code> if the introspected class is a container; + * <code>false</code> 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 + * + * @return true if the given nested element is supported + */ + public boolean supportsNestedElement(final String elementName) { + return supportsNestedElement("", elementName); + } + + /** + * Indicate if this element supports a nested element of the + * given name. + * + * <p>Note that this method will always return true if the + * introspected class is {@link #isDynamic dynamic} or contains a + * method named "add" with void return type and a single argument. + * To ge a more thorough answer, use the four-arg version of this + * method instead.</p> + * + * @param parentUri the uri of the parent + * @param elementName the name of the nested element being checked + * + * @return true if the given nested element is supported + */ + public boolean supportsNestedElement(final String parentUri, final String elementName) { + if (isDynamic() || addTypeMethods.size() > 0) { + return true; + } + return supportsReflectElement(parentUri, elementName); + } + + /** + * Indicate if this element supports a nested element of the + * given name. + * + * <p>Note that this method will always return true if the + * introspected class is {@link #isDynamic dynamic}, so be + * prepared to catch an exception about unsupported children when + * calling {@link #getElementCreator getElementCreator}.</p> + * + * @param parentUri the uri of the parent + * @param elementName the name of the nested element being checked + * @param project currently executing project instance + * @param parent the parent element + * + * @return true if the given nested element is supported + * @since Ant 1.8.0. + */ + public boolean supportsNestedElement(final String parentUri, final String elementName, + final Project project, final Object parent) { + if (addTypeMethods.size() > 0 + && createAddTypeCreator(project, parent, elementName) != null) { + return true; + } + return isDynamic() || supportsReflectElement(parentUri, elementName); + } + + /** + * Check if this element supports a nested element from reflection. + * + * @param parentUri the uri of the parent + * @param elementName the name of the nested element being checked + * + * @return true if the given nested element is supported + * @since Ant 1.8.0 + */ + public boolean supportsReflectElement( + String parentUri, final String elementName) { + final String name = ProjectHelper.extractNameFromComponentName(elementName); + if (!nestedCreators.containsKey(name.toLowerCase(Locale.ENGLISH))) { + return false; + } + String uri = ProjectHelper.extractUriFromComponentName(elementName); + if (uri.equals(ProjectHelper.ANT_CORE_URI)) { + uri = ""; + } + if ("".equals(uri)) { + return true; + } + if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) { + parentUri = ""; + } + return uri.equals(parentUri); + } + + /** + * Stores a named nested element using a storage method determined + * by the initial introspection. If no appropriate storage method + * is available, this method returns immediately. + * + * @param project Ignored in this implementation. + * May be <code>null</code>. + * + * @param parent Parent instance to store the child in. + * Must not be <code>null</code>. + * + * @param child Child instance to store in the parent. + * Should not be <code>null</code>. + * + * @param elementName Name of the child element to store. + * May be <code>null</code>, in which case + * this method returns immediately. + * + * @exception BuildException if the storage method fails. + */ + public void storeElement(final Project project, final Object parent, final Object child, + final String elementName) throws BuildException { + if (elementName == null) { + return; + } + final NestedCreator ns = nestedCreators.get(elementName.toLowerCase(Locale.ENGLISH)); + if (ns == null) { + return; + } + try { + ns.store(parent, child); + } catch (final IllegalAccessException ie) { + // impossible as getMethods should only return public methods + throw new BuildException(ie); + } catch (final InstantiationException ine) { + // impossible as getMethods should only return public methods + throw new BuildException(ine); + } catch (final InvocationTargetException ite) { + throw extractBuildException(ite); + } + } + + /** + * Helper method to extract the inner fault from an {@link InvocationTargetException}, and turn + * it into a BuildException. If it is already a BuildException, it is type cast and returned; if + * not a new BuildException is created containing the child as nested text. + * @param ite + * @return the nested exception + */ + private static BuildException extractBuildException(final InvocationTargetException ite) { + final Throwable t = ite.getTargetException(); + if (t instanceof BuildException) { + return (BuildException) t; + } + return new BuildException(t); + } + + /** + * Returns the type of a named nested element. + * + * @param elementName The name of the element to find the type of. + * Must not be <code>null</code>. + * + * @return the type of the nested element with the specified name. + * This will never be <code>null</code>. + * + * @exception BuildException if the introspected class does not + * support the named nested element. + */ + public Class<?> getElementType(final String elementName) throws BuildException { + final Class<?> nt = nestedTypes.get(elementName); + if (nt == null) { + throw new UnsupportedElementException("Class " + + bean.getName() + " doesn't support the nested \"" + + elementName + "\" element.", elementName); + } + return nt; + } + + /** + * Returns the type of a named attribute. + * + * @param attributeName The name of the attribute to find the type of. + * Must not be <code>null</code>. + * + * @return the type of the attribute with the specified name. + * This will never be <code>null</code>. + * + * @exception BuildException if the introspected class does not + * support the named attribute. + */ + public Class<?> getAttributeType(final String attributeName) throws BuildException { + final Class<?> at = attributeTypes.get(attributeName); + if (at == null) { + throw new UnsupportedAttributeException("Class " + + bean.getName() + " doesn't support the \"" + + attributeName + "\" attribute.", attributeName); + } + 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 <code>null</code>. + * @throws BuildException if the introspected class does not + * support the nested text. + * @since Ant 1.6.3 + */ + public Method getAddTextMethod() throws BuildException { + if (!supportsCharacters()) { + throw new BuildException("Class " + bean.getName() + + " doesn't support nested text data."); + } + 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 <code>null</code>. + * @return the method on this introspected class that adds or creates this + * nested element. Can be <code>null</code> 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(final String elementName) throws BuildException { + final Object creator = nestedCreators.get(elementName); + if (creator == null) { + throw new UnsupportedElementException("Class " + + bean.getName() + " doesn't support the nested \"" + + elementName + "\" element.", elementName); + } + 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 <code>null</code>. + * @return the method on this introspected class that sets this attribute. + * This will never be <code>null</code>. + * @throws BuildException if the introspected class does not + * support the named attribute. + * @since Ant 1.6.3 + */ + public Method getAttributeMethod(final String attributeName) throws BuildException { + final Object setter = attributeSetters.get(attributeName); + if (setter == null) { + throw new UnsupportedAttributeException("Class " + + bean.getName() + " doesn't support the \"" + + attributeName + "\" attribute.", attributeName); + } + return ((AttributeSetter) setter).method; + } + + /** + * Returns whether or not the introspected class supports PCDATA. + * + * @return whether or not the introspected class supports PCDATA. + */ + public boolean supportsCharacters() { + return addText != null; + } + + /** + * Returns an enumeration of the names of the attributes supported by the introspected class. + * + * @return an enumeration of the names of the attributes supported by the introspected class. + * @see #getAttributeMap + */ + public Enumeration<String> getAttributes() { + return attributeSetters.keys(); + } + + /** + * Returns a read-only map of attributes supported by the introspected class. + * + * @return an attribute name to attribute <code>Class</code> + * unmodifiable map. Can be empty, but never <code>null</code>. + * @since Ant 1.6.3 + */ + public Map<String, Class<?>> getAttributeMap() { + return attributeTypes.isEmpty() + ? Collections.<String, Class<?>> emptyMap() : 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<String> 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 <code>Class</code> + * unmodifiable map. Can be empty, but never <code>null</code>. + * @since Ant 1.6.3 + */ + public Map<String, Class<?>> getNestedElementMap() { + return nestedTypes.isEmpty() + ? Collections.<String, Class<?>> emptyMap() : Collections.unmodifiableMap(nestedTypes); + } + + /** + * Returns a read-only list of extension points supported + * by the introspected class. + * <p> + * A task/type or nested element with void methods named <code>add()</code> + * or <code>addConfigured()</code>, taking a single class or interface + * argument, supports extensions point. This method returns the list of + * all these <em>void add[Configured](type)</em> methods. + * + * @return a list of void, single argument add() or addConfigured() + * <code>Method</code>s 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 <code>null</code>. + * @since Ant 1.6.3 + */ + public List<Method> getExtensionPoints() { + return addTypeMethods.isEmpty() + ? Collections.<Method> emptyList() : Collections.unmodifiableList(addTypeMethods); + } + + /** + * Creates an implementation of AttributeSetter for the given + * attribute type. Conversions (where necessary) are automatically + * made for the following types: + * <ul> + * <li>String (left as it is) + * <li>Character/char (first character is used) + * <li>Boolean/boolean + * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used) + * <li>Class (Class.forName is used) + * <li>File (resolved relative to the appropriate project) + * <li>Path (resolve relative to the appropriate project) + * <li>Resource (resolved as a FileResource relative to the appropriate project) + * <li>FileProvider (resolved as a FileResource relative to the appropriate project) + * <li>EnumeratedAttribute (uses its own + * {@link EnumeratedAttribute#setValue(String) setValue} method) + * <li>Other primitive types (wrapper classes are used with constructors + * taking String) + * </ul> + * + * If none of the above covers the given parameters, a constructor for the + * appropriate class taking a String parameter is used if it is available. + * + * @param m The method to invoke on the bean when the setter is invoked. + * Must not be <code>null</code>. + * @param arg The type of the single argument of the bean's method. + * Must not be <code>null</code>. + * @param attrName the name of the attribute for which the setter is being + * created. + * + * @return an appropriate AttributeSetter instance, or <code>null</code> + * if no appropriate conversion is available. + */ + private AttributeSetter createAttributeSetter(final Method m, + final Class<?> arg, + final String attrName) { + // use wrappers for primitive classes, e.g. int and + // Integer are treated identically + final Class<?> reflectedArg = PRIMITIVE_TYPE_MAP.containsKey(arg) + ? PRIMITIVE_TYPE_MAP.get(arg) : arg; + + // Object.class - it gets handled differently by AttributeSetter + if (java.lang.Object.class == reflectedArg) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, + IllegalAccessException { + throw new BuildException( + "Internal ant problem - this should not get called"); + } + }; + } + // simplest case - setAttribute expects String + if (java.lang.String.class.equals(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException { + m.invoke(parent, (Object[]) new String[] {value}); + } + }; + } + // char and Character get special treatment - take the first character + if (java.lang.Character.class.equals(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException { + if (value.length() == 0) { + throw new BuildException("The value \"\" is not a " + + "legal value for attribute \"" + attrName + "\""); + } + m.invoke(parent, (Object[]) new Character[] {new Character(value.charAt(0))}); + } + }; + } + // boolean and Boolean get special treatment because we have a nice method in Project + if (java.lang.Boolean.class.equals(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException { + m.invoke(parent, (Object[]) new Boolean[] { + Project.toBoolean(value) ? Boolean.TRUE : Boolean.FALSE }); + } + }; + } + // Class doesn't have a String constructor but a decent factory method + if (java.lang.Class.class.equals(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException, BuildException { + try { + m.invoke(parent, new Object[] {Class.forName(value)}); + } catch (final ClassNotFoundException ce) { + throw new BuildException(ce); + } + } + }; + } + // resolve relative paths through Project + if (java.io.File.class.equals(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException { + m.invoke(parent, new Object[] {p.resolveFile(value)}); + } + }; + } + // resolve Resources/FileProviders as FileResources relative to Project: + if (Resource.class.equals(reflectedArg) || FileProvider.class.equals(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + void set(final Project p, final Object parent, final String value) throws InvocationTargetException, + IllegalAccessException, BuildException { + m.invoke(parent, new Object[] {new FileResource(p, p.resolveFile(value))}); + }; + }; + } + // EnumeratedAttributes have their own helper class + if (EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException, BuildException { + try { + final EnumeratedAttribute ea = (EnumeratedAttribute) reflectedArg.newInstance(); + ea.setValue(value); + m.invoke(parent, new Object[] {ea}); + } catch (final InstantiationException ie) { + throw new BuildException(ie); + } + } + }; + } + + final AttributeSetter setter = getEnumSetter(reflectedArg, m, arg); + if (setter != null) { + return setter; + } + + if (java.lang.Long.class.equals(reflectedArg)) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException, BuildException { + try { + m.invoke(parent, new Object[] { + new Long(StringUtils.parseHumanSizes(value)) }); + } catch (final NumberFormatException e) { + throw new BuildException("Can't assign non-numeric" + + " value '" + value + "' to" + + " attribute " + attrName); + } catch (final InvocationTargetException e) { + throw e; + } catch (final IllegalAccessException e) { + throw e; + } catch (final Exception e) { + throw new BuildException(e); + } + } + }; + } + // worst case. look for a public String constructor and use it + // also supports new Whatever(Project, String) as for Path or Reference + // This is used (deliberately) for all primitives/wrappers other than + // char, boolean, and long. + boolean includeProject; + Constructor<?> c; + try { + // First try with Project. + c = reflectedArg.getConstructor(Project.class, String.class); + includeProject = true; + } catch (final NoSuchMethodException nme) { + // OK, try without. + try { + c = reflectedArg.getConstructor(String.class); + includeProject = false; + } catch (final NoSuchMethodException nme2) { + // Well, no matching constructor. + return null; + } + } + final boolean finalIncludeProject = includeProject; + final Constructor<?> finalConstructor = c; + + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException, BuildException { + try { + final Object[] args = finalIncludeProject + ? new Object[] {p, value} : new Object[] {value}; + + final Object attribute = finalConstructor.newInstance(args); + if (p != null) { + p.setProjectReference(attribute); + } + m.invoke(parent, new Object[] {attribute}); + } catch (final InvocationTargetException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IllegalArgumentException) { + throw new BuildException("Can't assign value '" + value + + "' to attribute " + attrName + + ", reason: " + + cause.getClass() + + " with message '" + + cause.getMessage() + "'"); + } + throw e; + } catch (final InstantiationException ie) { + throw new BuildException(ie); + } + } + }; + } + + private AttributeSetter getEnumSetter( + final Class<?> reflectedArg, final Method m, final Class<?> arg) { + if (reflectedArg.isEnum()) { + return new AttributeSetter(m, arg) { + @Override + public void set(final Project p, final Object parent, final String value) + throws InvocationTargetException, IllegalAccessException, + BuildException { + Enum<?> setValue; + try { + @SuppressWarnings({ "unchecked", "rawtypes" }) + final Enum<?> enumValue = Enum.valueOf((Class<? extends Enum>) reflectedArg, + value); + setValue = enumValue; + } catch (final IllegalArgumentException e) { + //there is specific logic here for the value + // being out of the allowed set of enumerations. + throw new BuildException("'" + value + "' is not a permitted value for " + + reflectedArg.getName()); + } + m.invoke(parent, setValue); + } + }; + } + return null; + } + + /** + * Returns a description of the type of the given element in + * relation to a given project. This is used for logging purposes + * when the element is asked to cope with some data it has no way of handling. + * + * @param project The project the element is defined in. Must not be <code>null</code>. + * + * @param element The element to describe. Must not be <code>null</code>. + * + * @return a description of the element type + */ + private String getElementName(final Project project, final Object element) { + return project.getElementName(element); + } + + /** + * Extracts the name of a property from a method name by subtracting + * a given prefix and converting into lower case. It is up to calling + * code to make sure the method name does actually begin with the + * specified prefix - no checking is done in this method. + * + * @param methodName The name of the method in question. Must not be <code>null</code>. + * @param prefix The prefix to remove. Must not be <code>null</code>. + * + * @return the lower-cased method name with the prefix removed. + */ + private static String getPropertyName(final String methodName, final String prefix) { + return methodName.substring(prefix.length()).toLowerCase(Locale.ENGLISH); + } + + /** + * creator - allows use of create/store external + * to IntrospectionHelper. + * The class is final as it has a private constructor. + */ + public static final class Creator { + private final NestedCreator nestedCreator; + private final Object parent; + private final 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(final Project project, final Object parent, final 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(final 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 polymorphic form for this element"); + } + final ComponentHelper helper = ComponentHelper.getComponentHelper(project); + nestedObject = helper.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 (final IllegalAccessException ex) { + throw new BuildException(ex); + } catch (final InstantiationException ex) { + throw new BuildException(ex); + } catch (final IllegalArgumentException ex) { + if (polyType == null) { + throw ex; + } + throw new BuildException("Invalid type used " + polyType); + } catch (final InvocationTargetException ex) { + throw extractBuildException(ex); + } + } + + /** + * @return the real object (used currently only for presetdef). + */ + public Object getRealObject() { + return nestedCreator.getRealObject(); + } + + /** + * Stores the nested element object using a storage method determined by introspection. + * + */ + public void store() { + try { + nestedCreator.store(parent, nestedObject); + } catch (final IllegalAccessException ex) { + throw new BuildException(ex); + } catch (final InstantiationException ex) { + throw new BuildException(ex); + } catch (final IllegalArgumentException ex) { + if (polyType == null) { + throw ex; + } + throw new BuildException("Invalid type used " + polyType); + } catch (final InvocationTargetException ex) { + throw extractBuildException(ex); + } + } + } + + /** + * Internal interface used to create nested elements. Not documented + * in detail for reasons of source code readability. + */ + private abstract static class NestedCreator { + private final Method method; // the method called to add/create the nested element + + protected NestedCreator(final Method m) { + method = m; + } + Method getMethod() { + return method; + } + boolean isPolyMorphic() { + return false; + } + Object getRealObject() { + return null; + } + abstract Object create(Project project, Object parent, Object child) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + + void store(final Object parent, final Object child) + throws InvocationTargetException, IllegalAccessException, InstantiationException { + // DO NOTHING + } + } + + private static class CreateNestedCreator extends NestedCreator { + CreateNestedCreator(final Method m) { + super(m); + } + + @Override + Object create(final Project project, final Object parent, final Object ignore) + throws InvocationTargetException, IllegalAccessException { + return getMethod().invoke(parent, new Object[] {}); + } + } + + /** Version to use for addXXX and addConfiguredXXX */ + private static class AddNestedCreator extends NestedCreator { + + static final int ADD = 1; + static final int ADD_CONFIGURED = 2; + + private final Constructor<?> constructor; + private final int behavior; // ADD or ADD_CONFIGURED + + AddNestedCreator(final Method m, final Constructor<?> c, final int behavior) { + super(m); + this.constructor = c; + this.behavior = behavior; + } + + @Override + boolean isPolyMorphic() { + return true; + } + + @Override + Object create(final Project project, final Object parent, Object child) + throws InvocationTargetException, IllegalAccessException, InstantiationException { + if (child == null) { + child = constructor.newInstance( + constructor.getParameterTypes().length == 0 + ? new Object[] {} : new Object[] {project}); + } + if (child instanceof PreSetDef.PreSetDefinition) { + child = ((PreSetDef.PreSetDefinition) child).createObject(project); + } + if (behavior == ADD) { + istore(parent, child); + } + return child; + } + + @Override + void store(final Object parent, final Object child) + throws InvocationTargetException, IllegalAccessException, InstantiationException { + if (behavior == ADD_CONFIGURED) { + istore(parent, child); + } + } + + private void istore(final Object parent, final Object child) + throws InvocationTargetException, IllegalAccessException, InstantiationException { + getMethod().invoke(parent, new Object[] {child}); + } + } + + /** + * Internal interface used to setting element attributes. Not documented + * in detail for reasons of source code readability. + */ + private abstract static class AttributeSetter { + private final Method method; // the method called to set the attribute + private final Class<?> type; + protected AttributeSetter(final Method m, final Class<?> type) { + method = m; + this.type = type; + } + void setObject(final Project p, final Object parent, final Object value) + throws InvocationTargetException, IllegalAccessException, BuildException { + if (type != null) { + Class<?> useType = type; + if (type.isPrimitive()) { + if (value == null) { + throw new BuildException( + "Attempt to set primitive " + + getPropertyName(method.getName(), "set") + + " to null on " + parent); + } + useType = PRIMITIVE_TYPE_MAP.get(type); + } + if (value == null || useType.isInstance(value)) { + method.invoke(parent, new Object[] {value}); + return; + } + } + set(p, parent, value.toString()); + } + abstract void set(Project p, Object parent, String value) + throws InvocationTargetException, IllegalAccessException, BuildException; + } + + /** + * Clears the static cache of on build finished. + */ + public static synchronized void clearCache() { + HELPERS.clear(); + } + + /** + * Create a NestedCreator for the given element. + * @param project owning project + * @param parent Parent object used to create the instance. + * @param elementName name of the element + * @return a nested creator, or null if there is no component of the given name, or it + * has no matching add type methods + * @throws BuildException + */ + private NestedCreator createAddTypeCreator( + final Project project, final Object parent, final String elementName) throws BuildException { + if (addTypeMethods.size() == 0) { + return null; + } + final ComponentHelper helper = ComponentHelper.getComponentHelper(project); + + final MethodAndObject restricted = createRestricted( + helper, elementName, addTypeMethods); + final MethodAndObject topLevel = createTopLevel( + helper, elementName, addTypeMethods); + + if (restricted == null && topLevel == null) { + return null; + } + + if (restricted != null && topLevel != null) { + throw new BuildException( + "ambiguous: type and component definitions for " + + elementName); + } + + final MethodAndObject methodAndObject + = restricted != null ? restricted : topLevel; + + Object rObject = methodAndObject.object; + if (methodAndObject.object instanceof PreSetDef.PreSetDefinition) { + rObject = ((PreSetDef.PreSetDefinition) methodAndObject.object) + .createObject(project); + } + final Object nestedObject = methodAndObject.object; + final Object realObject = rObject; + + return new NestedCreator(methodAndObject.method) { + @Override + Object create(final Project project, final Object parent, final Object ignore) + throws InvocationTargetException, IllegalAccessException { + if (!getMethod().getName().endsWith("Configured")) { + getMethod().invoke(parent, new Object[] {realObject}); + } + return nestedObject; + } + + @Override + Object getRealObject() { + return realObject; + } + + @Override + void store(final Object parent, final Object child) throws InvocationTargetException, + IllegalAccessException, InstantiationException { + if (getMethod().getName().endsWith("Configured")) { + getMethod().invoke(parent, new Object[] {realObject}); + } + } + }; + } + + /** + * Inserts an add or addConfigured method into + * the addTypeMethods array. The array is + * ordered so that the more derived classes are first. + * If both add and addConfigured are present, the addConfigured will take priority. + * @param method the <code>Method</code> to insert. + */ + private void insertAddTypeMethod(final Method method) { + final Class<?> argClass = method.getParameterTypes()[0]; + final int size = addTypeMethods.size(); + for (int c = 0; c < size; ++c) { + final Method current = addTypeMethods.get(c); + if (current.getParameterTypes()[0].equals(argClass)) { + if (method.getName().equals("addConfigured")) { + // add configured replaces the add method + addTypeMethods.set(c, method); + } + return; // Already present + } + if (current.getParameterTypes()[0].isAssignableFrom(argClass)) { + addTypeMethods.add(c, method); + return; // higher derived + } + } + addTypeMethods.add(method); + } + + /** + * Search the list of methods to find the first method + * that has a parameter that accepts the nested element object. + * @param paramClass the <code>Class</code> type to search for. + * @param methods the <code>List</code> of methods to search. + * @return a matching <code>Method</code>; null if none found. + */ + private Method findMatchingMethod(final Class<?> paramClass, final List<Method> methods) { + if (paramClass == null) { + return null; + } + Class<?> matchedClass = null; + Method matchedMethod = null; + + final int size = methods.size(); + for (int i = 0; i < size; ++i) { + final Method method = methods.get(i); + final Class<?> methodClass = method.getParameterTypes()[0]; + if (methodClass.isAssignableFrom(paramClass)) { + if (matchedClass == null) { + matchedClass = methodClass; + matchedMethod = method; + } else if (!methodClass.isAssignableFrom(matchedClass)) { + throw new BuildException("ambiguous: types " + matchedClass.getName() + " and " + + methodClass.getName() + " match " + paramClass.getName()); + } + } + } + return matchedMethod; + } + + private String condenseText(final String text) { + if (text.length() <= MAX_REPORT_NESTED_TEXT) { + return text; + } + final int ends = (MAX_REPORT_NESTED_TEXT - ELLIPSIS.length()) / 2; + return new StringBuffer(text).replace(ends, text.length() - ends, ELLIPSIS).toString(); + } + + + private static class MethodAndObject { + private final Method method; + private final Object object; + public MethodAndObject(final Method method, final Object object) { + this.method = method; + this.object = object; + } + } + + /** + * + */ + private AntTypeDefinition findRestrictedDefinition( + final ComponentHelper helper, final String componentName, final List<Method> methods) { + AntTypeDefinition definition = null; + Class<?> matchedDefinitionClass = null; + + final List<AntTypeDefinition> definitions = helper.getRestrictedDefinitions(componentName); + if (definitions == null) { + return null; + } + synchronized (definitions) { + final int size = definitions.size(); + for (int i = 0; i < size; ++i) { + final AntTypeDefinition d = definitions.get(i); + final Class<?> exposedClass = d.getExposedClass(helper.getProject()); + if (exposedClass == null) { + continue; + } + final Method method = findMatchingMethod(exposedClass, methods); + if (method == null) { + continue; + } + if (matchedDefinitionClass != null) { + throw new BuildException( + "ambiguous: restricted definitions for " + + componentName + " " + + matchedDefinitionClass + " and " + exposedClass); + } + matchedDefinitionClass = exposedClass; + definition = d; + } + } + return definition; + } + + private MethodAndObject createRestricted( + final ComponentHelper helper, final String elementName, final List<Method> addTypeMethods) { + + final Project project = helper.getProject(); + + final AntTypeDefinition restrictedDefinition = + findRestrictedDefinition(helper, elementName, addTypeMethods); + + if (restrictedDefinition == null) { + return null; + } + + final Method addMethod = findMatchingMethod( + restrictedDefinition.getExposedClass(project), addTypeMethods); + if (addMethod == null) { + throw new BuildException( + "Ant Internal Error - contract mismatch for " + + elementName); + } + final Object addedObject = restrictedDefinition.create(project); + if (addedObject == null) { + throw new BuildException( + "Failed to create object " + elementName + + " of type " + restrictedDefinition.getTypeClass(project)); + } + return new MethodAndObject(addMethod, addedObject); + } + + private MethodAndObject createTopLevel( + final ComponentHelper helper, final String elementName, final List<Method> methods) { + final Class<?> clazz = helper.getComponentClass(elementName); + if (clazz == null) { + return null; + } + final Method addMethod = findMatchingMethod(clazz, addTypeMethods); + if (addMethod == null) { + return null; + } + final Object addedObject = helper.createComponent(elementName); + return new MethodAndObject(addMethod, addedObject); + } + +} |