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 26 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2000 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.DataType;
  57. import org.apache.tools.ant.types.EnumeratedAttribute;
  58. import java.lang.reflect.Method;
  59. import java.lang.reflect.InvocationTargetException;
  60. import java.lang.reflect.Constructor;
  61. import java.io.File;
  62. import java.util.Enumeration;
  63. import java.util.Hashtable;
  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. * holds the types of the attributes that could be set.
  74. */
  75. private Hashtable attributeTypes;
  76. /**
  77. * holds the attribute setter methods.
  78. */
  79. private Hashtable attributeSetters;
  80. /**
  81. * Holds the types of nested elements that could be created.
  82. */
  83. private Hashtable nestedTypes;
  84. /**
  85. * Holds methods to create nested elements.
  86. */
  87. private Hashtable nestedCreators;
  88. /**
  89. * Holds methods to store configured nested elements.
  90. */
  91. private Hashtable nestedStorers;
  92. /**
  93. * The method to add PCDATA stuff.
  94. */
  95. private Method addText = null;
  96. /**
  97. * The Class that's been introspected.
  98. */
  99. private Class bean;
  100. /**
  101. * instances we've already created
  102. */
  103. private static Hashtable helpers = new Hashtable();
  104. private IntrospectionHelper(final Class bean) {
  105. attributeTypes = new Hashtable();
  106. attributeSetters = new Hashtable();
  107. nestedTypes = new Hashtable();
  108. nestedCreators = new Hashtable();
  109. nestedStorers = new Hashtable();
  110. this.bean = bean;
  111. Method[] methods = bean.getMethods();
  112. for (int i=0; i<methods.length; i++) {
  113. final Method m = methods[i];
  114. final String name = m.getName();
  115. Class returnType = m.getReturnType();
  116. Class[] args = m.getParameterTypes();
  117. // not really user settable properties on tasks
  118. if (org.apache.tools.ant.Task.class.isAssignableFrom(bean)
  119. && args.length == 1 &&
  120. (
  121. (
  122. "setLocation".equals(name) && org.apache.tools.ant.Location.class.equals(args[0])
  123. ) || (
  124. "setTaskType".equals(name) && java.lang.String.class.equals(args[0])
  125. )
  126. )) {
  127. continue;
  128. }
  129. // hide addTask for TaskContainers
  130. if (org.apache.tools.ant.TaskContainer.class.isAssignableFrom(bean)
  131. && args.length == 1 && "addTask".equals(name)
  132. && org.apache.tools.ant.Task.class.equals(args[0])) {
  133. continue;
  134. }
  135. if ("addText".equals(name)
  136. && java.lang.Void.TYPE.equals(returnType)
  137. && args.length == 1
  138. && java.lang.String.class.equals(args[0])) {
  139. addText = methods[i];
  140. } else if (name.startsWith("set")
  141. && java.lang.Void.TYPE.equals(returnType)
  142. && args.length == 1
  143. && !args[0].isArray()) {
  144. String propName = getPropertyName(name, "set");
  145. AttributeSetter as = createAttributeSetter(m, args[0]);
  146. if (as != null) {
  147. attributeTypes.put(propName, args[0]);
  148. attributeSetters.put(propName, as);
  149. }
  150. } else if (name.startsWith("create")
  151. && !returnType.isArray()
  152. && !returnType.isPrimitive()
  153. && args.length == 0) {
  154. String propName = getPropertyName(name, "create");
  155. nestedTypes.put(propName, returnType);
  156. nestedCreators.put(propName, new NestedCreator() {
  157. public Object create(Object parent)
  158. throws InvocationTargetException,
  159. IllegalAccessException {
  160. return m.invoke(parent, new Object[] {});
  161. }
  162. });
  163. } else if (name.startsWith("addConfigured")
  164. && java.lang.Void.TYPE.equals(returnType)
  165. && args.length == 1
  166. && !java.lang.String.class.equals(args[0])
  167. && !args[0].isArray()
  168. && !args[0].isPrimitive()) {
  169. try {
  170. final Constructor c =
  171. args[0].getConstructor(new Class[] {});
  172. String propName = getPropertyName(name, "addConfigured");
  173. nestedTypes.put(propName, args[0]);
  174. nestedCreators.put(propName, new NestedCreator() {
  175. public Object create(Object parent)
  176. throws InvocationTargetException, IllegalAccessException, InstantiationException {
  177. Object o = c.newInstance(new Object[] {});
  178. return o;
  179. }
  180. });
  181. nestedStorers.put(propName, new NestedStorer() {
  182. public void store(Object parent, Object child)
  183. throws InvocationTargetException, IllegalAccessException, InstantiationException {
  184. m.invoke(parent, new Object[] {child});
  185. }
  186. });
  187. } catch (NoSuchMethodException nse) {
  188. }
  189. } else if (name.startsWith("add")
  190. && java.lang.Void.TYPE.equals(returnType)
  191. && args.length == 1
  192. && !java.lang.String.class.equals(args[0])
  193. && !args[0].isArray()
  194. && !args[0].isPrimitive()) {
  195. try {
  196. final Constructor c =
  197. args[0].getConstructor(new Class[] {});
  198. String propName = getPropertyName(name, "add");
  199. nestedTypes.put(propName, args[0]);
  200. nestedCreators.put(propName, new NestedCreator() {
  201. public Object create(Object parent)
  202. throws InvocationTargetException, IllegalAccessException, InstantiationException {
  203. Object o = c.newInstance(new Object[] {});
  204. m.invoke(parent, new Object[] {o});
  205. return o;
  206. }
  207. });
  208. } catch (NoSuchMethodException nse) {
  209. }
  210. }
  211. }
  212. }
  213. /**
  214. * Factory method for helper objects.
  215. */
  216. public static synchronized IntrospectionHelper getHelper(Class c) {
  217. IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c);
  218. if (ih == null) {
  219. ih = new IntrospectionHelper(c);
  220. helpers.put(c, ih);
  221. }
  222. return ih;
  223. }
  224. /**
  225. * Sets the named attribute.
  226. */
  227. public void setAttribute(Project p, Object element, String attributeName,
  228. String value)
  229. throws BuildException {
  230. AttributeSetter as = (AttributeSetter) attributeSetters.get(attributeName);
  231. if (as == null) {
  232. String msg = getElementName(p, element) +
  233. //String msg = "Class " + element.getClass().getName() +
  234. " doesn't support the \"" + attributeName + "\" attribute.";
  235. throw new BuildException(msg);
  236. }
  237. try {
  238. as.set(p, element, value);
  239. } catch (IllegalAccessException ie) {
  240. // impossible as getMethods should only return public methods
  241. throw new BuildException(ie);
  242. } catch (InvocationTargetException ite) {
  243. Throwable t = ite.getTargetException();
  244. if (t instanceof BuildException) {
  245. throw (BuildException) t;
  246. }
  247. throw new BuildException(t);
  248. }
  249. }
  250. /**
  251. * Adds PCDATA areas.
  252. */
  253. public void addText(Project project, Object element, String text) {
  254. if (addText == null) {
  255. String msg = getElementName(project, element) +
  256. //String msg = "Class " + element.getClass().getName() +
  257. " doesn't support nested text data.";
  258. throw new BuildException(msg);
  259. }
  260. try {
  261. addText.invoke(element, new String[] {text});
  262. } catch (IllegalAccessException ie) {
  263. // impossible as getMethods should only return public methods
  264. throw new BuildException(ie);
  265. } catch (InvocationTargetException ite) {
  266. Throwable t = ite.getTargetException();
  267. if (t instanceof BuildException) {
  268. throw (BuildException) t;
  269. }
  270. throw new BuildException(t);
  271. }
  272. }
  273. /**
  274. * Creates a named nested element.
  275. */
  276. public Object createElement(Project project, Object element, String elementName)
  277. throws BuildException {
  278. NestedCreator nc = (NestedCreator) nestedCreators.get(elementName);
  279. if (nc == null) {
  280. String msg = getElementName(project, element) +
  281. //String msg = "Class " + element.getClass().getName() +
  282. " doesn't support the nested \"" + elementName + "\" element.";
  283. throw new BuildException(msg);
  284. }
  285. try {
  286. Object nestedElement = nc.create(element);
  287. if (nestedElement instanceof DataType) {
  288. ((DataType)nestedElement).setProject(project);
  289. }
  290. return nestedElement;
  291. } catch (IllegalAccessException ie) {
  292. // impossible as getMethods should only return public methods
  293. throw new BuildException(ie);
  294. } catch (InstantiationException ine) {
  295. // impossible as getMethods should only return public methods
  296. throw new BuildException(ine);
  297. } catch (InvocationTargetException ite) {
  298. Throwable t = ite.getTargetException();
  299. if (t instanceof BuildException) {
  300. throw (BuildException) t;
  301. }
  302. throw new BuildException(t);
  303. }
  304. }
  305. /**
  306. * Creates a named nested element.
  307. */
  308. public void storeElement(Project project, Object element, Object child, String elementName)
  309. throws BuildException {
  310. if (elementName == null) {
  311. return;
  312. }
  313. NestedStorer ns = (NestedStorer)nestedStorers.get(elementName);
  314. if (ns == null) {
  315. return;
  316. }
  317. try {
  318. ns.store(element, child);
  319. } catch (IllegalAccessException ie) {
  320. // impossible as getMethods should only return public methods
  321. throw new BuildException(ie);
  322. } catch (InstantiationException ine) {
  323. // impossible as getMethods should only return public methods
  324. throw new BuildException(ine);
  325. } catch (InvocationTargetException ite) {
  326. Throwable t = ite.getTargetException();
  327. if (t instanceof BuildException) {
  328. throw (BuildException) t;
  329. }
  330. throw new BuildException(t);
  331. }
  332. }
  333. /**
  334. * returns the type of a named nested element.
  335. */
  336. public Class getElementType(String elementName)
  337. throws BuildException {
  338. Class nt = (Class) nestedTypes.get(elementName);
  339. if (nt == null) {
  340. String msg = "Class " + bean.getName() +
  341. " doesn't support the nested \"" + elementName + "\" element.";
  342. throw new BuildException(msg);
  343. }
  344. return nt;
  345. }
  346. /**
  347. * returns the type of a named attribute.
  348. */
  349. public Class getAttributeType(String attributeName)
  350. throws BuildException {
  351. Class at = (Class) attributeTypes.get(attributeName);
  352. if (at == null) {
  353. String msg = "Class " + bean.getName() +
  354. " doesn't support the \"" + attributeName + "\" attribute.";
  355. throw new BuildException(msg);
  356. }
  357. return at;
  358. }
  359. /**
  360. * Does the introspected class support PCDATA?
  361. */
  362. public boolean supportsCharacters() {
  363. return addText != null;
  364. }
  365. /**
  366. * Return all attribues supported by the introspected class.
  367. */
  368. public Enumeration getAttributes() {
  369. return attributeSetters.keys();
  370. }
  371. /**
  372. * Return all nested elements supported by the introspected class.
  373. */
  374. public Enumeration getNestedElements() {
  375. return nestedTypes.keys();
  376. }
  377. /**
  378. * Create a proper implementation of AttributeSetter for the given
  379. * attribute type.
  380. */
  381. private AttributeSetter createAttributeSetter(final Method m,
  382. final Class arg) {
  383. // simplest case - setAttribute expects String
  384. if (java.lang.String.class.equals(arg)) {
  385. return new AttributeSetter() {
  386. public void set(Project p, Object parent, String value)
  387. throws InvocationTargetException, IllegalAccessException {
  388. m.invoke(parent, new String[] {value});
  389. }
  390. };
  391. // now for the primitive types, use their wrappers
  392. } else if (java.lang.Character.class.equals(arg)
  393. || java.lang.Character.TYPE.equals(arg)) {
  394. return new AttributeSetter() {
  395. public void set(Project p, Object parent, String value)
  396. throws InvocationTargetException, IllegalAccessException {
  397. m.invoke(parent, new Character[] {new Character(value.charAt(0))});
  398. }
  399. };
  400. } else if (java.lang.Byte.TYPE.equals(arg)) {
  401. return new AttributeSetter() {
  402. public void set(Project p, Object parent, String value)
  403. throws InvocationTargetException, IllegalAccessException {
  404. m.invoke(parent, new Byte[] {new Byte(value)});
  405. }
  406. };
  407. } else if (java.lang.Short.TYPE.equals(arg)) {
  408. return new AttributeSetter() {
  409. public void set(Project p, Object parent, String value)
  410. throws InvocationTargetException, IllegalAccessException {
  411. m.invoke(parent, new Short[] {new Short(value)});
  412. }
  413. };
  414. } else if (java.lang.Integer.TYPE.equals(arg)) {
  415. return new AttributeSetter() {
  416. public void set(Project p, Object parent, String value)
  417. throws InvocationTargetException, IllegalAccessException {
  418. m.invoke(parent, new Integer[] {new Integer(value)});
  419. }
  420. };
  421. } else if (java.lang.Long.TYPE.equals(arg)) {
  422. return new AttributeSetter() {
  423. public void set(Project p, Object parent, String value)
  424. throws InvocationTargetException, IllegalAccessException {
  425. m.invoke(parent, new Long[] {new Long(value)});
  426. }
  427. };
  428. } else if (java.lang.Float.TYPE.equals(arg)) {
  429. return new AttributeSetter() {
  430. public void set(Project p, Object parent, String value)
  431. throws InvocationTargetException, IllegalAccessException {
  432. m.invoke(parent, new Float[] {new Float(value)});
  433. }
  434. };
  435. } else if (java.lang.Double.TYPE.equals(arg)) {
  436. return new AttributeSetter() {
  437. public void set(Project p, Object parent, String value)
  438. throws InvocationTargetException, IllegalAccessException {
  439. m.invoke(parent, new Double[] {new Double(value)});
  440. }
  441. };
  442. // boolean gets an extra treatment, because we have a nice method
  443. // in Project
  444. } else if (java.lang.Boolean.class.equals(arg)
  445. || java.lang.Boolean.TYPE.equals(arg)) {
  446. return new AttributeSetter() {
  447. public void set(Project p, Object parent, String value)
  448. throws InvocationTargetException, IllegalAccessException {
  449. m.invoke(parent,
  450. new Boolean[] {new Boolean(Project.toBoolean(value))});
  451. }
  452. };
  453. // Class doesn't have a String constructor but a decent factory method
  454. } else if (java.lang.Class.class.equals(arg)) {
  455. return new AttributeSetter() {
  456. public void set(Project p, Object parent, String value)
  457. throws InvocationTargetException, IllegalAccessException, BuildException {
  458. try {
  459. m.invoke(parent, new Class[] {Class.forName(value)});
  460. } catch (ClassNotFoundException ce) {
  461. throw new BuildException(ce);
  462. }
  463. }
  464. };
  465. // resolve relative paths through Project
  466. } else if (java.io.File.class.equals(arg)) {
  467. return new AttributeSetter() {
  468. public void set(Project p, Object parent, String value)
  469. throws InvocationTargetException, IllegalAccessException {
  470. m.invoke(parent, new File[] {p.resolveFile(value)});
  471. }
  472. };
  473. // resolve relative paths through Project
  474. } else if (org.apache.tools.ant.types.Path.class.equals(arg)) {
  475. return new AttributeSetter() {
  476. public void set(Project p, Object parent, String value)
  477. throws InvocationTargetException, IllegalAccessException {
  478. m.invoke(parent, new Path[] {new Path(p, value)});
  479. }
  480. };
  481. // EnumeratedAttributes have their own helper class
  482. } else if (org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom(arg)) {
  483. return new AttributeSetter() {
  484. public void set(Project p, Object parent, String value)
  485. throws InvocationTargetException, IllegalAccessException, BuildException {
  486. try {
  487. org.apache.tools.ant.types.EnumeratedAttribute ea = (org.apache.tools.ant.types.EnumeratedAttribute)arg.newInstance();
  488. ea.setValue(value);
  489. m.invoke(parent, new EnumeratedAttribute[] {ea});
  490. } catch (InstantiationException ie) {
  491. throw new BuildException(ie);
  492. }
  493. }
  494. };
  495. // worst case. look for a public String constructor and use it
  496. } else {
  497. try {
  498. final Constructor c =
  499. arg.getConstructor(new Class[] {java.lang.String.class});
  500. return new AttributeSetter() {
  501. public void set(Project p, Object parent,
  502. String value)
  503. throws InvocationTargetException, IllegalAccessException, BuildException {
  504. try {
  505. Object attribute = c.newInstance(new String[] {value});
  506. if (attribute instanceof DataType) {
  507. ((DataType)attribute).setProject(p);
  508. }
  509. m.invoke(parent, new Object[] {attribute});
  510. } catch (InstantiationException ie) {
  511. throw new BuildException(ie);
  512. }
  513. }
  514. };
  515. } catch (NoSuchMethodException nme) {
  516. }
  517. }
  518. return null;
  519. }
  520. protected String getElementName(Project project, Object element)
  521. {
  522. Hashtable elements = project.getTaskDefinitions();
  523. String typeName = "task";
  524. if (!elements.contains( element.getClass() ))
  525. {
  526. elements = project.getDataTypeDefinitions();
  527. typeName = "data type";
  528. if (!elements.contains( element.getClass() ))
  529. {
  530. elements = null;
  531. }
  532. }
  533. if (elements != null)
  534. {
  535. Enumeration e = elements.keys();
  536. while (e.hasMoreElements())
  537. {
  538. String elementName = (String) e.nextElement();
  539. Class elementClass = (Class) elements.get( elementName );
  540. if ( element.getClass().equals( elementClass ) )
  541. {
  542. return "The <" + elementName + "> " + typeName;
  543. }
  544. }
  545. }
  546. return "Class " + element.getClass().getName();
  547. }
  548. /**
  549. * extract the name of a property from a method name - subtracting
  550. * a given prefix.
  551. */
  552. private String getPropertyName(String methodName, String prefix) {
  553. int start = prefix.length();
  554. return methodName.substring(start).toLowerCase();
  555. }
  556. private interface NestedCreator {
  557. Object create(Object parent)
  558. throws InvocationTargetException, IllegalAccessException, InstantiationException;
  559. }
  560. private interface NestedStorer {
  561. void store(Object parent, Object child)
  562. throws InvocationTargetException, IllegalAccessException, InstantiationException;
  563. }
  564. private interface AttributeSetter {
  565. void set(Project p, Object parent, String value)
  566. throws InvocationTargetException, IllegalAccessException,
  567. BuildException;
  568. }
  569. public void buildStarted(BuildEvent event) {}
  570. public void buildFinished(BuildEvent event) {
  571. attributeTypes.clear();
  572. attributeSetters.clear();
  573. nestedTypes.clear();
  574. nestedCreators.clear();
  575. addText = null;
  576. helpers.clear();
  577. }
  578. public void targetStarted(BuildEvent event) {}
  579. public void targetFinished(BuildEvent event) {}
  580. public void taskStarted(BuildEvent event) {}
  581. public void taskFinished(BuildEvent event) {}
  582. public void messageLogged(BuildEvent event) {}
  583. }