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.Method;
20  import java.lang.reflect.Modifier;
21  
22  import org.springframework.asm.MethodVisitor;
23  import org.springframework.core.MethodParameter;
24  import org.springframework.core.convert.TypeDescriptor;
25  import org.springframework.expression.EvaluationException;
26  import org.springframework.expression.TypeConverter;
27  import org.springframework.expression.TypedValue;
28  import org.springframework.expression.spel.CodeFlow;
29  import org.springframework.expression.spel.ExpressionState;
30  import org.springframework.expression.spel.SpelEvaluationException;
31  import org.springframework.expression.spel.SpelMessage;
32  import org.springframework.expression.spel.support.ReflectionHelper;
33  import org.springframework.util.ReflectionUtils;
34  
35  /**
36   * A function reference is of the form "#someFunction(a,b,c)". Functions may be defined in
37   * the context prior to the expression being evaluated or within the expression itself
38   * using a lambda function definition. For example: Lambda function definition in an
39   * expression: "(#max = {|x,y|$x>$y?$x:$y};max(2,3))" Calling context defined function:
40   * "#isEven(37)". Functions may also be static java methods, registered in the context
41   * prior to invocation of the expression.
42   *
43   * <p>Functions are very simplistic, the arguments are not part of the definition (right
44   * now), so the names must be unique.
45   *
46   * @author Andy Clement
47   * @since 3.0
48   */
49  public class FunctionReference extends SpelNodeImpl {
50  
51  	private final String name;
52  
53  	// Captures the most recently used method for the function invocation *if* the method
54  	// can safely be used for compilation (i.e. no argument conversion is going on)
55  	private Method method;
56  	
57  	private boolean argumentConversionOccurred;
58  
59  
60  	public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) {
61  		super(pos,arguments);
62  		this.name = functionName;
63  	}
64  
65  
66  	@Override
67  	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
68  		TypedValue value = state.lookupVariable(this.name);
69  		if (value == null) {
70  			throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.name);
71  		}
72  
73  		// Two possibilities: a lambda function or a Java static method registered as a function
74  		if (!(value.getValue() instanceof Method)) {
75  			throw new SpelEvaluationException(SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass());
76  		}
77  
78  		try {
79  			return executeFunctionJLRMethod(state, (Method) value.getValue());
80  		}
81  		catch (SpelEvaluationException ex) {
82  			ex.setPosition(getStartPosition());
83  			throw ex;
84  		}
85  	}
86  
87  	/**
88  	 * Execute a function represented as a java.lang.reflect.Method.
89  	 * @param state the expression evaluation state
90  	 * @param method the method to invoke
91  	 * @return the return value of the invoked Java method
92  	 * @throws EvaluationException if there is any problem invoking the method
93  	 */
94  	private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException {
95  		this.method = null;
96  		Object[] functionArgs = getArguments(state);
97  
98  		if (!method.isVarArgs() && method.getParameterTypes().length != functionArgs.length) {
99  			throw new SpelEvaluationException(SpelMessage.INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,
100 					functionArgs.length, method.getParameterTypes().length);
101 		}
102 		// Only static methods can be called in this way
103 		if (!Modifier.isStatic(method.getModifiers())) {
104 			throw new SpelEvaluationException(getStartPosition(),
105 					SpelMessage.FUNCTION_MUST_BE_STATIC,
106 					method.getDeclaringClass().getName() + "." + method.getName(), this.name);
107 		}
108 
109 		argumentConversionOccurred = false;
110 		// Convert arguments if necessary and remap them for varargs if required
111 		if (functionArgs != null) {
112 			TypeConverter converter = state.getEvaluationContext().getTypeConverter();
113 			argumentConversionOccurred = ReflectionHelper.convertAllArguments(converter, functionArgs, method);
114 		}
115 		if (method.isVarArgs()) {
116 			functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(method.getParameterTypes(), functionArgs);
117 		}
118 
119 		try {
120 			ReflectionUtils.makeAccessible(method);
121 			Object result = method.invoke(method.getClass(), functionArgs);
122 			if (!argumentConversionOccurred) {
123 				this.method = method;
124 				this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
125 			}
126 			return new TypedValue(result, new TypeDescriptor(new MethodParameter(method, -1)).narrow(result));
127 		}
128 		catch (Exception ex) {
129 			throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_FUNCTION_CALL,
130 					this.name, ex.getMessage());
131 		}
132 	}
133 
134 	@Override
135 	public String toStringAST() {
136 		StringBuilder sb = new StringBuilder("#").append(this.name);
137 		sb.append("(");
138 		for (int i = 0; i < getChildCount(); i++) {
139 			if (i > 0) {
140 				sb.append(",");
141 			}
142 			sb.append(getChild(i).toStringAST());
143 		}
144 		sb.append(")");
145 		return sb.toString();
146 	}
147 
148 	/**
149 	 * Compute the arguments to the function, they are the children of this expression node.
150 	 * @return an array of argument values for the function call
151 	 */
152 	private Object[] getArguments(ExpressionState state) throws EvaluationException {
153 		// Compute arguments to the function
154 		Object[] arguments = new Object[getChildCount()];
155 		for (int i = 0; i < arguments.length; i++) {
156 			arguments[i] = this.children[i].getValueInternal(state).getValue();
157 		}
158 		return arguments;
159 	}
160 	
161 	@Override
162 	public boolean isCompilable() {
163 		if (this.method == null || argumentConversionOccurred) {
164 			return false;
165 		}
166 		int methodModifiers = this.method.getModifiers();
167 		if (!Modifier.isStatic(methodModifiers) || 
168 			!Modifier.isPublic(methodModifiers) ||
169 			!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
170 			return false;
171 		}
172 		for (SpelNodeImpl child : this.children) {
173 			if (!child.isCompilable()) {
174 				return false;
175 			}
176 		}
177 		return true;
178 	}
179 	
180 	@Override 
181 	public void generateCode(MethodVisitor mv,CodeFlow cf) {
182 		String methodDeclaringClassSlashedDescriptor = this.method.getDeclaringClass().getName().replace('.', '/');
183 		generateCodeForArguments(mv, cf, method, this.children);
184 		mv.visitMethodInsn(INVOKESTATIC, methodDeclaringClassSlashedDescriptor, this.method.getName(),
185 				CodeFlow.createSignatureDescriptor(this.method), false);
186 		cf.pushDescriptor(this.exitTypeDescriptor);
187 	}
188 
189 }