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.BeanInfo;
0021: import java.beans.IndexedPropertyDescriptor;
0022: import java.beans.IntrospectionException;
0023: import java.beans.Introspector;
0024: import java.beans.PropertyDescriptor;
0025: import java.lang.reflect.Array;
0026: import java.lang.reflect.InvocationTargetException;
0027: import java.lang.reflect.Method;
0028: import java.util.HashMap;
0029: import java.util.Iterator;
0030: import java.util.List;
0031: import java.util.Map;
0032:
0033: import org.apache.commons.beanutils.expression.DefaultResolver;
0034: import org.apache.commons.beanutils.expression.Resolver;
0035: import org.apache.commons.collections.FastHashMap;
0036: import org.apache.commons.logging.Log;
0037: import org.apache.commons.logging.LogFactory;
0038:
0039: /**
0040: * Utility methods for using Java Reflection APIs to facilitate generic
0041: * property getter and setter operations on Java objects. Much of this
0042: * code was originally included in <code>BeanUtils</code>, but has been
0043: * separated because of the volume of code involved.
0044: * <p>
0045: * In general, the objects that are examined and modified using these
0046: * methods are expected to conform to the property getter and setter method
0047: * naming conventions described in the JavaBeans Specification (Version 1.0.1).
0048: * No data type conversions are performed, and there are no usage of any
0049: * <code>PropertyEditor</code> classes that have been registered, although
0050: * a convenient way to access the registered classes themselves is included.
0051: * <p>
0052: * For the purposes of this class, five formats for referencing a particular
0053: * property value of a bean are defined, with the <i>default</i> layout of an
0054: * identifying String in parentheses. However the notation for these formats
0055: * and how they are resolved is now (since BeanUtils 1.8.0) controlled by
0056: * the configured {@link Resolver} implementation:
0057: * <ul>
0058: * <li><strong>Simple (<code>name</code>)</strong> - The specified
0059: * <code>name</code> identifies an individual property of a particular
0060: * JavaBean. The name of the actual getter or setter method to be used
0061: * is determined using standard JavaBeans instrospection, so that (unless
0062: * overridden by a <code>BeanInfo</code> class, a property named "xyz"
0063: * will have a getter method named <code>getXyz()</code> or (for boolean
0064: * properties only) <code>isXyz()</code>, and a setter method named
0065: * <code>setXyz()</code>.</li>
0066: * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first
0067: * name element is used to select a property getter, as for simple
0068: * references above. The object returned for this property is then
0069: * consulted, using the same approach, for a property getter for a
0070: * property named <code>name2</code>, and so on. The property value that
0071: * is ultimately retrieved or modified is the one identified by the
0072: * last name element.</li>
0073: * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
0074: * property value is assumed to be an array, or this JavaBean is assumed
0075: * to have indexed property getter and setter methods. The appropriate
0076: * (zero-relative) entry in the array is selected. <code>List</code>
0077: * objects are now also supported for read/write. You simply need to define
0078: * a getter that returns the <code>List</code></li>
0079: * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean
0080: * is assumed to have an property getter and setter methods with an
0081: * additional attribute of type <code>java.lang.String</code>.</li>
0082: * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
0083: * Combining mapped, nested, and indexed references is also
0084: * supported.</li>
0085: * </ul>
0086: *
0087: * @author Craig R. McClanahan
0088: * @author Ralph Schaer
0089: * @author Chris Audley
0090: * @author Rey Francois
0091: * @author Gregor Rayman
0092: * @author Jan Sorensen
0093: * @author Scott Sanders
0094: * @author Erik Meade
0095: * @version $Revision: 557008 $ $Date: 2007-07-17 19:27:26 +0100 (Tue, 17 Jul 2007) $
0096: * @see Resolver
0097: * @see PropertyUtils
0098: * @since 1.7
0099: */
0100:
0101: public class PropertyUtilsBean {
0102:
0103: private Resolver resolver = new DefaultResolver();
0104:
0105: // --------------------------------------------------------- Class Methods
0106:
0107: /**
0108: * Return the PropertyUtils bean instance.
0109: * @return The PropertyUtils bean instance
0110: */
0111: protected static PropertyUtilsBean getInstance() {
0112: return BeanUtilsBean.getInstance().getPropertyUtils();
0113: }
0114:
0115: // --------------------------------------------------------- Variables
0116:
0117: /**
0118: * The cache of PropertyDescriptor arrays for beans we have already
0119: * introspected, keyed by the java.lang.Class of this object.
0120: */
0121: private FastHashMap descriptorsCache = null;
0122: private FastHashMap mappedDescriptorsCache = null;
0123: private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
0124: private static final Class[] LIST_CLASS_PARAMETER = new Class[] { java.util.List.class };
0125:
0126: /** Log instance */
0127: private Log log = LogFactory.getLog(PropertyUtils.class);
0128:
0129: // ---------------------------------------------------------- Constructors
0130:
0131: /** Base constructor */
0132: public PropertyUtilsBean() {
0133: descriptorsCache = new FastHashMap();
0134: descriptorsCache.setFast(true);
0135: mappedDescriptorsCache = new FastHashMap();
0136: mappedDescriptorsCache.setFast(true);
0137: }
0138:
0139: // --------------------------------------------------------- Public Methods
0140:
0141: /**
0142: * Return the configured {@link Resolver} implementation used by BeanUtils.
0143: * <p>
0144: * The {@link Resolver} handles the <i>property name</i>
0145: * expressions and the implementation in use effectively
0146: * controls the dialect of the <i>expression language</i>
0147: * that BeanUtils recongnises.
0148: * <p>
0149: * {@link DefaultResolver} is the default implementation used.
0150: *
0151: * @return resolver The property expression resolver.
0152: */
0153: public Resolver getResolver() {
0154: return resolver;
0155: }
0156:
0157: /**
0158: * Configure the {@link Resolver} implementation used by BeanUtils.
0159: * <p>
0160: * The {@link Resolver} handles the <i>property name</i>
0161: * expressions and the implementation in use effectively
0162: * controls the dialect of the <i>expression language</i>
0163: * that BeanUtils recongnises.
0164: * <p>
0165: * {@link DefaultResolver} is the default implementation used.
0166: *
0167: * @param resolver The property expression resolver.
0168: */
0169: public void setResolver(Resolver resolver) {
0170: if (resolver == null) {
0171: this .resolver = new DefaultResolver();
0172: } else {
0173: this .resolver = resolver;
0174: }
0175: }
0176:
0177: /**
0178: * Clear any cached property descriptors information for all classes
0179: * loaded by any class loaders. This is useful in cases where class
0180: * loaders are thrown away to implement class reloading.
0181: */
0182: public void clearDescriptors() {
0183:
0184: descriptorsCache.clear();
0185: mappedDescriptorsCache.clear();
0186: Introspector.flushCaches();
0187:
0188: }
0189:
0190: /**
0191: * <p>Copy property values from the "origin" bean to the "destination" bean
0192: * for all cases where the property names are the same (even though the
0193: * actual getter and setter methods might have been customized via
0194: * <code>BeanInfo</code> classes). No conversions are performed on the
0195: * actual property values -- it is assumed that the values retrieved from
0196: * the origin bean are assignment-compatible with the types expected by
0197: * the destination bean.</p>
0198: *
0199: * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
0200: * to contain String-valued <strong>simple</strong> property names as the keys, pointing
0201: * at the corresponding property values that will be set in the destination
0202: * bean.<strong>Note</strong> that this method is intended to perform
0203: * a "shallow copy" of the properties and so complex properties
0204: * (for example, nested ones) will not be copied.</p>
0205: *
0206: * <p>Note, that this method will not copy a List to a List, or an Object[]
0207: * to an Object[]. It's specifically for copying JavaBean properties. </p>
0208: *
0209: * @param dest Destination bean whose properties are modified
0210: * @param orig Origin bean whose properties are retrieved
0211: *
0212: * @exception IllegalAccessException if the caller does not have
0213: * access to the property accessor method
0214: * @exception IllegalArgumentException if the <code>dest</code> or
0215: * <code>orig</code> argument is null
0216: * @exception InvocationTargetException if the property accessor method
0217: * throws an exception
0218: * @exception NoSuchMethodException if an accessor method for this
0219: * propety cannot be found
0220: */
0221: public void copyProperties(Object dest, Object orig)
0222: throws IllegalAccessException, InvocationTargetException,
0223: NoSuchMethodException {
0224:
0225: if (dest == null) {
0226: throw new IllegalArgumentException(
0227: "No destination bean specified");
0228: }
0229: if (orig == null) {
0230: throw new IllegalArgumentException(
0231: "No origin bean specified");
0232: }
0233:
0234: if (orig instanceof DynaBean) {
0235: DynaProperty[] origDescriptors = ((DynaBean) orig)
0236: .getDynaClass().getDynaProperties();
0237: for (int i = 0; i < origDescriptors.length; i++) {
0238: String name = origDescriptors[i].getName();
0239: if (isReadable(orig, name) && isWriteable(dest, name)) {
0240: try {
0241: Object value = ((DynaBean) orig).get(name);
0242: if (dest instanceof DynaBean) {
0243: ((DynaBean) dest).set(name, value);
0244: } else {
0245: setSimpleProperty(dest, name, value);
0246: }
0247: } catch (NoSuchMethodException e) {
0248: if (log.isDebugEnabled()) {
0249: log.debug("Error writing to '" + name
0250: + "' on class '" + dest.getClass()
0251: + "'", e);
0252: }
0253: }
0254: }
0255: }
0256: } else if (orig instanceof Map) {
0257: Iterator names = ((Map) orig).keySet().iterator();
0258: while (names.hasNext()) {
0259: String name = (String) names.next();
0260: if (isWriteable(dest, name)) {
0261: try {
0262: Object value = ((Map) orig).get(name);
0263: if (dest instanceof DynaBean) {
0264: ((DynaBean) dest).set(name, value);
0265: } else {
0266: setSimpleProperty(dest, name, value);
0267: }
0268: } catch (NoSuchMethodException e) {
0269: if (log.isDebugEnabled()) {
0270: log.debug("Error writing to '" + name
0271: + "' on class '" + dest.getClass()
0272: + "'", e);
0273: }
0274: }
0275: }
0276: }
0277: } else /* if (orig is a standard JavaBean) */{
0278: PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
0279: for (int i = 0; i < origDescriptors.length; i++) {
0280: String name = origDescriptors[i].getName();
0281: if (isReadable(orig, name) && isWriteable(dest, name)) {
0282: try {
0283: Object value = getSimpleProperty(orig, name);
0284: if (dest instanceof DynaBean) {
0285: ((DynaBean) dest).set(name, value);
0286: } else {
0287: setSimpleProperty(dest, name, value);
0288: }
0289: } catch (NoSuchMethodException e) {
0290: if (log.isDebugEnabled()) {
0291: log.debug("Error writing to '" + name
0292: + "' on class '" + dest.getClass()
0293: + "'", e);
0294: }
0295: }
0296: }
0297: }
0298: }
0299:
0300: }
0301:
0302: /**
0303: * <p>Return the entire set of properties for which the specified bean
0304: * provides a read method. This map contains the unconverted property
0305: * values for all properties for which a read method is provided
0306: * (i.e. where the <code>getReadMethod()</code> returns non-null).</p>
0307: *
0308: * <p><strong>FIXME</strong> - Does not account for mapped properties.</p>
0309: *
0310: * @param bean Bean whose properties are to be extracted
0311: * @return The set of properties for the bean
0312: *
0313: * @exception IllegalAccessException if the caller does not have
0314: * access to the property accessor method
0315: * @exception IllegalArgumentException if <code>bean</code> is null
0316: * @exception InvocationTargetException if the property accessor method
0317: * throws an exception
0318: * @exception NoSuchMethodException if an accessor method for this
0319: * propety cannot be found
0320: */
0321: public Map describe(Object bean) throws IllegalAccessException,
0322: InvocationTargetException, NoSuchMethodException {
0323:
0324: if (bean == null) {
0325: throw new IllegalArgumentException("No bean specified");
0326: }
0327: Map description = new HashMap();
0328: if (bean instanceof DynaBean) {
0329: DynaProperty[] descriptors = ((DynaBean) bean)
0330: .getDynaClass().getDynaProperties();
0331: for (int i = 0; i < descriptors.length; i++) {
0332: String name = descriptors[i].getName();
0333: description.put(name, getProperty(bean, name));
0334: }
0335: } else {
0336: PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
0337: for (int i = 0; i < descriptors.length; i++) {
0338: String name = descriptors[i].getName();
0339: if (descriptors[i].getReadMethod() != null) {
0340: description.put(name, getProperty(bean, name));
0341: }
0342: }
0343: }
0344: return (description);
0345:
0346: }
0347:
0348: /**
0349: * Return the value of the specified indexed property of the specified
0350: * bean, with no type conversions. The zero-relative index of the
0351: * required value must be included (in square brackets) as a suffix to
0352: * the property name, or <code>IllegalArgumentException</code> will be
0353: * thrown. In addition to supporting the JavaBeans specification, this
0354: * method has been extended to support <code>List</code> objects as well.
0355: *
0356: * @param bean Bean whose property is to be extracted
0357: * @param name <code>propertyname[index]</code> of the property value
0358: * to be extracted
0359: * @return the indexed property value
0360: *
0361: * @exception IndexOutOfBoundsException if the specified index
0362: * is outside the valid range for the underlying array or List
0363: * @exception IllegalAccessException if the caller does not have
0364: * access to the property accessor method
0365: * @exception IllegalArgumentException if <code>bean</code> or
0366: * <code>name</code> is null
0367: * @exception InvocationTargetException if the property accessor method
0368: * throws an exception
0369: * @exception NoSuchMethodException if an accessor method for this
0370: * propety cannot be found
0371: */
0372: public Object getIndexedProperty(Object bean, String name)
0373: throws IllegalAccessException, InvocationTargetException,
0374: NoSuchMethodException {
0375:
0376: if (bean == null) {
0377: throw new IllegalArgumentException("No bean specified");
0378: }
0379: if (name == null) {
0380: throw new IllegalArgumentException(
0381: "No name specified for bean class '"
0382: + bean.getClass() + "'");
0383: }
0384:
0385: // Identify the index of the requested individual property
0386: int index = -1;
0387: try {
0388: index = resolver.getIndex(name);
0389: } catch (IllegalArgumentException e) {
0390: throw new IllegalArgumentException(
0391: "Invalid indexed property '" + name
0392: + "' on bean class '" + bean.getClass()
0393: + "' " + e.getMessage());
0394: }
0395: if (index < 0) {
0396: throw new IllegalArgumentException(
0397: "Invalid indexed property '" + name
0398: + "' on bean class '" + bean.getClass()
0399: + "'");
0400: }
0401:
0402: // Isolate the name
0403: name = resolver.getProperty(name);
0404:
0405: // Request the specified indexed property value
0406: return (getIndexedProperty(bean, name, index));
0407:
0408: }
0409:
0410: /**
0411: * Return the value of the specified indexed property of the specified
0412: * bean, with no type conversions. In addition to supporting the JavaBeans
0413: * specification, this method has been extended to support
0414: * <code>List</code> objects as well.
0415: *
0416: * @param bean Bean whose property is to be extracted
0417: * @param name Simple property name of the property value to be extracted
0418: * @param index Index of the property value to be extracted
0419: * @return the indexed property value
0420: *
0421: * @exception IndexOutOfBoundsException if the specified index
0422: * is outside the valid range for the underlying property
0423: * @exception IllegalAccessException if the caller does not have
0424: * access to the property accessor method
0425: * @exception IllegalArgumentException if <code>bean</code> or
0426: * <code>name</code> is null
0427: * @exception InvocationTargetException if the property accessor method
0428: * throws an exception
0429: * @exception NoSuchMethodException if an accessor method for this
0430: * propety cannot be found
0431: */
0432: public Object getIndexedProperty(Object bean, String name, int index)
0433: throws IllegalAccessException, InvocationTargetException,
0434: NoSuchMethodException {
0435:
0436: if (bean == null) {
0437: throw new IllegalArgumentException("No bean specified");
0438: }
0439: if (name == null || name.length() == 0) {
0440: if (bean.getClass().isArray()) {
0441: return Array.get(bean, index);
0442: } else if (bean instanceof List) {
0443: return ((List) bean).get(index);
0444: }
0445: }
0446: if (name == null) {
0447: throw new IllegalArgumentException(
0448: "No name specified for bean class '"
0449: + bean.getClass() + "'");
0450: }
0451:
0452: // Handle DynaBean instances specially
0453: if (bean instanceof DynaBean) {
0454: DynaProperty descriptor = ((DynaBean) bean).getDynaClass()
0455: .getDynaProperty(name);
0456: if (descriptor == null) {
0457: throw new NoSuchMethodException("Unknown property '"
0458: + name + "' on bean class '" + bean.getClass()
0459: + "'");
0460: }
0461: return (((DynaBean) bean).get(name, index));
0462: }
0463:
0464: // Retrieve the property descriptor for the specified property
0465: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
0466: name);
0467: if (descriptor == null) {
0468: throw new NoSuchMethodException("Unknown property '" + name
0469: + "' on bean class '" + bean.getClass() + "'");
0470: }
0471:
0472: // Call the indexed getter method if there is one
0473: if (descriptor instanceof IndexedPropertyDescriptor) {
0474: Method readMethod = ((IndexedPropertyDescriptor) descriptor)
0475: .getIndexedReadMethod();
0476: readMethod = MethodUtils.getAccessibleMethod(readMethod);
0477: if (readMethod != null) {
0478: Object[] subscript = new Object[1];
0479: subscript[0] = new Integer(index);
0480: try {
0481: return (invokeMethod(readMethod, bean, subscript));
0482: } catch (InvocationTargetException e) {
0483: if (e.getTargetException() instanceof IndexOutOfBoundsException) {
0484: throw (IndexOutOfBoundsException) e
0485: .getTargetException();
0486: } else {
0487: throw e;
0488: }
0489: }
0490: }
0491: }
0492:
0493: // Otherwise, the underlying property must be an array
0494: Method readMethod = getReadMethod(descriptor);
0495: if (readMethod == null) {
0496: throw new NoSuchMethodException("Property '" + name
0497: + "' has no " + "getter method on bean class '"
0498: + bean.getClass() + "'");
0499: }
0500:
0501: // Call the property getter and return the value
0502: Object value = invokeMethod(readMethod, bean, new Object[0]);
0503: if (!value.getClass().isArray()) {
0504: if (!(value instanceof java.util.List)) {
0505: throw new IllegalArgumentException("Property '" + name
0506: + "' is not indexed on bean class '"
0507: + bean.getClass() + "'");
0508: } else {
0509: //get the List's value
0510: return ((java.util.List) value).get(index);
0511: }
0512: } else {
0513: //get the array's value
0514: return (Array.get(value, index));
0515: }
0516:
0517: }
0518:
0519: /**
0520: * Return the value of the specified mapped property of the
0521: * specified bean, with no type conversions. The key of the
0522: * required value must be included (in brackets) as a suffix to
0523: * the property name, or <code>IllegalArgumentException</code> will be
0524: * thrown.
0525: *
0526: * @param bean Bean whose property is to be extracted
0527: * @param name <code>propertyname(key)</code> of the property value
0528: * to be extracted
0529: * @return the mapped property value
0530: *
0531: * @exception IllegalAccessException if the caller does not have
0532: * access to the property accessor method
0533: * @exception InvocationTargetException if the property accessor method
0534: * throws an exception
0535: * @exception NoSuchMethodException if an accessor method for this
0536: * propety cannot be found
0537: */
0538: public Object getMappedProperty(Object bean, String name)
0539: throws IllegalAccessException, InvocationTargetException,
0540: NoSuchMethodException {
0541:
0542: if (bean == null) {
0543: throw new IllegalArgumentException("No bean specified");
0544: }
0545: if (name == null) {
0546: throw new IllegalArgumentException(
0547: "No name specified for bean class '"
0548: + bean.getClass() + "'");
0549: }
0550:
0551: // Identify the key of the requested individual property
0552: String key = null;
0553: try {
0554: key = resolver.getKey(name);
0555: } catch (IllegalArgumentException e) {
0556: throw new IllegalArgumentException(
0557: "Invalid mapped property '" + name
0558: + "' on bean class '" + bean.getClass()
0559: + "' " + e.getMessage());
0560: }
0561: if (key == null) {
0562: throw new IllegalArgumentException(
0563: "Invalid mapped property '" + name
0564: + "' on bean class '" + bean.getClass()
0565: + "'");
0566: }
0567:
0568: // Isolate the name
0569: name = resolver.getProperty(name);
0570:
0571: // Request the specified indexed property value
0572: return (getMappedProperty(bean, name, key));
0573:
0574: }
0575:
0576: /**
0577: * Return the value of the specified mapped property of the specified
0578: * bean, with no type conversions.
0579: *
0580: * @param bean Bean whose property is to be extracted
0581: * @param name Mapped property name of the property value to be extracted
0582: * @param key Key of the property value to be extracted
0583: * @return the mapped property value
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: * propety cannot be found
0591: */
0592: public Object getMappedProperty(Object bean, String name, String key)
0593: throws IllegalAccessException, InvocationTargetException,
0594: NoSuchMethodException {
0595:
0596: if (bean == null) {
0597: throw new IllegalArgumentException("No bean specified");
0598: }
0599: if (name == null) {
0600: throw new IllegalArgumentException(
0601: "No name specified for bean class '"
0602: + bean.getClass() + "'");
0603: }
0604: if (key == null) {
0605: throw new IllegalArgumentException(
0606: "No key specified for property '" + name
0607: + "' on bean class " + bean.getClass()
0608: + "'");
0609: }
0610:
0611: // Handle DynaBean instances specially
0612: if (bean instanceof DynaBean) {
0613: DynaProperty descriptor = ((DynaBean) bean).getDynaClass()
0614: .getDynaProperty(name);
0615: if (descriptor == null) {
0616: throw new NoSuchMethodException("Unknown property '"
0617: + name + "'+ on bean class '" + bean.getClass()
0618: + "'");
0619: }
0620: return (((DynaBean) bean).get(name, key));
0621: }
0622:
0623: Object result = null;
0624:
0625: // Retrieve the property descriptor for the specified property
0626: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
0627: name);
0628: if (descriptor == null) {
0629: throw new NoSuchMethodException("Unknown property '" + name
0630: + "'+ on bean class '" + bean.getClass() + "'");
0631: }
0632:
0633: if (descriptor instanceof MappedPropertyDescriptor) {
0634: // Call the keyed getter method if there is one
0635: Method readMethod = ((MappedPropertyDescriptor) descriptor)
0636: .getMappedReadMethod();
0637: readMethod = MethodUtils.getAccessibleMethod(readMethod);
0638: if (readMethod != null) {
0639: Object[] keyArray = new Object[1];
0640: keyArray[0] = key;
0641: result = invokeMethod(readMethod, bean, keyArray);
0642: } else {
0643: throw new NoSuchMethodException(
0644: "Property '"
0645: + name
0646: + "' has no mapped getter method on bean class '"
0647: + bean.getClass() + "'");
0648: }
0649: } else {
0650: /* means that the result has to be retrieved from a map */
0651: Method readMethod = getReadMethod(descriptor);
0652: if (readMethod != null) {
0653: Object invokeResult = invokeMethod(readMethod, bean,
0654: new Object[0]);
0655: /* test and fetch from the map */
0656: if (invokeResult instanceof java.util.Map) {
0657: result = ((java.util.Map) invokeResult).get(key);
0658: }
0659: } else {
0660: throw new NoSuchMethodException(
0661: "Property '"
0662: + name
0663: + "' has no mapped getter method on bean class '"
0664: + bean.getClass() + "'");
0665: }
0666: }
0667: return result;
0668:
0669: }
0670:
0671: /**
0672: * <p>Return the mapped property descriptors for this bean class.</p>
0673: *
0674: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0675: *
0676: * @param beanClass Bean class to be introspected
0677: * @return the mapped property descriptors
0678: * @deprecated This method should not be exposed
0679: */
0680: public FastHashMap getMappedPropertyDescriptors(Class beanClass) {
0681:
0682: if (beanClass == null) {
0683: return null;
0684: }
0685:
0686: // Look up any cached descriptors for this bean class
0687: return (FastHashMap) mappedDescriptorsCache.get(beanClass);
0688:
0689: }
0690:
0691: /**
0692: * <p>Return the mapped property descriptors for this bean.</p>
0693: *
0694: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0695: *
0696: * @param bean Bean to be introspected
0697: * @return the mapped property descriptors
0698: * @deprecated This method should not be exposed
0699: */
0700: public FastHashMap getMappedPropertyDescriptors(Object bean) {
0701:
0702: if (bean == null) {
0703: return null;
0704: }
0705: return (getMappedPropertyDescriptors(bean.getClass()));
0706:
0707: }
0708:
0709: /**
0710: * Return the value of the (possibly nested) property of the specified
0711: * name, for the specified bean, with no type conversions.
0712: *
0713: * @param bean Bean whose property is to be extracted
0714: * @param name Possibly nested name of the property to be extracted
0715: * @return the nested property value
0716: *
0717: * @exception IllegalAccessException if the caller does not have
0718: * access to the property accessor method
0719: * @exception IllegalArgumentException if <code>bean</code> or
0720: * <code>name</code> is null
0721: * @exception NestedNullException if a nested reference to a
0722: * property returns null
0723: * @exception InvocationTargetException
0724: * if the property accessor method throws an exception
0725: * @exception NoSuchMethodException if an accessor method for this
0726: * propety cannot be found
0727: */
0728: public Object getNestedProperty(Object bean, String name)
0729: throws IllegalAccessException, InvocationTargetException,
0730: NoSuchMethodException {
0731:
0732: if (bean == null) {
0733: throw new IllegalArgumentException("No bean specified");
0734: }
0735: if (name == null) {
0736: throw new IllegalArgumentException(
0737: "No name specified for bean class '"
0738: + bean.getClass() + "'");
0739: }
0740:
0741: // Resolve nested references
0742: while (resolver.hasNested(name)) {
0743: String next = resolver.next(name);
0744: Object nestedBean = null;
0745: if (bean instanceof Map) {
0746: nestedBean = getPropertyOfMapBean((Map) bean, next);
0747: } else if (resolver.isMapped(next)) {
0748: nestedBean = getMappedProperty(bean, next);
0749: } else if (resolver.isIndexed(next)) {
0750: nestedBean = getIndexedProperty(bean, next);
0751: } else {
0752: nestedBean = getSimpleProperty(bean, next);
0753: }
0754: if (nestedBean == null) {
0755: throw new NestedNullException(
0756: "Null property value for '" + name
0757: + "' on bean class '" + bean.getClass()
0758: + "'");
0759: }
0760: bean = nestedBean;
0761: name = resolver.remove(name);
0762: }
0763:
0764: if (bean instanceof Map) {
0765: bean = getPropertyOfMapBean((Map) bean, name);
0766: } else if (resolver.isMapped(name)) {
0767: bean = getMappedProperty(bean, name);
0768: } else if (resolver.isIndexed(name)) {
0769: bean = getIndexedProperty(bean, name);
0770: } else {
0771: bean = getSimpleProperty(bean, name);
0772: }
0773: return bean;
0774:
0775: }
0776:
0777: /**
0778: * This method is called by getNestedProperty and setNestedProperty to
0779: * define what it means to get a property from an object which implements
0780: * Map. See setPropertyOfMapBean for more information.
0781: *
0782: * @param bean Map bean
0783: * @param propertyName The property name
0784: * @return the property value
0785: *
0786: * @throws IllegalArgumentException when the propertyName is regarded as
0787: * being invalid.
0788: *
0789: * @throws IllegalAccessException just in case subclasses override this
0790: * method to try to access real getter methods and find permission is denied.
0791: *
0792: * @throws InvocationTargetException just in case subclasses override this
0793: * method to try to access real getter methods, and find it throws an
0794: * exception when invoked.
0795: *
0796: * @throws NoSuchMethodException just in case subclasses override this
0797: * method to try to access real getter methods, and want to fail if
0798: * no simple method is available.
0799: */
0800: protected Object getPropertyOfMapBean(Map bean, String propertyName)
0801: throws IllegalArgumentException, IllegalAccessException,
0802: InvocationTargetException, NoSuchMethodException {
0803:
0804: if (resolver.isMapped(propertyName)) {
0805: String name = resolver.getProperty(propertyName);
0806: if (name == null || name.length() == 0) {
0807: propertyName = resolver.getKey(propertyName);
0808: }
0809: }
0810:
0811: if (resolver.isIndexed(propertyName)
0812: || resolver.isMapped(propertyName)) {
0813: throw new IllegalArgumentException(
0814: "Indexed or mapped properties are not supported on"
0815: + " objects of type Map: " + propertyName);
0816: }
0817:
0818: return bean.get(propertyName);
0819: }
0820:
0821: /**
0822: * Return the value of the specified property of the specified bean,
0823: * no matter which property reference format is used, with no
0824: * type conversions.
0825: *
0826: * @param bean Bean whose property is to be extracted
0827: * @param name Possibly indexed and/or nested name of the property
0828: * to be extracted
0829: * @return the property value
0830: *
0831: * @exception IllegalAccessException if the caller does not have
0832: * access to the property accessor method
0833: * @exception IllegalArgumentException if <code>bean</code> or
0834: * <code>name</code> is null
0835: * @exception InvocationTargetException if the property accessor method
0836: * throws an exception
0837: * @exception NoSuchMethodException if an accessor method for this
0838: * propety cannot be found
0839: */
0840: public Object getProperty(Object bean, String name)
0841: throws IllegalAccessException, InvocationTargetException,
0842: NoSuchMethodException {
0843:
0844: return (getNestedProperty(bean, name));
0845:
0846: }
0847:
0848: /**
0849: * <p>Retrieve the property descriptor for the specified property of the
0850: * specified bean, or return <code>null</code> if there is no such
0851: * descriptor. This method resolves indexed and nested property
0852: * references in the same manner as other methods in this class, except
0853: * that if the last (or only) name element is indexed, the descriptor
0854: * for the last resolved property itself is returned.</p>
0855: *
0856: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0857: *
0858: * @param bean Bean for which a property descriptor is requested
0859: * @param name Possibly indexed and/or nested name of the property for
0860: * which a property descriptor is requested
0861: * @return the property descriptor
0862: *
0863: * @exception IllegalAccessException if the caller does not have
0864: * access to the property accessor method
0865: * @exception IllegalArgumentException if <code>bean</code> or
0866: * <code>name</code> is null
0867: * @exception IllegalArgumentException if a nested reference to a
0868: * property returns null
0869: * @exception InvocationTargetException if the property accessor method
0870: * throws an exception
0871: * @exception NoSuchMethodException if an accessor method for this
0872: * propety cannot be found
0873: */
0874: public PropertyDescriptor getPropertyDescriptor(Object bean,
0875: String name) throws IllegalAccessException,
0876: InvocationTargetException, NoSuchMethodException {
0877:
0878: if (bean == null) {
0879: throw new IllegalArgumentException("No bean specified");
0880: }
0881: if (name == null) {
0882: throw new IllegalArgumentException(
0883: "No name specified for bean class '"
0884: + bean.getClass() + "'");
0885: }
0886:
0887: // Resolve nested references
0888: while (resolver.hasNested(name)) {
0889: String next = resolver.next(name);
0890: Object nestedBean = null;
0891: if (bean instanceof Map) {
0892: nestedBean = getPropertyOfMapBean((Map) bean, next);
0893: } else if (resolver.isMapped(next)) {
0894: nestedBean = getMappedProperty(bean, next);
0895: } else if (resolver.isIndexed(next)) {
0896: nestedBean = getIndexedProperty(bean, next);
0897: } else {
0898: nestedBean = getSimpleProperty(bean, next);
0899: }
0900: if (nestedBean == null) {
0901: throw new NestedNullException(
0902: "Null property value for '" + name
0903: + "' on bean class '" + bean.getClass()
0904: + "'");
0905: }
0906: bean = nestedBean;
0907: name = resolver.remove(name);
0908: }
0909:
0910: // Remove any subscript from the final name value
0911: name = resolver.getProperty(name);
0912:
0913: // Look up and return this property from our cache
0914: // creating and adding it to the cache if not found.
0915: if ((bean == null) || (name == null)) {
0916: return (null);
0917: }
0918:
0919: PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
0920: if (descriptors != null) {
0921:
0922: for (int i = 0; i < descriptors.length; i++) {
0923: if (name.equals(descriptors[i].getName())) {
0924: return (descriptors[i]);
0925: }
0926: }
0927: }
0928:
0929: PropertyDescriptor result = null;
0930: FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean);
0931: if (mappedDescriptors == null) {
0932: mappedDescriptors = new FastHashMap();
0933: mappedDescriptors.setFast(true);
0934: mappedDescriptorsCache.put(bean.getClass(),
0935: mappedDescriptors);
0936: }
0937: result = (PropertyDescriptor) mappedDescriptors.get(name);
0938: if (result == null) {
0939: // not found, try to create it
0940: try {
0941: result = new MappedPropertyDescriptor(name, bean
0942: .getClass());
0943: } catch (IntrospectionException ie) {
0944: /* Swallow IntrospectionException
0945: * TODO: Why?
0946: */
0947: }
0948: if (result != null) {
0949: mappedDescriptors.put(name, result);
0950: }
0951: }
0952:
0953: return result;
0954:
0955: }
0956:
0957: /**
0958: * <p>Retrieve the property descriptors for the specified class,
0959: * introspecting and caching them the first time a particular bean class
0960: * is encountered.</p>
0961: *
0962: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0963: *
0964: * @param beanClass Bean class for which property descriptors are requested
0965: * @return the property descriptors
0966: *
0967: * @exception IllegalArgumentException if <code>beanClass</code> is null
0968: */
0969: public PropertyDescriptor[] getPropertyDescriptors(Class beanClass) {
0970:
0971: if (beanClass == null) {
0972: throw new IllegalArgumentException(
0973: "No bean class specified");
0974: }
0975:
0976: // Look up any cached descriptors for this bean class
0977: PropertyDescriptor[] descriptors = null;
0978: descriptors = (PropertyDescriptor[]) descriptorsCache
0979: .get(beanClass);
0980: if (descriptors != null) {
0981: return (descriptors);
0982: }
0983:
0984: // Introspect the bean and cache the generated descriptors
0985: BeanInfo beanInfo = null;
0986: try {
0987: beanInfo = Introspector.getBeanInfo(beanClass);
0988: } catch (IntrospectionException e) {
0989: return (new PropertyDescriptor[0]);
0990: }
0991: descriptors = beanInfo.getPropertyDescriptors();
0992: if (descriptors == null) {
0993: descriptors = new PropertyDescriptor[0];
0994: }
0995:
0996: // ----------------- Workaround for Bug 28358 --------- START ------------------
0997: //
0998: // The following code fixes an issue where IndexedPropertyDescriptor behaves
0999: // Differently in different versions of the JDK for 'indexed' properties which
1000: // use java.util.List (rather than an array).
1001: //
1002: // If you have a Bean with the following getters/setters for an indexed property:
1003: //
1004: // public List getFoo()
1005: // public Object getFoo(int index)
1006: // public void setFoo(List foo)
1007: // public void setFoo(int index, Object foo)
1008: //
1009: // then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod()
1010: // behave as follows:
1011: //
1012: // JDK 1.3.1_04: returns valid Method objects from these methods.
1013: // JDK 1.4.2_05: returns null from these methods.
1014: //
1015: for (int i = 0; i < descriptors.length; i++) {
1016: if (descriptors[i] instanceof IndexedPropertyDescriptor) {
1017: IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor) descriptors[i];
1018: String propName = descriptor.getName().substring(0, 1)
1019: .toUpperCase()
1020: + descriptor.getName().substring(1);
1021:
1022: if (descriptor.getReadMethod() == null) {
1023: String methodName = descriptor
1024: .getIndexedReadMethod() != null ? descriptor
1025: .getIndexedReadMethod().getName()
1026: : "get" + propName;
1027: Method readMethod = MethodUtils
1028: .getMatchingAccessibleMethod(beanClass,
1029: methodName, EMPTY_CLASS_PARAMETERS);
1030: if (readMethod != null) {
1031: try {
1032: descriptor.setReadMethod(readMethod);
1033: } catch (Exception e) {
1034: log
1035: .error(
1036: "Error setting indexed property read method",
1037: e);
1038: }
1039: }
1040: }
1041: if (descriptor.getWriteMethod() == null) {
1042: String methodName = descriptor
1043: .getIndexedWriteMethod() != null ? descriptor
1044: .getIndexedWriteMethod().getName()
1045: : "set" + propName;
1046: Method writeMethod = MethodUtils
1047: .getMatchingAccessibleMethod(beanClass,
1048: methodName, LIST_CLASS_PARAMETER);
1049: if (writeMethod == null) {
1050: Method[] methods = beanClass.getMethods();
1051: for (int j = 0; j < methods.length; j++) {
1052: if (methods[j].getName().equals(methodName)) {
1053: Class[] parameterTypes = methods[j]
1054: .getParameterTypes();
1055: if (parameterTypes.length == 1
1056: && List.class
1057: .isAssignableFrom(parameterTypes[0])) {
1058: writeMethod = methods[j];
1059: break;
1060: }
1061: }
1062: }
1063: }
1064: if (writeMethod != null) {
1065: try {
1066: descriptor.setWriteMethod(writeMethod);
1067: } catch (Exception e) {
1068: log
1069: .error(
1070: "Error setting indexed property write method",
1071: e);
1072: }
1073: }
1074: }
1075: }
1076: }
1077: // ----------------- Workaround for Bug 28358 ---------- END -------------------
1078:
1079: descriptorsCache.put(beanClass, descriptors);
1080: return (descriptors);
1081:
1082: }
1083:
1084: /**
1085: * <p>Retrieve the property descriptors for the specified bean,
1086: * introspecting and caching them the first time a particular bean class
1087: * is encountered.</p>
1088: *
1089: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1090: *
1091: * @param bean Bean for which property descriptors are requested
1092: * @return the property descriptors
1093: *
1094: * @exception IllegalArgumentException if <code>bean</code> is null
1095: */
1096: public PropertyDescriptor[] getPropertyDescriptors(Object bean) {
1097:
1098: if (bean == null) {
1099: throw new IllegalArgumentException("No bean specified");
1100: }
1101: return (getPropertyDescriptors(bean.getClass()));
1102:
1103: }
1104:
1105: /**
1106: * <p>Return the Java Class repesenting the property editor class that has
1107: * been registered for this property (if any). This method follows the
1108: * same name resolution rules used by <code>getPropertyDescriptor()</code>,
1109: * so if the last element of a name reference is indexed, the property
1110: * editor for the underlying property's class is returned.</p>
1111: *
1112: * <p>Note that <code>null</code> will be returned if there is no property,
1113: * or if there is no registered property editor class. Because this
1114: * return value is ambiguous, you should determine the existence of the
1115: * property itself by other means.</p>
1116: *
1117: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1118: *
1119: * @param bean Bean for which a property descriptor is requested
1120: * @param name Possibly indexed and/or nested name of the property for
1121: * which a property descriptor is requested
1122: * @return the property editor class
1123: *
1124: * @exception IllegalAccessException if the caller does not have
1125: * access to the property accessor method
1126: * @exception IllegalArgumentException if <code>bean</code> or
1127: * <code>name</code> is null
1128: * @exception IllegalArgumentException if a nested reference to a
1129: * property returns null
1130: * @exception InvocationTargetException if the property accessor method
1131: * throws an exception
1132: * @exception NoSuchMethodException if an accessor method for this
1133: * propety cannot be found
1134: */
1135: public Class getPropertyEditorClass(Object bean, String name)
1136: throws IllegalAccessException, InvocationTargetException,
1137: NoSuchMethodException {
1138:
1139: if (bean == null) {
1140: throw new IllegalArgumentException("No bean specified");
1141: }
1142: if (name == null) {
1143: throw new IllegalArgumentException(
1144: "No name specified for bean class '"
1145: + bean.getClass() + "'");
1146: }
1147:
1148: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1149: name);
1150: if (descriptor != null) {
1151: return (descriptor.getPropertyEditorClass());
1152: } else {
1153: return (null);
1154: }
1155:
1156: }
1157:
1158: /**
1159: * Return the Java Class representing the property type of the specified
1160: * property, or <code>null</code> if there is no such property for the
1161: * specified bean. This method follows the same name resolution rules
1162: * used by <code>getPropertyDescriptor()</code>, so if the last element
1163: * of a name reference is indexed, the type of the property itself will
1164: * be returned. If the last (or only) element has no property with the
1165: * specified name, <code>null</code> is returned.
1166: *
1167: * @param bean Bean for which a property descriptor is requested
1168: * @param name Possibly indexed and/or nested name of the property for
1169: * which a property descriptor is requested
1170: * @return The property type
1171: *
1172: * @exception IllegalAccessException if the caller does not have
1173: * access to the property accessor method
1174: * @exception IllegalArgumentException if <code>bean</code> or
1175: * <code>name</code> is null
1176: * @exception IllegalArgumentException if a nested reference to a
1177: * property returns null
1178: * @exception InvocationTargetException if the property accessor method
1179: * throws an exception
1180: * @exception NoSuchMethodException if an accessor method for this
1181: * propety cannot be found
1182: */
1183: public Class getPropertyType(Object bean, String name)
1184: throws IllegalAccessException, InvocationTargetException,
1185: NoSuchMethodException {
1186:
1187: if (bean == null) {
1188: throw new IllegalArgumentException("No bean specified");
1189: }
1190: if (name == null) {
1191: throw new IllegalArgumentException(
1192: "No name specified for bean class '"
1193: + bean.getClass() + "'");
1194: }
1195:
1196: // Special handling for DynaBeans
1197: if (bean instanceof DynaBean) {
1198: DynaProperty descriptor = ((DynaBean) bean).getDynaClass()
1199: .getDynaProperty(name);
1200: if (descriptor == null) {
1201: return (null);
1202: }
1203: Class type = descriptor.getType();
1204: if (type == null) {
1205: return (null);
1206: } else if (type.isArray()) {
1207: return (type.getComponentType());
1208: } else {
1209: return (type);
1210: }
1211: }
1212:
1213: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1214: name);
1215: if (descriptor == null) {
1216: return (null);
1217: } else if (descriptor instanceof IndexedPropertyDescriptor) {
1218: return (((IndexedPropertyDescriptor) descriptor)
1219: .getIndexedPropertyType());
1220: } else if (descriptor instanceof MappedPropertyDescriptor) {
1221: return (((MappedPropertyDescriptor) descriptor)
1222: .getMappedPropertyType());
1223: } else {
1224: return (descriptor.getPropertyType());
1225: }
1226:
1227: }
1228:
1229: /**
1230: * <p>Return an accessible property getter method for this property,
1231: * if there is one; otherwise return <code>null</code>.</p>
1232: *
1233: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1234: *
1235: * @param descriptor Property descriptor to return a getter for
1236: * @return The read method
1237: */
1238: public Method getReadMethod(PropertyDescriptor descriptor) {
1239:
1240: return (MethodUtils.getAccessibleMethod(descriptor
1241: .getReadMethod()));
1242:
1243: }
1244:
1245: /**
1246: * Return the value of the specified simple property of the specified
1247: * bean, with no type conversions.
1248: *
1249: * @param bean Bean whose property is to be extracted
1250: * @param name Name of the property to be extracted
1251: * @return The property value
1252: *
1253: * @exception IllegalAccessException if the caller does not have
1254: * access to the property accessor method
1255: * @exception IllegalArgumentException if <code>bean</code> or
1256: * <code>name</code> is null
1257: * @exception IllegalArgumentException if the property name
1258: * is nested or indexed
1259: * @exception InvocationTargetException if the property accessor method
1260: * throws an exception
1261: * @exception NoSuchMethodException if an accessor method for this
1262: * propety cannot be found
1263: */
1264: public Object getSimpleProperty(Object bean, String name)
1265: throws IllegalAccessException, InvocationTargetException,
1266: NoSuchMethodException {
1267:
1268: if (bean == null) {
1269: throw new IllegalArgumentException("No bean specified");
1270: }
1271: if (name == null) {
1272: throw new IllegalArgumentException(
1273: "No name specified for bean class '"
1274: + bean.getClass() + "'");
1275: }
1276:
1277: // Validate the syntax of the property name
1278: if (resolver.hasNested(name)) {
1279: throw new IllegalArgumentException(
1280: "Nested property names are not allowed: Property '"
1281: + name + "' on bean class '"
1282: + bean.getClass() + "'");
1283: } else if (resolver.isIndexed(name)) {
1284: throw new IllegalArgumentException(
1285: "Indexed property names are not allowed: Property '"
1286: + name + "' on bean class '"
1287: + bean.getClass() + "'");
1288: } else if (resolver.isMapped(name)) {
1289: throw new IllegalArgumentException(
1290: "Mapped property names are not allowed: Property '"
1291: + name + "' on bean class '"
1292: + bean.getClass() + "'");
1293: }
1294:
1295: // Handle DynaBean instances specially
1296: if (bean instanceof DynaBean) {
1297: DynaProperty descriptor = ((DynaBean) bean).getDynaClass()
1298: .getDynaProperty(name);
1299: if (descriptor == null) {
1300: throw new NoSuchMethodException("Unknown property '"
1301: + name + "' on dynaclass '"
1302: + ((DynaBean) bean).getDynaClass() + "'");
1303: }
1304: return (((DynaBean) bean).get(name));
1305: }
1306:
1307: // Retrieve the property getter method for the specified property
1308: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1309: name);
1310: if (descriptor == null) {
1311: throw new NoSuchMethodException("Unknown property '" + name
1312: + "' on class '" + bean.getClass() + "'");
1313: }
1314: Method readMethod = getReadMethod(descriptor);
1315: if (readMethod == null) {
1316: throw new NoSuchMethodException("Property '" + name
1317: + "' has no getter method in class '"
1318: + bean.getClass() + "'");
1319: }
1320:
1321: // Call the property getter and return the value
1322: Object value = invokeMethod(readMethod, bean, new Object[0]);
1323: return (value);
1324:
1325: }
1326:
1327: /**
1328: * <p>Return an accessible property setter method for this property,
1329: * if there is one; otherwise return <code>null</code>.</p>
1330: *
1331: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1332: *
1333: * @param descriptor Property descriptor to return a setter for
1334: * @return The write method
1335: */
1336: public Method getWriteMethod(PropertyDescriptor descriptor) {
1337:
1338: return (MethodUtils.getAccessibleMethod(descriptor
1339: .getWriteMethod()));
1340:
1341: }
1342:
1343: /**
1344: * <p>Return <code>true</code> if the specified property name identifies
1345: * a readable property on the specified bean; otherwise, return
1346: * <code>false</code>.
1347: *
1348: * @param bean Bean to be examined (may be a {@link DynaBean}
1349: * @param name Property name to be evaluated
1350: * @return <code>true</code> if the property is readable,
1351: * otherwise <code>false</code>
1352: *
1353: * @exception IllegalArgumentException if <code>bean</code>
1354: * or <code>name</code> is <code>null</code>
1355: *
1356: * @since BeanUtils 1.6
1357: */
1358: public boolean isReadable(Object bean, String name) {
1359:
1360: // Validate method parameters
1361: if (bean == null) {
1362: throw new IllegalArgumentException("No bean specified");
1363: }
1364: if (name == null) {
1365: throw new IllegalArgumentException(
1366: "No name specified for bean class '"
1367: + bean.getClass() + "'");
1368: }
1369:
1370: // Treat WrapDynaBean as special case - may be a write-only property
1371: // (see Jira issue# BEANUTILS-61)
1372: if (bean instanceof WrapDynaBean) {
1373: bean = ((WrapDynaBean) bean).getInstance();
1374: }
1375:
1376: // Return the requested result
1377: if (bean instanceof DynaBean) {
1378: // All DynaBean properties are readable
1379: return (((DynaBean) bean).getDynaClass().getDynaProperty(
1380: name) != null);
1381: } else {
1382: try {
1383: PropertyDescriptor desc = getPropertyDescriptor(bean,
1384: name);
1385: if (desc != null) {
1386: Method readMethod = getReadMethod(desc);
1387: if (readMethod == null) {
1388: if (desc instanceof IndexedPropertyDescriptor) {
1389: readMethod = ((IndexedPropertyDescriptor) desc)
1390: .getIndexedReadMethod();
1391: } else if (desc instanceof MappedPropertyDescriptor) {
1392: readMethod = ((MappedPropertyDescriptor) desc)
1393: .getMappedReadMethod();
1394: }
1395: readMethod = MethodUtils
1396: .getAccessibleMethod(readMethod);
1397: }
1398: return (readMethod != null);
1399: } else {
1400: return (false);
1401: }
1402: } catch (IllegalAccessException e) {
1403: return (false);
1404: } catch (InvocationTargetException e) {
1405: return (false);
1406: } catch (NoSuchMethodException e) {
1407: return (false);
1408: }
1409: }
1410:
1411: }
1412:
1413: /**
1414: * <p>Return <code>true</code> if the specified property name identifies
1415: * a writeable property on the specified bean; otherwise, return
1416: * <code>false</code>.
1417: *
1418: * @param bean Bean to be examined (may be a {@link DynaBean}
1419: * @param name Property name to be evaluated
1420: * @return <code>true</code> if the property is writeable,
1421: * otherwise <code>false</code>
1422: *
1423: * @exception IllegalArgumentException if <code>bean</code>
1424: * or <code>name</code> is <code>null</code>
1425: *
1426: * @since BeanUtils 1.6
1427: */
1428: public boolean isWriteable(Object bean, String name) {
1429:
1430: // Validate method parameters
1431: if (bean == null) {
1432: throw new IllegalArgumentException("No bean specified");
1433: }
1434: if (name == null) {
1435: throw new IllegalArgumentException(
1436: "No name specified for bean class '"
1437: + bean.getClass() + "'");
1438: }
1439:
1440: // Treat WrapDynaBean as special case - may be a read-only property
1441: // (see Jira issue# BEANUTILS-61)
1442: if (bean instanceof WrapDynaBean) {
1443: bean = ((WrapDynaBean) bean).getInstance();
1444: }
1445:
1446: // Return the requested result
1447: if (bean instanceof DynaBean) {
1448: // All DynaBean properties are writeable
1449: return (((DynaBean) bean).getDynaClass().getDynaProperty(
1450: name) != null);
1451: } else {
1452: try {
1453: PropertyDescriptor desc = getPropertyDescriptor(bean,
1454: name);
1455: if (desc != null) {
1456: Method writeMethod = getWriteMethod(desc);
1457: if (writeMethod == null) {
1458: if (desc instanceof IndexedPropertyDescriptor) {
1459: writeMethod = ((IndexedPropertyDescriptor) desc)
1460: .getIndexedWriteMethod();
1461: } else if (desc instanceof MappedPropertyDescriptor) {
1462: writeMethod = ((MappedPropertyDescriptor) desc)
1463: .getMappedWriteMethod();
1464: }
1465: writeMethod = MethodUtils
1466: .getAccessibleMethod(writeMethod);
1467: }
1468: return (writeMethod != null);
1469: } else {
1470: return (false);
1471: }
1472: } catch (IllegalAccessException e) {
1473: return (false);
1474: } catch (InvocationTargetException e) {
1475: return (false);
1476: } catch (NoSuchMethodException e) {
1477: return (false);
1478: }
1479: }
1480:
1481: }
1482:
1483: /**
1484: * Set the value of the specified indexed property of the specified
1485: * bean, with no type conversions. The zero-relative index of the
1486: * required value must be included (in square brackets) as a suffix to
1487: * the property name, or <code>IllegalArgumentException</code> will be
1488: * thrown. In addition to supporting the JavaBeans specification, this
1489: * method has been extended to support <code>List</code> objects as well.
1490: *
1491: * @param bean Bean whose property is to be modified
1492: * @param name <code>propertyname[index]</code> of the property value
1493: * to be modified
1494: * @param value Value to which the specified property element
1495: * should be set
1496: *
1497: * @exception IndexOutOfBoundsException if the specified index
1498: * is outside the valid range for the underlying property
1499: * @exception IllegalAccessException if the caller does not have
1500: * access to the property accessor method
1501: * @exception IllegalArgumentException if <code>bean</code> or
1502: * <code>name</code> is null
1503: * @exception InvocationTargetException if the property accessor method
1504: * throws an exception
1505: * @exception NoSuchMethodException if an accessor method for this
1506: * propety cannot be found
1507: */
1508: public void setIndexedProperty(Object bean, String name,
1509: Object value) throws IllegalAccessException,
1510: InvocationTargetException, NoSuchMethodException {
1511:
1512: if (bean == null) {
1513: throw new IllegalArgumentException("No bean specified");
1514: }
1515: if (name == null) {
1516: throw new IllegalArgumentException(
1517: "No name specified for bean class '"
1518: + bean.getClass() + "'");
1519: }
1520:
1521: // Identify the index of the requested individual property
1522: int index = -1;
1523: try {
1524: index = resolver.getIndex(name);
1525: } catch (IllegalArgumentException e) {
1526: throw new IllegalArgumentException(
1527: "Invalid indexed property '" + name
1528: + "' on bean class '" + bean.getClass()
1529: + "'");
1530: }
1531: if (index < 0) {
1532: throw new IllegalArgumentException(
1533: "Invalid indexed property '" + name
1534: + "' on bean class '" + bean.getClass()
1535: + "'");
1536: }
1537:
1538: // Isolate the name
1539: name = resolver.getProperty(name);
1540:
1541: // Set the specified indexed property value
1542: setIndexedProperty(bean, name, index, value);
1543:
1544: }
1545:
1546: /**
1547: * Set the value of the specified indexed property of the specified
1548: * bean, with no type conversions. In addition to supporting the JavaBeans
1549: * specification, this method has been extended to support
1550: * <code>List</code> objects as well.
1551: *
1552: * @param bean Bean whose property is to be set
1553: * @param name Simple property name of the property value to be set
1554: * @param index Index of the property value to be set
1555: * @param value Value to which the indexed property element is to be set
1556: *
1557: * @exception IndexOutOfBoundsException if the specified index
1558: * is outside the valid range for the underlying property
1559: * @exception IllegalAccessException if the caller does not have
1560: * access to the property accessor method
1561: * @exception IllegalArgumentException if <code>bean</code> or
1562: * <code>name</code> is null
1563: * @exception InvocationTargetException if the property accessor method
1564: * throws an exception
1565: * @exception NoSuchMethodException if an accessor method for this
1566: * propety cannot be found
1567: */
1568: public void setIndexedProperty(Object bean, String name, int index,
1569: Object value) throws IllegalAccessException,
1570: InvocationTargetException, NoSuchMethodException {
1571:
1572: if (bean == null) {
1573: throw new IllegalArgumentException("No bean specified");
1574: }
1575: if (name == null || name.length() == 0) {
1576: if (bean.getClass().isArray()) {
1577: Array.set(bean, index, value);
1578: return;
1579: } else if (bean instanceof List) {
1580: ((List) bean).set(index, value);
1581: return;
1582: }
1583: }
1584: if (name == null) {
1585: throw new IllegalArgumentException(
1586: "No name specified for bean class '"
1587: + bean.getClass() + "'");
1588: }
1589:
1590: // Handle DynaBean instances specially
1591: if (bean instanceof DynaBean) {
1592: DynaProperty descriptor = ((DynaBean) bean).getDynaClass()
1593: .getDynaProperty(name);
1594: if (descriptor == null) {
1595: throw new NoSuchMethodException("Unknown property '"
1596: + name + "' on bean class '" + bean.getClass()
1597: + "'");
1598: }
1599: ((DynaBean) bean).set(name, index, value);
1600: return;
1601: }
1602:
1603: // Retrieve the property descriptor for the specified property
1604: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1605: name);
1606: if (descriptor == null) {
1607: throw new NoSuchMethodException("Unknown property '" + name
1608: + "' on bean class '" + bean.getClass() + "'");
1609: }
1610:
1611: // Call the indexed setter method if there is one
1612: if (descriptor instanceof IndexedPropertyDescriptor) {
1613: Method writeMethod = ((IndexedPropertyDescriptor) descriptor)
1614: .getIndexedWriteMethod();
1615: writeMethod = MethodUtils.getAccessibleMethod(writeMethod);
1616: if (writeMethod != null) {
1617: Object[] subscript = new Object[2];
1618: subscript[0] = new Integer(index);
1619: subscript[1] = value;
1620: try {
1621: if (log.isTraceEnabled()) {
1622: String valueClassName = value == null ? "<null>"
1623: : value.getClass().getName();
1624: log.trace("setSimpleProperty: Invoking method "
1625: + writeMethod + " with index=" + index
1626: + ", value=" + value + " (class "
1627: + valueClassName + ")");
1628: }
1629: invokeMethod(writeMethod, bean, subscript);
1630: } catch (InvocationTargetException e) {
1631: if (e.getTargetException() instanceof IndexOutOfBoundsException) {
1632: throw (IndexOutOfBoundsException) e
1633: .getTargetException();
1634: } else {
1635: throw e;
1636: }
1637: }
1638: return;
1639: }
1640: }
1641:
1642: // Otherwise, the underlying property must be an array or a list
1643: Method readMethod = getReadMethod(descriptor);
1644: if (readMethod == null) {
1645: throw new NoSuchMethodException("Property '" + name
1646: + "' has no getter method on bean class '"
1647: + bean.getClass() + "'");
1648: }
1649:
1650: // Call the property getter to get the array or list
1651: Object array = invokeMethod(readMethod, bean, new Object[0]);
1652: if (!array.getClass().isArray()) {
1653: if (array instanceof List) {
1654: // Modify the specified value in the List
1655: ((List) array).set(index, value);
1656: } else {
1657: throw new IllegalArgumentException("Property '" + name
1658: + "' is not indexed on bean class '"
1659: + bean.getClass() + "'");
1660: }
1661: } else {
1662: // Modify the specified value in the array
1663: Array.set(array, index, value);
1664: }
1665:
1666: }
1667:
1668: /**
1669: * Set the value of the specified mapped property of the
1670: * specified bean, with no type conversions. The key of the
1671: * value to set must be included (in brackets) as a suffix to
1672: * the property name, or <code>IllegalArgumentException</code> will be
1673: * thrown.
1674: *
1675: * @param bean Bean whose property is to be set
1676: * @param name <code>propertyname(key)</code> of the property value
1677: * to be set
1678: * @param value The property value to be set
1679: *
1680: * @exception IllegalAccessException if the caller does not have
1681: * access to the property accessor method
1682: * @exception InvocationTargetException if the property accessor method
1683: * throws an exception
1684: * @exception NoSuchMethodException if an accessor method for this
1685: * propety cannot be found
1686: */
1687: public void setMappedProperty(Object bean, String name, Object value)
1688: throws IllegalAccessException, InvocationTargetException,
1689: NoSuchMethodException {
1690:
1691: if (bean == null) {
1692: throw new IllegalArgumentException("No bean specified");
1693: }
1694: if (name == null) {
1695: throw new IllegalArgumentException(
1696: "No name specified for bean class '"
1697: + bean.getClass() + "'");
1698: }
1699:
1700: // Identify the key of the requested individual property
1701: String key = null;
1702: try {
1703: key = resolver.getKey(name);
1704: } catch (IllegalArgumentException e) {
1705: throw new IllegalArgumentException(
1706: "Invalid mapped property '" + name
1707: + "' on bean class '" + bean.getClass()
1708: + "'");
1709: }
1710: if (key == null) {
1711: throw new IllegalArgumentException(
1712: "Invalid mapped property '" + name
1713: + "' on bean class '" + bean.getClass()
1714: + "'");
1715: }
1716:
1717: // Isolate the name
1718: name = resolver.getProperty(name);
1719:
1720: // Request the specified indexed property value
1721: setMappedProperty(bean, name, key, value);
1722:
1723: }
1724:
1725: /**
1726: * Set the value of the specified mapped property of the specified
1727: * bean, with no type conversions.
1728: *
1729: * @param bean Bean whose property is to be set
1730: * @param name Mapped property name of the property value to be set
1731: * @param key Key of the property value to be set
1732: * @param value The property value to be set
1733: *
1734: * @exception IllegalAccessException if the caller does not have
1735: * access to the property accessor method
1736: * @exception InvocationTargetException if the property accessor method
1737: * throws an exception
1738: * @exception NoSuchMethodException if an accessor method for this
1739: * propety cannot be found
1740: */
1741: public void setMappedProperty(Object bean, String name, String key,
1742: Object value) throws IllegalAccessException,
1743: InvocationTargetException, NoSuchMethodException {
1744:
1745: if (bean == null) {
1746: throw new IllegalArgumentException("No bean specified");
1747: }
1748: if (name == null) {
1749: throw new IllegalArgumentException(
1750: "No name specified for bean class '"
1751: + bean.getClass() + "'");
1752: }
1753: if (key == null) {
1754: throw new IllegalArgumentException(
1755: "No key specified for property '" + name
1756: + "' on bean class '" + bean.getClass()
1757: + "'");
1758: }
1759:
1760: // Handle DynaBean instances specially
1761: if (bean instanceof DynaBean) {
1762: DynaProperty descriptor = ((DynaBean) bean).getDynaClass()
1763: .getDynaProperty(name);
1764: if (descriptor == null) {
1765: throw new NoSuchMethodException("Unknown property '"
1766: + name + "' on bean class '" + bean.getClass()
1767: + "'");
1768: }
1769: ((DynaBean) bean).set(name, key, value);
1770: return;
1771: }
1772:
1773: // Retrieve the property descriptor for the specified property
1774: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1775: name);
1776: if (descriptor == null) {
1777: throw new NoSuchMethodException("Unknown property '" + name
1778: + "' on bean class '" + bean.getClass() + "'");
1779: }
1780:
1781: if (descriptor instanceof MappedPropertyDescriptor) {
1782: // Call the keyed setter method if there is one
1783: Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor)
1784: .getMappedWriteMethod();
1785: mappedWriteMethod = MethodUtils
1786: .getAccessibleMethod(mappedWriteMethod);
1787: if (mappedWriteMethod != null) {
1788: Object[] params = new Object[2];
1789: params[0] = key;
1790: params[1] = value;
1791: if (log.isTraceEnabled()) {
1792: String valueClassName = value == null ? "<null>"
1793: : value.getClass().getName();
1794: log.trace("setSimpleProperty: Invoking method "
1795: + mappedWriteMethod + " with key=" + key
1796: + ", value=" + value + " (class "
1797: + valueClassName + ")");
1798: }
1799: invokeMethod(mappedWriteMethod, bean, params);
1800: } else {
1801: throw new NoSuchMethodException("Property '" + name
1802: + "' has no mapped setter method"
1803: + "on bean class '" + bean.getClass() + "'");
1804: }
1805: } else {
1806: /* means that the result has to be retrieved from a map */
1807: Method readMethod = getReadMethod(descriptor);
1808: if (readMethod != null) {
1809: Object invokeResult = invokeMethod(readMethod, bean,
1810: new Object[0]);
1811: /* test and fetch from the map */
1812: if (invokeResult instanceof java.util.Map) {
1813: ((java.util.Map) invokeResult).put(key, value);
1814: }
1815: } else {
1816: throw new NoSuchMethodException(
1817: "Property '"
1818: + name
1819: + "' has no mapped getter method on bean class '"
1820: + bean.getClass() + "'");
1821: }
1822: }
1823:
1824: }
1825:
1826: /**
1827: * Set the value of the (possibly nested) property of the specified
1828: * name, for the specified bean, with no type conversions.
1829: * <p>
1830: * Example values for parameter "name" are:
1831: * <ul>
1832: * <li> "a" -- sets the value of property a of the specified bean </li>
1833: * <li> "a.b" -- gets the value of property a of the specified bean,
1834: * then on that object sets the value of property b.</li>
1835: * <li> "a(key)" -- sets a value of mapped-property a on the specified
1836: * bean. This effectively means bean.setA("key").</li>
1837: * <li> "a[3]" -- sets a value of indexed-property a on the specified
1838: * bean. This effectively means bean.setA(3).</li>
1839: * </ul>
1840: *
1841: * @param bean Bean whose property is to be modified
1842: * @param name Possibly nested name of the property to be modified
1843: * @param value Value to which the property is to be set
1844: *
1845: * @exception IllegalAccessException if the caller does not have
1846: * access to the property accessor method
1847: * @exception IllegalArgumentException if <code>bean</code> or
1848: * <code>name</code> is null
1849: * @exception IllegalArgumentException if a nested reference to a
1850: * property returns null
1851: * @exception InvocationTargetException if the property accessor method
1852: * throws an exception
1853: * @exception NoSuchMethodException if an accessor method for this
1854: * propety cannot be found
1855: */
1856: public void setNestedProperty(Object bean, String name, Object value)
1857: throws IllegalAccessException, InvocationTargetException,
1858: NoSuchMethodException {
1859:
1860: if (bean == null) {
1861: throw new IllegalArgumentException("No bean specified");
1862: }
1863: if (name == null) {
1864: throw new IllegalArgumentException(
1865: "No name specified for bean class '"
1866: + bean.getClass() + "'");
1867: }
1868:
1869: // Resolve nested references
1870: while (resolver.hasNested(name)) {
1871: String next = resolver.next(name);
1872: Object nestedBean = null;
1873: if (bean instanceof Map) {
1874: nestedBean = getPropertyOfMapBean((Map) bean, next);
1875: } else if (resolver.isMapped(next)) {
1876: nestedBean = getMappedProperty(bean, next);
1877: } else if (resolver.isIndexed(next)) {
1878: nestedBean = getIndexedProperty(bean, next);
1879: } else {
1880: nestedBean = getSimpleProperty(bean, next);
1881: }
1882: if (nestedBean == null) {
1883: throw new NestedNullException(
1884: "Null property value for '" + name
1885: + "' on bean class '" + bean.getClass()
1886: + "'");
1887: }
1888: bean = nestedBean;
1889: name = resolver.remove(name);
1890: }
1891:
1892: if (bean instanceof Map) {
1893: setPropertyOfMapBean((Map) bean, name, value);
1894: } else if (resolver.isMapped(name)) {
1895: setMappedProperty(bean, name, value);
1896: } else if (resolver.isIndexed(name)) {
1897: setIndexedProperty(bean, name, value);
1898: } else {
1899: setSimpleProperty(bean, name, value);
1900: }
1901:
1902: }
1903:
1904: /**
1905: * This method is called by method setNestedProperty when the current bean
1906: * is found to be a Map object, and defines how to deal with setting
1907: * a property on a Map.
1908: * <p>
1909: * The standard implementation here is to:
1910: * <ul>
1911: * <li>call bean.set(propertyName) for all propertyName values.</li>
1912: * <li>throw an IllegalArgumentException if the property specifier
1913: * contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially
1914: * simple properties; mapping and indexing operations do not make sense
1915: * when accessing a map (even thought the returned object may be a Map
1916: * or an Array).</li>
1917: * </ul>
1918: * <p>
1919: * The default behaviour of beanutils 1.7.1 or later is for assigning to
1920: * "a.b" to mean a.put(b, obj) always. However the behaviour of beanutils
1921: * version 1.6.0, 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such
1922: * a method existed, and a.put(b, obj) otherwise. In version 1.5 it meant
1923: * a.put(b, obj) always (ie the same as the behaviour in the current version).
1924: * In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is
1925: * all <i>very</i> unfortunate]
1926: * <p>
1927: * Users who would like to customise the meaning of "a.b" in method
1928: * setNestedProperty when a is a Map can create a custom subclass of
1929: * this class and override this method to implement the behaviour of
1930: * their choice, such as restoring the pre-1.4 behaviour of this class
1931: * if they wish. When overriding this method, do not forget to deal
1932: * with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
1933: * <p>
1934: * Note, however, that the recommended solution for objects that
1935: * implement Map but want their simple properties to come first is
1936: * for <i>those</i> objects to override their get/put methods to implement
1937: * that behaviour, and <i>not</i> to solve the problem by modifying the
1938: * default behaviour of the PropertyUtilsBean class by overriding this
1939: * method.
1940: *
1941: * @param bean Map bean
1942: * @param propertyName The property name
1943: * @param value the property value
1944: *
1945: * @throws IllegalArgumentException when the propertyName is regarded as
1946: * being invalid.
1947: *
1948: * @throws IllegalAccessException just in case subclasses override this
1949: * method to try to access real setter methods and find permission is denied.
1950: *
1951: * @throws InvocationTargetException just in case subclasses override this
1952: * method to try to access real setter methods, and find it throws an
1953: * exception when invoked.
1954: *
1955: * @throws NoSuchMethodException just in case subclasses override this
1956: * method to try to access real setter methods, and want to fail if
1957: * no simple method is available.
1958: */
1959: protected void setPropertyOfMapBean(Map bean, String propertyName,
1960: Object value) throws IllegalArgumentException,
1961: IllegalAccessException, InvocationTargetException,
1962: NoSuchMethodException {
1963:
1964: if (resolver.isMapped(propertyName)) {
1965: String name = resolver.getProperty(propertyName);
1966: if (name == null || name.length() == 0) {
1967: propertyName = resolver.getKey(propertyName);
1968: }
1969: }
1970:
1971: if (resolver.isIndexed(propertyName)
1972: || resolver.isMapped(propertyName)) {
1973: throw new IllegalArgumentException(
1974: "Indexed or mapped properties are not supported on"
1975: + " objects of type Map: " + propertyName);
1976: }
1977:
1978: bean.put(propertyName, value);
1979: }
1980:
1981: /**
1982: * Set the value of the specified property of the specified bean,
1983: * no matter which property reference format is used, with no
1984: * type conversions.
1985: *
1986: * @param bean Bean whose property is to be modified
1987: * @param name Possibly indexed and/or nested name of the property
1988: * to be modified
1989: * @param value Value to which this property is to be set
1990: *
1991: * @exception IllegalAccessException if the caller does not have
1992: * access to the property accessor method
1993: * @exception IllegalArgumentException if <code>bean</code> or
1994: * <code>name</code> is null
1995: * @exception InvocationTargetException if the property accessor method
1996: * throws an exception
1997: * @exception NoSuchMethodException if an accessor method for this
1998: * propety cannot be found
1999: */
2000: public void setProperty(Object bean, String name, Object value)
2001: throws IllegalAccessException, InvocationTargetException,
2002: NoSuchMethodException {
2003:
2004: setNestedProperty(bean, name, value);
2005:
2006: }
2007:
2008: /**
2009: * Set the value of the specified simple property of the specified bean,
2010: * with no type conversions.
2011: *
2012: * @param bean Bean whose property is to be modified
2013: * @param name Name of the property to be modified
2014: * @param value Value to which the property should be set
2015: *
2016: * @exception IllegalAccessException if the caller does not have
2017: * access to the property accessor method
2018: * @exception IllegalArgumentException if <code>bean</code> or
2019: * <code>name</code> is null
2020: * @exception IllegalArgumentException if the property name is
2021: * nested or indexed
2022: * @exception InvocationTargetException if the property accessor method
2023: * throws an exception
2024: * @exception NoSuchMethodException if an accessor method for this
2025: * propety cannot be found
2026: */
2027: public void setSimpleProperty(Object bean, String name, Object value)
2028: throws IllegalAccessException, InvocationTargetException,
2029: NoSuchMethodException {
2030:
2031: if (bean == null) {
2032: throw new IllegalArgumentException("No bean specified");
2033: }
2034: if (name == null) {
2035: throw new IllegalArgumentException(
2036: "No name specified for bean class '"
2037: + bean.getClass() + "'");
2038: }
2039:
2040: // Validate the syntax of the property name
2041: if (resolver.hasNested(name)) {
2042: throw new IllegalArgumentException(
2043: "Nested property names are not allowed: Property '"
2044: + name + "' on bean class '"
2045: + bean.getClass() + "'");
2046: } else if (resolver.isIndexed(name)) {
2047: throw new IllegalArgumentException(
2048: "Indexed property names are not allowed: Property '"
2049: + name + "' on bean class '"
2050: + bean.getClass() + "'");
2051: } else if (resolver.isMapped(name)) {
2052: throw new IllegalArgumentException(
2053: "Mapped property names are not allowed: Property '"
2054: + name + "' on bean class '"
2055: + bean.getClass() + "'");
2056: }
2057:
2058: // Handle DynaBean instances specially
2059: if (bean instanceof DynaBean) {
2060: DynaProperty descriptor = ((DynaBean) bean).getDynaClass()
2061: .getDynaProperty(name);
2062: if (descriptor == null) {
2063: throw new NoSuchMethodException("Unknown property '"
2064: + name + "' on dynaclass '"
2065: + ((DynaBean) bean).getDynaClass() + "'");
2066: }
2067: ((DynaBean) bean).set(name, value);
2068: return;
2069: }
2070:
2071: // Retrieve the property setter method for the specified property
2072: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
2073: name);
2074: if (descriptor == null) {
2075: throw new NoSuchMethodException("Unknown property '" + name
2076: + "' on class '" + bean.getClass() + "'");
2077: }
2078: Method writeMethod = getWriteMethod(descriptor);
2079: if (writeMethod == null) {
2080: throw new NoSuchMethodException("Property '" + name
2081: + "' has no setter method in class '"
2082: + bean.getClass() + "'");
2083: }
2084:
2085: // Call the property setter method
2086: Object[] values = new Object[1];
2087: values[0] = value;
2088: if (log.isTraceEnabled()) {
2089: String valueClassName = value == null ? "<null>" : value
2090: .getClass().getName();
2091: log.trace("setSimpleProperty: Invoking method "
2092: + writeMethod + " with value " + value + " (class "
2093: + valueClassName + ")");
2094: }
2095: invokeMethod(writeMethod, bean, values);
2096:
2097: }
2098:
2099: /** This just catches and wraps IllegalArgumentException. */
2100: private Object invokeMethod(Method method, Object bean,
2101: Object[] values) throws IllegalAccessException,
2102: InvocationTargetException {
2103: try {
2104:
2105: return method.invoke(bean, values);
2106:
2107: } catch (IllegalArgumentException cause) {
2108: if (bean == null) {
2109: throw new IllegalArgumentException(
2110: "No bean specified "
2111: + "- this should have been checked before reaching this method");
2112: }
2113: String valueString = "";
2114: if (values != null) {
2115: for (int i = 0; i < values.length; i++) {
2116: if (i > 0) {
2117: valueString += ", ";
2118: }
2119: valueString += (values[i]).getClass().getName();
2120: }
2121: }
2122: String expectedString = "";
2123: Class[] parTypes = method.getParameterTypes();
2124: if (parTypes != null) {
2125: for (int i = 0; i < parTypes.length; i++) {
2126: if (i > 0) {
2127: expectedString += ", ";
2128: }
2129: expectedString += parTypes[i].getName();
2130: }
2131: }
2132: IllegalArgumentException e = new IllegalArgumentException(
2133: "Cannot invoke "
2134: + method.getDeclaringClass().getName()
2135: + "." + method.getName()
2136: + " on bean class '" + bean.getClass()
2137: + "' - "
2138: + cause.getMessage()
2139: // as per https://issues.apache.org/jira/browse/BEANUTILS-224
2140: + " - had objects of type \"" + valueString
2141: + "\" but expected signature \""
2142: + expectedString + "\"");
2143: if (!BeanUtils.initCause(e, cause)) {
2144: log.error("Method invocation failed", cause);
2145: }
2146: throw e;
2147:
2148: }
2149: }
2150: }
|