0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.commons.beanutils;
0019:
0020: import java.beans.IndexedPropertyDescriptor;
0021: import java.beans.PropertyDescriptor;
0022: import java.lang.reflect.Array;
0023: import java.lang.reflect.InvocationTargetException;
0024: import java.lang.reflect.Method;
0025: import java.util.ArrayList;
0026: import java.util.Collection;
0027: import java.util.HashMap;
0028: import java.util.Iterator;
0029: import java.util.Map;
0030:
0031: import org.apache.commons.beanutils.expression.Resolver;
0032: import org.apache.commons.logging.Log;
0033: import org.apache.commons.logging.LogFactory;
0034:
0035: /**
0036: * <p>JavaBean property population methods.</p>
0037: *
0038: * <p>This class provides implementations for the utility methods in
0039: * {@link BeanUtils}.
0040: * Different instances can be used to isolate caches between classloaders
0041: * and to vary the value converters registered.</p>
0042: *
0043: * @author Craig R. McClanahan
0044: * @author Ralph Schaer
0045: * @author Chris Audley
0046: * @author Rey Francois
0047: * @author Gregor Rayman
0048: * @version $Revision: 556229 $ $Date: 2007-07-14 07:11:19 +0100 (Sat, 14 Jul 2007) $
0049: * @see BeanUtils
0050: * @since 1.7
0051: */
0052:
0053: public class BeanUtilsBean {
0054:
0055: // ------------------------------------------------------ Private Class Variables
0056:
0057: /**
0058: * Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
0059: */
0060: private static final ContextClassLoaderLocal BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal() {
0061: // Creates the default instance used when the context classloader is unavailable
0062: protected Object initialValue() {
0063: return new BeanUtilsBean();
0064: }
0065: };
0066:
0067: /**
0068: * Gets the instance which provides the functionality for {@link BeanUtils}.
0069: * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
0070: * This mechanism provides isolation for web apps deployed in the same container.
0071: *
0072: * @return The (pseudo-singleton) BeanUtils bean instance
0073: */
0074: public static BeanUtilsBean getInstance() {
0075: return (BeanUtilsBean) BEANS_BY_CLASSLOADER.get();
0076: }
0077:
0078: /**
0079: * Sets the instance which provides the functionality for {@link BeanUtils}.
0080: * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
0081: * This mechanism provides isolation for web apps deployed in the same container.
0082: *
0083: * @param newInstance The (pseudo-singleton) BeanUtils bean instance
0084: */
0085: public static void setInstance(BeanUtilsBean newInstance) {
0086: BEANS_BY_CLASSLOADER.set(newInstance);
0087: }
0088:
0089: // --------------------------------------------------------- Attributes
0090:
0091: /**
0092: * Logging for this instance
0093: */
0094: private Log log = LogFactory.getLog(BeanUtils.class);
0095:
0096: /** Used to perform conversions between object types when setting properties */
0097: private ConvertUtilsBean convertUtilsBean;
0098:
0099: /** Used to access properties*/
0100: private PropertyUtilsBean propertyUtilsBean;
0101:
0102: /** A reference to Throwable's initCause method, or null if it's not there in this JVM */
0103: private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();
0104:
0105: // --------------------------------------------------------- Constuctors
0106:
0107: /**
0108: * <p>Constructs an instance using new property
0109: * and conversion instances.</p>
0110: */
0111: public BeanUtilsBean() {
0112: this (new ConvertUtilsBean(), new PropertyUtilsBean());
0113: }
0114:
0115: /**
0116: * <p>Constructs an instance using given conversion instances
0117: * and new {@link PropertyUtilsBean} instance.</p>
0118: *
0119: * @param convertUtilsBean use this <code>ConvertUtilsBean</code>
0120: * to perform conversions from one object to another
0121: *
0122: * @since 1.8.0
0123: */
0124: public BeanUtilsBean(ConvertUtilsBean convertUtilsBean) {
0125: this (convertUtilsBean, new PropertyUtilsBean());
0126: }
0127:
0128: /**
0129: * <p>Constructs an instance using given property and conversion instances.</p>
0130: *
0131: * @param convertUtilsBean use this <code>ConvertUtilsBean</code>
0132: * to perform conversions from one object to another
0133: * @param propertyUtilsBean use this <code>PropertyUtilsBean</code>
0134: * to access properties
0135: */
0136: public BeanUtilsBean(ConvertUtilsBean convertUtilsBean,
0137: PropertyUtilsBean propertyUtilsBean) {
0138:
0139: this .convertUtilsBean = convertUtilsBean;
0140: this .propertyUtilsBean = propertyUtilsBean;
0141: }
0142:
0143: // --------------------------------------------------------- Public Methods
0144:
0145: /**
0146: * <p>Clone a bean based on the available property getters and setters,
0147: * even if the bean class itself does not implement Cloneable.</p>
0148: *
0149: * <p>
0150: * <strong>Note:</strong> this method creates a <strong>shallow</strong> clone.
0151: * In other words, any objects referred to by the bean are shared with the clone
0152: * rather than being cloned in turn.
0153: * </p>
0154: *
0155: * @param bean Bean to be cloned
0156: * @return the cloned bean
0157: *
0158: * @exception IllegalAccessException if the caller does not have
0159: * access to the property accessor method
0160: * @exception InstantiationException if a new instance of the bean's
0161: * class cannot be instantiated
0162: * @exception InvocationTargetException if the property accessor method
0163: * throws an exception
0164: * @exception NoSuchMethodException if an accessor method for this
0165: * property cannot be found
0166: */
0167: public Object cloneBean(Object bean) throws IllegalAccessException,
0168: InstantiationException, InvocationTargetException,
0169: NoSuchMethodException {
0170:
0171: if (log.isDebugEnabled()) {
0172: log.debug("Cloning bean: " + bean.getClass().getName());
0173: }
0174: Object newBean = null;
0175: if (bean instanceof DynaBean) {
0176: newBean = ((DynaBean) bean).getDynaClass().newInstance();
0177: } else {
0178: newBean = bean.getClass().newInstance();
0179: }
0180: getPropertyUtils().copyProperties(newBean, bean);
0181: return (newBean);
0182:
0183: }
0184:
0185: /**
0186: * <p>Copy property values from the origin bean to the destination bean
0187: * for all cases where the property names are the same. For each
0188: * property, a conversion is attempted as necessary. All combinations of
0189: * standard JavaBeans and DynaBeans as origin and destination are
0190: * supported. Properties that exist in the origin bean, but do not exist
0191: * in the destination bean (or are read-only in the destination bean) are
0192: * silently ignored.</p>
0193: *
0194: * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
0195: * to contain String-valued <strong>simple</strong> property names as the keys, pointing at
0196: * the corresponding property values that will be converted (if necessary)
0197: * and set in the destination bean. <strong>Note</strong> that this method
0198: * is intended to perform a "shallow copy" of the properties and so complex
0199: * properties (for example, nested ones) will not be copied.</p>
0200: *
0201: * <p>This method differs from <code>populate()</code>, which
0202: * was primarily designed for populating JavaBeans from the map of request
0203: * parameters retrieved on an HTTP request, is that no scalar->indexed
0204: * or indexed->scalar manipulations are performed. If the origin property
0205: * is indexed, the destination property must be also.</p>
0206: *
0207: * <p>If you know that no type conversions are required, the
0208: * <code>copyProperties()</code> method in {@link PropertyUtils} will
0209: * execute faster than this method.</p>
0210: *
0211: * <p><strong>FIXME</strong> - Indexed and mapped properties that do not
0212: * have getter and setter methods for the underlying array or Map are not
0213: * copied by this method.</p>
0214: *
0215: * @param dest Destination bean whose properties are modified
0216: * @param orig Origin bean whose properties are retrieved
0217: *
0218: * @exception IllegalAccessException if the caller does not have
0219: * access to the property accessor method
0220: * @exception IllegalArgumentException if the <code>dest</code> or
0221: * <code>orig</code> argument is null or if the <code>dest</code>
0222: * property type is different from the source type and the relevant
0223: * converter has not been registered.
0224: * @exception InvocationTargetException if the property accessor method
0225: * throws an exception
0226: */
0227: public void copyProperties(Object dest, Object orig)
0228: throws IllegalAccessException, InvocationTargetException {
0229:
0230: // Validate existence of the specified beans
0231: if (dest == null) {
0232: throw new IllegalArgumentException(
0233: "No destination bean specified");
0234: }
0235: if (orig == null) {
0236: throw new IllegalArgumentException(
0237: "No origin bean specified");
0238: }
0239: if (log.isDebugEnabled()) {
0240: log.debug("BeanUtils.copyProperties(" + dest + ", " + orig
0241: + ")");
0242: }
0243:
0244: // Copy the properties, converting as necessary
0245: if (orig instanceof DynaBean) {
0246: DynaProperty[] origDescriptors = ((DynaBean) orig)
0247: .getDynaClass().getDynaProperties();
0248: for (int i = 0; i < origDescriptors.length; i++) {
0249: String name = origDescriptors[i].getName();
0250: // Need to check isReadable() for WrapDynaBean
0251: // (see Jira issue# BEANUTILS-61)
0252: if (getPropertyUtils().isReadable(orig, name)
0253: && getPropertyUtils().isWriteable(dest, name)) {
0254: Object value = ((DynaBean) orig).get(name);
0255: copyProperty(dest, name, value);
0256: }
0257: }
0258: } else if (orig instanceof Map) {
0259: Iterator names = ((Map) orig).keySet().iterator();
0260: while (names.hasNext()) {
0261: String name = (String) names.next();
0262: if (getPropertyUtils().isWriteable(dest, name)) {
0263: Object value = ((Map) orig).get(name);
0264: copyProperty(dest, name, value);
0265: }
0266: }
0267: } else /* if (orig is a standard JavaBean) */{
0268: PropertyDescriptor[] origDescriptors = getPropertyUtils()
0269: .getPropertyDescriptors(orig);
0270: for (int i = 0; i < origDescriptors.length; i++) {
0271: String name = origDescriptors[i].getName();
0272: if ("class".equals(name)) {
0273: continue; // No point in trying to set an object's class
0274: }
0275: if (getPropertyUtils().isReadable(orig, name)
0276: && getPropertyUtils().isWriteable(dest, name)) {
0277: try {
0278: Object value = getPropertyUtils()
0279: .getSimpleProperty(orig, name);
0280: copyProperty(dest, name, value);
0281: } catch (NoSuchMethodException e) {
0282: // Should not happen
0283: }
0284: }
0285: }
0286: }
0287:
0288: }
0289:
0290: /**
0291: * <p>Copy the specified property value to the specified destination bean,
0292: * performing any type conversion that is required. If the specified
0293: * bean does not have a property of the specified name, or the property
0294: * is read only on the destination bean, return without
0295: * doing anything. If you have custom destination property types, register
0296: * {@link Converter}s for them by calling the <code>register()</code>
0297: * method of {@link ConvertUtils}.</p>
0298: *
0299: * <p><strong>IMPLEMENTATION RESTRICTIONS</strong>:</p>
0300: * <ul>
0301: * <li>Does not support destination properties that are indexed,
0302: * but only an indexed setter (as opposed to an array setter)
0303: * is available.</li>
0304: * <li>Does not support destination properties that are mapped,
0305: * but only a keyed setter (as opposed to a Map setter)
0306: * is available.</li>
0307: * <li>The desired property type of a mapped setter cannot be
0308: * determined (since Maps support any data type), so no conversion
0309: * will be performed.</li>
0310: * </ul>
0311: *
0312: * @param bean Bean on which setting is to be performed
0313: * @param name Property name (can be nested/indexed/mapped/combo)
0314: * @param value Value to be set
0315: *
0316: * @exception IllegalAccessException if the caller does not have
0317: * access to the property accessor method
0318: * @exception InvocationTargetException if the property accessor method
0319: * throws an exception
0320: */
0321: public void copyProperty(Object bean, String name, Object value)
0322: throws IllegalAccessException, InvocationTargetException {
0323:
0324: // Trace logging (if enabled)
0325: if (log.isTraceEnabled()) {
0326: StringBuffer sb = new StringBuffer(" copyProperty(");
0327: sb.append(bean);
0328: sb.append(", ");
0329: sb.append(name);
0330: sb.append(", ");
0331: if (value == null) {
0332: sb.append("<NULL>");
0333: } else if (value instanceof String) {
0334: sb.append((String) value);
0335: } else if (value instanceof String[]) {
0336: String[] values = (String[]) value;
0337: sb.append('[');
0338: for (int i = 0; i < values.length; i++) {
0339: if (i > 0) {
0340: sb.append(',');
0341: }
0342: sb.append(values[i]);
0343: }
0344: sb.append(']');
0345: } else {
0346: sb.append(value.toString());
0347: }
0348: sb.append(')');
0349: log.trace(sb.toString());
0350: }
0351:
0352: // Resolve any nested expression to get the actual target bean
0353: Object target = bean;
0354: Resolver resolver = getPropertyUtils().getResolver();
0355: while (resolver.hasNested(name)) {
0356: try {
0357: target = getPropertyUtils().getProperty(target,
0358: resolver.next(name));
0359: name = resolver.remove(name);
0360: } catch (NoSuchMethodException e) {
0361: return; // Skip this property setter
0362: }
0363: }
0364: if (log.isTraceEnabled()) {
0365: log.trace(" Target bean = " + target);
0366: log.trace(" Target name = " + name);
0367: }
0368:
0369: // Declare local variables we will require
0370: String propName = resolver.getProperty(name); // Simple name of target property
0371: Class type = null; // Java type of target property
0372: int index = resolver.getIndex(name); // Indexed subscript value (if any)
0373: String key = resolver.getKey(name); // Mapped key value (if any)
0374:
0375: // Calculate the target property type
0376: if (target instanceof DynaBean) {
0377: DynaClass dynaClass = ((DynaBean) target).getDynaClass();
0378: DynaProperty dynaProperty = dynaClass
0379: .getDynaProperty(propName);
0380: if (dynaProperty == null) {
0381: return; // Skip this property setter
0382: }
0383: type = dynaProperty.getType();
0384: } else {
0385: PropertyDescriptor descriptor = null;
0386: try {
0387: descriptor = getPropertyUtils().getPropertyDescriptor(
0388: target, name);
0389: if (descriptor == null) {
0390: return; // Skip this property setter
0391: }
0392: } catch (NoSuchMethodException e) {
0393: return; // Skip this property setter
0394: }
0395: type = descriptor.getPropertyType();
0396: if (type == null) {
0397: // Most likely an indexed setter on a POJB only
0398: if (log.isTraceEnabled()) {
0399: log.trace(" target type for property '"
0400: + propName
0401: + "' is null, so skipping ths setter");
0402: }
0403: return;
0404: }
0405: }
0406: if (log.isTraceEnabled()) {
0407: log.trace(" target propName=" + propName + ", type="
0408: + type + ", index=" + index + ", key=" + key);
0409: }
0410:
0411: // Convert the specified value to the required type and store it
0412: if (index >= 0) { // Destination must be indexed
0413: value = convert(value, type.getComponentType());
0414: try {
0415: getPropertyUtils().setIndexedProperty(target, propName,
0416: index, value);
0417: } catch (NoSuchMethodException e) {
0418: throw new InvocationTargetException(e, "Cannot set "
0419: + propName);
0420: }
0421: } else if (key != null) { // Destination must be mapped
0422: // Maps do not know what the preferred data type is,
0423: // so perform no conversions at all
0424: // FIXME - should we create or support a TypedMap?
0425: try {
0426: getPropertyUtils().setMappedProperty(target, propName,
0427: key, value);
0428: } catch (NoSuchMethodException e) {
0429: throw new InvocationTargetException(e, "Cannot set "
0430: + propName);
0431: }
0432: } else { // Destination must be simple
0433: value = convert(value, type);
0434: try {
0435: getPropertyUtils().setSimpleProperty(target, propName,
0436: value);
0437: } catch (NoSuchMethodException e) {
0438: throw new InvocationTargetException(e, "Cannot set "
0439: + propName);
0440: }
0441: }
0442:
0443: }
0444:
0445: /**
0446: * <p>Return the entire set of properties for which the specified bean
0447: * provides a read method. This map contains the to <code>String</code>
0448: * converted property values for all properties for which a read method
0449: * is provided (i.e. where the getReadMethod() returns non-null).</p>
0450: *
0451: * <p>This map can be fed back to a call to
0452: * <code>BeanUtils.populate()</code> to reconsitute the same set of
0453: * properties, modulo differences for read-only and write-only
0454: * properties, but only if there are no indexed properties.</p>
0455: *
0456: * <p><strong>Warning:</strong> if any of the bean property implementations
0457: * contain (directly or indirectly) a call to this method then
0458: * a stack overflow may result. For example:
0459: * <code><pre>
0460: * class MyBean
0461: * {
0462: * public Map getParameterMap()
0463: * {
0464: * BeanUtils.describe(this);
0465: * }
0466: * }
0467: * </pre></code>
0468: * will result in an infinite regression when <code>getParametersMap</code>
0469: * is called. It is recommended that such methods are given alternative
0470: * names (for example, <code>parametersMap</code>).
0471: * </p>
0472: * @param bean Bean whose properties are to be extracted
0473: * @return Map of property descriptors
0474: *
0475: * @exception IllegalAccessException if the caller does not have
0476: * access to the property accessor method
0477: * @exception InvocationTargetException if the property accessor method
0478: * throws an exception
0479: * @exception NoSuchMethodException if an accessor method for this
0480: * property cannot be found
0481: */
0482: public Map describe(Object bean) throws IllegalAccessException,
0483: InvocationTargetException, NoSuchMethodException {
0484:
0485: if (bean == null) {
0486: // return (Collections.EMPTY_MAP);
0487: return (new java.util.HashMap());
0488: }
0489:
0490: if (log.isDebugEnabled()) {
0491: log.debug("Describing bean: " + bean.getClass().getName());
0492: }
0493:
0494: Map description = new HashMap();
0495: if (bean instanceof DynaBean) {
0496: DynaProperty[] descriptors = ((DynaBean) bean)
0497: .getDynaClass().getDynaProperties();
0498: for (int i = 0; i < descriptors.length; i++) {
0499: String name = descriptors[i].getName();
0500: description.put(name, getProperty(bean, name));
0501: }
0502: } else {
0503: PropertyDescriptor[] descriptors = getPropertyUtils()
0504: .getPropertyDescriptors(bean);
0505: for (int i = 0; i < descriptors.length; i++) {
0506: String name = descriptors[i].getName();
0507: if (getPropertyUtils().getReadMethod(descriptors[i]) != null) {
0508: description.put(name, getProperty(bean, name));
0509: }
0510: }
0511: }
0512: return (description);
0513:
0514: }
0515:
0516: /**
0517: * Return the value of the specified array property of the specified
0518: * bean, as a String array.
0519: *
0520: * @param bean Bean whose property is to be extracted
0521: * @param name Name of the property to be extracted
0522: * @return The array property value
0523: *
0524: * @exception IllegalAccessException if the caller does not have
0525: * access to the property accessor method
0526: * @exception InvocationTargetException if the property accessor method
0527: * throws an exception
0528: * @exception NoSuchMethodException if an accessor method for this
0529: * property cannot be found
0530: */
0531: public String[] getArrayProperty(Object bean, String name)
0532: throws IllegalAccessException, InvocationTargetException,
0533: NoSuchMethodException {
0534:
0535: Object value = getPropertyUtils().getProperty(bean, name);
0536: if (value == null) {
0537: return (null);
0538: } else if (value instanceof Collection) {
0539: ArrayList values = new ArrayList();
0540: Iterator items = ((Collection) value).iterator();
0541: while (items.hasNext()) {
0542: Object item = items.next();
0543: if (item == null) {
0544: values.add((String) null);
0545: } else {
0546: // convert to string using convert utils
0547: values.add(getConvertUtils().convert(item));
0548: }
0549: }
0550: return ((String[]) values
0551: .toArray(new String[values.size()]));
0552: } else if (value.getClass().isArray()) {
0553: int n = Array.getLength(value);
0554: String[] results = new String[n];
0555: for (int i = 0; i < n; i++) {
0556: Object item = Array.get(value, i);
0557: if (item == null) {
0558: results[i] = null;
0559: } else {
0560: // convert to string using convert utils
0561: results[i] = getConvertUtils().convert(item);
0562: }
0563: }
0564: return (results);
0565: } else {
0566: String[] results = new String[1];
0567: results[0] = getConvertUtils().convert(value);
0568: return (results);
0569: }
0570:
0571: }
0572:
0573: /**
0574: * Return the value of the specified indexed property of the specified
0575: * bean, as a String. The zero-relative index of the
0576: * required value must be included (in square brackets) as a suffix to
0577: * the property name, or <code>IllegalArgumentException</code> will be
0578: * thrown.
0579: *
0580: * @param bean Bean whose property is to be extracted
0581: * @param name <code>propertyname[index]</code> of the property value
0582: * to be extracted
0583: * @return The indexed property's value, converted to a String
0584: *
0585: * @exception IllegalAccessException if the caller does not have
0586: * access to the property accessor method
0587: * @exception InvocationTargetException if the property accessor method
0588: * throws an exception
0589: * @exception NoSuchMethodException if an accessor method for this
0590: * property cannot be found
0591: */
0592: public String getIndexedProperty(Object bean, String name)
0593: throws IllegalAccessException, InvocationTargetException,
0594: NoSuchMethodException {
0595:
0596: Object value = getPropertyUtils()
0597: .getIndexedProperty(bean, name);
0598: return (getConvertUtils().convert(value));
0599:
0600: }
0601:
0602: /**
0603: * Return the value of the specified indexed property of the specified
0604: * bean, as a String. The index is specified as a method parameter and
0605: * must *not* be included in the property name expression
0606: *
0607: * @param bean Bean whose property is to be extracted
0608: * @param name Simple property name of the property value to be extracted
0609: * @param index Index of the property value to be extracted
0610: * @return The indexed property's value, converted to a String
0611: *
0612: * @exception IllegalAccessException if the caller does not have
0613: * access to the property accessor method
0614: * @exception InvocationTargetException if the property accessor method
0615: * throws an exception
0616: * @exception NoSuchMethodException if an accessor method for this
0617: * property cannot be found
0618: */
0619: public String getIndexedProperty(Object bean, String name, int index)
0620: throws IllegalAccessException, InvocationTargetException,
0621: NoSuchMethodException {
0622:
0623: Object value = getPropertyUtils().getIndexedProperty(bean,
0624: name, index);
0625: return (getConvertUtils().convert(value));
0626:
0627: }
0628:
0629: /**
0630: * Return the value of the specified indexed property of the specified
0631: * bean, as a String. The String-valued key of the required value
0632: * must be included (in parentheses) as a suffix to
0633: * the property name, or <code>IllegalArgumentException</code> will be
0634: * thrown.
0635: *
0636: * @param bean Bean whose property is to be extracted
0637: * @param name <code>propertyname(index)</code> of the property value
0638: * to be extracted
0639: * @return The mapped property's value, converted to a String
0640: *
0641: * @exception IllegalAccessException if the caller does not have
0642: * access to the property accessor method
0643: * @exception InvocationTargetException if the property accessor method
0644: * throws an exception
0645: * @exception NoSuchMethodException if an accessor method for this
0646: * property cannot be found
0647: */
0648: public String getMappedProperty(Object bean, String name)
0649: throws IllegalAccessException, InvocationTargetException,
0650: NoSuchMethodException {
0651:
0652: Object value = getPropertyUtils().getMappedProperty(bean, name);
0653: return (getConvertUtils().convert(value));
0654:
0655: }
0656:
0657: /**
0658: * Return the value of the specified mapped property of the specified
0659: * bean, as a String. The key is specified as a method parameter and
0660: * must *not* be included in the property name expression
0661: *
0662: * @param bean Bean whose property is to be extracted
0663: * @param name Simple property name of the property value to be extracted
0664: * @param key Lookup key of the property value to be extracted
0665: * @return The mapped property's value, converted to a String
0666: *
0667: * @exception IllegalAccessException if the caller does not have
0668: * access to the property accessor method
0669: * @exception InvocationTargetException if the property accessor method
0670: * throws an exception
0671: * @exception NoSuchMethodException if an accessor method for this
0672: * property cannot be found
0673: */
0674: public String getMappedProperty(Object bean, String name, String key)
0675: throws IllegalAccessException, InvocationTargetException,
0676: NoSuchMethodException {
0677:
0678: Object value = getPropertyUtils().getMappedProperty(bean, name,
0679: key);
0680: return (getConvertUtils().convert(value));
0681:
0682: }
0683:
0684: /**
0685: * Return the value of the (possibly nested) property of the specified
0686: * name, for the specified bean, as a String.
0687: *
0688: * @param bean Bean whose property is to be extracted
0689: * @param name Possibly nested name of the property to be extracted
0690: * @return The nested property's value, converted to a String
0691: *
0692: * @exception IllegalAccessException if the caller does not have
0693: * access to the property accessor method
0694: * @exception IllegalArgumentException if a nested reference to a
0695: * property returns null
0696: * @exception InvocationTargetException if the property accessor method
0697: * throws an exception
0698: * @exception NoSuchMethodException if an accessor method for this
0699: * property cannot be found
0700: */
0701: public String getNestedProperty(Object bean, String name)
0702: throws IllegalAccessException, InvocationTargetException,
0703: NoSuchMethodException {
0704:
0705: Object value = getPropertyUtils().getNestedProperty(bean, name);
0706: return (getConvertUtils().convert(value));
0707:
0708: }
0709:
0710: /**
0711: * Return the value of the specified property of the specified bean,
0712: * no matter which property reference format is used, as a String.
0713: *
0714: * @param bean Bean whose property is to be extracted
0715: * @param name Possibly indexed and/or nested name of the property
0716: * to be extracted
0717: * @return The property's value, converted to a String
0718: *
0719: * @exception IllegalAccessException if the caller does not have
0720: * access to the property accessor method
0721: * @exception InvocationTargetException if the property accessor method
0722: * throws an exception
0723: * @exception NoSuchMethodException if an accessor method for this
0724: * property cannot be found
0725: */
0726: public String getProperty(Object bean, String name)
0727: throws IllegalAccessException, InvocationTargetException,
0728: NoSuchMethodException {
0729:
0730: return (getNestedProperty(bean, name));
0731:
0732: }
0733:
0734: /**
0735: * Return the value of the specified simple property of the specified
0736: * bean, converted to a String.
0737: *
0738: * @param bean Bean whose property is to be extracted
0739: * @param name Name of the property to be extracted
0740: * @return The property's value, converted to a String
0741: *
0742: * @exception IllegalAccessException if the caller does not have
0743: * access to the property accessor method
0744: * @exception InvocationTargetException if the property accessor method
0745: * throws an exception
0746: * @exception NoSuchMethodException if an accessor method for this
0747: * property cannot be found
0748: */
0749: public String getSimpleProperty(Object bean, String name)
0750: throws IllegalAccessException, InvocationTargetException,
0751: NoSuchMethodException {
0752:
0753: Object value = getPropertyUtils().getSimpleProperty(bean, name);
0754: return (getConvertUtils().convert(value));
0755:
0756: }
0757:
0758: /**
0759: * <p>Populate the JavaBeans properties of the specified bean, based on
0760: * the specified name/value pairs. This method uses Java reflection APIs
0761: * to identify corresponding "property setter" method names, and deals
0762: * with setter arguments of type <code>String</code>, <code>boolean</code>,
0763: * <code>int</code>, <code>long</code>, <code>float</code>, and
0764: * <code>double</code>. In addition, array setters for these types (or the
0765: * corresponding primitive types) can also be identified.</p>
0766: *
0767: * <p>The particular setter method to be called for each property is
0768: * determined using the usual JavaBeans introspection mechanisms. Thus,
0769: * you may identify custom setter methods using a BeanInfo class that is
0770: * associated with the class of the bean itself. If no such BeanInfo
0771: * class is available, the standard method name conversion ("set" plus
0772: * the capitalized name of the property in question) is used.</p>
0773: *
0774: * <p><strong>NOTE</strong>: It is contrary to the JavaBeans Specification
0775: * to have more than one setter method (with different argument
0776: * signatures) for the same property.</p>
0777: *
0778: * <p><strong>WARNING</strong> - The logic of this method is customized
0779: * for extracting String-based request parameters from an HTTP request.
0780: * It is probably not what you want for general property copying with
0781: * type conversion. For that purpose, check out the
0782: * <code>copyProperties()</code> method instead.</p>
0783: *
0784: * @param bean JavaBean whose properties are being populated
0785: * @param properties Map keyed by property name, with the
0786: * corresponding (String or String[]) value(s) to be set
0787: *
0788: * @exception IllegalAccessException if the caller does not have
0789: * access to the property accessor method
0790: * @exception InvocationTargetException if the property accessor method
0791: * throws an exception
0792: */
0793: public void populate(Object bean, Map properties)
0794: throws IllegalAccessException, InvocationTargetException {
0795:
0796: // Do nothing unless both arguments have been specified
0797: if ((bean == null) || (properties == null)) {
0798: return;
0799: }
0800: if (log.isDebugEnabled()) {
0801: log.debug("BeanUtils.populate(" + bean + ", " + properties
0802: + ")");
0803: }
0804:
0805: // Loop through the property name/value pairs to be set
0806: Iterator names = properties.keySet().iterator();
0807: while (names.hasNext()) {
0808:
0809: // Identify the property name and value(s) to be assigned
0810: String name = (String) names.next();
0811: if (name == null) {
0812: continue;
0813: }
0814: Object value = properties.get(name);
0815:
0816: // Perform the assignment for this property
0817: setProperty(bean, name, value);
0818:
0819: }
0820:
0821: }
0822:
0823: /**
0824: * <p>Set the specified property value, performing type conversions as
0825: * required to conform to the type of the destination property.</p>
0826: *
0827: * <p>If the property is read only then the method returns
0828: * without throwing an exception.</p>
0829: *
0830: * <p>If <code>null</code> is passed into a property expecting a primitive value,
0831: * then this will be converted as if it were a <code>null</code> string.</p>
0832: *
0833: * <p><strong>WARNING</strong> - The logic of this method is customized
0834: * to meet the needs of <code>populate()</code>, and is probably not what
0835: * you want for general property copying with type conversion. For that
0836: * purpose, check out the <code>copyProperty()</code> method instead.</p>
0837: *
0838: * <p><strong>WARNING</strong> - PLEASE do not modify the behavior of this
0839: * method without consulting with the Struts developer community. There
0840: * are some subtleties to its functionality that are not documented in the
0841: * Javadoc description above, yet are vital to the way that Struts utilizes
0842: * this method.</p>
0843: *
0844: * @param bean Bean on which setting is to be performed
0845: * @param name Property name (can be nested/indexed/mapped/combo)
0846: * @param value Value to be set
0847: *
0848: * @exception IllegalAccessException if the caller does not have
0849: * access to the property accessor method
0850: * @exception InvocationTargetException if the property accessor method
0851: * throws an exception
0852: */
0853: public void setProperty(Object bean, String name, Object value)
0854: throws IllegalAccessException, InvocationTargetException {
0855:
0856: // Trace logging (if enabled)
0857: if (log.isTraceEnabled()) {
0858: StringBuffer sb = new StringBuffer(" setProperty(");
0859: sb.append(bean);
0860: sb.append(", ");
0861: sb.append(name);
0862: sb.append(", ");
0863: if (value == null) {
0864: sb.append("<NULL>");
0865: } else if (value instanceof String) {
0866: sb.append((String) value);
0867: } else if (value instanceof String[]) {
0868: String[] values = (String[]) value;
0869: sb.append('[');
0870: for (int i = 0; i < values.length; i++) {
0871: if (i > 0) {
0872: sb.append(',');
0873: }
0874: sb.append(values[i]);
0875: }
0876: sb.append(']');
0877: } else {
0878: sb.append(value.toString());
0879: }
0880: sb.append(')');
0881: log.trace(sb.toString());
0882: }
0883:
0884: // Resolve any nested expression to get the actual target bean
0885: Object target = bean;
0886: Resolver resolver = getPropertyUtils().getResolver();
0887: while (resolver.hasNested(name)) {
0888: try {
0889: target = getPropertyUtils().getProperty(target,
0890: resolver.next(name));
0891: name = resolver.remove(name);
0892: } catch (NoSuchMethodException e) {
0893: return; // Skip this property setter
0894: }
0895: }
0896: if (log.isTraceEnabled()) {
0897: log.trace(" Target bean = " + target);
0898: log.trace(" Target name = " + name);
0899: }
0900:
0901: // Declare local variables we will require
0902: String propName = resolver.getProperty(name); // Simple name of target property
0903: Class type = null; // Java type of target property
0904: int index = resolver.getIndex(name); // Indexed subscript value (if any)
0905: String key = resolver.getKey(name); // Mapped key value (if any)
0906:
0907: // Calculate the property type
0908: if (target instanceof DynaBean) {
0909: DynaClass dynaClass = ((DynaBean) target).getDynaClass();
0910: DynaProperty dynaProperty = dynaClass
0911: .getDynaProperty(propName);
0912: if (dynaProperty == null) {
0913: return; // Skip this property setter
0914: }
0915: type = dynaProperty.getType();
0916: } else {
0917: PropertyDescriptor descriptor = null;
0918: try {
0919: descriptor = getPropertyUtils().getPropertyDescriptor(
0920: target, name);
0921: if (descriptor == null) {
0922: return; // Skip this property setter
0923: }
0924: } catch (NoSuchMethodException e) {
0925: return; // Skip this property setter
0926: }
0927: if (descriptor instanceof MappedPropertyDescriptor) {
0928: if (((MappedPropertyDescriptor) descriptor)
0929: .getMappedWriteMethod() == null) {
0930: if (log.isDebugEnabled()) {
0931: log.debug("Skipping read-only property");
0932: }
0933: return; // Read-only, skip this property setter
0934: }
0935: type = ((MappedPropertyDescriptor) descriptor)
0936: .getMappedPropertyType();
0937: } else if (index >= 0
0938: && descriptor instanceof IndexedPropertyDescriptor) {
0939: if (((IndexedPropertyDescriptor) descriptor)
0940: .getIndexedWriteMethod() == null) {
0941: if (log.isDebugEnabled()) {
0942: log.debug("Skipping read-only property");
0943: }
0944: return; // Read-only, skip this property setter
0945: }
0946: type = ((IndexedPropertyDescriptor) descriptor)
0947: .getIndexedPropertyType();
0948: } else if (key != null) {
0949: if (descriptor.getReadMethod() == null) {
0950: if (log.isDebugEnabled()) {
0951: log.debug("Skipping read-only property");
0952: }
0953: return; // Read-only, skip this property setter
0954: }
0955: type = (value == null) ? Object.class : value
0956: .getClass();
0957: } else {
0958: if (descriptor.getWriteMethod() == null) {
0959: if (log.isDebugEnabled()) {
0960: log.debug("Skipping read-only property");
0961: }
0962: return; // Read-only, skip this property setter
0963: }
0964: type = descriptor.getPropertyType();
0965: }
0966: }
0967:
0968: // Convert the specified value to the required type
0969: Object newValue = null;
0970: if (type.isArray() && (index < 0)) { // Scalar value into array
0971: if (value == null) {
0972: String[] values = new String[1];
0973: values[0] = (String) value;
0974: newValue = getConvertUtils().convert((String[]) values,
0975: type);
0976: } else if (value instanceof String) {
0977: newValue = getConvertUtils().convert(value, type);
0978: } else if (value instanceof String[]) {
0979: newValue = getConvertUtils().convert((String[]) value,
0980: type);
0981: } else {
0982: newValue = convert(value, type);
0983: }
0984: } else if (type.isArray()) { // Indexed value into array
0985: if (value instanceof String || value == null) {
0986: newValue = getConvertUtils().convert((String) value,
0987: type.getComponentType());
0988: } else if (value instanceof String[]) {
0989: newValue = getConvertUtils().convert(
0990: ((String[]) value)[0], type.getComponentType());
0991: } else {
0992: newValue = convert(value, type.getComponentType());
0993: }
0994: } else { // Value into scalar
0995: if ((value instanceof String) || (value == null)) {
0996: newValue = getConvertUtils().convert((String) value,
0997: type);
0998: } else if (value instanceof String[]) {
0999: newValue = getConvertUtils().convert(
1000: ((String[]) value)[0], type);
1001: } else {
1002: newValue = convert(value, type);
1003: }
1004: }
1005:
1006: // Invoke the setter method
1007: try {
1008: if (index >= 0) {
1009: getPropertyUtils().setIndexedProperty(target, propName,
1010: index, newValue);
1011: } else if (key != null) {
1012: getPropertyUtils().setMappedProperty(target, propName,
1013: key, newValue);
1014: } else {
1015: getPropertyUtils().setProperty(target, propName,
1016: newValue);
1017: }
1018: } catch (NoSuchMethodException e) {
1019: throw new InvocationTargetException(e, "Cannot set "
1020: + propName);
1021: }
1022:
1023: }
1024:
1025: /**
1026: * Gets the <code>ConvertUtilsBean</code> instance used to perform the conversions.
1027: *
1028: * @return The ConvertUtils bean instance
1029: */
1030: public ConvertUtilsBean getConvertUtils() {
1031: return convertUtilsBean;
1032: }
1033:
1034: /**
1035: * Gets the <code>PropertyUtilsBean</code> instance used to access properties.
1036: *
1037: * @return The ConvertUtils bean instance
1038: */
1039: public PropertyUtilsBean getPropertyUtils() {
1040: return propertyUtilsBean;
1041: }
1042:
1043: /**
1044: * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
1045: *
1046: * @param throwable The throwable.
1047: * @param cause The cause of the throwable.
1048: * @return true if the cause was initialized, otherwise false.
1049: */
1050: public boolean initCause(Throwable throwable, Throwable cause) {
1051: if (INIT_CAUSE_METHOD != null && cause != null) {
1052: try {
1053: INIT_CAUSE_METHOD.invoke(throwable,
1054: new Object[] { cause });
1055: return true;
1056: } catch (Throwable e) {
1057: return false; // can't initialize cause
1058: }
1059: }
1060: return false;
1061: }
1062:
1063: /**
1064: * <p>Convert the value to an object of the specified class (if
1065: * possible).</p>
1066: *
1067: * @param value Value to be converted (may be null)
1068: * @param type Class of the value to be converted to
1069: * @return The converted value
1070: *
1071: * @exception ConversionException if thrown by an underlying Converter
1072: */
1073: protected Object convert(Object value, Class type) {
1074: Converter converter = getConvertUtils().lookup(type);
1075: if (converter != null) {
1076: log.trace(" USING CONVERTER " + converter);
1077: return converter.convert(type, value);
1078: } else {
1079: return value;
1080: }
1081: }
1082:
1083: /**
1084: * Returns a <code>Method<code> allowing access to
1085: * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
1086: * or <code>null</code> if the method
1087: * does not exist.
1088: *
1089: * @return A <code>Method<code> for <code>Throwable.initCause</code>, or
1090: * <code>null</code> if unavailable.
1091: */
1092: private static Method getInitCauseMethod() {
1093: try {
1094: Class[] paramsClasses = new Class[] { Throwable.class };
1095: return Throwable.class
1096: .getMethod("initCause", paramsClasses);
1097: } catch (NoSuchMethodException e) {
1098: Log log = LogFactory.getLog(BeanUtils.class);
1099: if (log.isWarnEnabled()) {
1100: log
1101: .warn("Throwable does not have initCause() method in JDK 1.3");
1102: }
1103: return null;
1104: } catch (Throwable e) {
1105: Log log = LogFactory.getLog(BeanUtils.class);
1106: if (log.isWarnEnabled()) {
1107: log
1108: .warn(
1109: "Error getting the Throwable initCause() method",
1110: e);
1111: }
1112: return null;
1113: }
1114: }
1115: }
|