Details
-
Improvement
-
Status: Open
-
Major
-
Resolution: Unresolved
-
3.0
-
None
-
None
-
Struts 2.1.8.1 (updated to use OGNL 3.0), Java 1.6
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;
}
}