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.IndexedPropertyDescriptor;
  18. import java.beans.PropertyDescriptor;
  19. import java.lang.reflect.Array;
  20. import java.lang.reflect.InvocationTargetException;
  21. import java.lang.reflect.Method;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.HashMap;
  25. import java.util.Iterator;
  26. import java.util.Map;
  27. import java.util.WeakHashMap;
  28. import org.apache.commons.collections.FastHashMap;
  29. import org.apache.commons.logging.Log;
  30. import org.apache.commons.logging.LogFactory;
  31. /**
  32. * <p>JavaBean property population methods.</p>
  33. *
  34. * <p>This class provides implementations for the utility methods in
  35. * {@link BeanUtils}.
  36. * Different instances can be used to isolate caches between classloaders
  37. * and to vary the value converters registered.</p>
  38. *
  39. * @author Craig R. McClanahan
  40. * @author Ralph Schaer
  41. * @author Chris Audley
  42. * @author Rey François
  43. * @author Gregor Raıman
  44. * @version $Revision: 1.16 $ $Date: 2004/02/28 13:18:33 $
  45. * @see BeanUtils
  46. * @since 1.7
  47. */
  48. public class BeanUtilsBean {
  49. // ------------------------------------------------------ Private Class Variables
  50. /**
  51. * Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
  52. */
  53. private static final ContextClassLoaderLocal
  54. beansByClassLoader = new ContextClassLoaderLocal() {
  55. // Creates the default instance used when the context classloader is unavailable
  56. protected Object initialValue() {
  57. return new BeanUtilsBean();
  58. }
  59. };
  60. /**
  61. * Gets the instance which provides the functionality for {@link BeanUtils}.
  62. * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
  63. * This mechanism provides isolation for web apps deployed in the same container.
  64. */
  65. public synchronized static BeanUtilsBean getInstance() {
  66. return (BeanUtilsBean) beansByClassLoader.get();
  67. }
  68. /**
  69. * Sets the instance which provides the functionality for {@link BeanUtils}.
  70. * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
  71. * This mechanism provides isolation for web apps deployed in the same container.
  72. */
  73. public synchronized static void setInstance(BeanUtilsBean newInstance) {
  74. beansByClassLoader.set(newInstance);
  75. }
  76. // --------------------------------------------------------- Attributes
  77. /**
  78. * Logging for this instance
  79. */
  80. private Log log = LogFactory.getLog(BeanUtils.class);
  81. /** Used to perform conversions between object types when setting properties */
  82. private ConvertUtilsBean convertUtilsBean;
  83. /** Used to access properties*/
  84. private PropertyUtilsBean propertyUtilsBean;
  85. // --------------------------------------------------------- Constuctors
  86. /**
  87. * <p>Constructs an instance using new property
  88. * and conversion instances.</p>
  89. */
  90. public BeanUtilsBean() {
  91. this(new ConvertUtilsBean(), new PropertyUtilsBean());
  92. }
  93. /**
  94. * <p>Constructs an instance using given property and conversion instances.</p>
  95. *
  96. * @param convertUtilsBean use this <code>ConvertUtilsBean</code>
  97. * to perform conversions from one object to another
  98. * @param propertyUtilsBean use this <code>PropertyUtilsBean</code>
  99. * to access properties
  100. */
  101. public BeanUtilsBean(
  102. ConvertUtilsBean convertUtilsBean,
  103. PropertyUtilsBean propertyUtilsBean) {
  104. this.convertUtilsBean = convertUtilsBean;
  105. this.propertyUtilsBean = propertyUtilsBean;
  106. }
  107. // --------------------------------------------------------- Public Methods
  108. /**
  109. * <p>Clone a bean based on the available property getters and setters,
  110. * even if the bean class itself does not implement Cloneable.</p>
  111. *
  112. * <p>
  113. * <strong>Note:</strong> this method creates a <strong>shallow</strong> clone.
  114. * In other words, any objects referred to by the bean are shared with the clone
  115. * rather than being cloned in turn.
  116. * </p>
  117. *
  118. * @param bean Bean to be cloned
  119. *
  120. * @exception IllegalAccessException if the caller does not have
  121. * access to the property accessor method
  122. * @exception InstantiationException if a new instance of the bean's
  123. * class cannot be instantiated
  124. * @exception InvocationTargetException if the property accessor method
  125. * throws an exception
  126. * @exception NoSuchMethodException if an accessor method for this
  127. * property cannot be found
  128. */
  129. public Object cloneBean(Object bean)
  130. throws IllegalAccessException, InstantiationException,
  131. InvocationTargetException, NoSuchMethodException {
  132. if (log.isDebugEnabled()) {
  133. log.debug("Cloning bean: " + bean.getClass().getName());
  134. }
  135. Class clazz = bean.getClass();
  136. Object newBean = null;
  137. if (bean instanceof DynaBean) {
  138. newBean = ((DynaBean) bean).getDynaClass().newInstance();
  139. } else {
  140. newBean = bean.getClass().newInstance();
  141. }
  142. getPropertyUtils().copyProperties(newBean, bean);
  143. return (newBean);
  144. }
  145. /**
  146. * <p>Copy property values from the origin bean to the destination bean
  147. * for all cases where the property names are the same. For each
  148. * property, a conversion is attempted as necessary. All combinations of
  149. * standard JavaBeans and DynaBeans as origin and destination are
  150. * supported. Properties that exist in the origin bean, but do not exist
  151. * in the destination bean (or are read-only in the destination bean) are
  152. * silently ignored.</p>
  153. *
  154. * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
  155. * to contain String-valued <strong>simple</strong> property names as the keys, pointing at
  156. * the corresponding property values that will be converted (if necessary)
  157. * and set in the destination bean. <strong>Note</strong> that this method
  158. * is intended to perform a "shallow copy" of the properties and so complex
  159. * properties (for example, nested ones) will not be copied.</p>
  160. *
  161. * <p>This method differs from <code>populate()</code>, which
  162. * was primarily designed for populating JavaBeans from the map of request
  163. * parameters retrieved on an HTTP request, is that no scalar->indexed
  164. * or indexed->scalar manipulations are performed. If the origin property
  165. * is indexed, the destination property must be also.</p>
  166. *
  167. * <p>If you know that no type conversions are required, the
  168. * <code>copyProperties()</code> method in {@link PropertyUtils} will
  169. * execute faster than this method.</p>
  170. *
  171. * <p><strong>FIXME</strong> - Indexed and mapped properties that do not
  172. * have getter and setter methods for the underlying array or Map are not
  173. * copied by this method.</p>
  174. *
  175. * @param dest Destination bean whose properties are modified
  176. * @param orig Origin bean whose properties are retrieved
  177. *
  178. * @exception IllegalAccessException if the caller does not have
  179. * access to the property accessor method
  180. * @exception IllegalArgumentException if the <code>dest</code> or
  181. * <code>orig</code> argument is null
  182. * @exception InvocationTargetException if the property accessor method
  183. * throws an exception
  184. */
  185. public void copyProperties(Object dest, Object orig)
  186. throws IllegalAccessException, InvocationTargetException {
  187. // Validate existence of the specified beans
  188. if (dest == null) {
  189. throw new IllegalArgumentException
  190. ("No destination bean specified");
  191. }
  192. if (orig == null) {
  193. throw new IllegalArgumentException("No origin bean specified");
  194. }
  195. if (log.isDebugEnabled()) {
  196. log.debug("BeanUtils.copyProperties(" + dest + ", " +
  197. orig + ")");
  198. }
  199. // Copy the properties, converting as necessary
  200. if (orig instanceof DynaBean) {
  201. DynaProperty origDescriptors[] =
  202. ((DynaBean) orig).getDynaClass().getDynaProperties();
  203. for (int i = 0; i < origDescriptors.length; i++) {
  204. String name = origDescriptors[i].getName();
  205. if (getPropertyUtils().isWriteable(dest, name)) {
  206. Object value = ((DynaBean) orig).get(name);
  207. copyProperty(dest, name, value);
  208. }
  209. }
  210. } else if (orig instanceof Map) {
  211. Iterator names = ((Map) orig).keySet().iterator();
  212. while (names.hasNext()) {
  213. String name = (String) names.next();
  214. if (getPropertyUtils().isWriteable(dest, name)) {
  215. Object value = ((Map) orig).get(name);
  216. copyProperty(dest, name, value);
  217. }
  218. }
  219. } else /* if (orig is a standard JavaBean) */ {
  220. PropertyDescriptor origDescriptors[] =
  221. getPropertyUtils().getPropertyDescriptors(orig);
  222. for (int i = 0; i < origDescriptors.length; i++) {
  223. String name = origDescriptors[i].getName();
  224. if ("class".equals(name)) {
  225. continue; // No point in trying to set an object's class
  226. }
  227. if (getPropertyUtils().isReadable(orig, name) &&
  228. getPropertyUtils().isWriteable(dest, name)) {
  229. try {
  230. Object value =
  231. getPropertyUtils().getSimpleProperty(orig, name);
  232. copyProperty(dest, name, value);
  233. } catch (NoSuchMethodException e) {
  234. ; // Should not happen
  235. }
  236. }
  237. }
  238. }
  239. }
  240. /**
  241. * <p>Copy the specified property value to the specified destination bean,
  242. * performing any type conversion that is required. If the specified
  243. * bean does not have a property of the specified name, or the property
  244. * is read only on the destination bean, return without
  245. * doing anything. If you have custom destination property types, register
  246. * {@link Converter}s for them by calling the <code>register()</code>
  247. * method of {@link ConvertUtils}.</p>
  248. *
  249. * <p><strong>IMPLEMENTATION RESTRICTIONS</strong>:</p>
  250. * <ul>
  251. * <li>Does not support destination properties that are indexed,
  252. * but only an indexed setter (as opposed to an array setter)
  253. * is available.</li>
  254. * <li>Does not support destination properties that are mapped,
  255. * but only a keyed setter (as opposed to a Map setter)
  256. * is available.</li>
  257. * <li>The desired property type of a mapped setter cannot be
  258. * determined (since Maps support any data type), so no conversion
  259. * will be performed.</li>
  260. * </ul>
  261. *
  262. * @param bean Bean on which setting is to be performed
  263. * @param name Property name (can be nested/indexed/mapped/combo)
  264. * @param value Value to be set
  265. *
  266. * @exception IllegalAccessException if the caller does not have
  267. * access to the property accessor method
  268. * @exception InvocationTargetException if the property accessor method
  269. * throws an exception
  270. */
  271. public void copyProperty(Object bean, String name, Object value)
  272. throws IllegalAccessException, InvocationTargetException {
  273. // Trace logging (if enabled)
  274. if (log.isTraceEnabled()) {
  275. StringBuffer sb = new StringBuffer(" copyProperty(");
  276. sb.append(bean);
  277. sb.append(", ");
  278. sb.append(name);
  279. sb.append(", ");
  280. if (value == null) {
  281. sb.append("<NULL>");
  282. } else if (value instanceof String) {
  283. sb.append((String) value);
  284. } else if (value instanceof String[]) {
  285. String values[] = (String[]) value;
  286. sb.append('[');
  287. for (int i = 0; i < values.length; i++) {
  288. if (i > 0) {
  289. sb.append(',');
  290. }
  291. sb.append(values[i]);
  292. }
  293. sb.append(']');
  294. } else {
  295. sb.append(value.toString());
  296. }
  297. sb.append(')');
  298. log.trace(sb.toString());
  299. }
  300. // Resolve any nested expression to get the actual target bean
  301. Object target = bean;
  302. int delim = name.lastIndexOf(PropertyUtils.NESTED_DELIM);
  303. if (delim >= 0) {
  304. try {
  305. target =
  306. getPropertyUtils().getProperty(bean, name.substring(0, delim));
  307. } catch (NoSuchMethodException e) {
  308. return; // Skip this property setter
  309. }
  310. name = name.substring(delim + 1);
  311. if (log.isTraceEnabled()) {
  312. log.trace(" Target bean = " + target);
  313. log.trace(" Target name = " + name);
  314. }
  315. }
  316. // Declare local variables we will require
  317. String propName = null; // Simple name of target property
  318. Class type = null; // Java type of target property
  319. int index = -1; // Indexed subscript value (if any)
  320. String key = null; // Mapped key value (if any)
  321. // Calculate the target property name, index, and key values
  322. propName = name;
  323. int i = propName.indexOf(PropertyUtils.INDEXED_DELIM);
  324. if (i >= 0) {
  325. int k = propName.indexOf(PropertyUtils.INDEXED_DELIM2);
  326. try {
  327. index =
  328. Integer.parseInt(propName.substring(i + 1, k));
  329. } catch (NumberFormatException e) {
  330. ;
  331. }
  332. propName = propName.substring(0, i);
  333. }
  334. int j = propName.indexOf(PropertyUtils.MAPPED_DELIM);
  335. if (j >= 0) {
  336. int k = propName.indexOf(PropertyUtils.MAPPED_DELIM2);
  337. try {
  338. key = propName.substring(j + 1, k);
  339. } catch (IndexOutOfBoundsException e) {
  340. ;
  341. }
  342. propName = propName.substring(0, j);
  343. }
  344. // Calculate the target property type
  345. if (target instanceof DynaBean) {
  346. DynaClass dynaClass = ((DynaBean) target).getDynaClass();
  347. DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
  348. if (dynaProperty == null) {
  349. return; // Skip this property setter
  350. }
  351. type = dynaProperty.getType();
  352. } else {
  353. PropertyDescriptor descriptor = null;
  354. try {
  355. descriptor =
  356. getPropertyUtils().getPropertyDescriptor(target, name);
  357. if (descriptor == null) {
  358. return; // Skip this property setter
  359. }
  360. } catch (NoSuchMethodException e) {
  361. return; // Skip this property setter
  362. }
  363. type = descriptor.getPropertyType();
  364. if (type == null) {
  365. // Most likely an indexed setter on a POJB only
  366. if (log.isTraceEnabled()) {
  367. log.trace(" target type for property '" +
  368. propName + "' is null, so skipping ths setter");
  369. }
  370. return;
  371. }
  372. }
  373. if (log.isTraceEnabled()) {
  374. log.trace(" target propName=" + propName + ", type=" +
  375. type + ", index=" + index + ", key=" + key);
  376. }
  377. // Convert the specified value to the required type and store it
  378. if (index >= 0) { // Destination must be indexed
  379. Converter converter = getConvertUtils().lookup(type.getComponentType());
  380. if (converter != null) {
  381. log.trace(" USING CONVERTER " + converter);
  382. value = converter.convert(type, value);
  383. }
  384. try {
  385. getPropertyUtils().setIndexedProperty(target, propName,
  386. index, value);
  387. } catch (NoSuchMethodException e) {
  388. throw new InvocationTargetException
  389. (e, "Cannot set " + propName);
  390. }
  391. } else if (key != null) { // Destination must be mapped
  392. // Maps do not know what the preferred data type is,
  393. // so perform no conversions at all
  394. // FIXME - should we create or support a TypedMap?
  395. try {
  396. getPropertyUtils().setMappedProperty(target, propName,
  397. key, value);
  398. } catch (NoSuchMethodException e) {
  399. throw new InvocationTargetException
  400. (e, "Cannot set " + propName);
  401. }
  402. } else { // Destination must be simple
  403. Converter converter = getConvertUtils().lookup(type);
  404. if (converter != null) {
  405. log.trace(" USING CONVERTER " + converter);
  406. value = converter.convert(type, value);
  407. }
  408. try {
  409. getPropertyUtils().setSimpleProperty(target, propName, value);
  410. } catch (NoSuchMethodException e) {
  411. throw new InvocationTargetException
  412. (e, "Cannot set " + propName);
  413. }
  414. }
  415. }
  416. /**
  417. * <p>Return the entire set of properties for which the specified bean
  418. * provides a read method. This map contains the to <code>String</code>
  419. * converted property values for all properties for which a read method
  420. * is provided (i.e. where the getReadMethod() returns non-null).</p>
  421. *
  422. * <p>This map can be fed back to a call to
  423. * <code>BeanUtils.populate()</code> to reconsitute the same set of
  424. * properties, modulo differences for read-only and write-only
  425. * properties, but only if there are no indexed properties.</p>
  426. *
  427. * @param bean Bean whose properties are to be extracted
  428. *
  429. * @exception IllegalAccessException if the caller does not have
  430. * access to the property accessor method
  431. * @exception InvocationTargetException if the property accessor method
  432. * throws an exception
  433. * @exception NoSuchMethodException if an accessor method for this
  434. * property cannot be found
  435. */
  436. public Map describe(Object bean)
  437. throws IllegalAccessException, InvocationTargetException,
  438. NoSuchMethodException {
  439. if (bean == null) {
  440. // return (Collections.EMPTY_MAP);
  441. return (new java.util.HashMap());
  442. }
  443. if (log.isDebugEnabled()) {
  444. log.debug("Describing bean: " + bean.getClass().getName());
  445. }
  446. Map description = new HashMap();
  447. if (bean instanceof DynaBean) {
  448. DynaProperty descriptors[] =
  449. ((DynaBean) bean).getDynaClass().getDynaProperties();
  450. for (int i = 0; i < descriptors.length; i++) {
  451. String name = descriptors[i].getName();
  452. description.put(name, getProperty(bean, name));
  453. }
  454. } else {
  455. PropertyDescriptor descriptors[] =
  456. getPropertyUtils().getPropertyDescriptors(bean);
  457. for (int i = 0; i < descriptors.length; i++) {
  458. String name = descriptors[i].getName();
  459. if (descriptors[i].getReadMethod() != null)
  460. description.put(name, getProperty(bean, name));
  461. }
  462. }
  463. return (description);
  464. }
  465. /**
  466. * Return the value of the specified array property of the specified
  467. * bean, as a String array.
  468. *
  469. * @param bean Bean whose property is to be extracted
  470. * @param name Name of the property to be extracted
  471. *
  472. * @exception IllegalAccessException if the caller does not have
  473. * access to the property accessor method
  474. * @exception InvocationTargetException if the property accessor method
  475. * throws an exception
  476. * @exception NoSuchMethodException if an accessor method for this
  477. * property cannot be found
  478. */
  479. public String[] getArrayProperty(Object bean, String name)
  480. throws IllegalAccessException, InvocationTargetException,
  481. NoSuchMethodException {
  482. Object value = getPropertyUtils().getProperty(bean, name);
  483. if (value == null) {
  484. return (null);
  485. } else if (value instanceof Collection) {
  486. ArrayList values = new ArrayList();
  487. Iterator items = ((Collection) value).iterator();
  488. while (items.hasNext()) {
  489. Object item = items.next();
  490. if (item == null) {
  491. values.add((String) null);
  492. } else {
  493. // convert to string using convert utils
  494. values.add(getConvertUtils().convert(item));
  495. }
  496. }
  497. return ((String[]) values.toArray(new String[values.size()]));
  498. } else if (value.getClass().isArray()) {
  499. int n = Array.getLength(value);
  500. String results[] = new String[n];
  501. for (int i = 0; i < n; i++) {
  502. Object item = Array.get(value, i);
  503. if (item == null) {
  504. results[i] = null;
  505. } else {
  506. // convert to string using convert utils
  507. results[i] = getConvertUtils().convert(item);
  508. }
  509. }
  510. return (results);
  511. } else {
  512. String results[] = new String[1];
  513. results[0] = value.toString();
  514. return (results);
  515. }
  516. }
  517. /**
  518. * Return the value of the specified indexed property of the specified
  519. * bean, as a String. The zero-relative index of the
  520. * required value must be included (in square brackets) as a suffix to
  521. * the property name, or <code>IllegalArgumentException</code> will be
  522. * thrown.
  523. *
  524. * @param bean Bean whose property is to be extracted
  525. * @param name <code>propertyname[index]</code> of the property value
  526. * to be extracted
  527. *
  528. * @exception IllegalAccessException if the caller does not have
  529. * access to the property accessor method
  530. * @exception InvocationTargetException if the property accessor method
  531. * throws an exception
  532. * @exception NoSuchMethodException if an accessor method for this
  533. * property cannot be found
  534. */
  535. public String getIndexedProperty(Object bean, String name)
  536. throws IllegalAccessException, InvocationTargetException,
  537. NoSuchMethodException {
  538. Object value = getPropertyUtils().getIndexedProperty(bean, name);
  539. return (getConvertUtils().convert(value));
  540. }
  541. /**
  542. * Return the value of the specified indexed property of the specified
  543. * bean, as a String. The index is specified as a method parameter and
  544. * must *not* be included in the property name expression
  545. *
  546. * @param bean Bean whose property is to be extracted
  547. * @param name Simple property name of the property value to be extracted
  548. * @param index Index of the property value to be extracted
  549. *
  550. * @exception IllegalAccessException if the caller does not have
  551. * access to the property accessor method
  552. * @exception InvocationTargetException if the property accessor method
  553. * throws an exception
  554. * @exception NoSuchMethodException if an accessor method for this
  555. * property cannot be found
  556. */
  557. public String getIndexedProperty(Object bean,
  558. String name, int index)
  559. throws IllegalAccessException, InvocationTargetException,
  560. NoSuchMethodException {
  561. Object value = getPropertyUtils().getIndexedProperty(bean, name, index);
  562. return (getConvertUtils().convert(value));
  563. }
  564. /**
  565. * Return the value of the specified indexed property of the specified
  566. * bean, as a String. The String-valued key of the required value
  567. * must be included (in parentheses) as a suffix to
  568. * the property name, or <code>IllegalArgumentException</code> will be
  569. * thrown.
  570. *
  571. * @param bean Bean whose property is to be extracted
  572. * @param name <code>propertyname(index)</code> of the property value
  573. * to be extracted
  574. *
  575. * @exception IllegalAccessException if the caller does not have
  576. * access to the property accessor method
  577. * @exception InvocationTargetException if the property accessor method
  578. * throws an exception
  579. * @exception NoSuchMethodException if an accessor method for this
  580. * property cannot be found
  581. */
  582. public String getMappedProperty(Object bean, String name)
  583. throws IllegalAccessException, InvocationTargetException,
  584. NoSuchMethodException {
  585. Object value = getPropertyUtils().getMappedProperty(bean, name);
  586. return (getConvertUtils().convert(value));
  587. }
  588. /**
  589. * Return the value of the specified mapped property of the specified
  590. * bean, as a String. The key is specified as a method parameter and
  591. * must *not* be included in the property name expression
  592. *
  593. * @param bean Bean whose property is to be extracted
  594. * @param name Simple property name of the property value to be extracted
  595. * @param key Lookup key of the property value to be extracted
  596. *
  597. * @exception IllegalAccessException if the caller does not have
  598. * access to the property accessor method
  599. * @exception InvocationTargetException if the property accessor method
  600. * throws an exception
  601. * @exception NoSuchMethodException if an accessor method for this
  602. * property cannot be found
  603. */
  604. public String getMappedProperty(Object bean,
  605. String name, String key)
  606. throws IllegalAccessException, InvocationTargetException,
  607. NoSuchMethodException {
  608. Object value = getPropertyUtils().getMappedProperty(bean, name, key);
  609. return (getConvertUtils().convert(value));
  610. }
  611. /**
  612. * Return the value of the (possibly nested) property of the specified
  613. * name, for the specified bean, as a String.
  614. *
  615. * @param bean Bean whose property is to be extracted
  616. * @param name Possibly nested name of the property to be extracted
  617. *
  618. * @exception IllegalAccessException if the caller does not have
  619. * access to the property accessor method
  620. * @exception IllegalArgumentException if a nested reference to a
  621. * property returns null
  622. * @exception InvocationTargetException if the property accessor method
  623. * throws an exception
  624. * @exception NoSuchMethodException if an accessor method for this
  625. * property cannot be found
  626. */
  627. public String getNestedProperty(Object bean, String name)
  628. throws IllegalAccessException, InvocationTargetException,
  629. NoSuchMethodException {
  630. Object value = getPropertyUtils().getNestedProperty(bean, name);
  631. return (getConvertUtils().convert(value));
  632. }
  633. /**
  634. * Return the value of the specified property of the specified bean,
  635. * no matter which property reference format is used, as a String.
  636. *
  637. * @param bean Bean whose property is to be extracted
  638. * @param name Possibly indexed and/or nested name of the property
  639. * to be extracted
  640. *
  641. * @exception IllegalAccessException if the caller does not have
  642. * access to the property accessor method
  643. * @exception InvocationTargetException if the property accessor method
  644. * throws an exception
  645. * @exception NoSuchMethodException if an accessor method for this
  646. * property cannot be found
  647. */
  648. public String getProperty(Object bean, String name)
  649. throws IllegalAccessException, InvocationTargetException,
  650. NoSuchMethodException {
  651. return (getNestedProperty(bean, name));
  652. }
  653. /**
  654. * Return the value of the specified simple property of the specified
  655. * bean, converted to a String.
  656. *
  657. * @param bean Bean whose property is to be extracted
  658. * @param name Name of the property to be extracted
  659. *
  660. * @exception IllegalAccessException if the caller does not have
  661. * access to the property accessor method
  662. * @exception InvocationTargetException if the property accessor method
  663. * throws an exception
  664. * @exception NoSuchMethodException if an accessor method for this
  665. * property cannot be found
  666. */
  667. public String getSimpleProperty(Object bean, String name)
  668. throws IllegalAccessException, InvocationTargetException,
  669. NoSuchMethodException {
  670. Object value = getPropertyUtils().getSimpleProperty(bean, name);
  671. return (getConvertUtils().convert(value));
  672. }
  673. /**
  674. * <p>Populate the JavaBeans properties of the specified bean, based on
  675. * the specified name/value pairs. This method uses Java reflection APIs
  676. * to identify corresponding "property setter" method names, and deals
  677. * with setter arguments of type <code>String</code>, <code>boolean</code>,
  678. * <code>int</code>, <code>long</code>, <code>float</code>, and
  679. * <code>double</code>. In addition, array setters for these types (or the
  680. * corresponding primitive types) can also be identified.</p>
  681. *
  682. * <p>The particular setter method to be called for each property is
  683. * determined using the usual JavaBeans introspection mechanisms. Thus,
  684. * you may identify custom setter methods using a BeanInfo class that is
  685. * associated with the class of the bean itself. If no such BeanInfo
  686. * class is available, the standard method name conversion ("set" plus
  687. * the capitalized name of the property in question) is used.</p>
  688. *
  689. * <p><strong>NOTE</strong>: It is contrary to the JavaBeans Specification
  690. * to have more than one setter method (with different argument
  691. * signatures) for the same property.</p>
  692. *
  693. * <p><strong>WARNING</strong> - The logic of this method is customized
  694. * for extracting String-based request parameters from an HTTP request.
  695. * It is probably not what you want for general property copying with
  696. * type conversion. For that purpose, check out the
  697. * <code>copyProperties()</code> method instead.</p>
  698. *
  699. * @param bean JavaBean whose properties are being populated
  700. * @param properties Map keyed by property name, with the
  701. * corresponding (String or String[]) value(s) to be set
  702. *
  703. * @exception IllegalAccessException if the caller does not have
  704. * access to the property accessor method
  705. * @exception InvocationTargetException if the property accessor method
  706. * throws an exception
  707. */
  708. public void populate(Object bean, Map properties)
  709. throws IllegalAccessException, InvocationTargetException {
  710. // Do nothing unless both arguments have been specified
  711. if ((bean == null) || (properties == null)) {
  712. return;
  713. }
  714. if (log.isDebugEnabled()) {
  715. log.debug("BeanUtils.populate(" + bean + ", " +
  716. properties + ")");
  717. }
  718. // Loop through the property name/value pairs to be set
  719. Iterator names = properties.keySet().iterator();
  720. while (names.hasNext()) {
  721. // Identify the property name and value(s) to be assigned
  722. String name = (String) names.next();
  723. if (name == null) {
  724. continue;
  725. }
  726. Object value = properties.get(name);
  727. // Perform the assignment for this property
  728. setProperty(bean, name, value);
  729. }
  730. }
  731. /**
  732. * <p>Set the specified property value, performing type conversions as
  733. * required to conform to the type of the destination property.</p>
  734. *
  735. * <p>If the property is read only then the method returns
  736. * without throwing an exception.</p>
  737. *
  738. * <p>If <code>null</code> is passed into a property expecting a primitive value,
  739. * then this will be converted as if it were a <code>null</code> string.</p>
  740. *
  741. * <p><strong>WARNING</strong> - The logic of this method is customized
  742. * to meet the needs of <code>populate()</code>, and is probably not what
  743. * you want for general property copying with type conversion. For that
  744. * purpose, check out the <code>copyProperty()</code> method instead.</p>
  745. *
  746. * <p><strong>WARNING</strong> - PLEASE do not modify the behavior of this
  747. * method without consulting with the Struts developer community. There
  748. * are some subtleties to its functionality that are not documented in the
  749. * Javadoc description above, yet are vital to the way that Struts utilizes
  750. * this method.</p>
  751. *
  752. * @param bean Bean on which setting is to be performed
  753. * @param name Property name (can be nested/indexed/mapped/combo)
  754. * @param value Value to be set
  755. *
  756. * @exception IllegalAccessException if the caller does not have
  757. * access to the property accessor method
  758. * @exception InvocationTargetException if the property accessor method
  759. * throws an exception
  760. */
  761. public void setProperty(Object bean, String name, Object value)
  762. throws IllegalAccessException, InvocationTargetException {
  763. // Trace logging (if enabled)
  764. if (log.isTraceEnabled()) {
  765. StringBuffer sb = new StringBuffer(" setProperty(");
  766. sb.append(bean);
  767. sb.append(", ");
  768. sb.append(name);
  769. sb.append(", ");
  770. if (value == null) {
  771. sb.append("<NULL>");
  772. } else if (value instanceof String) {
  773. sb.append((String) value);
  774. } else if (value instanceof String[]) {
  775. String values[] = (String[]) value;
  776. sb.append('[');
  777. for (int i = 0; i < values.length; i++) {
  778. if (i > 0) {
  779. sb.append(',');
  780. }
  781. sb.append(values[i]);
  782. }
  783. sb.append(']');
  784. } else {
  785. sb.append(value.toString());
  786. }
  787. sb.append(')');
  788. log.trace(sb.toString());
  789. }
  790. // Resolve any nested expression to get the actual target bean
  791. Object target = bean;
  792. int delim = findLastNestedIndex(name);
  793. if (delim >= 0) {
  794. try {
  795. target =
  796. getPropertyUtils().getProperty(bean, name.substring(0, delim));
  797. } catch (NoSuchMethodException e) {
  798. return; // Skip this property setter
  799. }
  800. name = name.substring(delim + 1);
  801. if (log.isTraceEnabled()) {
  802. log.trace(" Target bean = " + target);
  803. log.trace(" Target name = " + name);
  804. }
  805. }
  806. // Declare local variables we will require
  807. String propName = null; // Simple name of target property
  808. Class type = null; // Java type of target property
  809. int index = -1; // Indexed subscript value (if any)
  810. String key = null; // Mapped key value (if any)
  811. // Calculate the property name, index, and key values
  812. propName = name;
  813. int i = propName.indexOf(PropertyUtils.INDEXED_DELIM);
  814. if (i >= 0) {
  815. int k = propName.indexOf(PropertyUtils.INDEXED_DELIM2);
  816. try {
  817. index =
  818. Integer.parseInt(propName.substring(i + 1, k));
  819. } catch (NumberFormatException e) {
  820. ;
  821. }
  822. propName = propName.substring(0, i);
  823. }
  824. int j = propName.indexOf(PropertyUtils.MAPPED_DELIM);
  825. if (j >= 0) {
  826. int k = propName.indexOf(PropertyUtils.MAPPED_DELIM2);
  827. try {
  828. key = propName.substring(j + 1, k);
  829. } catch (IndexOutOfBoundsException e) {
  830. ;
  831. }
  832. propName = propName.substring(0, j);
  833. }
  834. // Calculate the property type
  835. if (target instanceof DynaBean) {
  836. DynaClass dynaClass = ((DynaBean) target).getDynaClass();
  837. DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
  838. if (dynaProperty == null) {
  839. return; // Skip this property setter
  840. }
  841. type = dynaProperty.getType();
  842. } else {
  843. PropertyDescriptor descriptor = null;
  844. try {
  845. descriptor =
  846. getPropertyUtils().getPropertyDescriptor(target, name);
  847. if (descriptor == null) {
  848. return; // Skip this property setter
  849. }
  850. } catch (NoSuchMethodException e) {
  851. return; // Skip this property setter
  852. }
  853. if (descriptor instanceof MappedPropertyDescriptor) {
  854. if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {
  855. if (log.isDebugEnabled()) {
  856. log.debug("Skipping read-only property");
  857. }
  858. return; // Read-only, skip this property setter
  859. }
  860. type = ((MappedPropertyDescriptor) descriptor).
  861. getMappedPropertyType();
  862. } else if (descriptor instanceof IndexedPropertyDescriptor) {
  863. if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) {
  864. if (log.isDebugEnabled()) {
  865. log.debug("Skipping read-only property");
  866. }
  867. return; // Read-only, skip this property setter
  868. }
  869. type = ((IndexedPropertyDescriptor) descriptor).
  870. getIndexedPropertyType();
  871. } else {
  872. if (descriptor.getWriteMethod() == null) {
  873. if (log.isDebugEnabled()) {
  874. log.debug("Skipping read-only property");
  875. }
  876. return; // Read-only, skip this property setter
  877. }
  878. type = descriptor.getPropertyType();
  879. }
  880. }
  881. // Convert the specified value to the required type
  882. Object newValue = null;
  883. if (type.isArray() && (index < 0)) { // Scalar value into array
  884. if (value == null) {
  885. String values[] = new String[1];
  886. values[0] = (String) value;
  887. newValue = getConvertUtils().convert((String[]) values, type);
  888. } else if (value instanceof String) {
  889. String values[] = new String[1];
  890. values[0] = (String) value;
  891. newValue = getConvertUtils().convert((String[]) values, type);
  892. } else if (value instanceof String[]) {
  893. newValue = getConvertUtils().convert((String[]) value, type);
  894. } else {
  895. newValue = value;
  896. }
  897. } else if (type.isArray()) { // Indexed value into array
  898. if (value instanceof String) {
  899. newValue = getConvertUtils().convert((String) value,
  900. type.getComponentType());
  901. } else if (value instanceof String[]) {
  902. newValue = getConvertUtils().convert(((String[]) value)[0],
  903. type.getComponentType());
  904. } else {
  905. newValue = value;
  906. }
  907. } else { // Value into scalar
  908. if ((value instanceof String) || (value == null)) {
  909. newValue = getConvertUtils().convert((String) value, type);
  910. } else if (value instanceof String[]) {
  911. newValue = getConvertUtils().convert(((String[]) value)[0],
  912. type);
  913. } else if (getConvertUtils().lookup(value.getClass()) != null) {
  914. newValue = getConvertUtils().convert(value.toString(), type);
  915. } else {
  916. newValue = value;
  917. }
  918. }
  919. // Invoke the setter method
  920. try {
  921. if (index >= 0) {
  922. getPropertyUtils().setIndexedProperty(target, propName,
  923. index, newValue);
  924. } else if (key != null) {
  925. getPropertyUtils().setMappedProperty(target, propName,
  926. key, newValue);
  927. } else {
  928. getPropertyUtils().setProperty(target, propName, newValue);
  929. }
  930. } catch (NoSuchMethodException e) {
  931. throw new InvocationTargetException
  932. (e, "Cannot set " + propName);
  933. }
  934. }
  935. private int findLastNestedIndex(String expression)
  936. {
  937. // walk back from the end to the start
  938. // and find the first index that
  939. int bracketCount = 0;
  940. for (int i = expression.length() - 1; i>=0 ; i--) {
  941. char at = expression.charAt(i);
  942. switch (at) {
  943. case PropertyUtils.NESTED_DELIM:
  944. if (bracketCount < 1) {
  945. return i;
  946. }
  947. break;
  948. case PropertyUtils.MAPPED_DELIM:
  949. case PropertyUtils.INDEXED_DELIM:
  950. // not bothered which
  951. --bracketCount;
  952. break;
  953. case PropertyUtils.MAPPED_DELIM2:
  954. case PropertyUtils.INDEXED_DELIM2:
  955. // not bothered which
  956. ++bracketCount;
  957. break;
  958. }
  959. }
  960. // can't find any
  961. return -1;
  962. }
  963. /**
  964. * Gets the <code>ConvertUtilsBean</code> instance used to perform the conversions.
  965. */
  966. public ConvertUtilsBean getConvertUtils() {
  967. return convertUtilsBean;
  968. }
  969. /**
  970. * Gets the <code>PropertyUtilsBean</code> instance used to access properties.
  971. */
  972. public PropertyUtilsBean getPropertyUtils() {
  973. return propertyUtilsBean;
  974. }
  975. }