-
-
Notifications
You must be signed in to change notification settings - Fork 87
OgnlRuntime.findParameterTypes() is wrong #1
Description
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;
}
}