1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.expression.spel.ast;
18
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Modifier;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25
26 import org.springframework.asm.MethodVisitor;
27 import org.springframework.core.convert.TypeDescriptor;
28 import org.springframework.expression.AccessException;
29 import org.springframework.expression.EvaluationContext;
30 import org.springframework.expression.EvaluationException;
31 import org.springframework.expression.ExpressionInvocationTargetException;
32 import org.springframework.expression.MethodExecutor;
33 import org.springframework.expression.MethodResolver;
34 import org.springframework.expression.TypedValue;
35 import org.springframework.expression.spel.CodeFlow;
36 import org.springframework.expression.spel.ExpressionState;
37 import org.springframework.expression.spel.SpelEvaluationException;
38 import org.springframework.expression.spel.SpelMessage;
39 import org.springframework.expression.spel.support.ReflectiveMethodExecutor;
40 import org.springframework.expression.spel.support.ReflectiveMethodResolver;
41
42
43
44
45
46
47
48
49 public class MethodReference extends SpelNodeImpl {
50
51 private final String name;
52
53 private final boolean nullSafe;
54
55 private volatile CachedMethodExecutor cachedExecutor;
56
57
58 public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) {
59 super(pos, arguments);
60 this.name = methodName;
61 this.nullSafe = nullSafe;
62 }
63
64
65 public final String getName() {
66 return this.name;
67 }
68
69 @Override
70 protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
71 Object[] arguments = getArguments(state);
72 if (state.getActiveContextObject().getValue() == null) {
73 throwIfNotNullSafe(getArgumentTypes(arguments));
74 return ValueRef.NullValueRef.INSTANCE;
75 }
76 return new MethodValueRef(state, arguments);
77 }
78
79 @Override
80 public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
81 EvaluationContext evaluationContext = state.getEvaluationContext();
82 Object value = state.getActiveContextObject().getValue();
83 TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor();
84 Object[] arguments = getArguments(state);
85 TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments);
86 updateExitTypeDescriptor();
87 return result;
88 }
89
90 private TypedValue getValueInternal(EvaluationContext evaluationContext,
91 Object value, TypeDescriptor targetType, Object[] arguments) {
92
93 List<TypeDescriptor> argumentTypes = getArgumentTypes(arguments);
94 if (value == null) {
95 throwIfNotNullSafe(argumentTypes);
96 return TypedValue.NULL;
97 }
98
99 MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes);
100 if (executorToUse != null) {
101 try {
102 return executorToUse.execute(evaluationContext, value, arguments);
103 }
104 catch (AccessException ex) {
105
106
107
108
109
110
111
112
113
114
115
116 throwSimpleExceptionIfPossible(value, ex);
117
118
119
120 this.cachedExecutor = null;
121 }
122 }
123
124
125 executorToUse = findAccessorForMethod(this.name, argumentTypes, value, evaluationContext);
126 this.cachedExecutor = new CachedMethodExecutor(
127 executorToUse, (value instanceof Class ? (Class<?>) value : null), targetType, argumentTypes);
128 try {
129 return executorToUse.execute(evaluationContext, value, arguments);
130 }
131 catch (AccessException ex) {
132
133 throwSimpleExceptionIfPossible(value, ex);
134 throw new SpelEvaluationException(getStartPosition(), ex,
135 SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name,
136 value.getClass().getName(), ex.getMessage());
137 }
138 }
139
140 private void throwIfNotNullSafe(List<TypeDescriptor> argumentTypes) {
141 if (!this.nullSafe) {
142 throw new SpelEvaluationException(getStartPosition(),
143 SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED,
144 FormatHelper.formatMethodForMessage(this.name, argumentTypes));
145 }
146 }
147
148 private Object[] getArguments(ExpressionState state) {
149 Object[] arguments = new Object[getChildCount()];
150 for (int i = 0; i < arguments.length; i++) {
151
152 try {
153 state.pushActiveContextObject(state.getRootContextObject());
154 arguments[i] = this.children[i].getValueInternal(state).getValue();
155 }
156 finally {
157 state.popActiveContextObject();
158 }
159 }
160 return arguments;
161 }
162
163 private List<TypeDescriptor> getArgumentTypes(Object... arguments) {
164 List<TypeDescriptor> descriptors = new ArrayList<TypeDescriptor>(arguments.length);
165 for (Object argument : arguments) {
166 descriptors.add(TypeDescriptor.forObject(argument));
167 }
168 return Collections.unmodifiableList(descriptors);
169 }
170
171 private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value,
172 TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
173
174 List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers();
175 if (methodResolvers == null || methodResolvers.size() != 1 ||
176 !(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) {
177
178 return null;
179 }
180
181 CachedMethodExecutor executorToCheck = this.cachedExecutor;
182 if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) {
183 return executorToCheck.get();
184 }
185 this.cachedExecutor = null;
186 return null;
187 }
188
189 private MethodExecutor findAccessorForMethod(String name, List<TypeDescriptor> argumentTypes,
190 Object targetObject, EvaluationContext evaluationContext) throws SpelEvaluationException {
191
192 List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers();
193 if (methodResolvers != null) {
194 for (MethodResolver methodResolver : methodResolvers) {
195 try {
196 MethodExecutor methodExecutor = methodResolver.resolve(
197 evaluationContext, targetObject, name, argumentTypes);
198 if (methodExecutor != null) {
199 return methodExecutor;
200 }
201 }
202 catch (AccessException ex) {
203 throw new SpelEvaluationException(getStartPosition(), ex,
204 SpelMessage.PROBLEM_LOCATING_METHOD, name, targetObject.getClass());
205 }
206 }
207 }
208
209 throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND,
210 FormatHelper.formatMethodForMessage(name, argumentTypes),
211 FormatHelper.formatClassNameForMessage(
212 targetObject instanceof Class ? ((Class<?>) targetObject) : targetObject.getClass()));
213 }
214
215
216
217
218
219 private void throwSimpleExceptionIfPossible(Object value, AccessException ex) {
220 if (ex.getCause() instanceof InvocationTargetException) {
221 Throwable rootCause = ex.getCause().getCause();
222 if (rootCause instanceof RuntimeException) {
223 throw (RuntimeException) rootCause;
224 }
225 throw new ExpressionInvocationTargetException(getStartPosition(),
226 "A problem occurred when trying to execute method '" + this.name +
227 "' on object of type [" + value.getClass().getName() + "]", rootCause);
228 }
229 }
230
231 private void updateExitTypeDescriptor() {
232 CachedMethodExecutor executorToCheck = this.cachedExecutor;
233 if (executorToCheck != null && executorToCheck.get() instanceof ReflectiveMethodExecutor) {
234 Method method = ((ReflectiveMethodExecutor) executorToCheck.get()).getMethod();
235 this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
236 }
237 }
238
239 @Override
240 public String toStringAST() {
241 StringBuilder sb = new StringBuilder(this.name);
242 sb.append("(");
243 for (int i = 0; i < getChildCount(); i++) {
244 if (i > 0) {
245 sb.append(",");
246 }
247 sb.append(getChild(i).toStringAST());
248 }
249 sb.append(")");
250 return sb.toString();
251 }
252
253
254
255
256
257 @Override
258 public boolean isCompilable() {
259 CachedMethodExecutor executorToCheck = this.cachedExecutor;
260 if (executorToCheck == null || !(executorToCheck.get() instanceof ReflectiveMethodExecutor)) {
261 return false;
262 }
263
264 for (SpelNodeImpl child : this.children) {
265 if (!child.isCompilable()) {
266 return false;
267 }
268 }
269
270 ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) executorToCheck.get();
271 if (executor.didArgumentConversionOccur()) {
272 return false;
273 }
274 Method method = executor.getMethod();
275 Class<?> clazz = method.getDeclaringClass();
276 if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) {
277 return false;
278 }
279
280 return true;
281 }
282
283 @Override
284 public void generateCode(MethodVisitor mv, CodeFlow cf) {
285 CachedMethodExecutor executorToCheck = this.cachedExecutor;
286 if (executorToCheck == null || !(executorToCheck.get() instanceof ReflectiveMethodExecutor)) {
287 throw new IllegalStateException("No applicable cached executor found: " + executorToCheck);
288 }
289
290 ReflectiveMethodExecutor methodExecutor = (ReflectiveMethodExecutor)executorToCheck.get();
291 Method method = methodExecutor.getMethod();
292 boolean isStaticMethod = Modifier.isStatic(method.getModifiers());
293 String descriptor = cf.lastDescriptor();
294
295 if (descriptor == null && !isStaticMethod) {
296 cf.loadTarget(mv);
297 }
298
299 if (CodeFlow.isPrimitive(descriptor)) {
300 CodeFlow.insertBoxIfNecessary(mv, descriptor.charAt(0));
301 }
302
303 boolean itf = method.getDeclaringClass().isInterface();
304 String methodDeclaringClassSlashedDescriptor = null;
305 if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
306 methodDeclaringClassSlashedDescriptor = method.getDeclaringClass().getName().replace('.', '/');
307 }
308 else {
309 methodDeclaringClassSlashedDescriptor = methodExecutor.getPublicDeclaringClass().getName().replace('.', '/');
310 }
311 if (!isStaticMethod) {
312 if (descriptor == null || !descriptor.substring(1).equals(methodDeclaringClassSlashedDescriptor)) {
313 CodeFlow.insertCheckCast(mv, "L"+ methodDeclaringClassSlashedDescriptor);
314 }
315 }
316 generateCodeForArguments(mv, cf, method, children);
317 mv.visitMethodInsn(isStaticMethod ? INVOKESTATIC : INVOKEVIRTUAL,
318 methodDeclaringClassSlashedDescriptor, method.getName(), CodeFlow.createSignatureDescriptor(method), itf);
319 cf.pushDescriptor(this.exitTypeDescriptor);
320 }
321
322
323 private class MethodValueRef implements ValueRef {
324
325 private final EvaluationContext evaluationContext;
326
327 private final Object value;
328
329 private final TypeDescriptor targetType;
330
331 private final Object[] arguments;
332
333 public MethodValueRef(ExpressionState state, Object[] arguments) {
334 this.evaluationContext = state.getEvaluationContext();
335 this.value = state.getActiveContextObject().getValue();
336 this.targetType = state.getActiveContextObject().getTypeDescriptor();
337 this.arguments = arguments;
338 }
339
340 @Override
341 public TypedValue getValue() {
342 TypedValue result = MethodReference.this.getValueInternal(
343 this.evaluationContext, this.value, this.targetType, this.arguments);
344 updateExitTypeDescriptor();
345 return result;
346 }
347
348 @Override
349 public void setValue(Object newValue) {
350 throw new IllegalAccessError();
351 }
352
353 @Override
354 public boolean isWritable() {
355 return false;
356 }
357 }
358
359
360 private static class CachedMethodExecutor {
361
362 private final MethodExecutor methodExecutor;
363
364 private final Class<?> staticClass;
365
366 private final TypeDescriptor target;
367
368 private final List<TypeDescriptor> argumentTypes;
369
370 public CachedMethodExecutor(MethodExecutor methodExecutor, Class<?> staticClass,
371 TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
372 this.methodExecutor = methodExecutor;
373 this.staticClass = staticClass;
374 this.target = target;
375 this.argumentTypes = argumentTypes;
376 }
377
378 public boolean isSuitable(Object value, TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
379 return ((this.staticClass == null || this.staticClass.equals(value)) &&
380 this.target.equals(target) && this.argumentTypes.equals(argumentTypes));
381 }
382
383 public MethodExecutor get() {
384 return this.methodExecutor;
385 }
386 }
387
388 }