![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.commons.beanutils2.PropertyUtilsBean Maven / Gradle / Ivy
Show all versions of commons-beanutils2 Show documentation
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.beanutils2; 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.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.beanutils2.expression.DefaultResolver; import org.apache.commons.beanutils2.expression.Resolver; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * 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}, but has been * separated because of the volume of code involved. *
is* 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} classes that have been registered, although * a convenient way to access the registered classes themselves is included. *
* For the purposes of this class, five formats for referencing a particular * property value of a bean are defined, with the default layout of an * identifying String in parentheses. However the notation for these formats * and how they are resolved is now (since BeanUtils 1.8.0) controlled by * the configured {@link Resolver} implementation: *
*
* * @see Resolver * @see PropertyUtils * @since 1.7 */ public class PropertyUtilsBean { private Resolver resolver = new DefaultResolver(); /** * Gets the PropertyUtils bean instance. * @return The PropertyUtils bean instance */ protected static PropertyUtilsBean getInstance() { return BeanUtilsBean.getInstance().getPropertyUtils(); } /** * The cache of PropertyDescriptor arrays for beans we have already * introspected, keyed by the java.lang.Class of this object. */ private ConcurrentWeakKeyHashMap- Simple ({@code name}) - The specified * {@code name} 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 introspection, so that (unless * overridden by a {@code BeanInfo} class, a property named "xyz" * will have a getter method named {@code getXyz()} or (for boolean * properties only) {@code isXyz()}, and a setter method named * {@code setXyz()}.
*- Nested ({@code name1.name2.name3}) 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}, and so on. The property value that * is ultimately retrieved or modified is the one identified by the * last name element.
*- Indexed ({@code name[index]}) - 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} * objects are now also supported for read/write. You simply need to define * a getter that returns the {@code List}
*- Mapped ({@code name(key)}) - The JavaBean * is assumed to have an property getter and setter methods with an * additional attribute of type {@code java.lang.String}.
*- Combined ({@code name1.name2[index].name3(key)}) - * Combining mapped, nested, and indexed references is also * supported.
*, BeanIntrospectionData> descriptorsCache = null; private ConcurrentWeakKeyHashMap , Map> mappedDescriptorsCache = null; /** An empty object array */ private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; /** Log instance */ private final Log log = LogFactory.getLog(PropertyUtilsBean.class); /** The list with BeanIntrospector objects. */ private final List introspectors; /** Base constructor */ public PropertyUtilsBean() { descriptorsCache = new ConcurrentWeakKeyHashMap<>(); mappedDescriptorsCache = new ConcurrentWeakKeyHashMap<>(); introspectors = new CopyOnWriteArrayList<>(); resetBeanIntrospectors(); } /** * Gets the configured {@link Resolver} implementation used by BeanUtils. * * The {@link Resolver} handles the property name * expressions and the implementation in use effectively * controls the dialect of the expression language * that BeanUtils recognizes. *
* {@link DefaultResolver} is the default implementation used. * * @return resolver The property expression resolver. * @since 1.8.0 */ public Resolver getResolver() { return resolver; } /** * Configure the {@link Resolver} implementation used by BeanUtils. *
* The {@link Resolver} handles the property name * expressions and the implementation in use effectively * controls the dialect of the expression language * that BeanUtils recognizes. *
* {@link DefaultResolver} is the default implementation used. * * @param resolver The property expression resolver. * @since 1.8.0 */ public void setResolver(final Resolver resolver) { if (resolver == null) { this.resolver = new DefaultResolver(); } else { this.resolver = resolver; } } /** * Resets the {@link BeanIntrospector} objects registered at this instance. After this * method was called, only the default {@code BeanIntrospector} is registered. * * @since 1.9 */ public final void resetBeanIntrospectors() { introspectors.clear(); introspectors.add(DefaultBeanIntrospector.INSTANCE); introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); } /** * Adds a {@code BeanIntrospector}. This object is invoked when the * property descriptors of a class need to be obtained. * * @param introspector the {@code BeanIntrospector} to be added (must * not be null * @throws IllegalArgumentException if the argument is null * @since 1.9 */ public void addBeanIntrospector(final BeanIntrospector introspector) { if (introspector == null) { throw new IllegalArgumentException( "BeanIntrospector must not be null!"); } introspectors.add(introspector); } /** * Removes the specified {@code BeanIntrospector}. * * @param introspector the {@code BeanIntrospector} to be removed * @return true if the {@code BeanIntrospector} existed and * could be removed, false otherwise * @since 1.9 */ public boolean removeBeanIntrospector(final BeanIntrospector introspector) { return introspectors.remove(introspector); } /** * 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(); } /** *
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} 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.
* *If the origin "bean" is actually a {@code Map}, it is assumed * to contain String-valued simple property names as the keys, pointing * at the corresponding property values that will be set in the destination * bean.Note 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.
* *Note, that this method will not copy a List to a List, or an Object[] * to an Object[]. It's specifically for copying JavaBean properties.
* * @param dest Destination bean whose properties are modified * @param orig Origin bean whose properties are retrieved * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if the {@code dest} or * {@code orig} argument is null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public void copyProperties(final Object dest, final 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) { final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties(); for (final DynaProperty origDescriptor : origDescriptors) { final String name = origDescriptor.getName(); if (isReadable(orig, name) && isWriteable(dest, name)) { try { final Object value = ((DynaBean) orig).get(name); if (dest instanceof DynaBean) { ((DynaBean) dest).set(name, value); } else { setSimpleProperty(dest, name, value); } } catch (final NoSuchMethodException e) { if (log.isDebugEnabled()) { log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e); } } } } } else if (orig instanceof Map) { for (final Map.Entry, ?> entry : ((Map, ?>) orig).entrySet()) { final String name = (String)entry.getKey(); if (isWriteable(dest, name)) { try { if (dest instanceof DynaBean) { ((DynaBean) dest).set(name, entry.getValue()); } else { setSimpleProperty(dest, name, entry.getValue()); } } catch (final NoSuchMethodException e) { if (log.isDebugEnabled()) { log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e); } } } } } else /* if (orig is a standard JavaBean) */ { final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig); for (final PropertyDescriptor origDescriptor : origDescriptors) { final String name = origDescriptor.getName(); if (isReadable(orig, name) && isWriteable(dest, name)) { try { final Object value = getSimpleProperty(orig, name); if (dest instanceof DynaBean) { ((DynaBean) dest).set(name, value); } else { setSimpleProperty(dest, name, value); } } catch (final NoSuchMethodException e) { if (log.isDebugEnabled()) { log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e); } } } } } } /** *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()} returns non-null).
* *FIXME - Does not account for mapped properties.
* * @param bean Bean whose properties are to be extracted * @return The set of properties for the bean * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} is null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Mapdescribe(final Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } final Map description = new HashMap<>(); if (bean instanceof DynaBean) { final DynaProperty[] descriptors = ((DynaBean) bean).getDynaClass().getDynaProperties(); for (final DynaProperty descriptor : descriptors) { final String name = descriptor.getName(); description.put(name, getProperty(bean, name)); } } else { final PropertyDescriptor[] descriptors = getPropertyDescriptors(bean); for (final PropertyDescriptor descriptor : descriptors) { final String name = descriptor.getName(); if (descriptor.getReadMethod() != null) { description.put(name, getProperty(bean, name)); } } } return description; } /** * Gets 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} will be * thrown. In addition to supporting the JavaBeans specification, this * method has been extended to support {@code List} objects as well. * * @param bean Bean whose property is to be extracted * @param name {@code propertyname[index]} of the property value * to be extracted * @return the indexed property value * * @throws IndexOutOfBoundsException if the specified index * is outside the valid range for the underlying array or List * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Object getIndexedProperty(final 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 for bean class '" + bean.getClass() + "'"); } // Identify the index of the requested individual property int index = -1; try { index = resolver.getIndex(name); } catch (final IllegalArgumentException e) { throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage()); } if (index < 0) { throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'"); } // Isolate the name name = resolver.getProperty(name); // Request the specified indexed property value return getIndexedProperty(bean, name, index); } /** * Gets 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} 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 * @return the indexed property value * * @throws IndexOutOfBoundsException if the specified index * is outside the valid range for the underlying property * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Object getIndexedProperty(final Object bean, final String name, final int index) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null || name.isEmpty()) { if (bean.getClass().isArray()) { return Array.get(bean, index); } else if (bean instanceof List) { return ((List>)bean).get(index); } } if (name == null) { throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'"); } return ((DynaBean) bean).get(name, index); } // Retrieve the property descriptor for the specified property final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'"); } // Call the indexed getter method if there is one if (descriptor instanceof IndexedPropertyDescriptor) { Method readMethod = ((IndexedPropertyDescriptor) descriptor). getIndexedReadMethod(); readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod); if (readMethod != null) { final Object[] subscript = new Object[1]; subscript[0] = Integer.valueOf(index); try { return invokeMethod(readMethod,bean, subscript); } catch (final InvocationTargetException e) { if (e.getTargetException() instanceof IndexOutOfBoundsException) { throw (IndexOutOfBoundsException) e.getTargetException(); } throw e; } } } // Otherwise, the underlying property must be an array final Method readMethod = getReadMethod(bean.getClass(), descriptor); if (readMethod == null) { throw new NoSuchMethodException("Property '" + name + "' has no " + "getter method on bean class '" + bean.getClass() + "'"); } // Call the property getter and return the value final Object value = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY); if (!value.getClass().isArray()) { if (!(value instanceof java.util.List)) { throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'"); } //get the List's value return ((java.util.List>) value).get(index); } //get the array's value try { return Array.get(value, index); } catch (final ArrayIndexOutOfBoundsException e) { throw new ArrayIndexOutOfBoundsException("Index: " + index + ", Size: " + Array.getLength(value) + " for property '" + name + "'"); } } /** * Gets 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} will be * thrown. * * @param bean Bean whose property is to be extracted * @param name {@code propertyname(key)} of the property value * to be extracted * @return the mapped property value * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Object getMappedProperty(final 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 for bean class '" + bean.getClass() + "'"); } // Identify the key of the requested individual property String key = null; try { key = resolver.getKey(name); } catch (final IllegalArgumentException e) { throw new IllegalArgumentException ("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage()); } if (key == null) { throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'"); } // Isolate the name name = resolver.getProperty(name); // Request the specified indexed property value return getMappedProperty(bean, name, key); } /** * Gets 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 * @return the mapped property value * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Object getMappedProperty(final Object bean, final String name, final String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); } if (key == null) { throw new IllegalArgumentException("No key specified for property '" + name + "' on bean class " + bean.getClass() + "'"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'+ on bean class '" + bean.getClass() + "'"); } return ((DynaBean) bean).get(name, key); } Object result = null; // Retrieve the property descriptor for the specified property final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "'+ on bean class '" + bean.getClass() + "'"); } if (descriptor instanceof MappedPropertyDescriptor) { // Call the keyed getter method if there is one Method readMethod = ((MappedPropertyDescriptor) descriptor). getMappedReadMethod(); readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod); if (readMethod != null) { final Object[] keyArray = new Object[1]; keyArray[0] = key; result = invokeMethod(readMethod, bean, keyArray); } else { throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'"); } } else { /* means that the result has to be retrieved from a map */ final Method readMethod = getReadMethod(bean.getClass(), descriptor); if (readMethod != null) { final Object invokeResult = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY); /* 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 on bean class '" + bean.getClass() + "'"); } } return result; } /** * Return the mapped property descriptors for this bean class.
* *FIXME - Does not work with DynaBeans.
* * @param beanClass Bean class to be introspected * @return the mapped property descriptors */ Map, Map> getMappedPropertyDescriptors(final Class> beanClass) { if (beanClass == null) { return null; } // Look up any cached descriptors for this bean class return mappedDescriptorsCache.get(beanClass); } /** * Return the mapped property descriptors for this bean.
* *FIXME - Does not work with DynaBeans.
* * @param bean Bean to be introspected * @return the mapped property descriptors */ Map getMappedPropertyDescriptors(final Object bean) { if (bean == null) { return null; } return getMappedPropertyDescriptors(bean.getClass()); } /** * Gets 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 * @return the nested property value * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws NestedNullException if a nested reference to a * property returns null * @throws InvocationTargetException * if the property accessor method throws an exception * @throws NoSuchMethodException if an accessor method for this * property 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 for bean class '" + bean.getClass() + "'"); } // Resolve nested references while (resolver.hasNested(name)) { final String next = resolver.next(name); Object nestedBean = null; if (bean instanceof Map) { nestedBean = getPropertyOfMapBean((Map, ?>) bean, next); } else if (resolver.isMapped(next)) { nestedBean = getMappedProperty(bean, next); } else if (resolver.isIndexed(next)) { nestedBean = getIndexedProperty(bean, next); } else { nestedBean = getSimpleProperty(bean, next); } if (nestedBean == null) { throw new NestedNullException ("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'"); } bean = nestedBean; name = resolver.remove(name); } if (bean instanceof Map) { bean = getPropertyOfMapBean((Map, ?>) bean, name); } else if (resolver.isMapped(name)) { bean = getMappedProperty(bean, name); } else if (resolver.isIndexed(name)) { bean = getIndexedProperty(bean, name); } else { bean = getSimpleProperty(bean, name); } return bean; } /** * This method is called by getNestedProperty and setNestedProperty to * define what it means to get a property from an object which implements * Map. See setPropertyOfMapBean for more information. * * @param bean Map bean * @param propertyName The property name * @return the property value * * @throws IllegalArgumentException when the propertyName is regarded as * being invalid. * * @throws IllegalAccessException just in case subclasses override this * method to try to access real getter methods and find permission is denied. * * @throws InvocationTargetException just in case subclasses override this * method to try to access real getter methods, and find it throws an * exception when invoked. * * @throws NoSuchMethodException just in case subclasses override this * method to try to access real getter methods, and want to fail if * no simple method is available. * @since 1.8.0 */ protected Object getPropertyOfMapBean(final Map, ?> bean, String propertyName) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (resolver.isMapped(propertyName)) { final String name = resolver.getProperty(propertyName); if (name == null || name.isEmpty()) { propertyName = resolver.getKey(propertyName); } } if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) { throw new IllegalArgumentException( "Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName); } return bean.get(propertyName); } /** * Gets 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 * @return the property value * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Object getProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return getNestedProperty(bean, name); } /** *Retrieve the property descriptor for the specified property of the * specified bean, or return {@code null} 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.
* *FIXME - Does not work with DynaBeans.
* *Note that for Java 8 and above, this method no longer return * IndexedPropertyDescriptor for {@link List}-typed properties, only for * properties typed as native array. (BEANUTILS-492). * * @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 * @return the property descriptor * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws IllegalArgumentException if a nested reference to a * property returns null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property 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 for bean class '" + bean.getClass() + "'"); } // Resolve nested references while (resolver.hasNested(name)) { final String next = resolver.next(name); final Object nestedBean = getProperty(bean, next); if (nestedBean == null) { throw new NestedNullException ("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'"); } bean = nestedBean; name = resolver.remove(name); } // Remove any subscript from the final name value name = resolver.getProperty(name); // Look up and return this property from our cache // creating and adding it to the cache if not found. if (name == null) { return null; } final BeanIntrospectionData data = getIntrospectionData(bean.getClass()); PropertyDescriptor result = data.getDescriptor(name); if (result != null) { return result; } Map mappedDescriptors = getMappedPropertyDescriptors(bean); if (mappedDescriptors == null) { mappedDescriptors = new ConcurrentHashMap
, Map>(); 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 (final IntrospectionException ie) { /* Swallow IntrospectionException * TODO: Why? */ } if (result != null) { mappedDescriptors.put(name, result); } } return result; } /** * Retrieve the property descriptors for the specified class, * introspecting and caching them the first time a particular bean class * is encountered.
* *FIXME - Does not work with DynaBeans.
* * @param beanClass Bean class for which property descriptors are requested * @return the property descriptors * * @throws IllegalArgumentException if {@code beanClass} is null */ public PropertyDescriptor[] getPropertyDescriptors(final Class> beanClass) { return getIntrospectionData(beanClass).getDescriptors(); } /** *Retrieve the property descriptors for the specified bean, * introspecting and caching them the first time a particular bean class * is encountered.
* *FIXME - Does not work with DynaBeans.
* * @param bean Bean for which property descriptors are requested * @return the property descriptors * * @throws IllegalArgumentException if {@code bean} is null */ public PropertyDescriptor[] getPropertyDescriptors(final Object bean) { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } return getPropertyDescriptors(bean.getClass()); } /** *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()}, * so if the last element of a name reference is indexed, the property * editor for the underlying property's class is returned.
* *Note that {@code null} 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.
* *FIXME - Does not work with DynaBeans.
* * @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 * @return the property editor class * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws IllegalArgumentException if a nested reference to a * property returns null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Class> getPropertyEditorClass(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); } final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor != null) { return descriptor.getPropertyEditorClass(); } return null; } /** * Gets the Java Class representing the property type of the specified * property, or {@code null} if there is no such property for the * specified bean. This method follows the same name resolution rules * used by {@code getPropertyDescriptor()}, 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} is returned. ** If the property is an indexed property (e.g. {@code String[]}), * this method will return the type of the items within that array. * Note that from Java 8 and newer, this method do not support * such index types from items within an Collection, and will * instead return the collection type (e.g. java.util.List) from the * getter method. * * @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 * @return The property type * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws IllegalArgumentException if a nested reference to a * property returns null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property 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 for bean class '" + bean.getClass() + "'"); } // Resolve nested references while (resolver.hasNested(name)) { final String next = resolver.next(name); final Object nestedBean = getProperty(bean, next); if (nestedBean == null) { throw new NestedNullException ("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'"); } bean = nestedBean; name = resolver.remove(name); } // Remove any subscript from the final name value name = resolver.getProperty(name); // Special handling for DynaBeans if (bean instanceof DynaBean) { final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { return null; } final Class> type = descriptor.getType(); if (type == null) { return null; } else if (type.isArray()) { return type.getComponentType(); } else { return type; } } final 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(); } } /** *
Return an accessible property getter method for this property, * if there is one; otherwise return {@code null}.
* *FIXME - Does not work with DynaBeans.
* * @param descriptor Property descriptor to return a getter for * @return The read method */ public Method getReadMethod(final PropertyDescriptor descriptor) { return MethodUtils.getAccessibleMethod(descriptor.getReadMethod()); } /** *Return an accessible property getter method for this property, * if there is one; otherwise return {@code null}.
* *FIXME - Does not work with DynaBeans.
* * @param clazz The class of the read method will be invoked on * @param descriptor Property descriptor to return a getter for * @return The read method */ Method getReadMethod(final Class> clazz, final PropertyDescriptor descriptor) { return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod()); } /** * Gets 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 * @return The property value * * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws IllegalArgumentException if the property name * is nested or indexed * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public Object getSimpleProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); } // Validate the syntax of the property name if (resolver.hasNested(name)) { throw new IllegalArgumentException ("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); } else if (resolver.isIndexed(name)) { throw new IllegalArgumentException ("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); } else if (resolver.isMapped(name)) { throw new IllegalArgumentException ("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); } // Handle DynaBean instances specially if (bean instanceof DynaBean) { final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'" ); } return ((DynaBean) bean).get(name); } // Retrieve the property getter method for the specified property final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'" ); } final Method readMethod = getReadMethod(bean.getClass(), descriptor); if (readMethod == null) { throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'"); } // Call the property getter and return the value final Object value = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY); return value; } /** *Return an accessible property setter method for this property, * if there is one; otherwise return {@code null}.
* *Note: This method does not work correctly with custom bean * introspection under certain circumstances. It may return {@code null} * even if a write method is defined for the property in question. Use * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the * correct result is returned.
*FIXME - Does not work with DynaBeans.
* * @param descriptor Property descriptor to return a setter for * @return The write method */ public Method getWriteMethod(final PropertyDescriptor descriptor) { return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod()); } /** *Return an accessible property setter method for this property, * if there is one; otherwise return {@code null}.
* *FIXME - Does not work with DynaBeans.
* * @param clazz The class of the read method will be invoked on * @param descriptor Property descriptor to return a setter for * @return The write method * @since 1.9.1 */ public Method getWriteMethod(final Class> clazz, final PropertyDescriptor descriptor) { final BeanIntrospectionData data = getIntrospectionData(clazz); return MethodUtils.getAccessibleMethod(clazz, data.getWriteMethod(clazz, descriptor)); } /** *Return {@code true} if the specified property name identifies * a readable property on the specified bean; otherwise, return * {@code false}. * * @param bean Bean to be examined (may be a {@link DynaBean} * @param name Property name to be evaluated * @return {@code true} if the property is readable, * otherwise {@code false} * * @throws IllegalArgumentException if {@code bean} * or {@code name
null} * * @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 for bean class '" + bean.getClass() + "'"); } // Resolve nested references while (resolver.hasNested(name)) { final String next = resolver.next(name); Object nestedBean = null; try { nestedBean = getProperty(bean, next); } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { return false; } if (nestedBean == null) { throw new NestedNullException ("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'"); } bean = nestedBean; name = resolver.remove(name); } // Remove any subscript from the final name value name = resolver.getProperty(name); // Treat WrapDynaBean as special case - may be a write-only property // (see Jira issue# BEANUTILS-61) if (bean instanceof WrapDynaBean) { bean = ((WrapDynaBean)bean).getInstance(); } // Return the requested result if (bean instanceof DynaBean) { // All DynaBean properties are readable return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null; } try { final PropertyDescriptor desc = getPropertyDescriptor(bean, name); if (desc != null) { Method readMethod = getReadMethod(bean.getClass(), desc); if (readMethod == null) { if (desc instanceof IndexedPropertyDescriptor) { readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); } else if (desc instanceof MappedPropertyDescriptor) { readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod(); } readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod); } return readMethod != null; } return false; } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { return false; } } /** *
isReturn {@code true} if the specified property name identifies * a writeable property on the specified bean; otherwise, return * {@code false}. * * @param bean Bean to be examined (may be a {@link DynaBean} * @param name Property name to be evaluated * @return {@code true} if the property is writeable, * otherwise {@code false} * * @throws IllegalArgumentException if {@code bean} * or {@code name
null} * * @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 for bean class '" + bean.getClass() + "'"); } // Resolve nested references while (resolver.hasNested(name)) { final String next = resolver.next(name); Object nestedBean = null; try { nestedBean = getProperty(bean, next); } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { return false; } if (nestedBean == null) { throw new NestedNullException ("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'"); } bean = nestedBean; name = resolver.remove(name); } // Remove any subscript from the final name value name = resolver.getProperty(name); // Treat WrapDynaBean as special case - may be a read-only property // (see Jira issue# BEANUTILS-61) if (bean instanceof WrapDynaBean) { bean = ((WrapDynaBean)bean).getInstance(); } // Return the requested result if (bean instanceof DynaBean) { // All DynaBean properties are writeable return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null; } try { final PropertyDescriptor desc = getPropertyDescriptor(bean, name); if (desc != null) { Method writeMethod = getWriteMethod(bean.getClass(), desc); if (writeMethod == null) { if (desc instanceof IndexedPropertyDescriptor) { writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod(); } else if (desc instanceof MappedPropertyDescriptor) { writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod(); } writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod); } return writeMethod != null; } return false; } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { return false; } } /** * Sets 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} will be * thrown. In addition to supporting the JavaBeans specification, this * method has been extended to support {@code List} objects as well. * * @param bean Bean whose property is to be modified * @param name {@code propertyname[index]} of the property value * to be modified * @param value Value to which the specified property element * should be set * * @throws IndexOutOfBoundsException if the specified index * is outside the valid range for the underlying property * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public void setIndexedProperty(final Object bean, String name, final Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null) { throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); } // Identify the index of the requested individual property int index = -1; try { index = resolver.getIndex(name); } catch (final IllegalArgumentException e) { throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'"); } if (index < 0) { throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'"); } // Isolate the name name = resolver.getProperty(name); // Set the specified indexed property value setIndexedProperty(bean, name, index, value); } /** * Sets 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} 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 * * @throws IndexOutOfBoundsException if the specified index * is outside the valid range for the underlying property * @throws IllegalAccessException if the caller does not have * access to the property accessor method * @throws IllegalArgumentException if {@code bean} or * {@code name} is null * @throws InvocationTargetException if the property accessor method * throws an exception * @throws NoSuchMethodException if an accessor method for this * property cannot be found */ public void setIndexedProperty(final Object bean, final String name, final int index, final Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } if (name == null || name.isEmpty()) { if (bean.getClass().isArray()) { Array.set(bean, index, value); return; } else if (bean instanceof List) { final List