1. /*
  2. * Copyright 2001-2004 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. package org.apache.commons.beanutils;
  17. import java.beans.BeanInfo;
  18. import java.beans.IndexedPropertyDescriptor;
  19. import java.beans.IntrospectionException;
  20. import java.beans.Introspector;
  21. import java.beans.PropertyDescriptor;
  22. import java.lang.reflect.Array;
  23. import java.lang.reflect.InvocationTargetException;
  24. import java.lang.reflect.Method;
  25. import java.util.HashMap;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import java.util.Map;
  29. import org.apache.commons.collections.FastHashMap;
  30. import org.apache.commons.logging.Log;
  31. import org.apache.commons.logging.LogFactory;
  32. /**
  33. * Utility methods for using Java Reflection APIs to facilitate generic
  34. * property getter and setter operations on Java objects. Much of this
  35. * code was originally included in <code>BeanUtils</code>, but has been
  36. * separated because of the volume of code involved.
  37. * <p>
  38. * In general, the objects that are examined and modified using these
  39. * methods are expected to conform to the property getter and setter method
  40. * naming conventions described in the JavaBeans Specification (Version 1.0.1).
  41. * No data type conversions are performed, and there are no usage of any
  42. * <code>PropertyEditor</code> classes that have been registered, although
  43. * a convenient way to access the registered classes themselves is included.
  44. * <p>
  45. * For the purposes of this class, five formats for referencing a particular
  46. * property value of a bean are defined, with the layout of an identifying
  47. * String in parentheses:
  48. * <ul>
  49. * <li><strong>Simple (<code>name</code>)</strong> - The specified
  50. * <code>name</code> identifies an individual property of a particular
  51. * JavaBean. The name of the actual getter or setter method to be used
  52. * is determined using standard JavaBeans instrospection, so that (unless
  53. * overridden by a <code>BeanInfo</code> class, a property named "xyz"
  54. * will have a getter method named <code>getXyz()</code> or (for boolean
  55. * properties only) <code>isXyz()</code>, and a setter method named
  56. * <code>setXyz()</code>.</li>
  57. * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first
  58. * name element is used to select a property getter, as for simple
  59. * references above. The object returned for this property is then
  60. * consulted, using the same approach, for a property getter for a
  61. * property named <code>name2</code>, and so on. The property value that
  62. * is ultimately retrieved or modified is the one identified by the
  63. * last name element.</li>
  64. * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
  65. * property value is assumed to be an array, or this JavaBean is assumed
  66. * to have indexed property getter and setter methods. The appropriate
  67. * (zero-relative) entry in the array is selected. <code>List</code>
  68. * objects are now also supported for read/write. You simply need to define
  69. * a getter that returns the <code>List</code></li>
  70. * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean
  71. * is assumed to have an property getter and setter methods with an
  72. * additional attribute of type <code>java.lang.String</code>.</li>
  73. * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
  74. * Combining mapped, nested, and indexed references is also
  75. * supported.</li>
  76. * </ul>
  77. *
  78. * @author Craig R. McClanahan
  79. * @author Ralph Schaer
  80. * @author Chris Audley
  81. * @author Rey François
  82. * @author Gregor Raıman
  83. * @author Jan Sorensen
  84. * @author Scott Sanders
  85. * @version $Revision: 1.14.2.1 $ $Date: 2004/07/27 21:31:00 $
  86. * @see PropertyUtils
  87. * @since 1.7
  88. */
  89. public class PropertyUtilsBean {
  90. // --------------------------------------------------------- Class Methods
  91. protected static PropertyUtilsBean getInstance() {
  92. return BeanUtilsBean.getInstance().getPropertyUtils();
  93. }
  94. // --------------------------------------------------------- Variables
  95. /**
  96. * The cache of PropertyDescriptor arrays for beans we have already
  97. * introspected, keyed by the java.lang.Class of this object.
  98. */
  99. private FastHashMap descriptorsCache = null;
  100. private FastHashMap mappedDescriptorsCache = null;
  101. /** Log instance */
  102. private Log log = LogFactory.getLog(PropertyUtils.class);
  103. // ---------------------------------------------------------- Constructors
  104. /** Base constructor */
  105. public PropertyUtilsBean() {
  106. descriptorsCache = new FastHashMap();
  107. descriptorsCache.setFast(true);
  108. mappedDescriptorsCache = new FastHashMap();
  109. mappedDescriptorsCache.setFast(true);
  110. }
  111. // --------------------------------------------------------- Public Methods
  112. /**
  113. * Clear any cached property descriptors information for all classes
  114. * loaded by any class loaders. This is useful in cases where class
  115. * loaders are thrown away to implement class reloading.
  116. */
  117. public void clearDescriptors() {
  118. descriptorsCache.clear();
  119. mappedDescriptorsCache.clear();
  120. Introspector.flushCaches();
  121. }
  122. /**
  123. * <p>Copy property values from the "origin" bean to the "destination" bean
  124. * for all cases where the property names are the same (even though the
  125. * actual getter and setter methods might have been customized via
  126. * <code>BeanInfo</code> classes). No conversions are performed on the
  127. * actual property values -- it is assumed that the values retrieved from
  128. * the origin bean are assignment-compatible with the types expected by
  129. * the destination bean.</p>
  130. *
  131. * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
  132. * to contain String-valued <strong>simple</strong> property names as the keys, pointing
  133. * at the corresponding property values that will be set in the destination
  134. * bean.<strong>Note</strong> that this method is intended to perform
  135. * a "shallow copy" of the properties and so complex properties
  136. * (for example, nested ones) will not be copied.</p>
  137. *
  138. * @param dest Destination bean whose properties are modified
  139. * @param orig Origin bean whose properties are retrieved
  140. *
  141. * @exception IllegalAccessException if the caller does not have
  142. * access to the property accessor method
  143. * @exception IllegalArgumentException if the <code>dest</code> or
  144. * <code>orig</code> argument is null
  145. * @exception InvocationTargetException if the property accessor method
  146. * throws an exception
  147. * @exception NoSuchMethodException if an accessor method for this
  148. * propety cannot be found
  149. */
  150. public void copyProperties(Object dest, Object orig)
  151. throws IllegalAccessException, InvocationTargetException,
  152. NoSuchMethodException {
  153. if (dest == null) {
  154. throw new IllegalArgumentException
  155. ("No destination bean specified");
  156. }
  157. if (orig == null) {
  158. throw new IllegalArgumentException("No origin bean specified");
  159. }
  160. if (orig instanceof DynaBean) {
  161. DynaProperty origDescriptors[] =
  162. ((DynaBean) orig).getDynaClass().getDynaProperties();
  163. for (int i = 0; i < origDescriptors.length; i++) {
  164. String name = origDescriptors[i].getName();
  165. if (dest instanceof DynaBean) {
  166. if (isWriteable(dest, name)) {
  167. Object value = ((DynaBean) orig).get(name);
  168. ((DynaBean) dest).set(name, value);
  169. }
  170. } else /* if (dest is a standard JavaBean) */ {
  171. if (isWriteable(dest, name)) {
  172. Object value = ((DynaBean) orig).get(name);
  173. setSimpleProperty(dest, name, value);
  174. }
  175. }
  176. }
  177. } else if (orig instanceof Map) {
  178. Iterator names = ((Map) orig).keySet().iterator();
  179. while (names.hasNext()) {
  180. String name = (String) names.next();
  181. if (dest instanceof DynaBean) {
  182. if (isWriteable(dest, name)) {
  183. Object value = ((Map) orig).get(name);
  184. ((DynaBean) dest).set(name, value);
  185. }
  186. } else /* if (dest is a standard JavaBean) */ {
  187. if (isWriteable(dest, name)) {
  188. Object value = ((Map) orig).get(name);
  189. setSimpleProperty(dest, name, value);
  190. }
  191. }
  192. }
  193. } else /* if (orig is a standard JavaBean) */ {
  194. PropertyDescriptor origDescriptors[] =
  195. getPropertyDescriptors(orig);
  196. for (int i = 0; i < origDescriptors.length; i++) {
  197. String name = origDescriptors[i].getName();
  198. if (isReadable(orig, name)) {
  199. if (dest instanceof DynaBean) {
  200. if (isWriteable(dest, name)) {
  201. Object value = getSimpleProperty(orig, name);
  202. ((DynaBean) dest).set(name, value);
  203. }
  204. } else /* if (dest is a standard JavaBean) */ {
  205. if (isWriteable(dest, name)) {
  206. Object value = getSimpleProperty(orig, name);
  207. setSimpleProperty(dest, name, value);
  208. }
  209. }
  210. }
  211. }
  212. }
  213. }
  214. /**
  215. * <p>Return the entire set of properties for which the specified bean
  216. * provides a read method. This map contains the unconverted property
  217. * values for all properties for which a read method is provided
  218. * (i.e. where the <code>getReadMethod()</code> returns non-null).</p>
  219. *
  220. * <p><strong>FIXME</strong> - Does not account for mapped properties.</p>
  221. *
  222. * @param bean Bean whose properties are to be extracted
  223. *
  224. * @exception IllegalAccessException if the caller does not have
  225. * access to the property accessor method
  226. * @exception IllegalArgumentException if <code>bean</code> is null
  227. * @exception InvocationTargetException if the property accessor method
  228. * throws an exception
  229. * @exception NoSuchMethodException if an accessor method for this
  230. * propety cannot be found
  231. */
  232. public Map describe(Object bean)
  233. throws IllegalAccessException, InvocationTargetException,
  234. NoSuchMethodException {
  235. if (bean == null) {
  236. throw new IllegalArgumentException("No bean specified");
  237. }
  238. Map description = new HashMap();
  239. if (bean instanceof DynaBean) {
  240. DynaProperty descriptors[] =
  241. ((DynaBean) bean).getDynaClass().getDynaProperties();
  242. for (int i = 0; i < descriptors.length; i++) {
  243. String name = descriptors[i].getName();
  244. description.put(name, getProperty(bean, name));
  245. }
  246. } else {
  247. PropertyDescriptor descriptors[] =
  248. getPropertyDescriptors(bean);
  249. for (int i = 0; i < descriptors.length; i++) {
  250. String name = descriptors[i].getName();
  251. if (descriptors[i].getReadMethod() != null)
  252. description.put(name, getProperty(bean, name));
  253. }
  254. }
  255. return (description);
  256. }
  257. /**
  258. * Return the value of the specified indexed property of the specified
  259. * bean, with no type conversions. The zero-relative index of the
  260. * required value must be included (in square brackets) as a suffix to
  261. * the property name, or <code>IllegalArgumentException</code> will be
  262. * thrown. In addition to supporting the JavaBeans specification, this
  263. * method has been extended to support <code>List</code> objects as well.
  264. *
  265. * @param bean Bean whose property is to be extracted
  266. * @param name <code>propertyname[index]</code> of the property value
  267. * to be extracted
  268. *
  269. * @exception ArrayIndexOutOfBoundsException if the specified index
  270. * is outside the valid range for the underlying array
  271. * @exception IllegalAccessException if the caller does not have
  272. * access to the property accessor method
  273. * @exception IllegalArgumentException if <code>bean</code> or
  274. * <code>name</code> is null
  275. * @exception InvocationTargetException if the property accessor method
  276. * throws an exception
  277. * @exception NoSuchMethodException if an accessor method for this
  278. * propety cannot be found
  279. */
  280. public Object getIndexedProperty(Object bean, String name)
  281. throws IllegalAccessException, InvocationTargetException,
  282. NoSuchMethodException {
  283. if (bean == null) {
  284. throw new IllegalArgumentException("No bean specified");
  285. }
  286. if (name == null) {
  287. throw new IllegalArgumentException("No name specified");
  288. }
  289. // Identify the index of the requested individual property
  290. int delim = name.indexOf(PropertyUtils.INDEXED_DELIM);
  291. int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2);
  292. if ((delim < 0) || (delim2 <= delim)) {
  293. throw new IllegalArgumentException("Invalid indexed property '" +
  294. name + "'");
  295. }
  296. int index = -1;
  297. try {
  298. String subscript = name.substring(delim + 1, delim2);
  299. index = Integer.parseInt(subscript);
  300. } catch (NumberFormatException e) {
  301. throw new IllegalArgumentException("Invalid indexed property '" +
  302. name + "'");
  303. }
  304. name = name.substring(0, delim);
  305. // Request the specified indexed property value
  306. return (getIndexedProperty(bean, name, index));
  307. }
  308. /**
  309. * Return the value of the specified indexed property of the specified
  310. * bean, with no type conversions. In addition to supporting the JavaBeans
  311. * specification, this method has been extended to support
  312. * <code>List</code> objects as well.
  313. *
  314. * @param bean Bean whose property is to be extracted
  315. * @param name Simple property name of the property value to be extracted
  316. * @param index Index of the property value to be extracted
  317. *
  318. * @exception ArrayIndexOutOfBoundsException if the specified index
  319. * is outside the valid range for the underlying array
  320. * @exception IllegalAccessException if the caller does not have
  321. * access to the property accessor method
  322. * @exception IllegalArgumentException if <code>bean</code> or
  323. * <code>name</code> is null
  324. * @exception InvocationTargetException if the property accessor method
  325. * throws an exception
  326. * @exception NoSuchMethodException if an accessor method for this
  327. * propety cannot be found
  328. */
  329. public Object getIndexedProperty(Object bean,
  330. String name, int index)
  331. throws IllegalAccessException, InvocationTargetException,
  332. NoSuchMethodException {
  333. if (bean == null) {
  334. throw new IllegalArgumentException("No bean specified");
  335. }
  336. if (name == null) {
  337. throw new IllegalArgumentException("No name specified");
  338. }
  339. // Handle DynaBean instances specially
  340. if (bean instanceof DynaBean) {
  341. DynaProperty descriptor =
  342. ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  343. if (descriptor == null) {
  344. throw new NoSuchMethodException("Unknown property '" +
  345. name + "'");
  346. }
  347. return (((DynaBean) bean).get(name, index));
  348. }
  349. // Retrieve the property descriptor for the specified property
  350. PropertyDescriptor descriptor =
  351. getPropertyDescriptor(bean, name);
  352. if (descriptor == null) {
  353. throw new NoSuchMethodException("Unknown property '" +
  354. name + "'");
  355. }
  356. // Call the indexed getter method if there is one
  357. if (descriptor instanceof IndexedPropertyDescriptor) {
  358. Method readMethod = ((IndexedPropertyDescriptor) descriptor).
  359. getIndexedReadMethod();
  360. if (readMethod != null) {
  361. Object subscript[] = new Object[1];
  362. subscript[0] = new Integer(index);
  363. try {
  364. return (invokeMethod(readMethod,bean, subscript));
  365. } catch (InvocationTargetException e) {
  366. if (e.getTargetException() instanceof
  367. ArrayIndexOutOfBoundsException) {
  368. throw (ArrayIndexOutOfBoundsException)
  369. e.getTargetException();
  370. } else {
  371. throw e;
  372. }
  373. }
  374. }
  375. }
  376. // Otherwise, the underlying property must be an array
  377. Method readMethod = getReadMethod(descriptor);
  378. if (readMethod == null) {
  379. throw new NoSuchMethodException("Property '" + name +
  380. "' has no getter method");
  381. }
  382. // Call the property getter and return the value
  383. Object value = invokeMethod(readMethod, bean, new Object[0]);
  384. if (!value.getClass().isArray()) {
  385. if (!(value instanceof java.util.List)) {
  386. throw new IllegalArgumentException("Property '" + name
  387. + "' is not indexed");
  388. } else {
  389. //get the List's value
  390. return ((java.util.List) value).get(index);
  391. }
  392. } else {
  393. //get the array's value
  394. return (Array.get(value, index));
  395. }
  396. }
  397. /**
  398. * Return the value of the specified mapped property of the
  399. * specified bean, with no type conversions. The key of the
  400. * required value must be included (in brackets) as a suffix to
  401. * the property name, or <code>IllegalArgumentException</code> will be
  402. * thrown.
  403. *
  404. * @param bean Bean whose property is to be extracted
  405. * @param name <code>propertyname(key)</code> of the property value
  406. * to be extracted
  407. *
  408. * @exception IllegalAccessException if the caller does not have
  409. * access to the property accessor method
  410. * @exception InvocationTargetException if the property accessor method
  411. * throws an exception
  412. * @exception NoSuchMethodException if an accessor method for this
  413. * propety cannot be found
  414. */
  415. public Object getMappedProperty(Object bean, String name)
  416. throws IllegalAccessException, InvocationTargetException,
  417. NoSuchMethodException {
  418. if (bean == null) {
  419. throw new IllegalArgumentException("No bean specified");
  420. }
  421. if (name == null) {
  422. throw new IllegalArgumentException("No name specified");
  423. }
  424. // Identify the index of the requested individual property
  425. int delim = name.indexOf(PropertyUtils.MAPPED_DELIM);
  426. int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
  427. if ((delim < 0) || (delim2 <= delim)) {
  428. throw new IllegalArgumentException
  429. ("Invalid mapped property '" + name + "'");
  430. }
  431. // Isolate the name and the key
  432. String key = name.substring(delim + 1, delim2);
  433. name = name.substring(0, delim);
  434. // Request the specified indexed property value
  435. return (getMappedProperty(bean, name, key));
  436. }
  437. /**
  438. * Return the value of the specified mapped property of the specified
  439. * bean, with no type conversions.
  440. *
  441. * @param bean Bean whose property is to be extracted
  442. * @param name Mapped property name of the property value to be extracted
  443. * @param key Key of the property value to be extracted
  444. *
  445. * @exception IllegalAccessException if the caller does not have
  446. * access to the property accessor method
  447. * @exception InvocationTargetException if the property accessor method
  448. * throws an exception
  449. * @exception NoSuchMethodException if an accessor method for this
  450. * propety cannot be found
  451. */
  452. public Object getMappedProperty(Object bean,
  453. String name, String key)
  454. throws IllegalAccessException, InvocationTargetException,
  455. NoSuchMethodException {
  456. if (bean == null) {
  457. throw new IllegalArgumentException("No bean specified");
  458. }
  459. if (name == null) {
  460. throw new IllegalArgumentException("No name specified");
  461. }
  462. if (key == null) {
  463. throw new IllegalArgumentException("No key specified");
  464. }
  465. // Handle DynaBean instances specially
  466. if (bean instanceof DynaBean) {
  467. DynaProperty descriptor =
  468. ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  469. if (descriptor == null) {
  470. throw new NoSuchMethodException("Unknown property '" +
  471. name + "'");
  472. }
  473. return (((DynaBean) bean).get(name, key));
  474. }
  475. Object result = null;
  476. // Retrieve the property descriptor for the specified property
  477. PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  478. if (descriptor == null) {
  479. throw new NoSuchMethodException("Unknown property '" +
  480. name + "'");
  481. }
  482. if (descriptor instanceof MappedPropertyDescriptor) {
  483. // Call the keyed getter method if there is one
  484. Method readMethod = ((MappedPropertyDescriptor) descriptor).
  485. getMappedReadMethod();
  486. if (readMethod != null) {
  487. Object keyArray[] = new Object[1];
  488. keyArray[0] = key;
  489. result = invokeMethod(readMethod, bean, keyArray);
  490. } else {
  491. throw new NoSuchMethodException("Property '" + name +
  492. "' has no mapped getter method");
  493. }
  494. } else {
  495. /* means that the result has to be retrieved from a map */
  496. Method readMethod = descriptor.getReadMethod();
  497. if (readMethod != null) {
  498. Object invokeResult = invokeMethod(readMethod, bean, new Object[0]);
  499. /* test and fetch from the map */
  500. if (invokeResult instanceof java.util.Map) {
  501. result = ((java.util.Map)invokeResult).get(key);
  502. }
  503. } else {
  504. throw new NoSuchMethodException("Property '" + name +
  505. "' has no mapped getter method");
  506. }
  507. }
  508. return result;
  509. }
  510. /**
  511. * <p>Return the mapped property descriptors for this bean class.</p>
  512. *
  513. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  514. *
  515. * @param beanClass Bean class to be introspected
  516. * @deprecated This method should not be exposed
  517. */
  518. public FastHashMap getMappedPropertyDescriptors(Class beanClass) {
  519. if (beanClass == null) {
  520. return null;
  521. }
  522. // Look up any cached descriptors for this bean class
  523. return (FastHashMap) mappedDescriptorsCache.get(beanClass);
  524. }
  525. /**
  526. * <p>Return the mapped property descriptors for this bean.</p>
  527. *
  528. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  529. *
  530. * @param bean Bean to be introspected
  531. * @deprecated This method should not be exposed
  532. */
  533. public FastHashMap getMappedPropertyDescriptors(Object bean) {
  534. if (bean == null) {
  535. return null;
  536. }
  537. return (getMappedPropertyDescriptors(bean.getClass()));
  538. }
  539. /**
  540. * Return the value of the (possibly nested) property of the specified
  541. * name, for the specified bean, with no type conversions.
  542. *
  543. * @param bean Bean whose property is to be extracted
  544. * @param name Possibly nested name of the property to be extracted
  545. *
  546. * @exception IllegalAccessException if the caller does not have
  547. * access to the property accessor method
  548. * @exception IllegalArgumentException if <code>bean</code> or
  549. * <code>name</code> is null
  550. * @exception NestedNullException if a nested reference to a
  551. * property returns null
  552. * @exception InvocationTargetException
  553. * if the property accessor method throws an exception
  554. * @exception NoSuchMethodException if an accessor method for this
  555. * propety cannot be found
  556. */
  557. public Object getNestedProperty(Object bean, String name)
  558. throws IllegalAccessException, InvocationTargetException,
  559. NoSuchMethodException {
  560. if (bean == null) {
  561. throw new IllegalArgumentException("No bean specified");
  562. }
  563. if (name == null) {
  564. throw new IllegalArgumentException("No name specified");
  565. }
  566. int indexOfINDEXED_DELIM = -1;
  567. int indexOfMAPPED_DELIM = -1;
  568. int indexOfMAPPED_DELIM2 = -1;
  569. int indexOfNESTED_DELIM = -1;
  570. while (true) {
  571. indexOfNESTED_DELIM = name.indexOf(PropertyUtils.NESTED_DELIM);
  572. indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
  573. indexOfMAPPED_DELIM2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
  574. if (indexOfMAPPED_DELIM2 >= 0 && indexOfMAPPED_DELIM >=0 &&
  575. (indexOfNESTED_DELIM < 0 || indexOfNESTED_DELIM > indexOfMAPPED_DELIM)) {
  576. indexOfNESTED_DELIM =
  577. name.indexOf(PropertyUtils.NESTED_DELIM, indexOfMAPPED_DELIM2);
  578. } else {
  579. indexOfNESTED_DELIM = name.indexOf(PropertyUtils.NESTED_DELIM);
  580. }
  581. if (indexOfNESTED_DELIM < 0) {
  582. break;
  583. }
  584. String next = name.substring(0, indexOfNESTED_DELIM);
  585. indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
  586. indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
  587. if (bean instanceof Map) {
  588. bean = ((Map) bean).get(next);
  589. } else if (indexOfMAPPED_DELIM >= 0) {
  590. bean = getMappedProperty(bean, next);
  591. } else if (indexOfINDEXED_DELIM >= 0) {
  592. bean = getIndexedProperty(bean, next);
  593. } else {
  594. bean = getSimpleProperty(bean, next);
  595. }
  596. if (bean == null) {
  597. throw new NestedNullException
  598. ("Null property value for '" +
  599. name.substring(0, indexOfNESTED_DELIM) + "'");
  600. }
  601. name = name.substring(indexOfNESTED_DELIM + 1);
  602. }
  603. indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
  604. indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
  605. if (bean instanceof Map) {
  606. bean = ((Map) bean).get(name);
  607. } else if (indexOfMAPPED_DELIM >= 0) {
  608. bean = getMappedProperty(bean, name);
  609. } else if (indexOfINDEXED_DELIM >= 0) {
  610. bean = getIndexedProperty(bean, name);
  611. } else {
  612. bean = getSimpleProperty(bean, name);
  613. }
  614. return bean;
  615. }
  616. /**
  617. * Return the value of the specified property of the specified bean,
  618. * no matter which property reference format is used, with no
  619. * type conversions.
  620. *
  621. * @param bean Bean whose property is to be extracted
  622. * @param name Possibly indexed and/or nested name of the property
  623. * to be extracted
  624. *
  625. * @exception IllegalAccessException if the caller does not have
  626. * access to the property accessor method
  627. * @exception IllegalArgumentException if <code>bean</code> or
  628. * <code>name</code> is null
  629. * @exception InvocationTargetException if the property accessor method
  630. * throws an exception
  631. * @exception NoSuchMethodException if an accessor method for this
  632. * propety cannot be found
  633. */
  634. public Object getProperty(Object bean, String name)
  635. throws IllegalAccessException, InvocationTargetException,
  636. NoSuchMethodException {
  637. return (getNestedProperty(bean, name));
  638. }
  639. /**
  640. * <p>Retrieve the property descriptor for the specified property of the
  641. * specified bean, or return <code>null</code> if there is no such
  642. * descriptor. This method resolves indexed and nested property
  643. * references in the same manner as other methods in this class, except
  644. * that if the last (or only) name element is indexed, the descriptor
  645. * for the last resolved property itself is returned.</p>
  646. *
  647. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  648. *
  649. * @param bean Bean for which a property descriptor is requested
  650. * @param name Possibly indexed and/or nested name of the property for
  651. * which a property descriptor is requested
  652. *
  653. * @exception IllegalAccessException if the caller does not have
  654. * access to the property accessor method
  655. * @exception IllegalArgumentException if <code>bean</code> or
  656. * <code>name</code> is null
  657. * @exception IllegalArgumentException if a nested reference to a
  658. * property returns null
  659. * @exception InvocationTargetException if the property accessor method
  660. * throws an exception
  661. * @exception NoSuchMethodException if an accessor method for this
  662. * propety cannot be found
  663. */
  664. public PropertyDescriptor getPropertyDescriptor(Object bean,
  665. String name)
  666. throws IllegalAccessException, InvocationTargetException,
  667. NoSuchMethodException {
  668. if (bean == null) {
  669. throw new IllegalArgumentException("No bean specified");
  670. }
  671. if (name == null) {
  672. throw new IllegalArgumentException("No name specified");
  673. }
  674. // Resolve nested references
  675. while (true) {
  676. int period = findNextNestedIndex(name);
  677. if (period < 0) {
  678. break;
  679. }
  680. String next = name.substring(0, period);
  681. int indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
  682. int indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
  683. if (indexOfMAPPED_DELIM >= 0 &&
  684. (indexOfINDEXED_DELIM < 0 ||
  685. indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) {
  686. bean = getMappedProperty(bean, next);
  687. } else {
  688. if (indexOfINDEXED_DELIM >= 0) {
  689. bean = getIndexedProperty(bean, next);
  690. } else {
  691. bean = getSimpleProperty(bean, next);
  692. }
  693. }
  694. if (bean == null) {
  695. throw new IllegalArgumentException
  696. ("Null property value for '" +
  697. name.substring(0, period) + "'");
  698. }
  699. name = name.substring(period + 1);
  700. }
  701. // Remove any subscript from the final name value
  702. int left = name.indexOf(PropertyUtils.INDEXED_DELIM);
  703. if (left >= 0) {
  704. name = name.substring(0, left);
  705. }
  706. left = name.indexOf(PropertyUtils.MAPPED_DELIM);
  707. if (left >= 0) {
  708. name = name.substring(0, left);
  709. }
  710. // Look up and return this property from our cache
  711. // creating and adding it to the cache if not found.
  712. if ((bean == null) || (name == null)) {
  713. return (null);
  714. }
  715. PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
  716. if (descriptors != null) {
  717. for (int i = 0; i < descriptors.length; i++) {
  718. if (name.equals(descriptors[i].getName()))
  719. return (descriptors[i]);
  720. }
  721. }
  722. PropertyDescriptor result = null;
  723. FastHashMap mappedDescriptors =
  724. getMappedPropertyDescriptors(bean);
  725. if (mappedDescriptors == null) {
  726. mappedDescriptors = new FastHashMap();
  727. mappedDescriptors.setFast(true);
  728. mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
  729. }
  730. result = (PropertyDescriptor) mappedDescriptors.get(name);
  731. if (result == null) {
  732. // not found, try to create it
  733. try {
  734. result =
  735. new MappedPropertyDescriptor(name, bean.getClass());
  736. } catch (IntrospectionException ie) {
  737. }
  738. if (result != null) {
  739. mappedDescriptors.put(name, result);
  740. }
  741. }
  742. return result;
  743. }
  744. private int findNextNestedIndex(String expression)
  745. {
  746. // walk back from the end to the start
  747. // and find the first index that
  748. int bracketCount = 0;
  749. for (int i=0, size=expression.length(); i<size ; i++) {
  750. char at = expression.charAt(i);
  751. switch (at) {
  752. case PropertyUtils.NESTED_DELIM:
  753. if (bracketCount < 1) {
  754. return i;
  755. }
  756. break;
  757. case PropertyUtils.MAPPED_DELIM:
  758. case PropertyUtils.INDEXED_DELIM:
  759. // not bothered which
  760. ++bracketCount;
  761. break;
  762. case PropertyUtils.MAPPED_DELIM2:
  763. case PropertyUtils.INDEXED_DELIM2:
  764. // not bothered which
  765. --bracketCount;
  766. break;
  767. }
  768. }
  769. // can't find any
  770. return -1;
  771. }
  772. /**
  773. * <p>Retrieve the property descriptors for the specified class,
  774. * introspecting and caching them the first time a particular bean class
  775. * is encountered.</p>
  776. *
  777. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  778. *
  779. * @param beanClass Bean class for which property descriptors are requested
  780. *
  781. * @exception IllegalArgumentException if <code>beanClass</code> is null
  782. */
  783. public PropertyDescriptor[]
  784. getPropertyDescriptors(Class beanClass) {
  785. if (beanClass == null) {
  786. throw new IllegalArgumentException("No bean class specified");
  787. }
  788. // Look up any cached descriptors for this bean class
  789. PropertyDescriptor descriptors[] = null;
  790. descriptors =
  791. (PropertyDescriptor[]) descriptorsCache.get(beanClass);
  792. if (descriptors != null) {
  793. return (descriptors);
  794. }
  795. // Introspect the bean and cache the generated descriptors
  796. BeanInfo beanInfo = null;
  797. try {
  798. beanInfo = Introspector.getBeanInfo(beanClass);
  799. } catch (IntrospectionException e) {
  800. return (new PropertyDescriptor[0]);
  801. }
  802. descriptors = beanInfo.getPropertyDescriptors();
  803. if (descriptors == null) {
  804. descriptors = new PropertyDescriptor[0];
  805. }
  806. descriptorsCache.put(beanClass, descriptors);
  807. return (descriptors);
  808. }
  809. /**
  810. * <p>Retrieve the property descriptors for the specified bean,
  811. * introspecting and caching them the first time a particular bean class
  812. * is encountered.</p>
  813. *
  814. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  815. *
  816. * @param bean Bean for which property descriptors are requested
  817. *
  818. * @exception IllegalArgumentException if <code>bean</code> is null
  819. */
  820. public PropertyDescriptor[] getPropertyDescriptors(Object bean) {
  821. if (bean == null) {
  822. throw new IllegalArgumentException("No bean specified");
  823. }
  824. return (getPropertyDescriptors(bean.getClass()));
  825. }
  826. /**
  827. * <p>Return the Java Class repesenting the property editor class that has
  828. * been registered for this property (if any). This method follows the
  829. * same name resolution rules used by <code>getPropertyDescriptor()</code>,
  830. * so if the last element of a name reference is indexed, the property
  831. * editor for the underlying property's class is returned.</p>
  832. *
  833. * <p>Note that <code>null</code> will be returned if there is no property,
  834. * or if there is no registered property editor class. Because this
  835. * return value is ambiguous, you should determine the existence of the
  836. * property itself by other means.</p>
  837. *
  838. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  839. *
  840. * @param bean Bean for which a property descriptor is requested
  841. * @param name Possibly indexed and/or nested name of the property for
  842. * which a property descriptor is requested
  843. *
  844. * @exception IllegalAccessException if the caller does not have
  845. * access to the property accessor method
  846. * @exception IllegalArgumentException if <code>bean</code> or
  847. * <code>name</code> is null
  848. * @exception IllegalArgumentException if a nested reference to a
  849. * property returns null
  850. * @exception InvocationTargetException if the property accessor method
  851. * throws an exception
  852. * @exception NoSuchMethodException if an accessor method for this
  853. * propety cannot be found
  854. */
  855. public Class getPropertyEditorClass(Object bean, String name)
  856. throws IllegalAccessException, InvocationTargetException,
  857. NoSuchMethodException {
  858. if (bean == null) {
  859. throw new IllegalArgumentException("No bean specified");
  860. }
  861. if (name == null) {
  862. throw new IllegalArgumentException("No name specified");
  863. }
  864. PropertyDescriptor descriptor =
  865. getPropertyDescriptor(bean, name);
  866. if (descriptor != null) {
  867. return (descriptor.getPropertyEditorClass());
  868. } else {
  869. return (null);
  870. }
  871. }
  872. /**
  873. * Return the Java Class representing the property type of the specified
  874. * property, or <code>null</code> if there is no such property for the
  875. * specified bean. This method follows the same name resolution rules
  876. * used by <code>getPropertyDescriptor()</code>, so if the last element
  877. * of a name reference is indexed, the type of the property itself will
  878. * be returned. If the last (or only) element has no property with the
  879. * specified name, <code>null</code> is returned.
  880. *
  881. * @param bean Bean for which a property descriptor is requested
  882. * @param name Possibly indexed and/or nested name of the property for
  883. * which a property descriptor is requested
  884. *
  885. * @exception IllegalAccessException if the caller does not have
  886. * access to the property accessor method
  887. * @exception IllegalArgumentException if <code>bean</code> or
  888. * <code>name</code> is null
  889. * @exception IllegalArgumentException if a nested reference to a
  890. * property returns null
  891. * @exception InvocationTargetException if the property accessor method
  892. * throws an exception
  893. * @exception NoSuchMethodException if an accessor method for this
  894. * propety cannot be found
  895. */
  896. public Class getPropertyType(Object bean, String name)
  897. throws IllegalAccessException, InvocationTargetException,
  898. NoSuchMethodException {
  899. if (bean == null) {
  900. throw new IllegalArgumentException("No bean specified");
  901. }
  902. if (name == null) {
  903. throw new IllegalArgumentException("No name specified");
  904. }
  905. // Special handling for DynaBeans
  906. if (bean instanceof DynaBean) {
  907. DynaProperty descriptor =
  908. ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  909. if (descriptor == null) {
  910. return (null);
  911. }
  912. Class type = descriptor.getType();
  913. if (type == null) {
  914. return (null);
  915. } else if (type.isArray()) {
  916. return (type.getComponentType());
  917. } else {
  918. return (type);
  919. }
  920. }
  921. PropertyDescriptor descriptor =
  922. getPropertyDescriptor(bean, name);
  923. if (descriptor == null) {
  924. return (null);
  925. } else if (descriptor instanceof IndexedPropertyDescriptor) {
  926. return (((IndexedPropertyDescriptor) descriptor).
  927. getIndexedPropertyType());
  928. } else if (descriptor instanceof MappedPropertyDescriptor) {
  929. return (((MappedPropertyDescriptor) descriptor).
  930. getMappedPropertyType());
  931. } else {
  932. return (descriptor.getPropertyType());
  933. }
  934. }
  935. /**
  936. * <p>Return an accessible property getter method for this property,
  937. * if there is one; otherwise return <code>null</code>.</p>
  938. *
  939. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  940. *
  941. * @param descriptor Property descriptor to return a getter for
  942. */
  943. public Method getReadMethod(PropertyDescriptor descriptor) {
  944. return (MethodUtils.getAccessibleMethod(descriptor.getReadMethod()));
  945. }
  946. /**
  947. * Return the value of the specified simple property of the specified
  948. * bean, with no type conversions.
  949. *
  950. * @param bean Bean whose property is to be extracted
  951. * @param name Name of the property to be extracted
  952. *
  953. * @exception IllegalAccessException if the caller does not have
  954. * access to the property accessor method
  955. * @exception IllegalArgumentException if <code>bean</code> or
  956. * <code>name</code> is null
  957. * @exception IllegalArgumentException if the property name
  958. * is nested or indexed
  959. * @exception InvocationTargetException if the property accessor method
  960. * throws an exception
  961. * @exception NoSuchMethodException if an accessor method for this
  962. * propety cannot be found
  963. */
  964. public Object getSimpleProperty(Object bean, String name)
  965. throws IllegalAccessException, InvocationTargetException,
  966. NoSuchMethodException {
  967. if (bean == null) {
  968. throw new IllegalArgumentException("No bean specified");
  969. }
  970. if (name == null) {
  971. throw new IllegalArgumentException("No name specified");
  972. }
  973. // Validate the syntax of the property name
  974. if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
  975. throw new IllegalArgumentException
  976. ("Nested property names are not allowed");
  977. } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
  978. throw new IllegalArgumentException
  979. ("Indexed property names are not allowed");
  980. } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
  981. throw new IllegalArgumentException
  982. ("Mapped property names are not allowed");
  983. }
  984. // Handle DynaBean instances specially
  985. if (bean instanceof DynaBean) {
  986. DynaProperty descriptor =
  987. ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  988. if (descriptor == null) {
  989. throw new NoSuchMethodException("Unknown property '" +
  990. name + "'");
  991. }
  992. return (((DynaBean) bean).get(name));
  993. }
  994. // Retrieve the property getter method for the specified property
  995. PropertyDescriptor descriptor =
  996. getPropertyDescriptor(bean, name);
  997. if (descriptor == null) {
  998. throw new NoSuchMethodException("Unknown property '" +
  999. name + "'");
  1000. }
  1001. Method readMethod = getReadMethod(descriptor);
  1002. if (readMethod == null) {
  1003. throw new NoSuchMethodException("Property '" + name +
  1004. "' has no getter method");
  1005. }
  1006. // Call the property getter and return the value
  1007. Object value = invokeMethod(readMethod, bean, new Object[0]);
  1008. return (value);
  1009. }
  1010. /**
  1011. * <p>Return an accessible property setter method for this property,
  1012. * if there is one; otherwise return <code>null</code>.</p>
  1013. *
  1014. * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
  1015. *
  1016. * @param descriptor Property descriptor to return a setter for
  1017. */
  1018. public Method getWriteMethod(PropertyDescriptor descriptor) {
  1019. return (MethodUtils.getAccessibleMethod(descriptor.getWriteMethod()));
  1020. }
  1021. /**
  1022. * <p>Return <code>true</code> if the specified property name identifies
  1023. * a readable property on the specified bean; otherwise, return
  1024. * <code>false</code>.
  1025. *
  1026. * @param bean Bean to be examined (may be a {@link DynaBean}
  1027. * @param name Property name to be evaluated
  1028. *
  1029. * @exception IllegalArgumentException if <code>bean</code>
  1030. * or <code>name</code> is <code>null</code>
  1031. *
  1032. * @since BeanUtils 1.6
  1033. */
  1034. public boolean isReadable(Object bean, String name) {
  1035. // Validate method parameters
  1036. if (bean == null) {
  1037. throw new IllegalArgumentException("No bean specified");
  1038. }
  1039. if (name == null) {
  1040. throw new IllegalArgumentException("No name specified");
  1041. }
  1042. // Return the requested result
  1043. if (bean instanceof DynaBean) {
  1044. // All DynaBean properties are readable
  1045. return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
  1046. } else {
  1047. try {
  1048. PropertyDescriptor desc =
  1049. getPropertyDescriptor(bean, name);
  1050. if (desc != null) {
  1051. Method readMethod = desc.getReadMethod();
  1052. if ((readMethod == null) &&
  1053. (desc instanceof IndexedPropertyDescriptor)) {
  1054. readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
  1055. }
  1056. return (readMethod != null);
  1057. } else {
  1058. return (false);
  1059. }
  1060. } catch (IllegalAccessException e) {
  1061. return (false);
  1062. } catch (InvocationTargetException e) {
  1063. return (false);
  1064. } catch (NoSuchMethodException e) {
  1065. return (false);
  1066. }
  1067. }
  1068. }
  1069. /**
  1070. * <p>Return <code>true</code> if the specified property name identifies
  1071. * a writeable property on the specified bean; otherwise, return
  1072. * <code>false</code>.
  1073. *
  1074. * @param bean Bean to be examined (may be a {@link DynaBean}
  1075. * @param name Property name to be evaluated
  1076. *
  1077. * @exception IllegalPointerException if <code>bean</code>
  1078. * or <code>name</code> is <code>null</code>
  1079. *
  1080. * @since BeanUtils 1.6
  1081. */
  1082. public boolean isWriteable(Object bean, String name) {
  1083. // Validate method parameters
  1084. if (bean == null) {
  1085. throw new IllegalArgumentException("No bean specified");
  1086. }
  1087. if (name == null) {
  1088. throw new IllegalArgumentException("No name specified");
  1089. }
  1090. // Return the requested result
  1091. if (bean instanceof DynaBean) {
  1092. // All DynaBean properties are writeable
  1093. return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
  1094. } else {
  1095. try {
  1096. PropertyDescriptor desc =
  1097. getPropertyDescriptor(bean, name);
  1098. if (desc != null) {
  1099. Method writeMethod = desc.getWriteMethod();
  1100. if ((writeMethod == null) &&
  1101. (desc instanceof IndexedPropertyDescriptor)) {
  1102. writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
  1103. }
  1104. return (writeMethod != null);
  1105. } else {
  1106. return (false);
  1107. }
  1108. } catch (IllegalAccessException e) {
  1109. return (false);
  1110. } catch (InvocationTargetException e) {
  1111. return (false);
  1112. } catch (NoSuchMethodException e) {
  1113. return (false);
  1114. }
  1115. }
  1116. }
  1117. /**
  1118. * Set the value of the specified indexed property of the specified
  1119. * bean, with no type conversions. The zero-relative index of the
  1120. * required value must be included (in square brackets) as a suffix to
  1121. * the property name, or <code>IllegalArgumentException</code> will be
  1122. * thrown. In addition to supporting the JavaBeans specification, this
  1123. * method has been extended to support <code>List</code> objects as well.
  1124. *
  1125. * @param bean Bean whose property is to be modified
  1126. * @param name <code>propertyname[index]</code> of the property value
  1127. * to be modified
  1128. * @param value Value to which the specified property element
  1129. * should be set
  1130. *
  1131. * @exception ArrayIndexOutOfBoundsException if the specified index
  1132. * is outside the valid range for the underlying array
  1133. * @exception IllegalAccessException if the caller does not have
  1134. * access to the property accessor method
  1135. * @exception IllegalArgumentException if <code>bean</code> or
  1136. * <code>name</code> is null
  1137. * @exception InvocationTargetException if the property accessor method
  1138. * throws an exception
  1139. * @exception NoSuchMethodException if an accessor method for this
  1140. * propety cannot be found
  1141. */
  1142. public void setIndexedProperty(Object bean, String name,
  1143. Object value)
  1144. throws IllegalAccessException, InvocationTargetException,
  1145. NoSuchMethodException {
  1146. if (bean == null) {
  1147. throw new IllegalArgumentException("No bean specified");
  1148. }
  1149. if (name == null) {
  1150. throw new IllegalArgumentException("No name specified");
  1151. }
  1152. // Identify the index of the requested individual property
  1153. int delim = name.indexOf(PropertyUtils.INDEXED_DELIM);
  1154. int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2);
  1155. if ((delim < 0) || (delim2 <= delim)) {
  1156. throw new IllegalArgumentException("Invalid indexed property '" +
  1157. name + "'");
  1158. }
  1159. int index = -1;
  1160. try {
  1161. String subscript = name.substring(delim + 1, delim2);
  1162. index = Integer.parseInt(subscript);
  1163. } catch (NumberFormatException e) {
  1164. throw new IllegalArgumentException("Invalid indexed property '" +
  1165. name + "'");
  1166. }
  1167. name = name.substring(0, delim);
  1168. // Set the specified indexed property value
  1169. setIndexedProperty(bean, name, index, value);
  1170. }
  1171. /**
  1172. * Set the value of the specified indexed property of the specified
  1173. * bean, with no type conversions. In addition to supporting the JavaBeans
  1174. * specification, this method has been extended to support
  1175. * <code>List</code> objects as well.
  1176. *
  1177. * @param bean Bean whose property is to be set
  1178. * @param name Simple property name of the property value to be set
  1179. * @param index Index of the property value to be set
  1180. * @param value Value to which the indexed property element is to be set
  1181. *
  1182. * @exception ArrayIndexOutOfBoundsException if the specified index
  1183. * is outside the valid range for the underlying array
  1184. * @exception IllegalAccessException if the caller does not have
  1185. * access to the property accessor method
  1186. * @exception IllegalArgumentException if <code>bean</code> or
  1187. * <code>name</code> is null
  1188. * @exception InvocationTargetException if the property accessor method
  1189. * throws an exception
  1190. * @exception NoSuchMethodException if an accessor method for this
  1191. * propety cannot be found
  1192. */
  1193. public void setIndexedProperty(Object bean, String name,
  1194. int index, Object value)
  1195. throws IllegalAccessException, InvocationTargetException,
  1196. NoSuchMethodException {
  1197. if (bean == null) {
  1198. throw new IllegalArgumentException("No bean specified");
  1199. }
  1200. if (name == null) {
  1201. throw new IllegalArgumentException("No name specified");
  1202. }
  1203. // Handle DynaBean instances specially
  1204. if (bean instanceof DynaBean) {
  1205. DynaProperty descriptor =
  1206. ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  1207. if (descriptor == null) {
  1208. throw new NoSuchMethodException("Unknown property '" +
  1209. name + "'");
  1210. }
  1211. ((DynaBean) bean).set(name, index, value);
  1212. return;
  1213. }
  1214. // Retrieve the property descriptor for the specified property
  1215. PropertyDescriptor descriptor =
  1216. getPropertyDescriptor(bean, name);
  1217. if (descriptor == null) {
  1218. throw new NoSuchMethodException("Unknown property '" +
  1219. name + "'");
  1220. }
  1221. // Call the indexed setter method if there is one
  1222. if (descriptor instanceof IndexedPropertyDescriptor) {
  1223. Method writeMethod = ((IndexedPropertyDescriptor) descriptor).
  1224. getIndexedWriteMethod();
  1225. if (writeMethod != null) {
  1226. Object subscript[] = new Object[2];
  1227. subscript[0] = new Integer(index);
  1228. subscript[1] = value;
  1229. try {
  1230. if (log.isTraceEnabled()) {
  1231. String valueClassName =
  1232. value == null ? "<null>"
  1233. : value.getClass().getName();
  1234. log.trace("setSimpleProperty: Invoking method "
  1235. + writeMethod +" with index=" + index
  1236. + ", value=" + value
  1237. + " (class " + valueClassName+ ")");
  1238. }
  1239. invokeMethod(writeMethod, bean, subscript);
  1240. } catch (InvocationTargetException e) {
  1241. if (e.getTargetException() instanceof
  1242. ArrayIndexOutOfBoundsException) {
  1243. throw (ArrayIndexOutOfBoundsException)
  1244. e.getTargetException();
  1245. } else {
  1246. throw e;
  1247. }
  1248. }
  1249. return;
  1250. }
  1251. }
  1252. // Otherwise, the underlying property must be an array or a list
  1253. Method readMethod = descriptor.getReadMethod();
  1254. if (readMethod == null) {
  1255. throw new NoSuchMethodException("Property '" + name +
  1256. "' has no getter method");
  1257. }
  1258. // Call the property getter to get the array or list
  1259. Object array = invokeMethod(readMethod, bean, new Object[0]);
  1260. if (!array.getClass().isArray()) {
  1261. if (array instanceof List) {
  1262. // Modify the specified value in the List
  1263. ((List) array).set(index, value);
  1264. } else {
  1265. throw new IllegalArgumentException("Property '" + name +
  1266. "' is not indexed");
  1267. }
  1268. } else {
  1269. // Modify the specified value in the array
  1270. Array.set(array, index, value);
  1271. }
  1272. }
  1273. /**
  1274. * Set the value of the specified mapped property of the
  1275. * specified bean, with no type conversions. The key of the
  1276. * value to set must be included (in brackets) as a suffix to
  1277. * the property name, or <code>IllegalArgumentException</code> will be
  1278. * thrown.
  1279. *
  1280. * @param bean Bean whose property is to be set
  1281. * @param name <code>propertyname(key)</code> of the property value
  1282. * to be set
  1283. * @param value The property value to be set
  1284. *
  1285. * @exception IllegalAccessException if the caller does not have
  1286. * access to the property accessor method
  1287. * @exception InvocationTargetException if the property accessor method
  1288. * throws an exception
  1289. * @exception NoSuchMethodException if an accessor method for this
  1290. * propety cannot be found
  1291. */
  1292. public void setMappedProperty(Object bean, String name,
  1293. Object value)
  1294. throws IllegalAccessException, InvocationTargetException,
  1295. NoSuchMethodException {
  1296. if (bean == null) {
  1297. throw new IllegalArgumentException("No bean specified");
  1298. }
  1299. if (name == null) {
  1300. throw new IllegalArgumentException("No name specified");
  1301. }
  1302. // Identify the index of the requested individual property
  1303. int delim = name.indexOf(PropertyUtils.MAPPED_DELIM);
  1304. int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
  1305. if ((delim < 0) || (delim2 <= delim)) {
  1306. throw new IllegalArgumentException
  1307. ("Invalid mapped property '" + name + "'");
  1308. }
  1309. // Isolate the name and the key
  1310. String key = name.substring(delim + 1, delim2);
  1311. name = name.substring(0, delim);
  1312. // Request the specified indexed property value
  1313. setMappedProperty(bean, name, key, value);
  1314. }
  1315. /**
  1316. * Set the value of the specified mapped property of the specified
  1317. * bean, with no type conversions.
  1318. *
  1319. * @param bean Bean whose property is to be set
  1320. * @param name Mapped property name of the property value to be set
  1321. * @param key Key of the property value to be set
  1322. * @param value The property value to be set
  1323. *
  1324. * @exception IllegalAccessException if the caller does not have
  1325. * access to the property accessor method
  1326. * @exception InvocationTargetException if the property accessor method
  1327. * throws an exception
  1328. * @exception NoSuchMethodException if an accessor method for this
  1329. * propety cannot be found
  1330. */
  1331. public void setMappedProperty(Object bean, String name,
  1332. String key, Object value)
  1333. throws IllegalAccessException, InvocationTargetException,
  1334. NoSuchMethodException {
  1335. if (bean == null) {
  1336. throw new IllegalArgumentException("No bean specified");
  1337. }
  1338. if (name == null) {
  1339. throw new IllegalArgumentException("No name specified");
  1340. }
  1341. if (key == null) {
  1342. throw new IllegalArgumentException("No key specified");
  1343. }
  1344. // Handle DynaBean instances specially
  1345. if (bean instanceof DynaBean) {
  1346. DynaProperty descriptor =
  1347. ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  1348. if (descriptor == null) {
  1349. throw new NoSuchMethodException("Unknown property '" +
  1350. name + "'");
  1351. }
  1352. ((DynaBean) bean).set(name, key, value);
  1353. return;
  1354. }
  1355. // Retrieve the property descriptor for the specified property
  1356. PropertyDescriptor descriptor =
  1357. getPropertyDescriptor(bean, name);
  1358. if (descriptor == null) {
  1359. throw new NoSuchMethodException("Unknown property '" +
  1360. name + "'");
  1361. }
  1362. if (descriptor instanceof MappedPropertyDescriptor) {
  1363. // Call the keyed setter method if there is one
  1364. Method mappedWriteMethod =
  1365. ((MappedPropertyDescriptor) descriptor).
  1366. getMappedWriteMethod();
  1367. if (mappedWriteMethod != null) {
  1368. Object params[] = new Object[2];
  1369. params[0] = key;
  1370. params[1] = value;
  1371. if (log.isTraceEnabled()) {
  1372. String valueClassName =
  1373. value == null ? "<null>" : value.getClass().getName();
  1374. log.trace("setSimpleProperty: Invoking method "
  1375. + mappedWriteMethod + " with key=" + key
  1376. + ", value=" + value
  1377. + " (class " + valueClassName +")");
  1378. }
  1379. invokeMethod(mappedWriteMethod, bean, params);
  1380. } else {
  1381. throw new NoSuchMethodException
  1382. ("Property '" + name +
  1383. "' has no mapped setter method");
  1384. }
  1385. } else {
  1386. /* means that the result has to be retrieved from a map */
  1387. Method readMethod = descriptor.getReadMethod();
  1388. if (readMethod != null) {
  1389. Object invokeResult = invokeMethod(readMethod, bean, new Object[0]);
  1390. /* test and fetch from the map */
  1391. if (invokeResult instanceof java.util.Map) {
  1392. ((java.util.Map)invokeResult).put(key, value);
  1393. }
  1394. } else {
  1395. throw new NoSuchMethodException("Property '" + name +
  1396. "' has no mapped getter method");
  1397. }
  1398. }
  1399. }
  1400. /**
  1401. * Set the value of the (possibly nested) property of the specified
  1402. * name, for the specified bean, with no type conversions.
  1403. *
  1404. * @param bean Bean whose property is to be modified
  1405. * @param name Possibly nested name of the property to be modified
  1406. * @param value Value to which the property is to be set
  1407. *
  1408. * @exception IllegalAccessException if the caller does not have
  1409. * access to the property accessor method
  1410. * @exception IllegalArgumentException if <code>bean</code> or
  1411. * <code>name</code> is null
  1412. * @exception IllegalArgumentException if a nested reference to a
  1413. * property returns null
  1414. * @exception InvocationTargetException if the property accessor method
  1415. * throws an exception
  1416. * @exception NoSuchMethodException if an accessor method for this
  1417. * propety cannot be found
  1418. */
  1419. public void setNestedProperty(Object bean,
  1420. String name, Object value)
  1421. throws IllegalAccessException, InvocationTargetException,
  1422. NoSuchMethodException {
  1423. if (bean == null) {
  1424. throw new IllegalArgumentException("No bean specified");
  1425. }
  1426. if (name == null) {
  1427. throw new IllegalArgumentException("No name specified");
  1428. }
  1429. int indexOfINDEXED_DELIM = -1;
  1430. int indexOfMAPPED_DELIM = -1;
  1431. while (true) {
  1432. int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
  1433. if (delim < 0) {
  1434. break;
  1435. }
  1436. String next = name.substring(0, delim);
  1437. indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
  1438. indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
  1439. if (bean instanceof Map) {
  1440. bean = ((Map) bean).get(next);
  1441. } else if (indexOfMAPPED_DELIM >= 0) {
  1442. bean = getMappedProperty(bean, next);
  1443. } else if (indexOfINDEXED_DELIM >= 0) {
  1444. bean = getIndexedProperty(bean, next);
  1445. } else {
  1446. bean = getSimpleProperty(bean, next);
  1447. }
  1448. if (bean == null) {
  1449. throw new IllegalArgumentException
  1450. ("Null property value for '" +
  1451. name.substring(0, delim) + "'");
  1452. }
  1453. name = name.substring(delim + 1);
  1454. }
  1455. indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
  1456. indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
  1457. if (bean instanceof Map) {
  1458. // check to see if the class has a standard property
  1459. PropertyDescriptor descriptor =
  1460. getPropertyDescriptor(bean, name);
  1461. if (descriptor == null) {
  1462. // no - then put the value into the map
  1463. ((Map) bean).put(name, value);
  1464. } else {
  1465. // yes - use that instead
  1466. setSimpleProperty(bean, name, value);
  1467. }
  1468. } else if (indexOfMAPPED_DELIM >= 0) {
  1469. setMappedProperty(bean, name, value);
  1470. } else if (indexOfINDEXED_DELIM >= 0) {
  1471. setIndexedProperty(bean, name, value);
  1472. } else {
  1473. setSimpleProperty(bean, name, value);
  1474. }
  1475. }
  1476. /**
  1477. * Set the value of the specified property of the specified bean,
  1478. * no matter which property reference format is used, with no
  1479. * type conversions.
  1480. *
  1481. * @param bean Bean whose property is to be modified
  1482. * @param name Possibly indexed and/or nested name of the property
  1483. * to be modified
  1484. * @param value Value to which this property is to be set
  1485. *
  1486. * @exception IllegalAccessException if the caller does not have
  1487. * access to the property accessor method
  1488. * @exception IllegalArgumentException if <code>bean</code> or
  1489. * <code>name</code> is null
  1490. * @exception InvocationTargetException if the property accessor method
  1491. * throws an exception
  1492. * @exception NoSuchMethodException if an accessor method for this
  1493. * propety cannot be found
  1494. */
  1495. public void setProperty(Object bean, String name, Object value)
  1496. throws IllegalAccessException, InvocationTargetException,
  1497. NoSuchMethodException {
  1498. setNestedProperty(bean, name, value);
  1499. }
  1500. /**
  1501. * Set the value of the specified simple property of the specified bean,
  1502. * with no type conversions.
  1503. *
  1504. * @param bean Bean whose property is to be modified
  1505. * @param name Name of the property to be modified
  1506. * @param value Value to which the property should be set
  1507. *
  1508. * @exception IllegalAccessException if the caller does not have
  1509. * access to the property accessor method
  1510. * @exception IllegalArgumentException if <code>bean</code> or
  1511. * <code>name</code> is null
  1512. * @exception IllegalArgumentException if the property name is
  1513. * nested or indexed
  1514. * @exception InvocationTargetException if the property accessor method
  1515. * throws an exception
  1516. * @exception NoSuchMethodException if an accessor method for this
  1517. * propety cannot be found
  1518. */
  1519. public void setSimpleProperty(Object bean,
  1520. String name, Object value)
  1521. throws IllegalAccessException, InvocationTargetException,
  1522. NoSuchMethodException {
  1523. if (bean == null) {
  1524. throw new IllegalArgumentException("No bean specified");
  1525. }
  1526. if (name == null) {
  1527. throw new IllegalArgumentException("No name specified");
  1528. }
  1529. // Validate the syntax of the property name
  1530. if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
  1531. throw new IllegalArgumentException
  1532. ("Nested property names are not allowed");
  1533. } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
  1534. throw new IllegalArgumentException
  1535. ("Indexed property names are not allowed");
  1536. } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
  1537. throw new IllegalArgumentException
  1538. ("Mapped property names are not allowed");
  1539. }
  1540. // Handle DynaBean instances specially
  1541. if (bean instanceof DynaBean) {
  1542. DynaProperty descriptor =
  1543. ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  1544. if (descriptor == null) {
  1545. throw new NoSuchMethodException("Unknown property '" +
  1546. name + "'");
  1547. }
  1548. ((DynaBean) bean).set(name, value);
  1549. return;
  1550. }
  1551. // Retrieve the property setter method for the specified property
  1552. PropertyDescriptor descriptor =
  1553. getPropertyDescriptor(bean, name);
  1554. if (descriptor == null) {
  1555. throw new NoSuchMethodException("Unknown property '" +
  1556. name + "'");
  1557. }
  1558. Method writeMethod = getWriteMethod(descriptor);
  1559. if (writeMethod == null) {
  1560. throw new NoSuchMethodException("Property '" + name +
  1561. "' has no setter method");
  1562. }
  1563. // Call the property setter method
  1564. Object values[] = new Object[1];
  1565. values[0] = value;
  1566. if (log.isTraceEnabled()) {
  1567. String valueClassName =
  1568. value == null ? "<null>" : value.getClass().getName();
  1569. log.trace("setSimpleProperty: Invoking method " + writeMethod
  1570. + " with value " + value + " (class " + valueClassName + ")");
  1571. }
  1572. invokeMethod(writeMethod, bean, values);
  1573. }
  1574. /** This just catches and wraps IllegalArgumentException. */
  1575. private Object invokeMethod(
  1576. Method method,
  1577. Object bean,
  1578. Object[] values)
  1579. throws
  1580. IllegalAccessException,
  1581. InvocationTargetException {
  1582. try {
  1583. return method.invoke(bean, values);
  1584. } catch (IllegalArgumentException e) {
  1585. log.error("Method invocation failed.", e);
  1586. throw new IllegalArgumentException(
  1587. "Cannot invoke " + method.getDeclaringClass().getName() + "."
  1588. + method.getName() + " - " + e.getMessage());
  1589. }
  1590. }
  1591. }