org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Google, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Google, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.wb.internal.core.utils.reflect;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

import org.eclipse.wb.internal.core.EnvironmentUtils;
import org.eclipse.wb.internal.core.utils.check.Assert;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableObjectEx;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;

import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
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.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

/**
 * Contains different Java reflection utilities.
 * 
 * @author scheglov_ke
 * @coverage core.util
 */
public class ReflectionUtils {
    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    private ReflectionUtils() {
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Access
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Java 8 uses "MethodRef" that uses {@link SoftReference} to a {@link Method}. But this means
     * that when it looses a reference to a <em>protected</em> {@link Method}, it cannot restore it. I
     * wish we had done it differently, not by using {@link PropertyDescriptor}.
     */
    public static Method getWriteMethod(PropertyDescriptor propertyDescriptor) {
        try {
            return propertyDescriptor.getWriteMethod();
        } catch (Throwable e) {
            return null;
        }
    }

    /**
     * Java 8 uses "MethodRef" that uses {@link SoftReference} to a {@link Method}. But this means
     * that when it looses a reference to a <em>protected</em> {@link Method}, it cannot restore it. I
     * wish we had done it differently, not by using {@link PropertyDescriptor}.
     */
    public static Method getReadMethod(PropertyDescriptor propertyDescriptor) {
        try {
            return propertyDescriptor.getReadMethod();
        } catch (Throwable e) {
            return null;
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Class
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return the {@link ClassLoader} that was used to load given {@link Class}. Always return not
     *         <code>null</code> value, even if {@link Class} was loaded using bootstrap
     *         {@link ClassLoader}.
     */
    public static ClassLoader getClassLoader(Class<?> clazz) {
        ClassLoader classLoader = clazz.getClassLoader();
        if (classLoader == null) {
            classLoader = ClassLoader.getSystemClassLoader();
        }
        return classLoader;
    }

    /**
     * @return the {@link Class} with given name - primitive or {@link Object} (including arrays).
     */
    public static Class<?> getClassByName(ClassLoader classLoader, String className) throws Exception {
        Assert.isNotNull(className);
        // check for primitive type
        if ("boolean".equals(className)) {
            return boolean.class;
        } else if ("byte".equals(className)) {
            return byte.class;
        } else if ("char".equals(className)) {
            return char.class;
        } else if ("short".equals(className)) {
            return short.class;
        } else if ("int".equals(className)) {
            return int.class;
        } else if ("long".equals(className)) {
            return long.class;
        } else if ("float".equals(className)) {
            return float.class;
        } else if ("double".equals(className)) {
            return double.class;
        }
        // check for array
        if (className.endsWith("[]")) {
            int dimensions = StringUtils.countMatches(className, "[]");
            String componentClassName = StringUtils.substringBefore(className, "[]");
            Class<?> componentClass = getClassByName(classLoader, componentClassName);
            return Array.newInstance(componentClass, new int[dimensions]).getClass();
        }
        // OK, load this class as object
        return classLoader.loadClass(className);
    }

    /**
     * @return <code>true</code> if given {@link ClassLoader} has class with given name.
     */
    public static boolean hasClass(ClassLoader classLoader, String name) {
        try {
            classLoader.loadClass(name);
            return true;
        } catch (Throwable e) {
            return false;
        }
    }

    /**
     * @return the {@link Object} default value for class with given {@code className}.
     */
    public static Object getDefaultValue(String className) {
        // primitive
        if ("boolean".equals(className)) {
            return false;
        } else if ("byte".equals(className)) {
            return (byte) 0;
        } else if ("char".equals(className)) {
            return (char) 0;
        } else if ("short".equals(className)) {
            return (short) 0;
        } else if ("int".equals(className)) {
            return 0;
        } else if ("long".equals(className)) {
            return 0L;
        } else if ("float".equals(className)) {
            return 0.0f;
        } else if ("double".equals(className)) {
            return 0.0;
        }
        // Object
        return null;
    }

    /**
     * @return the {@link Object} default value for class - false/zero for primitives,
     *         <code>"dynamic"</code> for {@link String} and empty collections.
     */
    public static Object getDefaultValue(Class<?> clazz) {
        if (clazz.isPrimitive()) {
            return getDefaultValue(clazz.getName());
        }
        // String
        if (isSuccessorOf(clazz, "java.lang.String")) {
            return "<dynamic>";
        }
        // collections
        if (isSuccessorOf(clazz, "java.util.List")) {
            return Lists.newArrayList();
        }
        if (isSuccessorOf(clazz, "java.util.Set")) {
            return Sets.newHashSet();
        }
        if (isSuccessorOf(clazz, "java.util.Map")) {
            return Maps.newHashMap();
        }
        // Object
        return null;
    }

    /**
     * @param clazz
     *          the {@link Class} to check.
     * @param requiredClass
     *          the name of {@link Class} that should be extended, or name of interface that should be
     *          implemented.
     * 
     * @return <code>true</code> if given {@link Class} extends <code>requiredClass</code> or
     *         implements interface with this name.
     */
    public static boolean isSuccessorOf(Class<?> clazz, String requiredClass) {
        if (clazz == null) {
            return false;
        }
        String clazzName = clazz.getName();
        // check cache
        {
            IsSuccessorResult result = isSuccessorOf_checkCache(clazz, requiredClass);
            if (result == IsSuccessorResult.TRUE) {
                return true;
            }
            if (result == IsSuccessorResult.FALSE) {
                return false;
            }
        }
        // check this Class
        if (clazzName.equals(requiredClass)) {
            isSuccessorOf_addCache(clazz, requiredClass, IsSuccessorResult.TRUE);
            return true;
        }
        // check super-Class
        if (isSuccessorOf(clazz.getSuperclass(), requiredClass)) {
            isSuccessorOf_addCache(clazz, requiredClass, IsSuccessorResult.TRUE);
            return true;
        }
        // check interfaces
        for (Class<?> interfaceClass : clazz.getInterfaces()) {
            if (isSuccessorOf(interfaceClass, requiredClass)) {
                isSuccessorOf_addCache(clazz, requiredClass, IsSuccessorResult.TRUE);
                return true;
            }
        }
        // no, not required Class
        isSuccessorOf_addCache(clazz, requiredClass, IsSuccessorResult.FALSE);
        return false;
    }

    private enum IsSuccessorResult {
        UNKNOWN, FALSE, TRUE
    }

    private static final Map<String, WeakHashMap<Class<?>, IsSuccessorResult>> m_isSuccessorOfCache = Maps
            .newHashMap();

    private static void isSuccessorOf_addCache(Class<?> clazz, String requiredClass, IsSuccessorResult result) {
        WeakHashMap<Class<?>, IsSuccessorResult> classes = m_isSuccessorOfCache.get(requiredClass);
        if (classes == null) {
            classes = new WeakHashMap<Class<?>, IsSuccessorResult>();
            m_isSuccessorOfCache.put(requiredClass, classes);
        }
        classes.put(clazz, result);
    }

    private static IsSuccessorResult isSuccessorOf_checkCache(Class<?> clazz, String requiredClass) {
        WeakHashMap<Class<?>, IsSuccessorResult> classes = m_isSuccessorOfCache.get(requiredClass);
        if (classes != null) {
            return classes.get(clazz);
        }
        return IsSuccessorResult.UNKNOWN;
    }

    /**
     * @return <code>true</code> if candidate can be used as target {@link Class}.
     */
    public static boolean isAssignableFrom(Class<?> targetClass, Object candidate) {
        // primitive
        if (targetClass.isPrimitive()) {
            if (targetClass == byte.class) {
                return candidate instanceof Byte;
            }
            if (targetClass == char.class) {
                return candidate instanceof Character;
            }
            if (targetClass == short.class) {
                return candidate instanceof Short;
            }
            if (targetClass == int.class) {
                return candidate instanceof Integer;
            }
            if (targetClass == long.class) {
                return candidate instanceof Long;
            }
            if (targetClass == float.class) {
                return candidate instanceof Float;
            }
            if (targetClass == double.class) {
                return candidate instanceof Double;
            }
        }
        // object
        if (candidate == null) {
            return true;
        }
        return targetClass.isInstance(candidate);
    }

    /**
     * @return <code>true</code> if candidate can be used as target {@link Class}.
     */
    public static boolean isSuccessorOf(Object candidate, String requiredClass) {
        if (candidate == null) {
            return false;
        }
        Class<?> candidateClass = candidate.getClass();
        if (!requiredClass.contains(".")) {
            Field fieldTYPE = getFieldByName(candidateClass, "TYPE");
            if (fieldTYPE != null) {
                Class<?> primitiveType = (Class<?>) getFieldObject(candidateClass, "TYPE");
                if (ObjectUtils.equals(primitiveType.getName(), requiredClass)) {
                    return true;
                }
            }
            return false;
        } else {
            return isSuccessorOf(candidateClass, requiredClass);
        }
    }

    /**
     * @return the result of {@link Class#isMemberClass()} and ignore {@link NoClassDefFoundError}.
     */
    public static boolean isMemberClass(final Class<?> clazz) {
        Assert.isNotNull(clazz);
        return ExecutionUtils.runObjectIgnore(new RunnableObjectEx<Boolean>() {
            public Boolean runObject() throws Exception {
                return clazz.isMemberClass();
            }
        }, false);
    }

    /**
     * @return the list of all supertypes: superclasses and interfaces. Class itself and its
     *         interfaces first, then superclass.
     */
    public static List<Class<?>> getSuperHierarchy(Class<?> clazz) throws Exception {
        List<Class<?>> types = Lists.newArrayList();
        types.add(clazz);
        // check super Class
        Class<?> superclass = clazz.getSuperclass();
        if (superclass != null) {
            // interfaces
            for (Class<?> interfaceClass : clazz.getInterfaces()) {
                types.addAll(getSuperHierarchy(interfaceClass));
            }
            // super Class
            types.addAll(getSuperHierarchy(superclass));
        }
        // done
        return types;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Specific
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return <code>true</code> if two arrays have same number of classes and all "specific" classes
     *         are {@link #isMoreSpecific(Class, Class)} than "base".
     */
    public static boolean isMoreSpecific(Class<?> base[], Class<?> specific[]) {
        if (base.length != specific.length) {
            return false;
        }
        for (int i = 0; i < base.length; i++) {
            if (!isMoreSpecific(base[i], specific[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * @return <code>true</code> if "specific" is "base" or subclass of "base".
     */
    public static boolean isMoreSpecific(Class<?> base, Class<?> specific) {
        return base.isAssignableFrom(specific);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Enchanced classes support
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return <code>true</code> if given {@link Class} that is "enchanced", i.e. is generated by
     *         "CGLib", or some other library.
     */
    public static boolean isEnchancedClass(Class<?> clazz) {
        return clazz.getName().indexOf('$') != -1;
    }

    /**
     * @return the super-Class of given {@link Class} that is "normal", i.e. is not generated by
     *         "CGLib", or some other library. For now we just check that {@link Class} name has no
     *         <code>'$'</code> character.
     */
    public static Class<?> getNormalClass(Class<?> clazz) {
        while (isEnchancedClass(clazz)) {
            clazz = clazz.getSuperclass();
        }
        return clazz;
    }

    /**
     * @return the {@link String} presentation of given {@link Method}. If this {@link Method} is
     *         declared in some "enchanced" {@link Class}, and there was same {@link Method} in
     *         "normal" {@link Class}, then <code>toString</code> of "normal" {@link Method} returned.
     */
    public static String toString(Method method) {
        String methodSignature = getMethodSignature(method);
        // try to find in "normal" Class method with same signature
        while (isEnchancedClass(method.getDeclaringClass())) {
            Class<?> superClass = method.getDeclaringClass().getSuperclass();
            Method superMethod = getMethodBySignature(superClass, methodSignature);
            if (superMethod == null) {
                break;
            }
            method = superMethod;
        }
        // OK, use toString()
        return method.toString();
    }

    /**
     * @return the string presentation {@link Constructor} that uses short class names.
     */
    public static String getShortConstructorString(Constructor<?> constructor) {
        if (constructor == null) {
            return "<null-constructor>";
        }
        StringBuilder buffer = new StringBuilder();
        buffer.append(getShortName(constructor.getDeclaringClass()));
        buffer.append('(');
        // append parameters
        {
            Class<?>[] parameters = constructor.getParameterTypes();
            for (int i = 0; i < parameters.length; i++) {
                Class<?> parameterType = parameters[i];
                if (i != 0) {
                    buffer.append(',');
                }
                buffer.append(getShortName(parameterType));
            }
        }
        // close
        buffer.append(')');
        return buffer.toString();
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Signature
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @param runtime
     *          is <code>true</code> if we need name for class loading, <code>false</code> if we need
     *          name for source generation.
     * 
     * @return the fully qualified name of given {@link Type}.
     */
    public static String getFullyQualifiedName(Type type, boolean runtime) {
        Assert.isNotNull(type);
        // Class
        if (type instanceof Class<?>) {
            Class<?> clazz = (Class<?>) type;
            // array
            if (clazz.isArray()) {
                return getFullyQualifiedName(clazz.getComponentType(), runtime) + "[]";
            }
            // object
            String name = clazz.getName();
            if (!runtime) {
                name = name.replace('$', '.');
            }
            return name;
        }
        // GenericArrayType
        if (type instanceof GenericArrayType) {
            GenericArrayType genericArrayType = (GenericArrayType) type;
            return getFullyQualifiedName(genericArrayType.getGenericComponentType(), runtime) + "[]";
        }
        // ParameterizedType
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type rawType = parameterizedType.getRawType();
            // raw type
            StringBuilder sb = new StringBuilder();
            sb.append(getFullyQualifiedName(rawType, runtime));
            // type arguments
            sb.append("<");
            boolean firstTypeArgument = true;
            for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
                if (!firstTypeArgument) {
                    sb.append(",");
                }
                firstTypeArgument = false;
                sb.append(getFullyQualifiedName(typeArgument, runtime));
            }
            sb.append(">");
            // done
            return sb.toString();
        }
        // WildcardType
        if (type instanceof WildcardType) {
            WildcardType wildcardType = (WildcardType) type;
            return "? extends " + getFullyQualifiedName(wildcardType.getUpperBounds()[0], runtime);
        }
        // TypeVariable
        TypeVariable<?> typeVariable = (TypeVariable<?>) type;
        return typeVariable.getName();
    }

    /**
     * Appends fully qualified names of given parameter types (appends also <code>"()"</code>).
     */
    private static void appendParameterTypes(StringBuilder buffer, Type[] parameterTypes) {
        buffer.append('(');
        boolean firstParameter = true;
        for (Type parameterType : parameterTypes) {
            if (firstParameter) {
                firstParameter = false;
            } else {
                buffer.append(',');
            }
            buffer.append(getFullyQualifiedName(parameterType, false));
        }
        buffer.append(')');
    }

    /**
     * Same as {@link Class#getCanonicalName()}.
     * <p>
     * GWT has problems with canonical name of inner class, so we need such workaround.
     * http://code.google.com/p/google-web-toolkit/issues/detail?id=4346
     */
    public static String getCanonicalName(Class<?> clazz) {
        return getFullyQualifiedName(clazz, false);
    }

    /**
     * Returns the short name of {@link Class}, or same name for simple type name.
     * 
     * <pre>
      * getShortName("javax.swing.JPanel")  = "JPanel"
      * getShortName("boolean")             = "boolean"
      * </pre>
     * 
     * @return the short name of given {@link Class}.
     */
    public static String getShortName(Class<?> clazz) {
        String className = getFullyQualifiedName(clazz, false);
        // member Class
        if (clazz.isMemberClass()) {
            Class<?> topClass = getTopLevelClass(clazz);
            String topName = topClass.getName();
            String topPackage = StringUtils.substringBeforeLast(topName, ".") + ".";
            return className.substring(topPackage.length());
        }
        // normal top level Class, may be array
        if (className.indexOf('.') != -1) {
            return StringUtils.substringAfterLast(className, ".");
        }
        // primitive or default package
        return className;
    }

    private static Class<?> getTopLevelClass(Class<?> clazz) {
        while (clazz.isMemberClass()) {
            clazz = clazz.getEnclosingClass();
        }
        return clazz;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Modifiers
    //
    ////////////////////////////////////////////////////////////////////////////
    public static boolean isPublic(Constructor<?> constructor) {
        return Modifier.isPublic(constructor.getModifiers());
    }

    public static boolean isProtected(Constructor<?> constructor) {
        return Modifier.isProtected(constructor.getModifiers());
    }

    public static boolean isPrivate(Constructor<?> constructor) {
        return Modifier.isPrivate(constructor.getModifiers());
    }

    public static boolean isPackagePrivate(Constructor<?> constructor) {
        return !isPublic(constructor) && !isProtected(constructor) && !isPrivate(constructor);
    }

    public static boolean isPublic(Method method) {
        return Modifier.isPublic(method.getModifiers());
    }

    public static boolean isProtected(Method method) {
        return Modifier.isProtected(method.getModifiers());
    }

    public static boolean isPrivate(Method method) {
        return Modifier.isPrivate(method.getModifiers());
    }

    public static boolean isPackagePrivate(Method method) {
        return !isPublic(method) && !isProtected(method) && !isPrivate(method);
    }

    public static boolean isAbstract(Method method) {
        return Modifier.isAbstract(method.getModifiers());
    }

    public static boolean isPublic(Field field) {
        return Modifier.isPublic(field.getModifiers());
    }

    public static boolean isProtected(Field field) {
        return Modifier.isProtected(field.getModifiers());
    }

    public static boolean isPrivate(Field field) {
        return Modifier.isPrivate(field.getModifiers());
    }

    public static boolean isPackagePrivate(Field field) {
        return !isPublic(field) && !isProtected(field) && !isPrivate(field);
    }

    public static boolean isStatic(Field field) {
        return Modifier.isStatic(field.getModifiers());
    }

    public static boolean isAbstract(Class<?> clazz) {
        return Modifier.isAbstract(clazz.getModifiers());
    }

    public static boolean isPublic(Class<?> clazz) {
        return Modifier.isPublic(clazz.getModifiers());
    }

    public static boolean isProtected(Class<?> clazz) {
        return Modifier.isProtected(clazz.getModifiers());
    }

    public static boolean isPrivate(Class<?> clazz) {
        return Modifier.isPrivate(clazz.getModifiers());
    }

    public static boolean isPackagePrivate(Class<?> clazz) {
        return !isPublic(clazz) && !isProtected(clazz) && !isPrivate(clazz);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Method
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return all declared {@link Method}'s, including protected and private.
     */
    public static Map<String, Method> getMethods(Class<?> clazz) {
        Map<String, Method> methods = Maps.newHashMap();
        // process classes
        for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
            for (Method method : c.getDeclaredMethods()) {
                String signature = getMethodSignature(method);
                if (!methods.containsKey(signature)) {
                    method.setAccessible(true);
                    methods.put(signature, method);
                }
            }
        }
        // process interfaces
        for (Class<?> interfaceClass : clazz.getInterfaces()) {
            for (Method method : interfaceClass.getDeclaredMethods()) {
                String signature = getMethodSignature(method);
                if (!methods.containsKey(signature)) {
                    method.setAccessible(true);
                    methods.put(signature, method);
                }
            }
        }
        // done
        return methods;
    }

    /**
     * @return signature for given {@link Method}. This signature is not same signature as in JVM or
     *         JDT, just some string that unique identifies method in its {@link Class}.
     */
    public static String getMethodSignature(Method method) {
        Assert.isNotNull(method);
        return getMethodSignature(method.getName(), method.getParameterTypes());
    }

    /**
     * Returns the signature of {@link Method} with given combination of name and parameter types.
     * This signature is not same signature as in JVM or JDT, just some string that unique identifies
     * method in its {@link Class}.
     * 
     * @param name
     *          the name of {@link Method}.
     * @param parameterTypes
     *          the types of {@link Method} parameters.
     * 
     * @return signature of {@link Method}.
     */
    public static String getMethodSignature(String name, Type... parameterTypes) {
        Assert.isNotNull(name);
        Assert.isNotNull(parameterTypes);
        //
        StringBuilder buffer = new StringBuilder();
        buffer.append(name);
        appendParameterTypes(buffer, parameterTypes);
        return buffer.toString();
    }

    /**
     * @return generic signature for given {@link Method}. This signature is not same signature as in
     *         JVM or JDT, just some string that unique identifies constructor in its {@link Class}.
     *         It uses {@link Method#getGenericParameterTypes()}, so for each {@link TypeVariable} its
     *         name will be used in signature.
     */
    public static String getMethodGenericSignature(Method method) {
        Assert.isNotNull(method);
        return getMethodSignature(method.getName(), method.getGenericParameterTypes());
    }

    /**
     * Note: you should not use this method, using {@link #getMethodBySignature(Class, String)}.
     * 
     * @return the {@link Method} for given name, or <code>null</code> if no such method found.
     */
    public static Method getMethodByName(Class<?> clazz, String name) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(name);
        for (Method method : getMethods(clazz).values()) {
            if (method.getName().equals(name)) {
                return method;
            }
        }
        return null;
    }

    private static final ClassMap<Map<String, Method>> m_getMethodBySignature = ClassMap.create();

    /**
     * Returns the {@link Method} defined in {@link Class}. This method can have any visibility, i.e.
     * we can find even protected/private methods. Can return <code>null</code> if no method with
     * given signature found.
     * 
     * @param clazz
     *          the {@link Class} to get method from it, or its superclass.
     * @param signature
     *          the signature of method in same format as {@link #getMethodSignature(Method)}.
     * 
     * @return the {@link Method} for given signature, or <code>null</code> if no such method found.
     */
    public static Method getMethodBySignature(Class<?> clazz, String signature) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(signature);
        // prepare cache
        Map<String, Method> cache = m_getMethodBySignature.get(clazz);
        if (cache == null) {
            cache = getMethods(clazz);
            m_getMethodBySignature.put(clazz, cache);
        }
        // use cache
        return cache.get(signature);
    }

    /**
     * @param signature
     *          the generic signature of {@link Method} in same format as
     *          {@link #getMethodGenericSignature(Method)}.
     * 
     * @return the {@link Method} for given signature. This constructor can have any visibility, i.e.
     *         we can find even protected/private constructors.
     */
    public static Method getMethodByGenericSignature(Class<?> clazz, String signature) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(signature);
        // check public methods
        for (Method method : clazz.getMethods()) {
            if (getMethodGenericSignature(method).equals(signature)) {
                return method;
            }
        }
        // not found
        return null;
    }

    /**
     * Returns the {@link Method} defined in {@link Class}. This method can have any visibility, i.e.
     * we can find even protected/private methods. Can return <code>null</code> if no method with
     * given combination of name/parameters found.
     * 
     * @param clazz
     *          the {@link Class} to get method from it, or its superclass.
     * @param name
     *          the name of method.
     * @param parameterTypes
     *          the array of parameter types.
     * 
     * @return the {@link Method} for given name/parameters, or <code>null</code> if no such method
     *         found.
     */
    public static Method getMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(name);
        Assert.isNotNull(parameterTypes);
        String signature = getMethodSignature(name, parameterTypes);
        return getMethodBySignature(clazz, signature);
    }

    /**
     * @return <code>true</code> if given {@link Method} is declared in {@link Class} with given name
     *         or one of its superclasses.
     */
    public static boolean isAlreadyDeclaredIn(Method method, Class<?> targetClass) {
        Class<?> declaringClass = method.getDeclaringClass();
        return declaringClass.isAssignableFrom(targetClass);
    }

    /**
     * @return <code>true</code> if two {@link Method}s have same name, but parameter types of
     *         "specific" are {@link #isMoreSpecific(Class[], Class[])} than "base".
     */
    public static boolean isMoreSpecific(Method base, Method specific) {
        return base.getName().equals(specific.getName())
                && isMoreSpecific(base.getParameterTypes(), specific.getParameterTypes());
    }

    /**
     * @return the {@link Method} which is {@link #isMoreSpecific(Method, Method)} than all others.
     */
    public static Method getMostSpecific(List<Method> methods) {
        Method mostSpecific = null;
        for (Method method : methods) {
            if (mostSpecific == null || isMoreSpecific(mostSpecific, method)) {
                mostSpecific = method;
            }
        }
        return mostSpecific;
    }

    /**
     * @return the {@link Object} result of invoking method with given signature.
     */
    public static Object invokeMethodEx(Object object, String signature, Object... arguments) {
        try {
            return invokeMethod(object, signature, arguments);
        } catch (Throwable e) {
            throw propagate(e);
        }
    }

    /**
     * @return the {@link Object} result of invoking method with given signature.
     */
    public static Object invokeMethod(Object object, String signature, Object... arguments) throws Exception {
        Assert.isNotNull(object);
        Assert.isNotNull(arguments);
        // prepare class/object
        Class<?> refClass = getRefClass(object);
        Object refObject = getRefObject(object);
        // prepare method
        Method method = getMethodBySignature(refClass, signature);
        Assert.isNotNull(method, "Can not find method " + signature + " in " + refClass);
        // do invoke
        try {
            return method.invoke(refObject, arguments);
        } catch (InvocationTargetException e) {
            throw propagate(e.getCause());
        }
    }

    /**
     * Invokes method without parameters.
     * 
     * @return the {@link Object} result of invoking method.
     */
    public static Object invokeMethod2(Object object, String name) throws Exception {
        return invokeMethod(object, name + "()");
    }

    /**
     * Invokes method with single parameter.
     * 
     * @return the {@link Object} result of invoking method.
     */
    public static Object invokeMethod2(Object object, String name, Class<?> parameterType_1, Object argument_1)
            throws Exception {
        Class<?>[] types = new Class<?>[] { parameterType_1 };
        Object[] values = new Object[] { argument_1 };
        return invokeMethod2(object, name, types, values);
    }

    /**
     * Invokes method with two parameters.
     * 
     * @return the {@link Object} result of invoking method.
     */
    public static Object invokeMethod2(Object object, String name, Class<?> parameterType_1,
            Class<?> parameterType_2, Object argument_1, Object argument_2) throws Exception {
        Class<?>[] types = new Class<?>[] { parameterType_1, parameterType_2 };
        Object[] values = new Object[] { argument_1, argument_2 };
        return invokeMethod2(object, name, types, values);
    }

    /**
     * Invokes method with three parameters.
     * 
     * @return the {@link Object} result of invoking method.
     */
    public static Object invokeMethod2(Object object, String name, Class<?> parameterType_1,
            Class<?> parameterType_2, Class<?> parameterType_3, Object argument_1, Object argument_2,
            Object argument_3) throws Exception {
        Class<?>[] types = new Class<?>[] { parameterType_1, parameterType_2, parameterType_3 };
        Object[] values = new Object[] { argument_1, argument_2, argument_3 };
        return invokeMethod2(object, name, types, values);
    }

    /**
     * Invokes method with four parameters.
     * 
     * @return the {@link Object} result of invoking method.
     */
    public static Object invokeMethod2(Object object, String name, Class<?> parameterType_1,
            Class<?> parameterType_2, Class<?> parameterType_3, Class<?> parameterType_4, Object argument_1,
            Object argument_2, Object argument_3, Object argument_4) throws Exception {
        Class<?>[] types = new Class<?>[] { parameterType_1, parameterType_2, parameterType_3, parameterType_4 };
        Object[] values = new Object[] { argument_1, argument_2, argument_3, argument_4 };
        return invokeMethod2(object, name, types, values);
    }

    /**
     * Invokes method by name and parameter types.
     * 
     * @param object
     *          the object to call, may be {@link Class} for invoking static method.
     * @param name
     *          the name of method.
     * @param parameterTypes
     *          the types of parameters.
     * @param arguments
     *          the values of argument for invocation.
     * 
     * @return the {@link Object} result of invoking method.
     */
    public static Object invokeMethod2(Object object, String name, Class<?>[] parameterTypes, Object[] arguments)
            throws Exception {
        Assert.equals(parameterTypes.length, arguments.length);
        String signature = getMethodSignature(name, parameterTypes);
        return invokeMethod(object, signature, arguments);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return signature for given {@link Constructor}. This signature is not same signature as in JVM
     *         or JDT, just some string that unique identifies constructor in its {@link Class}.
     */
    public static String getConstructorSignature(Constructor<?> constructor) {
        Assert.isNotNull(constructor);
        return getConstructorSignature(constructor.getParameterTypes());
    }

    /**
     * @return generic signature for given {@link Constructor}. This signature is not same signature
     *         as in JVM or JDT, just some string that unique identifies constructor in its
     *         {@link Class}. It uses {@link Constructor#getGenericParameterTypes()}, so for each
     *         {@link TypeVariable} its name will be used in signature.
     */
    public static String getConstructorGenericSignature(Constructor<?> constructor) {
        Assert.isNotNull(constructor);
        return getConstructorSignature(constructor.getGenericParameterTypes());
    }

    /**
     * Returns the signature of {@link Constructor} with given parameter types. This signature is not
     * same signature as in JVM or JDT, just some string that unique identifies constructor in its
     * {@link Class}.
     * 
     * @param parameterTypes
     *          the types of {@link Constructor} parameters.
     * 
     * @return signature of {@link Constructor}.
     */
    public static String getConstructorSignature(Type... parameterTypes) {
        Assert.isNotNull(parameterTypes);
        //
        StringBuilder buffer = new StringBuilder();
        buffer.append("<init>");
        appendParameterTypes(buffer, parameterTypes);
        return buffer.toString();
    }

    /**
     * @param signature
     *          the signature of {@link Constructor} in same format as
     *          {@link #getConstructorSignature(Constructor)} .
     * 
     * @return the {@link Constructor} for given signature. This constructor can have any visibility,
     *         i.e. we can find even protected/private constructors.
     */
    @SuppressWarnings("unchecked")
    public static <T> Constructor<T> getConstructorBySignature(Class<T> clazz, String signature) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(signature);
        // check all declared constructors
        for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
            if (getConstructorSignature(constructor).equals(signature)) {
                constructor.setAccessible(true);
                return (Constructor<T>) constructor;
            }
        }
        // not found
        return null;
    }

    /**
     * @param signature
     *          the generic signature of {@link Constructor} in same format as
     *          {@link #getConstructorGenericSignature(Constructor)}.
     * 
     * @return the {@link Constructor} for given signature. This constructor can have any visibility,
     *         i.e. we can find even protected/private constructors.
     */
    @SuppressWarnings("unchecked")
    public static <T> Constructor<T> getConstructorByGenericSignature(Class<T> clazz, String signature) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(signature);
        // check all declared constructors
        for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
            if (getConstructorGenericSignature(constructor).equals(signature)) {
                constructor.setAccessible(true);
                return (Constructor<T>) constructor;
            }
        }
        // not found
        return null;
    }

    /**
     * @param parameterTypes
     *          the array of parameter types.
     * 
     * @return the {@link Constructor} for given signature. This constructor can have any visibility,
     *         i.e. we can find even protected/private constructors.
     */
    public static <T> Constructor<T> getConstructor(Class<T> clazz, Class<?>... parameterTypes) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(parameterTypes);
        String signature = getConstructorSignature(parameterTypes);
        return getConstructorBySignature(clazz, signature);
    }

    /**
     * @return the {@link Constructor} which can be invoked with given arguments, may be
     *         <code>null</code>.
     */
    @SuppressWarnings("unchecked")
    public static <T> Constructor<T> getConstructorForArguments(Class<T> clazz, Object... arguments) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(arguments);
        for (Constructor<?> constructor : clazz.getConstructors()) {
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            // check compatibility of each parameter and argument
            boolean canUse = true;
            if (parameterTypes.length == arguments.length) {
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    Object argument = arguments[i];
                    if (!isAssignableFrom(parameterType, argument)) {
                        canUse = false;
                        break;
                    }
                }
                // use if possible
                if (canUse) {
                    return (Constructor<T>) constructor;
                }
            }
        }
        // no compatible constructor
        return null;
    }

    /**
     * @return <code>true</code> if two given {@link Constructor}-s represent same constructor.
     */
    public static boolean equals(Constructor<?> constructor_1, Constructor<?> constructor_2) {
        if (constructor_1 == constructor_2) {
            return true;
        }
        return constructor_1.getDeclaringClass() == constructor_2.getDeclaringClass() && ObjectUtils
                .equals(getConstructorSignature(constructor_1), getConstructorSignature(constructor_2));
    }

    /**
     * @return the {@link Constructor} with minimal number of parameters.
     */
    public static Constructor<?> getShortestConstructor(Class<?> clazz) {
        Constructor<?> shortest = null;
        int minCount = Integer.MAX_VALUE;
        for (Constructor<?> constructor : clazz.getConstructors()) {
            int thisCount = constructor.getParameterTypes().length;
            if (minCount > thisCount) {
                shortest = constructor;
                minCount = thisCount;
            }
        }
        return shortest;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Field
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return all declared {@link Field}'s, including protected and private.
     */
    public static List<Field> getFields(Class<?> clazz) {
        List<Field> fields = Lists.newArrayList();
        while (clazz != null) {
            // add all declared field
            for (Field field : clazz.getDeclaredFields()) {
                field.setAccessible(true);
                fields.add(field);
            }
            // process superclass
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    /**
     * @return the {@link Field} of given class with given name or <code>null</code> if no such
     *         {@link Field} found.
     */
    public static Field getFieldByName(Class<?> clazz, String name) {
        Assert.isNotNull(clazz);
        Assert.isNotNull(name);
        // check fields of given class and its super classes
        while (clazz != null) {
            // check all declared field
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.getName().equals(name)) {
                    field.setAccessible(true);
                    return field;
                }
            }
            // check interfaces
            {
                Class<?>[] interfaceClasses = clazz.getInterfaces();
                for (Class<?> interfaceClass : interfaceClasses) {
                    Field field = getFieldByName(interfaceClass, name);
                    if (field != null) {
                        return field;
                    }
                }
            }
            // check superclass
            clazz = clazz.getSuperclass();
        }
        // not found
        return null;
    }

    /**
     * @return the {@link Object} value of field with given name.
     */
    public static Object getFieldObject(final Object object, final String name) {
        Assert.isNotNull(object);
        Assert.isNotNull(name);
        return ExecutionUtils.runObject(new RunnableObjectEx<Object>() {
            public Object runObject() throws Exception {
                Class<?> refClass = getRefClass(object);
                Object refObject = getRefObject(object);
                Field field = getFieldByName(refClass, name);
                if (field == null) {
                    throw new IllegalArgumentException("Unable to find '" + name + "' in " + refClass);
                }
                return field.get(refObject);
            }
        });
    }

    /**
     * @return the {@link String} value of field with given name.
     */
    public static String getFieldString(Object object, String name) {
        return (String) getFieldObject(object, name);
    }

    /**
     * @return the <code>int</code> value of field with given name.
     */
    public static int getFieldInt(Object object, String name) {
        return (Integer) getFieldObject(object, name);
    }

    /**
     * @return the <code>short</code> value of field with given name.
     */
    public static short getFieldShort(Object object, String name) {
        return (Short) getFieldObject(object, name);
    }

    /**
     * @return the <code>long</code> value of field with given name.
     */
    public static long getFieldLong(Object object, String name) {
        return (Long) getFieldObject(object, name);
    }

    /**
     * @return the <code>float</code> value of field with given name.
     */
    public static float getFieldFloat(Object object, String name) {
        return (Float) getFieldObject(object, name);
    }

    /**
     * @return the <code>boolean</code> value of field with given name.
     */
    public static boolean getFieldBoolean(Object object, String name) {
        return (Boolean) getFieldObject(object, name);
    }

    /**
     * Sets {@link Object} value of field with given name.
     */
    public static void setField(Object object, String name, Object value) {
        try {
            Assert.isNotNull(object);
            Class<?> refClass = getRefClass(object);
            Object refObject = getRefObject(object);
            getFieldByName(refClass, name).set(refObject, value);
        } catch (Throwable e) {
            throw propagate(e);
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Utils
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return the {@link Class} of given {@link Object} or casted object, if it is {@link Class}
     *         itself.
     */
    private static Class<?> getRefClass(Object object) {
        return object instanceof Class<?> ? (Class<?>) object : object.getClass();
    }

    /**
     * @return the {@link Object} that should be used as argument for {@link Field#get(Object)} and
     *         {@link Method#invoke(Object, Object[])}.
     */
    private static Object getRefObject(Object object) {
        return object instanceof Class<?> ? null : object;
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Throwable propagation
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * Helper class used in {@link #propagate(Throwable)}.
     */
    private static class ExceptionThrower {
        private static Throwable throwable;

        private ExceptionThrower() throws Throwable {
            if (System.getProperty("wbp.ReflectionUtils.propagate().InstantiationException") != null) {
                throw new InstantiationException();
            }
            if (System.getProperty("wbp.ReflectionUtils.propagate().IllegalAccessException") != null) {
                throw new IllegalAccessException();
            }
            throw throwable;
        }

        public static synchronized void spit(Throwable t) {
            if (System.getProperty("wbp.ReflectionUtils.propagate().dontThrow") == null) {
                ExceptionThrower.throwable = t;
                try {
                    ExceptionThrower.class.newInstance();
                } catch (InstantiationException e) {
                } catch (IllegalAccessException e) {
                } finally {
                    ExceptionThrower.throwable = null;
                }
            }
        }
    }

    /**
     * Propagates {@code throwable} as-is without any wrapping. This is trick.
     * 
     * @return nothing will ever be returned; this return type is only for your convenience, to use
     *         this method in "throw" statement.
     */
    public static RuntimeException propagate(Throwable throwable) {
        if (System.getProperty("wbp.ReflectionUtils.propagate().forceReturn") == null) {
            ExceptionThrower.spit(throwable);
        }
        return null;
    }

    /**
     * @return the {@link Exception} that is given {@link Throwable} or wraps it.
     */
    public static Exception getExceptionToThrow(Throwable e) {
        if (e instanceof Exception) {
            return (Exception) e;
        }
        return new Exception(e);
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // PropertyDescriptor
    //
    ////////////////////////////////////////////////////////////////////////////
    /**
     * @return the {@link BeanInfo} of given component {@link Class}.
     */
    public static BeanInfo getBeanInfo(Class<?> clazz) throws IntrospectionException {
        // standard components don't have BeanInfo's
        {
            String className = clazz.getName();
            if (className.startsWith("java.lang.") || className.startsWith("java.awt.")
                    || className.startsWith("javax.swing.") || className.startsWith("org.eclipse.swt")
                    || className.startsWith("org.eclipse.jface") || className.startsWith("org.eclipse.ui.forms")) {
                return null;
            }
        }
        // OK, get BeanInfo (may be not trivial)
        Introspector.flushCaches();
        String[] standard_beanInfoSearchPath = Introspector.getBeanInfoSearchPath();
        try {
            Introspector.setBeanInfoSearchPath(new String[] {});
            return Introspector.getBeanInfo(clazz);
        } finally {
            Introspector.flushCaches();
            Introspector.setBeanInfoSearchPath(standard_beanInfoSearchPath);
        }
    }

    private static ClassMap<List<PropertyDescriptor>> m_propertyDescriptorsCache = ClassMap.create();

    /**
     * Flushes cache for specified {@link Class}.
     */
    public static void flushPropertyDescriptorsCache(Class<?> clazz) {
        m_propertyDescriptorsCache.remove(clazz);
    }

    /**
     * @return the {@link PropertyDescriptor}'s for given {@link Class}.
     */
    public static List<PropertyDescriptor> getPropertyDescriptors(BeanInfo beanInfo, Class<?> componentClass)
            throws Exception {
        // check cache
        {
            List<PropertyDescriptor> descriptors = m_propertyDescriptorsCache.get(componentClass);
            if (descriptors != null) {
                return descriptors;
            }
        }
        // prepare descriptions
        List<PropertyDescriptor> descriptors = Lists.newArrayList();
        // if there is BeanInfo, try to use it
        if (beanInfo != null) {
            Collections.addAll(descriptors, beanInfo.getPropertyDescriptors());
            // remove indexed properties
            for (Iterator<PropertyDescriptor> I = descriptors.iterator(); I.hasNext();) {
                PropertyDescriptor descriptor = I.next();
                if (descriptor instanceof IndexedPropertyDescriptor) {
                    I.remove();
                }
            }
        }
        // prepare getters/setters
        Map<String, Method> propertyToGetter = Maps.newTreeMap();
        Map<String, Method> propertyToSetter = Maps.newTreeMap();
        // append existing getters/setters
        for (PropertyDescriptor propertyDescriptor : descriptors) {
            Method readMethod = getReadMethod(propertyDescriptor);
            Method writeMethod = getWriteMethod(propertyDescriptor);
            if (readMethod != null) {
                String propertyName = getQualifiedPropertyName(readMethod);
                propertyToGetter.put(propertyName, readMethod);
                propertyDescriptor.setName(propertyName);
            }
            if (writeMethod != null) {
                String propertyName = getQualifiedPropertyName(writeMethod);
                propertyToSetter.put(propertyName, writeMethod);
                propertyDescriptor.setName(propertyName);
            }
        }
        // append missing methods (most probably protected)
        Set<String> newPropertyNames = Sets.newTreeSet();
        appendPropertyComponents(componentClass, newPropertyNames, propertyToGetter, propertyToSetter);
        // create PropertyDescriptor's for new getters/setters
        for (String propertyName : newPropertyNames) {
            addPropertyDescriptor(descriptors, propertyName, propertyToGetter, propertyToSetter);
        }
        useSimplePropertyNamesWherePossible(descriptors);
        makeMethodsAccessible(descriptors);
        // OK, final result
        m_propertyDescriptorsCache.put(componentClass, descriptors);
        return descriptors;
    }

    private static void useSimplePropertyNamesWherePossible(List<PropertyDescriptor> descriptors) {
        // prepare map: simple name -> qualified names
        Multimap<String, String> simplePropertyNames = HashMultimap.create();
        for (PropertyDescriptor propertyDescriptor : descriptors) {
            String qualifiedPropertyName = propertyDescriptor.getName();
            String simplePropertyName = getSimplePropertyName(qualifiedPropertyName);
            simplePropertyNames.put(simplePropertyName, qualifiedPropertyName);
        }
        // if simple name is unique, use it
        for (PropertyDescriptor propertyDescriptor : descriptors) {
            String qualifiedPropertyName = propertyDescriptor.getName();
            String simplePropertyName = getSimplePropertyName(qualifiedPropertyName);
            if (simplePropertyNames.get(simplePropertyName).size() == 1) {
                propertyDescriptor.setName(simplePropertyName);
            }
        }
    }

    private static void makeMethodsAccessible(List<PropertyDescriptor> descriptors) {
        for (PropertyDescriptor propertyDescriptor : descriptors) {
            Method getMethod = getReadMethod(propertyDescriptor);
            Method setMethod = getWriteMethod(propertyDescriptor);
            if (getMethod != null) {
                getMethod.setAccessible(true);
            }
            if (setMethod != null) {
                setMethod.setAccessible(true);
            }
        }
    }

    private static void addPropertyDescriptor(List<PropertyDescriptor> descriptors, String qualifiedPropertyName,
            Map<String, Method> propertyToGetter, Map<String, Method> propertyToSetter) throws Exception {
        if (qualifiedPropertyName.startsWith("(")) {
            return;
        }
        // prepare methods
        Method getMethod = propertyToGetter.get(qualifiedPropertyName);
        Method setMethod = propertyToSetter.get(qualifiedPropertyName);
        if (!isValidForJavaIBM(getMethod) || !isValidForJavaIBM(setMethod)) {
            return;
        }
        if (getMethod != null && getMethod.getReturnType() == Void.TYPE) {
            return;
        }
        // add property
        descriptors.add(new PropertyDescriptor(qualifiedPropertyName, getMethod, setMethod));
    }

    /**
     * IMB Java does not allow to create {@link PropertyDescriptor} for non-public methods. See (Case
     * 37683).
     */
    private static boolean isValidForJavaIBM(Method method) {
        if (method == null) {
            return true;
        }
        return !EnvironmentUtils.isJavaIBM() || isPublic(method);
    }

    /**
     * Appends components for {@link PropertyDescriptor}'s of given {@link Class}, its super class and
     * implemented interfaces.
     */
    private static void appendPropertyComponents(Class<?> currentClass, Set<String> newPropertyNames,
            Map<String, Method> propertyToGetter, Map<String, Method> propertyToSetter) {
        for (Method method : currentClass.getDeclaredMethods()) {
            int methodModifiers = method.getModifiers();
            boolean isPublic = Modifier.isPublic(methodModifiers);
            boolean isProtected = Modifier.isProtected(methodModifiers);
            boolean isStatic = Modifier.isStatic(methodModifiers);
            if (method.isBridge()) {
                continue;
            }
            if (!isStatic && (isPublic || isProtected)) {
                method.setAccessible(true);
                String methodName = method.getName();
                if (methodName.startsWith("set") && method.getParameterTypes().length == 1) {
                    String propertyName = getQualifiedPropertyName(method);
                    if (!propertyToSetter.containsKey(propertyName)) {
                        newPropertyNames.add(propertyName);
                        propertyToSetter.put(propertyName, method);
                    }
                }
                if (method.getParameterTypes().length == 0) {
                    if (methodName.startsWith("get")) {
                        String propertyName = getQualifiedPropertyName(method);
                        if (!propertyToGetter.containsKey(propertyName)) {
                            newPropertyNames.add(propertyName);
                            propertyToGetter.put(propertyName, method);
                        }
                    }
                    if (methodName.startsWith("is")) {
                        String propertyName = getQualifiedPropertyName(method);
                        if (!propertyToGetter.containsKey(propertyName)) {
                            newPropertyNames.add(propertyName);
                            propertyToGetter.put(propertyName, method);
                        }
                    }
                }
            }
        }
        // process interfaces
        for (Class<?> interfaceClass : currentClass.getInterfaces()) {
            appendPropertyComponents(interfaceClass, newPropertyNames, propertyToGetter, propertyToSetter);
        }
        // process super Class
        if (currentClass.getSuperclass() != null) {
            appendPropertyComponents(currentClass.getSuperclass(), newPropertyNames, propertyToGetter,
                    propertyToSetter);
        }
    }

    private static String getQualifiedPropertyName(Method method) {
        // strip Method name to property name
        String propertyName;
        {
            propertyName = method.getName();
            if (propertyName.startsWith("is")) {
                propertyName = propertyName.substring(2);
            } else if (propertyName.startsWith("get")) {
                propertyName = propertyName.substring(3);
            } else if (propertyName.startsWith("set")) {
                propertyName = propertyName.substring(3);
            }
            propertyName = Introspector.decapitalize(propertyName);
        }
        // include also type
        String types;
        {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 0) {
                types = "(" + getFullyQualifiedName(method.getReturnType(), false) + ")";
            } else {
                StringBuilder buffer = new StringBuilder();
                appendParameterTypes(buffer, parameterTypes);
                types = buffer.toString();
            }
        }
        // return qualified property name
        return propertyName + types;
    }

    private static String getSimplePropertyName(String qualifiedPropertyName) {
        return StringUtils.substringBefore(qualifiedPropertyName, "(");
    }
}