Relax JavaBean rules for SpEL property access

Relax the method search algorithm used by `ReflectivePropertyAccessor`
to include methods of the form `getXY()` for properties of the form
`xy`.

Although the JavaBean specification indicates that a property `xy`
should use the accessors `getxY()` and `setxY()`, in practice many
developers choose to have an uppercase first character. The
`ReflectivePropertyAccessor` will now consider these style methods if
the traditional conventions fail to find a match.

Issue: SPR-10716
This commit is contained in:
Phillip Webb 2013-10-18 16:20:57 -07:00
parent 59fcf5014f
commit b25e91a550
2 changed files with 49 additions and 29 deletions

View File

@ -318,42 +318,34 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
* Find a getter method for the specified property. * Find a getter method for the specified property.
*/ */
protected Method findGetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) { protected Method findGetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
Method[] ms = getSortedClassMethods(clazz); return findMethodForProperty(getPropertyMethodSuffixes(propertyName),
String propertyMethodSuffix = getPropertyMethodSuffix(propertyName); new String[] { "get", "is" }, clazz, mustBeStatic, 0);
// Try "get*" method...
String getterName = "get" + propertyMethodSuffix;
for (Method method : ms) {
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
return method;
}
}
// Try "is*" method...
getterName = "is" + propertyMethodSuffix;
for (Method method : ms) {
if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 &&
(boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType())) &&
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
return method;
}
}
return null;
} }
/** /**
* Find a setter method for the specified property. * Find a setter method for the specified property.
*/ */
protected Method findSetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) { protected Method findSetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) {
return findMethodForProperty(getPropertyMethodSuffixes(propertyName),
new String[] { "set" }, clazz, mustBeStatic, 1);
}
private Method findMethodForProperty(String[] methodSuffixes, String[] prefixes, Class<?> clazz,
boolean mustBeStatic, int numberOfParams) {
Method[] methods = getSortedClassMethods(clazz); Method[] methods = getSortedClassMethods(clazz);
String setterName = "set" + getPropertyMethodSuffix(propertyName); for (String methodSuffix : methodSuffixes) {
for (String prefix : prefixes) {
for (Method method : methods) { for (Method method : methods) {
if (method.getName().equals(setterName) && method.getParameterTypes().length == 1 && if (method.getName().equals(prefix + methodSuffix)
(!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { && method.getParameterTypes().length == numberOfParams
&& (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) {
return method; return method;
} }
} }
}
}
return null; return null;
} }
/** /**
@ -370,14 +362,30 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return methods; return methods;
} }
/**
* Return the method suffixes for a given property name. The default implementation
* uses JavaBean conventions with additional support for properties of the form 'xY'
* where the method 'getXY()' is used in preference to the JavaBean convention of
* 'getxY()'.
*/
protected String[] getPropertyMethodSuffixes(String propertyName) {
String suffix = getPropertyMethodSuffix(propertyName);
if (suffix.length() > 0 && Character.isUpperCase(suffix.charAt(0))) {
return new String[] { suffix };
}
return new String[] { suffix, StringUtils.capitalize(suffix) };
}
/**
* Return the method suffix for a given property name. The default implementation
* uses JavaBean conventions.
*/
protected String getPropertyMethodSuffix(String propertyName) { protected String getPropertyMethodSuffix(String propertyName) {
if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) { if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) {
return propertyName; return propertyName;
} }
else {
return StringUtils.capitalize(propertyName); return StringUtils.capitalize(propertyName);
} }
}
/** /**
* Find a field of a certain name on a specified class * Find a field of a certain name on a specified class

View File

@ -335,6 +335,12 @@ public class ReflectionHelperTests extends ExpressionTestCase {
assertEquals("id",rpr.read(ctx,t,"Id").getValue()); assertEquals("id",rpr.read(ctx,t,"Id").getValue());
assertTrue(rpr.canRead(ctx,t,"Id")); assertTrue(rpr.canRead(ctx,t,"Id"));
// repro SPR-10994
assertEquals("xyZ",rpr.read(ctx,t,"xyZ").getValue());
assertTrue(rpr.canRead(ctx,t,"xyZ"));
assertEquals("xY",rpr.read(ctx,t,"xY").getValue());
assertTrue(rpr.canRead(ctx,t,"xY"));
// SPR-10122, ReflectivePropertyAccessor JavaBean property names compliance tests - setters // SPR-10122, ReflectivePropertyAccessor JavaBean property names compliance tests - setters
rpr.write(ctx, t, "pEBS","Test String"); rpr.write(ctx, t, "pEBS","Test String");
assertEquals("Test String",rpr.read(ctx,t,"pEBS").getValue()); assertEquals("Test String",rpr.read(ctx,t,"pEBS").getValue());
@ -429,6 +435,8 @@ public class ReflectionHelperTests extends ExpressionTestCase {
String id = "id"; String id = "id";
String ID = "ID"; String ID = "ID";
String pEBS = "pEBS"; String pEBS = "pEBS";
String xY = "xY";
String xyZ = "xyZ";
public String getProperty() { return property; } public String getProperty() { return property; }
public void setProperty(String value) { property = value; } public void setProperty(String value) { property = value; }
@ -445,6 +453,10 @@ public class ReflectionHelperTests extends ExpressionTestCase {
public String getID() { return ID; } public String getID() { return ID; }
public String getXY() { return xY; }
public String getXyZ() { return xyZ; }
public String getpEBS() { public String getpEBS() {
return pEBS; return pEBS;
} }