View Javadoc
1   /*
2    * Copyright 2002-2015 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;
18  
19  import java.util.Arrays;
20  import java.util.List;
21  
22  import org.springframework.expression.EvaluationException;
23  import org.springframework.expression.Expression;
24  import org.springframework.expression.ExpressionParser;
25  import org.springframework.expression.ParseException;
26  import org.springframework.expression.spel.standard.SpelExpressionParser;
27  import org.springframework.expression.spel.support.StandardEvaluationContext;
28  
29  import static org.junit.Assert.*;
30  
31  /**
32   * Common superclass for expression tests.
33   *
34   * @author Andy Clement
35   */
36  public abstract class AbstractExpressionTests {
37  
38  	private static final boolean DEBUG = false;
39  
40  	protected static final boolean SHOULD_BE_WRITABLE = true;
41  
42  	protected static final boolean SHOULD_NOT_BE_WRITABLE = false;
43  
44  
45  	protected final ExpressionParser parser = new SpelExpressionParser();
46  
47  	protected final StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
48  
49  
50  	/**
51  	 * Evaluate an expression and check that the actual result matches the
52  	 * expectedValue and the class of the result matches the expectedClassOfResult.
53  	 * @param expression the expression to evaluate
54  	 * @param expectedValue the expected result for evaluating the expression
55  	 * @param expectedResultType the expected class of the evaluation result
56  	 */
57  	public void evaluate(String expression, Object expectedValue, Class<?> expectedResultType) {
58  		Expression expr = parser.parseExpression(expression);
59  		if (expr == null) {
60  			fail("Parser returned null for expression");
61  		}
62  		if (DEBUG) {
63  			SpelUtilities.printAbstractSyntaxTree(System.out, expr);
64  		}
65  
66  		Object value = expr.getValue(eContext);
67  
68  		// Check the return value
69  		if (value == null) {
70  			if (expectedValue == null) {
71  				return;  // no point doing other checks
72  			}
73  			assertEquals("Expression returned null value, but expected '" + expectedValue + "'", expectedValue, null);
74  		}
75  
76  		Class<?> resultType = value.getClass();
77  		assertEquals("Type of the actual result was not as expected.  Expected '" + expectedResultType +
78  				"' but result was of type '" + resultType + "'", expectedResultType, resultType);
79  
80  		if (expectedValue instanceof String) {
81  			assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue,
82  					AbstractExpressionTests.stringValueOf(value));
83  		}
84  		else {
85  			assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
86  		}
87  	}
88  
89  	public void evaluateAndAskForReturnType(String expression, Object expectedValue, Class<?> expectedResultType) {
90  		Expression expr = parser.parseExpression(expression);
91  		if (expr == null) {
92  			fail("Parser returned null for expression");
93  		}
94  		if (DEBUG) {
95  			SpelUtilities.printAbstractSyntaxTree(System.out, expr);
96  		}
97  
98  		Object value = expr.getValue(eContext, expectedResultType);
99  		if (value == null) {
100 			if (expectedValue == null) {
101 				return;  // no point doing other checks
102 			}
103 			assertEquals("Expression returned null value, but expected '" + expectedValue + "'", expectedValue, null);
104 		}
105 
106 		Class<?> resultType = value.getClass();
107 		assertEquals("Type of the actual result was not as expected.  Expected '" + expectedResultType +
108 				"' but result was of type '" + resultType + "'", expectedResultType, resultType);
109 		assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
110 	}
111 
112 	/**
113 	 * Evaluate an expression and check that the actual result matches the
114 	 * expectedValue and the class of the result matches the expectedClassOfResult.
115 	 * This method can also check if the expression is writable (for example,
116 	 * it is a variable or property reference).
117 	 * @param expression the expression to evaluate
118 	 * @param expectedValue the expected result for evaluating the expression
119 	 * @param expectedClassOfResult the expected class of the evaluation result
120 	 * @param shouldBeWritable should the parsed expression be writable?
121 	 */
122 	public void evaluate(String expression, Object expectedValue, Class<?> expectedClassOfResult, boolean shouldBeWritable) {
123 		Expression expr = parser.parseExpression(expression);
124 		if (expr == null) {
125 			fail("Parser returned null for expression");
126 		}
127 		if (DEBUG) {
128 			SpelUtilities.printAbstractSyntaxTree(System.out, expr);
129 		}
130 		Object value = expr.getValue(eContext);
131 		if (value == null) {
132 			if (expectedValue == null) {
133 				return;  // no point doing other checks
134 			}
135 			assertEquals("Expression returned null value, but expected '" + expectedValue + "'", expectedValue, null);
136 		}
137 		Class<? extends Object> resultType = value.getClass();
138 		if (expectedValue instanceof String) {
139 			assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue,
140 					AbstractExpressionTests.stringValueOf(value));
141 		}
142 		else {
143 			assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
144 		}
145 		assertEquals("Type of the result was not as expected.  Expected '" + expectedClassOfResult +
146 				"' but result was of type '" + resultType + "'", expectedClassOfResult.equals(resultType), true);
147 
148 		boolean isWritable = expr.isWritable(eContext);
149 		if (isWritable != shouldBeWritable) {
150 			if (shouldBeWritable)
151 				fail("Expected the expression to be writable but it is not");
152 			else
153 				fail("Expected the expression to be readonly but it is not");
154 		}
155 	}
156 
157 	/**
158 	 * Evaluate the specified expression and ensure the expected message comes out.
159 	 * The message may have inserts and they will be checked if otherProperties is specified.
160 	 * The first entry in otherProperties should always be the position.
161 	 * @param expression the expression to evaluate
162 	 * @param expectedMessage the expected message
163 	 * @param otherProperties the expected inserts within the message
164 	 */
165 	protected void evaluateAndCheckError(String expression, SpelMessage expectedMessage, Object... otherProperties) {
166 		evaluateAndCheckError(expression, null, expectedMessage, otherProperties);
167 	}
168 
169 	/**
170 	 * Evaluate the specified expression and ensure the expected message comes out.
171 	 * The message may have inserts and they will be checked if otherProperties is specified.
172 	 * The first entry in otherProperties should always be the position.
173 	 * @param expression the expression to evaluate
174 	 * @param expectedReturnType ask the expression return value to be of this type if possible
175 	 * ({@code null} indicates don't ask for conversion)
176 	 * @param expectedMessage the expected message
177 	 * @param otherProperties the expected inserts within the message
178 	 */
179 	protected void evaluateAndCheckError(String expression, Class<?> expectedReturnType, SpelMessage expectedMessage,
180 			Object... otherProperties) {
181 		try {
182 			Expression expr = parser.parseExpression(expression);
183 			if (expr == null) {
184 				fail("Parser returned null for expression");
185 			}
186 			if (expectedReturnType != null) {
187 				expr.getValue(eContext, expectedReturnType);
188 			}
189 			else {
190 				expr.getValue(eContext);
191 			}
192 			fail("Should have failed with message " + expectedMessage);
193 		}
194 		catch (EvaluationException ee) {
195 			SpelEvaluationException ex = (SpelEvaluationException) ee;
196 			if (ex.getMessageCode() != expectedMessage) {
197 				assertEquals("Failed to get expected message", expectedMessage, ex.getMessageCode());
198 			}
199 			if (otherProperties != null && otherProperties.length != 0) {
200 				// first one is expected position of the error within the string
201 				int pos = ((Integer) otherProperties[0]).intValue();
202 				assertEquals("Did not get correct position reported in error ", pos, ex.getPosition());
203 				if (otherProperties.length > 1) {
204 					// Check inserts match
205 					Object[] inserts = ex.getInserts();
206 					if (inserts == null) {
207 						inserts = new Object[0];
208 					}
209 					if (inserts.length < otherProperties.length - 1) {
210 						fail("Cannot check " + (otherProperties.length - 1) +
211 								" properties of the exception, it only has " + inserts.length + " inserts");
212 					}
213 					for (int i = 1; i < otherProperties.length; i++) {
214 						if (otherProperties[i] == null) {
215 							if (inserts[i - 1] != null) {
216 								fail("Insert does not match, expected 'null' but insert value was '" +
217 										inserts[i - 1] + "'");
218 							}
219 						}
220 						else if (inserts[i - 1] == null) {
221 							if (otherProperties[i] != null) {
222 								fail("Insert does not match, expected '" + otherProperties[i] +
223 										"' but insert value was 'null'");
224 							}
225 						}
226 						else if (!inserts[i - 1].equals(otherProperties[i])) {
227 							fail("Insert does not match, expected '" + otherProperties[i] +
228 									"' but insert value was '" + inserts[i - 1] + "'");
229 						}
230 					}
231 				}
232 			}
233 		}
234 	}
235 
236 	/**
237 	 * Parse the specified expression and ensure the expected message comes out.
238 	 * The message may have inserts and they will be checked if otherProperties is specified.
239 	 * The first entry in otherProperties should always be the position.
240 	 * @param expression the expression to evaluate
241 	 * @param expectedMessage the expected message
242 	 * @param otherProperties the expected inserts within the message
243 	 */
244 	protected void parseAndCheckError(String expression, SpelMessage expectedMessage, Object... otherProperties) {
245 		try {
246 			Expression expr = parser.parseExpression(expression);
247 			SpelUtilities.printAbstractSyntaxTree(System.out, expr);
248 			fail("Parsing should have failed!");
249 		}
250 		catch (ParseException pe) {
251 			SpelParseException ex = (SpelParseException)pe;
252 			if (ex.getMessageCode() != expectedMessage) {
253 				assertEquals("Failed to get expected message", expectedMessage, ex.getMessageCode());
254 			}
255 			if (otherProperties != null && otherProperties.length != 0) {
256 				// first one is expected position of the error within the string
257 				int pos = ((Integer) otherProperties[0]).intValue();
258 				assertEquals("Did not get correct position reported in error ", pos, ex.getPosition());
259 				if (otherProperties.length > 1) {
260 					// Check inserts match
261 					Object[] inserts = ex.getInserts();
262 					if (inserts == null) {
263 						inserts = new Object[0];
264 					}
265 					if (inserts.length < otherProperties.length - 1) {
266 						fail("Cannot check " + (otherProperties.length - 1) +
267 								" properties of the exception, it only has " + inserts.length + " inserts");
268 					}
269 					for (int i = 1; i < otherProperties.length; i++) {
270 						if (!inserts[i - 1].equals(otherProperties[i])) {
271 							fail("Insert does not match, expected '" + otherProperties[i] +
272 									"' but insert value was '" + inserts[i - 1] + "'");
273 						}
274 					}
275 				}
276 			}
277 		}
278 	}
279 
280 
281 	protected static String stringValueOf(Object value) {
282 		return stringValueOf(value, false);
283 	}
284 
285 	/**
286 	 * Produce a nice string representation of the input object.
287 	 * @param value object to be formatted
288 	 * @return a nice string
289 	 */
290 	protected static String stringValueOf(Object value, boolean isNested) {
291 		// do something nice for arrays
292 		if (value == null) {
293 			return "null";
294 		}
295 		if (value.getClass().isArray()) {
296 			StringBuilder sb = new StringBuilder();
297 			if (value.getClass().getComponentType().isPrimitive()) {
298 				Class<?> primitiveType = value.getClass().getComponentType();
299 				if (primitiveType == Integer.TYPE) {
300 					int[] l = (int[]) value;
301 					sb.append("int[").append(l.length).append("]{");
302 					for (int j = 0; j < l.length; j++) {
303 						if (j > 0) {
304 							sb.append(",");
305 						}
306 						sb.append(stringValueOf(l[j]));
307 					}
308 					sb.append("}");
309 				}
310 				else if (primitiveType == Long.TYPE) {
311 					long[] l = (long[]) value;
312 					sb.append("long[").append(l.length).append("]{");
313 					for (int j = 0; j < l.length; j++) {
314 						if (j > 0) {
315 							sb.append(",");
316 						}
317 						sb.append(stringValueOf(l[j]));
318 					}
319 					sb.append("}");
320 				}
321 				else {
322 					throw new RuntimeException("Please implement support for type " + primitiveType.getName() +
323 							" in ExpressionTestCase.stringValueOf()");
324 				}
325 			}
326 			else if (value.getClass().getComponentType().isArray()) {
327 				List<Object> l = Arrays.asList((Object[]) value);
328 				if (!isNested) {
329 					sb.append(value.getClass().getComponentType().getName());
330 				}
331 				sb.append("[").append(l.size()).append("]{");
332 				int i = 0;
333 				for (Object object : l) {
334 					if (i > 0) {
335 						sb.append(",");
336 					}
337 					i++;
338 					sb.append(stringValueOf(object, true));
339 				}
340 				sb.append("}");
341 			}
342 			else {
343 				List<Object> l = Arrays.asList((Object[]) value);
344 				if (!isNested) {
345 					sb.append(value.getClass().getComponentType().getName());
346 				}
347 				sb.append("[").append(l.size()).append("]{");
348 				int i = 0;
349 				for (Object object : l) {
350 					if (i > 0) {
351 						sb.append(",");
352 					}
353 					i++;
354 					sb.append(stringValueOf(object));
355 				}
356 				sb.append("}");
357 			}
358 			return sb.toString();
359 		}
360 		else {
361 			return value.toString();
362 		}
363 	}
364 
365 }