You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

IntrospectionHelper.java 37 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Ant", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.tools.ant;
  55. import org.apache.tools.ant.types.Path;
  56. import org.apache.tools.ant.types.EnumeratedAttribute;
  57. import java.lang.reflect.Method;
  58. import java.lang.reflect.InvocationTargetException;
  59. import java.lang.reflect.Constructor;
  60. import java.io.File;
  61. import java.util.Enumeration;
  62. import java.util.Hashtable;
  63. import java.util.Locale;
  64. /**
  65. * Helper class that collects the methods a task or nested element
  66. * holds to set attributes, create nested elements or hold PCDATA
  67. * elements.
  68. *
  69. * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
  70. */
  71. public class IntrospectionHelper implements BuildListener {
  72. /**
  73. * Map from attribute names to attribute types
  74. * (String to Class).
  75. */
  76. private Hashtable attributeTypes;
  77. /**
  78. * Map from attribute names to attribute setter methods
  79. * (String to AttributeSetter).
  80. */
  81. private Hashtable attributeSetters;
  82. /**
  83. * Map from attribute names to nested types
  84. * (String to Class).
  85. */
  86. private Hashtable nestedTypes;
  87. /**
  88. * Map from attribute names to methods to create nested types
  89. * (String to NestedCreator).
  90. */
  91. private Hashtable nestedCreators;
  92. /**
  93. * Map from attribute names to methods to store configured nested types
  94. * (String to NestedStorer).
  95. */
  96. private Hashtable nestedStorers;
  97. /**
  98. * The method to invoke to add PCDATA.
  99. */
  100. private Method addText = null;
  101. /**
  102. * The class introspected by this instance.
  103. */
  104. private Class bean;
  105. /**
  106. * Helper instances we've already created (Class to IntrospectionHelper).
  107. */
  108. private static Hashtable helpers = new Hashtable();
  109. /**
  110. * Map from primitive types to wrapper classes for use in
  111. * createAttributeSetter (Class to Class). Note that char
  112. * and boolean are in here even though they get special treatment
  113. * - this way we only need to test for the wrapper class.
  114. */
  115. private static final Hashtable PRIMITIVE_TYPE_MAP = new Hashtable(8);
  116. // Set up PRIMITIVE_TYPE_MAP
  117. static {
  118. Class[] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE,
  119. Short.TYPE, Integer.TYPE, Long.TYPE,
  120. Float.TYPE, Double.TYPE};
  121. Class[] wrappers = {Boolean.class, Byte.class, Character.class,
  122. Short.class, Integer.class, Long.class,
  123. Float.class, Double.class};
  124. for (int i = 0; i < primitives.length; i++) {
  125. PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]);
  126. }
  127. }
  128. // XXX: (Jon Skeet) The documentation below doesn't draw a clear
  129. // distinction between addConfigured and add. It's obvious what the
  130. // code *here* does (addConfigured sets both a creator method which
  131. // calls a no-arg constructor and a storer method which calls the
  132. // method we're looking at, whlie add just sets a creator method
  133. // which calls the method we're looking at) but it's not at all
  134. // obvious what the difference in actual *effect* will be later
  135. // on. I can't see any mention of addConfiguredXXX in "Developing
  136. // with Ant" (at least in the version on the web site). Someone
  137. // who understands should update this documentation
  138. // (and preferably the manual too) at some stage.
  139. /**
  140. * Sole constructor, which is private to ensure that all
  141. * IntrospectionHelpers are created via {@link #getHelper(Class) getHelper}.
  142. * Introspects the given class for bean-like methods.
  143. * Each method is examined in turn, and the following rules are applied:
  144. * <p>
  145. * <ul>
  146. * <li>If the method is <code>Task.setLocation(Location)</code>,
  147. * <code>Task.setTaskType(String)</code>
  148. * or <code>TaskContainer.addTask(Task)</code>, it is ignored. These
  149. * methods are handled differently elsewhere.
  150. * <li><code>void addText(String)</code> is recognised as the method for
  151. * adding PCDATA to a bean.
  152. * <li><code>void setFoo(Bar)</code> is recognised as a method for
  153. * setting the value of attribute <code>foo</code>, so long as
  154. * <code>Bar</code> is non-void and is not an array type. Non-String
  155. * parameter types always overload String parameter types, but that is
  156. * the only guarantee made in terms of priority.
  157. * <li><code>Foo createBar()</code> is recognised as a method for
  158. * creating a nested element called <code>bar</code> of type
  159. * <code>Foo</code>, so long as <code>Foo</code> is not a primitive or
  160. * array type.
  161. * <li><code>void addConfiguredFoo(Bar)</code> is recognised as a
  162. * method for storing a pre-configured element called
  163. * <code>foo</code> and of type <code>Bar</code>, so long as
  164. * <code>Bar</code> is not an array, primitive or String type.
  165. * <code>Bar</code> must have an accessible constructor taking no
  166. * arguments.
  167. * <li><code>void addFoo(Bar)</code> is recognised as a
  168. * method for storing an element called <code>foobar</code>
  169. * and of type <code>Baz</code>, so long as
  170. * <code>Baz</code> is not an array, primitive or String type.
  171. * <code>Baz</code> must have an accessible constructor taking no
  172. * arguments.
  173. * </ul>
  174. * Note that only one method is retained to create/set/addConfigured/add
  175. * any element or attribute.
  176. *
  177. * @param bean The bean type to introspect.
  178. * Must not be <code>null</code>.
  179. *
  180. * @see #getHelper(Class)
  181. */
  182. private IntrospectionHelper(final Class bean) {
  183. attributeTypes = new Hashtable();
  184. attributeSetters = new Hashtable();
  185. nestedTypes = new Hashtable();
  186. nestedCreators = new Hashtable();
  187. nestedStorers = new Hashtable();
  188. this.bean = bean;
  189. Method[] methods = bean.getMethods();
  190. for (int i = 0; i < methods.length; i++) {
  191. final Method m = methods[i];
  192. final String name = m.getName();
  193. Class returnType = m.getReturnType();
  194. Class[] args = m.getParameterTypes();
  195. // not really user settable properties on tasks
  196. if (org.apache.tools.ant.Task.class.isAssignableFrom(bean)
  197. && args.length == 1 && isHiddenSetMethod(name, args[0])) {
  198. continue;
  199. }
  200. // hide addTask for TaskContainers
  201. if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean)
  202. && args.length == 1 && "addTask".equals(name)
  203. && org.apache.tools.ant.Task.class.equals(args[0])) {
  204. continue;
  205. }
  206. if ("addText".equals(name)
  207. && java.lang.Void.TYPE.equals(returnType)
  208. && args.length == 1
  209. && java.lang.String.class.equals(args[0])) {
  210. addText = methods[i];
  211. } else if (name.startsWith("set")
  212. && java.lang.Void.TYPE.equals(returnType)
  213. && args.length == 1
  214. && !args[0].isArray()) {
  215. String propName = getPropertyName(name, "set");
  216. if (attributeSetters.get(propName) != null) {
  217. if (java.lang.String.class.equals(args[0])) {
  218. /*
  219. Ignore method m, as there is an overloaded
  220. form of this method that takes in a
  221. non-string argument, which gains higher
  222. priority.
  223. */
  224. continue;
  225. }
  226. /*
  227. If the argument is not a String, and if there
  228. is an overloaded form of this method already defined,
  229. we just override that with the new one.
  230. This mechanism does not guarantee any specific order
  231. in which the methods will be selected: so any code
  232. that depends on the order in which "set" methods have
  233. been defined, is not guaranteed to be selected in any
  234. particular order.
  235. */
  236. }
  237. AttributeSetter as = createAttributeSetter(m, args[0]);
  238. if (as != null) {
  239. attributeTypes.put(propName, args[0]);
  240. attributeSetters.put(propName, as);
  241. }
  242. } else if (name.startsWith("create")
  243. && !returnType.isArray()
  244. && !returnType.isPrimitive()
  245. && args.length == 0) {
  246. String propName = getPropertyName(name, "create");
  247. nestedTypes.put(propName, returnType);
  248. nestedCreators.put(propName, new NestedCreator() {
  249. public Object create(Object parent)
  250. throws InvocationTargetException,
  251. IllegalAccessException {
  252. return m.invoke(parent, new Object[] {});
  253. }
  254. });
  255. nestedStorers.remove(propName);
  256. } else if (name.startsWith("addConfigured")
  257. && java.lang.Void.TYPE.equals(returnType)
  258. && args.length == 1
  259. && !java.lang.String.class.equals(args[0])
  260. && !args[0].isArray()
  261. && !args[0].isPrimitive()) {
  262. try {
  263. final Constructor c =
  264. args[0].getConstructor(new Class[] {});
  265. String propName = getPropertyName(name, "addConfigured");
  266. nestedTypes.put(propName, args[0]);
  267. nestedCreators.put(propName, new NestedCreator() {
  268. public Object create(Object parent)
  269. throws InvocationTargetException, IllegalAccessException, InstantiationException {
  270. Object o = c.newInstance(new Object[] {});
  271. return o;
  272. }
  273. });
  274. nestedStorers.put(propName, new NestedStorer() {
  275. public void store(Object parent, Object child)
  276. throws InvocationTargetException, IllegalAccessException, InstantiationException {
  277. m.invoke(parent, new Object[] {child});
  278. }
  279. });
  280. } catch (NoSuchMethodException nse) {
  281. }
  282. } else if (name.startsWith("add")
  283. && java.lang.Void.TYPE.equals(returnType)
  284. && args.length == 1
  285. && !java.lang.String.class.equals(args[0])
  286. && !args[0].isArray()
  287. && !args[0].isPrimitive()) {
  288. try {
  289. final Constructor c =
  290. args[0].getConstructor(new Class[] {});
  291. String propName = getPropertyName(name, "add");
  292. nestedTypes.put(propName, args[0]);
  293. nestedCreators.put(propName, new NestedCreator() {
  294. public Object create(Object parent)
  295. throws InvocationTargetException, IllegalAccessException, InstantiationException {
  296. Object o = c.newInstance(new Object[] {});
  297. m.invoke(parent, new Object[] {o});
  298. return o;
  299. }
  300. });
  301. nestedStorers.remove(name);
  302. } catch (NoSuchMethodException nse) {
  303. }
  304. }
  305. }
  306. }
  307. /**
  308. * Certain set methods are part of the Ant core interface to tasks and
  309. * therefore not to be considered for introspection
  310. *
  311. * @param name the name of the set method
  312. * @param type the type of the set method's parameter
  313. * @return true if the given set method is to be hidden.
  314. */
  315. private boolean isHiddenSetMethod(String name, Class type) {
  316. if ("setLocation".equals(name)
  317. && org.apache.tools.ant.Location.class.equals(type)) {
  318. return true;
  319. }
  320. if ("setTaskType".equals(name)
  321. && java.lang.String.class.equals(type)) {
  322. return true;
  323. }
  324. return false;
  325. }
  326. /**
  327. * Returns a helper for the given class, either from the cache
  328. * or by creating a new instance.
  329. *
  330. * @param c The class for which a helper is required.
  331. * Must not be <code>null</code>.
  332. *
  333. * @return a helper for the specified class
  334. */
  335. public static synchronized IntrospectionHelper getHelper(Class c) {
  336. IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
  337. if (ih == null) {
  338. ih = new IntrospectionHelper(c);
  339. helpers.put(c, ih);
  340. }
  341. return ih;
  342. }
  343. /**
  344. * Sets the named attribute in the given element, which is part of the
  345. * given project.
  346. *
  347. * @param p The project containing the element. This is used when files
  348. * need to be resolved. Must not be <code>null</code>.
  349. * @param element The element to set the attribute in. Must not be
  350. * <code>null</code>.
  351. * @param attributeName The name of the attribute to set. Must not be
  352. * <code>null</code>.
  353. * @param value The value to set the attribute to. This may be interpreted
  354. * or converted to the necessary type if the setter method
  355. * doesn't just take a string. Must not be <code>null</code>.
  356. *
  357. * @exception BuildException if the introspected class doesn't support
  358. * the given attribute, or if the setting
  359. * method fails.
  360. */
  361. public void setAttribute(Project p, Object element, String attributeName,
  362. String value)
  363. throws BuildException {
  364. AttributeSetter as
  365. = (AttributeSetter) attributeSetters.get(attributeName);
  366. if (as == null) {
  367. String msg = p.getElementName(element) +
  368. //String msg = "Class " + element.getClass().getName() +
  369. " doesn't support the \"" + attributeName + "\" attribute.";
  370. throw new BuildException(msg);
  371. }
  372. try {
  373. as.set(p, element, value);
  374. } catch (IllegalAccessException ie) {
  375. // impossible as getMethods should only return public methods
  376. throw new BuildException(ie);
  377. } catch (InvocationTargetException ite) {
  378. Throwable t = ite.getTargetException();
  379. if (t instanceof BuildException) {
  380. throw (BuildException) t;
  381. }
  382. throw new BuildException(t);
  383. }
  384. }
  385. /**
  386. * Adds PCDATA to an element, using the element's
  387. * <code>void addText(String)</code> method, if it has one. If no
  388. * such method is present, a BuildException is thrown if the
  389. * given text contains non-whitespace.
  390. *
  391. * @param project The project which the element is part of.
  392. * Must not be <code>null</code>.
  393. * @param element The element to add the text to.
  394. * Must not be <code>null</code>.
  395. * @param text The text to add.
  396. * Must not be <code>null</code>.
  397. *
  398. * @exception BuildException if non-whitespace text is provided and no
  399. * method is available to handle it, or if
  400. * the handling method fails.
  401. */
  402. public void addText(Project project, Object element, String text)
  403. throws BuildException {
  404. if (addText == null) {
  405. // Element doesn't handle text content
  406. if (text.trim().length() == 0) {
  407. // Only whitespace - ignore
  408. return;
  409. } else {
  410. // Not whitespace - fail
  411. String msg = project.getElementName(element) +
  412. " doesn't support nested text data.";
  413. throw new BuildException(msg);
  414. }
  415. }
  416. try {
  417. addText.invoke(element, new String[] {text});
  418. } catch (IllegalAccessException ie) {
  419. // impossible as getMethods should only return public methods
  420. throw new BuildException(ie);
  421. } catch (InvocationTargetException ite) {
  422. Throwable t = ite.getTargetException();
  423. if (t instanceof BuildException) {
  424. throw (BuildException) t;
  425. }
  426. throw new BuildException(t);
  427. }
  428. }
  429. /**
  430. * Creates a named nested element. Depending on the results of the
  431. * initial introspection, either a method in the given parent instance
  432. * or a simple no-arg constructor is used to create an instance of the
  433. * specified element type.
  434. *
  435. * @param project Project to which the parent object belongs.
  436. * Must not be <code>null</code>. If the resulting
  437. * object is an instance of ProjectComponent, its
  438. * Project reference is set to this parameter value.
  439. * @param parent Parent object used to create the instance.
  440. * Must not be <code>null</code>.
  441. * @param elementName Name of the element to create an instance of.
  442. * Must not be <code>null</code>.
  443. *
  444. * @return an instance of the specified element type
  445. *
  446. * @exception BuildException if no method is available to create the
  447. * element instance, or if the creating method
  448. * fails.
  449. */
  450. public Object createElement(Project project, Object parent,
  451. String elementName) throws BuildException {
  452. NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
  453. if (nc == null) {
  454. String msg = project.getElementName(parent) +
  455. " doesn't support the nested \"" + elementName + "\" element.";
  456. throw new BuildException(msg);
  457. }
  458. try {
  459. Object nestedElement = nc.create(parent);
  460. if (nestedElement instanceof ProjectComponent) {
  461. ((ProjectComponent) nestedElement).setProject(project);
  462. }
  463. return nestedElement;
  464. } catch (IllegalAccessException ie) {
  465. // impossible as getMethods should only return public methods
  466. throw new BuildException(ie);
  467. } catch (InstantiationException ine) {
  468. // impossible as getMethods should only return public methods
  469. throw new BuildException(ine);
  470. } catch (InvocationTargetException ite) {
  471. Throwable t = ite.getTargetException();
  472. if (t instanceof BuildException) {
  473. throw (BuildException) t;
  474. }
  475. throw new BuildException(t);
  476. }
  477. }
  478. /**
  479. * Stores a named nested element using a storage method determined
  480. * by the initial introspection. If no appropriate storage method
  481. * is available, this method returns immediately.
  482. *
  483. * @param project Ignored in this implementation.
  484. * May be <code>null</code>.
  485. *
  486. * @param parent Parent instance to store the child in.
  487. * Must not be <code>null</code>.
  488. *
  489. * @param child Child instance to store in the parent.
  490. * Should not be <code>null</code>.
  491. *
  492. * @param elementName Name of the child element to store.
  493. * May be <code>null</code>, in which case
  494. * this method returns immediately.
  495. *
  496. * @exception BuildException if the storage method fails.
  497. */
  498. public void storeElement(Project project, Object parent, Object child,
  499. String elementName) throws BuildException {
  500. if (elementName == null) {
  501. return;
  502. }
  503. NestedStorer ns = (NestedStorer) nestedStorers.get(elementName);
  504. if (ns == null) {
  505. return;
  506. }
  507. try {
  508. ns.store(parent, child);
  509. } catch (IllegalAccessException ie) {
  510. // impossible as getMethods should only return public methods
  511. throw new BuildException(ie);
  512. } catch (InstantiationException ine) {
  513. // impossible as getMethods should only return public methods
  514. throw new BuildException(ine);
  515. } catch (InvocationTargetException ite) {
  516. Throwable t = ite.getTargetException();
  517. if (t instanceof BuildException) {
  518. throw (BuildException) t;
  519. }
  520. throw new BuildException(t);
  521. }
  522. }
  523. /**
  524. * Returns the type of a named nested element.
  525. *
  526. * @param elementName The name of the element to find the type of.
  527. * Must not be <code>null</code>.
  528. *
  529. * @return the type of the nested element with the specified name.
  530. * This will never be <code>null</code>.
  531. *
  532. * @exception BuildException if the introspected class does not
  533. * support the named nested element.
  534. */
  535. public Class getElementType(String elementName)
  536. throws BuildException {
  537. Class nt = (Class) nestedTypes.get(elementName);
  538. if (nt == null) {
  539. String msg = "Class " + bean.getName() +
  540. " doesn't support the nested \"" + elementName + "\" element.";
  541. throw new BuildException(msg);
  542. }
  543. return nt;
  544. }
  545. /**
  546. * Returns the type of a named attribute.
  547. *
  548. * @param attributeName The name of the attribute to find the type of.
  549. * Must not be <code>null</code>.
  550. *
  551. * @return the type of the attribute with the specified name.
  552. * This will never be <code>null</code>.
  553. *
  554. * @exception BuildException if the introspected class does not
  555. * support the named attribute.
  556. */
  557. public Class getAttributeType(String attributeName)
  558. throws BuildException {
  559. Class at = (Class) attributeTypes.get(attributeName);
  560. if (at == null) {
  561. String msg = "Class " + bean.getName() +
  562. " doesn't support the \"" + attributeName + "\" attribute.";
  563. throw new BuildException(msg);
  564. }
  565. return at;
  566. }
  567. /**
  568. * Returns whether or not the introspected class supports PCDATA.
  569. *
  570. * @return whether or not the introspected class supports PCDATA.
  571. */
  572. public boolean supportsCharacters() {
  573. return addText != null;
  574. }
  575. /**
  576. * Returns an enumeration of the names of the attributes supported
  577. * by the introspected class.
  578. *
  579. * @return an enumeration of the names of the attributes supported
  580. * by the introspected class.
  581. */
  582. public Enumeration getAttributes() {
  583. return attributeSetters.keys();
  584. }
  585. /**
  586. * Returns an enumeration of the names of the nested elements supported
  587. * by the introspected class.
  588. *
  589. * @return an enumeration of the names of the nested elements supported
  590. * by the introspected class.
  591. */
  592. public Enumeration getNestedElements() {
  593. return nestedTypes.keys();
  594. }
  595. /**
  596. * Creates an implementation of AttributeSetter for the given
  597. * attribute type. Conversions (where necessary) are automatically
  598. * made for the following types:
  599. * <ul>
  600. * <li>String (left as it is)
  601. * <li>Character/char (first character is used)
  602. * <li>Boolean/boolean
  603. * ({@link Project#toBoolean(String) Project.toBoolean(String)} is used)
  604. * <li>Class (Class.forName is used)
  605. * <li>File (resolved relative to the appropriate project)
  606. * <li>Path (resolve relative to the appropriate project)
  607. * <li>EnumeratedAttribute (uses its own
  608. * {@link EnumeratedAttribute#setValue(String) setValue} method)
  609. * <li>Other primitive types (wrapper classes are used with constructors
  610. * taking String)
  611. * </ul>
  612. *
  613. * If none of the above covers the given parameters, a constructor for the
  614. * appropriate class taking a String parameter is used if it is available.
  615. *
  616. * @param m The method to invoke on the bean when the setter is invoked.
  617. * Must not be <code>null</code>.
  618. * @param arg The type of the single argument of the bean's method.
  619. * Must not be <code>null</code>.
  620. *
  621. * @return an appropriate AttributeSetter instance, or <code>null</code>
  622. * if no appropriate conversion is available.
  623. */
  624. private AttributeSetter createAttributeSetter(final Method m,
  625. Class arg) {
  626. // use wrappers for primitive classes, e.g. int and
  627. // Integer are treated identically
  628. final Class reflectedArg = PRIMITIVE_TYPE_MAP.containsKey (arg)
  629. ? (Class) PRIMITIVE_TYPE_MAP.get(arg) : arg;
  630. // simplest case - setAttribute expects String
  631. if (java.lang.String.class.equals(reflectedArg)) {
  632. return new AttributeSetter() {
  633. public void set(Project p, Object parent, String value)
  634. throws InvocationTargetException, IllegalAccessException {
  635. m.invoke(parent, new String[] {value});
  636. }
  637. };
  638. // char and Character get special treatment - take the first character
  639. } else if (java.lang.Character.class.equals(reflectedArg)) {
  640. return new AttributeSetter() {
  641. public void set(Project p, Object parent, String value)
  642. throws InvocationTargetException, IllegalAccessException {
  643. m.invoke(parent, new Character[] {new Character(value.charAt(0))});
  644. }
  645. };
  646. // boolean and Boolean get special treatment because we
  647. // have a nice method in Project
  648. } else if (java.lang.Boolean.class.equals(reflectedArg)) {
  649. return new AttributeSetter() {
  650. public void set(Project p, Object parent, String value)
  651. throws InvocationTargetException, IllegalAccessException {
  652. m.invoke(parent,
  653. new Boolean[] {new Boolean(Project.toBoolean(value))});
  654. }
  655. };
  656. // Class doesn't have a String constructor but a decent factory method
  657. } else if (java.lang.Class.class.equals(reflectedArg)) {
  658. return new AttributeSetter() {
  659. public void set(Project p, Object parent, String value)
  660. throws InvocationTargetException, IllegalAccessException, BuildException {
  661. try {
  662. m.invoke(parent, new Class[] {Class.forName(value)});
  663. } catch (ClassNotFoundException ce) {
  664. throw new BuildException(ce);
  665. }
  666. }
  667. };
  668. // resolve relative paths through Project
  669. } else if (java.io.File.class.equals(reflectedArg)) {
  670. return new AttributeSetter() {
  671. public void set(Project p, Object parent, String value)
  672. throws InvocationTargetException, IllegalAccessException {
  673. m.invoke(parent, new File[] {p.resolveFile(value)});
  674. }
  675. };
  676. // resolve relative paths through Project
  677. } else if (org.apache.tools.ant.types.Path.class.equals(reflectedArg)) {
  678. return new AttributeSetter() {
  679. public void set(Project p, Object parent, String value)
  680. throws InvocationTargetException, IllegalAccessException {
  681. m.invoke(parent, new Path[] {new Path(p, value)});
  682. }
  683. };
  684. // EnumeratedAttributes have their own helper class
  685. } else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(reflectedArg)) {
  686. return new AttributeSetter() {
  687. public void set(Project p, Object parent, String value)
  688. throws InvocationTargetException, IllegalAccessException, BuildException {
  689. try {
  690. org.apache.tools.ant.types.EnumeratedAttribute ea =
  691. (org.apache.tools.ant.types.EnumeratedAttribute) reflectedArg.newInstance();
  692. ea.setValue(value);
  693. m.invoke(parent, new EnumeratedAttribute[] {ea});
  694. } catch (InstantiationException ie) {
  695. throw new BuildException(ie);
  696. }
  697. }
  698. };
  699. // worst case. look for a public String constructor and use it
  700. // This is used (deliberately) for all primitives/wrappers other than
  701. // char and boolean
  702. } else {
  703. try {
  704. final Constructor c =
  705. reflectedArg.getConstructor(new Class[] {java.lang.String.class});
  706. return new AttributeSetter() {
  707. public void set(Project p, Object parent,
  708. String value)
  709. throws InvocationTargetException, IllegalAccessException, BuildException {
  710. try {
  711. Object attribute = c.newInstance(new String[] {value});
  712. if (attribute instanceof ProjectComponent) {
  713. ((ProjectComponent) attribute).setProject(p);
  714. }
  715. m.invoke(parent, new Object[] {attribute});
  716. } catch (InstantiationException ie) {
  717. throw new BuildException(ie);
  718. }
  719. }
  720. };
  721. } catch (NoSuchMethodException nme) {
  722. }
  723. }
  724. return null;
  725. }
  726. /**
  727. * Returns a description of the type of the given element in
  728. * relation to a given project. This is used for logging purposes
  729. * when the element is asked to cope with some data it has no
  730. * way of handling.
  731. *
  732. * @param project The project the element is defined in.
  733. * Must not be <code>null</code>.
  734. *
  735. * @param element The element to describe.
  736. * Must not be <code>null</code>.
  737. *
  738. * @return a description of the element type
  739. */
  740. protected String getElementName(Project project, Object element) {
  741. return project.getElementName(element);
  742. }
  743. /**
  744. * Extracts the name of a property from a method name by subtracting
  745. * a given prefix and converting into lower case. It is up to calling
  746. * code to make sure the method name does actually begin with the
  747. * specified prefix - no checking is done in this method.
  748. *
  749. * @param methodName The name of the method in question.
  750. * Must not be <code>null</code>.
  751. * @param prefix The prefix to remove.
  752. * Must not be <code>null</code>.
  753. *
  754. * @return the lower-cased method name with the prefix removed.
  755. */
  756. private String getPropertyName(String methodName, String prefix) {
  757. int start = prefix.length();
  758. return methodName.substring(start).toLowerCase(Locale.US);
  759. }
  760. /**
  761. * Internal interface used to create nested elements. Not documented
  762. * in detail for reasons of source code readability.
  763. */
  764. private interface NestedCreator {
  765. Object create(Object parent)
  766. throws InvocationTargetException, IllegalAccessException, InstantiationException;
  767. }
  768. /**
  769. * Internal interface used to storing nested elements. Not documented
  770. * in detail for reasons of source code readability.
  771. */
  772. private interface NestedStorer {
  773. void store(Object parent, Object child)
  774. throws InvocationTargetException, IllegalAccessException, InstantiationException;
  775. }
  776. /**
  777. * Internal interface used to setting element attributes. Not documented
  778. * in detail for reasons of source code readability.
  779. */
  780. private interface AttributeSetter {
  781. void set(Project p, Object parent, String value)
  782. throws InvocationTargetException, IllegalAccessException,
  783. BuildException;
  784. }
  785. /**
  786. * Clears all storage used by this class, including the static cache of
  787. * helpers.
  788. *
  789. * @param event Ignored in this implementation.
  790. */
  791. public void buildFinished(BuildEvent event) {
  792. attributeTypes.clear();
  793. attributeSetters.clear();
  794. nestedTypes.clear();
  795. nestedCreators.clear();
  796. addText = null;
  797. helpers.clear();
  798. }
  799. /** Empty implementation to satisfy the BuildListener interface. */
  800. public void buildStarted(BuildEvent event) {}
  801. /** Empty implementation to satisfy the BuildListener interface. */
  802. public void targetStarted(BuildEvent event) {}
  803. /** Empty implementation to satisfy the BuildListener interface. */
  804. public void targetFinished(BuildEvent event) {}
  805. /** Empty implementation to satisfy the BuildListener interface. */
  806. public void taskStarted(BuildEvent event) {}
  807. /** Empty implementation to satisfy the BuildListener interface. */
  808. public void taskFinished(BuildEvent event) {}
  809. /** Empty implementation to satisfy the BuildListener interface. */
  810. public void messageLogged(BuildEvent event) {}
  811. }