Open Source Repository

Home /spring/spring-beans-3.0.5 | Repository Home



org/springframework/beans/BeanUtils.java
/*
 * Copyright 2002-2010 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.
 *
 <p>Mainly for use within the framework, but to some degree also
 * useful for application classes.
 *
 @author Rod Johnson
 @author Juergen Hoeller
 @author Rob Harrop
 @author Sam Brannen
 */
public abstract class BeanUtils {

  private static final Log logger = LogFactory.getLog(BeanUtils.class);

  private static final Map<Class<?>, Boolean> unknownEditorTypes =
      Collections.synchronizedMap(new WeakHashMap<Class<?>, Boolean>());


  /**
   * 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.
   @param clazz class to instantiate
   @return the new instance
   @throws BeanInstantiationException if the bean cannot be instantiated
   */
  public static <T> T instantiate(Class<T> clazzthrows BeanInstantiationException {
    Assert.notNull(clazz, "Class must not be null");
    if (clazz.isInterface()) {
      throw new BeanInstantiationException(clazz, "Specified class is an interface");
    }
    try {
      return clazz.newInstance();
    }
    catch (InstantiationException ex) {
      throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
    }
    catch (IllegalAccessException ex) {
      throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
    }
  }

  /**
   * 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.
   <p>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 <T> T instantiateClass(Class<T> clazzthrows 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());
    }
    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.
   <p>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 <T> T instantiateClass(Constructor<T> ctor, Object... argsthrows 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(),
          "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.
   <p>Checks <code>Class.getMethod</code> first, falling back to
   <code>findDeclaredMethod</code>. 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 <code>null</code> 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.
   <p>Checks <code>Class.getDeclaredMethod</code>, 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 <code>null</code> 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.
   <p>Checks <code>Class.getMethods</code> first, falling back to
   <code>findDeclaredMethodWithMinimalParameters</code>. This allows for finding 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 <code>null</code> 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 = findMethodWithMinimalParameters(clazz.getMethods(), methodName);
    if (targetMethod == null) {
      targetMethod = 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.
   <p>Checks <code>Class.getDeclaredMethods</code>, 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 <code>null</code> 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 = findMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName);
    if (targetMethod == null && clazz.getSuperclass() != null) {
      targetMethod = 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 <code>null</code> 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
   */
  public static Method findMethodWithMinimalParameters(Method[] methods, String methodName)
      throws IllegalArgumentException {

    Method targetMethod = null;
    int numMethodsFoundWithCurrentMinimumArgs = 0;
    for (Method method : methods) {
      if (method.getName().equals(methodName)) {
        int numParams = method.getParameterTypes().length;
        if (targetMethod == null || numParams < targetMethod.getParameterTypes().length) {
          targetMethod = method;
          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 <code>methodName[([arg_list])]</code>,
   * where <code>arg_list</code> is an optional, comma-separated list of fully-qualified
   * type names, and attempts to resolve that signature against the supplied <code>Class</code>.
   <p>When not supplying an argument list (<code>methodName</code>) 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.
   <p>Note then that <code>methodName</code> and <code>methodName()</code> are <strong>not</strong>
   * resolved in the same way. The signature <code>methodName</code> means the method called
   <code>methodName</code> with the least number of arguments, whereas <code>methodName()</code>
   * means the method called <code>methodName</code> with exactly 0 arguments.
   <p>If no method can be found, then <code>null</code> 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 > -&& lastParen == -1) {
      throw new IllegalArgumentException("Invalid method signature '" + signature +
          "': expected closing ')' for args list");
    }
    else if (lastParen > -&& firstParen == -1) {
      throw new IllegalArgumentException("Invalid method signature '" + signature +
          "': expected opening '(' for args list");
    }
    else if (firstParen == -&& 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 <code>PropertyDescriptor</code>s of a given class.
   @param clazz the Class to retrieve the PropertyDescriptors for
   @return an array of <code>PropertyDescriptors</code> for the given class
   @throws BeansException if PropertyDescriptor look fails
   */
  public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazzthrows BeansException {
    CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
    return cr.getPropertyDescriptors();
  }

  /**
   * Retrieve the JavaBeans <code>PropertyDescriptors</code> 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 <code>null</code> 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 <code>PropertyDescriptor</code> 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 <code>null</code> if none
   @throws BeansException if PropertyDescriptor lookup fails
   */
  public static PropertyDescriptor findPropertyForMethod(Method methodthrows BeansException {
    Assert.notNull(method, "Method must not be null");
    PropertyDescriptor[] pds = getPropertyDescriptors(method.getDeclaringClass());
    for (PropertyDescriptor pd : pds) {
      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").
   <p>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 <code>null</code> 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) {
      try {
        cl = ClassLoader.getSystemClassLoader();
        if (cl == null) {
          return null;
        }
      }
      catch (Throwable ex) {
        // e.g. AccessControlException on Google App Engine
        if (logger.isDebugEnabled()) {
          logger.debug("Could not access system ClassLoader: " + ex);
        }
        return null;
      }
    }
    String editorName = targetType.getName() "Editor";
    try {
      Class<?> editorClass = cl.loadClass(editorName);
      if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
        if (logger.isWarnEnabled()) {
          logger.warn("Editor class [" + editorName +
              "] does not implement [java.beans.PropertyEditor] interface");
        }
        unknownEditorTypes.put(targetType, Boolean.TRUE);
        return null;
      }
      return (PropertyEditorinstantiateClass(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 <code>Object.class</code> as fallback
   */
  public static Class<?> findPropertyType(String propertyName, Class<?>[] beanClasses) {
    if (beanClasses != null) {
      for (Class<?> beanClass : beanClasses) {
        PropertyDescriptor pd = getPropertyDescriptor(beanClass, 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(
          ((GenericTypeAwarePropertyDescriptorpd).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.
   <p>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|| clazz.isEnum() ||
        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);
  }


  /**
   * Copy the property values of the given source bean into the target bean.
   <p>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.
   <p>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 targetthrows 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).
   <p>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.
   <p>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".
   <p>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.
   <p>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.
   <p>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<String> ignoreList = (ignoreProperties != null? Arrays.asList(ignorePropertiesnull;

    for (PropertyDescriptor targetPd : targetPds) {
      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);
            Method writeMethod = targetPd.getWriteMethod();
            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
              writeMethod.setAccessible(true);
            }
            writeMethod.invoke(target, value);
          }
          catch (Throwable ex) {
            throw new FatalBeanException("Could not copy properties from source to target", ex);
          }
        }
      }
    }
  }

}