/* * Copyright 2002-2008 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.beans; import java.beans.PropertyDescriptor; import java.beans.PropertyEditor; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URI; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * Static convenience methods for JavaBeans: for instantiating beans, * checking bean property types, copying bean properties, etc. * *
Mainly for use within the framework, but to some degree also * useful for application classes. * * @author Rod Johnson * @author Juergen Hoeller * @author Rob Harrop */ public abstract class BeanUtils { private static final Log logger = LogFactory.getLog(BeanUtils.class); private static final Map unknownEditorTypes = Collections.synchronizedMap(new WeakHashMap()); /** * Convenience method to instantiate a class using its no-arg constructor. * As this method doesn't try to load classes by name, it should avoid * class-loading issues. *
Note that this method tries to set the constructor accessible * if given a non-accessible (that is, non-public) constructor. * @param clazz class to instantiate * @return the new instance * @throws BeanInstantiationException if the bean cannot be instantiated */ public static Object instantiateClass(Class clazz) throws BeanInstantiationException { Assert.notNull(clazz, "Class must not be null"); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { return instantiateClass(clazz.getDeclaredConstructor((Class[]) null), null); } catch (NoSuchMethodException ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } /** * Convenience method to instantiate a class using the given constructor. * As this method doesn't try to load classes by name, it should avoid * class-loading issues. *
Note that this method tries to set the constructor accessible * if given a non-accessible (that is, non-public) constructor. * @param ctor the constructor to instantiate * @param args the constructor arguments to apply * @return the new instance * @throws BeanInstantiationException if the bean cannot be instantiated */ public static Object instantiateClass(Constructor ctor, Object[] args) throws BeanInstantiationException { Assert.notNull(ctor, "Constructor must not be null"); try { ReflectionUtils.makeAccessible(ctor); return ctor.newInstance(args); } catch (InstantiationException ex) { throw new BeanInstantiationException(ctor.getDeclaringClass(), "Is it an abstract class?", ex); } catch (IllegalAccessException ex) { throw new BeanInstantiationException(ctor.getDeclaringClass(), "Has the class definition changed? Is the constructor accessible?", ex); } catch (IllegalArgumentException ex) { throw new BeanInstantiationException(ctor.getDeclaringClass(), "Illegal arguments for constructor", ex); } catch (InvocationTargetException ex) { throw new BeanInstantiationException(ctor.getDeclaringClass(), "Constructor threw exception", ex.getTargetException()); } } /** * Find a method with the given method name and the given parameter types, * declared on the given class or one of its superclasses. Prefers public methods, * but will return a protected, package access, or private method too. *
Checks Class.getMethod
first, falling back to
* findDeclaredMethod
. This allows to find public methods
* without issues even in environments with restricted Java security settings.
* @param clazz the class to check
* @param methodName the name of the method to find
* @param paramTypes the parameter types of the method to find
* @return the Method object, or null
if not found
* @see java.lang.Class#getMethod
* @see #findDeclaredMethod
*/
public static Method findMethod(Class clazz, String methodName, Class[] paramTypes) {
try {
return clazz.getMethod(methodName, paramTypes);
}
catch (NoSuchMethodException ex) {
return findDeclaredMethod(clazz, methodName, paramTypes);
}
}
/**
* Find a method with the given method name and the given parameter types,
* declared on the given class or one of its superclasses. Will return a public,
* protected, package access, or private method.
*
Checks Class.getDeclaredMethod
, cascading upwards to all superclasses.
* @param clazz the class to check
* @param methodName the name of the method to find
* @param paramTypes the parameter types of the method to find
* @return the Method object, or null
if not found
* @see java.lang.Class#getDeclaredMethod
*/
public static Method findDeclaredMethod(Class clazz, String methodName, Class[] paramTypes) {
try {
return clazz.getDeclaredMethod(methodName, paramTypes);
}
catch (NoSuchMethodException ex) {
if (clazz.getSuperclass() != null) {
return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
}
return null;
}
}
/**
* Find a method with the given method name and minimal parameters (best case: none),
* declared on the given class or one of its superclasses. Prefers public methods,
* but will return a protected, package access, or private method too.
*
Checks Class.getMethods
first, falling back to
* findDeclaredMethodWithMinimalParameters
. This allows to find public
* methods without issues even in environments with restricted Java security settings.
* @param clazz the class to check
* @param methodName the name of the method to find
* @return the Method object, or null
if not found
* @throws IllegalArgumentException if methods of the given name were found but
* could not be resolved to a unique method with minimal parameters
* @see java.lang.Class#getMethods
* @see #findDeclaredMethodWithMinimalParameters
*/
public static Method findMethodWithMinimalParameters(Class clazz, String methodName)
throws IllegalArgumentException {
Method targetMethod = doFindMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName);
if (targetMethod == null) {
return findDeclaredMethodWithMinimalParameters(clazz, methodName);
}
return targetMethod;
}
/**
* Find a method with the given method name and minimal parameters (best case: none),
* declared on the given class or one of its superclasses. Will return a public,
* protected, package access, or private method.
*
Checks Class.getDeclaredMethods
, cascading upwards to all superclasses.
* @param clazz the class to check
* @param methodName the name of the method to find
* @return the Method object, or null
if not found
* @throws IllegalArgumentException if methods of the given name were found but
* could not be resolved to a unique method with minimal parameters
* @see java.lang.Class#getDeclaredMethods
*/
public static Method findDeclaredMethodWithMinimalParameters(Class clazz, String methodName)
throws IllegalArgumentException {
Method targetMethod = doFindMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName);
if (targetMethod == null && clazz.getSuperclass() != null) {
return findDeclaredMethodWithMinimalParameters(clazz.getSuperclass(), methodName);
}
return targetMethod;
}
/**
* Find a method with the given method name and minimal parameters (best case: none)
* in the given list of methods.
* @param methods the methods to check
* @param methodName the name of the method to find
* @return the Method object, or null
if not found
* @throws IllegalArgumentException if methods of the given name were found but
* could not be resolved to a unique method with minimal parameters
*/
private static Method doFindMethodWithMinimalParameters(Method[] methods, String methodName)
throws IllegalArgumentException {
Method targetMethod = null;
int numMethodsFoundWithCurrentMinimumArgs = 0;
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equals(methodName)) {
int numParams = methods[i].getParameterTypes().length;
if (targetMethod == null ||
numParams < targetMethod.getParameterTypes().length) {
targetMethod = methods[i];
numMethodsFoundWithCurrentMinimumArgs = 1;
}
else {
if (targetMethod.getParameterTypes().length == numParams) {
// Additional candidate with same length.
numMethodsFoundWithCurrentMinimumArgs++;
}
}
}
}
if (numMethodsFoundWithCurrentMinimumArgs > 1) {
throw new IllegalArgumentException("Cannot resolve method '" + methodName +
"' to a unique method. Attempted to resolve to overloaded method with " +
"the least number of parameters, but there were " +
numMethodsFoundWithCurrentMinimumArgs + " candidates.");
}
return targetMethod;
}
/**
* Parse a method signature in the form methodName[([arg_list])]
,
* where arg_list
is an optional, comma-separated list of fully-qualified
* type names, and attempts to resolve that signature against the supplied Class
.
*
When not supplying an argument list (methodName
) the method whose name
* matches and has the least number of parameters will be returned. When supplying an
* argument type list, only the method whose name and argument types match will be returned.
*
Note then that methodName
and methodName()
are not
* resolved in the same way. The signature methodName
means the method called
* methodName
with the least number of arguments, whereas methodName()
* means the method called methodName
with exactly 0 arguments.
*
If no method can be found, then null
is returned.
* @param signature the method signature as String representation
* @param clazz the class to resolve the method signature against
* @return the resolved Method
* @see #findMethod
* @see #findMethodWithMinimalParameters
*/
public static Method resolveSignature(String signature, Class clazz) {
Assert.hasText(signature, "'signature' must not be empty");
Assert.notNull(clazz, "Class must not be null");
int firstParen = signature.indexOf("(");
int lastParen = signature.indexOf(")");
if (firstParen > -1 && lastParen == -1) {
throw new IllegalArgumentException("Invalid method signature '" + signature +
"': expected closing ')' for args list");
}
else if (lastParen > -1 && firstParen == -1) {
throw new IllegalArgumentException("Invalid method signature '" + signature +
"': expected opening '(' for args list");
}
else if (firstParen == -1 && lastParen == -1) {
return findMethodWithMinimalParameters(clazz, signature);
}
else {
String methodName = signature.substring(0, firstParen);
String[] parameterTypeNames =
StringUtils.commaDelimitedListToStringArray(signature.substring(firstParen + 1, lastParen));
Class[] parameterTypes = new Class[parameterTypeNames.length];
for (int i = 0; i < parameterTypeNames.length; i++) {
String parameterTypeName = parameterTypeNames[i].trim();
try {
parameterTypes[i] = ClassUtils.forName(parameterTypeName, clazz.getClassLoader());
}
catch (Throwable ex) {
throw new IllegalArgumentException("Invalid method signature: unable to resolve type [" +
parameterTypeName + "] for argument " + i + ". Root cause: " + ex);
}
}
return findMethod(clazz, methodName, parameterTypes);
}
}
/**
* Retrieve the JavaBeans PropertyDescriptor
s of a given class.
* @param clazz the Class to retrieve the PropertyDescriptors for
* @return an array of PropertyDescriptors
for the given class
* @throws BeansException if PropertyDescriptor look fails
*/
public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getBeanInfo().getPropertyDescriptors();
}
/**
* Retrieve the JavaBeans PropertyDescriptors
for the given property.
* @param clazz the Class to retrieve the PropertyDescriptor for
* @param propertyName the name of the property
* @return the corresponding PropertyDescriptor, or null
if none
* @throws BeansException if PropertyDescriptor lookup fails
*/
public static PropertyDescriptor getPropertyDescriptor(Class clazz, String propertyName)
throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptor(propertyName);
}
/**
* Find a JavaBeans PropertyDescriptor
for the given method,
* with the method either being the read method or the write method for
* that bean property.
* @param method the method to find a corresponding PropertyDescriptor for
* @return the corresponding PropertyDescriptor, or null
if none
* @throws BeansException if PropertyDescriptor lookup fails
*/
public static PropertyDescriptor findPropertyForMethod(Method method) throws BeansException {
Assert.notNull(method, "Method must not be null");
PropertyDescriptor[] pds = getPropertyDescriptors(method.getDeclaringClass());
for (int i = 0; i < pds.length; i++) {
PropertyDescriptor pd = pds[i];
if (method.equals(pd.getReadMethod()) || method.equals(pd.getWriteMethod())) {
return pd;
}
}
return null;
}
/**
* Find a JavaBeans PropertyEditor following the 'Editor' suffix convention
* (e.g. "mypackage.MyDomainClass" -> "mypackage.MyDomainClassEditor").
*
Compatible to the standard JavaBeans convention as implemented by
* {@link java.beans.PropertyEditorManager} but isolated from the latter's
* registered default editors for primitive types.
* @param targetType the type to find an editor for
* @return the corresponding editor, or null
if none found
*/
public static PropertyEditor findEditorByConvention(Class targetType) {
if (targetType == null || targetType.isArray() || unknownEditorTypes.containsKey(targetType)) {
return null;
}
ClassLoader cl = targetType.getClassLoader();
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
if (cl == null) {
return null;
}
}
String editorName = targetType.getName() + "Editor";
try {
Class editorClass = cl.loadClass(editorName);
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
logger.warn("Editor class [" + editorName +
"] does not implement [java.beans.PropertyEditor] interface");
unknownEditorTypes.put(targetType, Boolean.TRUE);
return null;
}
return (PropertyEditor) instantiateClass(editorClass);
}
catch (ClassNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("No property editor [" + editorName + "] found for type " +
targetType.getName() + " according to 'Editor' suffix convention");
}
unknownEditorTypes.put(targetType, Boolean.TRUE);
return null;
}
}
/**
* Determine the bean property type for the given property from the
* given classes/interfaces, if possible.
* @param propertyName the name of the bean property
* @param beanClasses the classes to check against
* @return the property type, or Object.class
as fallback
*/
public static Class findPropertyType(String propertyName, Class[] beanClasses) {
if (beanClasses != null) {
for (int i = 0; i < beanClasses.length; i++) {
PropertyDescriptor pd = getPropertyDescriptor(beanClasses[i], propertyName);
if (pd != null) {
return pd.getPropertyType();
}
}
}
return Object.class;
}
/**
* Obtain a new MethodParameter object for the write method of the
* specified property.
* @param pd the PropertyDescriptor for the property
* @return a corresponding MethodParameter object
*/
public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) {
if (pd instanceof GenericTypeAwarePropertyDescriptor) {
return new MethodParameter(
((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodParameter());
}
else {
return new MethodParameter(pd.getWriteMethod(), 0);
}
}
/**
* Check if the given type represents a "simple" property:
* a primitive, a String or other CharSequence, a Number, a Date,
* a URI, a URL, a Locale, a Class, or a corresponding array.
*
Used to determine properties to check for a "simple" dependency-check.
* @param clazz the type to check
* @return whether the given type represents a "simple" property
* @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
* @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
*/
public static boolean isSimpleProperty(Class clazz) {
Assert.notNull(clazz, "Class must not be null");
return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
}
/**
* Check if the given type represents a "simple" value type:
* a primitive, a String or other CharSequence, a Number, a Date,
* a URI, a URL, a Locale or a Class.
* @param clazz the type to check
* @return whether the given type represents a "simple" value type
*/
public static boolean isSimpleValueType(Class clazz) {
return ClassUtils.isPrimitiveOrWrapper(clazz) || CharSequence.class.isAssignableFrom(clazz) ||
Number.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) ||
clazz.equals(URI.class) || clazz.equals(URL.class) ||
clazz.equals(Locale.class) || clazz.equals(Class.class);
}
/**
* Determine if the given target type is assignable from the given value
* type, assuming setting by reflection. Considers primitive wrapper
* classes as assignable to the corresponding primitive types.
* @param targetType the target type
* @param valueType the value type that should be assigned to the target type
* @return if the target type is assignable from the value type
* @deprecated as of Spring 2.0, in favor of ClassUtils.isAssignable
* @see org.springframework.util.ClassUtils#isAssignable(Class, Class)
*/
public static boolean isAssignable(Class targetType, Class valueType) {
return ClassUtils.isAssignable(targetType, valueType);
}
/**
* Determine if the given type is assignable from the given value,
* assuming setting by reflection. Considers primitive wrapper classes
* as assignable to the corresponding primitive types.
* @param type the target type
* @param value the value that should be assigned to the type
* @return if the type is assignable from the value
* @deprecated as of Spring 2.0, in favor of ClassUtils.isAssignableValue
* @see org.springframework.util.ClassUtils#isAssignableValue(Class, Object)
*/
public static boolean isAssignable(Class type, Object value) {
return ClassUtils.isAssignableValue(type, value);
}
/**
* Copy the property values of the given source bean into the target bean.
*
Note: The source and target classes do not have to match or even be derived * from each other, as long as the properties match. Any bean properties that the * source bean exposes but the target bean does not will silently be ignored. *
This is just a convenience method. For more complex transfer needs, * consider using a full BeanWrapper. * @param source the source bean * @param target the target bean * @throws BeansException if the copying failed * @see BeanWrapper */ public static void copyProperties(Object source, Object target) throws BeansException { copyProperties(source, target, null, null); } /** * Copy the property values of the given source bean into the given target bean, * only setting properties defined in the given "editable" class (or interface). *
Note: The source and target classes do not have to match or even be derived * from each other, as long as the properties match. Any bean properties that the * source bean exposes but the target bean does not will silently be ignored. *
This is just a convenience method. For more complex transfer needs, * consider using a full BeanWrapper. * @param source the source bean * @param target the target bean * @param editable the class (or interface) to restrict property setting to * @throws BeansException if the copying failed * @see BeanWrapper */ public static void copyProperties(Object source, Object target, Class editable) throws BeansException { copyProperties(source, target, editable, null); } /** * Copy the property values of the given source bean into the given target bean, * ignoring the given "ignoreProperties". *
Note: The source and target classes do not have to match or even be derived * from each other, as long as the properties match. Any bean properties that the * source bean exposes but the target bean does not will silently be ignored. *
This is just a convenience method. For more complex transfer needs, * consider using a full BeanWrapper. * @param source the source bean * @param target the target bean * @param ignoreProperties array of property names to ignore * @throws BeansException if the copying failed * @see BeanWrapper */ public static void copyProperties(Object source, Object target, String[] ignoreProperties) throws BeansException { copyProperties(source, target, null, ignoreProperties); } /** * Copy the property values of the given source bean into the given target bean. *
Note: The source and target classes do not have to match or even be derived * from each other, as long as the properties match. Any bean properties that the * source bean exposes but the target bean does not will silently be ignored. * @param source the source bean * @param target the target bean * @param editable the class (or interface) to restrict property setting to * @param ignoreProperties array of property names to ignore * @throws BeansException if the copying failed * @see BeanWrapper */ private static void copyProperties(Object source, Object target, Class editable, String[] ignoreProperties) throws BeansException { Assert.notNull(source, "Source must not be null"); Assert.notNull(target, "Target must not be null"); Class actualEditable = target.getClass(); if (editable != null) { if (!editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); } actualEditable = editable; } PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); List ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null; for (int i = 0; i < targetPds.length; i++) { PropertyDescriptor targetPd = targetPds[i]; if (targetPd.getWriteMethod() != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) { PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null && sourcePd.getReadMethod() != null) { try { Method readMethod = sourcePd.getReadMethod(); if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } Object value = readMethod.invoke(source, new Object[0]); Method writeMethod = targetPd.getWriteMethod(); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } writeMethod.invoke(target, new Object[] {value}); } catch (Throwable ex) { throw new FatalBeanException("Could not copy properties from source to target", ex); } } } } } }