com.feilong.lib.beanutils.PropertyUtilsBean Maven / Gradle / Ivy
Show all versions of feilong 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 com.feilong.lib.beanutils;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
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 java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.beanutils.DynaBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feilong.lib.beanutils.expression.DefaultResolver;
import com.feilong.lib.beanutils.expression.Resolver;
/**
* 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 BeanUtils
, but has been
* separated because of the volume of code involved.
*
*
* 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
* 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:
*
*
*
* - Simple (
name
) - The specified
* 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 instrospection, so that (unless
* overridden by a BeanInfo
class, a property named "xyz"
* will have a getter method named getXyz()
or (for boolean
* properties only) isXyz()
, and a setter method named
* setXyz()
.
*
* - Nested (
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 name2
, and so on. The property value that
* is ultimately retrieved or modified is the one identified by the
* last name element.
*
* - Indexed (
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. List
* objects are now also supported for read/write. You simply need to define
* a getter that returns the List
* - Mapped (
name(key)
) - The JavaBean
* is assumed to have an property getter and setter methods with an
* additional attribute of type java.lang.String
.
* - Combined (
name1.name2[index].name3(key)
) -
* Combining mapped, nested, and indexed references is also
* supported.
*
*
*
* @version $Id$
* @see Resolver
* @see PropertyUtils
* @since 1.7
*/
public class PropertyUtilsBean{
/** The Constant log. */
private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtilsBean.class);
//---------------------------------------------------------------
private Resolver resolver = new DefaultResolver();
// --------------------------------------------------------- Class Methods
/**
* Return the PropertyUtils bean instance.
*
* @return The PropertyUtils bean instance
*/
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 WeakFastHashMap, BeanIntrospectionData> descriptorsCache = null;
private WeakFastHashMap, FastHashMap> mappedDescriptorsCache = null;
/** An empty object array */
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/** The list with BeanIntrospector objects. */
private final List introspectors;
// ---------------------------------------------------------- Constructors
/** Base constructor */
public PropertyUtilsBean(){
descriptorsCache = new WeakFastHashMap<>();
descriptorsCache.setFast(true);
mappedDescriptorsCache = new WeakFastHashMap<>();
mappedDescriptorsCache.setFast(true);
introspectors = new CopyOnWriteArrayList<>();
resetBeanIntrospectors();
}
// --------------------------------------------------------- Public Methods
/**
* Return 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 recongnises.
*
* {@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 recongnises.
*
* {@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);
}
/**
*
* 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
* 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 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 dest
or
* orig
argument is null
* @throws InvocationTargetException
* if the property accessor method
* throws an exception
* @throws NoSuchMethodException
* if an accessor method for this
* propety 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 (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 (LOGGER.isDebugEnabled()){
LOGGER.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
}
}
}
}
}else if (orig instanceof Map){
final Iterator entries = ((Map) orig).entrySet().iterator();
while (entries.hasNext()){
final Map.Entry entry = (Entry) entries.next();
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 (LOGGER.isDebugEnabled()){
LOGGER.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
}
}
}
}
}else /* if (orig is a standard JavaBean) */ {
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
for (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 (LOGGER.isDebugEnabled()){
LOGGER.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 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 bean
is null
* @throws InvocationTargetException
* if the property accessor method
* throws an exception
* @throws NoSuchMethodException
* if an accessor method for this
* propety cannot be found
*/
public Map describe(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 (DynaProperty descriptor : descriptors){
final String name = descriptor.getName();
description.put(name, getProperty(bean, name));
}
}else{
final PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
for (PropertyDescriptor descriptor : descriptors){
final String name = descriptor.getName();
if (descriptor.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 IllegalArgumentException
will be
* thrown. In addition to supporting the JavaBeans specification, this
* method has been extended to support List
objects as well.
*
* @param bean
* Bean whose property is to be extracted
* @param name
* 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 bean
or
* name
is null
* @throws InvocationTargetException
* if the property accessor method
* throws an exception
* @throws NoSuchMethodException
* if an accessor method for this
* propety 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));
}
/**
* 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
* 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 bean
or
* name
is null
* @throws InvocationTargetException
* if the property accessor method
* throws an exception
* @throws NoSuchMethodException
* if an accessor method for this
* propety 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.length() == 0){
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] = new Integer(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, 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 + "'");
}
}
/**
* 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 IllegalArgumentException
will be
* thrown.
*
* @param bean
* Bean whose property is to be extracted
* @param name
* 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
* propety 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));
}
/**
* 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
* @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
* propety 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, 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
* @deprecated This method should not be exposed
*/
@Deprecated
public FastHashMap 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
* @deprecated This method should not be exposed
*/
@Deprecated
private FastHashMap getMappedPropertyDescriptors(final Object bean){
if (bean == null){
return null;
}
return (getMappedPropertyDescriptors(bean.getClass()));
}
/**
* 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
*/
private 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.length() == 0){
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);
}
/**
* 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
* @return the property value
*
* @throws IllegalAccessException
* if the caller does not have
* access to the property accessor method
* @throws IllegalArgumentException
* if bean
or
* name
is null
* @throws InvocationTargetException
* if the property accessor method
* throws an exception
* @throws NoSuchMethodException
* if an accessor method for this
* propety cannot be found
*/
public Object getProperty(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){
return getPropertyOfMapBean((Map) bean, name);
}else if (resolver.isMapped(name)){
return getMappedProperty(bean, name);
}else if (resolver.isIndexed(name)){
return getIndexedProperty(bean, name);
}else{
return getSimpleProperty(bean, name);
}
}
/**
*
* Retrieve the property descriptor for the specified property of the
* specified bean, or return 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 bean
or
* 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
* 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 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;
}
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 (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 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 bean
is null
*/
public PropertyDescriptor[] getPropertyDescriptors(final Object bean){
if (bean == null){
throw new IllegalArgumentException("No bean specified");
}
return (getPropertyDescriptors(bean.getClass()));
}
/**
*
* Return an accessible property getter method for this property,
* if there is one; otherwise return 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 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
*/
static Method getReadMethod(final Class clazz,final PropertyDescriptor descriptor){
return MethodUtils.getAccessibleMethod(clazz, 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
* @return The property value
*
* @throws IllegalAccessException
* if the caller does not have
* access to the property accessor method
* @throws IllegalArgumentException
* if bean
or
* 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
* propety 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() + "'");
}
return getSimpleProperty(bean, name, descriptor);
}
/**
*
* @since 3.0.6
*/
private static Object getSimpleProperty(final Object bean,final String name,final PropertyDescriptor descriptor)
throws NoSuchMethodException,IllegalAccessException,InvocationTargetException{
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
return invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
}
/**
*
* Return an accessible property setter method for this property,
* if there is one; otherwise return 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 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
*/
private Method getWriteMethod(final Class clazz,final PropertyDescriptor descriptor){
final BeanIntrospectionData data = getIntrospectionData(clazz);
return (MethodUtils.getAccessibleMethod(clazz, data.getWriteMethod(clazz, descriptor)));
}
/**
*
* Return true
if the specified property name identifies
* a readable property on the specified bean; otherwise, return
* false
.
*
* @param bean
* Bean to be examined (may be a {@link DynaBean}
* @param name
* Property name to be evaluated
* @return true
if the property is readable,
* otherwise false
*
* @throws IllegalArgumentException
* if bean
* or name
is 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 e){
return false;
}catch (final InvocationTargetException e){
return false;
}catch (final NoSuchMethodException 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 e){
return false;
}catch (final InvocationTargetException e){
return false;
}catch (final NoSuchMethodException e){
return false;
}
}
/**
*
* Return true
if the specified property name identifies
* a writeable property on the specified bean; otherwise, return
* false
.
*
* @param bean
* Bean to be examined (may be a {@link DynaBean}
* @param name
* Property name to be evaluated
* @return true
if the property is writeable,
* otherwise false
*
* @throws IllegalArgumentException
* if bean
* or name
is 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 e){
return false;
}catch (final InvocationTargetException e){
return false;
}catch (final NoSuchMethodException 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 e){
return (false);
}catch (final InvocationTargetException e){
return (false);
}catch (final 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 IllegalArgumentException
will be
* thrown. In addition to supporting the JavaBeans specification, this
* method has been extended to support List
objects as well.
*
* @param bean
* Bean whose property is to be modified
* @param name
* 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 bean
or
* name
is null
* @throws InvocationTargetException
* if the property accessor method
* throws an exception
* @throws NoSuchMethodException
* if an accessor method for this
* propety cannot be found
*/
private 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);
}
/**
* 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
* 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 bean
or
* name
is null
* @throws InvocationTargetException
* if the property accessor method
* throws an exception
* @throws NoSuchMethodException
* if an accessor method for this
* propety 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.length() == 0){
if (bean.getClass().isArray()){
Array.set(bean, index, value);
return;
}else if (bean instanceof List){
final List