source: release-kits/lirk3/resources/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/IntrospectionHelper.java@ 14982

Last change on this file since 14982 was 14982, checked in by oranfry, 16 years ago

initial import of LiRK3

File size: 59.0 KB
Line 
1/*
2 * Copyright 2000-2005 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.apache.tools.ant;
19
20import java.lang.reflect.Constructor;
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Method;
23import java.util.ArrayList;
24import java.util.Collections;
25import java.util.Enumeration;
26import java.util.Hashtable;
27import java.util.HashMap;
28import java.util.List;
29import java.util.Locale;
30import java.util.Map;
31import org.apache.tools.ant.types.EnumeratedAttribute;
32import org.apache.tools.ant.taskdefs.PreSetDef;
33
34/**
35 * Helper class that collects the methods a task or nested element
36 * holds to set attributes, create nested elements or hold PCDATA
37 * elements.
38 * The class is final as it has a private constructor.
39 */
40public final class IntrospectionHelper implements BuildListener {
41
42 /**
43 * EMPTY_MAP was added in java 1.3 (EMTPY_SET and EMPTY_LIST
44 * is in java 1.2!)
45 */
46 private static final Map EMPTY_MAP
47 = Collections.unmodifiableMap(new HashMap(0));
48
49 /**
50 * Map from attribute names to attribute types
51 * (String to Class).
52 */
53 private Hashtable attributeTypes = new Hashtable();
54
55 /**
56 * Map from attribute names to attribute setter methods
57 * (String to AttributeSetter).
58 */
59 private Hashtable attributeSetters = new Hashtable();
60
61 /**
62 * Map from attribute names to nested types
63 * (String to Class).
64 */
65 private Hashtable nestedTypes = new Hashtable();
66
67 /**
68 * Map from attribute names to methods to create nested types
69 * (String to NestedCreator).
70 */
71 private Hashtable nestedCreators = new Hashtable();
72
73 /**
74 * Vector of methods matching add[Configured](Class) pattern.
75 */
76 private List addTypeMethods = new ArrayList();
77
78 /**
79 * The method to invoke to add PCDATA.
80 */
81 private Method addText = null;
82
83 /**
84 * The class introspected by this instance.
85 */
86 private Class bean;
87
88 /**
89 * Helper instances we've already created (Class to IntrospectionHelper).
90 */
91 private static Hashtable helpers = new Hashtable();
92
93 /**
94 * Map from primitive types to wrapper classes for use in
95 * createAttributeSetter (Class to Class). Note that char
96 * and boolean are in here even though they get special treatment
97 * - this way we only need to test for the wrapper class.
98 */
99 private static final Hashtable PRIMITIVE_TYPE_MAP = new Hashtable(8);
100
101 // Set up PRIMITIVE_TYPE_MAP
102 static {
103 Class[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE,
104 Short.TYPE, Integer.TYPE, Long.TYPE,
105 Float.TYPE, Double.TYPE};
106 Class[] wrappers = {Boolean.class, Byte.class, Character.class,
107 Short.class, Integer.class, Long.class,
108 Float.class, Double.class};
109 for (int i = 0; i < primitives.length; i++) {
110 PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]);
111 }
112 }
113
114 // XXX: (Jon Skeet) The documentation below doesn't draw a clear
115 // distinction between addConfigured and add. It's obvious what the
116 // code *here* does (addConfigured sets both a creator method which
117 // calls a no-arg constructor and a storer method which calls the
118 // method we're looking at, while add just sets a creator method
119 // which calls the method we're looking at) but it's not at all
120 // obvious what the difference in actual *effect* will be later
121 // on. I can't see any mention of addConfiguredXXX in "Developing
122 // with Ant" (at least in the version on the web site). Someone
123 // who understands should update this documentation
124 // (and preferably the manual too) at some stage.
125 /**
126 * Sole constructor, which is private to ensure that all
127 * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
128 * Introspects the given class for bean-like methods.
129 * Each method is examined in turn, and the following rules are applied:
130 * <p>
131 * <ul>
132 * <li>If the method is <code>Task.setLocation(Location)</code>,
133 * <code>Task.setTaskType(String)</code>
134 * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These
135 * methods are handled differently elsewhere.
136 * <li><code>void addText(String)</code> is recognised as the method for
137 * adding PCDATA to a bean.
138 * <li><code>void setFoo(Bar)</code> is recognised as a method for
139 * setting the value of attribute <code>foo</code>, so long as
140 * <code>Bar</code> is non-void and is not an array type. Non-String
141 * parameter types always overload String parameter types, but that is
142 * the only guarantee made in terms of priority.
143 * <li><code>Foo createBar()</code> is recognised as a method for
144 * creating a nested element called <code>bar</code> of type
145 * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
146 * array type.
147 * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
148 * method for storing a pre-configured element called
149 * <code>foo</code> and of type <code>Bar</code>, so long as
150 * <code>Bar</code> is not an array, primitive or String type.
151 * <code>Bar</code> must have an accessible constructor taking no
152 * arguments.
153 * <li><code>void addFoo(Bar)</code> is recognised as a
154 * method for storing an element called <code>foobar</code>
155 * and of type <code>Baz</code>, so long as
156 * <code>Baz</code> is not an array, primitive or String type.
157 * <code>Baz</code> must have an accessible constructor taking no
158 * arguments.
159 * </ul>
160 * Note that only one method is retained to create/set/addConfigured/add
161 * any element or attribute.
162 *
163 * @param bean The bean type to introspect.
164 * Must not be <code>null</code>.
165 *
166 * @see #getHelper(Class)
167 */
168 private IntrospectionHelper(final Class bean) {
169 this.bean = bean;
170
171 Method[] methods = bean.getMethods();
172 for (int i = 0; i < methods.length; i++) {
173 final Method m = methods[i];
174 final String name = m.getName();
175 Class returnType = m.getReturnType();
176 Class[] args = m.getParameterTypes();
177
178 // check of add[Configured](Class) pattern
179 if (args.length == 1 && java.lang.Void.TYPE.equals(returnType)
180 && ("add".equals(name) || "addConfigured".equals(name))) {
181 insertAddTypeMethod(m);
182 continue;
183 }
184 // not really user settable properties on tasks
185 if (org.apache.tools.ant.Task.class.isAssignableFrom(bean)
186 && args.length == 1 && isHiddenSetMethod(name, args[0])) {
187 continue;
188 }
189 // hide addTask for TaskContainers
190 if (isContainer() && args.length == 1 && "addTask".equals(name)
191 && org.apache.tools.ant.Task.class.equals(args[0])) {
192 continue;
193 }
194 if ("addText".equals(name) && java.lang.Void.TYPE.equals(returnType)
195 && args.length == 1 && java.lang.String.class.equals(args[0])) {
196
197 addText = methods[i];
198 } else if (name.startsWith("set")
199 && java.lang.Void.TYPE.equals(returnType)
200 && args.length == 1 && !args[0].isArray()) {
201
202 String propName = getPropertyName(name, "set");
203 if (attributeSetters.get(propName) != null) {
204 if (java.lang.String.class.equals(args[0])) {
205 /*
206 Ignore method m, as there is an overloaded
207 form of this method that takes in a
208 non-string argument, which gains higher
209 priority.
210 */
211 continue;
212 }
213 /*
214 If the argument is not a String, and if there
215 is an overloaded form of this method already defined,
216 we just override that with the new one.
217 This mechanism does not guarantee any specific order
218 in which the methods will be selected: so any code
219 that depends on the order in which "set" methods have
220 been defined, is not guaranteed to be selected in any
221 particular order.
222 */
223 }
224 AttributeSetter as = createAttributeSetter(m, args[0], propName);
225 if (as != null) {
226 attributeTypes.put(propName, args[0]);
227 attributeSetters.put(propName, as);
228 }
229 } else if (name.startsWith("create") && !returnType.isArray()
230 && !returnType.isPrimitive() && args.length == 0) {
231
232 String propName = getPropertyName(name, "create");
233 // Check if a create of this property is already present
234 // add takes preference over create for CB purposes
235 if (nestedCreators.get(propName) == null) {
236 nestedTypes.put(propName, returnType);
237 nestedCreators.put(propName, new CreateNestedCreator(m));
238 }
239 } else if (name.startsWith("addConfigured")
240 && java.lang.Void.TYPE.equals(returnType) && args.length == 1
241 && !java.lang.String.class.equals(args[0])
242 && !args[0].isArray() && !args[0].isPrimitive()) {
243 try {
244 Constructor constructor = null;
245 try {
246 constructor = args[0].getConstructor(new Class[] {});
247 } catch (NoSuchMethodException ex) {
248 constructor =
249 args[0].getConstructor(new Class[] {Project.class});
250 }
251 String propName = getPropertyName(name, "addConfigured");
252 nestedTypes.put(propName, args[0]);
253 nestedCreators.put(propName, new AddNestedCreator(m,
254 constructor, AddNestedCreator.ADD_CONFIGURED));
255 } catch (NoSuchMethodException nse) {
256 // ignore
257 }
258 } else if (name.startsWith("add")
259 && java.lang.Void.TYPE.equals(returnType) && args.length == 1
260 && !java.lang.String.class.equals(args[0])
261 && !args[0].isArray() && !args[0].isPrimitive()) {
262 try {
263 Constructor constructor = null;
264 try {
265 constructor = args[0].getConstructor(new Class[] {});
266 } catch (NoSuchMethodException ex) {
267 constructor =
268 args[0].getConstructor(new Class[] {Project.class});
269 }
270 String propName = getPropertyName(name, "add");
271 nestedTypes.put(propName, args[0]);
272 nestedCreators.put(propName, new AddNestedCreator(m,
273 constructor, AddNestedCreator.ADD));
274 } catch (NoSuchMethodException nse) {
275 // ignore
276 }
277 }
278 }
279 }
280
281 /**
282 * Certain set methods are part of the Ant core interface to tasks and
283 * therefore not to be considered for introspection
284 *
285 * @param name the name of the set method
286 * @param type the type of the set method's parameter
287 * @return true if the given set method is to be hidden.
288 */
289 private boolean isHiddenSetMethod(String name, Class type) {
290 if ("setLocation".equals(name)
291 && org.apache.tools.ant.Location.class.equals(type)) {
292 return true;
293 }
294
295 if ("setTaskType".equals(name)
296 && java.lang.String.class.equals(type)) {
297 return true;
298 }
299
300 return false;
301 }
302
303 /**
304 * Returns a helper for the given class, either from the cache
305 * or by creating a new instance.
306 *
307 * @param c The class for which a helper is required.
308 * Must not be <code>null</code>.
309 *
310 * @return a helper for the specified class
311 */
312 public static synchronized IntrospectionHelper getHelper(Class c) {
313 IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
314 if (ih == null) {
315 ih = new IntrospectionHelper(c);
316 helpers.put(c, ih);
317 }
318 return ih;
319 }
320
321 /**
322 * Returns a helper for the given class, either from the cache
323 * or by creating a new instance.
324 *
325 * The method will make sure the helper will be cleaned up at the end of
326 * the project, and only one instance will be created for each class.
327 *
328 * @param p the project instance.
329 * @param c The class for which a helper is required.
330 * Must not be <code>null</code>.
331 *
332 * @return a helper for the specified class
333 */
334 public static IntrospectionHelper getHelper(Project p, Class c) {
335 IntrospectionHelper ih = getHelper(c);
336 // Cleanup at end of project
337 p.addBuildListener(ih);
338 return ih;
339 }
340
341 /**
342 * Sets the named attribute in the given element, which is part of the
343 * given project.
344 *
345 * @param p The project containing the element. This is used when files
346 * need to be resolved. Must not be <code>null</code>.
347 * @param element The element to set the attribute in. Must not be
348 * <code>null</code>.
349 * @param attributeName The name of the attribute to set. Must not be
350 * <code>null</code>.
351 * @param value The value to set the attribute to. This may be interpreted
352 * or converted to the necessary type if the setter method
353 * doesn't just take a string. Must not be <code>null</code>.
354 *
355 * @exception BuildException if the introspected class doesn't support
356 * the given attribute, or if the setting
357 * method fails.
358 */
359 public void setAttribute(Project p, Object element, String attributeName,
360 String value) throws BuildException {
361 AttributeSetter as
362 = (AttributeSetter) attributeSetters.get(
363 attributeName.toLowerCase(Locale.US));
364 if (as == null) {
365 if (element instanceof DynamicAttributeNS) {
366 DynamicAttributeNS dc = (DynamicAttributeNS) element;
367 String uriPlusPrefix =
368 ProjectHelper.extractUriFromComponentName(attributeName);
369 String uri =
370 ProjectHelper.extractUriFromComponentName(uriPlusPrefix);
371 String localName =
372 ProjectHelper.extractNameFromComponentName(attributeName);
373 String qName = ("".equals(uri)
374 ? localName : (uri + ":" + localName));
375
376 dc.setDynamicAttribute(uri, localName, qName, value);
377 return;
378 } else if (element instanceof DynamicAttribute) {
379 DynamicAttribute dc = (DynamicAttribute) element;
380 dc.setDynamicAttribute(attributeName.toLowerCase(Locale.US), value);
381 return;
382 } else {
383 if (attributeName.indexOf(':') != -1) {
384 return; // Ignore attribute from unknown uri's
385 }
386 String msg = getElementName(p, element)
387 + " doesn't support the \"" + attributeName
388 + "\" attribute.";
389 throw new UnsupportedAttributeException(msg, attributeName);
390 }
391 }
392 try {
393 as.set(p, element, value);
394 } catch (IllegalAccessException ie) {
395 // impossible as getMethods should only return public methods
396 throw new BuildException(ie);
397 } catch (InvocationTargetException ite) {
398 Throwable t = ite.getTargetException();
399 if (t instanceof BuildException) {
400 throw (BuildException) t;
401 }
402 throw new BuildException(t);
403 }
404 }
405
406
407 /**
408 * Adds PCDATA to an element, using the element's
409 * <code>void addText(String)</code> method, if it has one. If no
410 * such method is present, a BuildException is thrown if the
411 * given text contains non-whitespace.
412 *
413 * @param project The project which the element is part of.
414 * Must not be <code>null</code>.
415 * @param element The element to add the text to.
416 * Must not be <code>null</code>.
417 * @param text The text to add.
418 * Must not be <code>null</code>.
419 *
420 * @exception BuildException if non-whitespace text is provided and no
421 * method is available to handle it, or if
422 * the handling method fails.
423 */
424 public void addText(Project project, Object element, String text)
425 throws BuildException {
426 if (addText == null) {
427 // Element doesn't handle text content
428 if (text.trim().length() == 0) {
429 // Only whitespace - ignore
430 return;
431 } else {
432 // Not whitespace - fail
433 String msg = project.getElementName(element)
434 + " doesn't support nested text data.";
435 throw new BuildException(msg);
436 }
437 }
438 try {
439 addText.invoke(element, new Object[] {text});
440 } catch (IllegalAccessException ie) {
441 // impossible as getMethods should only return public methods
442 throw new BuildException(ie);
443 } catch (InvocationTargetException ite) {
444 Throwable t = ite.getTargetException();
445 if (t instanceof BuildException) {
446 throw (BuildException) t;
447 }
448 throw new BuildException(t);
449 }
450 }
451
452 /**
453 * Utility method to throw a NotSupported exception
454 *
455 * @param project the Project instance.
456 * @param parent the object which doesn't support a requested element
457 * @param elementName the name of the Element which is trying to be created.
458 */
459 public void throwNotSupported(Project project, Object parent,
460 String elementName) {
461 String msg = project.getElementName(parent)
462 + " doesn't support the nested \"" + elementName + "\" element.";
463 throw new UnsupportedElementException(msg, elementName);
464 }
465
466 private NestedCreator getNestedCreator(
467 Project project, String parentUri, Object parent,
468 String elementName, UnknownElement child) throws BuildException {
469
470 String uri = ProjectHelper.extractUriFromComponentName(elementName);
471 String name = ProjectHelper.extractNameFromComponentName(elementName);
472
473 if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
474 uri = "";
475 }
476 if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
477 parentUri = "";
478 }
479 NestedCreator nc = null;
480 if (uri.equals(parentUri) || uri.equals("")) {
481 nc = (NestedCreator) nestedCreators.get(
482 name.toLowerCase(Locale.US));
483 }
484 if (nc == null) {
485 nc = createAddTypeCreator(project, parent, elementName);
486 }
487 if (nc == null && parent instanceof DynamicElementNS) {
488 DynamicElementNS dc = (DynamicElementNS) parent;
489 String qName = (child == null ? name : child.getQName());
490 final Object nestedElement =
491 dc.createDynamicElement(
492 (child == null ? "" : child.getNamespace()),
493 name, qName);
494 if (nestedElement != null) {
495 nc = new NestedCreator(null) {
496 Object create(
497 Project project, Object parent, Object ignore) {
498 return nestedElement;
499 }
500 };
501 }
502 }
503 if (nc == null && parent instanceof DynamicElement) {
504 DynamicElement dc = (DynamicElement) parent;
505 final Object nestedElement =
506 dc.createDynamicElement(name.toLowerCase(Locale.US));
507 if (nestedElement != null) {
508 nc = new NestedCreator(null) {
509 Object create(
510 Project project, Object parent, Object ignore) {
511 return nestedElement;
512 }
513 };
514 }
515 }
516 if (nc == null) {
517 throwNotSupported(project, parent, elementName);
518 }
519 return nc;
520 }
521
522 /**
523 * Creates a named nested element. Depending on the results of the
524 * initial introspection, either a method in the given parent instance
525 * or a simple no-arg constructor is used to create an instance of the
526 * specified element type.
527 *
528 * @param project Project to which the parent object belongs.
529 * Must not be <code>null</code>. If the resulting
530 * object is an instance of ProjectComponent, its
531 * Project reference is set to this parameter value.
532 * @param parent Parent object used to create the instance.
533 * Must not be <code>null</code>.
534 * @param elementName Name of the element to create an instance of.
535 * Must not be <code>null</code>.
536 *
537 * @return an instance of the specified element type
538 * @deprecated This is not a namespace aware method.
539 *
540 * @exception BuildException if no method is available to create the
541 * element instance, or if the creating method
542 * fails.
543 */
544 public Object createElement(Project project, Object parent,
545 String elementName) throws BuildException {
546 NestedCreator nc = getNestedCreator(project, "", parent, elementName, null);
547 try {
548 Object nestedElement = nc.create(project, parent, null);
549 if (project != null) {
550 project.setProjectReference(nestedElement);
551 }
552 return nestedElement;
553 } catch (IllegalAccessException ie) {
554 // impossible as getMethods should only return public methods
555 throw new BuildException(ie);
556 } catch (InstantiationException ine) {
557 // impossible as getMethods should only return public methods
558 throw new BuildException(ine);
559 } catch (InvocationTargetException ite) {
560 Throwable t = ite.getTargetException();
561 if (t instanceof BuildException) {
562 throw (BuildException) t;
563 }
564 throw new BuildException(t);
565 }
566 }
567
568 /**
569 * returns an object that creates and stores an object
570 * for an element of a parent.
571 *
572 * @param project Project to which the parent object belongs.
573 * @param parentUri The namespace uri of the parent object.
574 * @param parent Parent object used to create the creator object to
575 * create and store and instance of a subelement.
576 * @param elementName Name of the element to create an instance of.
577 * @param ue The unknown element associated with the element.
578 * @return a creator object to create and store the element instance.
579 */
580 public Creator getElementCreator(
581 Project project, String parentUri, Object parent, String elementName,
582 UnknownElement ue) {
583 NestedCreator nc = getNestedCreator(
584 project, parentUri, parent, elementName, ue);
585 return new Creator(project, parent, nc);
586 }
587
588 /**
589 * Indicates whether the introspected class is a dynamic one,
590 * supporting arbitrary nested elements and/or attributes.
591 *
592 * @return <code>true<code> if the introspected class is dynamic;
593 * <code>false<code> otherwise.
594 * @since Ant 1.6.3
595 *
596 * @see DynamicElement
597 * @see DynamicElementNS
598 */
599 public boolean isDynamic() {
600 return DynamicElement.class.isAssignableFrom(bean)
601 || DynamicElementNS.class.isAssignableFrom(bean);
602 }
603
604 /**
605 * Indicates whether the introspected class is a task container,
606 * supporting arbitrary nested tasks/types.
607 *
608 * @return <code>true<code> if the introspected class is a container;
609 * <code>false<code> otherwise.
610 * @since Ant 1.6.3
611 *
612 * @see TaskContainer
613 */
614 public boolean isContainer() {
615 return TaskContainer.class.isAssignableFrom(bean);
616 }
617
618 /**
619 * Indicates if this element supports a nested element of the
620 * given name.
621 *
622 * @param elementName the name of the nested element being checked
623 *
624 * @return true if the given nested element is supported
625 */
626 public boolean supportsNestedElement(String elementName) {
627 return nestedCreators.containsKey(elementName.toLowerCase(Locale.US))
628 || isDynamic()
629 || addTypeMethods.size() != 0;
630 }
631
632 /**
633 * Indicate if this element supports a nested element of the
634 * given name.
635 *
636 * @param parentUri the uri of the parent
637 * @param elementName the name of the nested element being checked
638 *
639 * @return true if the given nested element is supported
640 */
641 public boolean supportsNestedElement(String parentUri, String elementName) {
642 if (parentUri.equals(ProjectHelper.ANT_CORE_URI)) {
643 parentUri = "";
644 }
645 String uri = ProjectHelper.extractUriFromComponentName(elementName);
646 if (uri.equals(ProjectHelper.ANT_CORE_URI)) {
647 uri = "";
648 }
649 String name = ProjectHelper.extractNameFromComponentName(elementName);
650
651 return (
652 nestedCreators.containsKey(name.toLowerCase(Locale.US))
653 && (uri.equals(parentUri) || "".equals(uri)))
654 || isDynamic() || addTypeMethods.size() != 0;
655 }
656
657 /**
658 * Stores a named nested element using a storage method determined
659 * by the initial introspection. If no appropriate storage method
660 * is available, this method returns immediately.
661 *
662 * @param project Ignored in this implementation.
663 * May be <code>null</code>.
664 *
665 * @param parent Parent instance to store the child in.
666 * Must not be <code>null</code>.
667 *
668 * @param child Child instance to store in the parent.
669 * Should not be <code>null</code>.
670 *
671 * @param elementName Name of the child element to store.
672 * May be <code>null</code>, in which case
673 * this method returns immediately.
674 *
675 * @exception BuildException if the storage method fails.
676 */
677 public void storeElement(Project project, Object parent, Object child,
678 String elementName) throws BuildException {
679 if (elementName == null) {
680 return;
681 }
682 NestedCreator ns = (NestedCreator) nestedCreators.get(
683 elementName.toLowerCase(Locale.US));
684 if (ns == null) {
685 return;
686 }
687 try {
688 ns.store(parent, child);
689 } catch (IllegalAccessException ie) {
690 // impossible as getMethods should only return public methods
691 throw new BuildException(ie);
692 } catch (InstantiationException ine) {
693 // impossible as getMethods should only return public methods
694 throw new BuildException(ine);
695 } catch (InvocationTargetException ite) {
696 Throwable t = ite.getTargetException();
697 if (t instanceof BuildException) {
698 throw (BuildException) t;
699 }
700 throw new BuildException(t);
701 }
702 }
703
704 /**
705 * Returns the type of a named nested element.
706 *
707 * @param elementName The name of the element to find the type of.
708 * Must not be <code>null</code>.
709 *
710 * @return the type of the nested element with the specified name.
711 * This will never be <code>null</code>.
712 *
713 * @exception BuildException if the introspected class does not
714 * support the named nested element.
715 */
716 public Class getElementType(String elementName)
717 throws BuildException {
718 Class nt = (Class) nestedTypes.get(elementName);
719 if (nt == null) {
720 throw new UnsupportedElementException("Class "
721 + bean.getName() + " doesn't support the nested \""
722 + elementName + "\" element.", elementName);
723 }
724 return nt;
725 }
726
727 /**
728 * Returns the type of a named attribute.
729 *
730 * @param attributeName The name of the attribute to find the type of.
731 * Must not be <code>null</code>.
732 *
733 * @return the type of the attribute with the specified name.
734 * This will never be <code>null</code>.
735 *
736 * @exception BuildException if the introspected class does not
737 * support the named attribute.
738 */
739 public Class getAttributeType(String attributeName)
740 throws BuildException {
741 Class at = (Class) attributeTypes.get(attributeName);
742 if (at == null) {
743 throw new UnsupportedAttributeException("Class "
744 + bean.getName() + " doesn't support the \""
745 + attributeName + "\" attribute.", attributeName);
746 }
747 return at;
748 }
749
750 /**
751 * Returns the addText method when the introspected
752 * class supports nested text.
753 *
754 * @return the method on this introspected class that adds nested text.
755 * Cannot be <code>null</code>.
756 * @throws BuildException if the introspected class does not
757 * support the nested text.
758 * @since Ant 1.6.3
759 */
760 public Method getAddTextMethod()
761 throws BuildException {
762 if (!supportsCharacters()) {
763 throw new BuildException("Class " + bean.getName()
764 + " doesn't support nested text data.");
765 }
766 return addText;
767 }
768
769 /**
770 * Returns the adder or creator method of a named nested element.
771 *
772 * @param elementName The name of the attribute to find the setter
773 * method of. Must not be <code>null</code>.
774 * @return the method on this introspected class that adds or creates this
775 * nested element. Can be <code>null</code> when the introspected
776 * class is a dynamic configurator!
777 * @throws BuildException if the introspected class does not
778 * support the named nested element.
779 * @since Ant 1.6.3
780 */
781 public Method getElementMethod(String elementName)
782 throws BuildException {
783 Object creator = nestedCreators.get(elementName);
784 if (creator == null) {
785 throw new UnsupportedElementException("Class "
786 + bean.getName() + " doesn't support the nested \""
787 + elementName + "\" element.", elementName);
788 }
789 return ((NestedCreator) creator).method;
790 }
791
792 /**
793 * Returns the setter method of a named attribute.
794 *
795 * @param attributeName The name of the attribute to find the setter
796 * method of. Must not be <code>null</code>.
797 * @return the method on this introspected class that sets this attribute.
798 * This will never be <code>null</code>.
799 * @throws BuildException if the introspected class does not
800 * support the named attribute.
801 * @since Ant 1.6.3
802 */
803 public Method getAttributeMethod(String attributeName)
804 throws BuildException {
805 Object setter = attributeSetters.get(attributeName);
806 if (setter == null) {
807 throw new UnsupportedAttributeException("Class "
808 + bean.getName() + " doesn't support the \""
809 + attributeName + "\" attribute.", attributeName);
810 }
811 return ((AttributeSetter) setter).method;
812 }
813
814 /**
815 * Returns whether or not the introspected class supports PCDATA.
816 *
817 * @return whether or not the introspected class supports PCDATA.
818 */
819 public boolean supportsCharacters() {
820 return addText != null;
821 }
822
823 /**
824 * Returns an enumeration of the names of the attributes supported
825 * by the introspected class.
826 *
827 * @return an enumeration of the names of the attributes supported
828 * by the introspected class.
829 * @see #getAttributeMap
830 */
831 public Enumeration getAttributes() {
832 return attributeSetters.keys();
833 }
834
835 /**
836 * Returns a read-only map of attributes supported
837 * by the introspected class.
838 *
839 * @return an attribute name to attribute <code>Class</code>
840 * unmodifiable map. Can be empty, but never <code>null</code>.
841 * @since Ant 1.6.3
842 */
843 public Map getAttributeMap() {
844 return (attributeTypes.size() < 1)
845 ? EMPTY_MAP : Collections.unmodifiableMap(attributeTypes);
846 }
847
848 /**
849 * Returns an enumeration of the names of the nested elements supported
850 * by the introspected class.
851 *
852 * @return an enumeration of the names of the nested elements supported
853 * by the introspected class.
854 * @see #getNestedElementMap
855 */
856 public Enumeration getNestedElements() {
857 return nestedTypes.keys();
858 }
859
860 /**
861 * Returns a read-only map of nested elements supported
862 * by the introspected class.
863 *
864 * @return a nested-element name to nested-element <code>Class</code>
865 * unmodifiable map. Can be empty, but never <code>null</code>.
866 * @since Ant 1.6.3
867 */
868 public Map getNestedElementMap() {
869 return (nestedTypes.size() < 1)
870 ? EMPTY_MAP : Collections.unmodifiableMap(nestedTypes);
871 }
872
873 /**
874 * Returns a read-only list of extension points supported
875 * by the introspected class.
876 * <p>
877 * A task/type or nested element with void methods named <code>add()<code>
878 * or <code>addConfigured()</code>, taking a single class or interface
879 * argument, supports extensions point. This method returns the list of
880 * all these <em>void add[Configured](type)</em> methods.
881 *
882 * @return a list of void, single argument add() or addConfigured()
883 * <code>Method<code>s of all supported extension points.
884 * These methods are sorted such that if the argument type of a
885 * method derives from another type also an argument of a method
886 * of this list, the method with the most derived argument will
887 * always appear first. Can be empty, but never <code>null</code>.
888 * @since Ant 1.6.3
889 */
890 public List getExtensionPoints() {
891 return (addTypeMethods.size() < 1) ? Collections.EMPTY_LIST
892 : Collections.unmodifiableList(addTypeMethods);
893 }
894
895 /**
896 * Creates an implementation of AttributeSetter for the given
897 * attribute type. Conversions (where necessary) are automatically
898 * made for the following types:
899 * <ul>
900 * <li>String (left as it is)
901 * <li>Character/char (first character is used)
902 * <li>Boolean/boolean
903 * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used)
904 * <li>Class (Class.forName is used)
905 * <li>File (resolved relative to the appropriate project)
906 * <li>Path (resolve relative to the appropriate project)
907 * <li>EnumeratedAttribute (uses its own
908 * {@link EnumeratedAttribute#setValue(String) setValue} method)
909 * <li>Other primitive types (wrapper classes are used with constructors
910 * taking String)
911 * </ul>
912 *
913 * If none of the above covers the given parameters, a constructor for the
914 * appropriate class taking a String parameter is used if it is available.
915 *
916 * @param m The method to invoke on the bean when the setter is invoked.
917 * Must not be <code>null</code>.
918 * @param arg The type of the single argument of the bean's method.
919 * Must not be <code>null</code>.
920 * @param attrName the name of the attribute for which the setter is being
921 * created.
922 *
923 * @return an appropriate AttributeSetter instance, or <code>null</code>
924 * if no appropriate conversion is available.
925 */
926 private AttributeSetter createAttributeSetter(final Method m,
927 Class arg,
928 final String attrName) {
929 // use wrappers for primitive classes, e.g. int and
930 // Integer are treated identically
931 final Class reflectedArg = PRIMITIVE_TYPE_MAP.containsKey(arg)
932 ? (Class) PRIMITIVE_TYPE_MAP.get(arg) : arg;
933
934 // simplest case - setAttribute expects String
935 if (java.lang.String.class.equals(reflectedArg)) {
936 return new AttributeSetter(m) {
937 public void set(Project p, Object parent, String value)
938 throws InvocationTargetException, IllegalAccessException {
939 m.invoke(parent, (Object[]) (new String[] {value}));
940 }
941 };
942 // char and Character get special treatment - take the first character
943 } else if (java.lang.Character.class.equals(reflectedArg)) {
944 return new AttributeSetter(m) {
945 public void set(Project p, Object parent, String value)
946 throws InvocationTargetException, IllegalAccessException {
947 if (value.length() == 0) {
948 throw new BuildException("The value \"\" is not a "
949 + "legal value for attribute \"" + attrName + "\"");
950 }
951 m.invoke(parent, (Object[])
952 (new Character[] {new Character(value.charAt(0))}));
953 }
954 };
955 // boolean and Boolean get special treatment because we
956 // have a nice method in Project
957 } else if (java.lang.Boolean.class.equals(reflectedArg)) {
958 return new AttributeSetter(m) {
959 public void set(Project p, Object parent, String value)
960 throws InvocationTargetException, IllegalAccessException {
961 m.invoke(parent, (Object[]) (
962 new Boolean[] {Project.toBoolean(value)
963 ? Boolean.TRUE : Boolean.FALSE}));
964 }
965 };
966 // Class doesn't have a String constructor but a decent factory method
967 } else if (java.lang.Class.class.equals(reflectedArg)) {
968 return new AttributeSetter(m) {
969 public void set(Project p, Object parent, String value)
970 throws InvocationTargetException, IllegalAccessException, BuildException {
971 try {
972 m.invoke(parent, new Object[] {Class.forName(value)});
973 } catch (ClassNotFoundException ce) {
974 throw new BuildException(ce);
975 }
976 }
977 };
978 // resolve relative paths through Project
979 } else if (java.io.File.class.equals(reflectedArg)) {
980 return new AttributeSetter(m) {
981 public void set(Project p, Object parent, String value)
982 throws InvocationTargetException, IllegalAccessException {
983 m.invoke(parent, new Object[] {p.resolveFile(value)});
984 }
985 };
986 // EnumeratedAttributes have their own helper class
987 } else if (EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
988 return new AttributeSetter(m) {
989 public void set(Project p, Object parent, String value)
990 throws InvocationTargetException, IllegalAccessException, BuildException {
991 try {
992 EnumeratedAttribute ea =
993 (EnumeratedAttribute) reflectedArg.newInstance();
994 ea.setValue(value);
995 m.invoke(parent, new Object[] {ea});
996 } catch (InstantiationException ie) {
997 throw new BuildException(ie);
998 }
999 }
1000 };
1001 // worst case. look for a public String constructor and use it
1002 // also supports new Whatever(Project, String) as for Path or Reference
1003 // This is used (deliberately) for all primitives/wrappers other than
1004 // char and boolean
1005 } else {
1006 boolean includeProject;
1007 Constructor c;
1008 try {
1009 // First try with Project.
1010 c = reflectedArg.getConstructor(new Class[] {Project.class, String.class});
1011 includeProject = true;
1012 } catch (NoSuchMethodException nme) {
1013 // OK, try without.
1014 try {
1015 c = reflectedArg.getConstructor(new Class[] {String.class});
1016 includeProject = false;
1017 } catch (NoSuchMethodException nme2) {
1018 // Well, no matching constructor.
1019 return null;
1020 }
1021 }
1022 final boolean finalIncludeProject = includeProject;
1023 final Constructor finalConstructor = c;
1024
1025 return new AttributeSetter(m) {
1026 public void set(Project p, Object parent, String value)
1027 throws InvocationTargetException, IllegalAccessException, BuildException {
1028 try {
1029 Object[] args = (finalIncludeProject)
1030 ? new Object[] {p, value} : new Object[] {value};
1031
1032 Object attribute = finalConstructor.newInstance(args);
1033 if (p != null) {
1034 p.setProjectReference(attribute);
1035 }
1036 m.invoke(parent, new Object[] {attribute});
1037 } catch (InstantiationException ie) {
1038 throw new BuildException(ie);
1039 }
1040 }
1041 };
1042 }
1043 }
1044
1045 /**
1046 * Returns a description of the type of the given element in
1047 * relation to a given project. This is used for logging purposes
1048 * when the element is asked to cope with some data it has no
1049 * way of handling.
1050 *
1051 * @param project The project the element is defined in.
1052 * Must not be <code>null</code>.
1053 *
1054 * @param element The element to describe.
1055 * Must not be <code>null</code>.
1056 *
1057 * @return a description of the element type
1058 */
1059 protected String getElementName(Project project, Object element) {
1060 return project.getElementName(element);
1061 }
1062
1063 /**
1064 * Extracts the name of a property from a method name by subtracting
1065 * a given prefix and converting into lower case. It is up to calling
1066 * code to make sure the method name does actually begin with the
1067 * specified prefix - no checking is done in this method.
1068 *
1069 * @param methodName The name of the method in question.
1070 * Must not be <code>null</code>.
1071 * @param prefix The prefix to remove.
1072 * Must not be <code>null</code>.
1073 *
1074 * @return the lower-cased method name with the prefix removed.
1075 */
1076 private String getPropertyName(String methodName, String prefix) {
1077 return methodName.substring(prefix.length()).toLowerCase(Locale.US);
1078 }
1079
1080 /**
1081 * creator - allows use of create/store external
1082 * to IntrospectionHelper.
1083 * The class is final as it has a private constructor.
1084 */
1085 public static final class Creator {
1086 private NestedCreator nestedCreator;
1087 private Object parent;
1088 private Project project;
1089 private Object nestedObject;
1090 private String polyType;
1091
1092 /**
1093 * Creates a new Creator instance.
1094 * This object is given to the UnknownElement to create
1095 * objects for sub-elements. UnknownElement calls
1096 * create to create an object, the object then gets
1097 * configured and then UnknownElement calls store.
1098 * SetPolyType may be used to override the type used
1099 * to create the object with. SetPolyType gets called
1100 * before create.
1101 *
1102 * @param project the current project
1103 * @param parent the parent object to create the object in
1104 * @param nestedCreator the nested creator object to use
1105 */
1106 private Creator(
1107 Project project, Object parent, NestedCreator nestedCreator) {
1108 this.project = project;
1109 this.parent = parent;
1110 this.nestedCreator = nestedCreator;
1111 }
1112
1113 /**
1114 * Used to override the class used to create the object.
1115 *
1116 * @param polyType a ant component type name
1117 */
1118 public void setPolyType(String polyType) {
1119 this.polyType = polyType;
1120 }
1121
1122 /**
1123 * Create an object using this creator, which is determined
1124 * by introspection.
1125 *
1126 * @return the created object
1127 */
1128 public Object create() {
1129 if (polyType != null) {
1130 if (!nestedCreator.isPolyMorphic()) {
1131 throw new BuildException(
1132 "Not allowed to use the polymorphic form"
1133 + " for this element");
1134 }
1135 Class elementClass = nestedCreator.getElementClass();
1136 ComponentHelper helper =
1137 ComponentHelper.getComponentHelper(project);
1138 nestedObject = helper.createComponent(polyType);
1139 if (nestedObject == null) {
1140 throw new BuildException(
1141 "Unable to create object of type " + polyType);
1142 }
1143 }
1144 try {
1145 nestedObject = nestedCreator.create(
1146 project, parent, nestedObject);
1147 if (project != null) {
1148 project.setProjectReference(nestedObject);
1149 }
1150 return nestedObject;
1151 } catch (IllegalAccessException ex) {
1152 throw new BuildException(ex);
1153 } catch (InstantiationException ex) {
1154 throw new BuildException(ex);
1155 } catch (IllegalArgumentException ex) {
1156 if (polyType != null) {
1157 throw new BuildException(
1158 "Invalid type used " + polyType);
1159 }
1160 throw ex;
1161 } catch (InvocationTargetException ex) {
1162 Throwable t = ex.getTargetException();
1163 if (t instanceof BuildException) {
1164 throw (BuildException) t;
1165 }
1166 throw new BuildException(t);
1167 }
1168 }
1169
1170 /**
1171 * @return the real object (used currently only
1172 * for preset def).
1173 */
1174 public Object getRealObject() {
1175 return nestedCreator.getRealObject();
1176 }
1177
1178 /**
1179 * Stores the nested element object using a storage method
1180 * determined by introspection.
1181 *
1182 */
1183 public void store() {
1184 try {
1185 nestedCreator.store(parent, nestedObject);
1186 } catch (IllegalAccessException ex) {
1187 throw new BuildException(ex);
1188 } catch (InstantiationException ex) {
1189 throw new BuildException(ex);
1190 } catch (IllegalArgumentException ex) {
1191 if (polyType != null) {
1192 throw new BuildException(
1193 "Invalid type used " + polyType);
1194 }
1195 throw ex;
1196 } catch (InvocationTargetException ex) {
1197 Throwable t = ex.getTargetException();
1198 if (t instanceof BuildException) {
1199 throw (BuildException) t;
1200 }
1201 throw new BuildException(t);
1202 }
1203 }
1204 }
1205
1206 /**
1207 * Internal interface used to create nested elements. Not documented
1208 * in detail for reasons of source code readability.
1209 */
1210 private abstract static class NestedCreator {
1211 Method method; // the method called to add/create the nested element
1212
1213 NestedCreator(Method m) {
1214 this.method = m;
1215 }
1216 boolean isPolyMorphic() {
1217 return false;
1218 }
1219 Class getElementClass() {
1220 return null;
1221 }
1222 Object getRealObject() {
1223 return null;
1224 }
1225 abstract Object create(Project project, Object parent, Object child)
1226 throws InvocationTargetException,
1227 IllegalAccessException,
1228 InstantiationException;
1229 void store(Object parent, Object child)
1230 throws InvocationTargetException,
1231 IllegalAccessException,
1232 InstantiationException {
1233 // DO NOTHING
1234 }
1235 }
1236
1237 private class CreateNestedCreator extends NestedCreator {
1238 CreateNestedCreator(Method m) {
1239 super(m);
1240 }
1241
1242 Object create(Project project, Object parent, Object ignore)
1243 throws InvocationTargetException, IllegalAccessException {
1244 return method.invoke(parent, new Object[] {});
1245 }
1246 }
1247
1248 /** Version to use for addXXX and addConfiguredXXX */
1249 private class AddNestedCreator extends NestedCreator {
1250
1251 static final int ADD = 1;
1252 static final int ADD_CONFIGURED = 2;
1253
1254 protected Constructor constructor;
1255 protected int behavior;
1256
1257 AddNestedCreator(Method m, Constructor c, int behavior) {
1258 super(m);
1259 this.constructor = c;
1260 this.behavior = behavior;
1261 }
1262
1263 boolean isPolyMorphic() {
1264 return true;
1265 }
1266
1267 Class getElementClass() {
1268 return constructor.getDeclaringClass();
1269 }
1270
1271 Object create(Project project, Object parent, Object child)
1272 throws InvocationTargetException,
1273 IllegalAccessException, InstantiationException {
1274 if (child != null) {
1275 // Empty
1276 } else {
1277 child = constructor.newInstance(
1278 (constructor.getParameterTypes().length == 0)
1279 ? new Object[] {} : new Object[] {project});
1280 }
1281 if (child instanceof PreSetDef.PreSetDefinition) {
1282 child = ((PreSetDef.PreSetDefinition) child)
1283 .createObject(project);
1284 }
1285 if (behavior == ADD) {
1286 istore(parent, child);
1287 }
1288 return child;
1289 }
1290
1291 void store(Object parent, Object child)
1292 throws InvocationTargetException,
1293 IllegalAccessException, InstantiationException {
1294 if (behavior == ADD_CONFIGURED) {
1295 istore(parent, child);
1296 }
1297 }
1298
1299 private void istore(Object parent, Object child)
1300 throws InvocationTargetException,
1301 IllegalAccessException, InstantiationException {
1302 method.invoke(parent, new Object[] {child});
1303 }
1304 }
1305
1306 /**
1307 * Internal interface used to setting element attributes. Not documented
1308 * in detail for reasons of source code readability.
1309 */
1310 private abstract static class AttributeSetter {
1311 Method method; // the method called to set the attribute
1312 AttributeSetter(Method m) {
1313 this.method = m;
1314 }
1315 abstract void set(Project p, Object parent, String value)
1316 throws InvocationTargetException,
1317 IllegalAccessException,
1318 BuildException;
1319 }
1320
1321 /**
1322 * Clears all storage used by this class, including the static cache of
1323 * helpers.
1324 *
1325 * @param event Ignored in this implementation.
1326 */
1327 public void buildFinished(BuildEvent event) {
1328 attributeTypes.clear();
1329 attributeSetters.clear();
1330 nestedTypes.clear();
1331 nestedCreators.clear();
1332 addText = null;
1333 helpers.clear();
1334 }
1335
1336 /**
1337 * Empty implementation to satisfy the BuildListener interface.
1338 * @param event Ignored in this implementation.
1339 */
1340 public void buildStarted(BuildEvent event) {
1341 }
1342
1343 /**
1344 * Empty implementation to satisfy the BuildListener interface.
1345 *
1346 * @param event Ignored in this implementation.
1347 */
1348 public void targetStarted(BuildEvent event) {
1349 }
1350
1351 /**
1352 * Empty implementation to satisfy the BuildListener interface.
1353 *
1354 * @param event Ignored in this implementation.
1355 */
1356 public void targetFinished(BuildEvent event) {
1357 }
1358
1359 /**
1360 * Empty implementation to satisfy the BuildListener interface.
1361 *
1362 * @param event Ignored in this implementation.
1363 */
1364 public void taskStarted(BuildEvent event) {
1365 }
1366
1367 /**
1368 * Empty implementation to satisfy the BuildListener interface.
1369 *
1370 * @param event Ignored in this implementation.
1371 */
1372 public void taskFinished(BuildEvent event) {
1373 }
1374
1375 /**
1376 * Empty implementation to satisfy the BuildListener interface.
1377 *
1378 * @param event Ignored in this implementation.
1379 */
1380 public void messageLogged(BuildEvent event) {
1381 }
1382
1383 /**
1384 *
1385 */
1386 private NestedCreator createAddTypeCreator(
1387 Project project, Object parent, String elementName)
1388 throws BuildException {
1389 if (addTypeMethods.size() == 0) {
1390 return null;
1391 }
1392 ComponentHelper helper = ComponentHelper.getComponentHelper(project);
1393
1394 Object addedObject = null;
1395 Method addMethod = null;
1396 Class clazz = helper.getComponentClass(elementName);
1397 if (clazz == null) {
1398 return null;
1399 }
1400 addMethod = findMatchingMethod(clazz, addTypeMethods);
1401 if (addMethod == null) {
1402 return null;
1403 }
1404 addedObject = helper.createComponent(elementName);
1405 if (addedObject == null) {
1406 return null;
1407 }
1408 Object rObject = addedObject;
1409 if (addedObject instanceof PreSetDef.PreSetDefinition) {
1410 rObject = ((PreSetDef.PreSetDefinition) addedObject).createObject(
1411 project);
1412 }
1413 final Object nestedObject = addedObject;
1414 final Object realObject = rObject;
1415
1416 return new NestedCreator(addMethod) {
1417 Object create(Project project, Object parent, Object ignore)
1418 throws InvocationTargetException, IllegalAccessException {
1419 if (!method.getName().endsWith("Configured")) {
1420 method.invoke(parent, new Object[]{realObject});
1421 }
1422 return nestedObject;
1423 }
1424
1425 Object getRealObject() {
1426 return realObject;
1427 }
1428
1429 void store(Object parent, Object child)
1430 throws InvocationTargetException, IllegalAccessException,
1431 InstantiationException {
1432 if (method.getName().endsWith("Configured")) {
1433 method.invoke(parent, new Object[]{realObject});
1434 }
1435 }
1436 };
1437 }
1438
1439 /**
1440 * Inserts an add or addConfigured method into
1441 * the addTypeMethods array. The array is
1442 * ordered so that the more derived classes
1443 * are first.
1444 * @param method the <code>Method</code> to insert.
1445 */
1446 private void insertAddTypeMethod(Method method) {
1447 Class argClass = method.getParameterTypes()[0];
1448 for (int c = 0; c < addTypeMethods.size(); ++c) {
1449 Method current = (Method) addTypeMethods.get(c);
1450 if (current.getParameterTypes()[0].equals(argClass)) {
1451 return; // Already present
1452 }
1453 if (current.getParameterTypes()[0].isAssignableFrom(
1454 argClass)) {
1455 addTypeMethods.add(c, method);
1456 return; // higher derived
1457 }
1458 }
1459 addTypeMethods.add(method);
1460 }
1461
1462 /**
1463 * Search the list of methods to find the first method
1464 * that has a parameter that accepts the nested element object.
1465 * @param paramClass the <code>Class</code> type to search for.
1466 * @param methods the <code>List</code> of methods to search.
1467 * @return a matching <code>Method</code>; null if none found.
1468 */
1469 private Method findMatchingMethod(Class paramClass, List methods) {
1470 Class matchedClass = null;
1471 Method matchedMethod = null;
1472
1473 for (int i = 0; i < methods.size(); ++i) {
1474 Method method = (Method) methods.get(i);
1475 Class methodClass = method.getParameterTypes()[0];
1476 if (methodClass.isAssignableFrom(paramClass)) {
1477 if (matchedClass == null) {
1478 matchedClass = methodClass;
1479 matchedMethod = method;
1480 } else {
1481 if (!methodClass.isAssignableFrom(matchedClass)) {
1482 throw new BuildException("ambiguous: types "
1483 + matchedClass.getName() + " and "
1484 + methodClass.getName() + " match "
1485 + paramClass.getName());
1486 }
1487 }
1488 }
1489 }
1490 return matchedMethod;
1491 }
1492
1493}
Note: See TracBrowser for help on using the repository browser.