Java tutorial
/* * Copyright 2001-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.beanutils; import java.beans.BeanInfo; import java.beans.IndexedPropertyDescriptor; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.collections.FastHashMap; /** * Utility methods for using Java Reflection APIs to facilitate generic * property getter and setter operations on Java objects. Much of this * code was originally included in <code>BeanUtils</code>, but has been * separated because of the volume of code involved. * <p> * In general, the objects that are examined and modified using these * methods are expected to conform to the property getter and setter method * naming conventions described in the JavaBeans Specification (Version 1.0.1). * No data type conversions are performed, and there are no usage of any * <code>PropertyEditor</code> classes that have been registered, although * a convenient way to access the registered classes themselves is included. * <p> * For the purposes of this class, five formats for referencing a particular * property value of a bean are defined, with the layout of an identifying * String in parentheses: * <ul> * <li><strong>Simple (<code>name</code>)</strong> - The specified * <code>name</code> identifies an individual property of a particular * JavaBean. The name of the actual getter or setter method to be used * is determined using standard JavaBeans instrospection, so that (unless * overridden by a <code>BeanInfo</code> class, a property named "xyz" * will have a getter method named <code>getXyz()</code> or (for boolean * properties only) <code>isXyz()</code>, and a setter method named * <code>setXyz()</code>.</li> * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first * name element is used to select a property getter, as for simple * references above. The object returned for this property is then * consulted, using the same approach, for a property getter for a * property named <code>name2</code>, and so on. The property value that * is ultimately retrieved or modified is the one identified by the * last name element.</li> * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying * property value is assumed to be an array, or this JavaBean is assumed * to have indexed property getter and setter methods. The appropriate * (zero-relative) entry in the array is selected. <code>List</code> * objects are now also supported for read/write. You simply need to define * a getter that returns the <code>List</code></li> * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean * is assumed to have an property getter and setter methods with an * additional attribute of type <code>java.lang.String</code>.</li> * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> - * Combining mapped, nested, and indexed references is also * supported.</li> * </ul> * * @author Craig R. McClanahan * @author Ralph Schaer * @author Chris Audley * @author Rey Franois * @author Gregor Raman * @author Jan Sorensen * @author Scott Sanders * @version $Revision: 1.14.2.1 $ $Date: 2004/07/27 21:31:00 $ * @see PropertyUtils * @since 1.7 */ public class PropertyUtilsBean { // --------------------------------------------------------- Class Methods protected static PropertyUtilsBean getInstance() { return BeanUtilsBean.getInstance().getPropertyUtils(); } // --------------------------------------------------------- Variables /** * The cache of PropertyDescriptor arrays for beans we have already * introspected, keyed by the java.lang.Class of this object. */ private FastHashMap descriptorsCache = null; private FastHashMap mappedDescriptorsCache = null; // ---------------------------------------------------------- Constructors /** Base constructor */ public PropertyUtilsBean() { descriptorsCache = new FastHashMap(); descriptorsCache.setFast(true); mappedDescriptorsCache = new FastHashMap(); mappedDescriptorsCache.setFast(true); } // --------------------------------------------------------- Public Methods /** * Clear any cached property descriptors information for all classes * loaded by any class loaders. This is useful in cases where class * loaders are thrown away to implement class reloading. */ public void clearDescriptors() { descriptorsCache.clear(); mappedDescriptorsCache.clear(); Introspector.flushCaches(); } /** * <p>Copy property values from the "origin" bean to the "destination" bean * for all cases where the property names are the same (even though the * actual getter and setter methods might have been customized via * <code>BeanInfo</code> classes). No conversions are performed on the * actual property values -- it is assumed that the values retrieved from * the origin bean are assignment-compatible with the types expected by * the destination bean.</p> * * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed * to contain String-valued <strong>simple</strong> property names as the keys, pointing * at the corresponding property values that will be set in the destination * bean.<strong>Note</strong> that this method is intended to perform * a "shallow copy" of the properties and so complex properties * (for example, nested ones) will not be copied.</p> * * @param dest Destination bean whose properties are modified * @param orig Origin bean whose properties are retrieved * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if the <code>dest</code> or * <code>orig</code> argument is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (dest == null) { throw new IllegalArgumentException("No destination bean specified"); } if (orig == null) { throw new IllegalArgumentException("No origin bean specified"); } if (orig instanceof DynaBean) { DynaProperty origDescriptors[] = ((DynaBean) orig).getDynaClass().getDynaProperties(); for (int i = 0; i < origDescriptors.length; i++) { String name = origDescriptors[i].getName(); if (dest instanceof DynaBean) { if (isWriteable(dest, name)) { Object value = ((DynaBean) orig).get(name); ((DynaBean) dest).set(name, value); } } else /* if (dest is a standard JavaBean) */ { if (isWriteable(dest, name)) { Object value = ((DynaBean) orig).get(name); setSimpleProperty(dest, name, value); } } } } else if (orig instanceof Map) { Iterator names = ((Map) orig).keySet().iterator(); while (names.hasNext()) { String name = (String) names.next(); if (dest instanceof DynaBean) { if (isWriteable(dest, name)) { Object value = ((Map) orig).get(name); ((DynaBean) dest).set(name, value); } } else /* if (dest is a standard JavaBean) */ { if (isWriteable(dest, name)) { Object value = ((Map) orig).get(name); setSimpleProperty(dest, name, value); } } } } else /* if (orig is a standard JavaBean) */ { PropertyDescriptor origDescriptors[] = getPropertyDescriptors(orig); for (int i = 0; i < origDescriptors.length; i++) { String name = origDescriptors[i].getName(); if (isReadable(orig, name)) { if (dest instanceof DynaBean) { if (isWriteable(dest, name)) { Object value = getSimpleProperty(orig, name); ((DynaBean) dest).set(name, value); } } else /* if (dest is a standard JavaBean) */ { if (isWriteable(dest, name)) { Object value = getSimpleProperty(orig, name); setSimpleProperty(dest, name, value); } } } } } } /** * <p>Return the entire set of properties for which the specified bean * provides a read method. This map contains the unconverted property * values for all properties for which a read method is provided * (i.e. where the <code>getReadMethod()</code> returns non-null).</p> * * <p><strong>FIXME</strong> - Does not account for mapped properties.</p> * * @param bean Bean whose properties are to be extracted * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Map describe(Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } Map description = new HashMap(); if (bean instanceof DynaBean) { DynaProperty descriptors[] = ((DynaBean) bean).getDynaClass().getDynaProperties(); for (int i = 0; i < descriptors.length; i++) { String name = descriptors[i].getName(); description.put(name, getProperty(bean, name)); } } else { PropertyDescriptor descriptors[] = getPropertyDescriptors(bean); for (int i = 0; i < descriptors.length; i++) { String name = descriptors[i].getName(); if (descriptors[i].getReadMethod() != null) description.put(name, getProperty(bean, name)); } } return (description); } /** * Return the value of the specified indexed property of the specified * bean, with no type conversions. The zero-relative index of the * required value must be included (in square brackets) as a suffix to * the property name, or <code>IllegalArgumentException</code> will be * thrown. In addition to supporting the JavaBeans specification, this * method has been extended to support <code>List</code> objects as well. * * @param bean Bean whose property is to be extracted * @param name <code>propertyname[index]</code> of the property value * to be extracted * * @exception ArrayIndexOutOfBoundsException if the specified index * is outside the valid range for the underlying array * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Object getIndexedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Identify the index of the requested individual property int delim = name.indexOf(PropertyUtils.INDEXED_DELIM); int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2); if ((delim < 0) || (delim2 <= delim)) { throw new IllegalArgumentException("Invalid indexed property '" + name + "'"); } int index = -1; try { String subscript = name.substring(delim + 1, delim2); index = Integer.parseInt(subscript); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid indexed property '" + name + "'"); } name = name.substring(0, delim); // Request the specified indexed property value return (getIndexedProperty(bean, name, index)); } /** * Return the value of the specified indexed property of the specified * bean, with no type conversions. In addition to supporting the JavaBeans * specification, this method has been extended to support * <code>List</code> objects as well. * * @param bean Bean whose property is to be extracted * @param name Simple property name of the property value to be extracted * @param index Index of the property value to be extracted * * @exception ArrayIndexOutOfBoundsException if the specified index * is outside the valid range for the underlying array * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Object getIndexedProperty(Object bean, String name, int index) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } return (((DynaBean) bean).get(name, index)); } // Retrieve the property descriptor for the specified property PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } // Call the indexed getter method if there is one if (descriptor instanceof IndexedPropertyDescriptor) { Method readMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedReadMethod(); if (readMethod != null) { Object subscript[] = new Object[1]; subscript[0] = new Integer(index); try { return (invokeMethod(readMethod, bean, subscript)); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof ArrayIndexOutOfBoundsException) { throw (ArrayIndexOutOfBoundsException) e.getTargetException(); } else { throw e; } } } } // Otherwise, the underlying property must be an array Method readMethod = getReadMethod(descriptor); if (readMethod == null) { throw new NoSuchMethodException("Property '" + name + "' has no getter method"); } // Call the property getter and return the value Object value = invokeMethod(readMethod, bean, new Object[0]); if (!value.getClass().isArray()) { if (!(value instanceof java.util.List)) { throw new IllegalArgumentException("Property '" + name + "' is not indexed"); } else { //get the List's value return ((java.util.List) value).get(index); } } else { //get the array's value return (Array.get(value, index)); } } /** * Return the value of the specified mapped property of the * specified bean, with no type conversions. The key of the * required value must be included (in brackets) as a suffix to * the property name, or <code>IllegalArgumentException</code> will be * thrown. * * @param bean Bean whose property is to be extracted * @param name <code>propertyname(key)</code> of the property value * to be extracted * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Object getMappedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Identify the index of the requested individual property int delim = name.indexOf(PropertyUtils.MAPPED_DELIM); int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2); if ((delim < 0) || (delim2 <= delim)) { throw new IllegalArgumentException("Invalid mapped property '" + name + "'"); } // Isolate the name and the key String key = name.substring(delim + 1, delim2); name = name.substring(0, delim); // Request the specified indexed property value return (getMappedProperty(bean, name, key)); } /** * Return the value of the specified mapped property of the specified * bean, with no type conversions. * * @param bean Bean whose property is to be extracted * @param name Mapped property name of the property value to be extracted * @param key Key of the property value to be extracted * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Object getMappedProperty(Object bean, String name, String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } if (key == null) { throw new IllegalArgumentException("No key specified"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } return (((DynaBean) bean).get(name, key)); } Object result = null; // Retrieve the property descriptor for the specified property PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } if (descriptor instanceof MappedPropertyDescriptor) { // Call the keyed getter method if there is one Method readMethod = ((MappedPropertyDescriptor) descriptor).getMappedReadMethod(); if (readMethod != null) { Object keyArray[] = new Object[1]; keyArray[0] = key; result = invokeMethod(readMethod, bean, keyArray); } else { throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method"); } } else { /* means that the result has to be retrieved from a map */ Method readMethod = descriptor.getReadMethod(); if (readMethod != null) { Object invokeResult = invokeMethod(readMethod, bean, new Object[0]); /* test and fetch from the map */ if (invokeResult instanceof java.util.Map) { result = ((java.util.Map) invokeResult).get(key); } } else { throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method"); } } return result; } /** * <p>Return the mapped property descriptors for this bean class.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param beanClass Bean class to be introspected * @deprecated This method should not be exposed */ public FastHashMap getMappedPropertyDescriptors(Class beanClass) { if (beanClass == null) { return null; } // Look up any cached descriptors for this bean class return (FastHashMap) mappedDescriptorsCache.get(beanClass); } /** * <p>Return the mapped property descriptors for this bean.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param bean Bean to be introspected * @deprecated This method should not be exposed */ public FastHashMap getMappedPropertyDescriptors(Object bean) { if (bean == null) { return null; } return (getMappedPropertyDescriptors(bean.getClass())); } /** * Return the value of the (possibly nested) property of the specified * name, for the specified bean, with no type conversions. * * @param bean Bean whose property is to be extracted * @param name Possibly nested name of the property to be extracted * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception NestedNullException if a nested reference to a * property returns null * @exception InvocationTargetException * if the property accessor method throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } int indexOfINDEXED_DELIM = -1; int indexOfMAPPED_DELIM = -1; int indexOfMAPPED_DELIM2 = -1; int indexOfNESTED_DELIM = -1; while (true) { indexOfNESTED_DELIM = name.indexOf(PropertyUtils.NESTED_DELIM); indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM); indexOfMAPPED_DELIM2 = name.indexOf(PropertyUtils.MAPPED_DELIM2); if (indexOfMAPPED_DELIM2 >= 0 && indexOfMAPPED_DELIM >= 0 && (indexOfNESTED_DELIM < 0 || indexOfNESTED_DELIM > indexOfMAPPED_DELIM)) { indexOfNESTED_DELIM = name.indexOf(PropertyUtils.NESTED_DELIM, indexOfMAPPED_DELIM2); } else { indexOfNESTED_DELIM = name.indexOf(PropertyUtils.NESTED_DELIM); } if (indexOfNESTED_DELIM < 0) { break; } String next = name.substring(0, indexOfNESTED_DELIM); indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM); indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM); if (bean instanceof Map) { bean = ((Map) bean).get(next); } else if (indexOfMAPPED_DELIM >= 0) { bean = getMappedProperty(bean, next); } else if (indexOfINDEXED_DELIM >= 0) { bean = getIndexedProperty(bean, next); } else { bean = getSimpleProperty(bean, next); } if (bean == null) { throw new NestedNullException( "Null property value for '" + name.substring(0, indexOfNESTED_DELIM) + "'"); } name = name.substring(indexOfNESTED_DELIM + 1); } indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM); indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM); if (bean instanceof Map) { bean = ((Map) bean).get(name); } else if (indexOfMAPPED_DELIM >= 0) { bean = getMappedProperty(bean, name); } else if (indexOfINDEXED_DELIM >= 0) { bean = getIndexedProperty(bean, name); } else { bean = getSimpleProperty(bean, name); } return bean; } /** * Return the value of the specified property of the specified bean, * no matter which property reference format is used, with no * type conversions. * * @param bean Bean whose property is to be extracted * @param name Possibly indexed and/or nested name of the property * to be extracted * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return (getNestedProperty(bean, name)); } /** * <p>Retrieve the property descriptor for the specified property of the * specified bean, or return <code>null</code> if there is no such * descriptor. This method resolves indexed and nested property * references in the same manner as other methods in this class, except * that if the last (or only) name element is indexed, the descriptor * for the last resolved property itself is returned.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param bean Bean for which a property descriptor is requested * @param name Possibly indexed and/or nested name of the property for * which a property descriptor is requested * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception IllegalArgumentException if a nested reference to a * property returns null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Resolve nested references while (true) { int period = findNextNestedIndex(name); if (period < 0) { break; } String next = name.substring(0, period); int indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM); int indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM); if (indexOfMAPPED_DELIM >= 0 && (indexOfINDEXED_DELIM < 0 || indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) { bean = getMappedProperty(bean, next); } else { if (indexOfINDEXED_DELIM >= 0) { bean = getIndexedProperty(bean, next); } else { bean = getSimpleProperty(bean, next); } } if (bean == null) { throw new IllegalArgumentException("Null property value for '" + name.substring(0, period) + "'"); } name = name.substring(period + 1); } // Remove any subscript from the final name value int left = name.indexOf(PropertyUtils.INDEXED_DELIM); if (left >= 0) { name = name.substring(0, left); } left = name.indexOf(PropertyUtils.MAPPED_DELIM); if (left >= 0) { name = name.substring(0, left); } // Look up and return this property from our cache // creating and adding it to the cache if not found. if ((bean == null) || (name == null)) { return (null); } PropertyDescriptor descriptors[] = getPropertyDescriptors(bean); if (descriptors != null) { for (int i = 0; i < descriptors.length; i++) { if (name.equals(descriptors[i].getName())) return (descriptors[i]); } } PropertyDescriptor result = null; FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean); if (mappedDescriptors == null) { mappedDescriptors = new FastHashMap(); mappedDescriptors.setFast(true); mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors); } result = (PropertyDescriptor) mappedDescriptors.get(name); if (result == null) { // not found, try to create it try { result = new MappedPropertyDescriptor(name, bean.getClass()); } catch (IntrospectionException ie) { } if (result != null) { mappedDescriptors.put(name, result); } } return result; } private int findNextNestedIndex(String expression) { // walk back from the end to the start // and find the first index that int bracketCount = 0; for (int i = 0, size = expression.length(); i < size; i++) { char at = expression.charAt(i); switch (at) { case PropertyUtils.NESTED_DELIM: if (bracketCount < 1) { return i; } break; case PropertyUtils.MAPPED_DELIM: case PropertyUtils.INDEXED_DELIM: // not bothered which ++bracketCount; break; case PropertyUtils.MAPPED_DELIM2: case PropertyUtils.INDEXED_DELIM2: // not bothered which --bracketCount; break; } } // can't find any return -1; } /** * <p>Retrieve the property descriptors for the specified class, * introspecting and caching them the first time a particular bean class * is encountered.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param beanClass Bean class for which property descriptors are requested * * @exception IllegalArgumentException if <code>beanClass</code> is null */ public PropertyDescriptor[] getPropertyDescriptors(Class beanClass) { if (beanClass == null) { throw new IllegalArgumentException("No bean class specified"); } // Look up any cached descriptors for this bean class PropertyDescriptor descriptors[] = null; descriptors = (PropertyDescriptor[]) descriptorsCache.get(beanClass); if (descriptors != null) { return (descriptors); } // Introspect the bean and cache the generated descriptors BeanInfo beanInfo = null; try { beanInfo = Introspector.getBeanInfo(beanClass); } catch (IntrospectionException e) { return (new PropertyDescriptor[0]); } descriptors = beanInfo.getPropertyDescriptors(); if (descriptors == null) { descriptors = new PropertyDescriptor[0]; } descriptorsCache.put(beanClass, descriptors); return (descriptors); } /** * <p>Retrieve the property descriptors for the specified bean, * introspecting and caching them the first time a particular bean class * is encountered.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param bean Bean for which property descriptors are requested * * @exception IllegalArgumentException if <code>bean</code> is null */ public PropertyDescriptor[] getPropertyDescriptors(Object bean) { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } return (getPropertyDescriptors(bean.getClass())); } /** * <p>Return the Java Class repesenting the property editor class that has * been registered for this property (if any). This method follows the * same name resolution rules used by <code>getPropertyDescriptor()</code>, * so if the last element of a name reference is indexed, the property * editor for the underlying property's class is returned.</p> * * <p>Note that <code>null</code> will be returned if there is no property, * or if there is no registered property editor class. Because this * return value is ambiguous, you should determine the existence of the * property itself by other means.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param bean Bean for which a property descriptor is requested * @param name Possibly indexed and/or nested name of the property for * which a property descriptor is requested * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception IllegalArgumentException if a nested reference to a * property returns null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Class getPropertyEditorClass(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor != null) { return (descriptor.getPropertyEditorClass()); } else { return (null); } } /** * Return the Java Class representing the property type of the specified * property, or <code>null</code> if there is no such property for the * specified bean. This method follows the same name resolution rules * used by <code>getPropertyDescriptor()</code>, so if the last element * of a name reference is indexed, the type of the property itself will * be returned. If the last (or only) element has no property with the * specified name, <code>null</code> is returned. * * @param bean Bean for which a property descriptor is requested * @param name Possibly indexed and/or nested name of the property for * which a property descriptor is requested * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception IllegalArgumentException if a nested reference to a * property returns null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Class getPropertyType(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Special handling for DynaBeans if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { return (null); } Class type = descriptor.getType(); if (type == null) { return (null); } else if (type.isArray()) { return (type.getComponentType()); } else { return (type); } } PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { return (null); } else if (descriptor instanceof IndexedPropertyDescriptor) { return (((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType()); } else if (descriptor instanceof MappedPropertyDescriptor) { return (((MappedPropertyDescriptor) descriptor).getMappedPropertyType()); } else { return (descriptor.getPropertyType()); } } /** * <p>Return an accessible property getter method for this property, * if there is one; otherwise return <code>null</code>.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param descriptor Property descriptor to return a getter for */ public Method getReadMethod(PropertyDescriptor descriptor) { return (MethodUtils.getAccessibleMethod(descriptor.getReadMethod())); } /** * Return the value of the specified simple property of the specified * bean, with no type conversions. * * @param bean Bean whose property is to be extracted * @param name Name of the property to be extracted * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception IllegalArgumentException if the property name * is nested or indexed * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Validate the syntax of the property name if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) { throw new IllegalArgumentException("Nested property names are not allowed"); } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) { throw new IllegalArgumentException("Indexed property names are not allowed"); } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) { throw new IllegalArgumentException("Mapped property names are not allowed"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } return (((DynaBean) bean).get(name)); } // Retrieve the property getter method for the specified property PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } Method readMethod = getReadMethod(descriptor); if (readMethod == null) { throw new NoSuchMethodException("Property '" + name + "' has no getter method"); } // Call the property getter and return the value Object value = invokeMethod(readMethod, bean, new Object[0]); return (value); } /** * <p>Return an accessible property setter method for this property, * if there is one; otherwise return <code>null</code>.</p> * * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> * * @param descriptor Property descriptor to return a setter for */ public Method getWriteMethod(PropertyDescriptor descriptor) { return (MethodUtils.getAccessibleMethod(descriptor.getWriteMethod())); } /** * <p>Return <code>true</code> if the specified property name identifies * a readable property on the specified bean; otherwise, return * <code>false</code>. * * @param bean Bean to be examined (may be a {@link DynaBean} * @param name Property name to be evaluated * * @exception IllegalArgumentException if <code>bean</code> * or <code>name</code> is <code>null</code> * * @since BeanUtils 1.6 */ public boolean isReadable(Object bean, String name) { // Validate method parameters if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Return the requested result if (bean instanceof DynaBean) { // All DynaBean properties are readable return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null); } else { try { PropertyDescriptor desc = getPropertyDescriptor(bean, name); if (desc != null) { Method readMethod = desc.getReadMethod(); if ((readMethod == null) && (desc instanceof IndexedPropertyDescriptor)) { readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); } return (readMethod != null); } else { return (false); } } catch (IllegalAccessException e) { return (false); } catch (InvocationTargetException e) { return (false); } catch (NoSuchMethodException e) { return (false); } } } /** * <p>Return <code>true</code> if the specified property name identifies * a writeable property on the specified bean; otherwise, return * <code>false</code>. * * @param bean Bean to be examined (may be a {@link DynaBean} * @param name Property name to be evaluated * * @exception IllegalPointerException if <code>bean</code> * or <code>name</code> is <code>null</code> * * @since BeanUtils 1.6 */ public boolean isWriteable(Object bean, String name) { // Validate method parameters if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Return the requested result if (bean instanceof DynaBean) { // All DynaBean properties are writeable return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null); } else { try { PropertyDescriptor desc = getPropertyDescriptor(bean, name); if (desc != null) { Method writeMethod = desc.getWriteMethod(); if ((writeMethod == null) && (desc instanceof IndexedPropertyDescriptor)) { writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod(); } return (writeMethod != null); } else { return (false); } } catch (IllegalAccessException e) { return (false); } catch (InvocationTargetException e) { return (false); } catch (NoSuchMethodException e) { return (false); } } } /** * Set the value of the specified indexed property of the specified * bean, with no type conversions. The zero-relative index of the * required value must be included (in square brackets) as a suffix to * the property name, or <code>IllegalArgumentException</code> will be * thrown. In addition to supporting the JavaBeans specification, this * method has been extended to support <code>List</code> objects as well. * * @param bean Bean whose property is to be modified * @param name <code>propertyname[index]</code> of the property value * to be modified * @param value Value to which the specified property element * should be set * * @exception ArrayIndexOutOfBoundsException if the specified index * is outside the valid range for the underlying array * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void setIndexedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Identify the index of the requested individual property int delim = name.indexOf(PropertyUtils.INDEXED_DELIM); int delim2 = name.indexOf(PropertyUtils.INDEXED_DELIM2); if ((delim < 0) || (delim2 <= delim)) { throw new IllegalArgumentException("Invalid indexed property '" + name + "'"); } int index = -1; try { String subscript = name.substring(delim + 1, delim2); index = Integer.parseInt(subscript); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid indexed property '" + name + "'"); } name = name.substring(0, delim); // Set the specified indexed property value setIndexedProperty(bean, name, index, value); } /** * Set the value of the specified indexed property of the specified * bean, with no type conversions. In addition to supporting the JavaBeans * specification, this method has been extended to support * <code>List</code> objects as well. * * @param bean Bean whose property is to be set * @param name Simple property name of the property value to be set * @param index Index of the property value to be set * @param value Value to which the indexed property element is to be set * * @exception ArrayIndexOutOfBoundsException if the specified index * is outside the valid range for the underlying array * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void setIndexedProperty(Object bean, String name, int index, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } ((DynaBean) bean).set(name, index, value); return; } // Retrieve the property descriptor for the specified property PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } // Call the indexed setter method if there is one if (descriptor instanceof IndexedPropertyDescriptor) { Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod(); if (writeMethod != null) { Object subscript[] = new Object[2]; subscript[0] = new Integer(index); subscript[1] = value; try { invokeMethod(writeMethod, bean, subscript); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof ArrayIndexOutOfBoundsException) { throw (ArrayIndexOutOfBoundsException) e.getTargetException(); } else { throw e; } } return; } } // Otherwise, the underlying property must be an array or a list Method readMethod = descriptor.getReadMethod(); if (readMethod == null) { throw new NoSuchMethodException("Property '" + name + "' has no getter method"); } // Call the property getter to get the array or list Object array = invokeMethod(readMethod, bean, new Object[0]); if (!array.getClass().isArray()) { if (array instanceof List) { // Modify the specified value in the List ((List) array).set(index, value); } else { throw new IllegalArgumentException("Property '" + name + "' is not indexed"); } } else { // Modify the specified value in the array Array.set(array, index, value); } } /** * Set the value of the specified mapped property of the * specified bean, with no type conversions. The key of the * value to set must be included (in brackets) as a suffix to * the property name, or <code>IllegalArgumentException</code> will be * thrown. * * @param bean Bean whose property is to be set * @param name <code>propertyname(key)</code> of the property value * to be set * @param value The property value to be set * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void setMappedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Identify the index of the requested individual property int delim = name.indexOf(PropertyUtils.MAPPED_DELIM); int delim2 = name.indexOf(PropertyUtils.MAPPED_DELIM2); if ((delim < 0) || (delim2 <= delim)) { throw new IllegalArgumentException("Invalid mapped property '" + name + "'"); } // Isolate the name and the key String key = name.substring(delim + 1, delim2); name = name.substring(0, delim); // Request the specified indexed property value setMappedProperty(bean, name, key, value); } /** * Set the value of the specified mapped property of the specified * bean, with no type conversions. * * @param bean Bean whose property is to be set * @param name Mapped property name of the property value to be set * @param key Key of the property value to be set * @param value The property value to be set * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void setMappedProperty(Object bean, String name, String key, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } if (key == null) { throw new IllegalArgumentException("No key specified"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } ((DynaBean) bean).set(name, key, value); return; } // Retrieve the property descriptor for the specified property PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } if (descriptor instanceof MappedPropertyDescriptor) { // Call the keyed setter method if there is one Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod(); if (mappedWriteMethod != null) { Object params[] = new Object[2]; params[0] = key; params[1] = value; invokeMethod(mappedWriteMethod, bean, params); } else { throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method"); } } else { /* means that the result has to be retrieved from a map */ Method readMethod = descriptor.getReadMethod(); if (readMethod != null) { Object invokeResult = invokeMethod(readMethod, bean, new Object[0]); /* test and fetch from the map */ if (invokeResult instanceof java.util.Map) { ((java.util.Map) invokeResult).put(key, value); } } else { throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method"); } } } /** * Set the value of the (possibly nested) property of the specified * name, for the specified bean, with no type conversions. * * @param bean Bean whose property is to be modified * @param name Possibly nested name of the property to be modified * @param value Value to which the property is to be set * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception IllegalArgumentException if a nested reference to a * property returns null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void setNestedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } int indexOfINDEXED_DELIM = -1; int indexOfMAPPED_DELIM = -1; while (true) { int delim = name.indexOf(PropertyUtils.NESTED_DELIM); if (delim < 0) { break; } String next = name.substring(0, delim); indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM); indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM); if (bean instanceof Map) { bean = ((Map) bean).get(next); } else if (indexOfMAPPED_DELIM >= 0) { bean = getMappedProperty(bean, next); } else if (indexOfINDEXED_DELIM >= 0) { bean = getIndexedProperty(bean, next); } else { bean = getSimpleProperty(bean, next); } if (bean == null) { throw new IllegalArgumentException("Null property value for '" + name.substring(0, delim) + "'"); } name = name.substring(delim + 1); } indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM); indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM); if (bean instanceof Map) { // check to see if the class has a standard property PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { // no - then put the value into the map ((Map) bean).put(name, value); } else { // yes - use that instead setSimpleProperty(bean, name, value); } } else if (indexOfMAPPED_DELIM >= 0) { setMappedProperty(bean, name, value); } else if (indexOfINDEXED_DELIM >= 0) { setIndexedProperty(bean, name, value); } else { setSimpleProperty(bean, name, value); } } /** * Set the value of the specified property of the specified bean, * no matter which property reference format is used, with no * type conversions. * * @param bean Bean whose property is to be modified * @param name Possibly indexed and/or nested name of the property * to be modified * @param value Value to which this property is to be set * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void setProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { setNestedProperty(bean, name, value); } /** * Set the value of the specified simple property of the specified bean, * with no type conversions. * * @param bean Bean whose property is to be modified * @param name Name of the property to be modified * @param value Value to which the property should be set * * @exception IllegalAccessException if the caller does not have * access to the property accessor method * @exception IllegalArgumentException if <code>bean</code> or * <code>name</code> is null * @exception IllegalArgumentException if the property name is * nested or indexed * @exception InvocationTargetException if the property accessor method * throws an exception * @exception NoSuchMethodException if an accessor method for this * propety cannot be found */ public void setSimpleProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified"); } // Validate the syntax of the property name if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) { throw new IllegalArgumentException("Nested property names are not allowed"); } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) { throw new IllegalArgumentException("Indexed property names are not allowed"); } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) { throw new IllegalArgumentException("Mapped property names are not allowed"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } ((DynaBean) bean).set(name, value); return; } // Retrieve the property setter method for the specified property PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'"); } Method writeMethod = getWriteMethod(descriptor); if (writeMethod == null) { throw new NoSuchMethodException("Property '" + name + "' has no setter method"); } // Call the property setter method Object values[] = new Object[1]; values[0] = value; invokeMethod(writeMethod, bean, values); } /** This just catches and wraps IllegalArgumentException. */ private Object invokeMethod(Method method, Object bean, Object[] values) throws IllegalAccessException, InvocationTargetException { try { return method.invoke(bean, values); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " - " + e.getMessage()); } } }