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

org.databene.commons.BeanUtil Maven / Gradle / Ivy

Go to download

'databene commons' is an open source Java library by Volker Bergmann. It provides extensions to the Java core library by utility classes, abstract concepts and concrete implementations.

There is a newer version: 1.0.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 org.databene.commons;

import org.databene.commons.converter.AnyConverter;
import org.databene.commons.converter.ConverterManager;
import org.databene.commons.converter.ToStringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
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.*;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.beans.Introspector;
import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.beans.IntrospectionException;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;

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

    private static final Logger logger = LoggerFactory.getLogger(BeanUtil.class);
    
    private static final HashSet NON_CLASS_NAMES = new HashSet(100);
    
    private static 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 Map> integralNumberTypeMap;

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

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

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

    /**
     * Map of primitive Java number types
     */
    private static 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 -----------------------------------------------------------------------------------------------

    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;
    }
    
    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);
    }

    public static boolean isPrimitiveType(String className) {
        return primitiveTypeMap.containsKey(className);
    }

    public static boolean isPrimitiveNumberType(String className) {
        return primitiveNumberTypeMap.containsKey(className);
    }

    public static boolean isNumberType(Class type) {
    	return (isIntegralNumberType(type) || isDecimalNumberType(type));
    }
    
    public static boolean isIntegralNumberType(Class type) {
        return isIntegralNumberType(type.getName());
    }

    public static boolean isIntegralNumberType(String className) {
        return integralNumberTypeMap.containsKey(className);
    }

    public static boolean isDecimalNumberType(Class type) {
        return isDecimalNumberType(type.getName());
    }

    public static boolean isDecimalNumberType(String className) {
        return decimalNumberTypeMap.containsKey(className);
    }

    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);
    }

	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 ---------------------------------------------------------------------------------------------------

    @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 e) {
                throw ExceptionMapper.configurationException(e, name);
            } catch (NullPointerException e) {
            	// this is raised by the Eclipse BundleLoader if it does not find the class
                throw ExceptionMapper.configurationException(e, name);
            }
        }
    }

	public static ClassLoader getContextClassLoader() {
		ClassLoader result = Thread.currentThread().getContextClassLoader();
		if (result == null)
			result = BeanUtil.class.getClassLoader();
		return result;
	}

	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;
	}
	
	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;
	}
	
	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);
		}
	}
    
	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);
    }

    public static  T newInstance(Class type) {
    	return newInstance(type, true, null);
    }

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

    @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 constructor the constructor to invoke
     * @param params the parameters to provide to the constructor
     * @param  the class of the object to instantiate
     * @return a new instance of the class
     */
    public static  T newInstance(Constructor constructor, Object ... params) {
        return newInstance(constructor, true, params);
    }

    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 e) {
            throw ExceptionMapper.configurationException(e, type);
        } catch (IllegalAccessException e) {
            throw ExceptionMapper.configurationException(e, type);
        } catch (InvocationTargetException e) {
            throw ExceptionMapper.configurationException(e, type);
        }
    }
    
	@SuppressWarnings("unchecked")
    public static  T clone(T object) {
        try {
            Method cloneMethod = object.getClass().getMethod("clone");
            return (T) cloneMethod.invoke(object);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Unexpected exception", e); // This is not supposed to happen
        } catch (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);
        }
    }

	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;
    }

    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();
    }

    @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);
    }

    @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);
    }

    public static Object invokeStatic(Class targetClass, String methodName, Object ... args) {
        return invokeStatic(targetClass, methodName, true, args);
    }

    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);
    }

    @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 e) {
            throw ExceptionMapper.configurationException(e, method);
        } catch (InvocationTargetException e) {
            throw ExceptionMapper.configurationException(e, method);
        }
    }

    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;
    }

    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;
        if (isNumberType(foundType) && isNumberType(expectedType))
        	return true;
	    return false;
    }

	// 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;
    }

    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;
    }

    public static boolean hasProperty(Class beanClass, String propertyName) {
        return (getPropertyDescriptor(beanClass, propertyName) != null);
    }

    public static boolean hasWriteableProperty(Class beanClass, String propertyName) {
        PropertyDescriptor descriptor = getPropertyDescriptor(beanClass, propertyName);
		return (descriptor != null ? descriptor.getWriteMethod() != null : false);
    }

    /**
     * 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);
	        }
	    }
    }
    
    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 e) {
            throw ExceptionMapper.configurationException(e, writeMethod);
        } catch (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);
    }
*/

    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;
    }

    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);
    }

    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 e) {
            throw ExceptionMapper.configurationException(e, readMethod);
        } catch (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);
    }

    public static void setPropertyValue(Object bean, String propertyName, Object propertyValue, boolean strict) {
    	setPropertyValue(bean, propertyName, propertyValue, strict, !strict);
    }

    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 e) {
            throw ExceptionMapper.configurationException(e, writeMethod);
        } catch (InvocationTargetException e) {
            throw ExceptionMapper.configurationException(e, writeMethod);
        }
    }

	private static boolean isWrapperTypeOf(Class propertyType,
			Object propertyValue) {
		String propertyTypeName = propertyType.getName();
		return (isPrimitiveType(propertyTypeName) && getWrapper(propertyType.getName()) == propertyValue.getClass());
	}
    
    @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;
    }

    @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;
    }
    
    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 e) {
			throw new RuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new ObjectNotFoundException(e);
		} catch (URISyntaxException e) {
			throw new RuntimeException(e);
		}
	}

	private static List> findClassesInDirectory(File directory, String packagePath, List> classes) 
			throws ClassNotFoundException {
		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));
			}
		}
		return classes;
	}
    
	private static List> 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 = 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));
			 }
		 }
		 return classes;
	}

    // invisible helpers -----------------------------------------------------------------------------------------------

    /**
     * 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 (T) type.newInstance();
        } catch (InstantiationException e) {
            throw ExceptionMapper.configurationException(e, type);
        } catch (IllegalAccessException e) {
            throw ExceptionMapper.configurationException(e, type);
        }
    }

    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;
        	}
        }
    }

    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 e) {
        	throw new ConfigurationError(e);
        } catch (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);
        }
    }

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

        public PrimitiveTypeMapping(Class primitiveType, Class wrapperType) {
            this.primitiveType = primitiveType;
            this.wrapperType = wrapperType;
        }
    }

    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();
    }
    
    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);
    }

	public static String toString(Object bean) {
		return toString(bean, false);
	}

	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();
	}

	public static  String simpleClassName(Object o) {
		if (o == null)
			return null;
		return (o instanceof Class ? ((Class) o).getName() : o.getClass().getSimpleName());
	}
	
	/** 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;
    }

	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;
		}
	}

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

	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;
	}

}