0001: /*
0002: * The Apache Software License, Version 1.1
0003: *
0004: * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
0005: * reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * 1. Redistributions of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: *
0014: * 2. Redistributions in binary form must reproduce the above copyright
0015: * notice, this list of conditions and the following disclaimer in
0016: * the documentation and/or other materials provided with the
0017: * distribution.
0018: *
0019: * 3. The end-user documentation included with the redistribution,
0020: * if any, must include the following acknowledgement:
0021: * "This product includes software developed by the
0022: * Apache Software Foundation (http://www.apache.org/)."
0023: * Alternately, this acknowledgement may appear in the software itself,
0024: * if and wherever such third-party acknowledgements normally appear.
0025: *
0026: * 4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
0027: * Foundation" must not be used to endorse or promote products derived
0028: * from this software without prior written permission. For written
0029: * permission, please contact apache@apache.org.
0030: *
0031: * 5. Products derived from this software may not be called "Apache",
0032: * "Apache" nor may "Apache" appear in their names without prior
0033: * written permission of the Apache Software Foundation.
0034: *
0035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0046: * SUCH DAMAGE.
0047: * ====================================================================
0048: *
0049: * This software consists of voluntary contributions made by many
0050: * individuals on behalf of the Apache Software Foundation. For more
0051: * information on the Apache Software Foundation, please see
0052: * <http://www.apache.org/>.
0053: *
0054: */
0055:
0056: package com.opensymphony.workflow.designer.beanutils;
0057:
0058: import java.beans.*;
0059: import java.lang.reflect.Array;
0060: import java.lang.reflect.InvocationTargetException;
0061: import java.lang.reflect.Method;
0062: import java.util.*;
0063:
0064: /**
0065: * Utility methods for using Java Reflection APIs to facilitate generic
0066: * property getter and setter operations on Java objects. Much of this
0067: * code was originally included in <code>BeanUtils</code>, but has been
0068: * separated because of the volume of code involved.
0069: * <p/>
0070: * In general, the objects that are examined and modified using these
0071: * methods are expected to conform to the property getter and setter method
0072: * naming conventions described in the JavaBeans Specification (Version 1.0.1).
0073: * No data type conversions are performed, and there are no usage of any
0074: * <code>PropertyEditor</code> classes that have been registered, although
0075: * a convenient way to access the registered classes themselves is included.
0076: * <p/>
0077: * For the purposes of this class, five formats for referencing a particular
0078: * property value of a bean are defined, with the layout of an identifying
0079: * String in parentheses:
0080: * <ul>
0081: * <li><strong>Simple (<code>name</code>)</strong> - The specified
0082: * <code>name</code> identifies an individual property of a particular
0083: * JavaBean. The name of the actual getter or setter method to be used
0084: * is determined using standard JavaBeans instrospection, so that (unless
0085: * overridden by a <code>BeanInfo</code> class, a property named "xyz"
0086: * will have a getter method named <code>getXyz()</code> or (for boolean
0087: * properties only) <code>isXyz()</code>, and a setter method named
0088: * <code>setXyz()</code>.</li>
0089: * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first
0090: * name element is used to select a property getter, as for simple
0091: * references above. The object returned for this property is then
0092: * consulted, using the same approach, for a property getter for a
0093: * property named <code>name2</code>, and so on. The property value that
0094: * is ultimately retrieved or modified is the one identified by the
0095: * last name element.</li>
0096: * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
0097: * property value is assumed to be an array, or this JavaBean is assumed
0098: * to have indexed property getter and setter methods. The appropriate
0099: * (zero-relative) entry in the array is selected. <code>List</code>
0100: * objects are now also supported for read/write. You simply need to define
0101: * a getter that returns the <code>List</code></li>
0102: * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean
0103: * is assumed to have an property getter and setter methods with an
0104: * additional attribute of type <code>java.lang.String</code>.</li>
0105: * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
0106: * Combining mapped, nested, and indexed references is also
0107: * supported.</li>
0108: * </ul>
0109: *
0110: * @author Craig R. McClanahan
0111: * @author Ralph Schaer
0112: * @author Chris Audley
0113: * @author Rey Fran�ois
0114: * @author Gregor Ra�man
0115: * @author Jan Sorensen
0116: * @author Scott Sanders
0117: * @see PropertyUtils
0118: * @since 1.7
0119: */
0120:
0121: public class PropertyUtilsBean {
0122:
0123: private static final PropertyUtilsBean instance = new PropertyUtilsBean();
0124:
0125: // --------------------------------------------------------- Class Methods
0126:
0127: protected static PropertyUtilsBean getInstance() {
0128: return instance;
0129: }
0130:
0131: // --------------------------------------------------------- Variables
0132:
0133: /**
0134: * The cache of PropertyDescriptor arrays for beans we have already
0135: * introspected, keyed by the java.lang.Class of this object.
0136: */
0137: private Map descriptorsCache = null;
0138: private Map mappedDescriptorsCache = null;
0139:
0140: // ---------------------------------------------------------- Constructors
0141:
0142: /**
0143: * Base constructor
0144: */
0145: public PropertyUtilsBean() {
0146: descriptorsCache = new HashMap();
0147: mappedDescriptorsCache = new HashMap();
0148: }
0149:
0150: // --------------------------------------------------------- Public Methods
0151:
0152: /**
0153: * Clear any cached property descriptors information for all classes
0154: * loaded by any class loaders. This is useful in cases where class
0155: * loaders are thrown away to implement class reloading.
0156: */
0157: public void clearDescriptors() {
0158:
0159: descriptorsCache.clear();
0160: mappedDescriptorsCache.clear();
0161: Introspector.flushCaches();
0162:
0163: }
0164:
0165: /**
0166: * <p>Copy property values from the "origin" bean to the "destination" bean
0167: * for all cases where the property names are the same (even though the
0168: * actual getter and setter methods might have been customized via
0169: * <code>BeanInfo</code> classes). No conversions are performed on the
0170: * actual property values -- it is assumed that the values retrieved from
0171: * the origin bean are assignment-compatible with the types expected by
0172: * the destination bean.</p>
0173: * <p/>
0174: * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
0175: * to contain String-valued <strong>simple</strong> property names as the keys, pointing
0176: * at the corresponding property values that will be set in the destination
0177: * bean.<strong>Note</strong> that this method is intended to perform
0178: * a "shallow copy" of the properties and so complex properties
0179: * (for example, nested ones) will not be copied.</p>
0180: *
0181: * @param dest Destination bean whose properties are modified
0182: * @param orig Origin bean whose properties are retrieved
0183: * @throws IllegalAccessException if the caller does not have
0184: * access to the property accessor method
0185: * @throws IllegalArgumentException if the <code>dest</code> or
0186: * <code>orig</code> argument is null
0187: * @throws InvocationTargetException if the property accessor method
0188: * throws an exception
0189: * @throws NoSuchMethodException if an accessor method for this
0190: * propety cannot be found
0191: */
0192: public void copyProperties(Object dest, Object orig)
0193: throws IllegalAccessException, InvocationTargetException,
0194: NoSuchMethodException {
0195:
0196: if (dest == null) {
0197: throw new IllegalArgumentException(
0198: "No destination bean specified");
0199: }
0200: if (orig == null) {
0201: throw new IllegalArgumentException(
0202: "No origin bean specified");
0203: }
0204: if (orig instanceof Map) {
0205: Iterator names = ((Map) orig).keySet().iterator();
0206: while (names.hasNext()) {
0207: String name = (String) names.next();
0208: if (isWriteable(dest, name)) {
0209: Object value = ((Map) orig).get(name);
0210: setSimpleProperty(dest, name, value);
0211: }
0212: }
0213: } else /* if (orig is a standard JavaBean) */
0214: {
0215: PropertyDescriptor origDescriptors[] = getPropertyDescriptors(orig);
0216: for (int i = 0; i < origDescriptors.length; i++) {
0217: String name = origDescriptors[i].getName();
0218: if (isReadable(orig, name)) {
0219: if (isWriteable(dest, name)) {
0220: Object value = getSimpleProperty(orig, name);
0221: setSimpleProperty(dest, name, value);
0222: }
0223: }
0224: }
0225: }
0226:
0227: }
0228:
0229: /**
0230: * <p>Return the entire set of properties for which the specified bean
0231: * provides a read method. This map contains the unconverted property
0232: * values for all properties for which a read method is provided
0233: * (i.e. where the <code>getReadMethod()</code> returns non-null).</p>
0234: * <p/>
0235: * <p><strong>FIXME</strong> - Does not account for mapped properties.</p>
0236: *
0237: * @param bean Bean whose properties are to be extracted
0238: * @throws IllegalAccessException if the caller does not have
0239: * access to the property accessor method
0240: * @throws IllegalArgumentException if <code>bean</code> is null
0241: * @throws InvocationTargetException if the property accessor method
0242: * throws an exception
0243: * @throws NoSuchMethodException if an accessor method for this
0244: * propety cannot be found
0245: */
0246: public Map describe(Object bean) throws IllegalAccessException,
0247: InvocationTargetException, NoSuchMethodException {
0248:
0249: if (bean == null) {
0250: throw new IllegalArgumentException("No bean specified");
0251: }
0252: Map description = new HashMap();
0253: PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
0254: for (int i = 0; i < descriptors.length; i++) {
0255: String name = descriptors[i].getName();
0256: if (descriptors[i].getReadMethod() != null)
0257: description.put(name, getProperty(bean, name));
0258: }
0259: return (description);
0260:
0261: }
0262:
0263: /**
0264: * Return the value of the specified indexed property of the specified
0265: * bean, with no type conversions. The zero-relative index of the
0266: * required value must be included (in square brackets) as a suffix to
0267: * the property name, or <code>IllegalArgumentException</code> will be
0268: * thrown. In addition to supporting the JavaBeans specification, this
0269: * method has been extended to support <code>List</code> objects as well.
0270: *
0271: * @param bean Bean whose property is to be extracted
0272: * @param name <code>propertyname[index]</code> of the property value
0273: * to be extracted
0274: * @throws ArrayIndexOutOfBoundsException if the specified index
0275: * is outside the valid range for the underlying array
0276: * @throws IllegalAccessException if the caller does not have
0277: * access to the property accessor method
0278: * @throws IllegalArgumentException if <code>bean</code> or
0279: * <code>name</code> is null
0280: * @throws InvocationTargetException if the property accessor method
0281: * throws an exception
0282: * @throws NoSuchMethodException if an accessor method for this
0283: * propety cannot be found
0284: */
0285: public Object getIndexedProperty(Object bean, String name)
0286: throws IllegalAccessException, InvocationTargetException,
0287: NoSuchMethodException {
0288:
0289: if (bean == null) {
0290: throw new IllegalArgumentException("No bean specified");
0291: }
0292: if (name == null) {
0293: throw new IllegalArgumentException("No name specified");
0294: }
0295:
0296: // Identify the index of the requested individual property
0297: int delim = name.indexOf(PropertyUtils.INDEXED_DELIM);
0298: int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2);
0299: if ((delim < 0) || (delim2 <= delim)) {
0300: throw new IllegalArgumentException(
0301: "Invalid indexed property '" + name + "'");
0302: }
0303: int index;
0304: try {
0305: String subscript = name.substring(delim + 1, delim2);
0306: index = Integer.parseInt(subscript);
0307: } catch (NumberFormatException e) {
0308: throw new IllegalArgumentException(
0309: "Invalid indexed property '" + name + "'");
0310: }
0311: name = name.substring(0, delim);
0312:
0313: // Request the specified indexed property value
0314: return (getIndexedProperty(bean, name, index));
0315:
0316: }
0317:
0318: /**
0319: * Return the value of the specified indexed property of the specified
0320: * bean, with no type conversions. In addition to supporting the JavaBeans
0321: * specification, this method has been extended to support
0322: * <code>List</code> objects as well.
0323: *
0324: * @param bean Bean whose property is to be extracted
0325: * @param name Simple property name of the property value to be extracted
0326: * @param index Index of the property value to be extracted
0327: * @throws ArrayIndexOutOfBoundsException if the specified index
0328: * is outside the valid range for the underlying array
0329: * @throws IllegalAccessException if the caller does not have
0330: * access to the property accessor method
0331: * @throws IllegalArgumentException if <code>bean</code> or
0332: * <code>name</code> is null
0333: * @throws InvocationTargetException if the property accessor method
0334: * throws an exception
0335: * @throws NoSuchMethodException if an accessor method for this
0336: * propety cannot be found
0337: */
0338: public Object getIndexedProperty(Object bean, String name, int index)
0339: throws IllegalAccessException, InvocationTargetException,
0340: NoSuchMethodException {
0341:
0342: if (bean == null) {
0343: throw new IllegalArgumentException("No bean specified");
0344: }
0345: if (name == null) {
0346: throw new IllegalArgumentException("No name specified");
0347: }
0348:
0349: // Retrieve the property descriptor for the specified property
0350: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
0351: name);
0352: if (descriptor == null) {
0353: throw new NoSuchMethodException("Unknown property '" + name
0354: + "'");
0355: }
0356:
0357: // Call the indexed getter method if there is one
0358: if (descriptor instanceof IndexedPropertyDescriptor) {
0359: Method readMethod = ((IndexedPropertyDescriptor) descriptor)
0360: .getIndexedReadMethod();
0361: if (readMethod != null) {
0362: Object subscript[] = new Object[1];
0363: subscript[0] = new Integer(index);
0364: try {
0365: return (invokeMethod(readMethod, bean, subscript));
0366: } catch (InvocationTargetException e) {
0367: if (e.getTargetException() instanceof ArrayIndexOutOfBoundsException) {
0368: throw (ArrayIndexOutOfBoundsException) e
0369: .getTargetException();
0370: } else {
0371: throw e;
0372: }
0373: }
0374: }
0375: }
0376:
0377: // Otherwise, the underlying property must be an array
0378: Method readMethod = getReadMethod(descriptor);
0379: if (readMethod == null) {
0380: throw new NoSuchMethodException("Property '" + name
0381: + "' has no getter method");
0382: }
0383:
0384: // Call the property getter and return the value
0385: Object value = invokeMethod(readMethod, bean, new Object[0]);
0386: if (!value.getClass().isArray()) {
0387: if (!(value instanceof java.util.List)) {
0388: throw new IllegalArgumentException("Property '" + name
0389: + "' is not indexed");
0390: } else {
0391: //get the List's value
0392: return ((java.util.List) value).get(index);
0393: }
0394: } else {
0395: //get the array's value
0396: return (Array.get(value, index));
0397: }
0398:
0399: }
0400:
0401: /**
0402: * Return the value of the specified mapped property of the
0403: * specified bean, with no type conversions. The key of the
0404: * required value must be included (in brackets) as a suffix to
0405: * the property name, or <code>IllegalArgumentException</code> will be
0406: * thrown.
0407: *
0408: * @param bean Bean whose property is to be extracted
0409: * @param name <code>propertyname(key)</code> of the property value
0410: * to be extracted
0411: * @throws IllegalAccessException if the caller does not have
0412: * access to the property accessor method
0413: * @throws InvocationTargetException if the property accessor method
0414: * throws an exception
0415: * @throws NoSuchMethodException if an accessor method for this
0416: * propety cannot be found
0417: */
0418: public Object getMappedProperty(Object bean, String name)
0419: throws IllegalAccessException, InvocationTargetException,
0420: NoSuchMethodException {
0421:
0422: if (bean == null) {
0423: throw new IllegalArgumentException("No bean specified");
0424: }
0425: if (name == null) {
0426: throw new IllegalArgumentException("No name specified");
0427: }
0428:
0429: // Identify the index of the requested individual property
0430: int delim = name.indexOf(PropertyUtils.MAPPED_DELIM);
0431: int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
0432: if ((delim < 0) || (delim2 <= delim)) {
0433: throw new IllegalArgumentException(
0434: "Invalid mapped property '" + name + "'");
0435: }
0436:
0437: // Isolate the name and the key
0438: String key = name.substring(delim + 1, delim2);
0439: name = name.substring(0, delim);
0440:
0441: // Request the specified indexed property value
0442: return (getMappedProperty(bean, name, key));
0443:
0444: }
0445:
0446: /**
0447: * Return the value of the specified mapped property of the specified
0448: * bean, with no type conversions.
0449: *
0450: * @param bean Bean whose property is to be extracted
0451: * @param name Mapped property name of the property value to be extracted
0452: * @param key Key of the property value to be extracted
0453: * @throws IllegalAccessException if the caller does not have
0454: * access to the property accessor method
0455: * @throws InvocationTargetException if the property accessor method
0456: * throws an exception
0457: * @throws NoSuchMethodException if an accessor method for this
0458: * propety cannot be found
0459: */
0460: public Object getMappedProperty(Object bean, String name, String key)
0461: throws IllegalAccessException, InvocationTargetException,
0462: NoSuchMethodException {
0463:
0464: if (bean == null) {
0465: throw new IllegalArgumentException("No bean specified");
0466: }
0467: if (name == null) {
0468: throw new IllegalArgumentException("No name specified");
0469: }
0470: if (key == null) {
0471: throw new IllegalArgumentException("No key specified");
0472: }
0473:
0474: // Handle DynaBean instances specially
0475: Object result = null;
0476:
0477: // Retrieve the property descriptor for the specified property
0478: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
0479: name);
0480: if (descriptor == null) {
0481: throw new NoSuchMethodException("Unknown property '" + name
0482: + "'");
0483: }
0484:
0485: if (descriptor instanceof MappedPropertyDescriptor) {
0486: // Call the keyed getter method if there is one
0487: Method readMethod = ((MappedPropertyDescriptor) descriptor)
0488: .getMappedReadMethod();
0489: if (readMethod != null) {
0490: Object keyArray[] = new Object[1];
0491: keyArray[0] = key;
0492: result = invokeMethod(readMethod, bean, keyArray);
0493: } else {
0494: throw new NoSuchMethodException("Property '" + name
0495: + "' has no mapped getter method");
0496: }
0497: } else {
0498: /* means that the result has to be retrieved from a map */
0499: Method readMethod = descriptor.getReadMethod();
0500: if (readMethod != null) {
0501: Object invokeResult = invokeMethod(readMethod, bean,
0502: new Object[0]);
0503: /* test and fetch from the map */
0504: if (invokeResult instanceof java.util.Map) {
0505: result = ((java.util.Map) invokeResult).get(key);
0506: }
0507: } else {
0508: throw new NoSuchMethodException("Property '" + name
0509: + "' has no mapped getter method");
0510: }
0511: }
0512: return result;
0513:
0514: }
0515:
0516: /**
0517: * <p>Return the mapped property descriptors for this bean class.</p>
0518: * <p/>
0519: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0520: *
0521: * @param beanClass Bean class to be introspected
0522: */
0523: public Map getMappedPropertyDescriptors(Class beanClass) {
0524:
0525: if (beanClass == null) {
0526: return null;
0527: }
0528:
0529: // Look up any cached descriptors for this bean class
0530: return (Map) mappedDescriptorsCache.get(beanClass);
0531:
0532: }
0533:
0534: /**
0535: * <p>Return the mapped property descriptors for this bean.</p>
0536: * <p/>
0537: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0538: *
0539: * @param bean Bean to be introspected
0540: */
0541: public Map getMappedPropertyDescriptors(Object bean) {
0542:
0543: if (bean == null) {
0544: return null;
0545: }
0546: return (getMappedPropertyDescriptors(bean.getClass()));
0547:
0548: }
0549:
0550: /**
0551: * Return the value of the (possibly nested) property of the specified
0552: * name, for the specified bean, with no type conversions.
0553: *
0554: * @param bean Bean whose property is to be extracted
0555: * @param name Possibly nested name of the property to be extracted
0556: * @throws IllegalAccessException if the caller does not have
0557: * access to the property accessor method
0558: * @throws IllegalArgumentException if <code>bean</code> or
0559: * <code>name</code> is null
0560: * @throws IllegalArgumentException if a nested reference to a
0561: * property returns null
0562: * @throws InvocationTargetException if the property accessor method throws an exception
0563: * @throws NoSuchMethodException if an accessor method for this
0564: * propety cannot be found
0565: */
0566: public Object getNestedProperty(Object bean, String name)
0567: throws IllegalAccessException, InvocationTargetException,
0568: NoSuchMethodException {
0569:
0570: if (bean == null) {
0571: throw new IllegalArgumentException("No bean specified");
0572: }
0573: if (name == null) {
0574: throw new IllegalArgumentException("No name specified");
0575: }
0576:
0577: int indexOfINDEXED_DELIM;
0578: int indexOfMAPPED_DELIM;
0579: int indexOfMAPPED_DELIM2;
0580: int indexOfNESTED_DELIM;
0581: while (true) {
0582: indexOfNESTED_DELIM = name
0583: .indexOf(PropertyUtils.NESTED_DELIM);
0584: indexOfMAPPED_DELIM = name
0585: .indexOf(PropertyUtils.MAPPED_DELIM);
0586: indexOfMAPPED_DELIM2 = name
0587: .indexOf(PropertyUtils.MAPPED_DELIM2);
0588: if (indexOfMAPPED_DELIM2 >= 0
0589: && indexOfMAPPED_DELIM >= 0
0590: && (indexOfNESTED_DELIM < 0 || indexOfNESTED_DELIM > indexOfMAPPED_DELIM)) {
0591: indexOfNESTED_DELIM = name.indexOf(
0592: PropertyUtils.NESTED_DELIM,
0593: indexOfMAPPED_DELIM2);
0594: } else {
0595: indexOfNESTED_DELIM = name
0596: .indexOf(PropertyUtils.NESTED_DELIM);
0597: }
0598: if (indexOfNESTED_DELIM < 0) {
0599: break;
0600: }
0601: String next = name.substring(0, indexOfNESTED_DELIM);
0602: indexOfINDEXED_DELIM = next
0603: .indexOf(PropertyUtils.INDEXED_DELIM);
0604: indexOfMAPPED_DELIM = next
0605: .indexOf(PropertyUtils.MAPPED_DELIM);
0606: if (bean instanceof Map) {
0607: bean = ((Map) bean).get(next);
0608: } else if (indexOfMAPPED_DELIM >= 0) {
0609: bean = getMappedProperty(bean, next);
0610: } else if (indexOfINDEXED_DELIM >= 0) {
0611: bean = getIndexedProperty(bean, next);
0612: } else {
0613: bean = getSimpleProperty(bean, next);
0614: }
0615: if (bean == null) {
0616: throw new NullPointerException(
0617: "Null property value for '"
0618: + name
0619: .substring(0,
0620: indexOfNESTED_DELIM)
0621: + "'");
0622: }
0623: name = name.substring(indexOfNESTED_DELIM + 1);
0624: }
0625:
0626: indexOfINDEXED_DELIM = name
0627: .indexOf(PropertyUtils.INDEXED_DELIM);
0628: indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
0629:
0630: if (bean instanceof Map) {
0631: bean = ((Map) bean).get(name);
0632: } else if (indexOfMAPPED_DELIM >= 0) {
0633: bean = getMappedProperty(bean, name);
0634: } else if (indexOfINDEXED_DELIM >= 0) {
0635: bean = getIndexedProperty(bean, name);
0636: } else {
0637: bean = getSimpleProperty(bean, name);
0638: }
0639: return bean;
0640:
0641: }
0642:
0643: /**
0644: * Return the value of the specified property of the specified bean,
0645: * no matter which property reference format is used, with no
0646: * type conversions.
0647: *
0648: * @param bean Bean whose property is to be extracted
0649: * @param name Possibly indexed and/or nested name of the property
0650: * to be extracted
0651: * @throws IllegalAccessException if the caller does not have
0652: * access to the property accessor method
0653: * @throws IllegalArgumentException if <code>bean</code> or
0654: * <code>name</code> is null
0655: * @throws InvocationTargetException if the property accessor method
0656: * throws an exception
0657: * @throws NoSuchMethodException if an accessor method for this
0658: * propety cannot be found
0659: */
0660: public Object getProperty(Object bean, String name)
0661: throws IllegalAccessException, InvocationTargetException,
0662: NoSuchMethodException {
0663:
0664: return (getNestedProperty(bean, name));
0665:
0666: }
0667:
0668: /**
0669: * <p>Retrieve the property descriptor for the specified property of the
0670: * specified bean, or return <code>null</code> if there is no such
0671: * descriptor. This method resolves indexed and nested property
0672: * references in the same manner as other methods in this class, except
0673: * that if the last (or only) name element is indexed, the descriptor
0674: * for the last resolved property itself is returned.</p>
0675: * <p/>
0676: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0677: *
0678: * @param bean Bean for which a property descriptor is requested
0679: * @param name Possibly indexed and/or nested name of the property for
0680: * which a property descriptor is requested
0681: * @throws IllegalAccessException if the caller does not have
0682: * access to the property accessor method
0683: * @throws IllegalArgumentException if <code>bean</code> or
0684: * <code>name</code> is null
0685: * @throws IllegalArgumentException if a nested reference to a
0686: * property returns null
0687: * @throws InvocationTargetException if the property accessor method
0688: * throws an exception
0689: * @throws NoSuchMethodException if an accessor method for this
0690: * propety cannot be found
0691: */
0692: public PropertyDescriptor getPropertyDescriptor(Object bean,
0693: String name) throws IllegalAccessException,
0694: InvocationTargetException, NoSuchMethodException {
0695:
0696: if (bean == null) {
0697: throw new IllegalArgumentException("No bean specified");
0698: }
0699: if (name == null) {
0700: throw new IllegalArgumentException("No name specified");
0701: }
0702:
0703: // Resolve nested references
0704: while (true) {
0705: int period = findNextNestedIndex(name);
0706: if (period < 0) {
0707: break;
0708: }
0709: String next = name.substring(0, period);
0710: int indexOfINDEXED_DELIM = next
0711: .indexOf(PropertyUtils.INDEXED_DELIM);
0712: int indexOfMAPPED_DELIM = next
0713: .indexOf(PropertyUtils.MAPPED_DELIM);
0714: if (indexOfMAPPED_DELIM >= 0
0715: && (indexOfINDEXED_DELIM < 0 || indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) {
0716: bean = getMappedProperty(bean, next);
0717: } else {
0718: if (indexOfINDEXED_DELIM >= 0) {
0719: bean = getIndexedProperty(bean, next);
0720: } else {
0721: bean = getSimpleProperty(bean, next);
0722: }
0723: }
0724: if (bean == null) {
0725: throw new IllegalArgumentException(
0726: "Null property value for '"
0727: + name.substring(0, period) + "'");
0728: }
0729: name = name.substring(period + 1);
0730: }
0731:
0732: // Remove any subscript from the final name value
0733: int left = name.indexOf(PropertyUtils.INDEXED_DELIM);
0734: if (left >= 0) {
0735: name = name.substring(0, left);
0736: }
0737: left = name.indexOf(PropertyUtils.MAPPED_DELIM);
0738: if (left >= 0) {
0739: name = name.substring(0, left);
0740: }
0741:
0742: // Look up and return this property from our cache
0743: // creating and adding it to the cache if not found.
0744: if ((bean == null) || (name == null)) {
0745: return (null);
0746: }
0747:
0748: PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
0749: if (descriptors != null) {
0750:
0751: for (int i = 0; i < descriptors.length; i++) {
0752: if (name.equals(descriptors[i].getName()))
0753: return (descriptors[i]);
0754: }
0755: }
0756:
0757: PropertyDescriptor result;
0758: Map mappedDescriptors = getMappedPropertyDescriptors(bean);
0759: if (mappedDescriptors == null) {
0760: mappedDescriptors = new HashMap();
0761: mappedDescriptorsCache.put(bean.getClass(),
0762: mappedDescriptors);
0763: }
0764: result = (PropertyDescriptor) mappedDescriptors.get(name);
0765: if (result == null) {
0766: // not found, try to create it
0767: try {
0768: result = new MappedPropertyDescriptor(name, bean
0769: .getClass());
0770: } catch (IntrospectionException ie) {
0771: }
0772: if (result != null) {
0773: mappedDescriptors.put(name, result);
0774: }
0775: }
0776:
0777: return result;
0778:
0779: }
0780:
0781: private int findNextNestedIndex(String expression) {
0782: // walk back from the end to the start
0783: // and find the first index that
0784: int bracketCount = 0;
0785: for (int i = 0, size = expression.length(); i < size; i++) {
0786: char at = expression.charAt(i);
0787: switch (at) {
0788: case PropertyUtils.NESTED_DELIM:
0789: if (bracketCount < 1) {
0790: return i;
0791: }
0792: break;
0793:
0794: case PropertyUtils.MAPPED_DELIM:
0795: case PropertyUtils.INDEXED_DELIM:
0796: // not bothered which
0797: ++bracketCount;
0798: break;
0799:
0800: case PropertyUtils.MAPPED_DELIM2:
0801: case PropertyUtils.INDEXED_DELIM2:
0802: // not bothered which
0803: --bracketCount;
0804: break;
0805: }
0806: }
0807: // can't find any
0808: return -1;
0809: }
0810:
0811: /**
0812: * <p>Retrieve the property descriptors for the specified class,
0813: * introspecting and caching them the first time a particular bean class
0814: * is encountered.</p>
0815: * <p/>
0816: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0817: *
0818: * @param beanClass Bean class for which property descriptors are requested
0819: * @throws IllegalArgumentException if <code>beanClass</code> is null
0820: */
0821: public PropertyDescriptor[] getPropertyDescriptors(Class beanClass) {
0822:
0823: if (beanClass == null) {
0824: throw new IllegalArgumentException(
0825: "No bean class specified");
0826: }
0827:
0828: // Look up any cached descriptors for this bean class
0829: PropertyDescriptor[] descriptors;
0830: descriptors = (PropertyDescriptor[]) descriptorsCache
0831: .get(beanClass);
0832: if (descriptors != null) {
0833: return (descriptors);
0834: }
0835:
0836: // Introspect the bean and cache the generated descriptors
0837: BeanInfo beanInfo;
0838: try {
0839: beanInfo = Introspector.getBeanInfo(beanClass);
0840: } catch (IntrospectionException e) {
0841: return (new PropertyDescriptor[0]);
0842: }
0843: descriptors = beanInfo.getPropertyDescriptors();
0844: if (descriptors == null) {
0845: descriptors = new PropertyDescriptor[0];
0846: }
0847: descriptorsCache.put(beanClass, descriptors);
0848: return (descriptors);
0849:
0850: }
0851:
0852: /**
0853: * <p>Retrieve the property descriptors for the specified bean,
0854: * introspecting and caching them the first time a particular bean class
0855: * is encountered.</p>
0856: * <p/>
0857: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0858: *
0859: * @param bean Bean for which property descriptors are requested
0860: * @throws IllegalArgumentException if <code>bean</code> is null
0861: */
0862: public PropertyDescriptor[] getPropertyDescriptors(Object bean) {
0863:
0864: if (bean == null) {
0865: throw new IllegalArgumentException("No bean specified");
0866: }
0867: return (getPropertyDescriptors(bean.getClass()));
0868:
0869: }
0870:
0871: /**
0872: * <p>Return the Java Class repesenting the property editor class that has
0873: * been registered for this property (if any). This method follows the
0874: * same name resolution rules used by <code>getPropertyDescriptor()</code>,
0875: * so if the last element of a name reference is indexed, the property
0876: * editor for the underlying property's class is returned.</p>
0877: * <p/>
0878: * <p>Note that <code>null</code> will be returned if there is no property,
0879: * or if there is no registered property editor class. Because this
0880: * return value is ambiguous, you should determine the existence of the
0881: * property itself by other means.</p>
0882: * <p/>
0883: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0884: *
0885: * @param bean Bean for which a property descriptor is requested
0886: * @param name Possibly indexed and/or nested name of the property for
0887: * which a property descriptor is requested
0888: * @throws IllegalAccessException if the caller does not have
0889: * access to the property accessor method
0890: * @throws IllegalArgumentException if <code>bean</code> or
0891: * <code>name</code> is null
0892: * @throws IllegalArgumentException if a nested reference to a
0893: * property returns null
0894: * @throws InvocationTargetException if the property accessor method
0895: * throws an exception
0896: * @throws NoSuchMethodException if an accessor method for this
0897: * propety cannot be found
0898: */
0899: public Class getPropertyEditorClass(Object bean, String name)
0900: throws IllegalAccessException, InvocationTargetException,
0901: NoSuchMethodException {
0902:
0903: if (bean == null) {
0904: throw new IllegalArgumentException("No bean specified");
0905: }
0906: if (name == null) {
0907: throw new IllegalArgumentException("No name specified");
0908: }
0909:
0910: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
0911: name);
0912: if (descriptor != null) {
0913: return (descriptor.getPropertyEditorClass());
0914: } else {
0915: return (null);
0916: }
0917:
0918: }
0919:
0920: /**
0921: * Return the Java Class representing the property type of the specified
0922: * property, or <code>null</code> if there is no such property for the
0923: * specified bean. This method follows the same name resolution rules
0924: * used by <code>getPropertyDescriptor()</code>, so if the last element
0925: * of a name reference is indexed, the type of the property itself will
0926: * be returned. If the last (or only) element has no property with the
0927: * specified name, <code>null</code> is returned.
0928: *
0929: * @param bean Bean for which a property descriptor is requested
0930: * @param name Possibly indexed and/or nested name of the property for
0931: * which a property descriptor is requested
0932: * @throws IllegalAccessException if the caller does not have
0933: * access to the property accessor method
0934: * @throws IllegalArgumentException if <code>bean</code> or
0935: * <code>name</code> is null
0936: * @throws IllegalArgumentException if a nested reference to a
0937: * property returns null
0938: * @throws InvocationTargetException if the property accessor method
0939: * throws an exception
0940: * @throws NoSuchMethodException if an accessor method for this
0941: * propety cannot be found
0942: */
0943: public Class getPropertyType(Object bean, String name)
0944: throws IllegalAccessException, InvocationTargetException,
0945: NoSuchMethodException {
0946:
0947: if (bean == null) {
0948: throw new IllegalArgumentException("No bean specified");
0949: }
0950: if (name == null) {
0951: throw new IllegalArgumentException("No name specified");
0952: }
0953:
0954: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
0955: name);
0956: if (descriptor == null) {
0957: return (null);
0958: } else if (descriptor instanceof IndexedPropertyDescriptor) {
0959: return (((IndexedPropertyDescriptor) descriptor)
0960: .getIndexedPropertyType());
0961: } else if (descriptor instanceof MappedPropertyDescriptor) {
0962: return (((MappedPropertyDescriptor) descriptor)
0963: .getMappedPropertyType());
0964: } else {
0965: return (descriptor.getPropertyType());
0966: }
0967:
0968: }
0969:
0970: /**
0971: * <p>Return an accessible property getter method for this property,
0972: * if there is one; otherwise return <code>null</code>.</p>
0973: * <p/>
0974: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
0975: *
0976: * @param descriptor Property descriptor to return a getter for
0977: */
0978: public Method getReadMethod(PropertyDescriptor descriptor) {
0979:
0980: return (MethodUtils.getAccessibleMethod(descriptor
0981: .getReadMethod()));
0982:
0983: }
0984:
0985: /**
0986: * Return the value of the specified simple property of the specified
0987: * bean, with no type conversions.
0988: *
0989: * @param bean Bean whose property is to be extracted
0990: * @param name Name of the property to be extracted
0991: * @throws IllegalAccessException if the caller does not have
0992: * access to the property accessor method
0993: * @throws IllegalArgumentException if <code>bean</code> or
0994: * <code>name</code> is null
0995: * @throws IllegalArgumentException if the property name
0996: * is nested or indexed
0997: * @throws InvocationTargetException if the property accessor method
0998: * throws an exception
0999: * @throws NoSuchMethodException if an accessor method for this
1000: * propety cannot be found
1001: */
1002: public Object getSimpleProperty(Object bean, String name)
1003: throws IllegalAccessException, InvocationTargetException,
1004: NoSuchMethodException {
1005:
1006: if (bean == null) {
1007: throw new IllegalArgumentException("No bean specified");
1008: }
1009: if (name == null) {
1010: throw new IllegalArgumentException("No name specified");
1011: }
1012:
1013: // Validate the syntax of the property name
1014: if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
1015: throw new IllegalArgumentException(
1016: "Nested property names are not allowed");
1017: } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
1018: throw new IllegalArgumentException(
1019: "Indexed property names are not allowed");
1020: } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
1021: throw new IllegalArgumentException(
1022: "Mapped property names are not allowed");
1023: }
1024:
1025: // Retrieve the property getter method for the specified property
1026: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1027: name);
1028: if (descriptor == null) {
1029: throw new NoSuchMethodException("Unknown property '" + name
1030: + "' in bean " + bean + " of type "
1031: + bean.getClass());
1032: }
1033: Method readMethod = getReadMethod(descriptor);
1034: if (readMethod == null) {
1035: throw new NoSuchMethodException("Property '" + name
1036: + "' has no getter method");
1037: }
1038:
1039: // Call the property getter and return the value
1040: Object value = invokeMethod(readMethod, bean, new Object[0]);
1041: return (value);
1042:
1043: }
1044:
1045: /**
1046: * <p>Return an accessible property setter method for this property,
1047: * if there is one; otherwise return <code>null</code>.</p>
1048: * <p/>
1049: * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1050: *
1051: * @param descriptor Property descriptor to return a setter for
1052: */
1053: public Method getWriteMethod(PropertyDescriptor descriptor) {
1054:
1055: return (MethodUtils.getAccessibleMethod(descriptor
1056: .getWriteMethod()));
1057:
1058: }
1059:
1060: /**
1061: * <p>Return <code>true</code> if the specified property name identifies
1062: * a readable property on the specified bean; otherwise, return
1063: * <code>false</code>.
1064: *
1065: * @param bean Bean to be examined
1066: * @param name Property name to be evaluated
1067: * @throws IllegalArgumentException if <code>bean</code>
1068: * or <code>name</code> is <code>null</code>
1069: * @since BeanUtils 1.6
1070: */
1071: public boolean isReadable(Object bean, String name) {
1072:
1073: // Validate method parameters
1074: if (bean == null) {
1075: throw new IllegalArgumentException("No bean specified");
1076: }
1077: if (name == null) {
1078: throw new IllegalArgumentException("No name specified");
1079: }
1080:
1081: // Return the requested result
1082: try {
1083: PropertyDescriptor desc = getPropertyDescriptor(bean, name);
1084: if (desc != null) {
1085: Method readMethod = desc.getReadMethod();
1086: if ((readMethod == null)
1087: && (desc instanceof IndexedPropertyDescriptor)) {
1088: readMethod = ((IndexedPropertyDescriptor) desc)
1089: .getIndexedReadMethod();
1090: }
1091: return (readMethod != null);
1092: } else {
1093: return (false);
1094: }
1095: } catch (IllegalAccessException e) {
1096: return (false);
1097: } catch (InvocationTargetException e) {
1098: return (false);
1099: } catch (NoSuchMethodException e) {
1100: return (false);
1101: }
1102:
1103: }
1104:
1105: /**
1106: * <p>Return <code>true</code> if the specified property name identifies
1107: * a writeable property on the specified bean; otherwise, return
1108: * <code>false</code>.
1109: *
1110: * @param bean Bean to be examined
1111: * @param name Property name to be evaluated
1112: * @throws IllegalArgumentException if <code>bean</code>
1113: * or <code>name</code> is <code>null</code>
1114: * @since BeanUtils 1.6
1115: */
1116: public boolean isWriteable(Object bean, String name) {
1117:
1118: // Validate method parameters
1119: if (bean == null) {
1120: throw new IllegalArgumentException("No bean specified");
1121: }
1122: if (name == null) {
1123: throw new IllegalArgumentException("No name specified");
1124: }
1125:
1126: // Return the requested result
1127: try {
1128: PropertyDescriptor desc = getPropertyDescriptor(bean, name);
1129: if (desc != null) {
1130: Method writeMethod = desc.getWriteMethod();
1131: if ((writeMethod == null)
1132: && (desc instanceof IndexedPropertyDescriptor)) {
1133: writeMethod = ((IndexedPropertyDescriptor) desc)
1134: .getIndexedWriteMethod();
1135: }
1136: return (writeMethod != null);
1137: } else {
1138: return (false);
1139: }
1140: } catch (IllegalAccessException e) {
1141: return (false);
1142: } catch (InvocationTargetException e) {
1143: return (false);
1144: } catch (NoSuchMethodException e) {
1145: return (false);
1146: }
1147: }
1148:
1149: /**
1150: * Set the value of the specified indexed property of the specified
1151: * bean, with no type conversions. The zero-relative index of the
1152: * required value must be included (in square brackets) as a suffix to
1153: * the property name, or <code>IllegalArgumentException</code> will be
1154: * thrown. In addition to supporting the JavaBeans specification, this
1155: * method has been extended to support <code>List</code> objects as well.
1156: *
1157: * @param bean Bean whose property is to be modified
1158: * @param name <code>propertyname[index]</code> of the property value
1159: * to be modified
1160: * @param value Value to which the specified property element
1161: * should be set
1162: * @throws ArrayIndexOutOfBoundsException if the specified index
1163: * is outside the valid range for the underlying array
1164: * @throws IllegalAccessException if the caller does not have
1165: * access to the property accessor method
1166: * @throws IllegalArgumentException if <code>bean</code> or
1167: * <code>name</code> is null
1168: * @throws InvocationTargetException if the property accessor method
1169: * throws an exception
1170: * @throws NoSuchMethodException if an accessor method for this
1171: * propety cannot be found
1172: */
1173: public void setIndexedProperty(Object bean, String name,
1174: Object value) throws IllegalAccessException,
1175: InvocationTargetException, NoSuchMethodException {
1176:
1177: if (bean == null) {
1178: throw new IllegalArgumentException("No bean specified");
1179: }
1180: if (name == null) {
1181: throw new IllegalArgumentException("No name specified");
1182: }
1183:
1184: // Identify the index of the requested individual property
1185: int delim = name.indexOf(PropertyUtils.INDEXED_DELIM);
1186: int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2);
1187: if ((delim < 0) || (delim2 <= delim)) {
1188: throw new IllegalArgumentException(
1189: "Invalid indexed property '" + name + "'");
1190: }
1191: int index;
1192: try {
1193: String subscript = name.substring(delim + 1, delim2);
1194: index = Integer.parseInt(subscript);
1195: } catch (NumberFormatException e) {
1196: throw new IllegalArgumentException(
1197: "Invalid indexed property '" + name + "'");
1198: }
1199: name = name.substring(0, delim);
1200:
1201: // Set the specified indexed property value
1202: setIndexedProperty(bean, name, index, value);
1203:
1204: }
1205:
1206: /**
1207: * Set the value of the specified indexed property of the specified
1208: * bean, with no type conversions. In addition to supporting the JavaBeans
1209: * specification, this method has been extended to support
1210: * <code>List</code> objects as well.
1211: *
1212: * @param bean Bean whose property is to be set
1213: * @param name Simple property name of the property value to be set
1214: * @param index Index of the property value to be set
1215: * @param value Value to which the indexed property element is to be set
1216: * @throws ArrayIndexOutOfBoundsException if the specified index
1217: * is outside the valid range for the underlying array
1218: * @throws IllegalAccessException if the caller does not have
1219: * access to the property accessor method
1220: * @throws IllegalArgumentException if <code>bean</code> or
1221: * <code>name</code> is null
1222: * @throws InvocationTargetException if the property accessor method
1223: * throws an exception
1224: * @throws NoSuchMethodException if an accessor method for this
1225: * propety cannot be found
1226: */
1227: public void setIndexedProperty(Object bean, String name, int index,
1228: Object value) throws IllegalAccessException,
1229: InvocationTargetException, NoSuchMethodException {
1230:
1231: if (bean == null) {
1232: throw new IllegalArgumentException("No bean specified");
1233: }
1234: if (name == null) {
1235: throw new IllegalArgumentException("No name specified");
1236: }
1237:
1238: // Retrieve the property descriptor for the specified property
1239: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1240: name);
1241: if (descriptor == null) {
1242: throw new NoSuchMethodException("Unknown property '" + name
1243: + "'");
1244: }
1245:
1246: // Call the indexed setter method if there is one
1247: if (descriptor instanceof IndexedPropertyDescriptor) {
1248: Method writeMethod = ((IndexedPropertyDescriptor) descriptor)
1249: .getIndexedWriteMethod();
1250: if (writeMethod != null) {
1251: Object subscript[] = new Object[2];
1252: subscript[0] = new Integer(index);
1253: subscript[1] = value;
1254: try {
1255: invokeMethod(writeMethod, bean, subscript);
1256: } catch (InvocationTargetException e) {
1257: if (e.getTargetException() instanceof ArrayIndexOutOfBoundsException) {
1258: throw (ArrayIndexOutOfBoundsException) e
1259: .getTargetException();
1260: } else {
1261: throw e;
1262: }
1263: }
1264: return;
1265: }
1266: }
1267:
1268: // Otherwise, the underlying property must be an array or a list
1269: Method readMethod = descriptor.getReadMethod();
1270: if (readMethod == null) {
1271: throw new NoSuchMethodException("Property '" + name
1272: + "' has no getter method");
1273: }
1274:
1275: // Call the property getter to get the array or list
1276: Object array = invokeMethod(readMethod, bean, new Object[0]);
1277: if (!array.getClass().isArray()) {
1278: if (array instanceof List) {
1279: // Modify the specified value in the List
1280: ((List) array).set(index, value);
1281: } else {
1282: throw new IllegalArgumentException("Property '" + name
1283: + "' is not indexed");
1284: }
1285: } else {
1286: // Modify the specified value in the array
1287: Array.set(array, index, value);
1288: }
1289:
1290: }
1291:
1292: /**
1293: * Set the value of the specified mapped property of the
1294: * specified bean, with no type conversions. The key of the
1295: * value to set must be included (in brackets) as a suffix to
1296: * the property name, or <code>IllegalArgumentException</code> will be
1297: * thrown.
1298: *
1299: * @param bean Bean whose property is to be set
1300: * @param name <code>propertyname(key)</code> of the property value
1301: * to be set
1302: * @param value The property value to be set
1303: * @throws IllegalAccessException if the caller does not have
1304: * access to the property accessor method
1305: * @throws InvocationTargetException if the property accessor method
1306: * throws an exception
1307: * @throws NoSuchMethodException if an accessor method for this
1308: * propety cannot be found
1309: */
1310: public void setMappedProperty(Object bean, String name, Object value)
1311: throws IllegalAccessException, InvocationTargetException,
1312: NoSuchMethodException {
1313:
1314: if (bean == null) {
1315: throw new IllegalArgumentException("No bean specified");
1316: }
1317: if (name == null) {
1318: throw new IllegalArgumentException("No name specified");
1319: }
1320:
1321: // Identify the index of the requested individual property
1322: int delim = name.indexOf(PropertyUtils.MAPPED_DELIM);
1323: int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2);
1324: if ((delim < 0) || (delim2 <= delim)) {
1325: throw new IllegalArgumentException(
1326: "Invalid mapped property '" + name + "'");
1327: }
1328:
1329: // Isolate the name and the key
1330: String key = name.substring(delim + 1, delim2);
1331: name = name.substring(0, delim);
1332:
1333: // Request the specified indexed property value
1334: setMappedProperty(bean, name, key, value);
1335:
1336: }
1337:
1338: /**
1339: * Set the value of the specified mapped property of the specified
1340: * bean, with no type conversions.
1341: *
1342: * @param bean Bean whose property is to be set
1343: * @param name Mapped property name of the property value to be set
1344: * @param key Key of the property value to be set
1345: * @param value The property value to be set
1346: * @throws IllegalAccessException if the caller does not have
1347: * access to the property accessor method
1348: * @throws InvocationTargetException if the property accessor method
1349: * throws an exception
1350: * @throws NoSuchMethodException if an accessor method for this
1351: * propety cannot be found
1352: */
1353: public void setMappedProperty(Object bean, String name, String key,
1354: Object value) throws IllegalAccessException,
1355: InvocationTargetException, NoSuchMethodException {
1356:
1357: if (bean == null) {
1358: throw new IllegalArgumentException("No bean specified");
1359: }
1360: if (name == null) {
1361: throw new IllegalArgumentException("No name specified");
1362: }
1363: if (key == null) {
1364: throw new IllegalArgumentException("No key specified");
1365: }
1366:
1367: // Retrieve the property descriptor for the specified property
1368: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1369: name);
1370: if (descriptor == null) {
1371: throw new NoSuchMethodException("Unknown property '" + name
1372: + "'");
1373: }
1374:
1375: if (descriptor instanceof MappedPropertyDescriptor) {
1376: // Call the keyed setter method if there is one
1377: Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor)
1378: .getMappedWriteMethod();
1379: if (mappedWriteMethod != null) {
1380: Object params[] = new Object[2];
1381: params[0] = key;
1382: params[1] = value;
1383: invokeMethod(mappedWriteMethod, bean, params);
1384: } else {
1385: throw new NoSuchMethodException("Property '" + name
1386: + "' has no mapped setter method");
1387: }
1388: } else {
1389: /* means that the result has to be retrieved from a map */
1390: Method readMethod = descriptor.getReadMethod();
1391: if (readMethod != null) {
1392: Object invokeResult = invokeMethod(readMethod, bean,
1393: new Object[0]);
1394: /* test and fetch from the map */
1395: if (invokeResult instanceof java.util.Map) {
1396: ((java.util.Map) invokeResult).put(key, value);
1397: }
1398: } else {
1399: throw new NoSuchMethodException("Property '" + name
1400: + "' has no mapped getter method");
1401: }
1402: }
1403:
1404: }
1405:
1406: /**
1407: * Set the value of the (possibly nested) property of the specified
1408: * name, for the specified bean, with no type conversions.
1409: *
1410: * @param bean Bean whose property is to be modified
1411: * @param name Possibly nested name of the property to be modified
1412: * @param value Value to which the property is to be set
1413: * @throws IllegalAccessException if the caller does not have
1414: * access to the property accessor method
1415: * @throws IllegalArgumentException if <code>bean</code> or
1416: * <code>name</code> is null
1417: * @throws IllegalArgumentException if a nested reference to a
1418: * property returns null
1419: * @throws InvocationTargetException if the property accessor method
1420: * throws an exception
1421: * @throws NoSuchMethodException if an accessor method for this
1422: * propety cannot be found
1423: */
1424: public void setNestedProperty(Object bean, String name, Object value)
1425: throws IllegalAccessException, InvocationTargetException,
1426: NoSuchMethodException {
1427:
1428: if (bean == null) {
1429: throw new IllegalArgumentException("No bean specified");
1430: }
1431: if (name == null) {
1432: throw new IllegalArgumentException("No name specified");
1433: }
1434:
1435: int indexOfINDEXED_DELIM;
1436: int indexOfMAPPED_DELIM;
1437: while (true) {
1438: int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
1439: if (delim < 0) {
1440: break;
1441: }
1442: String next = name.substring(0, delim);
1443: indexOfINDEXED_DELIM = next
1444: .indexOf(PropertyUtils.INDEXED_DELIM);
1445: indexOfMAPPED_DELIM = next
1446: .indexOf(PropertyUtils.MAPPED_DELIM);
1447: if (bean instanceof Map) {
1448: bean = ((Map) bean).get(next);
1449: } else if (indexOfMAPPED_DELIM >= 0) {
1450: bean = getMappedProperty(bean, next);
1451: } else if (indexOfINDEXED_DELIM >= 0) {
1452: bean = getIndexedProperty(bean, next);
1453: } else {
1454: bean = getSimpleProperty(bean, next);
1455: }
1456: if (bean == null) {
1457: throw new IllegalArgumentException(
1458: "Null property value for '"
1459: + name.substring(0, delim) + "'");
1460: }
1461: name = name.substring(delim + 1);
1462: }
1463:
1464: indexOfINDEXED_DELIM = name
1465: .indexOf(PropertyUtils.INDEXED_DELIM);
1466: indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
1467:
1468: if (bean instanceof Map) {
1469: // check to see if the class has a standard property
1470: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1471: name);
1472: if (descriptor == null) {
1473: // no - then put the value into the map
1474: ((Map) bean).put(name, value);
1475: } else {
1476: // yes - use that instead
1477: setSimpleProperty(bean, name, value);
1478: }
1479: } else if (indexOfMAPPED_DELIM >= 0) {
1480: setMappedProperty(bean, name, value);
1481: } else if (indexOfINDEXED_DELIM >= 0) {
1482: setIndexedProperty(bean, name, value);
1483: } else {
1484: setSimpleProperty(bean, name, value);
1485: }
1486:
1487: }
1488:
1489: /**
1490: * Set the value of the specified property of the specified bean,
1491: * no matter which property reference format is used, with no
1492: * type conversions.
1493: *
1494: * @param bean Bean whose property is to be modified
1495: * @param name Possibly indexed and/or nested name of the property
1496: * to be modified
1497: * @param value Value to which this property is to be set
1498: * @throws IllegalAccessException if the caller does not have
1499: * access to the property accessor method
1500: * @throws IllegalArgumentException if <code>bean</code> or
1501: * <code>name</code> is null
1502: * @throws InvocationTargetException if the property accessor method
1503: * throws an exception
1504: * @throws NoSuchMethodException if an accessor method for this
1505: * propety cannot be found
1506: */
1507: public void setProperty(Object bean, String name, Object value)
1508: throws IllegalAccessException, InvocationTargetException,
1509: NoSuchMethodException {
1510:
1511: setNestedProperty(bean, name, value);
1512:
1513: }
1514:
1515: /**
1516: * Set the value of the specified simple property of the specified bean,
1517: * with no type conversions.
1518: *
1519: * @param bean Bean whose property is to be modified
1520: * @param name Name of the property to be modified
1521: * @param value Value to which the property should be set
1522: * @throws IllegalAccessException if the caller does not have
1523: * access to the property accessor method
1524: * @throws IllegalArgumentException if <code>bean</code> or
1525: * <code>name</code> is null
1526: * @throws IllegalArgumentException if the property name is
1527: * nested or indexed
1528: * @throws InvocationTargetException if the property accessor method
1529: * throws an exception
1530: * @throws NoSuchMethodException if an accessor method for this
1531: * propety cannot be found
1532: */
1533: public void setSimpleProperty(Object bean, String name, Object value)
1534: throws IllegalAccessException, InvocationTargetException,
1535: NoSuchMethodException {
1536:
1537: if (bean == null) {
1538: throw new IllegalArgumentException("No bean specified");
1539: }
1540: if (name == null) {
1541: throw new IllegalArgumentException("No name specified");
1542: }
1543:
1544: // Validate the syntax of the property name
1545: if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
1546: throw new IllegalArgumentException(
1547: "Nested property names are not allowed");
1548: } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
1549: throw new IllegalArgumentException(
1550: "Indexed property names are not allowed");
1551: } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
1552: throw new IllegalArgumentException(
1553: "Mapped property names are not allowed");
1554: }
1555:
1556: // Retrieve the property setter method for the specified property
1557: PropertyDescriptor descriptor = getPropertyDescriptor(bean,
1558: name);
1559: if (descriptor == null) {
1560: throw new NoSuchMethodException("Unknown property '" + name
1561: + "' in bean " + bean);
1562: }
1563: Method writeMethod = getWriteMethod(descriptor);
1564: if (writeMethod == null) {
1565: throw new NoSuchMethodException("Property '" + name
1566: + "' has no setter method in bean " + bean);
1567: }
1568:
1569: // Call the property setter method
1570: Object values[] = new Object[1];
1571: values[0] = value;
1572: invokeMethod(writeMethod, bean, values);
1573:
1574: }
1575:
1576: /**
1577: * This just catches and wraps IllegalArgumentException.
1578: */
1579: private Object invokeMethod(Method method, Object bean,
1580: Object[] values) throws IllegalAccessException,
1581: InvocationTargetException {
1582: try {
1583: return method.invoke(bean, values);
1584: } catch (IllegalArgumentException e) {
1585: throw new IllegalArgumentException("Cannot invoke "
1586: + method.getDeclaringClass().getName() + "."
1587: + method.getName() + " - " + e.getMessage());
1588: }
1589: }
1590: }
|