0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant;
0020:
0021: import java.lang.reflect.Constructor;
0022: import java.lang.reflect.InvocationTargetException;
0023: import java.lang.reflect.Method;
0024: import java.util.ArrayList;
0025: import java.util.Collections;
0026: import java.util.Enumeration;
0027: import java.util.Hashtable;
0028: import java.util.HashMap;
0029: import java.util.List;
0030: import java.util.Locale;
0031: import java.util.Map;
0032: import org.apache.tools.ant.types.EnumeratedAttribute;
0033: import org.apache.tools.ant.taskdefs.PreSetDef;
0034:
0035: /**
0036: * Helper class that collects the methods a task or nested element
0037: * holds to set attributes, create nested elements or hold PCDATA
0038: * elements.
0039: * The class is final as it has a private constructor.
0040: */
0041: public final class IntrospectionHelper {
0042:
0043: /**
0044: * EMPTY_MAP was added in java 1.3 (EMPTY_SET and EMPTY_LIST
0045: * is in java 1.2!)
0046: */
0047: private static final Map EMPTY_MAP = Collections
0048: .unmodifiableMap(new HashMap(0));
0049:
0050: /**
0051: * Helper instances we've already created (Class.getName() to IntrospectionHelper).
0052: */
0053: private static final Map HELPERS = new Hashtable();
0054:
0055: /**
0056: * Map from primitive types to wrapper classes for use in
0057: * createAttributeSetter (Class to Class). Note that char
0058: * and boolean are in here even though they get special treatment
0059: * - this way we only need to test for the wrapper class.
0060: */
0061: private static final Map PRIMITIVE_TYPE_MAP = new HashMap(8);
0062:
0063: // Set up PRIMITIVE_TYPE_MAP
0064: static {
0065: Class[] primitives = { Boolean.TYPE, Byte.TYPE, Character.TYPE,
0066: Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE,
0067: Double.TYPE };
0068: Class[] wrappers = { Boolean.class, Byte.class,
0069: Character.class, Short.class, Integer.class,
0070: Long.class, Float.class, Double.class };
0071: for (int i = 0; i < primitives.length; i++) {
0072: PRIMITIVE_TYPE_MAP.put(primitives[i], wrappers[i]);
0073: }
0074: }
0075:
0076: private static final int MAX_REPORT_NESTED_TEXT = 20;
0077: private static final String ELLIPSIS = "...";
0078:
0079: /**
0080: * Map from attribute names to attribute types
0081: * (String to Class).
0082: */
0083: private Hashtable attributeTypes = new Hashtable();
0084:
0085: /**
0086: * Map from attribute names to attribute setter methods
0087: * (String to AttributeSetter).
0088: */
0089: private Hashtable attributeSetters = new Hashtable();
0090:
0091: /**
0092: * Map from attribute names to nested types
0093: * (String to Class).
0094: */
0095: private Hashtable nestedTypes = new Hashtable();
0096:
0097: /**
0098: * Map from attribute names to methods to create nested types
0099: * (String to NestedCreator).
0100: */
0101: private Hashtable nestedCreators = new Hashtable();
0102:
0103: /**
0104: * Vector of methods matching add[Configured](Class) pattern.
0105: */
0106: private List addTypeMethods = new ArrayList();
0107:
0108: /**
0109: * The method to invoke to add PCDATA.
0110: */
0111: private Method addText = null;
0112:
0113: /**
0114: * The class introspected by this instance.
0115: */
0116: private Class bean;
0117:
0118: /**
0119: * Sole constructor, which is private to ensure that all
0120: * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
0121: * Introspects the given class for bean-like methods.
0122: * Each method is examined in turn, and the following rules are applied:
0123: * <p>
0124: * <ul>
0125: * <li>If the method is <code>Task.setLocation(Location)</code>,
0126: * <code>Task.setTaskType(String)</code>
0127: * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These
0128: * methods are handled differently elsewhere.
0129: * <li><code>void addText(String)</code> is recognised as the method for
0130: * adding PCDATA to a bean.
0131: * <li><code>void setFoo(Bar)</code> is recognised as a method for
0132: * setting the value of attribute <code>foo</code>, so long as
0133: * <code>Bar</code> is non-void and is not an array type. Non-String
0134: * parameter types always overload String parameter types, but that is
0135: * the only guarantee made in terms of priority.
0136: * <li><code>Foo createBar()</code> is recognised as a method for
0137: * creating a nested element called <code>bar</code> of type
0138: * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
0139: * array type.
0140: * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
0141: * method for storing a pre-configured element called
0142: * <code>foo</code> and of type <code>Bar</code>, so long as
0143: * <code>Bar</code> is not an array, primitive or String type.
0144: * <code>Bar</code> must have an accessible constructor taking no
0145: * arguments.
0146: * <li><code>void addFoo(Bar)</code> is recognised as a method for storing
0147: * an element called <code>foo</code> and of type <code>Bar</code>, so
0148: * long as <code>Bar</code> is not an array, primitive or String type.
0149: * <code>Bar</code> must have an accessible constructor taking no
0150: * arguments. This is distinct from the 'addConfigured' idiom in that
0151: * the nested element is added to the parent immediately after it is
0152: * constructed; in practice this means that <code>addFoo(Bar)</code> should
0153: * do little or nothing with its argument besides storing it for later use.
0154: * </ul>
0155: * Note that only one method is retained to create/set/addConfigured/add
0156: * any element or attribute.
0157: *
0158: * @param bean The bean type to introspect.
0159: * Must not be <code>null</code>.
0160: *
0161: * @see #getHelper(Class)
0162: */
0163: private IntrospectionHelper(final Class bean) {
0164: this .bean = bean;
0165: Method[] methods = bean.getMethods();
0166: for (int i = 0; i < methods.length; i++) {
0167: final Method m = methods[i];
0168: final String name = m.getName();
0169: Class returnType = m.getReturnType();
0170: Class[] args = m.getParameterTypes();
0171:
0172: // check of add[Configured](Class) pattern
0173: if (args.length == 1
0174: && java.lang.Void.TYPE.equals(returnType)
0175: && ("add".equals(name) || "addConfigured"
0176: .equals(name))) {
0177: insertAddTypeMethod(m);
0178: continue;
0179: }
0180: // not really user settable properties on tasks/project components
0181: if (org.apache.tools.ant.ProjectComponent.class
0182: .isAssignableFrom(bean)
0183: && args.length == 1
0184: && isHiddenSetMethod(name, args[0])) {
0185: continue;
0186: }
0187: // hide addTask for TaskContainers
0188: if (isContainer() && args.length == 1
0189: && "addTask".equals(name)
0190: && org.apache.tools.ant.Task.class.equals(args[0])) {
0191: continue;
0192: }
0193: if ("addText".equals(name)
0194: && java.lang.Void.TYPE.equals(returnType)
0195: && args.length == 1
0196: && java.lang.String.class.equals(args[0])) {
0197:
0198: addText = methods[i];
0199: } else if (name.startsWith("set")
0200: && java.lang.Void.TYPE.equals(returnType)
0201: && args.length == 1 && !args[0].isArray()) {
0202: String propName = getPropertyName(name, "set");
0203: if (attributeSetters.get(propName) != null) {
0204: if (java.lang.String.class.equals(args[0])) {
0205: /*
0206: Ignore method m, as there is an overloaded
0207: form of this method that takes in a
0208: non-string argument, which gains higher
0209: priority.
0210: */
0211: continue;
0212: }
0213: /*
0214: If the argument is not a String or Location,
0215: and if there
0216: is an overloaded form of this method already defined,
0217: we just override that with the new one.
0218: This mechanism does not guarantee any specific order
0219: in which the methods will be selected: so any code
0220: that depends on the order in which "set" methods have
0221: been defined, is not guaranteed to be selected in any
0222: particular order.
0223: */
0224: }
0225: AttributeSetter as = createAttributeSetter(m, args[0],
0226: propName);
0227: if (as != null) {
0228: attributeTypes.put(propName, args[0]);
0229: attributeSetters.put(propName, as);
0230: }
0231: } else if (name.startsWith("create")
0232: && !returnType.isArray()
0233: && !returnType.isPrimitive() && args.length == 0) {
0234:
0235: String propName = getPropertyName(name, "create");
0236: // Check if a create of this property is already present
0237: // add takes preference over create for CB purposes
0238: if (nestedCreators.get(propName) == null) {
0239: nestedTypes.put(propName, returnType);
0240: nestedCreators.put(propName,
0241: new CreateNestedCreator(m));
0242: }
0243: } else if (name.startsWith("addConfigured")
0244: && java.lang.Void.TYPE.equals(returnType)
0245: && args.length == 1
0246: && !java.lang.String.class.equals(args[0])
0247: && !args[0].isArray() && !args[0].isPrimitive()) {
0248: try {
0249: Constructor constructor = null;
0250: try {
0251: constructor = args[0]
0252: .getConstructor(new Class[] {});
0253: } catch (NoSuchMethodException ex) {
0254: constructor = args[0]
0255: .getConstructor(new Class[] { Project.class });
0256: }
0257: String propName = getPropertyName(name,
0258: "addConfigured");
0259: nestedTypes.put(propName, args[0]);
0260: nestedCreators.put(propName, new AddNestedCreator(
0261: m, constructor,
0262: AddNestedCreator.ADD_CONFIGURED));
0263: } catch (NoSuchMethodException nse) {
0264: // ignore
0265: }
0266: } else if (name.startsWith("add")
0267: && java.lang.Void.TYPE.equals(returnType)
0268: && args.length == 1
0269: && !java.lang.String.class.equals(args[0])
0270: && !args[0].isArray() && !args[0].isPrimitive()) {
0271: try {
0272: Constructor constructor = null;
0273: try {
0274: constructor = args[0]
0275: .getConstructor(new Class[] {});
0276: } catch (NoSuchMethodException ex) {
0277: constructor = args[0]
0278: .getConstructor(new Class[] { Project.class });
0279: }
0280:
0281: String propName = getPropertyName(name, "add");
0282: if (nestedTypes.get(propName) != null) {
0283: /*
0284: * Ignore this method as there is an addConfigured
0285: * form of this method that has a higher
0286: * priority
0287: */
0288: continue;
0289: }
0290: nestedTypes.put(propName, args[0]);
0291: nestedCreators.put(propName, new AddNestedCreator(
0292: m, constructor, AddNestedCreator.ADD));
0293: } catch (NoSuchMethodException nse) {
0294: // ignore
0295: }
0296: }
0297: }
0298: }
0299:
0300: /**
0301: * Certain set methods are part of the Ant core interface to tasks and
0302: * therefore not to be considered for introspection
0303: *
0304: * @param name the name of the set method
0305: * @param type the type of the set method's parameter
0306: * @return true if the given set method is to be hidden.
0307: */
0308: private boolean isHiddenSetMethod(String name, Class type) {
0309: if ("setLocation".equals(name)
0310: && org.apache.tools.ant.Location.class.equals(type)) {
0311: return true;
0312: }
0313:
0314: if ("setTaskType".equals(name)
0315: && java.lang.String.class.equals(type)) {
0316: return true;
0317: }
0318:
0319: return false;
0320: }
0321:
0322: /**
0323: * Returns a helper for the given class, either from the cache
0324: * or by creating a new instance.
0325: *
0326: * @param c The class for which a helper is required.
0327: * Must not be <code>null</code>.
0328: *
0329: * @return a helper for the specified class
0330: */
0331: public static synchronized IntrospectionHelper getHelper(Class c) {
0332: return getHelper(null, c);
0333: }
0334:
0335: /**
0336: * Returns a helper for the given class, either from the cache
0337: * or by creating a new instance.
0338: *
0339: * The method will make sure the helper will be cleaned up at the end of
0340: * the project, and only one instance will be created for each class.
0341: *
0342: * @param p the project instance.
0343: * @param c The class for which a helper is required.
0344: * Must not be <code>null</code>.
0345: *
0346: * @return a helper for the specified class
0347: */
0348: public static IntrospectionHelper getHelper(Project p, Class c) {
0349: IntrospectionHelper ih = (IntrospectionHelper) HELPERS.get(c
0350: .getName());
0351: // If a helper cannot be found, or if the helper is for another
0352: // classloader, create a new IH
0353: if (ih == null || ih.bean != c) {
0354: ih = new IntrospectionHelper(c);
0355: if (p != null) {
0356: // #30162: do *not* cache this if there is no project, as we
0357: // cannot guarantee that the cache will be cleared.
0358: HELPERS.put(c.getName(), ih);
0359: }
0360: }
0361: return ih;
0362: }
0363:
0364: /**
0365: * Sets the named attribute in the given element, which is part of the
0366: * given project.
0367: *
0368: * @param p The project containing the element. This is used when files
0369: * need to be resolved. Must not be <code>null</code>.
0370: * @param element The element to set the attribute in. Must not be
0371: * <code>null</code>.
0372: * @param attributeName The name of the attribute to set. Must not be
0373: * <code>null</code>.
0374: * @param value The value to set the attribute to. This may be interpreted
0375: * or converted to the necessary type if the setter method
0376: * doesn't just take a string. Must not be <code>null</code>.
0377: *
0378: * @exception BuildException if the introspected class doesn't support
0379: * the given attribute, or if the setting
0380: * method fails.
0381: */
0382: public void setAttribute(Project p, Object element,
0383: String attributeName, String value) throws BuildException {
0384: AttributeSetter as = (AttributeSetter) attributeSetters
0385: .get(attributeName.toLowerCase(Locale.US));
0386: if (as == null) {
0387: if (element instanceof DynamicAttributeNS) {
0388: DynamicAttributeNS dc = (DynamicAttributeNS) element;
0389: String uriPlusPrefix = ProjectHelper
0390: .extractUriFromComponentName(attributeName);
0391: String uri = ProjectHelper
0392: .extractUriFromComponentName(uriPlusPrefix);
0393: String localName = ProjectHelper
0394: .extractNameFromComponentName(attributeName);
0395: String qName = ("".equals(uri) ? localName
0396: : (uri + ":" + localName));
0397:
0398: dc.setDynamicAttribute(uri, localName, qName, value);
0399: return;
0400: } else if (element instanceof DynamicAttribute) {
0401: DynamicAttribute dc = (DynamicAttribute) element;
0402: dc.setDynamicAttribute(attributeName
0403: .toLowerCase(Locale.US), value);
0404: return;
0405: } else {
0406: if (attributeName.indexOf(':') != -1) {
0407: return; // Ignore attribute from unknown uri's
0408: }
0409: String msg = getElementName(p, element)
0410: + " doesn't support the \"" + attributeName
0411: + "\" attribute.";
0412: throw new UnsupportedAttributeException(msg,
0413: attributeName);
0414: }
0415: }
0416: try {
0417: as.set(p, element, value);
0418: } catch (IllegalAccessException ie) {
0419: // impossible as getMethods should only return public methods
0420: throw new BuildException(ie);
0421: } catch (InvocationTargetException ite) {
0422: Throwable t = ite.getTargetException();
0423: if (t instanceof BuildException) {
0424: throw (BuildException) t;
0425: }
0426: throw new BuildException(t);
0427: }
0428: }
0429:
0430: /**
0431: * Adds PCDATA to an element, using the element's
0432: * <code>void addText(String)</code> method, if it has one. If no
0433: * such method is present, a BuildException is thrown if the
0434: * given text contains non-whitespace.
0435: *
0436: * @param project The project which the element is part of.
0437: * Must not be <code>null</code>.
0438: * @param element The element to add the text to.
0439: * Must not be <code>null</code>.
0440: * @param text The text to add.
0441: * Must not be <code>null</code>.
0442: *
0443: * @exception BuildException if non-whitespace text is provided and no
0444: * method is available to handle it, or if
0445: * the handling method fails.
0446: */
0447: public void addText(Project project, Object element, String text)
0448: throws BuildException {
0449: if (addText == null) {
0450: text = text.trim();
0451: // Element doesn't handle text content
0452: if (text.length() == 0) {
0453: // Only whitespace - ignore
0454: return;
0455: } else {
0456: // Not whitespace - fail
0457: String msg = project.getElementName(element)
0458: + " doesn't support nested text data (\""
0459: + condenseText(text) + "\").";
0460: throw new BuildException(msg);
0461: }
0462: }
0463: try {
0464: addText.invoke(element, new Object[] { text });
0465: } catch (IllegalAccessException ie) {
0466: // impossible as getMethods should only return public methods
0467: throw new BuildException(ie);
0468: } catch (InvocationTargetException ite) {
0469: Throwable t = ite.getTargetException();
0470: if (t instanceof BuildException) {
0471: throw (BuildException) t;
0472: }
0473: throw new BuildException(t);
0474: }
0475: }
0476:
0477: /**
0478: * Utility method to throw a NotSupported exception
0479: *
0480: * @param project the Project instance.
0481: * @param parent the object which doesn't support a requested element
0482: * @param elementName the name of the Element which is trying to be created.
0483: */
0484: public void throwNotSupported(Project project, Object parent,
0485: String elementName) {
0486: String msg = project.getElementName(parent)
0487: + " doesn't support the nested \"" + elementName
0488: + "\" element.";
0489: throw new UnsupportedElementException(msg, elementName);
0490: }
0491:
0492: private NestedCreator getNestedCreator(Project project,
0493: String parentUri, Object parent, String elementName,
0494: UnknownElement child) throws BuildException {
0495:
0496: String uri = ProjectHelper
0497: .extractUriFromComponentName(elementName);
0498: String name = ProjectHelper
0499: .extractNameFromComponentName(elementName);
0500:
0501: if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
0502: uri = "";
0503: }
0504: if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
0505: parentUri = "";
0506: }
0507: NestedCreator nc = null;
0508: if (uri.equals(parentUri) || uri.equals("")) {
0509: nc = (NestedCreator) nestedCreators.get(name
0510: .toLowerCase(Locale.US));
0511: }
0512: if (nc == null) {
0513: nc = createAddTypeCreator(project, parent, elementName);
0514: }
0515: if (nc == null && parent instanceof DynamicElementNS) {
0516: DynamicElementNS dc = (DynamicElementNS) parent;
0517: String qName = (child == null ? name : child.getQName());
0518: final Object nestedElement = dc.createDynamicElement(
0519: (child == null ? "" : child.getNamespace()), name,
0520: qName);
0521: if (nestedElement != null) {
0522: nc = new NestedCreator(null) {
0523: Object create(Project project, Object parent,
0524: Object ignore) {
0525: return nestedElement;
0526: }
0527: };
0528: }
0529: }
0530: if (nc == null && parent instanceof DynamicElement) {
0531: DynamicElement dc = (DynamicElement) parent;
0532: final Object nestedElement = dc.createDynamicElement(name
0533: .toLowerCase(Locale.US));
0534: if (nestedElement != null) {
0535: nc = new NestedCreator(null) {
0536: Object create(Project project, Object parent,
0537: Object ignore) {
0538: return nestedElement;
0539: }
0540: };
0541: }
0542: }
0543: if (nc == null) {
0544: throwNotSupported(project, parent, elementName);
0545: }
0546: return nc;
0547: }
0548:
0549: /**
0550: * Creates a named nested element. Depending on the results of the
0551: * initial introspection, either a method in the given parent instance
0552: * or a simple no-arg constructor is used to create an instance of the
0553: * specified element type.
0554: *
0555: * @param project Project to which the parent object belongs.
0556: * Must not be <code>null</code>. If the resulting
0557: * object is an instance of ProjectComponent, its
0558: * Project reference is set to this parameter value.
0559: * @param parent Parent object used to create the instance.
0560: * Must not be <code>null</code>.
0561: * @param elementName Name of the element to create an instance of.
0562: * Must not be <code>null</code>.
0563: *
0564: * @return an instance of the specified element type
0565: * @deprecated since 1.6.x.
0566: * This is not a namespace aware method.
0567: *
0568: * @exception BuildException if no method is available to create the
0569: * element instance, or if the creating method
0570: * fails.
0571: */
0572: public Object createElement(Project project, Object parent,
0573: String elementName) throws BuildException {
0574: NestedCreator nc = getNestedCreator(project, "", parent,
0575: elementName, null);
0576: try {
0577: Object nestedElement = nc.create(project, parent, null);
0578: if (project != null) {
0579: project.setProjectReference(nestedElement);
0580: }
0581: return nestedElement;
0582: } catch (IllegalAccessException ie) {
0583: // impossible as getMethods should only return public methods
0584: throw new BuildException(ie);
0585: } catch (InstantiationException ine) {
0586: // impossible as getMethods should only return public methods
0587: throw new BuildException(ine);
0588: } catch (InvocationTargetException ite) {
0589: Throwable t = ite.getTargetException();
0590: if (t instanceof BuildException) {
0591: throw (BuildException) t;
0592: }
0593: throw new BuildException(t);
0594: }
0595: }
0596:
0597: /**
0598: * returns an object that creates and stores an object
0599: * for an element of a parent.
0600: *
0601: * @param project Project to which the parent object belongs.
0602: * @param parentUri The namespace uri of the parent object.
0603: * @param parent Parent object used to create the creator object to
0604: * create and store and instance of a subelement.
0605: * @param elementName Name of the element to create an instance of.
0606: * @param ue The unknown element associated with the element.
0607: * @return a creator object to create and store the element instance.
0608: */
0609: public Creator getElementCreator(Project project, String parentUri,
0610: Object parent, String elementName, UnknownElement ue) {
0611: NestedCreator nc = getNestedCreator(project, parentUri, parent,
0612: elementName, ue);
0613: return new Creator(project, parent, nc);
0614: }
0615:
0616: /**
0617: * Indicates whether the introspected class is a dynamic one,
0618: * supporting arbitrary nested elements and/or attributes.
0619: *
0620: * @return <code>true<code> if the introspected class is dynamic;
0621: * <code>false<code> otherwise.
0622: * @since Ant 1.6.3
0623: *
0624: * @see DynamicElement
0625: * @see DynamicElementNS
0626: */
0627: public boolean isDynamic() {
0628: return DynamicElement.class.isAssignableFrom(bean)
0629: || DynamicElementNS.class.isAssignableFrom(bean);
0630: }
0631:
0632: /**
0633: * Indicates whether the introspected class is a task container,
0634: * supporting arbitrary nested tasks/types.
0635: *
0636: * @return <code>true<code> if the introspected class is a container;
0637: * <code>false<code> otherwise.
0638: * @since Ant 1.6.3
0639: *
0640: * @see TaskContainer
0641: */
0642: public boolean isContainer() {
0643: return TaskContainer.class.isAssignableFrom(bean);
0644: }
0645:
0646: /**
0647: * Indicates if this element supports a nested element of the
0648: * given name.
0649: *
0650: * @param elementName the name of the nested element being checked
0651: *
0652: * @return true if the given nested element is supported
0653: */
0654: public boolean supportsNestedElement(String elementName) {
0655: return nestedCreators.containsKey(elementName
0656: .toLowerCase(Locale.US))
0657: || isDynamic() || addTypeMethods.size() != 0;
0658: }
0659:
0660: /**
0661: * Indicate if this element supports a nested element of the
0662: * given name.
0663: *
0664: * @param parentUri the uri of the parent
0665: * @param elementName the name of the nested element being checked
0666: *
0667: * @return true if the given nested element is supported
0668: */
0669: public boolean supportsNestedElement(String parentUri,
0670: String elementName) {
0671: if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
0672: parentUri = "";
0673: }
0674: String uri = ProjectHelper
0675: .extractUriFromComponentName(elementName);
0676: if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
0677: uri = "";
0678: }
0679: String name = ProjectHelper
0680: .extractNameFromComponentName(elementName);
0681:
0682: return (nestedCreators.containsKey(name.toLowerCase(Locale.US)) && (uri
0683: .equals(parentUri) || "".equals(uri)))
0684: || isDynamic() || addTypeMethods.size() != 0;
0685: }
0686:
0687: /**
0688: * Stores a named nested element using a storage method determined
0689: * by the initial introspection. If no appropriate storage method
0690: * is available, this method returns immediately.
0691: *
0692: * @param project Ignored in this implementation.
0693: * May be <code>null</code>.
0694: *
0695: * @param parent Parent instance to store the child in.
0696: * Must not be <code>null</code>.
0697: *
0698: * @param child Child instance to store in the parent.
0699: * Should not be <code>null</code>.
0700: *
0701: * @param elementName Name of the child element to store.
0702: * May be <code>null</code>, in which case
0703: * this method returns immediately.
0704: *
0705: * @exception BuildException if the storage method fails.
0706: */
0707: public void storeElement(Project project, Object parent,
0708: Object child, String elementName) throws BuildException {
0709: if (elementName == null) {
0710: return;
0711: }
0712: NestedCreator ns = (NestedCreator) nestedCreators
0713: .get(elementName.toLowerCase(Locale.US));
0714: if (ns == null) {
0715: return;
0716: }
0717: try {
0718: ns.store(parent, child);
0719: } catch (IllegalAccessException ie) {
0720: // impossible as getMethods should only return public methods
0721: throw new BuildException(ie);
0722: } catch (InstantiationException ine) {
0723: // impossible as getMethods should only return public methods
0724: throw new BuildException(ine);
0725: } catch (InvocationTargetException ite) {
0726: Throwable t = ite.getTargetException();
0727: if (t instanceof BuildException) {
0728: throw (BuildException) t;
0729: }
0730: throw new BuildException(t);
0731: }
0732: }
0733:
0734: /**
0735: * Returns the type of a named nested element.
0736: *
0737: * @param elementName The name of the element to find the type of.
0738: * Must not be <code>null</code>.
0739: *
0740: * @return the type of the nested element with the specified name.
0741: * This will never be <code>null</code>.
0742: *
0743: * @exception BuildException if the introspected class does not
0744: * support the named nested element.
0745: */
0746: public Class getElementType(String elementName)
0747: throws BuildException {
0748: Class nt = (Class) nestedTypes.get(elementName);
0749: if (nt == null) {
0750: throw new UnsupportedElementException("Class "
0751: + bean.getName() + " doesn't support the nested \""
0752: + elementName + "\" element.", elementName);
0753: }
0754: return nt;
0755: }
0756:
0757: /**
0758: * Returns the type of a named attribute.
0759: *
0760: * @param attributeName The name of the attribute to find the type of.
0761: * Must not be <code>null</code>.
0762: *
0763: * @return the type of the attribute with the specified name.
0764: * This will never be <code>null</code>.
0765: *
0766: * @exception BuildException if the introspected class does not
0767: * support the named attribute.
0768: */
0769: public Class getAttributeType(String attributeName)
0770: throws BuildException {
0771: Class at = (Class) attributeTypes.get(attributeName);
0772: if (at == null) {
0773: throw new UnsupportedAttributeException("Class "
0774: + bean.getName() + " doesn't support the \""
0775: + attributeName + "\" attribute.", attributeName);
0776: }
0777: return at;
0778: }
0779:
0780: /**
0781: * Returns the addText method when the introspected
0782: * class supports nested text.
0783: *
0784: * @return the method on this introspected class that adds nested text.
0785: * Cannot be <code>null</code>.
0786: * @throws BuildException if the introspected class does not
0787: * support the nested text.
0788: * @since Ant 1.6.3
0789: */
0790: public Method getAddTextMethod() throws BuildException {
0791: if (!supportsCharacters()) {
0792: throw new BuildException("Class " + bean.getName()
0793: + " doesn't support nested text data.");
0794: }
0795: return addText;
0796: }
0797:
0798: /**
0799: * Returns the adder or creator method of a named nested element.
0800: *
0801: * @param elementName The name of the attribute to find the setter
0802: * method of. Must not be <code>null</code>.
0803: * @return the method on this introspected class that adds or creates this
0804: * nested element. Can be <code>null</code> when the introspected
0805: * class is a dynamic configurator!
0806: * @throws BuildException if the introspected class does not
0807: * support the named nested element.
0808: * @since Ant 1.6.3
0809: */
0810: public Method getElementMethod(String elementName)
0811: throws BuildException {
0812: Object creator = nestedCreators.get(elementName);
0813: if (creator == null) {
0814: throw new UnsupportedElementException("Class "
0815: + bean.getName() + " doesn't support the nested \""
0816: + elementName + "\" element.", elementName);
0817: }
0818: return ((NestedCreator) creator).method;
0819: }
0820:
0821: /**
0822: * Returns the setter method of a named attribute.
0823: *
0824: * @param attributeName The name of the attribute to find the setter
0825: * method of. Must not be <code>null</code>.
0826: * @return the method on this introspected class that sets this attribute.
0827: * This will never be <code>null</code>.
0828: * @throws BuildException if the introspected class does not
0829: * support the named attribute.
0830: * @since Ant 1.6.3
0831: */
0832: public Method getAttributeMethod(String attributeName)
0833: throws BuildException {
0834: Object setter = attributeSetters.get(attributeName);
0835: if (setter == null) {
0836: throw new UnsupportedAttributeException("Class "
0837: + bean.getName() + " doesn't support the \""
0838: + attributeName + "\" attribute.", attributeName);
0839: }
0840: return ((AttributeSetter) setter).method;
0841: }
0842:
0843: /**
0844: * Returns whether or not the introspected class supports PCDATA.
0845: *
0846: * @return whether or not the introspected class supports PCDATA.
0847: */
0848: public boolean supportsCharacters() {
0849: return addText != null;
0850: }
0851:
0852: /**
0853: * Returns an enumeration of the names of the attributes supported
0854: * by the introspected class.
0855: *
0856: * @return an enumeration of the names of the attributes supported
0857: * by the introspected class.
0858: * @see #getAttributeMap
0859: */
0860: public Enumeration getAttributes() {
0861: return attributeSetters.keys();
0862: }
0863:
0864: /**
0865: * Returns a read-only map of attributes supported
0866: * by the introspected class.
0867: *
0868: * @return an attribute name to attribute <code>Class</code>
0869: * unmodifiable map. Can be empty, but never <code>null</code>.
0870: * @since Ant 1.6.3
0871: */
0872: public Map getAttributeMap() {
0873: return (attributeTypes.size() < 1) ? EMPTY_MAP : Collections
0874: .unmodifiableMap(attributeTypes);
0875: }
0876:
0877: /**
0878: * Returns an enumeration of the names of the nested elements supported
0879: * by the introspected class.
0880: *
0881: * @return an enumeration of the names of the nested elements supported
0882: * by the introspected class.
0883: * @see #getNestedElementMap
0884: */
0885: public Enumeration getNestedElements() {
0886: return nestedTypes.keys();
0887: }
0888:
0889: /**
0890: * Returns a read-only map of nested elements supported
0891: * by the introspected class.
0892: *
0893: * @return a nested-element name to nested-element <code>Class</code>
0894: * unmodifiable map. Can be empty, but never <code>null</code>.
0895: * @since Ant 1.6.3
0896: */
0897: public Map getNestedElementMap() {
0898: return (nestedTypes.size() < 1) ? EMPTY_MAP : Collections
0899: .unmodifiableMap(nestedTypes);
0900: }
0901:
0902: /**
0903: * Returns a read-only list of extension points supported
0904: * by the introspected class.
0905: * <p>
0906: * A task/type or nested element with void methods named <code>add()<code>
0907: * or <code>addConfigured()</code>, taking a single class or interface
0908: * argument, supports extensions point. This method returns the list of
0909: * all these <em>void add[Configured](type)</em> methods.
0910: *
0911: * @return a list of void, single argument add() or addConfigured()
0912: * <code>Method<code>s of all supported extension points.
0913: * These methods are sorted such that if the argument type of a
0914: * method derives from another type also an argument of a method
0915: * of this list, the method with the most derived argument will
0916: * always appear first. Can be empty, but never <code>null</code>.
0917: * @since Ant 1.6.3
0918: */
0919: public List getExtensionPoints() {
0920: return (addTypeMethods.size() < 1) ? Collections.EMPTY_LIST
0921: : Collections.unmodifiableList(addTypeMethods);
0922: }
0923:
0924: /**
0925: * Creates an implementation of AttributeSetter for the given
0926: * attribute type. Conversions (where necessary) are automatically
0927: * made for the following types:
0928: * <ul>
0929: * <li>String (left as it is)
0930: * <li>Character/char (first character is used)
0931: * <li>Boolean/boolean
0932: * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used)
0933: * <li>Class (Class.forName is used)
0934: * <li>File (resolved relative to the appropriate project)
0935: * <li>Path (resolve relative to the appropriate project)
0936: * <li>EnumeratedAttribute (uses its own
0937: * {@link EnumeratedAttribute#setValue(String) setValue} method)
0938: * <li>Other primitive types (wrapper classes are used with constructors
0939: * taking String)
0940: * </ul>
0941: *
0942: * If none of the above covers the given parameters, a constructor for the
0943: * appropriate class taking a String parameter is used if it is available.
0944: *
0945: * @param m The method to invoke on the bean when the setter is invoked.
0946: * Must not be <code>null</code>.
0947: * @param arg The type of the single argument of the bean's method.
0948: * Must not be <code>null</code>.
0949: * @param attrName the name of the attribute for which the setter is being
0950: * created.
0951: *
0952: * @return an appropriate AttributeSetter instance, or <code>null</code>
0953: * if no appropriate conversion is available.
0954: */
0955: private AttributeSetter createAttributeSetter(final Method m,
0956: Class arg, final String attrName) {
0957: // use wrappers for primitive classes, e.g. int and
0958: // Integer are treated identically
0959: final Class reflectedArg = PRIMITIVE_TYPE_MAP.containsKey(arg) ? (Class) PRIMITIVE_TYPE_MAP
0960: .get(arg)
0961: : arg;
0962:
0963: // simplest case - setAttribute expects String
0964: if (java.lang.String.class.equals(reflectedArg)) {
0965: return new AttributeSetter(m) {
0966: public void set(Project p, Object parent, String value)
0967: throws InvocationTargetException,
0968: IllegalAccessException {
0969: m.invoke(parent,
0970: (Object[]) (new String[] { value }));
0971: }
0972: };
0973: // char and Character get special treatment - take the first character
0974: } else if (java.lang.Character.class.equals(reflectedArg)) {
0975: return new AttributeSetter(m) {
0976: public void set(Project p, Object parent, String value)
0977: throws InvocationTargetException,
0978: IllegalAccessException {
0979: if (value.length() == 0) {
0980: throw new BuildException(
0981: "The value \"\" is not a "
0982: + "legal value for attribute \""
0983: + attrName + "\"");
0984: }
0985: m
0986: .invoke(
0987: parent,
0988: (Object[]) (new Character[] { new Character(
0989: value.charAt(0)) }));
0990: }
0991: };
0992: // boolean and Boolean get special treatment because we
0993: // have a nice method in Project
0994: } else if (java.lang.Boolean.class.equals(reflectedArg)) {
0995: return new AttributeSetter(m) {
0996: public void set(Project p, Object parent, String value)
0997: throws InvocationTargetException,
0998: IllegalAccessException {
0999: m.invoke(parent,
1000: (Object[]) (new Boolean[] { Project
1001: .toBoolean(value) ? Boolean.TRUE
1002: : Boolean.FALSE }));
1003: }
1004: };
1005: // Class doesn't have a String constructor but a decent factory method
1006: } else if (java.lang.Class.class.equals(reflectedArg)) {
1007: return new AttributeSetter(m) {
1008: public void set(Project p, Object parent, String value)
1009: throws InvocationTargetException,
1010: IllegalAccessException, BuildException {
1011: try {
1012: m.invoke(parent, new Object[] { Class
1013: .forName(value) });
1014: } catch (ClassNotFoundException ce) {
1015: throw new BuildException(ce);
1016: }
1017: }
1018: };
1019: // resolve relative paths through Project
1020: } else if (java.io.File.class.equals(reflectedArg)) {
1021: return new AttributeSetter(m) {
1022: public void set(Project p, Object parent, String value)
1023: throws InvocationTargetException,
1024: IllegalAccessException {
1025: m.invoke(parent, new Object[] { p
1026: .resolveFile(value) });
1027: }
1028: };
1029: // EnumeratedAttributes have their own helper class
1030: } else if (EnumeratedAttribute.class
1031: .isAssignableFrom(reflectedArg)) {
1032: return new AttributeSetter(m) {
1033: public void set(Project p, Object parent, String value)
1034: throws InvocationTargetException,
1035: IllegalAccessException, BuildException {
1036: try {
1037: EnumeratedAttribute ea = (EnumeratedAttribute) reflectedArg
1038: .newInstance();
1039: ea.setValue(value);
1040: m.invoke(parent, new Object[] { ea });
1041: } catch (InstantiationException ie) {
1042: throw new BuildException(ie);
1043: }
1044: }
1045: };
1046: } else if (reflectedArg.getSuperclass() != null
1047: && reflectedArg.getSuperclass().getName().equals(
1048: "java.lang.Enum")) {
1049: return new AttributeSetter(m) {
1050: public void set(Project p, Object parent, String value)
1051: throws InvocationTargetException,
1052: IllegalAccessException, BuildException {
1053: try {
1054: m
1055: .invoke(
1056: parent,
1057: new Object[] { reflectedArg
1058: .getMethod(
1059: "valueOf",
1060: new Class[] { String.class })
1061: .invoke(
1062: null,
1063: new Object[] { value }) });
1064: } catch (InvocationTargetException x) {
1065: if (x.getTargetException() instanceof IllegalArgumentException) {
1066: throw new BuildException("'" + value
1067: + "' is not a permitted value for "
1068: + reflectedArg.getName());
1069: } else {
1070: throw new BuildException(x
1071: .getTargetException());
1072: }
1073: } catch (Exception x) {
1074: throw new BuildException(x);
1075: }
1076: }
1077: };
1078: // worst case. look for a public String constructor and use it
1079: // also supports new Whatever(Project, String) as for Path or Reference
1080: // This is used (deliberately) for all primitives/wrappers other than
1081: // char and boolean
1082: } else {
1083: boolean includeProject;
1084: Constructor c;
1085: try {
1086: // First try with Project.
1087: c = reflectedArg.getConstructor(new Class[] {
1088: Project.class, String.class });
1089: includeProject = true;
1090: } catch (NoSuchMethodException nme) {
1091: // OK, try without.
1092: try {
1093: c = reflectedArg
1094: .getConstructor(new Class[] { String.class });
1095: includeProject = false;
1096: } catch (NoSuchMethodException nme2) {
1097: // Well, no matching constructor.
1098: return null;
1099: }
1100: }
1101: final boolean finalIncludeProject = includeProject;
1102: final Constructor finalConstructor = c;
1103:
1104: return new AttributeSetter(m) {
1105: public void set(Project p, Object parent, String value)
1106: throws InvocationTargetException,
1107: IllegalAccessException, BuildException {
1108: try {
1109: Object[] args = (finalIncludeProject) ? new Object[] {
1110: p, value }
1111: : new Object[] { value };
1112:
1113: Object attribute = finalConstructor
1114: .newInstance(args);
1115: if (p != null) {
1116: p.setProjectReference(attribute);
1117: }
1118: m.invoke(parent, new Object[] { attribute });
1119: } catch (InstantiationException ie) {
1120: throw new BuildException(ie);
1121: }
1122: }
1123: };
1124: }
1125: }
1126:
1127: /**
1128: * Returns a description of the type of the given element in
1129: * relation to a given project. This is used for logging purposes
1130: * when the element is asked to cope with some data it has no
1131: * way of handling.
1132: *
1133: * @param project The project the element is defined in.
1134: * Must not be <code>null</code>.
1135: *
1136: * @param element The element to describe.
1137: * Must not be <code>null</code>.
1138: *
1139: * @return a description of the element type
1140: */
1141: protected String getElementName(Project project, Object element) {
1142: return project.getElementName(element);
1143: }
1144:
1145: /**
1146: * Extracts the name of a property from a method name by subtracting
1147: * a given prefix and converting into lower case. It is up to calling
1148: * code to make sure the method name does actually begin with the
1149: * specified prefix - no checking is done in this method.
1150: *
1151: * @param methodName The name of the method in question.
1152: * Must not be <code>null</code>.
1153: * @param prefix The prefix to remove.
1154: * Must not be <code>null</code>.
1155: *
1156: * @return the lower-cased method name with the prefix removed.
1157: */
1158: private String getPropertyName(String methodName, String prefix) {
1159: return methodName.substring(prefix.length()).toLowerCase(
1160: Locale.US);
1161: }
1162:
1163: /**
1164: * creator - allows use of create/store external
1165: * to IntrospectionHelper.
1166: * The class is final as it has a private constructor.
1167: */
1168: public static final class Creator {
1169: private NestedCreator nestedCreator;
1170: private Object parent;
1171: private Project project;
1172: private Object nestedObject;
1173: private String polyType;
1174:
1175: /**
1176: * Creates a new Creator instance.
1177: * This object is given to the UnknownElement to create
1178: * objects for sub-elements. UnknownElement calls
1179: * create to create an object, the object then gets
1180: * configured and then UnknownElement calls store.
1181: * SetPolyType may be used to override the type used
1182: * to create the object with. SetPolyType gets called
1183: * before create.
1184: *
1185: * @param project the current project
1186: * @param parent the parent object to create the object in
1187: * @param nestedCreator the nested creator object to use
1188: */
1189: private Creator(Project project, Object parent,
1190: NestedCreator nestedCreator) {
1191: this .project = project;
1192: this .parent = parent;
1193: this .nestedCreator = nestedCreator;
1194: }
1195:
1196: /**
1197: * Used to override the class used to create the object.
1198: *
1199: * @param polyType a ant component type name
1200: */
1201: public void setPolyType(String polyType) {
1202: this .polyType = polyType;
1203: }
1204:
1205: /**
1206: * Create an object using this creator, which is determined
1207: * by introspection.
1208: *
1209: * @return the created object
1210: */
1211: public Object create() {
1212: if (polyType != null) {
1213: if (!nestedCreator.isPolyMorphic()) {
1214: throw new BuildException(
1215: "Not allowed to use the polymorphic form"
1216: + " for this element");
1217: }
1218: ComponentHelper helper = ComponentHelper
1219: .getComponentHelper(project);
1220: nestedObject = helper.createComponent(polyType);
1221: if (nestedObject == null) {
1222: throw new BuildException(
1223: "Unable to create object of type "
1224: + polyType);
1225: }
1226: }
1227: try {
1228: nestedObject = nestedCreator.create(project, parent,
1229: nestedObject);
1230: if (project != null) {
1231: project.setProjectReference(nestedObject);
1232: }
1233: return nestedObject;
1234: } catch (IllegalAccessException ex) {
1235: throw new BuildException(ex);
1236: } catch (InstantiationException ex) {
1237: throw new BuildException(ex);
1238: } catch (IllegalArgumentException ex) {
1239: if (polyType != null) {
1240: throw new BuildException("Invalid type used "
1241: + polyType);
1242: }
1243: throw ex;
1244: } catch (InvocationTargetException ex) {
1245: Throwable t = ex.getTargetException();
1246: if (t instanceof BuildException) {
1247: throw (BuildException) t;
1248: }
1249: throw new BuildException(t);
1250: }
1251: }
1252:
1253: /**
1254: * @return the real object (used currently only
1255: * for preset def).
1256: */
1257: public Object getRealObject() {
1258: return nestedCreator.getRealObject();
1259: }
1260:
1261: /**
1262: * Stores the nested element object using a storage method
1263: * determined by introspection.
1264: *
1265: */
1266: public void store() {
1267: try {
1268: nestedCreator.store(parent, nestedObject);
1269: } catch (IllegalAccessException ex) {
1270: throw new BuildException(ex);
1271: } catch (InstantiationException ex) {
1272: throw new BuildException(ex);
1273: } catch (IllegalArgumentException ex) {
1274: if (polyType != null) {
1275: throw new BuildException("Invalid type used "
1276: + polyType);
1277: }
1278: throw ex;
1279: } catch (InvocationTargetException ex) {
1280: Throwable t = ex.getTargetException();
1281: if (t instanceof BuildException) {
1282: throw (BuildException) t;
1283: }
1284: throw new BuildException(t);
1285: }
1286: }
1287: }
1288:
1289: /**
1290: * Internal interface used to create nested elements. Not documented
1291: * in detail for reasons of source code readability.
1292: */
1293: private abstract static class NestedCreator {
1294: private Method method; // the method called to add/create the nested element
1295:
1296: NestedCreator(Method m) {
1297: this .method = m;
1298: }
1299:
1300: Method getMethod() {
1301: return method;
1302: }
1303:
1304: boolean isPolyMorphic() {
1305: return false;
1306: }
1307:
1308: Object getRealObject() {
1309: return null;
1310: }
1311:
1312: abstract Object create(Project project, Object parent,
1313: Object child) throws InvocationTargetException,
1314: IllegalAccessException, InstantiationException;
1315:
1316: void store(Object parent, Object child)
1317: throws InvocationTargetException,
1318: IllegalAccessException, InstantiationException {
1319: // DO NOTHING
1320: }
1321: }
1322:
1323: private class CreateNestedCreator extends NestedCreator {
1324: CreateNestedCreator(Method m) {
1325: super (m);
1326: }
1327:
1328: Object create(Project project, Object parent, Object ignore)
1329: throws InvocationTargetException,
1330: IllegalAccessException {
1331: return getMethod().invoke(parent, new Object[] {});
1332: }
1333: }
1334:
1335: /** Version to use for addXXX and addConfiguredXXX */
1336: private class AddNestedCreator extends NestedCreator {
1337:
1338: static final int ADD = 1;
1339: static final int ADD_CONFIGURED = 2;
1340:
1341: private Constructor constructor;
1342: private int behavior; // ADD or ADD_CONFIGURED
1343:
1344: AddNestedCreator(Method m, Constructor c, int behavior) {
1345: super (m);
1346: this .constructor = c;
1347: this .behavior = behavior;
1348: }
1349:
1350: boolean isPolyMorphic() {
1351: return true;
1352: }
1353:
1354: Object create(Project project, Object parent, Object child)
1355: throws InvocationTargetException,
1356: IllegalAccessException, InstantiationException {
1357: if (child == null) {
1358: child = constructor
1359: .newInstance((constructor.getParameterTypes().length == 0) ? new Object[] {}
1360: : new Object[] { project });
1361: }
1362: if (child instanceof PreSetDef.PreSetDefinition) {
1363: child = ((PreSetDef.PreSetDefinition) child)
1364: .createObject(project);
1365: }
1366: if (behavior == ADD) {
1367: istore(parent, child);
1368: }
1369: return child;
1370: }
1371:
1372: void store(Object parent, Object child)
1373: throws InvocationTargetException,
1374: IllegalAccessException, InstantiationException {
1375: if (behavior == ADD_CONFIGURED) {
1376: istore(parent, child);
1377: }
1378: }
1379:
1380: private void istore(Object parent, Object child)
1381: throws InvocationTargetException,
1382: IllegalAccessException, InstantiationException {
1383: getMethod().invoke(parent, new Object[] { child });
1384: }
1385: }
1386:
1387: /**
1388: * Internal interface used to setting element attributes. Not documented
1389: * in detail for reasons of source code readability.
1390: */
1391: private abstract static class AttributeSetter {
1392: private Method method; // the method called to set the attribute
1393:
1394: AttributeSetter(Method m) {
1395: this .method = m;
1396: }
1397:
1398: abstract void set(Project p, Object parent, String value)
1399: throws InvocationTargetException,
1400: IllegalAccessException, BuildException;
1401: }
1402:
1403: /**
1404: * Clears the static cache of on build finished.
1405: */
1406: public static void clearCache() {
1407: HELPERS.clear();
1408: }
1409:
1410: /**
1411: *
1412: */
1413: private NestedCreator createAddTypeCreator(Project project,
1414: Object parent, String elementName) throws BuildException {
1415: if (addTypeMethods.size() == 0) {
1416: return null;
1417: }
1418: ComponentHelper helper = ComponentHelper
1419: .getComponentHelper(project);
1420:
1421: Object addedObject = null;
1422: Method addMethod = null;
1423: Class clazz = helper.getComponentClass(elementName);
1424: if (clazz == null) {
1425: return null;
1426: }
1427: addMethod = findMatchingMethod(clazz, addTypeMethods);
1428: if (addMethod == null) {
1429: return null;
1430: }
1431: addedObject = helper.createComponent(elementName);
1432: if (addedObject == null) {
1433: return null;
1434: }
1435: Object rObject = addedObject;
1436: if (addedObject instanceof PreSetDef.PreSetDefinition) {
1437: rObject = ((PreSetDef.PreSetDefinition) addedObject)
1438: .createObject(project);
1439: }
1440: final Object nestedObject = addedObject;
1441: final Object realObject = rObject;
1442:
1443: return new NestedCreator(addMethod) {
1444: Object create(Project project, Object parent, Object ignore)
1445: throws InvocationTargetException,
1446: IllegalAccessException {
1447: if (!getMethod().getName().endsWith("Configured")) {
1448: getMethod().invoke(parent,
1449: new Object[] { realObject });
1450: }
1451: return nestedObject;
1452: }
1453:
1454: Object getRealObject() {
1455: return realObject;
1456: }
1457:
1458: void store(Object parent, Object child)
1459: throws InvocationTargetException,
1460: IllegalAccessException, InstantiationException {
1461: if (getMethod().getName().endsWith("Configured")) {
1462: getMethod().invoke(parent,
1463: new Object[] { realObject });
1464: }
1465: }
1466: };
1467: }
1468:
1469: /**
1470: * Inserts an add or addConfigured method into
1471: * the addTypeMethods array. The array is
1472: * ordered so that the more derived classes
1473: * are first.
1474: * If both add and addConfigured are present, the addConfigured
1475: * will take priority.
1476: * @param method the <code>Method</code> to insert.
1477: */
1478: private void insertAddTypeMethod(Method method) {
1479: Class argClass = method.getParameterTypes()[0];
1480: for (int c = 0; c < addTypeMethods.size(); ++c) {
1481: Method current = (Method) addTypeMethods.get(c);
1482: if (current.getParameterTypes()[0].equals(argClass)) {
1483: if (method.getName().equals("addConfigured")) {
1484: // add configured replaces the add method
1485: addTypeMethods.set(c, method);
1486: }
1487: return; // Already present
1488: }
1489: if (current.getParameterTypes()[0]
1490: .isAssignableFrom(argClass)) {
1491: addTypeMethods.add(c, method);
1492: return; // higher derived
1493: }
1494: }
1495: addTypeMethods.add(method);
1496: }
1497:
1498: /**
1499: * Search the list of methods to find the first method
1500: * that has a parameter that accepts the nested element object.
1501: * @param paramClass the <code>Class</code> type to search for.
1502: * @param methods the <code>List</code> of methods to search.
1503: * @return a matching <code>Method</code>; null if none found.
1504: */
1505: private Method findMatchingMethod(Class paramClass, List methods) {
1506: Class matchedClass = null;
1507: Method matchedMethod = null;
1508:
1509: for (int i = 0; i < methods.size(); ++i) {
1510: Method method = (Method) methods.get(i);
1511: Class methodClass = method.getParameterTypes()[0];
1512: if (methodClass.isAssignableFrom(paramClass)) {
1513: if (matchedClass == null) {
1514: matchedClass = methodClass;
1515: matchedMethod = method;
1516: } else {
1517: if (!methodClass.isAssignableFrom(matchedClass)) {
1518: throw new BuildException("ambiguous: types "
1519: + matchedClass.getName() + " and "
1520: + methodClass.getName() + " match "
1521: + paramClass.getName());
1522: }
1523: }
1524: }
1525: }
1526: return matchedMethod;
1527: }
1528:
1529: private String condenseText(final String text) {
1530: if (text.length() <= MAX_REPORT_NESTED_TEXT) {
1531: return text;
1532: }
1533: int ends = (MAX_REPORT_NESTED_TEXT - ELLIPSIS.length()) / 2;
1534: return new StringBuffer(text).replace(ends,
1535: text.length() - ends, ELLIPSIS).toString();
1536: }
1537: }
|