All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rapiddweller.common.BeanUtil Maven / Gradle / Ivy

Go to download

'rapiddweller Common' is an open source Java library forked from Databene Commons by Volker Bergmann. It provides extensions to the Java core library by utility classes, abstract concepts and concrete implementations.

There is a newer version: 2.0.1-jdk-11
Show newest version
/*
 * Copyright (C) 2004-2015 Volker Bergmann ([email protected]).
 * All rights reserved.
 *
 * 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 com.rapiddweller.common;

import com.rapiddweller.common.converter.AnyConverter;
import com.rapiddweller.common.converter.ConverterManager;
import com.rapiddweller.common.converter.ToStringConverter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Bundles reflection and introspection related operations.
 * Created: 01.07.2006 08:44:33
 *
 * @author Volker Bergmann
 * @since 0.1
 */
public final class BeanUtil {

  private static final Logger logger = LogManager.getLogger(BeanUtil.class);

  private static final HashSet NON_CLASS_NAMES = new HashSet<>(100);

  private static final Escalator escalator = new LoggerEscalator();

  // (static) attributes ---------------------------------------------------------------------------------------------

  private static final Map propertyDescriptors = new HashMap<>();

  /**
   * List of simple Java types.
   */
  private static final Class[] simpleTypes = {
      String.class,
      long.class, Long.class,
      int.class, Integer.class,
      short.class, Short.class,
      byte.class, Byte.class,
      boolean.class, Boolean.class,
      char.class, Character.class,
      float.class, Float.class,
      double.class, Double.class,
      BigDecimal.class, BigInteger.class
  };

  private static final Class[] integralNumberTypes = {
      long.class, Long.class,
      int.class, Integer.class,
      short.class, Short.class,
      byte.class, Byte.class,
      BigInteger.class
  };

  private static final Class[] decimalNumberTypes = {
      float.class, Float.class,
      double.class, Double.class,
      BigDecimal.class
  };

  private static final PrimitiveTypeMapping[] primitiveNumberTypes = {
      new PrimitiveTypeMapping(long.class, Long.class),
      new PrimitiveTypeMapping(int.class, Integer.class),
      new PrimitiveTypeMapping(short.class, Short.class),
      new PrimitiveTypeMapping(byte.class, Byte.class),
      new PrimitiveTypeMapping(float.class, Float.class),
      new PrimitiveTypeMapping(double.class, Double.class)
  };

  private static final PrimitiveTypeMapping[] primitiveNonNumberTypes = {
      new PrimitiveTypeMapping(boolean.class, Boolean.class),
      new PrimitiveTypeMapping(char.class, Character.class),
  };

  /**
   * Map of integral Java number types
   */
  private static final Map> integralNumberTypeMap;

  /**
   * Map of decimal Java number types
   */
  private static final Map> decimalNumberTypeMap;

  /**
   * Map of simple Java types
   */
  private static final Map> simpleTypeMap;

  /**
   * Map of primitive Java types
   */
  private static final Map> primitiveTypeMap;

  /**
   * Map of primitive Java number types
   */
  private static final Map> primitiveNumberTypeMap;

  // initialization --------------------------------------------------------------------------------------------------

  static {
    simpleTypeMap = map(simpleTypes);
    integralNumberTypeMap = map(integralNumberTypes);
    decimalNumberTypeMap = map(decimalNumberTypes);
    primitiveNumberTypeMap = new HashMap<>();
    primitiveTypeMap = new HashMap<>();
    for (PrimitiveTypeMapping mapping : primitiveNumberTypes) {
      primitiveNumberTypeMap.put(mapping.primitiveType.getName(), mapping.wrapperType);
      primitiveTypeMap.put(mapping.primitiveType.getName(), mapping.wrapperType);
    }
    for (PrimitiveTypeMapping mapping : primitiveNonNumberTypes) {
      primitiveTypeMap.put(mapping.primitiveType.getName(), mapping.wrapperType);
    }
  }

  private static Map> map(Class[] array) {
    Map> result = new HashMap<>();
    for (Class type : array) {
      result.put(type.getName(), type);
    }
    return result;
  }

  /**
   * Prevents instantiation of a BeanUtil object.
   */
  private BeanUtil() {
  }

  // type info methods -----------------------------------------------------------------------------------------------

  /**
   * Common super type class.
   *
   * @param objects the objects
   * @return the class
   */
  public static Class commonSuperType(Collection objects) {
    Iterator iterator = objects.iterator();
    if (!iterator.hasNext()) {
      return null;
    }
    Class result = null;
    while (iterator.hasNext()) {
      Object candidate = iterator.next();
      if (candidate != null) {
        Class candidateClass = candidate.getClass();
        if (result == null) {
          result = candidateClass;
        } else if (candidateClass != result && candidateClass.isAssignableFrom(result)) {
          result = candidateClass;
        }
      }
    }
    return result;
  }

  /**
   * Common sub type class.
   *
   * @param objects the objects
   * @return the class
   */
  public static Class commonSubType(Collection objects) {
    Iterator iterator = objects.iterator();
    if (!iterator.hasNext()) {
      return null;
    }
    Class result = null;
    while (iterator.hasNext()) {
      Object candidate = iterator.next();
      if (candidate != null) {
        Class candidateClass = candidate.getClass();
        if (result == null) {
          result = candidateClass;
        } else if (candidateClass != result && result.isAssignableFrom(candidateClass)) {
          result = candidateClass;
        }
      }
    }
    return result;
  }

  /**
   * Tells if the provided class name is the name of a simple Java type
   *
   * @param className the name to check
   * @return true if it is a simple type, else false
   */
  public static boolean isSimpleType(String className) {
    return simpleTypeMap.containsKey(className);
  }

  /**
   * Is primitive type boolean.
   *
   * @param className the class name
   * @return the boolean
   */
  public static boolean isPrimitiveType(String className) {
    return primitiveTypeMap.containsKey(className);
  }

  /**
   * Is primitive number type boolean.
   *
   * @param className the class name
   * @return the boolean
   */
  public static boolean isPrimitiveNumberType(String className) {
    return primitiveNumberTypeMap.containsKey(className);
  }

  /**
   * Is number type boolean.
   *
   * @param type the type
   * @return the boolean
   */
  public static boolean isNumberType(Class type) {
    return (isIntegralNumberType(type) || isDecimalNumberType(type));
  }

  /**
   * Is integral number type boolean.
   *
   * @param type the type
   * @return the boolean
   */
  public static boolean isIntegralNumberType(Class type) {
    return isIntegralNumberType(type.getName());
  }

  /**
   * Is integral number type boolean.
   *
   * @param className the class name
   * @return the boolean
   */
  public static boolean isIntegralNumberType(String className) {
    return integralNumberTypeMap.containsKey(className);
  }

  /**
   * Is decimal number type boolean.
   *
   * @param type the type
   * @return the boolean
   */
  public static boolean isDecimalNumberType(Class type) {
    return isDecimalNumberType(type.getName());
  }

  /**
   * Is decimal number type boolean.
   *
   * @param className the class name
   * @return the boolean
   */
  public static boolean isDecimalNumberType(String className) {
    return decimalNumberTypeMap.containsKey(className);
  }

  /**
   * Gets wrapper.
   *
   * @param primitiveClassName the primitive class name
   * @return the wrapper
   */
  public static Class getWrapper(String primitiveClassName) {
    return primitiveTypeMap.get(primitiveClassName);
  }

  /**
   * Tells if the specified class is a collection type.
   *
   * @param type the class to check
   * @return true if the class is a collection type, false otherwise
   */
  public static boolean isCollectionType(Class type) {
    return Collection.class.isAssignableFrom(type);
  }

  /**
   * Get types class [ ].
   *
   * @param objects the objects
   * @return the class [ ]
   */
  public static Class[] getTypes(Object... objects) {
    Class[] result = null;
    if (objects != null) {
      result = new Class[objects.length];
      for (int i = 0; i < objects.length; i++) {
        result[i] = (objects[i] != null ? objects[i].getClass() : null);
      }
    }
    return result;
  }

  // field operations ------------------------------------------------------------------------------------------------

  /**
   * Returns an object's attribute value
   *
   * @param obj           the object to query
   * @param attributeName the name of the attribute
   * @return the attribute value
   */
  public static Object getAttributeValue(Object obj, String attributeName) {
    if (obj == null) {
      throw new IllegalArgumentException("Object may not be null");
    }
    Field field = getField(obj.getClass(), attributeName);
    try {
      return field.get(obj);
    } catch (IllegalAccessException e) {
      throw ExceptionMapper.configurationException(e, field);
    }
  }

  /**
   * Sets an attribute value of an object.
   *
   * @param obj       the object to modify
   * @param fieldName the name of the attribute to set
   * @param value     the value to assign to the field
   */
  public static void setAttributeValue(Object obj, String fieldName, Object value) {
    Field field = getField(obj.getClass(), fieldName);
    setAttributeValue(obj, field, value);
  }

  /**
   * Returns a class' static attribute value
   *
   * @param objectType    the class to query
   * @param attributeName the name of the attribute
   * @return the attribute value
   */
  public static Object getStaticAttributeValue(Class objectType, String attributeName) {
    Field field = getField(objectType, attributeName);
    try {
      return field.get(null);
    } catch (IllegalAccessException e) {
      throw ExceptionMapper.configurationException(e, field);
    }
  }

  /**
   * Sets a static attribute value of a class.
   *
   * @param objectType the class to modify
   * @param fieldName  the name of the attribute to set
   * @param value      the value to assign to the field
   */
  public static void setStaticAttributeValue(Class objectType, String fieldName, Object value) {
    Field field = getField(objectType, fieldName);
    setAttributeValue(null, field, value);
  }

  /**
   * Sets an attribute value of an object.
   *
   * @param obj   the object to modify
   * @param field the attribute to set
   * @param value the value to assign to the field
   */
  public static void setAttributeValue(Object obj, Field field, Object value) {
    try {
      field.set(obj, value);
    } catch (IllegalAccessException e) {
      throw ExceptionMapper.configurationException(e, field);
    }
  }

  /**
   * Returns the generic type information of an attribute.
   *
   * @param field the field representation of the attribute.
   * @return an array of types that are used to parameterize the attribute.
   */
  public static Class[] getGenericTypes(Field field) {
    Type genericFieldType = field.getGenericType();
    if (!(genericFieldType instanceof ParameterizedType)) {
      return null; // type is not generic
    }
    ParameterizedType pType = (ParameterizedType) genericFieldType;
    Type[] args = pType.getActualTypeArguments();
    Class[] types = new Class[args.length];
    System.arraycopy(args, 0, types, 0, args.length);
    return types;
  }

  // instantiation ---------------------------------------------------------------------------------------------------

  /**
   * For name class.
   *
   * @param   the type parameter
   * @param name the name
   * @return the class
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public static  Class forName(String name) {
    Assert.notNull(name, "class name");
    Class type = simpleTypeMap.get(name);
    if (type != null) {
      return type;
    } else {
      try {
        return (Class) getContextClassLoader().loadClass(name);
      } catch (ClassNotFoundException | NullPointerException e) {
        throw ExceptionMapper.configurationException(e, name);
      } // this is raised by the Eclipse BundleLoader if it does not find the class

    }
  }

  /**
   * Gets context class loader.
   *
   * @return the context class loader
   */
  public static ClassLoader getContextClassLoader() {
    ClassLoader result = Thread.currentThread().getContextClassLoader();
    if (result == null) {
      result = BeanUtil.class.getClassLoader();
    }
    return result;
  }

  /**
   * Create jar class loader class loader.
   *
   * @param jarFile the jar file
   * @return the class loader
   */
  public static ClassLoader createJarClassLoader(File jarFile) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if (jarFile != null) {
      try {
        classLoader = new URLClassLoader(new URL[] {jarFile.toURI().toURL()}, classLoader);
      } catch (MalformedURLException e) {
        throw new RuntimeException("Unexpected error", e);
      }
    }
    return classLoader;
  }

  /**
   * Create directory class loader class loader.
   *
   * @param directory the directory
   * @return the class loader
   */
  public static ClassLoader createDirectoryClassLoader(File directory) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    try {
      classLoader = new URLClassLoader(new URL[] {directory.toURI().toURL()}, classLoader);
    } catch (MalformedURLException e) {
      throw new RuntimeException("Unexpected error", e);
    }
    return classLoader;
  }

  /**
   * Run with jar class loader.
   *
   * @param jarFile the jar file
   * @param action  the action
   */
  public static void runWithJarClassLoader(File jarFile, Runnable action) {
    Thread currentThread = Thread.currentThread();
    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
    try {
      currentThread.setContextClassLoader(createJarClassLoader(jarFile));
      action.run();
    } finally {
      currentThread.setContextClassLoader(contextClassLoader);
    }
  }

  /**
   * Call with jar class loader t.
   *
   * @param      the type parameter
   * @param jarFile the jar file
   * @param action  the action
   * @return the t
   * @throws Exception the exception
   */
  public static  T callWithJarClassLoader(File jarFile, Callable action) throws Exception {
    Thread currentThread = Thread.currentThread();
    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
    try {
      currentThread.setContextClassLoader(createJarClassLoader(jarFile));
      return action.call();
    } finally {
      currentThread.setContextClassLoader(contextClassLoader);
    }
  }

  /**
   * Instantiates a class by the default constructor.
   *
   * @param className the name of the class to instantiate
   * @return an instance of the class
   */
  public static Object newInstance(String className) {
    Class type = BeanUtil.forName(className);
    return newInstanceFromDefaultConstructor(type);
  }

  /**
   * New instance t.
   *
   * @param   the type parameter
   * @param type the type
   * @return the t
   */
  public static  T newInstance(Class type) {
    return newInstance(type, true, null);
  }

  /**
   * Creates an object of the specified type.
   *
   * @param         the class of the object to instantiate
   * @param type       the class to instantiate
   * @param parameters the constructor parameters
   * @return an object of the specified class
   */
  public static  T newInstance(Class type, Object[] parameters) {
    return newInstance(type, true, parameters);
  }

  /**
   * New instance t.
   *
   * @param         the type parameter
   * @param type       the type
   * @param strict     the strict
   * @param parameters the parameters
   * @return the t
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public static  T newInstance(Class type, boolean strict, Object[] parameters) {
    if (parameters == null || parameters.length == 0) {
      return newInstanceFromDefaultConstructor(type);
    }
    Constructor constructorToUse = null;
    try {
      Constructor[] constructors = (Constructor[]) type.getConstructors();
      List> candidates = new ArrayList<>(constructors.length);
      int paramCount = parameters.length;
      for (Constructor constructor : constructors) {
        if (constructor.getParameterTypes().length == paramCount) {
          candidates.add(constructor);
        }
      }
      if (candidates.size() == 1) {
        constructorToUse = candidates.get(0);
      } else if (candidates.size() == 0) {
        throw new ConfigurationError("No constructor with " + paramCount + " parameters found for " + type);
      } else {
        // there are several candidates - find the first one with matching types
        Class[] paramTypes = new Class[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
          paramTypes[i] = parameters[i].getClass();
        }
        for (Constructor c : type.getConstructors()) {
          if (typesMatch(c.getParameterTypes(), paramTypes)) {
            constructorToUse = c;
            break;
          }
        }
        // there is no ideal match
        if (constructorToUse == null) {
          if (strict) {
            throw new NoSuchMethodException("No appropriate constructor found: " + type + '(' + ArrayFormat.format(", ", paramTypes) + ')');
          }
          Exception mostRecentException = null;
          for (Constructor candidate : candidates) {
            try {
              return newInstance(candidate, strict, parameters);
            } catch (Exception e) {
              mostRecentException = e;
              logger.warn("Exception in constructor call: " + candidate, e);
              continue; // ignore exception and try next constructor
            }
          }
          // no constructor could be called without exception
          String errMsg = (mostRecentException != null ?
              "None of these constructors could be called without exception: " + candidates + ", latest exception: " + mostRecentException :
              type + " has no appropriate constructor for the arguments " + ArrayFormat.format(", ", parameters));
          throw new ConfigurationError(errMsg);
        }
      }
      if (!strict) {
        parameters = convertArray(parameters, constructorToUse.getParameterTypes());
      }
      return newInstance(constructorToUse, parameters);
    } catch (SecurityException e) {
      throw ExceptionMapper.configurationException(e, constructorToUse);
    } catch (NoSuchMethodException e) {
      throw ExceptionMapper.configurationException(e, type);
    }
  }

  /**
   * Creates a new instance of a class.
   *
   * @param          the class of the object to instantiate
   * @param constructor the constructor to invoke
   * @param params      the parameters to provide to the constructor
   * @return a new instance of the class
   */
  public static  T newInstance(Constructor constructor, Object... params) {
    return newInstance(constructor, true, params);
  }

  /**
   * New instance t.
   *
   * @param          the type parameter
   * @param constructor the constructor
   * @param strict      the strict
   * @param parameters  the parameters
   * @return the t
   */
  public static  T newInstance(Constructor constructor, boolean strict, Object... parameters) {
    if (!strict) {
      parameters = convertArray(parameters, constructor.getParameterTypes());
    }
    Class type = constructor.getDeclaringClass();
    if (deprecated(type)) {
      escalator.escalate("Instantiating a deprecated class: " + type.getName(), BeanUtil.class, null);
    }
    try {
      return constructor.newInstance(parameters);
    } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
      throw ExceptionMapper.configurationException(e, type);
    }
  }

  /**
   * Clone t.
   *
   * @param     the type parameter
   * @param object the object
   * @return the t
   */
  @SuppressWarnings("unchecked")
  public static  T clone(T object) {
    try {
      Method cloneMethod = object.getClass().getMethod("clone");
      return (T) cloneMethod.invoke(object);
    } catch (NoSuchMethodException | IllegalAccessException e) {
      throw new RuntimeException("Unexpected exception", e); // This is not supposed to happen
    } catch (InvocationTargetException e) {
      throw new RuntimeException("Execption occured in clone() method", e);
    }
  }

  /**
   * Clone all t [ ].
   *
   * @param    the type parameter
   * @param input the input
   * @return the t [ ]
   */
  public static  T[] cloneAll(T[] input) {
    T[] output = ArrayUtil.newInstance(ArrayUtil.componentType(input), input.length);
    for (int i = 0; i < input.length; i++) {
      output[i] = clone(input[i]);
    }
    return output;
  }


  // method operations -----------------------------------------------------------------------------------------------

  /**
   * Finds a method by reflection. This iterates all methods of the class, comparing names and parameter types.
   * Unlike the method Class.getMethod(String, Class ...), this method is able to match primitive and wrapper types.
   * If no appropriate method is found, a ConfigurationError is raised.
   *
   * @param type       the class that holds the method
   * @param methodName the name of the method
   * @param paramTypes the parameter types of the method
   * @return a method with matching names and parameters
   */
  public static Method getMethod(Class type, String methodName, Class... paramTypes) {
    Method method = findMethod(type, methodName, paramTypes);
    if (method == null) {
      throw new ConfigurationError("method not found in class " + type.getName() + ": " + methodName
          + '(' + ArrayFormat.format(paramTypes) + ')');
    }
    return method;
  }

  /**
   * Finds a method by reflection. This iterates all methods of the class, comparing names and parameter types.
   * Unlike the method Class.getMethod(String, Class ...), this method is able to match primitive and wrapper types.
   * If no appropriate method is found, 'null' is returned
   *
   * @param type       the class that holds the method
   * @param methodName the name of the method
   * @param paramTypes the parameter types of the method
   * @return a method with matching names and parameters
   */
  public static Method findMethod(Class type, String methodName, Class... paramTypes) {
    Method result = null;
    for (Method method : type.getMethods()) {
      if (!methodName.equals(method.getName())) {
        continue;
      }
      if (typesMatch(paramTypes, method.getParameterTypes())) {
        result = method;
        if ((ArrayUtil.isEmpty(paramTypes) && ArrayUtil.isEmpty(method.getParameterTypes())) ||
            paramTypes.length == method.getParameterTypes().length) {
          return method; // optimal match - return it immediately
        } else {
          result = method; // sub optimal match - store it, but keep on searching for better matches
        }
      }
    }
    return result;
  }

  /**
   * Find methods by name method [ ].
   *
   * @param type       the type
   * @param methodName the method name
   * @return the method [ ]
   */
  public static Method[] findMethodsByName(Class type, String methodName) {
    ArrayBuilder builder = new ArrayBuilder<>(Method.class);
    for (Method method : type.getMethods()) {
      if (methodName.equals(method.getName())) {
        builder.add(method);
      }
    }
    return builder.toArray();
  }

  /**
   * Find constructor constructor.
   *
   * @param         the type parameter
   * @param type       the type
   * @param paramTypes the param types
   * @return the constructor
   */
  @SuppressWarnings("unchecked")
  public static  Constructor findConstructor(Class type, Class... paramTypes) {
    Constructor[] ctors = (Constructor[]) type.getConstructors();
    for (Constructor ctor : ctors) {
      if (typesMatch(paramTypes, ctor.getParameterTypes())) {
        return ctor;
      }
    }
    return null;
  }

  /**
   * Invokes a method on a bean.
   *
   * @param target     the object on which to invoke the mthod
   * @param methodName the name of the method
   * @param args       the arguments to provide to the method
   * @return the invoked method's return value.
   */
  public static Object invoke(Object target, String methodName, Object... args) {
    return invoke(true, target, methodName, args);
  }

  /**
   * Invoke object.
   *
   * @param strict     the strict
   * @param target     the target
   * @param methodName the method name
   * @param args       the args
   * @return the object
   */
  @SuppressWarnings("rawtypes")
  public static Object invoke(boolean strict, Object target, String methodName, Object... args) {
    if (target == null) {
      throw new IllegalArgumentException("target is null");
    }
    Class[] argTypes = getTypes(args);
    Method method;
    if (target instanceof Class) {
      method = getMethod((Class) target, methodName, argTypes);
    } else {
      method = getMethod(target.getClass(), methodName, argTypes);
    }
    return invoke(target, method, strict, args);
  }

  /**
   * Invoke static object.
   *
   * @param targetClass the target class
   * @param methodName  the method name
   * @param args        the args
   * @return the object
   */
  public static Object invokeStatic(Class targetClass, String methodName, Object... args) {
    return invokeStatic(targetClass, methodName, true, args);
  }

  /**
   * Invoke static object.
   *
   * @param targetClass the target class
   * @param methodName  the method name
   * @param strict      the strict
   * @param args        the args
   * @return the object
   */
  public static Object invokeStatic(Class targetClass, String methodName, boolean strict, Object... args) {
    if (targetClass == null) {
      throw new IllegalArgumentException("target is null");
    }
    Class[] argClasses = new Class[args.length];
    for (int i = 0; i < args.length; i++) {
      argClasses[i] = (args[i] != null ? args[i].getClass() : null);
    }
    Method method = getMethod(targetClass, methodName, argClasses);
    return invoke(null, method, strict, args);
  }

  /**
   * Invokes a method on a bean.
   *
   * @param target the object on which to invoke the mthod
   * @param method the method to invoke
   * @param args   the arguments to provide to the method
   * @return the invoked method's return value.
   */
  public static Object invoke(Object target, Method method, Object[] args) {
    return invoke(target, method, true, args);
  }

  /**
   * Invoke object.
   *
   * @param target the target
   * @param method the method
   * @param strict the strict
   * @param args   the args
   * @return the object
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  public static Object invoke(Object target, Method method, boolean strict, Object[] args) {
    try {
      Object[] params;
      Class[] paramTypes = method.getParameterTypes();
      if (paramTypes.length == 0) {
        params = null;
      } else if (args.length == paramTypes.length) { // exact match
        // map one to one
        if (strict) {
          params = args;
        } else {
          params = new Object[paramTypes.length];
          for (int i = 0; i < paramTypes.length; i++) {
            Object arg = args[i];
            if (arg == null) {
              params[i] = null;
            } else {
              Converter converter = ConverterManager.getInstance().createConverter(arg.getClass(), paramTypes[i]);
              params[i] = converter.convert(arg);
            }
          }
        }
      } else if (args.length > paramTypes.length) { // varargs params?
        // map varargs
        params = new Object[paramTypes.length];
        for (int i = 0; i < paramTypes.length - 1; i++) {
          params[i] = (strict ? args[i] : AnyConverter.convert(args[i], paramTypes[i]));
        }
        Class varargsComponentType = paramTypes[paramTypes.length - 1].getComponentType();
        Object varargs = Array.newInstance(varargsComponentType, args.length - paramTypes.length + 1);
        for (int i = 0; i < args.length - paramTypes.length + 1; i++) {
          Object param = args[paramTypes.length - 1 + i];
          if (strict) {
            param = AnyConverter.convert(param, varargsComponentType);
          }
          Array.set(varargs, i, param);
        }
        params[params.length - 1] = varargs;
      } else if (args.length == paramTypes.length - 1) { // varargs of length 0
        params = new Object[paramTypes.length];
        for (int i = 0; i < paramTypes.length - 1; i++) {
          params[i] = (strict ? args[i] : AnyConverter.convert(args[i], paramTypes[i]));
        }
        Class varargsComponentType = paramTypes[paramTypes.length - 1].getComponentType();
        Object varargs = Array.newInstance(varargsComponentType, 0);
        params[params.length - 1] = varargs;
      } else {
        throw new RuntimeException("Method " + method.getName() + " requires " + paramTypes.length + " params, " +
            "but only " + args.length + " were provided. ");
      }
      return method.invoke(target, params);
    } catch (IllegalAccessException | InvocationTargetException e) {
      throw ExceptionMapper.configurationException(e, method);
    }
  }

  /**
   * Types match boolean.
   *
   * @param usedTypes     the used types
   * @param expectedTypes the expected types
   * @return the boolean
   */
  public static boolean typesMatch(Class[] usedTypes, Class[] expectedTypes) {
    // expectedTypes is empty
    if (ArrayUtil.isEmpty(expectedTypes)) {
      return ArrayUtil.isEmpty(usedTypes);
    }
    Class lastExpectedType = ArrayUtil.lastElementOf(expectedTypes);
    if (lastExpectedType.isArray()) {
      // process varargs parameter
      if (usedTypes.length < expectedTypes.length - 1) {
        return false; // fault
      }
      if (usedTypes.length == expectedTypes.length - 1) {
        return typesMatch(usedTypes, ArrayUtil.copyOfRange(expectedTypes, 0, usedTypes.length)); // empty varargs
      }
      // check if all used types match the varargs type
      if (usedTypes.length >= expectedTypes.length) {
        Class componentType = lastExpectedType.getComponentType();
        for (int i = expectedTypes.length - 1; i < usedTypes.length; i++) {
          Class foundType = usedTypes[i];
          if (!typeMatches(foundType, componentType)) {
            return false;
          }
        }
        return true;
      }
    }
    if (usedTypes.length != expectedTypes.length) {
      return false;
    }
    if (expectedTypes.length == 0 && usedTypes.length == 0) {
      return true;
    }
    for (int i = 0; i < usedTypes.length; i++) {
      Class expectedType = expectedTypes[i];
      Class foundType = usedTypes[i];
      if (!typeMatches(foundType, expectedType)) {
        return false;
      }
    }
    return true;
  }

  // JavaBean operations ---------------------------------------------------------------------------------------------

  /**
   * Returns the bean property descriptor of an attribute
   *
   * @param beanClass    the class that holds the attribute
   * @param propertyName the name of the property
   * @return the attribute's property descriptor
   */
  public static PropertyDescriptor getPropertyDescriptor(Class beanClass, String propertyName) {
    if (beanClass == null) {
      throw new IllegalArgumentException("beanClass is null");
    }
    String propertyId = beanClass.getName() + '#' + propertyName;
    PropertyDescriptor result = propertyDescriptors.get(propertyId);
    if (result != null) {
      return result;
    }
    // descriptor is new

    int separatorIndex = propertyName.indexOf('.');
    if (separatorIndex >= 0) {
      String localProperty = propertyName.substring(0, separatorIndex);
      String remoteProperty = propertyName.substring(separatorIndex + 1);
      PropertyDescriptor localPropertyDescriptor = getPropertyDescriptor(beanClass, localProperty);
      if (localPropertyDescriptor == null) {
        return null;
      }
      Class localPropertyType = localPropertyDescriptor.getPropertyType();
      result = getPropertyDescriptor(localPropertyType, remoteProperty);
    } else {
      try {
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor descriptor : descriptors) {
          String name = descriptor.getName();
          if (name.equals(propertyName)) {
            result = descriptor;
            break;
          }
        }
      } catch (IntrospectionException e) {
        throw ExceptionMapper.configurationException(e, propertyName);
      }
    }
    propertyDescriptors.put(propertyId, result);
    return result;
  }

  /**
   * Gets property descriptor.
   *
   * @param type         the type
   * @param propertyName the property name
   * @param required     the required
   * @return the property descriptor
   */
  public static PropertyDescriptor getPropertyDescriptor(
      Class type, String propertyName, boolean required) {
    PropertyDescriptor descriptor = getPropertyDescriptor(type, propertyName);
    if (required && descriptor == null) {
      throw new UnsupportedOperationException(type.getName() + " does not have a property " + propertyName);
    }
    return descriptor;
  }

  /**
   * Has property boolean.
   *
   * @param beanClass    the bean class
   * @param propertyName the property name
   * @return the boolean
   */
  public static boolean hasProperty(Class beanClass, String propertyName) {
    return (getPropertyDescriptor(beanClass, propertyName) != null);
  }

  /**
   * Has writeable property boolean.
   *
   * @param beanClass    the bean class
   * @param propertyName the property name
   * @return the boolean
   */
  public static boolean hasWriteableProperty(Class beanClass, String propertyName) {
    PropertyDescriptor descriptor = getPropertyDescriptor(beanClass, propertyName);
    return (descriptor != null && descriptor.getWriteMethod() != null);
  }

  /**
   * returns the name of a property read method.
   *
   * @param propertyName the name of the property
   * @param propertyType the type of the property
   * @return the name of the property read method
   */
  public static String readMethodName(String propertyName, Class propertyType) {
    if (boolean.class.equals(propertyType) || Boolean.class.equals(propertyType)) {
      return "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
    } else {
      return "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
    }
  }

  /**
   * returns the name of a property write method.
   *
   * @param propertyName the name of the property
   * @return the name of the property write method
   */
  public static String writeMethodName(String propertyName) {
    return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
  }

  /**
   * Finds all property descriptors of a bean class
   *
   * @param type the class to check
   * @return all found property descriptors
   */
  public static PropertyDescriptor[] getPropertyDescriptors(Class type) {
    try {
      return Introspector.getBeanInfo(type).getPropertyDescriptors();
    } catch (IntrospectionException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Copies a Map's values to the properties of a JavaBean,
   * using the Map entries' key values as bean property names.
   *
   * @param sourceBean the bean from which to read the properties
   * @param targetBean the bean on which to set the properties
   */
  public static void copyPropertyValues(Object sourceBean, Object targetBean) {
    PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(sourceBean.getClass());
    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
      String name = propertyDescriptor.getName();
      if (propertyDescriptor.getReadMethod() != null) {
        Object value = getPropertyValue(sourceBean, propertyDescriptor);
        setPropertyValue(targetBean, name, value, false, true);
      }
    }
  }

  /**
   * Sets property values.
   *
   * @param bean       the bean
   * @param properties the properties
   */
  public static void setPropertyValues(Object bean, Map properties) {
    Class beanClass = bean.getClass();
    Method writeMethod = null;
    try {
      BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
      PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
      for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
        String name = propertyDescriptor.getName();
        Object value = properties.get(name);
        if (value != null) {
          writeMethod = propertyDescriptor.getWriteMethod();
          Object targetTypeObject = AnyConverter.convert(value, propertyDescriptor.getPropertyType());
          writeMethod.invoke(bean, targetTypeObject);
        }
      }
    } catch (IntrospectionException e) {
      throw ExceptionMapper.configurationException(e, beanClass);
    } catch (IllegalAccessException | InvocationTargetException e) {
      throw ExceptionMapper.configurationException(e, writeMethod);
    }
  }
    
/*
    public static Class getPropertyType(Class beanClass, String propertyName) {
        PropertyDescriptor descriptor = getPropertyDescriptor(beanClass, propertyName);
        return (descriptor != null ? descriptor.getPropertyType() : null);
    }
*/

  /**
   * Gets property values.
   *
   * @param bean         the bean
   * @param includeClass the include class
   * @return the property values
   */
  public static Map getPropertyValues(Object bean, boolean includeClass) {
    Map result = new HashMap<>();
    PropertyDescriptor[] descriptors = getPropertyDescriptors(bean.getClass());
    for (PropertyDescriptor descriptor : descriptors) {
      String propertyName = descriptor.getName();
      if (includeClass || !"class".equals(propertyName)) {
        result.put(propertyName, getPropertyValue(bean, descriptor));
      }
    }
    return result;
  }

  /**
   * Gets readable property values.
   *
   * @param bean         the bean
   * @param includeClass the include class
   * @return the readable property values
   */
  public static Map getReadablePropertyValues(Object bean, boolean includeClass) {
    Map result = new HashMap<>();
    PropertyDescriptor[] descriptors = getPropertyDescriptors(bean.getClass());
    for (PropertyDescriptor descriptor : descriptors) {
      String propertyName = descriptor.getName();
      if (descriptor.getReadMethod() != null && (includeClass || !"class".equals(propertyName))) {
        result.put(propertyName, getPropertyValue(bean, descriptor));
      }
    }
    return result;
  }

  /**
   * Gets rw property values.
   *
   * @param bean         the bean
   * @param includeClass the include class
   * @return the rw property values
   */
  public static Map getRWPropertyValues(Object bean, boolean includeClass) {
    Map result = new HashMap<>();
    PropertyDescriptor[] descriptors = getPropertyDescriptors(bean.getClass());
    for (PropertyDescriptor descriptor : descriptors) {
      String propertyName = descriptor.getName();
      if (descriptor.getWriteMethod() != null && descriptor.getReadMethod() != null && (includeClass || !"class".equals(propertyName))) {
        result.put(propertyName, getPropertyValue(bean, descriptor));
      }
    }
    return result;
  }

  /**
   * Queries a property value on a JavaBean instance
   *
   * @param bean         the bean to read
   * @param propertyName the name of the property to read
   * @return the property value
   */
  public static Object getPropertyValue(Object bean, String propertyName) {
    return getPropertyValue(bean, propertyName, true);
  }

  /**
   * Gets property value.
   *
   * @param bean             the bean
   * @param propertyName     the property name
   * @param propertyRequired the property required
   * @return the property value
   */
  public static Object getPropertyValue(Object bean, String propertyName, boolean propertyRequired) {
    PropertyDescriptor descriptor = getPropertyDescriptor(bean.getClass(), propertyName);
    if (descriptor == null) {
      if (propertyRequired) {
        throw new ConfigurationError("Property '" + propertyName + "' not found in class " + bean.getClass());
      } else {
        return null;
      }
    }
    return getPropertyValue(bean, descriptor);
  }

  private static Object getPropertyValue(Object bean, PropertyDescriptor descriptor) {
    Method readMethod = null;
    try {
      readMethod = descriptor.getReadMethod();
      return readMethod.invoke(bean);
    } catch (IllegalAccessException | InvocationTargetException e) {
      throw ExceptionMapper.configurationException(e, readMethod);
    }
  }

  /**
   * Sets a property value on a JavaBean object.
   *
   * @param bean          the bean on which to set the property
   * @param propertyName  the name of the property to set
   * @param propertyValue the value to set the property to
   */
  public static void setPropertyValue(Object bean, String propertyName, Object propertyValue) {
    setPropertyValue(bean, propertyName, propertyValue, true);
  }

  /**
   * Sets property value.
   *
   * @param bean          the bean
   * @param propertyName  the property name
   * @param propertyValue the property value
   * @param strict        the strict
   */
  public static void setPropertyValue(Object bean, String propertyName, Object propertyValue, boolean strict) {
    setPropertyValue(bean, propertyName, propertyValue, strict, !strict);
  }

  /**
   * Sets property value.
   *
   * @param bean          the bean
   * @param propertyName  the property name
   * @param propertyValue the property value
   * @param required      the required
   * @param autoConvert   the auto convert
   */
  public static void setPropertyValue(Object bean, String propertyName, Object propertyValue, boolean required, boolean autoConvert) {
    Method writeMethod = null;
    try {
      Class beanClass = bean.getClass();
      PropertyDescriptor propertyDescriptor = getPropertyDescriptor(beanClass, propertyName);
      if (propertyDescriptor == null) {
        if (required) {
          throw new ConfigurationError(beanClass + " does not have a property '" + propertyName + "'");
        } else {
          return;
        }
      }
      writeMethod = propertyDescriptor.getWriteMethod();
      if (writeMethod != null) {
        Class propertyType = propertyDescriptor.getPropertyType();
        if (propertyValue != null) {
          Class argType = propertyValue.getClass();
          if (!propertyType.isAssignableFrom(argType) && !isWrapperTypeOf(propertyType, propertyValue) && !autoConvert) {
            throw new IllegalArgumentException("ArgumentType mismatch: expected "
                + propertyType.getName() + ", found " + propertyValue.getClass().getName());
          } else {
            propertyValue = AnyConverter.convert(propertyValue, propertyType);
          }
        }
        writeMethod.invoke(bean, propertyValue);
      } else if (required) {
        throw new UnsupportedOperationException("Cannot write read-only property '"
            + propertyDescriptor.getName() + "' of " + beanClass);
      } else {
        // no write method but property is not required, so ignore it silently
      }
    } catch (IllegalAccessException | InvocationTargetException e) {
      throw ExceptionMapper.configurationException(e, writeMethod);
    }
  }

  /**
   * Extract properties list.
   *
   * @param        the type parameter
   * @param   the type parameter
   * @param beans        the beans
   * @param propertyName the property name
   * @return the list
   */
  @SuppressWarnings("unchecked")
  public static  List extractProperties(Collection beans, String propertyName) {
    List result = new ArrayList<>(beans.size());
    for (BEAN bean : beans) {
      result.add((PROP_TYPE) getPropertyValue(bean, propertyName));
    }
    return result;
  }

  /**
   * Extract properties prop type [ ].
   *
   * @param        the type parameter
   * @param   the type parameter
   * @param beans        the beans
   * @param propertyName the property name
   * @param propertyType the property type
   * @return the prop type [ ]
   */
  @SuppressWarnings("unchecked")
  public static  PROP_TYPE[] extractProperties(BEAN[] beans, String propertyName, Class propertyType) {
    PROP_TYPE[] result = ArrayUtil.newInstance(propertyType, beans.length);
    for (int i = 0; i < beans.length; i++) {
      BEAN bean = beans[i];
      result[i] = (PROP_TYPE) getPropertyValue(bean, propertyName);
    }
    return result;
  }

  // class operations ------------------------------------------------------------------------------------------------

  /**
   * Prints information about a class' parents and methods to a PrintWriter
   *
   * @param object  the object to examine
   * @param printer the {@link PrintWriter} used to write the text representation
   */
  public static void printClassInfo(Object object, PrintWriter printer) {
    if (object == null) {
      printer.println("null");
      return;
    }
    Class type = object.getClass();
    printer.println(type);
    if (type.getSuperclass() != null) {
      printer.println("extends " + type.getSuperclass());
    }
    for (Class interf : type.getInterfaces()) {
      printer.println("implements " + interf);
    }
    for (Method method : type.getMethods()) {
      printer.println(method);
    }
  }

  /**
   * Checks if a class fulfills the JavaBeans contract.
   *
   * @param cls the class to check
   */
  public static void checkJavaBean(Class cls) {
    try {
      Constructor constructor = cls.getDeclaredConstructor();
      int classModifiers = cls.getModifiers();
      if (Modifier.isInterface(classModifiers)) {
        throw new RuntimeException(cls.getName() + " is an interface");
      }
      if (Modifier.isAbstract(classModifiers)) {
        throw new RuntimeException(cls.getName() + " cannot be instantiated - it is an abstract class");
      }
      int modifiers = constructor.getModifiers();
      if (!Modifier.isPublic(modifiers)) {
        throw new RuntimeException("No public default constructor in " + cls);
      }
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("No default constructor in class " + cls);
    } catch (SecurityException e) {
      logger.error("I am not allowed to check the class by using reflection, " +
          "so I just can hope the class is alright and go on: ", e);
    }
  }

  /**
   * Tells if a class is deprecated.
   *
   * @param type the class to check for deprecation
   * @return true if the class is deprecated, else false
   * @since 0.2.05
   */
  public static boolean deprecated(Class type) {
    Annotation[] annotations = type.getDeclaredAnnotations();
    for (Annotation annotation : annotations) {
      if (annotation instanceof Deprecated) {
        return true;
      }
    }
    return false;
  }

  /**
   * Gets classes.
   *
   * @param packageName the package name
   * @return the classes
   */
  public static List> getClasses(String packageName) {
    try {
      ClassLoader classLoader = getContextClassLoader();
      String packagePath = packageName.replace('.', '/');
      Enumeration resourceUris = classLoader.getResources(packagePath);
      List> classes = new ArrayList<>();
      while (resourceUris.hasMoreElements()) {
        URL resource = resourceUris.nextElement();
        String protocol = resource.getProtocol();
        if ("jar".equals(protocol)) {
          findClassesInJar(resource.getFile(), packagePath, classes);
        } else if ("file".equals(protocol)) {
          findClassesInDirectory(new File(resource.toURI()), packageName, classes);
        } else {
          throw new UnsupportedOperationException("Not a supported protocol: " + protocol);
        }
      }
      return classes;
    } catch (IOException | URISyntaxException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Gets field value.
   *
   * @param target the target
   * @param name   the name
   * @param strict the strict
   * @return the field value
   */
  public static Object getFieldValue(Object target, String name, boolean strict) {
    Class type = target.getClass();
    try {
      Field field = type.getField(name);
      return getFieldValue(field, target, strict);
    } catch (NoSuchFieldException e) {
      if (strict) {
        throw ExceptionMapper.configurationException(e, type.getName() + '.' + name);
      } else {
        escalator.escalate("Class '" + type + "' does not have a field '" + name + "'", type, name);
        return null;
      }
    }
  }

  /**
   * Gets field value.
   *
   * @param field  the field
   * @param target the target
   * @param strict the strict
   * @return the field value
   */
  public static Object getFieldValue(Field field, Object target, boolean strict) {
    try {
      if ((field.getModifiers() & Modifier.STATIC) == Modifier.STATIC) {
        return field.get(null);
      } else {
        return field.get(target);
      }
    } catch (IllegalArgumentException | IllegalAccessException e) {
      throw new ConfigurationError(e);
    }
  }

  /**
   * Returns a Field object that represents an attribute of a class
   *
   * @param type the class that holds the attribute
   * @param name the name of the attribute
   * @return a Field object that represents the attribute
   */
  public static Field getField(Class type, String name) {
    try {
      return type.getField(name);
    } catch (NoSuchFieldException e) {
      throw ExceptionMapper.configurationException(e, type.getName() + '.' + name);
    }
  }

  /**
   * Find methods by annotation method [ ].
   *
   * @param owner           the owner
   * @param annotationClass the annotation class
   * @return the method [ ]
   */
  public static Method[] findMethodsByAnnotation(
      Class owner, Class annotationClass) {
    Method[] methods = owner.getMethods();
    ArrayBuilder builder = new ArrayBuilder<>(Method.class);
    for (Method method : methods) {
      if (method.getAnnotation(annotationClass) != null) {
        builder.add(method);
      }
    }
    return builder.toArray();
  }

  /**
   * Get generic interface params type [ ].
   *
   * @param                the type parameter
   * @param                the type parameter
   * @param checkedClass      the checked class
   * @param searchedInterface the searched interface
   * @return the type [ ]
   */
  public static  Type[] getGenericInterfaceParams(Class checkedClass, Class searchedInterface) {
    for (Type type : checkedClass.getGenericInterfaces()) {
      ParameterizedType pt = (ParameterizedType) type;
      if (searchedInterface.equals(pt.getRawType())) {
        return pt.getActualTypeArguments();
      }
    }
    if (!Object.class.equals(checkedClass.getSuperclass())) {
      return getGenericInterfaceParams(checkedClass.getSuperclass(), searchedInterface);
    }
    throw new ConfigurationError(checkedClass + " does not implement interface with generic parameters: " + searchedInterface);
  }

  /**
   * Class name string.
   *
   * @param  the type parameter
   * @param o   the o
   * @return the string
   */
  public static  String className(Object o) {
    if (o == null) {
      return null;
    }
    return (o instanceof Class ? ((Class) o).getName() : o.getClass().getName());
  }

  /**
   * Simple class name string.
   *
   * @param  the type parameter
   * @param o   the o
   * @return the string
   */
  public static  String simpleClassName(Object o) {
    if (o == null) {
      return null;
    }
    return (o instanceof Class ? ((Class) o).getName() : o.getClass().getSimpleName());
  }


  // General method support, i.e. for toString(), equals(), hashCode() -----------------------------------------------

  /**
   * Tries to convert both arguments to the same type and then compares them
   *
   * @param o1 the first object to compare
   * @param o2 the second object to compare
   * @return true if they are equal, otherwise false
   */
  public static boolean equalsIgnoreType(Object o1, Object o2) {
    if (NullSafeComparator.equals(o1, o2)) {
      return true;
    }
    if (o1 == null || o2 == null) {
      return false;
    }
    if (o1.getClass() == o2.getClass()) {
      return false;
    }
    // OK, both are not null, but they have a different type
    if (o1 instanceof String && o2 instanceof Number) {
      Object tmp = o1;
      o1 = o2;
      o2 = tmp;
    }
    if (o1 instanceof Number) {
      if (o2 instanceof String) {
        o2 = AnyConverter.convert(o2, o1.getClass());
      }
      return (((Number) o1).doubleValue() == ((Number) o2).doubleValue());
    }
    return false;
  }

  /**
   * Null safe hash code int.
   *
   * @param object the object
   * @return the int
   */
  public static int nullSafeHashCode(Object object) {
    return (object != null ? object.hashCode() : 0);
  }

  /**
   * Exists class boolean.
   *
   * @param className the class name
   * @return the boolean
   */
  public static boolean existsClass(String className) {
    try {
      if (NON_CLASS_NAMES.contains(className)) {
        return false;
      }
      Class.forName(className);
      return true;
    } catch (ClassNotFoundException e) {
      NON_CLASS_NAMES.add(className);
      return false;
    }
  }

  /**
   * To string string.
   *
   * @param bean the bean
   * @return the string
   */
  public static String toString(Object bean) {
    return toString(bean, false);
  }

  /**
   * To string string.
   *
   * @param bean   the bean
   * @param simple the simple
   * @return the string
   */
  public static String toString(Object bean, boolean simple) {
    if (bean == null) {
      return null;
    }
    Class beanClass = bean.getClass();
    StringBuilder builder = new StringBuilder(simple ? beanClass.getSimpleName() : bean.getClass().getName());
    PropertyDescriptor[] descriptors = getPropertyDescriptors(bean.getClass());
    boolean first = true;
    for (PropertyDescriptor descriptor : descriptors) {
      String propertyName = descriptor.getName();
      if (!"class".equals(propertyName) && descriptor.getReadMethod() != null) {
        if (first) {
          builder.append('[');
        } else {
          builder.append(", ");
        }

        Object value = getPropertyValue(bean, propertyName);
        String valueString = ToStringConverter.convert(value, "null");
        builder.append(propertyName).append("=").append(valueString);
        first = false;
      }
    }
    if (!first) {
      builder.append(']');
    }
    return builder.toString();
  }

  // private helpers -------------------------------------------------------------------------------------------------

  private static boolean typeMatches(Class foundType, Class expectedType) {
    if (foundType == null) {
      return true;
    }
    if (expectedType.isAssignableFrom(foundType)) {
      return true;
    }
    if (isPrimitiveType(expectedType.getName()) &&
        foundType.equals(getWrapper(expectedType.getName()))) {
      return true;
    }
    if (isPrimitiveType(foundType.getName()) &&
        expectedType.equals(getWrapper(foundType.getName()))) {
      return true;
    }
    return isNumberType(foundType) && isNumberType(expectedType);
  }

  /**
   * Creates an instance of the class using the default constructor.
   *
   * @param type the type to instantiate
   * @return a new instance of the type
   * @since 0.2.06
   */
  @SuppressWarnings("cast")
  private static  T newInstanceFromDefaultConstructor(Class type) {
    if (type == null) {
      return null;
    }
    if (logger.isDebugEnabled()) {
      logger.debug("Instantiating " + type.getSimpleName());
    }
    if (deprecated(type)) {
      escalator.escalate("Instantiating a deprecated class: " + type.getName(), BeanUtil.class, null);
    }
    try {
      return type.getDeclaredConstructor().newInstance();
    } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
      throw ExceptionMapper.configurationException(e, type);
    }
  }

  private static void findClassesInDirectory(File directory, String packagePath, List> classes) {
    File[] files = directory.listFiles();
    for (File file : files) {
      String fileName = file.getName();
      if (file.isDirectory()) {
        findClassesInDirectory(file, packagePath + "." + fileName, classes);
      } else if (fileName.endsWith(".class") && !fileName.contains("$")) {
        String className = packagePath + '.' + fileName.substring(0, fileName.length() - 6);
        classes.add(BeanUtil.forName(className));
      }
    }
  }

  private static void findClassesInJar(String path, String packagePath, List> classes)
      throws IOException, URISyntaxException {
    // extract jar file name
    String fileName = path;
    if (fileName.contains("!")) {
      fileName = fileName.substring(0, fileName.indexOf('!'));
    }
    // extract classes
    JarFile jarFile = null;
    try {
      jarFile = new JarFile(new File(new URL(fileName).toURI()));
      Enumeration entries = jarFile.entries();
      while (entries.hasMoreElements()) {
        JarEntry entry = entries.nextElement();
        String entryName = entry.getName();
        if (entryName.startsWith(packagePath) && entryName.endsWith(".class") && !entry.isDirectory()
            && !entry.getName().contains("$")) {
          String className = entryName.replace('/', '.').substring(0, entryName.length() - 6);
          classes.add(BeanUtil.forName(className));
        }
      }
    } finally {
      IOUtil.close(jarFile);
    }
  }

  private static Object[] convertArray(Object[] values, Class[] targetTypes) {
    Object[] result = new Object[values.length];
    for (int i = 0; i < values.length; i++) {
      result[i] = AnyConverter.convert(values[i], targetTypes[i]);
    }
    return result;
  }

  private static boolean isWrapperTypeOf(Class propertyType,
                                         Object propertyValue) {
    String propertyTypeName = propertyType.getName();
    return (isPrimitiveType(propertyTypeName) && getWrapper(propertyType.getName()) == propertyValue.getClass());
  }

  /**
   * Represents a primitive-to-wrapper mapping.
   */
  private static final class PrimitiveTypeMapping {
    /**
     * The Primitive type.
     */
    public Class primitiveType;
    /**
     * The Wrapper type.
     */
    public Class wrapperType;

    /**
     * Instantiates a new Primitive type mapping.
     *
     * @param primitiveType the primitive type
     * @param wrapperType   the wrapper type
     */
    public PrimitiveTypeMapping(Class primitiveType, Class wrapperType) {
      this.primitiveType = primitiveType;
      this.wrapperType = wrapperType;
    }
  }

}