KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > IntrospectionHelper


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 package org.apache.tools.ant;
20
21 import java.lang.reflect.Constructor JavaDoc;
22 import java.lang.reflect.InvocationTargetException JavaDoc;
23 import java.lang.reflect.Method JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.Collections JavaDoc;
26 import java.util.Enumeration JavaDoc;
27 import java.util.Hashtable JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.Locale JavaDoc;
31 import java.util.Map JavaDoc;
32 import org.apache.tools.ant.types.EnumeratedAttribute;
33 import org.apache.tools.ant.taskdefs.PreSetDef;
34
35 /**
36  * Helper class that collects the methods a task or nested element
37  * holds to set attributes, create nested elements or hold PCDATA
38  * elements.
39  * The class is final as it has a private constructor.
40  */

41 public final class IntrospectionHelper {
42
43     /**
44      * EMPTY_MAP was added in java 1.3 (EMPTY_SET and EMPTY_LIST
45      * is in java 1.2!)
46      */

47     private static final Map JavaDoc EMPTY_MAP
48         = Collections.unmodifiableMap(new HashMap JavaDoc(0));
49
50     /**
51      * Helper instances we've already created (Class.getName() to IntrospectionHelper).
52      */

53     private static final Map JavaDoc HELPERS = new Hashtable JavaDoc();
54
55     /**
56      * Map from primitive types to wrapper classes for use in
57      * createAttributeSetter (Class to Class). Note that char
58      * and boolean are in here even though they get special treatment
59      * - this way we only need to test for the wrapper class.
60      */

61     private static final Map JavaDoc PRIMITIVE_TYPE_MAP = new HashMap JavaDoc(8);
62
63     // Set up PRIMITIVE_TYPE_MAP
64
static {
65         Class JavaDoc[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE,
66                               Short.TYPE, Integer.TYPE, Long.TYPE,
67                               Float.TYPE, Double.TYPE};
68         Class JavaDoc[] wrappers = {Boolean JavaDoc.class, Byte JavaDoc.class, Character JavaDoc.class,
69                             Short JavaDoc.class, Integer JavaDoc.class, Long JavaDoc.class,
70                             Float JavaDoc.class, Double JavaDoc.class};
71         for (int i = 0; i < primitives.length; i++) {
72             PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]);
73         }
74     }
75
76     private static final int MAX_REPORT_NESTED_TEXT = 20;
77     private static final String JavaDoc ELLIPSIS = "...";
78
79     /**
80      * Map from attribute names to attribute types
81      * (String to Class).
82      */

83     private Hashtable JavaDoc attributeTypes = new Hashtable JavaDoc();
84
85     /**
86      * Map from attribute names to attribute setter methods
87      * (String to AttributeSetter).
88      */

89     private Hashtable JavaDoc attributeSetters = new Hashtable JavaDoc();
90
91     /**
92      * Map from attribute names to nested types
93      * (String to Class).
94      */

95     private Hashtable JavaDoc nestedTypes = new Hashtable JavaDoc();
96
97     /**
98      * Map from attribute names to methods to create nested types
99      * (String to NestedCreator).
100      */

101     private Hashtable JavaDoc nestedCreators = new Hashtable JavaDoc();
102
103     /**
104      * Vector of methods matching add[Configured](Class) pattern.
105      */

106     private List JavaDoc addTypeMethods = new ArrayList JavaDoc();
107
108     /**
109      * The method to invoke to add PCDATA.
110      */

111     private Method JavaDoc addText = null;
112
113     /**
114      * The class introspected by this instance.
115      */

116     private Class JavaDoc bean;
117
118     /**
119      * Sole constructor, which is private to ensure that all
120      * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
121      * Introspects the given class for bean-like methods.
122      * Each method is examined in turn, and the following rules are applied:
123      * <p>
124      * <ul>
125      * <li>If the method is <code>Task.setLocation(Location)</code>,
126      * <code>Task.setTaskType(String)</code>
127      * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These
128      * methods are handled differently elsewhere.
129      * <li><code>void addText(String)</code> is recognised as the method for
130      * adding PCDATA to a bean.
131      * <li><code>void setFoo(Bar)</code> is recognised as a method for
132      * setting the value of attribute <code>foo</code>, so long as
133      * <code>Bar</code> is non-void and is not an array type. Non-String
134      * parameter types always overload String parameter types, but that is
135      * the only guarantee made in terms of priority.
136      * <li><code>Foo createBar()</code> is recognised as a method for
137      * creating a nested element called <code>bar</code> of type
138      * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
139      * array type.
140      * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
141      * method for storing a pre-configured element called
142      * <code>foo</code> and of type <code>Bar</code>, so long as
143      * <code>Bar</code> is not an array, primitive or String type.
144      * <code>Bar</code> must have an accessible constructor taking no
145      * arguments.
146      * <li><code>void addFoo(Bar)</code> is recognised as a method for storing
147      * an element called <code>foo</code> and of type <code>Bar</code>, so
148      * long as <code>Bar</code> is not an array, primitive or String type.
149      * <code>Bar</code> must have an accessible constructor taking no
150      * arguments. This is distinct from the 'addConfigured' idiom in that
151      * the nested element is added to the parent immediately after it is
152      * constructed; in practice this means that <code>addFoo(Bar)</code> should
153      * do little or nothing with its argument besides storing it for later use.
154      * </ul>
155      * Note that only one method is retained to create/set/addConfigured/add
156      * any element or attribute.
157      *
158      * @param bean The bean type to introspect.
159      * Must not be <code>null</code>.
160      *
161      * @see #getHelper(Class)
162      */

163     private IntrospectionHelper(final Class JavaDoc bean) {
164         this.bean = bean;
165         Method JavaDoc[] methods = bean.getMethods();
166         for (int i = 0; i < methods.length; i++) {
167             final Method JavaDoc m = methods[i];
168             final String JavaDoc name = m.getName();
169             Class JavaDoc returnType = m.getReturnType();
170             Class JavaDoc[] args = m.getParameterTypes();
171
172             // check of add[Configured](Class) pattern
173
if (args.length == 1 && java.lang.Void.TYPE.equals(returnType)
174                 && ("add".equals(name) || "addConfigured".equals(name))) {
175                 insertAddTypeMethod(m);
176                 continue;
177             }
178             // not really user settable properties on tasks/project components
179
if (org.apache.tools.ant.ProjectComponent.class.isAssignableFrom(
180                     bean)
181                  && args.length == 1 && isHiddenSetMethod(name, args[0])) {
182                 continue;
183             }
184             // hide addTask for TaskContainers
185
if (isContainer() && args.length == 1 && "addTask".equals(name)
186                 && org.apache.tools.ant.Task.class.equals(args[0])) {
187                 continue;
188             }
189             if ("addText".equals(name) && java.lang.Void.TYPE.equals(returnType)
190                 && args.length == 1 && java.lang.String JavaDoc.class.equals(args[0])) {
191
192                 addText = methods[i];
193             } else if (name.startsWith("set")
194                        && java.lang.Void.TYPE.equals(returnType)
195                        && args.length == 1 && !args[0].isArray()) {
196                 String JavaDoc propName = getPropertyName(name, "set");
197                 if (attributeSetters.get(propName) != null) {
198                     if (java.lang.String JavaDoc.class.equals(args[0])) {
199                         /*
200                             Ignore method m, as there is an overloaded
201                             form of this method that takes in a
202                             non-string argument, which gains higher
203                             priority.
204                         */

205                         continue;
206                     }
207                     /*
208                         If the argument is not a String or Location,
209                         and if there
210                         is an overloaded form of this method already defined,
211                         we just override that with the new one.
212                         This mechanism does not guarantee any specific order
213                         in which the methods will be selected: so any code
214                         that depends on the order in which "set" methods have
215                         been defined, is not guaranteed to be selected in any
216                         particular order.
217                     */

218                 }
219                 AttributeSetter as = createAttributeSetter(m, args[0], propName);
220                 if (as != null) {
221                     attributeTypes.put(propName, args[0]);
222                     attributeSetters.put(propName, as);
223                 }
224             } else if (name.startsWith("create") && !returnType.isArray()
225                        && !returnType.isPrimitive() && args.length == 0) {
226
227                 String JavaDoc propName = getPropertyName(name, "create");
228                 // Check if a create of this property is already present
229
// add takes preference over create for CB purposes
230
if (nestedCreators.get(propName) == null) {
231                     nestedTypes.put(propName, returnType);
232                     nestedCreators.put(propName, new CreateNestedCreator(m));
233                 }
234             } else if (name.startsWith("addConfigured")
235                 && java.lang.Void.TYPE.equals(returnType) && args.length == 1
236                 && !java.lang.String JavaDoc.class.equals(args[0])
237                 && !args[0].isArray() && !args[0].isPrimitive()) {
238                 try {
239                     Constructor JavaDoc constructor = null;
240                     try {
241                         constructor = args[0].getConstructor(new Class JavaDoc[] {});
242                     } catch (NoSuchMethodException JavaDoc ex) {
243                         constructor =
244                             args[0].getConstructor(new Class JavaDoc[] {Project.class});
245                     }
246                     String JavaDoc propName = getPropertyName(name, "addConfigured");
247                     nestedTypes.put(propName, args[0]);
248                     nestedCreators.put(propName, new AddNestedCreator(m,
249                         constructor, AddNestedCreator.ADD_CONFIGURED));
250                 } catch (NoSuchMethodException JavaDoc nse) {
251                     // ignore
252
}
253             } else if (name.startsWith("add")
254                 && java.lang.Void.TYPE.equals(returnType) && args.length == 1
255                 && !java.lang.String JavaDoc.class.equals(args[0])
256                 && !args[0].isArray() && !args[0].isPrimitive()) {
257                 try {
258                     Constructor JavaDoc constructor = null;
259                     try {
260                         constructor = args[0].getConstructor(new Class JavaDoc[] {});
261                     } catch (NoSuchMethodException JavaDoc ex) {
262                         constructor =
263                             args[0].getConstructor(new Class JavaDoc[] {Project.class});
264                     }
265
266                     String JavaDoc propName = getPropertyName(name, "add");
267                     if (nestedTypes.get(propName) != null) {
268                         /*
269                          * Ignore this method as there is an addConfigured
270                          * form of this method that has a higher
271                          * priority
272                          */

273                         continue;
274                     }
275                     nestedTypes.put(propName, args[0]);
276                     nestedCreators.put(propName, new AddNestedCreator(m,
277                         constructor, AddNestedCreator.ADD));
278                 } catch (NoSuchMethodException JavaDoc nse) {
279                     // ignore
280
}
281             }
282         }
283     }
284
285     /**
286      * Certain set methods are part of the Ant core interface to tasks and
287      * therefore not to be considered for introspection
288      *
289      * @param name the name of the set method
290      * @param type the type of the set method's parameter
291      * @return true if the given set method is to be hidden.
292      */

293     private boolean isHiddenSetMethod(String JavaDoc name, Class JavaDoc type) {
294         if ("setLocation".equals(name)
295              && org.apache.tools.ant.Location.class.equals(type)) {
296             return true;
297         }
298
299         if ("setTaskType".equals(name)
300              && java.lang.String JavaDoc.class.equals(type)) {
301             return true;
302         }
303
304         return false;
305     }
306
307     /**
308      * Returns a helper for the given class, either from the cache
309      * or by creating a new instance.
310      *
311      * @param c The class for which a helper is required.
312      * Must not be <code>null</code>.
313      *
314      * @return a helper for the specified class
315      */

316     public static synchronized IntrospectionHelper getHelper(Class JavaDoc c) {
317         return getHelper(null, c);
318     }
319
320     /**
321      * Returns a helper for the given class, either from the cache
322      * or by creating a new instance.
323      *
324      * The method will make sure the helper will be cleaned up at the end of
325      * the project, and only one instance will be created for each class.
326      *
327      * @param p the project instance.
328      * @param c The class for which a helper is required.
329      * Must not be <code>null</code>.
330      *
331      * @return a helper for the specified class
332      */

333     public static IntrospectionHelper getHelper(Project p, Class JavaDoc c) {
334         IntrospectionHelper ih = (IntrospectionHelper) HELPERS.get(c.getName());
335         // If a helper cannot be found, or if the helper is for another
336
// classloader, create a new IH
337
if (ih == null || ih.bean != c) {
338             ih = new IntrospectionHelper(c);
339             if (p != null) {
340                 // #30162: do *not* cache this if there is no project, as we
341
// cannot guarantee that the cache will be cleared.
342
HELPERS.put(c.getName(), ih);
343             }
344         }
345         return ih;
346     }
347
348     /**
349      * Sets the named attribute in the given element, which is part of the
350      * given project.
351      *
352      * @param p The project containing the element. This is used when files
353      * need to be resolved. Must not be <code>null</code>.
354      * @param element The element to set the attribute in. Must not be
355      * <code>null</code>.
356      * @param attributeName The name of the attribute to set. Must not be
357      * <code>null</code>.
358      * @param value The value to set the attribute to. This may be interpreted
359      * or converted to the necessary type if the setter method
360      * doesn't just take a string. Must not be <code>null</code>.
361      *
362      * @exception BuildException if the introspected class doesn't support
363      * the given attribute, or if the setting
364      * method fails.
365      */

366     public void setAttribute(Project p, Object JavaDoc element, String JavaDoc attributeName,
367                              String JavaDoc value) throws BuildException {
368         AttributeSetter as
369             = (AttributeSetter) attributeSetters.get(
370                 attributeName.toLowerCase(Locale.US));
371         if (as == null) {
372             if (element instanceof DynamicAttributeNS) {
373                 DynamicAttributeNS dc = (DynamicAttributeNS) element;
374                 String JavaDoc uriPlusPrefix =
375                     ProjectHelper.extractUriFromComponentName(attributeName);
376                 String JavaDoc uri =
377                     ProjectHelper.extractUriFromComponentName(uriPlusPrefix);
378                 String JavaDoc localName =
379                     ProjectHelper.extractNameFromComponentName(attributeName);
380                 String JavaDoc qName = ("".equals(uri)
381                                 ? localName : (uri + ":" + localName));
382
383                 dc.setDynamicAttribute(uri, localName, qName, value);
384                 return;
385             } else if (element instanceof DynamicAttribute) {
386                 DynamicAttribute dc = (DynamicAttribute) element;
387                 dc.setDynamicAttribute(attributeName.toLowerCase(Locale.US), value);
388                 return;
389             } else {
390                 if (attributeName.indexOf(':') != -1) {
391                     return; // Ignore attribute from unknown uri's
392
}
393                 String JavaDoc msg = getElementName(p, element)
394                     + " doesn't support the \"" + attributeName
395                     + "\" attribute.";
396                 throw new UnsupportedAttributeException(msg, attributeName);
397             }
398         }
399         try {
400             as.set(p, element, value);
401         } catch (IllegalAccessException JavaDoc ie) {
402             // impossible as getMethods should only return public methods
403
throw new BuildException(ie);
404         } catch (InvocationTargetException JavaDoc ite) {
405             Throwable JavaDoc t = ite.getTargetException();
406             if (t instanceof BuildException) {
407                 throw (BuildException) t;
408             }
409             throw new BuildException(t);
410         }
411     }
412
413
414     /**
415      * Adds PCDATA to an element, using the element's
416      * <code>void addText(String)</code> method, if it has one. If no
417      * such method is present, a BuildException is thrown if the
418      * given text contains non-whitespace.
419      *
420      * @param project The project which the element is part of.
421      * Must not be <code>null</code>.
422      * @param element The element to add the text to.
423      * Must not be <code>null</code>.
424      * @param text The text to add.
425      * Must not be <code>null</code>.
426      *
427      * @exception BuildException if non-whitespace text is provided and no
428      * method is available to handle it, or if
429      * the handling method fails.
430      */

431     public void addText(Project project, Object JavaDoc element, String JavaDoc text)
432         throws BuildException {
433         if (addText == null) {
434             text = text.trim();
435             // Element doesn't handle text content
436
if (text.length() == 0) {
437                 // Only whitespace - ignore
438
return;
439             } else {
440                 // Not whitespace - fail
441
String JavaDoc msg = project.getElementName(element)
442                     + " doesn't support nested text data (\""
443                     + condenseText(text) + "\").";
444                 throw new BuildException(msg);
445             }
446         }
447         try {
448             addText.invoke(element, new Object JavaDoc[] {text});
449         } catch (IllegalAccessException JavaDoc ie) {
450             // impossible as getMethods should only return public methods
451
throw new BuildException(ie);
452         } catch (InvocationTargetException JavaDoc ite) {
453             Throwable JavaDoc t = ite.getTargetException();
454             if (t instanceof BuildException) {
455                 throw (BuildException) t;
456             }
457             throw new BuildException(t);
458         }
459     }
460
461     /**
462      * Utility method to throw a NotSupported exception
463      *
464      * @param project the Project instance.
465      * @param parent the object which doesn't support a requested element
466      * @param elementName the name of the Element which is trying to be created.
467      */

468     public void throwNotSupported(Project project, Object JavaDoc parent,
469         String JavaDoc elementName) {
470         String JavaDoc msg = project.getElementName(parent)
471             + " doesn't support the nested \"" + elementName + "\" element.";
472         throw new UnsupportedElementException(msg, elementName);
473     }
474
475     private NestedCreator getNestedCreator(
476         Project project, String JavaDoc parentUri, Object JavaDoc parent,
477         String JavaDoc elementName, UnknownElement child) throws BuildException {
478
479         String JavaDoc uri = ProjectHelper.extractUriFromComponentName(elementName);
480         String JavaDoc name = ProjectHelper.extractNameFromComponentName(elementName);
481
482         if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
483             uri = "";
484         }
485         if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
486             parentUri = "";
487         }
488         NestedCreator nc = null;
489         if (uri.equals(parentUri) || uri.equals("")) {
490             nc = (NestedCreator) nestedCreators.get(
491                 name.toLowerCase(Locale.US));
492         }
493         if (nc == null) {
494             nc = createAddTypeCreator(project, parent, elementName);
495         }
496         if (nc == null && parent instanceof DynamicElementNS) {
497             DynamicElementNS dc = (DynamicElementNS) parent;
498             String JavaDoc qName = (child == null ? name : child.getQName());
499             final Object JavaDoc nestedElement =
500                 dc.createDynamicElement(
501                     (child == null ? "" : child.getNamespace()),
502                     name, qName);
503             if (nestedElement != null) {
504                 nc = new NestedCreator(null) {
505                     Object JavaDoc create(
506                         Project project, Object JavaDoc parent, Object JavaDoc ignore) {
507                         return nestedElement;
508                     }
509                 };
510             }
511         }
512         if (nc == null && parent instanceof DynamicElement) {
513             DynamicElement dc = (DynamicElement) parent;
514             final Object JavaDoc nestedElement =
515                 dc.createDynamicElement(name.toLowerCase(Locale.US));
516             if (nestedElement != null) {
517                 nc = new NestedCreator(null) {
518                     Object JavaDoc create(
519                         Project project, Object JavaDoc parent, Object JavaDoc ignore) {
520                         return nestedElement;
521                     }
522                 };
523             }
524         }
525         if (nc == null) {
526             throwNotSupported(project, parent, elementName);
527         }
528         return nc;
529     }
530
531     /**
532      * Creates a named nested element. Depending on the results of the
533      * initial introspection, either a method in the given parent instance
534      * or a simple no-arg constructor is used to create an instance of the
535      * specified element type.
536      *
537      * @param project Project to which the parent object belongs.
538      * Must not be <code>null</code>. If the resulting
539      * object is an instance of ProjectComponent, its
540      * Project reference is set to this parameter value.
541      * @param parent Parent object used to create the instance.
542      * Must not be <code>null</code>.
543      * @param elementName Name of the element to create an instance of.
544      * Must not be <code>null</code>.
545      *
546      * @return an instance of the specified element type
547      * @deprecated since 1.6.x.
548      * This is not a namespace aware method.
549      *
550      * @exception BuildException if no method is available to create the
551      * element instance, or if the creating method
552      * fails.
553      */

554     public Object JavaDoc createElement(Project project, Object JavaDoc parent,
555         String JavaDoc elementName) throws BuildException {
556         NestedCreator nc = getNestedCreator(project, "", parent, elementName, null);
557         try {
558             Object JavaDoc nestedElement = nc.create(project, parent, null);
559             if (project != null) {
560                 project.setProjectReference(nestedElement);
561             }
562             return nestedElement;
563         } catch (IllegalAccessException JavaDoc ie) {
564             // impossible as getMethods should only return public methods
565
throw new BuildException(ie);
566         } catch (InstantiationException JavaDoc ine) {
567             // impossible as getMethods should only return public methods
568
throw new BuildException(ine);
569         } catch (InvocationTargetException JavaDoc ite) {
570             Throwable JavaDoc t = ite.getTargetException();
571             if (t instanceof BuildException) {
572                 throw (BuildException) t;
573             }
574             throw new BuildException(t);
575         }
576     }
577
578     /**
579      * returns an object that creates and stores an object
580      * for an element of a parent.
581      *
582      * @param project Project to which the parent object belongs.
583      * @param parentUri The namespace uri of the parent object.
584      * @param parent Parent object used to create the creator object to
585      * create and store and instance of a subelement.
586      * @param elementName Name of the element to create an instance of.
587      * @param ue The unknown element associated with the element.
588      * @return a creator object to create and store the element instance.
589      */

590     public Creator getElementCreator(
591         Project project, String JavaDoc parentUri, Object JavaDoc parent, String JavaDoc elementName,
592         UnknownElement ue) {
593         NestedCreator nc = getNestedCreator(
594             project, parentUri, parent, elementName, ue);
595         return new Creator(project, parent, nc);
596     }
597
598     /**
599      * Indicates whether the introspected class is a dynamic one,
600      * supporting arbitrary nested elements and/or attributes.
601      *
602      * @return <code>true<code> if the introspected class is dynamic;
603      * <code>false<code> otherwise.
604      * @since Ant 1.6.3
605      *
606      * @see DynamicElement
607      * @see DynamicElementNS
608      */

609     public boolean isDynamic() {
610         return DynamicElement.class.isAssignableFrom(bean)
611             || DynamicElementNS.class.isAssignableFrom(bean);
612     }
613
614     /**
615      * Indicates whether the introspected class is a task container,
616      * supporting arbitrary nested tasks/types.
617      *
618      * @return <code>true<code> if the introspected class is a container;
619      * <code>false<code> otherwise.
620      * @since Ant 1.6.3
621      *
622      * @see TaskContainer
623      */

624     public boolean isContainer() {
625         return TaskContainer.class.isAssignableFrom(bean);
626     }
627
628     /**
629      * Indicates if this element supports a nested element of the
630      * given name.
631      *
632      * @param elementName the name of the nested element being checked
633      *
634      * @return true if the given nested element is supported
635      */

636     public boolean supportsNestedElement(String JavaDoc elementName) {
637         return nestedCreators.containsKey(elementName.toLowerCase(Locale.US))
638             || isDynamic()
639             || addTypeMethods.size() != 0;
640     }
641
642     /**
643      * Indicate if this element supports a nested element of the
644      * given name.
645      *
646      * @param parentUri the uri of the parent
647      * @param elementName the name of the nested element being checked
648      *
649      * @return true if the given nested element is supported
650      */

651     public boolean supportsNestedElement(String JavaDoc parentUri, String JavaDoc elementName) {
652         if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
653             parentUri = "";
654         }
655         String JavaDoc uri = ProjectHelper.extractUriFromComponentName(elementName);
656         if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
657             uri = "";
658         }
659         String JavaDoc name = ProjectHelper.extractNameFromComponentName(elementName);
660
661         return (
662             nestedCreators.containsKey(name.toLowerCase(Locale.US))
663             && (uri.equals(parentUri) || "".equals(uri)))
664             || isDynamic() || addTypeMethods.size() != 0;
665     }
666
667     /**
668      * Stores a named nested element using a storage method determined
669      * by the initial introspection. If no appropriate storage method
670      * is available, this method returns immediately.
671      *
672      * @param project Ignored in this implementation.
673      * May be <code>null</code>.
674      *
675      * @param parent Parent instance to store the child in.
676      * Must not be <code>null</code>.
677      *
678      * @param child Child instance to store in the parent.
679      * Should not be <code>null</code>.
680      *
681      * @param elementName Name of the child element to store.
682      * May be <code>null</code>, in which case
683      * this method returns immediately.
684      *
685      * @exception BuildException if the storage method fails.
686      */

687     public void storeElement(Project project, Object JavaDoc parent, Object JavaDoc child,
688         String JavaDoc elementName) throws BuildException {
689         if (elementName == null) {
690             return;
691         }
692         NestedCreator ns = (NestedCreator) nestedCreators.get(
693             elementName.toLowerCase(Locale.US));
694         if (ns == null) {
695             return;
696         }
697         try {
698             ns.store(parent, child);
699         } catch (IllegalAccessException JavaDoc ie) {
700             // impossible as getMethods should only return public methods
701
throw new BuildException(ie);
702         } catch (InstantiationException JavaDoc ine) {
703             // impossible as getMethods should only return public methods
704
throw new BuildException(ine);
705         } catch (InvocationTargetException JavaDoc ite) {
706             Throwable JavaDoc t = ite.getTargetException();
707             if (t instanceof BuildException) {
708                 throw (BuildException) t;
709             }
710             throw new BuildException(t);
711         }
712     }
713
714     /**
715      * Returns the type of a named nested element.
716      *
717      * @param elementName The name of the element to find the type of.
718      * Must not be <code>null</code>.
719      *
720      * @return the type of the nested element with the specified name.
721      * This will never be <code>null</code>.
722      *
723      * @exception BuildException if the introspected class does not
724      * support the named nested element.
725      */

726     public Class JavaDoc getElementType(String JavaDoc elementName)
727         throws BuildException {
728         Class JavaDoc nt = (Class JavaDoc) nestedTypes.get(elementName);
729         if (nt == null) {
730             throw new UnsupportedElementException("Class "
731                 + bean.getName() + " doesn't support the nested \""
732                 + elementName + "\" element.", elementName);
733         }
734         return nt;
735     }
736
737     /**
738      * Returns the type of a named attribute.
739      *
740      * @param attributeName The name of the attribute to find the type of.
741      * Must not be <code>null</code>.
742      *
743      * @return the type of the attribute with the specified name.
744      * This will never be <code>null</code>.
745      *
746      * @exception BuildException if the introspected class does not
747      * support the named attribute.
748      */

749     public Class JavaDoc getAttributeType(String JavaDoc attributeName)
750         throws BuildException {
751         Class JavaDoc at = (Class JavaDoc) attributeTypes.get(attributeName);
752         if (at == null) {
753             throw new UnsupportedAttributeException("Class "
754                 + bean.getName() + " doesn't support the \""
755                 + attributeName + "\" attribute.", attributeName);
756         }
757         return at;
758     }
759
760     /**
761      * Returns the addText method when the introspected
762      * class supports nested text.
763      *
764      * @return the method on this introspected class that adds nested text.
765      * Cannot be <code>null</code>.
766      * @throws BuildException if the introspected class does not
767      * support the nested text.
768      * @since Ant 1.6.3
769      */

770     public Method JavaDoc getAddTextMethod()
771                   throws BuildException {
772         if (!supportsCharacters()) {
773             throw new BuildException("Class " + bean.getName()
774                 + " doesn't support nested text data.");
775         }
776         return addText;
777     }
778
779     /**
780      * Returns the adder or creator method of a named nested element.
781      *
782      * @param elementName The name of the attribute to find the setter
783      * method of. Must not be <code>null</code>.
784      * @return the method on this introspected class that adds or creates this
785      * nested element. Can be <code>null</code> when the introspected
786      * class is a dynamic configurator!
787      * @throws BuildException if the introspected class does not
788      * support the named nested element.
789      * @since Ant 1.6.3
790      */

791     public Method JavaDoc getElementMethod(String JavaDoc elementName)
792                   throws BuildException {
793         Object JavaDoc creator = nestedCreators.get(elementName);
794         if (creator == null) {
795             throw new UnsupportedElementException("Class "
796                 + bean.getName() + " doesn't support the nested \""
797                 + elementName + "\" element.", elementName);
798         }
799         return ((NestedCreator) creator).method;
800     }
801
802     /**
803      * Returns the setter method of a named attribute.
804      *
805      * @param attributeName The name of the attribute to find the setter
806      * method of. Must not be <code>null</code>.
807      * @return the method on this introspected class that sets this attribute.
808      * This will never be <code>null</code>.
809      * @throws BuildException if the introspected class does not
810      * support the named attribute.
811      * @since Ant 1.6.3
812      */

813     public Method JavaDoc getAttributeMethod(String JavaDoc attributeName)
814                   throws BuildException {
815         Object JavaDoc setter = attributeSetters.get(attributeName);
816         if (setter == null) {
817             throw new UnsupportedAttributeException("Class "
818                 + bean.getName() + " doesn't support the \""
819                 + attributeName + "\" attribute.", attributeName);
820         }
821         return ((AttributeSetter) setter).method;
822     }
823
824     /**
825      * Returns whether or not the introspected class supports PCDATA.
826      *
827      * @return whether or not the introspected class supports PCDATA.
828      */

829     public boolean supportsCharacters() {
830         return addText != null;
831     }
832
833     /**
834      * Returns an enumeration of the names of the attributes supported
835      * by the introspected class.
836      *
837      * @return an enumeration of the names of the attributes supported
838      * by the introspected class.
839      * @see #getAttributeMap
840      */

841     public Enumeration JavaDoc getAttributes() {
842         return attributeSetters.keys();
843     }
844
845     /**
846      * Returns a read-only map of attributes supported
847      * by the introspected class.
848      *
849      * @return an attribute name to attribute <code>Class</code>
850      * unmodifiable map. Can be empty, but never <code>null</code>.
851      * @since Ant 1.6.3
852      */

853     public Map JavaDoc getAttributeMap() {
854         return (attributeTypes.size() < 1)
855             ? EMPTY_MAP : Collections.unmodifiableMap(attributeTypes);
856     }
857
858     /**
859      * Returns an enumeration of the names of the nested elements supported
860      * by the introspected class.
861      *
862      * @return an enumeration of the names of the nested elements supported
863      * by the introspected class.
864      * @see #getNestedElementMap
865      */

866     public Enumeration JavaDoc getNestedElements() {
867         return nestedTypes.keys();
868     }
869
870     /**
871      * Returns a read-only map of nested elements supported
872      * by the introspected class.
873      *
874      * @return a nested-element name to nested-element <code>Class</code>
875      * unmodifiable map. Can be empty, but never <code>null</code>.
876      * @since Ant 1.6.3
877      */

878     public Map JavaDoc getNestedElementMap() {
879         return (nestedTypes.size() < 1)
880             ? EMPTY_MAP : Collections.unmodifiableMap(nestedTypes);
881     }
882
883     /**
884      * Returns a read-only list of extension points supported
885      * by the introspected class.
886      * <p>
887      * A task/type or nested element with void methods named <code>add()<code>
888      * or <code>addConfigured()</code>, taking a single class or interface
889      * argument, supports extensions point. This method returns the list of
890      * all these <em>void add[Configured](type)</em> methods.
891      *
892      * @return a list of void, single argument add() or addConfigured()
893      * <code>Method<code>s of all supported extension points.
894      * These methods are sorted such that if the argument type of a
895      * method derives from another type also an argument of a method
896      * of this list, the method with the most derived argument will
897      * always appear first. Can be empty, but never <code>null</code>.
898      * @since Ant 1.6.3
899      */

900     public List JavaDoc getExtensionPoints() {
901         return (addTypeMethods.size() < 1) ? Collections.EMPTY_LIST
902             : Collections.unmodifiableList(addTypeMethods);
903     }
904
905     /**
906      * Creates an implementation of AttributeSetter for the given
907      * attribute type. Conversions (where necessary) are automatically
908      * made for the following types:
909      * <ul>
910      * <li>String (left as it is)
911      * <li>Character/char (first character is used)
912      * <li>Boolean/boolean
913      * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used)
914      * <li>Class (Class.forName is used)
915      * <li>File (resolved relative to the appropriate project)
916      * <li>Path (resolve relative to the appropriate project)
917      * <li>EnumeratedAttribute (uses its own
918      * {@link EnumeratedAttribute#setValue(String) setValue} method)
919      * <li>Other primitive types (wrapper classes are used with constructors
920      * taking String)
921      * </ul>
922      *
923      * If none of the above covers the given parameters, a constructor for the
924      * appropriate class taking a String parameter is used if it is available.
925      *
926      * @param m The method to invoke on the bean when the setter is invoked.
927      * Must not be <code>null</code>.
928      * @param arg The type of the single argument of the bean's method.
929      * Must not be <code>null</code>.
930      * @param attrName the name of the attribute for which the setter is being
931      * created.
932      *
933      * @return an appropriate AttributeSetter instance, or <code>null</code>
934      * if no appropriate conversion is available.
935      */

936     private AttributeSetter createAttributeSetter(final Method JavaDoc m,
937                                                   Class JavaDoc arg,
938                                                   final String JavaDoc attrName) {
939         // use wrappers for primitive classes, e.g. int and
940
// Integer are treated identically
941
final Class JavaDoc reflectedArg = PRIMITIVE_TYPE_MAP.containsKey(arg)
942             ? (Class JavaDoc) PRIMITIVE_TYPE_MAP.get(arg) : arg;
943
944         // simplest case - setAttribute expects String
945
if (java.lang.String JavaDoc.class.equals(reflectedArg)) {
946             return new AttributeSetter(m) {
947                 public void set(Project p, Object JavaDoc parent, String JavaDoc value)
948                         throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc {
949                     m.invoke(parent, (Object JavaDoc[]) (new String JavaDoc[] {value}));
950                 }
951             };
952         // char and Character get special treatment - take the first character
953
} else if (java.lang.Character JavaDoc.class.equals(reflectedArg)) {
954             return new AttributeSetter(m) {
955                 public void set(Project p, Object JavaDoc parent, String JavaDoc value)
956                         throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc {
957                     if (value.length() == 0) {
958                         throw new BuildException("The value \"\" is not a "
959                             + "legal value for attribute \"" + attrName + "\"");
960                     }
961                     m.invoke(parent, (Object JavaDoc[])
962                         (new Character JavaDoc[] {new Character JavaDoc(value.charAt(0))}));
963                 }
964             };
965         // boolean and Boolean get special treatment because we
966
// have a nice method in Project
967
} else if (java.lang.Boolean JavaDoc.class.equals(reflectedArg)) {
968             return new AttributeSetter(m) {
969                 public void set(Project p, Object JavaDoc parent, String JavaDoc value)
970                         throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc {
971                     m.invoke(parent, (Object JavaDoc[]) (
972                          new Boolean JavaDoc[] {Project.toBoolean(value)
973                                         ? Boolean.TRUE : Boolean.FALSE}));
974                 }
975             };
976         // Class doesn't have a String constructor but a decent factory method
977
} else if (java.lang.Class JavaDoc.class.equals(reflectedArg)) {
978             return new AttributeSetter(m) {
979                 public void set(Project p, Object JavaDoc parent, String JavaDoc value)
980                         throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc, BuildException {
981                     try {
982                         m.invoke(parent, new Object JavaDoc[] {Class.forName(value)});
983                     } catch (ClassNotFoundException JavaDoc ce) {
984                         throw new BuildException(ce);
985                     }
986                 }
987             };
988         // resolve relative paths through Project
989
} else if (java.io.File JavaDoc.class.equals(reflectedArg)) {
990             return new AttributeSetter(m) {
991                 public void set(Project p, Object JavaDoc parent, String JavaDoc value)
992                         throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc {
993                     m.invoke(parent, new Object JavaDoc[] {p.resolveFile(value)});
994                 }
995             };
996         // EnumeratedAttributes have their own helper class
997
} else if (EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
998             return new AttributeSetter(m) {
999                 public void set(Project p, Object JavaDoc parent, String JavaDoc value)
1000                        throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc, BuildException {
1001                    try {
1002                        EnumeratedAttribute ea =
1003                            (EnumeratedAttribute) reflectedArg.newInstance();
1004                        ea.setValue(value);
1005                        m.invoke(parent, new Object JavaDoc[] {ea});
1006                    } catch (InstantiationException JavaDoc ie) {
1007                        throw new BuildException(ie);
1008                    }
1009                }
1010            };
1011        } else if (reflectedArg.getSuperclass() != null
1012                   && reflectedArg.getSuperclass().getName().equals("java.lang.Enum")) {
1013            return new AttributeSetter(m) {
1014                public void set(Project p, Object JavaDoc parent, String JavaDoc value)
1015                        throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc, BuildException {
1016                    try {
1017                        m.invoke(parent, new Object JavaDoc[] {
1018                            reflectedArg.getMethod("valueOf", new Class JavaDoc[] {String JavaDoc.class}).
1019                                    invoke(null, new Object JavaDoc[] {value})});
1020                    } catch (InvocationTargetException JavaDoc x) {
1021                        if (x.getTargetException() instanceof IllegalArgumentException JavaDoc) {
1022                            throw new BuildException(
1023                                "'" + value + "' is not a permitted value for "
1024                                + reflectedArg.getName());
1025                        } else {
1026                            throw new BuildException(x.getTargetException());
1027                        }
1028                    } catch (Exception JavaDoc x) {
1029                        throw new BuildException(x);
1030                    }
1031                }
1032            };
1033        // worst case. look for a public String constructor and use it
1034
// also supports new Whatever(Project, String) as for Path or Reference
1035
// This is used (deliberately) for all primitives/wrappers other than
1036
// char and boolean
1037
} else {
1038            boolean includeProject;
1039            Constructor JavaDoc c;
1040            try {
1041                // First try with Project.
1042
c = reflectedArg.getConstructor(new Class JavaDoc[] {Project.class, String JavaDoc.class});
1043                includeProject = true;
1044            } catch (NoSuchMethodException JavaDoc nme) {
1045                // OK, try without.
1046
try {
1047                    c = reflectedArg.getConstructor(new Class JavaDoc[] {String JavaDoc.class});
1048                    includeProject = false;
1049                } catch (NoSuchMethodException JavaDoc nme2) {
1050                    // Well, no matching constructor.
1051
return null;
1052                }
1053            }
1054            final boolean finalIncludeProject = includeProject;
1055            final Constructor JavaDoc finalConstructor = c;
1056
1057            return new AttributeSetter(m) {
1058                public void set(Project p, Object JavaDoc parent, String JavaDoc value)
1059                        throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc, BuildException {
1060                    try {
1061                        Object JavaDoc[] args = (finalIncludeProject)
1062                            ? new Object JavaDoc[] {p, value} : new Object JavaDoc[] {value};
1063
1064                        Object JavaDoc attribute = finalConstructor.newInstance(args);
1065                        if (p != null) {
1066                            p.setProjectReference(attribute);
1067                        }
1068                        m.invoke(parent, new Object JavaDoc[] {attribute});
1069                    } catch (InstantiationException JavaDoc ie) {
1070                        throw new BuildException(ie);
1071                    }
1072                }
1073            };
1074        }
1075    }
1076
1077    /**
1078     * Returns a description of the type of the given element in
1079     * relation to a given project. This is used for logging purposes
1080     * when the element is asked to cope with some data it has no
1081     * way of handling.
1082     *
1083     * @param project The project the element is defined in.
1084     * Must not be <code>null</code>.
1085     *
1086     * @param element The element to describe.
1087     * Must not be <code>null</code>.
1088     *
1089     * @return a description of the element type
1090     */

1091    protected String JavaDoc getElementName(Project project, Object JavaDoc element) {
1092        return project.getElementName(element);
1093    }
1094
1095    /**
1096     * Extracts the name of a property from a method name by subtracting
1097     * a given prefix and converting into lower case. It is up to calling
1098     * code to make sure the method name does actually begin with the
1099     * specified prefix - no checking is done in this method.
1100     *
1101     * @param methodName The name of the method in question.
1102     * Must not be <code>null</code>.
1103     * @param prefix The prefix to remove.
1104     * Must not be <code>null</code>.
1105     *
1106     * @return the lower-cased method name with the prefix removed.
1107     */

1108    private String JavaDoc getPropertyName(String JavaDoc methodName, String JavaDoc prefix) {
1109        return methodName.substring(prefix.length()).toLowerCase(Locale.US);
1110    }
1111
1112    /**
1113     * creator - allows use of create/store external
1114     * to IntrospectionHelper.
1115     * The class is final as it has a private constructor.
1116     */

1117    public static final class Creator {
1118        private NestedCreator nestedCreator;
1119        private Object JavaDoc parent;
1120        private Project project;
1121        private Object JavaDoc nestedObject;
1122        private String JavaDoc polyType;
1123
1124        /**
1125         * Creates a new Creator instance.
1126         * This object is given to the UnknownElement to create
1127         * objects for sub-elements. UnknownElement calls
1128         * create to create an object, the object then gets
1129         * configured and then UnknownElement calls store.
1130         * SetPolyType may be used to override the type used
1131         * to create the object with. SetPolyType gets called
1132         * before create.
1133         *
1134         * @param project the current project
1135         * @param parent the parent object to create the object in
1136         * @param nestedCreator the nested creator object to use
1137         */

1138        private Creator(
1139            Project project, Object JavaDoc parent, NestedCreator nestedCreator) {
1140            this.project = project;
1141            this.parent = parent;
1142            this.nestedCreator = nestedCreator;
1143        }
1144
1145        /**
1146         * Used to override the class used to create the object.
1147         *
1148         * @param polyType a ant component type name
1149         */

1150        public void setPolyType(String JavaDoc polyType) {
1151            this.polyType = polyType;
1152        }
1153
1154        /**
1155         * Create an object using this creator, which is determined
1156         * by introspection.
1157         *
1158         * @return the created object
1159         */

1160        public Object JavaDoc create() {
1161            if (polyType != null) {
1162                if (!nestedCreator.isPolyMorphic()) {
1163                    throw new BuildException(
1164                        "Not allowed to use the polymorphic form"
1165                        + " for this element");
1166                }
1167                ComponentHelper helper =
1168                    ComponentHelper.getComponentHelper(project);
1169                nestedObject = helper.createComponent(polyType);
1170                if (nestedObject == null) {
1171                    throw new BuildException(
1172                        "Unable to create object of type " + polyType);
1173                }
1174            }
1175            try {
1176                nestedObject = nestedCreator.create(
1177                    project, parent, nestedObject);
1178                if (project != null) {
1179                    project.setProjectReference(nestedObject);
1180                }
1181                return nestedObject;
1182            } catch (IllegalAccessException JavaDoc ex) {
1183                throw new BuildException(ex);
1184            } catch (InstantiationException JavaDoc ex) {
1185                throw new BuildException(ex);
1186            } catch (IllegalArgumentException JavaDoc ex) {
1187                if (polyType != null) {
1188                    throw new BuildException(
1189                        "Invalid type used " + polyType);
1190                }
1191                throw ex;
1192            } catch (InvocationTargetException JavaDoc ex) {
1193                Throwable JavaDoc t = ex.getTargetException();
1194                if (t instanceof BuildException) {
1195                    throw (BuildException) t;
1196                }
1197                throw new BuildException(t);
1198            }
1199        }
1200
1201        /**
1202         * @return the real object (used currently only
1203         * for preset def).
1204         */

1205        public Object JavaDoc getRealObject() {
1206            return nestedCreator.getRealObject();
1207        }
1208
1209        /**
1210         * Stores the nested element object using a storage method
1211         * determined by introspection.
1212         *
1213         */

1214        public void store() {
1215            try {
1216                nestedCreator.store(parent, nestedObject);
1217            } catch (IllegalAccessException JavaDoc ex) {
1218                throw new BuildException(ex);
1219            } catch (InstantiationException JavaDoc ex) {
1220                throw new BuildException(ex);
1221            } catch (IllegalArgumentException JavaDoc ex) {
1222                if (polyType != null) {
1223                    throw new BuildException(
1224                        "Invalid type used " + polyType);
1225                }
1226                throw ex;
1227            } catch (InvocationTargetException JavaDoc ex) {
1228                Throwable JavaDoc t = ex.getTargetException();
1229                if (t instanceof BuildException) {
1230                    throw (BuildException) t;
1231                }
1232                throw new BuildException(t);
1233            }
1234        }
1235    }
1236
1237    /**
1238     * Internal interface used to create nested elements. Not documented
1239     * in detail for reasons of source code readability.
1240     */

1241    private abstract static class NestedCreator {
1242        private Method JavaDoc method; // the method called to add/create the nested element
1243

1244        NestedCreator(Method JavaDoc m) {
1245            this.method = m;
1246        }
1247        Method JavaDoc getMethod() {
1248            return method;
1249        }
1250        boolean isPolyMorphic() {
1251            return false;
1252        }
1253        Object JavaDoc getRealObject() {
1254            return null;
1255        }
1256        abstract Object JavaDoc create(Project project, Object JavaDoc parent, Object JavaDoc child)
1257                        throws InvocationTargetException JavaDoc,
1258                               IllegalAccessException JavaDoc,
1259                               InstantiationException JavaDoc;
1260        void store(Object JavaDoc parent, Object JavaDoc child)
1261             throws InvocationTargetException JavaDoc,
1262                    IllegalAccessException JavaDoc,
1263                    InstantiationException JavaDoc {
1264            // DO NOTHING
1265
}
1266    }
1267
1268    private class CreateNestedCreator extends NestedCreator {
1269        CreateNestedCreator(Method JavaDoc m) {
1270            super(m);
1271        }
1272
1273        Object JavaDoc create(Project project, Object JavaDoc parent, Object JavaDoc ignore)
1274            throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc {
1275            return getMethod().invoke(parent, new Object JavaDoc[] {});
1276        }
1277    }
1278
1279    /** Version to use for addXXX and addConfiguredXXX */
1280    private class AddNestedCreator extends NestedCreator {
1281
1282        static final int ADD = 1;
1283        static final int ADD_CONFIGURED = 2;
1284
1285        private Constructor JavaDoc constructor;
1286        private int behavior; // ADD or ADD_CONFIGURED
1287

1288        AddNestedCreator(Method JavaDoc m, Constructor JavaDoc c, int behavior) {
1289            super(m);
1290            this.constructor = c;
1291            this.behavior = behavior;
1292        }
1293
1294        boolean isPolyMorphic() {
1295            return true;
1296        }
1297
1298        Object JavaDoc create(Project project, Object JavaDoc parent, Object JavaDoc child)
1299                throws InvocationTargetException JavaDoc,
1300                IllegalAccessException JavaDoc, InstantiationException JavaDoc {
1301            if (child == null) {
1302                child = constructor.newInstance(
1303                    (constructor.getParameterTypes().length == 0)
1304                    ? new Object JavaDoc[] {} : new Object JavaDoc[] {project});
1305            }
1306            if (child instanceof PreSetDef.PreSetDefinition) {
1307                child = ((PreSetDef.PreSetDefinition) child)
1308                    .createObject(project);
1309            }
1310            if (behavior == ADD) {
1311                istore(parent, child);
1312            }
1313            return child;
1314        }
1315
1316        void store(Object JavaDoc parent, Object JavaDoc child)
1317                throws InvocationTargetException JavaDoc,
1318                IllegalAccessException JavaDoc, InstantiationException JavaDoc {
1319            if (behavior == ADD_CONFIGURED) {
1320                istore(parent, child);
1321            }
1322        }
1323
1324        private void istore(Object JavaDoc parent, Object JavaDoc child)
1325                throws InvocationTargetException JavaDoc,
1326                IllegalAccessException JavaDoc, InstantiationException JavaDoc {
1327            getMethod().invoke(parent, new Object JavaDoc[] {child});
1328        }
1329    }
1330
1331    /**
1332     * Internal interface used to setting element attributes. Not documented
1333     * in detail for reasons of source code readability.
1334     */

1335    private abstract static class AttributeSetter {
1336        private Method JavaDoc method; // the method called to set the attribute
1337
AttributeSetter(Method JavaDoc m) {
1338            this.method = m;
1339        }
1340        abstract void set(Project p, Object JavaDoc parent, String JavaDoc value)
1341                      throws InvocationTargetException JavaDoc,
1342                             IllegalAccessException JavaDoc,
1343                             BuildException;
1344    }
1345
1346    /**
1347     * Clears the static cache of on build finished.
1348     */

1349    public static void clearCache() {
1350        HELPERS.clear();
1351    }
1352
1353    /**
1354     *
1355     */

1356    private NestedCreator createAddTypeCreator(
1357        Project project, Object JavaDoc parent, String JavaDoc elementName)
1358        throws BuildException {
1359        if (addTypeMethods.size() == 0) {
1360            return null;
1361        }
1362        ComponentHelper helper = ComponentHelper.getComponentHelper(project);
1363
1364        Object JavaDoc addedObject = null;
1365        Method JavaDoc addMethod = null;
1366        Class JavaDoc clazz = helper.getComponentClass(elementName);
1367        if (clazz == null) {
1368            return null;
1369        }
1370        addMethod = findMatchingMethod(clazz, addTypeMethods);
1371        if (addMethod == null) {
1372            return null;
1373        }
1374        addedObject = helper.createComponent(elementName);
1375        if (addedObject == null) {
1376            return null;
1377        }
1378        Object JavaDoc rObject = addedObject;
1379        if (addedObject instanceof PreSetDef.PreSetDefinition) {
1380            rObject = ((PreSetDef.PreSetDefinition) addedObject).createObject(
1381                project);
1382        }
1383        final Object JavaDoc nestedObject = addedObject;
1384        final Object JavaDoc realObject = rObject;
1385
1386        return new NestedCreator(addMethod) {
1387            Object JavaDoc create(Project project, Object JavaDoc parent, Object JavaDoc ignore)
1388                    throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc {
1389                if (!getMethod().getName().endsWith("Configured")) {
1390                    getMethod().invoke(parent, new Object JavaDoc[] {realObject});
1391                }
1392                return nestedObject;
1393            }
1394
1395            Object JavaDoc getRealObject() {
1396                return realObject;
1397            }
1398
1399            void store(Object JavaDoc parent, Object JavaDoc child)
1400                    throws InvocationTargetException JavaDoc, IllegalAccessException JavaDoc,
1401                    InstantiationException JavaDoc {
1402                if (getMethod().getName().endsWith("Configured")) {
1403                    getMethod().invoke(parent, new Object JavaDoc[] {realObject});
1404                }
1405            }
1406        };
1407    }
1408
1409    /**
1410     * Inserts an add or addConfigured method into
1411     * the addTypeMethods array. The array is
1412     * ordered so that the more derived classes
1413     * are first.
1414     * If both add and addConfigured are present, the addConfigured
1415     * will take priority.
1416     * @param method the <code>Method</code> to insert.
1417     */

1418    private void insertAddTypeMethod(Method JavaDoc method) {
1419        Class JavaDoc argClass = method.getParameterTypes()[0];
1420        for (int c = 0; c < addTypeMethods.size(); ++c) {
1421            Method JavaDoc current = (Method JavaDoc) addTypeMethods.get(c);
1422            if (current.getParameterTypes()[0].equals(argClass)) {
1423                if (method.getName().equals("addConfigured")) {
1424                    // add configured replaces the add method
1425
addTypeMethods.set(c, method);
1426                }
1427                return; // Already present
1428
}
1429            if (current.getParameterTypes()[0].isAssignableFrom(
1430                            argClass)) {
1431                addTypeMethods.add(c, method);
1432                return; // higher derived
1433
}
1434        }
1435        addTypeMethods.add(method);
1436    }
1437
1438    /**
1439     * Search the list of methods to find the first method
1440     * that has a parameter that accepts the nested element object.
1441     * @param paramClass the <code>Class</code> type to search for.
1442     * @param methods the <code>List</code> of methods to search.
1443     * @return a matching <code>Method</code>; null if none found.
1444     */

1445    private Method JavaDoc findMatchingMethod(Class JavaDoc paramClass, List JavaDoc methods) {
1446        Class JavaDoc matchedClass = null;
1447        Method JavaDoc matchedMethod = null;
1448
1449        for (int i = 0; i < methods.size(); ++i) {
1450            Method JavaDoc method = (Method JavaDoc) methods.get(i);
1451            Class JavaDoc methodClass = method.getParameterTypes()[0];
1452            if (methodClass.isAssignableFrom(paramClass)) {
1453                if (matchedClass == null) {
1454                    matchedClass = methodClass;
1455                    matchedMethod = method;
1456                } else {
1457                    if (!methodClass.isAssignableFrom(matchedClass)) {
1458                        throw new BuildException("ambiguous: types "
1459                            + matchedClass.getName() + " and "
1460                            + methodClass.getName() + " match "
1461                            + paramClass.getName());
1462                    }
1463                }
1464            }
1465        }
1466        return matchedMethod;
1467    }
1468
1469    private String JavaDoc condenseText(final String JavaDoc text) {
1470        if (text.length() <= MAX_REPORT_NESTED_TEXT) {
1471            return text;
1472        }
1473        int ends = (MAX_REPORT_NESTED_TEXT - ELLIPSIS.length()) / 2;
1474        return new StringBuffer JavaDoc(text).replace(ends, text.length() - ends,
1475            ELLIPSIS).toString();
1476    }
1477}
1478
Popular Tags