Correctly resolve accessors for static properties on Class
Issue: SPR-11609
This commit is contained in:
@@ -186,9 +186,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObject.getValue(), eContext.getPropertyAccessors());
|
||||||
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors());
|
|
||||||
|
|
||||||
// Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
|
// Go through the accessors that may be able to resolve it. If they are a cacheable accessor then
|
||||||
// get the accessor and use it. If they are not cacheable but report they can read the property
|
// get the accessor and use it. If they are not cacheable but report they can read the property
|
||||||
// then ask them to read it
|
// then ask them to read it
|
||||||
@@ -214,7 +212,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, name,
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE, name,
|
||||||
FormatHelper.formatClassNameForMessage(contextObjectClass));
|
FormatHelper.formatClassNameForMessage(getObjectClass(contextObject.getValue())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,9 +234,7 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> contextObjectClass = getObjectClass(contextObject.getValue());
|
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObject.getValue(), eContext.getPropertyAccessors());
|
||||||
|
|
||||||
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObjectClass, eContext.getPropertyAccessors());
|
|
||||||
if (accessorsToTry != null) {
|
if (accessorsToTry != null) {
|
||||||
try {
|
try {
|
||||||
for (PropertyAccessor accessor : accessorsToTry) {
|
for (PropertyAccessor accessor : accessorsToTry) {
|
||||||
@@ -259,18 +255,16 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE, name,
|
throw new SpelEvaluationException(getStartPosition(), SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE, name,
|
||||||
FormatHelper.formatClassNameForMessage(contextObjectClass));
|
FormatHelper.formatClassNameForMessage(getObjectClass(contextObject.getValue())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext eContext) throws SpelEvaluationException {
|
public boolean isWritableProperty(String name, TypedValue contextObject, EvaluationContext eContext) throws SpelEvaluationException {
|
||||||
Object contextObjectValue = contextObject.getValue();
|
List<PropertyAccessor> accessorsToTry = getPropertyAccessorsToTry(contextObject.getValue(), eContext.getPropertyAccessors());
|
||||||
// TypeDescriptor td = state.getActiveContextObject().getTypeDescriptor();
|
if (accessorsToTry != null) {
|
||||||
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObjectValue), eContext.getPropertyAccessors());
|
for (PropertyAccessor accessor : accessorsToTry) {
|
||||||
if (resolversToTry != null) {
|
|
||||||
for (PropertyAccessor pfResolver : resolversToTry) {
|
|
||||||
try {
|
try {
|
||||||
if (pfResolver.canWrite(eContext, contextObjectValue, name)) {
|
if (accessor.canWrite(eContext, contextObject.getValue(), name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,27 +284,28 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
|||||||
* the start of the list. In addition, there are specific resolvers that exactly name the class in question and
|
* the start of the list. In addition, there are specific resolvers that exactly name the class in question and
|
||||||
* resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the
|
* resolvers that name a specific class but it is a supertype of the class we have. These are put at the end of the
|
||||||
* specific resolvers set and will be tried after exactly matching accessors but before generic accessors.
|
* specific resolvers set and will be tried after exactly matching accessors but before generic accessors.
|
||||||
* @param targetType the type upon which property access is being attempted
|
* @param contextObject the object upon which property access is being attempted
|
||||||
* @return a list of resolvers that should be tried in order to access the property
|
* @return a list of resolvers that should be tried in order to access the property
|
||||||
*/
|
*/
|
||||||
private List<PropertyAccessor> getPropertyAccessorsToTry(Class<?> targetType, List<PropertyAccessor> propertyAccessors) {
|
private List<PropertyAccessor> getPropertyAccessorsToTry(Object contextObject, List<PropertyAccessor> propertyAccessors) {
|
||||||
|
Class<?> targetType = (contextObject != null ? contextObject.getClass() : null);
|
||||||
|
|
||||||
List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
|
List<PropertyAccessor> specificAccessors = new ArrayList<PropertyAccessor>();
|
||||||
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
|
List<PropertyAccessor> generalAccessors = new ArrayList<PropertyAccessor>();
|
||||||
for (PropertyAccessor resolver : propertyAccessors) {
|
for (PropertyAccessor resolver : propertyAccessors) {
|
||||||
Class<?>[] targets = resolver.getSpecificTargetClasses();
|
Class<?>[] targets = resolver.getSpecificTargetClasses();
|
||||||
if (targets == null) { // generic resolver that says it can be used for any type
|
if (targets == null) {
|
||||||
|
// generic resolver that says it can be used for any type
|
||||||
generalAccessors.add(resolver);
|
generalAccessors.add(resolver);
|
||||||
}
|
}
|
||||||
else {
|
else if (targetType != null) {
|
||||||
if (targetType != null) {
|
for (Class<?> clazz : targets) {
|
||||||
for (Class<?> clazz : targets) {
|
if (clazz == targetType) {
|
||||||
if (clazz == targetType) {
|
specificAccessors.add(resolver);
|
||||||
specificAccessors.add( resolver);
|
break;
|
||||||
break;
|
}
|
||||||
}
|
else if (clazz.isAssignableFrom(targetType)) {
|
||||||
else if (clazz.isAssignableFrom(targetType)) {
|
generalAccessors.add(resolver);
|
||||||
generalAccessors.add(resolver);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,6 +286,11 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
|
|
||||||
static class MapAccessor implements PropertyAccessor {
|
static class MapAccessor implements PropertyAccessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?>[] getSpecificTargetClasses() {
|
||||||
|
return new Class<?>[] {Map.class};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
|
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
|
||||||
return (((Map<?, ?>) target).containsKey(name));
|
return (((Map<?, ?>) target).containsKey(name));
|
||||||
@@ -306,11 +311,6 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
|
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
|
||||||
((Map<String, Object>) target).put(name, newValue);
|
((Map<String, Object>) target).put(name, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?>[] getSpecificTargetClasses() {
|
|
||||||
return new Class[] { Map.class };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1774,10 +1774,10 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
public void SPR10486() throws Exception {
|
public void SPR10486() throws Exception {
|
||||||
SpelExpressionParser parser = new SpelExpressionParser();
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||||
SPR10486 rootObject = new SPR10486();
|
Spr10486 rootObject = new Spr10486();
|
||||||
Expression classNameExpression = parser.parseExpression("class.name");
|
Expression classNameExpression = parser.parseExpression("class.name");
|
||||||
Expression nameExpression = parser.parseExpression("name");
|
Expression nameExpression = parser.parseExpression("name");
|
||||||
assertThat(classNameExpression.getValue(context, rootObject), equalTo((Object) SPR10486.class.getName()));
|
assertThat(classNameExpression.getValue(context, rootObject), equalTo((Object) Spr10486.class.getName()));
|
||||||
assertThat(nameExpression.getValue(context, rootObject), equalTo((Object) "name"));
|
assertThat(nameExpression.getValue(context, rootObject), equalTo((Object) "name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1785,7 +1785,7 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
public void SPR11142() throws Exception {
|
public void SPR11142() throws Exception {
|
||||||
SpelExpressionParser parser = new SpelExpressionParser();
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||||
SPR11142 rootObject = new SPR11142();
|
Spr11142 rootObject = new Spr11142();
|
||||||
Expression expression = parser.parseExpression("something");
|
Expression expression = parser.parseExpression("something");
|
||||||
thrown.expect(SpelEvaluationException.class);
|
thrown.expect(SpelEvaluationException.class);
|
||||||
thrown.expectMessage("'something' cannot be found");
|
thrown.expectMessage("'something' cannot be found");
|
||||||
@@ -1837,26 +1837,6 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
assertEquals(1, expr.getValue(context));
|
assertEquals(1, expr.getValue(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class Spr11445Class implements BeanResolver {
|
|
||||||
|
|
||||||
private final AtomicInteger counter = new AtomicInteger();
|
|
||||||
|
|
||||||
public int echo(int invocation) {
|
|
||||||
return invocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int parameter() {
|
|
||||||
return counter.incrementAndGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
|
|
||||||
return beanName.equals("bean") ? this : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void SPR11494() {
|
public void SPR11494() {
|
||||||
Expression exp = new SpelExpressionParser().parseExpression("T(java.util.Arrays).asList('a','b')");
|
Expression exp = new SpelExpressionParser().parseExpression("T(java.util.Arrays).asList('a','b')");
|
||||||
@@ -1864,6 +1844,15 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
assertThat(list.size(), is(2));
|
assertThat(list.size(), is(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void SPR11609() {
|
||||||
|
StandardEvaluationContext sec = new StandardEvaluationContext();
|
||||||
|
sec.addPropertyAccessor(new MapAccessor());
|
||||||
|
Expression exp = new SpelExpressionParser().parseExpression(
|
||||||
|
"T(org.springframework.expression.spel.SpelReproTests$MapWithConstant).X");
|
||||||
|
assertEquals(1, exp.getValue(sec));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static enum ABC { A, B, C }
|
private static enum ABC { A, B, C }
|
||||||
|
|
||||||
@@ -1939,7 +1928,7 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class SPR10486 {
|
public static class Spr10486 {
|
||||||
|
|
||||||
private String name = "name";
|
private String name = "name";
|
||||||
|
|
||||||
@@ -1953,7 +1942,7 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class SPR11142 {
|
static class Spr11142 {
|
||||||
|
|
||||||
public String isSomething() {
|
public String isSomething() {
|
||||||
return "";
|
return "";
|
||||||
@@ -1983,4 +1972,29 @@ public class SpelReproTests extends AbstractExpressionTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static class Spr11445Class implements BeanResolver {
|
||||||
|
|
||||||
|
private final AtomicInteger counter = new AtomicInteger();
|
||||||
|
|
||||||
|
public int echo(int invocation) {
|
||||||
|
return invocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int parameter() {
|
||||||
|
return counter.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
|
||||||
|
return beanName.equals("bean") ? this : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class MapWithConstant extends HashMap {
|
||||||
|
|
||||||
|
public static final int X = 1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user