PropertyUtilsBean.java
- /*
- * 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.Objects;
- 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.
- * <p>
- * In general, the objects that are examined and modified using these methods are expected to conform to the property getter and setter method naming
- * conventions described in the JavaBeans Specification (Version 1.0.1). No data type conversions are performed, and there are no usage of any
- * {@code PropertyEditor} classes that have been registered, although a convenient way to access the registered classes themselves is included.
- * <p>
- * For the purposes of this class, five formats for referencing a particular property value of a bean are defined, with the <em>default</em> 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:
- * <ul>
- * <li><strong>Simple ({@code name})</strong> - 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()}.</li>
- * <li><strong>Nested ({@code name1.name2.name3})</strong> The first name element is used to select a property getter, as for simple references above. The
- * object returned for this property is then consulted, using the same approach, for a property getter for a property named {@code name2}, and so on. The
- * property value that is ultimately retrieved or modified is the one identified by the last name element.</li>
- * <li><strong>Indexed ({@code name[index]})</strong> - The underlying property value is assumed to be an array, or this JavaBean is assumed to have indexed
- * property getter and setter methods. The appropriate (zero-relative) entry in the array is selected. {@code List} objects are now also supported for
- * read/write. You simply need to define a getter that returns the {@code List}</li>
- * <li><strong>Mapped ({@code name(key)})</strong> - The JavaBean is assumed to have an property getter and setter methods with an additional attribute of type
- * {@link String}.</li>
- * <li><strong>Combined ({@code name1.name2[index].name3(key)})</strong> - Combining mapped, nested, and indexed references is also supported.</li>
- * </ul>
- *
- * @see Resolver
- * @see PropertyUtils
- * @since 1.7
- */
- public class PropertyUtilsBean {
- /** Log instance */
- private static final Log LOG = LogFactory.getLog(PropertyUtilsBean.class);
- /**
- * Gets the PropertyUtils bean instance.
- *
- * @return The PropertyUtils bean instance
- */
- protected static PropertyUtilsBean getInstance() {
- return BeanUtilsBean.getInstance().getPropertyUtils();
- }
- /**
- * Converts an object to a list of objects. This method is used when dealing with indexed properties. It assumes that indexed properties are stored as lists
- * of objects.
- *
- * @param obj the object to be converted
- * @return the resulting list of objects
- */
- @SuppressWarnings("unchecked")
- private static List<Object> toObjectList(final Object obj) {
- // indexed properties are stored in lists of objects
- return (List<Object>) obj;
- }
- /**
- * Converts an object to a map with property values. This method is used when dealing with mapped properties. It assumes that mapped properties are stored
- * in a Map<String, Object>.
- *
- * @param obj the object to be converted
- * @return the resulting properties map
- */
- @SuppressWarnings("unchecked")
- private static Map<String, Object> toPropertyMap(final Object obj) {
- // mapped properties are stores in maps of type <String, Object>
- return (Map<String, Object>) obj;
- }
- private Resolver resolver = new DefaultResolver();
- /**
- * The cache of PropertyDescriptor arrays for beans we have already introspected, keyed by the java.lang.Class of this object.
- */
- private final Map<Class<?>, BeanIntrospectionData> descriptorsCache;
- private final Map<Class<?>, Map> mappedDescriptorsCache;
- /** The list with BeanIntrospector objects. */
- private final List<BeanIntrospector> introspectors;
- /** Base constructor */
- public PropertyUtilsBean() {
- descriptorsCache = BeanUtils.createCache();
- mappedDescriptorsCache = BeanUtils.createCache();
- introspectors = new CopyOnWriteArrayList<>();
- resetBeanIntrospectors();
- }
- /**
- * 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 <strong>null</strong>
- * @throws IllegalArgumentException if the argument is <strong>null</strong>
- * @since 1.9
- */
- public void addBeanIntrospector(final BeanIntrospector introspector) {
- introspectors.add(Objects.requireNonNull(introspector, "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();
- }
- /**
- * <p>
- * Copy property values from the "origin" bean to the "destination" bean for all cases where the property names are the same (even though the actual getter
- * and setter methods might have been customized via {@code BeanInfo} classes). No conversions are performed on the actual property values -- it is assumed
- * that the values retrieved from the origin bean are assignment-compatible with the types expected by the destination bean.
- * </p>
- *
- * <p>
- * If the origin "bean" is actually a {@code Map}, it is assumed to contain String-valued <strong>simple</strong> property names as the keys, pointing at
- * the corresponding property values that will be set in the destination bean.<strong>Note</strong> that this method is intended to perform a "shallow copy"
- * of the properties and so complex properties (for example, nested ones) will not be copied.
- * </p>
- *
- * <p>
- * 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.
- * </p>
- *
- * @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,
- // TODO BEFORE 2.0
- // MISMATCH between implementation and Javadoc.
- NoSuchMethodException {
- Objects.requireNonNull(dest, "dest");
- Objects.requireNonNull(orig, "orig");
- 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);
- }
- }
- }
- }
- }
- }
- /**
- * <p>
- * Return the entire set of properties for which the specified bean provides a read method. This map contains the unconverted property values for all
- * properties for which a read method is provided (i.e. where the {@code getReadMethod()} returns non-null).
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not account for mapped properties.
- * </p>
- *
- * @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 Map<String, Object> describe(final Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
- Objects.requireNonNull(bean, "bean");
- final Map<String, Object> 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;
- }
- /**
- * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were added to this instance.
- *
- * @param beanClass the class to be inspected
- * @return a data object with the results of introspection
- */
- private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
- final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
- for (final BeanIntrospector bi : introspectors) {
- try {
- bi.introspect(ictx);
- } catch (final IntrospectionException iex) {
- LOG.error("Exception during introspection", iex);
- }
- }
- return new BeanIntrospectionData(ictx.getPropertyDescriptors());
- }
- /**
- * 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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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 {
- Objects.requireNonNull(bean, "bean");
- if (name == null || name.isEmpty()) {
- if (bean.getClass().isArray()) {
- return Array.get(bean, index);
- }
- if (bean instanceof List) {
- return ((List<?>) bean).get(index);
- }
- }
- Objects.requireNonNull(name, "name");
- // 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) {
- try {
- return invokeMethod(readMethod, bean, Integer.valueOf(index));
- } 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 List)) {
- throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
- }
- // get the List's value
- return ((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 + "'");
- }
- }
- /**
- * Obtains the {@code BeanIntrospectionData} object describing the specified bean class. This object is looked up in the internal cache. If necessary,
- * introspection is performed now on the affected bean class, and the results object is created.
- *
- * @param beanClass the bean class in question
- * @return the {@code BeanIntrospectionData} object for this class
- * @throws IllegalArgumentException if the bean class is <strong>null</strong>
- */
- private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
- Objects.requireNonNull(beanClass, "beanClass");
- // Look up any cached information for this bean class
- BeanIntrospectionData data = descriptorsCache.get(beanClass);
- if (data == null) {
- data = fetchIntrospectionData(beanClass);
- descriptorsCache.put(beanClass, data);
- }
- return data;
- }
- /**
- * 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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- Objects.requireNonNull(key, "key");
- // 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) {
- throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
- }
- result = invokeMethod(readMethod, bean, key);
- } else {
- /* means that the result has to be retrieved from a map */
- final Method readMethod = getReadMethod(bean.getClass(), descriptor);
- if (readMethod == null) {
- throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
- }
- final Object invokeResult = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
- /* test and fetch from the map */
- if (invokeResult instanceof Map) {
- result = ((Map<?, ?>) invokeResult).get(key);
- }
- }
- return result;
- }
- /**
- * <p>
- * Return the mapped property descriptors for this bean class.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * @param beanClass Bean class to be introspected
- * @return the mapped property descriptors
- */
- Map<Class<?>, Map> getMappedPropertyDescriptors(final Class<?> beanClass) {
- if (beanClass == null) {
- return null;
- }
- // Look up any cached descriptors for this bean class
- return mappedDescriptorsCache.get(beanClass);
- }
- /**
- * <p>
- * Return the mapped property descriptors for this bean.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * @param bean Bean to be introspected
- * @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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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;
- }
- /**
- * 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);
- }
- /**
- * <p>
- * 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.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * <p>
- * 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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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<Class<?>, 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;
- }
- /**
- * <p>
- * Retrieve the property descriptors for the specified class, introspecting and caching them the first time a particular bean class is encountered.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * @param beanClass Bean class for which property descriptors are requested
- * @return the property descriptors
- * @throws IllegalArgumentException if {@code beanClass} is null
- */
- public PropertyDescriptor[] getPropertyDescriptors(final Class<?> beanClass) {
- return getIntrospectionData(beanClass).getDescriptors();
- }
- /**
- * <p>
- * Retrieve the property descriptors for the specified bean, introspecting and caching them the first time a particular bean class is encountered.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * @param bean Bean for which property descriptors are requested
- * @return the property descriptors
- * @throws IllegalArgumentException if {@code bean} is null
- */
- public PropertyDescriptor[] getPropertyDescriptors(final Object bean) {
- Objects.requireNonNull(bean, "bean");
- return getPropertyDescriptors(bean.getClass());
- }
- /**
- * <p>
- * Return the Java Class repesenting the property editor class that has been registered for this property (if any). This method follows the same name
- * resolution rules used by {@code getPropertyDescriptor()}, so if the last element of a name reference is indexed, the property editor for the underlying
- * property's class is returned.
- * </p>
- *
- * <p>
- * Note that {@code null} will be returned if there is no property, or if there is no registered property editor class. Because this return value is
- * ambiguous, you should determine the existence of the property itself by other means.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * @param bean Bean for which a property descriptor is requested
- * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
- * @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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
- if (descriptor != null) {
- return descriptor.getPropertyEditorClass();
- }
- return null;
- }
- /**
- * 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 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.
- * <p>
- * If the property is an indexed property (for example {@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 (for example
- * java.util.List) from the getter method.
- * </p>
- *
- * @param bean Bean for which a property descriptor is requested
- * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
- * @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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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;
- }
- if (type.isArray()) {
- return type.getComponentType();
- }
- return type;
- }
- final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
- if (descriptor == null) {
- return null;
- }
- if (descriptor instanceof IndexedPropertyDescriptor) {
- return ((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType();
- }
- if (descriptor instanceof MappedPropertyDescriptor) {
- return ((MappedPropertyDescriptor) descriptor).getMappedPropertyType();
- }
- return descriptor.getPropertyType();
- }
- /**
- * <p>
- * Return the property getter method for this property if accessible from given {@code clazz} (and if there is one at all); otherwise return {@code null}.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * <p>
- * This fairly low-level method shouldn't be needed for most usecases. However, if you do have to implement something extra, you can improve consistency
- * with the standard code (for example that of {@link #getProperty getProperty()}) by calling this method instead of using
- * {@code descriptor.getReadMethod()} directly.
- * </p>
- *
- * @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
- * @since 2.0.0
- */
- public Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
- return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
- }
- /**
- * <p>
- * Return an accessible property getter method for this property, if there is one; otherwise return {@code null}.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * @param descriptor Property descriptor to return a getter for
- * @return The read method
- */
- public Method getReadMethod(final PropertyDescriptor descriptor) {
- return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
- }
- /**
- * Gets the configured {@link Resolver} implementation used by BeanUtils.
- * <p>
- * The {@link Resolver} handles the <em>property name</em> expressions and the implementation in use effectively controls the dialect of the <em>expression
- * language</em> that BeanUtils recognizes.
- * <p>
- * {@link DefaultResolver} is the default implementation used.
- *
- * @return resolver The property expression resolver.
- * @since 1.8.0
- */
- public Resolver getResolver() {
- return resolver;
- }
- /**
- * 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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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() + "'");
- }
- if (resolver.isIndexed(name)) {
- throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
- }
- 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
- return invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
- }
- /**
- * <p>
- * Return the property setter method for this property if accessible from given {@code clazz} (and if there is one at all); otherwise return {@code null}.
- * </p>
- *
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * <p>
- * This fairly low-level method shouldn't be needed for most usecases. However, if you do have to implement something extra, you can improve consistency
- * with the standard code (for example that of {@link #setProperty setProperty()}) by calling this method instead of using
- * {@code descriptor.getWriteMethod()} directly.
- * </p>
- *
- * @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));
- }
- /**
- * <p>
- * Return an accessible property setter method for this property, if there is one; otherwise return {@code null}.
- * </p>
- *
- * <p>
- * <em>Note:</em> 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.
- * </p>
- * <p>
- * <strong>FIXME</strong> - Does not work with DynaBeans.
- * </p>
- *
- * @param descriptor Property descriptor to return a setter for
- * @return The write method
- */
- public Method getWriteMethod(final PropertyDescriptor descriptor) {
- return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
- }
- /**
- * Delegates to {@link Method#invoke(Object, Object...)} and handles some unchecked exceptions.
- *
- * @see Method#invoke(Object, Object...)
- */
- private Object invokeMethod(final Method method, final Object bean, final Object... values) throws IllegalAccessException, InvocationTargetException {
- Objects.requireNonNull(bean, "bean");
- try {
- return method.invoke(bean, values);
- } catch (final NullPointerException | IllegalArgumentException cause) {
- // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
- // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
- final StringBuilder valueString = new StringBuilder();
- if (values != null) {
- for (int i = 0; i < values.length; i++) {
- if (i > 0) {
- valueString.append(", ");
- }
- if (values[i] == null) {
- valueString.append("<null>");
- } else {
- valueString.append(values[i].getClass().getName());
- }
- }
- }
- final StringBuilder expectedString = new StringBuilder();
- final Class<?>[] parTypes = method.getParameterTypes();
- if (parTypes != null) {
- for (int i = 0; i < parTypes.length; i++) {
- if (i > 0) {
- expectedString.append(", ");
- }
- expectedString.append(parTypes[i].getName());
- }
- }
- throw new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '"
- + bean.getClass() + "' - " + cause.getMessage()
- // as per https://issues.apache.org/jira/browse/BEANUTILS-224
- + " - had objects of type \"" + valueString + "\" but expected signature \"" + expectedString + "\"", cause);
- }
- }
- /**
- * 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</code> is <code>null}
- * @since 1.6
- */
- public boolean isReadable(Object bean, String name) {
- // Validate method parameters
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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;
- }
- }
- /**
- * Return {@code true} if the specified property name identifies a writable 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 writable, otherwise {@code false}
- * @throws IllegalArgumentException if {@code bean} or {@code name</code> is <code>null}
- * @since 1.6
- */
- public boolean isWriteable(Object bean, String name) {
- // Validate method parameters
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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 writable
- 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;
- }
- }
- /**
- * Removes the specified {@code BeanIntrospector}.
- *
- * @param introspector the {@code BeanIntrospector} to be removed
- * @return <strong>true</strong> if the {@code BeanIntrospector} existed and could be removed, <strong>false</strong> otherwise
- * @since 1.9
- */
- public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
- return introspectors.remove(introspector);
- }
- /**
- * 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);
- }
- /**
- * 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 {
- Objects.requireNonNull(bean, "bean");
- if (name == null || name.isEmpty()) {
- if (bean.getClass().isArray()) {
- Array.set(bean, index, value);
- return;
- }
- if (bean instanceof List) {
- final List<Object> list = toObjectList(bean);
- list.set(index, value);
- return;
- }
- }
- Objects.requireNonNull(name, "name");
- // 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() + "'");
- }
- ((DynaBean) bean).set(name, index, value);
- return;
- }
- // 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 setter method if there is one
- if (descriptor instanceof IndexedPropertyDescriptor) {
- Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod();
- writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
- if (writeMethod != null) {
- try {
- if (LOG.isTraceEnabled()) {
- final String valueClassName = value == null ? "<null>" : value.getClass().getName();
- LOG.trace("setSimpleProperty: Invoking method " + writeMethod + " with index=" + index + ", value=" + value + " (class "
- + valueClassName + ")");
- }
- invokeMethod(writeMethod, bean, Integer.valueOf(index), value);
- } catch (final InvocationTargetException e) {
- if (e.getTargetException() instanceof IndexOutOfBoundsException) {
- throw (IndexOutOfBoundsException) e.getTargetException();
- }
- throw e;
- }
- return;
- }
- }
- // Otherwise, the underlying property must be an array or a list
- 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 to get the array or list
- final Object array = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
- if (!array.getClass().isArray()) {
- if (!(array instanceof List)) {
- throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
- }
- // Modify the specified value in the List
- final List<Object> list = toObjectList(array);
- list.set(index, value);
- } else {
- // Modify the specified value in the array
- Array.set(array, index, value);
- }
- }
- /**
- * 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 {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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 mapped property of the specified bean, with no type conversions. The key of the value to set must be included (in
- * brackets) as a suffix to the property name, or {@code IllegalArgumentException} will be thrown.
- *
- * @param bean Bean whose property is to be set
- * @param name {@code propertyname(key)} of the property value to be set
- * @param value The property value to be set
- * @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 void setMappedProperty(final Object bean, String name, final Object value)
- throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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() + "'");
- }
- 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
- setMappedProperty(bean, name, key, value);
- }
- /**
- * Sets the value of the specified mapped property of the specified bean, with no type conversions.
- *
- * @param bean Bean whose property is to be set
- * @param name Mapped property name of the property value to be set
- * @param key Key of the property value to be set
- * @param value The property value to be set
- * @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 void setMappedProperty(final Object bean, final String name, final String key, final Object value)
- throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- Objects.requireNonNull(key, "key");
- // 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() + "'");
- }
- ((DynaBean) bean).set(name, key, value);
- return;
- }
- // 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 setter method if there is one
- Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod();
- mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
- if (mappedWriteMethod == null) {
- throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method" + "on bean class '" + bean.getClass() + "'");
- }
- if (LOG.isTraceEnabled()) {
- final String valueClassName = value == null ? "<null>" : value.getClass().getName();
- LOG.trace("setSimpleProperty: Invoking method " + mappedWriteMethod + " with key=" + key + ", value=" + value + " (class " + valueClassName
- + ")");
- }
- invokeMethod(mappedWriteMethod, bean, key, value);
- } else {
- /* means that the result has to be retrieved from a map */
- final Method readMethod = getReadMethod(bean.getClass(), descriptor);
- if (readMethod == null) {
- throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
- }
- final Object invokeResult = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
- /* test and fetch from the map */
- if (invokeResult instanceof Map) {
- toPropertyMap(invokeResult).put(key, value);
- }
- }
- }
- /**
- * Sets the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
- * <p>
- * Example values for parameter "name" are:
- * <ul>
- * <li>"a" -- sets the value of property a of the specified bean</li>
- * <li>"a.b" -- gets the value of property a of the specified bean, then on that object sets the value of property b.</li>
- * <li>"a(key)" -- sets a value of mapped-property a on the specified bean. This effectively means bean.setA("key").</li>
- * <li>"a[3]" -- sets a value of indexed-property a on the specified bean. This effectively means bean.setA(3).</li>
- * </ul>
- *
- * @param bean Bean whose property is to be modified
- * @param name Possibly nested name of the property to be modified
- * @param value Value to which the property is to be set
- * @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 void setNestedProperty(Object bean, String name, final Object value)
- throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- // 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) {
- setPropertyOfMapBean(toPropertyMap(bean), name, value);
- } else if (resolver.isMapped(name)) {
- setMappedProperty(bean, name, value);
- } else if (resolver.isIndexed(name)) {
- setIndexedProperty(bean, name, value);
- } else {
- setSimpleProperty(bean, name, value);
- }
- }
- /**
- * Sets the value of the specified property of the specified bean, no matter which property reference format is used, with no type conversions.
- *
- * @param bean Bean whose property is to be modified
- * @param name Possibly indexed and/or nested name of the property to be modified
- * @param value Value to which this property is to be set
- * @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 setProperty(final Object bean, final String name, final Object value)
- throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
- setNestedProperty(bean, name, value);
- }
- /**
- * This method is called by method setNestedProperty when the current bean is found to be a Map object, and defines how to deal with setting a property on a
- * Map.
- * <p>
- * The standard implementation here is to:
- * <ul>
- * <li>call bean.set(propertyName) for all propertyName values.</li>
- * <li>throw an IllegalArgumentException if the property specifier contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially simple properties;
- * mapping and indexing operations do not make sense when accessing a map (even thought the returned object may be a Map or an Array).</li>
- * </ul>
- * <p>
- * The default behavior of BeanUtils 1.7.1 or later is for assigning to "a.b" to mean a.put(b, obj) always. However the behavior of BeanUtils version 1.6.0,
- * 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such a method existed, and a.put(b, obj) otherwise. In version 1.5 it meant a.put(b, obj) always (ie
- * the same as the behavior in the current version). In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is all <em>very</em> unfortunate]
- * <p>
- * Users who would like to customize the meaning of "a.b" in method setNestedProperty when a is a Map can create a custom subclass of this class and
- * override this method to implement the behavior of their choice, such as restoring the pre-1.4 behavior of this class if they wish. When overriding this
- * method, do not forget to deal with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
- * <p>
- * Note, however, that the recommended solution for objects that implement Map but want their simple properties to come first is for <em>those</em> objects
- * to override their get/put methods to implement that behavior, and <em>not</em> to solve the problem by modifying the default behavior of the
- * PropertyUtilsBean class by overriding this method.
- *
- * @param bean Map bean
- * @param propertyName The property name
- * @param value 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 setter methods and find permission is denied.
- * @throws InvocationTargetException just in case subclasses override this method to try to access real setter methods, and find it throws an exception when
- * invoked.
- *
- * @throws NoSuchMethodException just in case subclasses override this method to try to access real setter methods, and want to fail if no simple method
- * is available.
- * @since 1.8.0
- */
- protected void setPropertyOfMapBean(final Map<String, Object> bean, String propertyName, final Object value)
- 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);
- }
- bean.put(propertyName, value);
- }
- /**
- * Configure the {@link Resolver} implementation used by BeanUtils.
- * <p>
- * The {@link Resolver} handles the <em>property name</em> expressions and the implementation in use effectively controls the dialect of the <em>expression
- * language</em> that BeanUtils recognizes.
- * <p>
- * {@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;
- }
- }
- /**
- * Sets the value of the specified simple property of the specified bean, with no type conversions.
- *
- * @param bean Bean whose property is to be modified
- * @param name Name of the property to be modified
- * @param value Value to which the property should be set
- * @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 void setSimpleProperty(final Object bean, final String name, final Object value)
- throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
- Objects.requireNonNull(bean, "bean");
- Objects.requireNonNull(name, "name");
- final Class<?> beanClass = 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 '" + beanClass + "'");
- }
- if (resolver.isIndexed(name)) {
- throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
- }
- if (resolver.isMapped(name)) {
- throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
- }
- // 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() + "'");
- }
- ((DynaBean) bean).set(name, value);
- return;
- }
- // Retrieve the property setter method for the specified property
- final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
- if (descriptor == null) {
- throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + beanClass + "'");
- }
- final Method writeMethod = getWriteMethod(beanClass, descriptor);
- if (writeMethod == null) {
- throw new NoSuchMethodException("Property '" + name + "' has no setter method in class '" + beanClass + "'");
- }
- // Call the property setter method
- if (LOG.isTraceEnabled()) {
- final String valueClassName = value == null ? "<null>" : value.getClass().getName();
- LOG.trace("setSimpleProperty: Invoking method " + writeMethod + " with value " + value + " (class " + valueClassName + ")");
- }
- invokeMethod(writeMethod, bean, value);
- }
- }