summaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/IntrospectionHelper.java
diff options
context:
space:
mode:
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.java1745
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);
+ }
+
+}