Skip to content

OgnlRuntime.findParameterTypes() is wrong #1

@ahecebs

Description

@ahecebs

I have a class that has 2 parameters:
Configure<Parent, Child>
which has method
setParent(Parent p)
and a concrete implementation ConfigureProfileItems extends Configure<Profile, Item>.

When OgnlRuntime enters the findParameterTypes() method the first time it determines that the parameter type should be {Profile} and caches it appropriately. The second time it enters this method it looks up the generic methods cache and compares this to the GenericTypes of the strutsAction.getGenericSuperClass() i.e. comparing {Profile} to {Profile, Item}. This fails and therefore has to recomputed every single time for this method.

Furthermore I have another class that extends ConfigureProfileItems therefore type.getGenericSuperclass() == null, so it is determined that the setParent() only parameter is an Object when the only parameter should be a Profile (since it extends ConfigureProfileItems).

Here is a suggested fix for this method that will determine the parameter types correctly and cache them correctly.

public class OgnlRuntime {

private static class GenericMethodParamKey {
    Class<?> actionClass;
    Method m;

    private GenericMethodParamKey(Class<?> actionClass, Method m) {
        this.actionClass = actionClass;
        this.m = m;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((actionClass == null) ? 0 : actionClass.getName().hashCode());
        result = prime * result + ((m == null) ? 0 : m.hashCode());
        return result;
    }
}
private static Hashtable<Method, Class<?>[]> nonGenericMethodParams = new Hashtable<Method, Class<?>[]>();
private static Hashtable<GenericMethodParamKey, Class<?>[]> genericMethodParams = new Hashtable<GenericMethodParamKey, Class<?>[]>();

public static Class<?>[] findParameterTypes(Class<?> clazz, Method m) {

    // Check the non generic methods
    Class<?>[] types = nonGenericMethodParams.get(m);
    if (types != null) {
        return types;
    }

    // Check the generic methods
    GenericMethodParamKey key = new GenericMethodParamKey(clazz, m);
    types = genericMethodParams.get(key);
    if (types != null) {
        return types;
    }

    Type[] params = determineMethodParams(clazz, m);
    types = new Class<?>[params.length];
    boolean nonGeneric = true;

    // replace any parameters on the method with their bound types
    for (int i = 0; i < types.length; i++) {
        Type type = params[i];
        if (type instanceof Class<?>) {
            Class<?> paramClass = (Class<?>) type;
            types[i] = paramClass;
        } else {
            Class<?> paramClass = m.getParameterTypes()[i];
            types[i] = paramClass;
        }
        nonGeneric =  nonGeneric && types[i] == m.getParameterTypes()[i];
    }

    if (nonGeneric) {
        nonGenericMethodParams.put(m, types);
    } else {
        genericMethodParams.put(key, types);
    }

    return types;
}

/**
 * This method gets the method parameters for method m called from class clazz, replacing any generic types
 * that are class-wide parameters.
 * 
 * If the method has any parameterization of its own, these types are still present in the result
 * @param clazz
 * @param m
 * @return
 */
private static Type[] determineMethodParams(Class<?> clazz, Method m) {

    // If this class is the methods declaring class then get the generic method parameters
    if (m.getDeclaringClass() == clazz) {
        return getMethodParameterTypes(m);
    }

    // Get the parameter types as defined in clazz's super class
    Class<?> superClazz = clazz.getSuperclass();
    Type[] types = determineMethodParams(superClazz, m);

    // replace the parameter types with overriding clazz's parameters
    Type superClass = clazz.getGenericSuperclass();
    if (superClass != null && ParameterizedType.class.isInstance(superClass)) {
        ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) superClass;

        // Compare Class2<S,T> and Class1<E,F> extends Class2<F,Integer> to replace
        // any Class2 variable S with Class1 variable F and Class2 variable T with java.lang.Integer
        Type[] actualTypes = parameterizedType.getActualTypeArguments();
        TypeVariable<?>[] classTypes = clazz.getSuperclass().getTypeParameters();

        for (int i = 0; i < types.length; i++) {
            Type type = types[i];

            for (int j = 0; j < actualTypes.length; j++) {
                TypeVariable<?> classType = classTypes[j];
                if (type.toString().equals(classType.getName())) {

                    // Generic method parameter is being overridden by subclass
                    types[i] = actualTypes[j];
                }
            }
        }
    }
    return types;
}

/**
 * This method gets the generic parameter types of a method 
 * @param method
 */
private static Type[] getMethodParameterTypes(Method method) {

    // Get Generic Types
    Type[] types = method.getGenericParameterTypes();

    // Replace any bounds where method like public <T extends E> void doSomething(T t); where
    // E is a class parameter
    TypeVariable<?>[] vars = method.getTypeParameters();
    for (int i = 0; i < types.length; i++) {
        Type type = types[i];

        for (int j = 0; j < vars.length; j++) {
            TypeVariable<?> typeVariable = vars[j];
            if (typeVariable.getName().equals(type.toString())) {
                types[i] = typeVariable.getBounds()[0];

                // Reset j incase we have a case where public <T extends E, S extends T> void doSomething(S s);
                j = -1;
                type = types[i];
            }
        }
    }
    return types;
}

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions