/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.expression.spel;
import java.util.Arrays;
import java.util.List;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import static org.junit.Assert.*;
/**
* Common superclass for expression tests.
*
* @author Andy Clement
*/
public abstract class AbstractExpressionTests {
private static final boolean DEBUG = false;
protected static final boolean SHOULD_BE_WRITABLE = true;
protected static final boolean SHOULD_NOT_BE_WRITABLE = false;
protected final ExpressionParser parser = new SpelExpressionParser();
protected final StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
/**
* Evaluate an expression and check that the actual result matches the
* expectedValue and the class of the result matches the expectedClassOfResult.
* @param expression the expression to evaluate
* @param expectedValue the expected result for evaluating the expression
* @param expectedResultType the expected class of the evaluation result
*/
public void evaluate(String expression, Object expectedValue, Class<?> expectedResultType) {
Expression expr = parser.parseExpression(expression);
if (expr == null) {
fail("Parser returned null for expression");
}
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}
Object value = expr.getValue(eContext);
// Check the return value
if (value == null) {
if (expectedValue == null) {
return; // no point doing other checks
}
assertEquals("Expression returned null value, but expected '" + expectedValue + "'", expectedValue, null);
}
Class<?> resultType = value.getClass();
assertEquals("Type of the actual result was not as expected. Expected '" + expectedResultType +
"' but result was of type '" + resultType + "'", expectedResultType, resultType);
if (expectedValue instanceof String) {
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue,
AbstractExpressionTests.stringValueOf(value));
}
else {
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
}
}
public void evaluateAndAskForReturnType(String expression, Object expectedValue, Class<?> expectedResultType) {
Expression expr = parser.parseExpression(expression);
if (expr == null) {
fail("Parser returned null for expression");
}
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}
Object value = expr.getValue(eContext, expectedResultType);
if (value == null) {
if (expectedValue == null) {
return; // no point doing other checks
}
assertEquals("Expression returned null value, but expected '" + expectedValue + "'", expectedValue, null);
}
Class<?> resultType = value.getClass();
assertEquals("Type of the actual result was not as expected. Expected '" + expectedResultType +
"' but result was of type '" + resultType + "'", expectedResultType, resultType);
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
}
/**
* Evaluate an expression and check that the actual result matches the
* expectedValue and the class of the result matches the expectedClassOfResult.
* This method can also check if the expression is writable (for example,
* it is a variable or property reference).
* @param expression the expression to evaluate
* @param expectedValue the expected result for evaluating the expression
* @param expectedClassOfResult the expected class of the evaluation result
* @param shouldBeWritable should the parsed expression be writable?
*/
public void evaluate(String expression, Object expectedValue, Class<?> expectedClassOfResult, boolean shouldBeWritable) {
Expression expr = parser.parseExpression(expression);
if (expr == null) {
fail("Parser returned null for expression");
}
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}
Object value = expr.getValue(eContext);
if (value == null) {
if (expectedValue == null) {
return; // no point doing other checks
}
assertEquals("Expression returned null value, but expected '" + expectedValue + "'", expectedValue, null);
}
Class<? extends Object> resultType = value.getClass();
if (expectedValue instanceof String) {
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue,
AbstractExpressionTests.stringValueOf(value));
}
else {
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
}
assertEquals("Type of the result was not as expected. Expected '" + expectedClassOfResult +
"' but result was of type '" + resultType + "'", expectedClassOfResult.equals(resultType), true);
boolean isWritable = expr.isWritable(eContext);
if (isWritable != shouldBeWritable) {
if (shouldBeWritable)
fail("Expected the expression to be writable but it is not");
else
fail("Expected the expression to be readonly but it is not");
}
}
/**
* Evaluate the specified expression and ensure the expected message comes out.
* The message may have inserts and they will be checked if otherProperties is specified.
* The first entry in otherProperties should always be the position.
* @param expression the expression to evaluate
* @param expectedMessage the expected message
* @param otherProperties the expected inserts within the message
*/
protected void evaluateAndCheckError(String expression, SpelMessage expectedMessage, Object... otherProperties) {
evaluateAndCheckError(expression, null, expectedMessage, otherProperties);
}
/**
* Evaluate the specified expression and ensure the expected message comes out.
* The message may have inserts and they will be checked if otherProperties is specified.
* The first entry in otherProperties should always be the position.
* @param expression the expression to evaluate
* @param expectedReturnType ask the expression return value to be of this type if possible
* ({@code null} indicates don't ask for conversion)
* @param expectedMessage the expected message
* @param otherProperties the expected inserts within the message
*/
protected void evaluateAndCheckError(String expression, Class<?> expectedReturnType, SpelMessage expectedMessage,
Object... otherProperties) {
try {
Expression expr = parser.parseExpression(expression);
if (expr == null) {
fail("Parser returned null for expression");
}
if (expectedReturnType != null) {
expr.getValue(eContext, expectedReturnType);
}
else {
expr.getValue(eContext);
}
fail("Should have failed with message " + expectedMessage);
}
catch (EvaluationException ee) {
SpelEvaluationException ex = (SpelEvaluationException) ee;
if (ex.getMessageCode() != expectedMessage) {
assertEquals("Failed to get expected message", expectedMessage, ex.getMessageCode());
}
if (otherProperties != null && otherProperties.length != 0) {
// first one is expected position of the error within the string
int pos = ((Integer) otherProperties[0]).intValue();
assertEquals("Did not get correct position reported in error ", pos, ex.getPosition());
if (otherProperties.length > 1) {
// Check inserts match
Object[] inserts = ex.getInserts();
if (inserts == null) {
inserts = new Object[0];
}
if (inserts.length < otherProperties.length - 1) {
fail("Cannot check " + (otherProperties.length - 1) +
" properties of the exception, it only has " + inserts.length + " inserts");
}
for (int i = 1; i < otherProperties.length; i++) {
if (otherProperties[i] == null) {
if (inserts[i - 1] != null) {
fail("Insert does not match, expected 'null' but insert value was '" +
inserts[i - 1] + "'");
}
}
else if (inserts[i - 1] == null) {
if (otherProperties[i] != null) {
fail("Insert does not match, expected '" + otherProperties[i] +
"' but insert value was 'null'");
}
}
else if (!inserts[i - 1].equals(otherProperties[i])) {
fail("Insert does not match, expected '" + otherProperties[i] +
"' but insert value was '" + inserts[i - 1] + "'");
}
}
}
}
}
}
/**
* Parse the specified expression and ensure the expected message comes out.
* The message may have inserts and they will be checked if otherProperties is specified.
* The first entry in otherProperties should always be the position.
* @param expression the expression to evaluate
* @param expectedMessage the expected message
* @param otherProperties the expected inserts within the message
*/
protected void parseAndCheckError(String expression, SpelMessage expectedMessage, Object... otherProperties) {
try {
Expression expr = parser.parseExpression(expression);
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
fail("Parsing should have failed!");
}
catch (ParseException pe) {
SpelParseException ex = (SpelParseException)pe;
if (ex.getMessageCode() != expectedMessage) {
assertEquals("Failed to get expected message", expectedMessage, ex.getMessageCode());
}
if (otherProperties != null && otherProperties.length != 0) {
// first one is expected position of the error within the string
int pos = ((Integer) otherProperties[0]).intValue();
assertEquals("Did not get correct position reported in error ", pos, ex.getPosition());
if (otherProperties.length > 1) {
// Check inserts match
Object[] inserts = ex.getInserts();
if (inserts == null) {
inserts = new Object[0];
}
if (inserts.length < otherProperties.length - 1) {
fail("Cannot check " + (otherProperties.length - 1) +
" properties of the exception, it only has " + inserts.length + " inserts");
}
for (int i = 1; i < otherProperties.length; i++) {
if (!inserts[i - 1].equals(otherProperties[i])) {
fail("Insert does not match, expected '" + otherProperties[i] +
"' but insert value was '" + inserts[i - 1] + "'");
}
}
}
}
}
}
protected static String stringValueOf(Object value) {
return stringValueOf(value, false);
}
/**
* Produce a nice string representation of the input object.
* @param value object to be formatted
* @return a nice string
*/
protected static String stringValueOf(Object value, boolean isNested) {
// do something nice for arrays
if (value == null) {
return "null";
}
if (value.getClass().isArray()) {
StringBuilder sb = new StringBuilder();
if (value.getClass().getComponentType().isPrimitive()) {
Class<?> primitiveType = value.getClass().getComponentType();
if (primitiveType == Integer.TYPE) {
int[] l = (int[]) value;
sb.append("int[").append(l.length).append("]{");
for (int j = 0; j < l.length; j++) {
if (j > 0) {
sb.append(",");
}
sb.append(stringValueOf(l[j]));
}
sb.append("}");
}
else if (primitiveType == Long.TYPE) {
long[] l = (long[]) value;
sb.append("long[").append(l.length).append("]{");
for (int j = 0; j < l.length; j++) {
if (j > 0) {
sb.append(",");
}
sb.append(stringValueOf(l[j]));
}
sb.append("}");
}
else {
throw new RuntimeException("Please implement support for type " + primitiveType.getName() +
" in ExpressionTestCase.stringValueOf()");
}
}
else if (value.getClass().getComponentType().isArray()) {
List<Object> l = Arrays.asList((Object[]) value);
if (!isNested) {
sb.append(value.getClass().getComponentType().getName());
}
sb.append("[").append(l.size()).append("]{");
int i = 0;
for (Object object : l) {
if (i > 0) {
sb.append(",");
}
i++;
sb.append(stringValueOf(object, true));
}
sb.append("}");
}
else {
List<Object> l = Arrays.asList((Object[]) value);
if (!isNested) {
sb.append(value.getClass().getComponentType().getName());
}
sb.append("[").append(l.size()).append("]{");
int i = 0;
for (Object object : l) {
if (i > 0) {
sb.append(",");
}
i++;
sb.append(stringValueOf(object));
}
sb.append("}");
}
return sb.toString();
}
else {
return value.toString();
}
}
}