View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * Expression language AST node that represents a method reference.
44   *
45   * @author Andy Clement
46   * @author Juergen Hoeller
47   * @since 3.0
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 				// Two reasons this can occur:
106 				// 1. the method invoked actually threw a real exception
107 				// 2. the method invoked was not passed the arguments it expected and
108 				//    has become 'stale'
109 
110 				// In the first case we should not retry, in the second case we should see
111 				// if there is a better suited method.
112 
113 				// To determine the situation, the AccessException will contain a cause.
114 				// If the cause is an InvocationTargetException, a user exception was
115 				// thrown inside the method. Otherwise the method could not be invoked.
116 				throwSimpleExceptionIfPossible(value, ex);
117 
118 				// At this point we know it wasn't a user problem so worth a retry if a
119 				// better candidate can be found.
120 				this.cachedExecutor = null;
121 			}
122 		}
123 
124 		// either there was no accessor or it no longer existed
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 			// Same unwrapping exception handling as above in above catch block
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 			// Make the root object the active context again for evaluating the parameter expressions
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 			// Not a default ReflectiveMethodResolver - don't know whether caching is valid
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 	 * Decode the AccessException, throwing a lightweight evaluation exception or, if the
217 	 * cause was a RuntimeException, throw the RuntimeException directly.
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 	 * A method reference is compilable if it has been resolved to a reflectively accessible method
255 	 * and the child nodes (arguments to the method) are also compilable.
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 }