Skip to content

Commit 5e6044c

Browse files
committed
Fixed EL resolution against static method with changing target class
Issue: SPR-10452
1 parent 476af2e commit 5e6044c

File tree

3 files changed

+63
-31
lines changed

3 files changed

+63
-31
lines changed

spring-expression/src/main/java/org/springframework/expression/TypedValue.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
import org.springframework.core.convert.TypeDescriptor;
2020

2121
/**
22-
* Encapsulates an object and a type descriptor that describes it. The type descriptor can
23-
* hold generic information that would not be accessible through a simple
24-
* {@code getClass()} call on the object.
22+
* Encapsulates an object and a type descriptor that describes it.
23+
* The type descriptor can hold generic information that would not be
24+
* accessible through a simple {@code getClass()} call on the object.
2525
*
2626
* @author Andy Clement
2727
* @author Juergen Hoeller
@@ -73,7 +73,7 @@ public TypeDescriptor getTypeDescriptor() {
7373
@Override
7474
public String toString() {
7575
StringBuilder str = new StringBuilder();
76-
str.append("TypedValue: '").append(this.value).append("' of [").append(getTypeDescriptor() + "]");
76+
str.append("TypedValue: '").append(this.value).append("' of [").append(getTypeDescriptor()).append("]");
7777
return str.toString();
7878
}
7979

spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,19 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
7676
Object value = state.getActiveContextObject().getValue();
7777
TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor();
7878
Object[] arguments = getArguments(state);
79-
return getValueInternal(evaluationContext, value, arguments, targetType);
79+
return getValueInternal(evaluationContext, value, targetType, arguments);
8080
}
8181

8282
private TypedValue getValueInternal(EvaluationContext evaluationContext,
83-
Object value, Object[] arguments, TypeDescriptor targetType) {
83+
Object value, TypeDescriptor targetType, Object[] arguments) {
8484

8585
List<TypeDescriptor> argumentTypes = getArgumentTypes(arguments);
8686
if (value == null) {
8787
throwIfNotNullSafe(argumentTypes);
8888
return TypedValue.NULL;
8989
}
9090

91-
MethodExecutor executorToUse = getCachedExecutor(targetType, argumentTypes);
91+
MethodExecutor executorToUse = getCachedExecutor(value, targetType, argumentTypes);
9292
if (executorToUse != null) {
9393
try {
9494
return executorToUse.execute(evaluationContext, value, arguments);
@@ -108,15 +108,16 @@ private TypedValue getValueInternal(EvaluationContext evaluationContext,
108108
// Otherwise the method could not be invoked.
109109
throwSimpleExceptionIfPossible(value, ae);
110110

111-
// at this point we know it wasn't a user problem so worth a retry if a
111+
// At this point we know it wasn't a user problem so worth a retry if a
112112
// better candidate can be found
113113
this.cachedExecutor = null;
114114
}
115115
}
116116

117117
// either there was no accessor or it no longer existed
118118
executorToUse = findAccessorForMethod(this.name, argumentTypes, value, evaluationContext);
119-
this.cachedExecutor = new CachedMethodExecutor(executorToUse, targetType, argumentTypes);
119+
this.cachedExecutor = new CachedMethodExecutor(
120+
executorToUse, (value instanceof Class ? (Class<?>) value : null), targetType, argumentTypes);
120121
try {
121122
return executorToUse.execute(evaluationContext, value, arguments);
122123
}
@@ -140,8 +141,7 @@ private void throwIfNotNullSafe(List<TypeDescriptor> argumentTypes) {
140141
private Object[] getArguments(ExpressionState state) {
141142
Object[] arguments = new Object[getChildCount()];
142143
for (int i = 0; i < arguments.length; i++) {
143-
// Make the root object the active context again for evaluating the parameter
144-
// expressions
144+
// Make the root object the active context again for evaluating the parameter expressions
145145
try {
146146
state.pushActiveContextObject(state.getRootContextObject());
147147
arguments[i] = this.children[i].getValueInternal(state).getValue();
@@ -161,40 +161,39 @@ private List<TypeDescriptor> getArgumentTypes(Object... arguments) {
161161
return Collections.unmodifiableList(descriptors);
162162
}
163163

164-
private MethodExecutor getCachedExecutor(TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
164+
private MethodExecutor getCachedExecutor(Object value, TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
165165
CachedMethodExecutor executorToCheck = this.cachedExecutor;
166-
if (executorToCheck != null && executorToCheck.isSuitable(target, argumentTypes)) {
166+
if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) {
167167
return executorToCheck.get();
168168
}
169169
this.cachedExecutor = null;
170170
return null;
171171
}
172172

173-
private MethodExecutor findAccessorForMethod(String name,
174-
List<TypeDescriptor> argumentTypes, Object contextObject,
175-
EvaluationContext evaluationContext) throws SpelEvaluationException {
173+
private MethodExecutor findAccessorForMethod(String name, List<TypeDescriptor> argumentTypes,
174+
Object targetObject, EvaluationContext evaluationContext) throws SpelEvaluationException {
176175

177176
List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers();
178177
if (methodResolvers != null) {
179178
for (MethodResolver methodResolver : methodResolvers) {
180179
try {
181180
MethodExecutor methodExecutor = methodResolver.resolve(
182-
evaluationContext, contextObject, name, argumentTypes);
181+
evaluationContext, targetObject, name, argumentTypes);
183182
if (methodExecutor != null) {
184183
return methodExecutor;
185184
}
186185
}
187186
catch (AccessException ex) {
188187
throw new SpelEvaluationException(getStartPosition(), ex,
189-
SpelMessage.PROBLEM_LOCATING_METHOD, name, contextObject.getClass());
188+
SpelMessage.PROBLEM_LOCATING_METHOD, name, targetObject.getClass());
190189
}
191190
}
192191
}
193192

194193
throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND,
195194
FormatHelper.formatMethodForMessage(name, argumentTypes),
196195
FormatHelper.formatClassNameForMessage(
197-
contextObject instanceof Class ? ((Class<?>) contextObject) : contextObject.getClass()));
196+
targetObject instanceof Class ? ((Class<?>) targetObject) : targetObject.getClass()));
198197
}
199198

200199
/**
@@ -249,7 +248,7 @@ public MethodValueRef(ExpressionState state) {
249248

250249
@Override
251250
public TypedValue getValue() {
252-
return getValueInternal(this.evaluationContext, this.value, this.arguments, this.targetType);
251+
return getValueInternal(this.evaluationContext, this.value, this.targetType, this.arguments);
253252
}
254253

255254
@Override
@@ -268,18 +267,22 @@ private static class CachedMethodExecutor {
268267

269268
private final MethodExecutor methodExecutor;
270269

270+
private final Class<?> staticClass;
271+
271272
private final TypeDescriptor target;
272273

273274
private final List<TypeDescriptor> argumentTypes;
274275

275-
public CachedMethodExecutor(MethodExecutor methodExecutor, TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
276+
public CachedMethodExecutor(MethodExecutor methodExecutor, Class<?> staticClass,
277+
TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
276278
this.methodExecutor = methodExecutor;
279+
this.staticClass = staticClass;
277280
this.target = target;
278281
this.argumentTypes = argumentTypes;
279282
}
280283

281-
public boolean isSuitable(TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
282-
return (this.methodExecutor != null && this.target != null &&
284+
public boolean isSuitable(Object value, TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
285+
return ((this.staticClass == null || this.staticClass.equals(value)) &&
283286
this.target.equals(target) && this.argumentTypes.equals(argumentTypes));
284287
}
285288

spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,7 @@
1616

1717
package org.springframework.expression.spel;
1818

19-
import static org.hamcrest.Matchers.is;
20-
import static org.junit.Assert.assertEquals;
21-
import static org.junit.Assert.assertFalse;
22-
import static org.junit.Assert.assertNotNull;
23-
import static org.junit.Assert.assertThat;
24-
import static org.junit.Assert.assertTrue;
25-
import static org.junit.Assert.fail;
26-
19+
import java.lang.reflect.Array;
2720
import java.lang.reflect.Field;
2821
import java.lang.reflect.Method;
2922
import java.util.ArrayList;
@@ -38,6 +31,7 @@
3831
import org.junit.Rule;
3932
import org.junit.Test;
4033
import org.junit.rules.ExpectedException;
34+
4135
import org.springframework.core.convert.TypeDescriptor;
4236
import org.springframework.expression.AccessException;
4337
import org.springframework.expression.BeanResolver;
@@ -58,6 +52,9 @@
5852
import org.springframework.expression.spel.support.StandardTypeLocator;
5953
import org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver;
6054

55+
import static org.hamcrest.Matchers.*;
56+
import static org.junit.Assert.*;
57+
6158
/**
6259
* Reproduction tests cornering various SpEL JIRA issues.
6360
*
@@ -1755,6 +1752,38 @@ public void SPR_10328() throws Exception {
17551752
exp.getValue(Arrays.asList("foo", "bar", "baz"));
17561753
}
17571754

1755+
@Test
1756+
public void SPR_10452() throws Exception {
1757+
SpelParserConfiguration configuration = new SpelParserConfiguration(false, false);
1758+
ExpressionParser parser = new SpelExpressionParser(configuration);
1759+
1760+
StandardEvaluationContext context = new StandardEvaluationContext();
1761+
Expression spel = parser.parseExpression("#enumType.values()");
1762+
1763+
context.setVariable("enumType", ABC.class);
1764+
Object result = spel.getValue(context);
1765+
assertNotNull(result);
1766+
assertTrue(result.getClass().isArray());
1767+
assertEquals(ABC.A, Array.get(result, 0));
1768+
assertEquals(ABC.B, Array.get(result, 1));
1769+
assertEquals(ABC.C, Array.get(result, 2));
1770+
1771+
context.setVariable("enumType", XYZ.class);
1772+
result = spel.getValue(context);
1773+
assertNotNull(result);
1774+
assertTrue(result.getClass().isArray());
1775+
assertEquals(XYZ.X, Array.get(result, 0));
1776+
assertEquals(XYZ.Y, Array.get(result, 1));
1777+
assertEquals(XYZ.Z, Array.get(result, 2));
1778+
}
1779+
1780+
1781+
1782+
private static enum ABC {A, B, C}
1783+
1784+
private static enum XYZ {X, Y, Z}
1785+
1786+
17581787
public static class BooleanHolder {
17591788

17601789
private Boolean simpleProperty = true;

0 commit comments

Comments
 (0)