First drop of SPEL

This commit is contained in:
Andy Clement
2008-08-12 16:14:43 +00:00
parent ca93824d2b
commit c2624ea05e
115 changed files with 22313 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2004-2008 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 junit.framework.Test;
import junit.framework.TestSuite;
/**
* Pulls together all the tests for Spring EL into a single suite.
*
* @author Andy Clement
*
*/
public class AllTests {
public static Test suite() {
TestSuite suite = new TestSuite("Spring Expression Language tests");
// $JUnit-BEGIN$
suite.addTestSuite(BooleanExpressionTests.class);
suite.addTestSuite(ParsingTests.class);
suite.addTestSuite(EvaluationTests.class);
suite.addTestSuite(OperatorTests.class);
suite.addTestSuite(ConstructorInvocationTests.class);
suite.addTestSuite(MethodInvocationTests.class);
suite.addTestSuite(PropertyAccessTests.class);
suite.addTestSuite(TypeReferencing.class);
suite.addTestSuite(PerformanceTests.class);
suite.addTestSuite(DefaultComparatorUnitTests.class);
suite.addTestSuite(TemplateExpressionParsing.class);
suite.addTestSuite(ExpressionLanguageScenarioTests.class);
suite.addTestSuite(ScenariosForSpringSecurity.class);
// $JUnit-END$
return suite;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2004-2008 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;
/**
* Tests the evaluation of real boolean expressions, these use AND, OR, NOT, TRUE, FALSE
*
* @author Andy Clement
*/
public class BooleanExpressionTests extends ExpressionTestCase {
public void testBooleanTrue() {
evaluate("true", Boolean.TRUE, Boolean.class);
}
public void testBooleanFalse() {
evaluate("false", Boolean.FALSE, Boolean.class);
}
public void testOr() {
evaluate("false or false", Boolean.FALSE, Boolean.class);
evaluate("false or true", Boolean.TRUE, Boolean.class);
evaluate("true or false", Boolean.TRUE, Boolean.class);
evaluate("true or true", Boolean.TRUE, Boolean.class);
}
public void testAnd() {
evaluate("false and false", Boolean.FALSE, Boolean.class);
evaluate("false and true", Boolean.FALSE, Boolean.class);
evaluate("true and false", Boolean.FALSE, Boolean.class);
evaluate("true and true", Boolean.TRUE, Boolean.class);
}
public void testNot() {
evaluate("!false", Boolean.TRUE, Boolean.class);
evaluate("!true", Boolean.FALSE, Boolean.class);
}
public void testCombinations01() {
evaluate("false and false or true", Boolean.TRUE, Boolean.class);
evaluate("true and false or true", Boolean.TRUE, Boolean.class);
evaluate("true and false or false", Boolean.FALSE, Boolean.class);
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2004-2008 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 org.springframework.expression.spel.standard.StandardEvaluationContext;
/**
* Tests invocation of constructors.
*
* @author Andy Clement
*/
public class ConstructorInvocationTests extends ExpressionTestCase {
public void testPrimitiveTypeArrayConstructors() {
evaluate("new int[]{1,2,3,4}.count()", 4, Integer.class);
evaluate("new boolean[]{true,false,true}.count()", 3, Integer.class);
evaluate("new char[]{'a','b','c'}.count()", 3, Integer.class);
evaluate("new long[]{1,2,3,4,5}.count()", 5, Integer.class);
evaluate("new short[]{2,3,4,5,6}.count()", 5, Integer.class);
evaluate("new double[]{1d,2d,3d,4d}.count()", 4, Integer.class);
evaluate("new float[]{1f,2f,3f,4f}.count()", 4, Integer.class);
evaluate("new byte[]{1,2,3,4}.count()", 4, Integer.class);
}
public void testPrimitiveTypeArrayConstructorsElements() {
evaluate("new int[]{1,2,3,4}[0]", 1, Integer.class);
evaluate("new boolean[]{true,false,true}[0]", true, Boolean.class);
evaluate("new char[]{'a','b','c'}[0]", 'a', Character.class);
evaluate("new long[]{1,2,3,4,5}[0]", 1L, Long.class);
evaluate("new short[]{2,3,4,5,6}[0]", (short) 2, Short.class);
evaluate("new double[]{1d,2d,3d,4d}[0]", (double) 1, Double.class);
evaluate("new float[]{1f,2f,3f,4f}[0]", (float) 1, Float.class);
evaluate("new byte[]{1,2,3,4}[0]", (byte) 1, Byte.class);
}
public void testTypeConstructors() {
evaluate("new String('hello world')", "hello world", String.class);
evaluate("new String(new char[]{'h','e','l','l','o'})", "hello", String.class);
}
public void testErrorCases() {
evaluateAndCheckError("new char[7]{'a','c','d','e'}", SpelMessages.INITIALIZER_LENGTH_INCORRECT);
evaluateAndCheckError("new char[3]{'a','c','d','e'}", SpelMessages.INITIALIZER_LENGTH_INCORRECT);
evaluateAndCheckError("new char[2]{'hello','world'}", SpelMessages.TYPE_CONVERSION_ERROR);
evaluateAndCheckError("new String('a','c','d')", SpelMessages.CONSTRUCTOR_NOT_FOUND);
}
public void testTypeArrayConstructors() {
evaluate("new String[]{'a','b','c','d'}[1]", "b", String.class);
evaluateAndCheckError("new String[]{'a','b','c','d'}.size()", SpelMessages.METHOD_NOT_FOUND, 30, "size()", "java.lang.String[]");
evaluateAndCheckError("new String[]{'a','b','c','d'}.juggernaut", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 30, "juggernaut", "java.lang.String[]");
evaluate("new String[]{'a','b','c','d'}.length", 4, Integer.class);
}
public void testMultiDimensionalArrays() {
evaluate("new String[3,4]","[Ljava.lang.String;[3]{java.lang.String[4]{null,null,null,null},java.lang.String[4]{null,null,null,null},java.lang.String[4]{null,null,null,null}}",new String[3][4].getClass());
}
/*
* These tests are attempting to call constructors where we need to widen or convert the argument in order to
* satisfy a suitable constructor.
*/
public void testWidening01() {
// widening of int 3 to double 3 is OK
evaluate("new Double(3)", 3.0d, Double.class);
// widening of int 3 to long 3 is OK
evaluate("new Long(3)", 3L, Long.class);
}
public void testArgumentConversion01() {
// Closest ctor will be new String(String) and converter supports Double>String
evaluate("new String(3.0d)", "3.0", String.class);
}
public void testVarargsInvocation01() throws Exception {
// Calling 'public TestCode(String... strings)'
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.setClasspath("target/test-classes/testcode.jar");
@SuppressWarnings("unused")
Object v = parser.parseExpression("new TestType('a','b','c')").getValue(ctx);
v = parser.parseExpression("new TestType('a')").getValue(ctx);
v = parser.parseExpression("new TestType()").getValue(ctx);
v = parser.parseExpression("new TestType(1,2,3)").getValue(ctx);
v = parser.parseExpression("new TestType(1)").getValue(ctx);
v = parser.parseExpression("new TestType(1,'a',3.0d)").getValue(ctx);
v = parser.parseExpression("new TestType(new String[]{'a','b','c'})").getValue(ctx);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2004-2008 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 junit.framework.TestCase;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.spel.standard.StandardComparator;
/**
* Unit tests for type comparison
*
* @author Andy Clement
*/
public class DefaultComparatorUnitTests extends TestCase {
public void testPrimitives() throws EvaluationException {
TypeComparator comparator = new StandardComparator();
// primitive int
assertTrue(comparator.compare(1, 2) < 0);
assertTrue(comparator.compare(1, 1) == 0);
assertTrue(comparator.compare(2, 1) > 0);
assertTrue(comparator.compare(1.0d, 2) < 0);
assertTrue(comparator.compare(1.0d, 1) == 0);
assertTrue(comparator.compare(2.0d, 1) > 0);
assertTrue(comparator.compare(1.0f, 2) < 0);
assertTrue(comparator.compare(1.0f, 1) == 0);
assertTrue(comparator.compare(2.0f, 1) > 0);
}
}

View File

@@ -0,0 +1,650 @@
/*
* Copyright 2004-2008 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.ArrayList;
import java.util.HashMap;
import org.springframework.expression.spel.ast.Lambda;
/**
* Tests the evaluation of real expressions in a real context.
*
* @author Andy Clement
*/
public class EvaluationTests extends ExpressionTestCase {
// literals: boolean, integer, string, hex, real, null, date
public void testLiteralBoolean01() {
evaluate("false", "false", Boolean.class);
}
public void testLiteralBoolean02() {
evaluate("true", "true", Boolean.class);
}
public void testLiteralInteger01() {
evaluate("1", "1", Integer.class);
}
public void testLiteralInteger02() {
evaluate("1415", "1415", Integer.class);
}
public void testLiteralString01() {
evaluate("'Hello World'", "Hello World", String.class);
}
public void testLiteralString02() {
evaluate("'joe bloggs'", "joe bloggs", String.class);
}
public void testLiteralString03() {
evaluate("'hello'", "hello", String.class);
}
public void testLiteralString04() {
evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class);
}
public void testLiteralString05() {
evaluate("\"Hello World\"", "Hello World", String.class);
}
public void testLiteralString06() {
evaluate("\"Hello ' World\"", "Hello ' World", String.class);
}
public void testLiteralHex01() {
evaluate("0x7FFFFFFF", "2147483647", Integer.class);
}
public void testLiteralReal01() {
evaluate("6.0221415E+23", "6.0221415E23", Double.class);
}
public void testLiteralNull01() {
evaluate("null", null, null);
}
// TODO 3 'default' format for date varies too much, we need to standardize on a format for EL
// public void testLiteralDate01() {
// eval("date('3-Feb-2008 4:50:20 PM').getTime()>0", "true", Boolean.class);
// }
public void testLiteralDate02() {
evaluate("date('19740824131030','yyyyMMddHHmmss').getHours()", "13", Integer.class);
}
// boolean operators: and or not
public void testBooleanOperators01() {
evaluate("false or false", "false", Boolean.class);
}
public void testBooleanOperators02() {
evaluate("false or true", "true", Boolean.class);
}
public void testBooleanOperators03() {
evaluate("true or false", "true", Boolean.class);
}
public void testBooleanOperators04() {
evaluate("true or true", "true", Boolean.class);
}
public void testBooleanOperators05() {
evaluate("false or true and false", "false", Boolean.class);
}
public void testBooleanErrors01() {
evaluateAndCheckError("1 or false", SpelMessages.TYPE_CONVERSION_ERROR, 0);
evaluateAndCheckError("false or 39", SpelMessages.TYPE_CONVERSION_ERROR, 9);
evaluateAndCheckError("true and 'hello'", SpelMessages.TYPE_CONVERSION_ERROR, 9);
evaluateAndCheckError(" 'hello' and 'goodbye'", SpelMessages.TYPE_CONVERSION_ERROR, 1);
evaluateAndCheckError("!35", SpelMessages.TYPE_CONVERSION_ERROR, 1);
evaluateAndCheckError("! 'foob'", SpelMessages.TYPE_CONVERSION_ERROR, 2);
}
// relational operators: lt, le, gt, ge, eq, ne
public void testRelOperatorGT01() {
evaluate("3 > 6", "false", Boolean.class);
}
public void testRelOperatorLT01() {
evaluate("3 < 6", "true", Boolean.class);
}
public void testRelOperatorLE01() {
evaluate("3 <= 6", "true", Boolean.class);
}
public void testRelOperatorGE01() {
evaluate("3 >= 6", "false", Boolean.class);
}
public void testRelOperatorGE02() {
evaluate("3 >= 3", "true", Boolean.class);
}
public void testRelOperatorsIn01() {
evaluate("3 in {1,2,3,4,5}", "true", Boolean.class);
}
public void testRelOperatorsIn02() {
evaluate("name in {null, \"Nikola Tesla\"}", "true", Boolean.class);
evaluate("name in {null, \"Anonymous\"}", "false", Boolean.class);
}
public void testRelOperatorsLike01() {
evaluate("'Abc' like '[A-Z]b.*'", "true", Boolean.class);
} // not the same as CSharp thing which matched '[A-Z]b*'
public void testRelOperatorsLike02() {
evaluate("'Abc' like '..'", "false", Boolean.class);
} // was '?'
public void testRelOperatorsLike03() {
evaluateAndCheckError("7 like '.'", SpelMessages.INVALID_FIRST_OPERAND_FOR_LIKE_OPERATOR);
}
public void testRelOperatorsLike04() {
evaluateAndCheckError("'abc' like 2.0", SpelMessages.INVALID_SECOND_OPERAND_FOR_LIKE_OPERATOR);
}
public void testRelOperatorsBetween01() {
evaluate("1 between {1, 5}", "true", Boolean.class);
}
public void testRelOperatorsBetween02() {
evaluate("'efg' between {'abc', 'xyz'}", "true", Boolean.class);
}
public void testRelOperatorsBetweenErrors01() {
evaluateAndCheckError("1 between T(String)", SpelMessages.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST, 12);
}
public void testRelOperatorsBetweenErrors02() {
evaluateAndCheckError("'abc' between {5,7}", SpelMessages.NOT_COMPARABLE, 6);
}
public void testRelOperatorsIs01() {
evaluate("'xyz' is T(int)", "false", Boolean.class);
}
public void testRelOperatorsIs02() {
evaluate("{1, 2, 3, 4, 5} is T(List)", "true", Boolean.class);
}
public void testRelOperatorsIs03() {
evaluate("{1, 2, 3, 4, 5} is T(List)", "true", Boolean.class);
}
public void testRelOperatorsMatches01() {
evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class);
}
public void testRelOperatorsMatches02() {
evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class);
}
// mathematical operators
public void testMathOperatorAdd01() {
evaluate("2 + 4", "6", Integer.class);
}
public void testMathOperatorAdd02() {
evaluate("'hello' + ' ' + 'world'", "hello world", String.class);
}
public void testMathOperatorSubtract01() {
evaluate("5 - 4", "1", Integer.class);
}
public void testMathOperatorMultiply01() {
evaluate("7 * 4", "28", Integer.class);
}
public void testMathOperatorDivide01() {
evaluate("8 / 4", "2", Integer.class);
}
public void testMathOperatorDivide02() {
evaluate("8.4 / 4", "2.1", Double.class);
}
public void testMathOperatorDivide03() {
evaluateAndAskForReturnType("8/4", new Double(2.0), Double.class);
}
// TODO decide about a conversion like this, should we support it and coerce silently?
// public void testMathOperatorDivide04() {
// evaluateAndAskForReturnType("8.4 / 4", "2", Integer.class);
// }
public void testMathOperatorModulus01() {
evaluate("7 % 4", "3", Integer.class);
}
// mixing operators
public void testMixingOperators01() {
evaluate("true and 5>3", "true", Boolean.class);
}
// property access
public void testPropertyField01() {
eval("name", "Nikola Tesla", String.class, false); // not writable because (1) name is private (2) there is no setter, only a getter
evaluateAndCheckError("madeup", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 0, "madeup",
"org.springframework.expression.spel.testresources.Inventor");
}
// nested properties
public void testPropertiesNested01() {
eval("placeOfBirth.city", "SmilJan", String.class, true);
}
public void testPropertiesNested02() {
evaluate("placeOfBirth.doubleIt(12)", "24", Integer.class);
}
// methods
public void testMethods01() {
evaluate("echo(12)", "12", String.class);
}
public void testMethods02() {
evaluate("echo(name)", "Nikola Tesla", String.class);
}
// inline list creation
public void testInlineListCreation01() {
evaluate("{1, 2, 3, 4, 5}", "[1, 2, 3, 4, 5]", ArrayList.class);
}
public void testInlineListCreation02() {
evaluate("{'abc', 'xyz'}", "[abc, xyz]", ArrayList.class);
}
// inline map creation
public void testInlineMapCreation01() {
evaluate("#{'key1':'Value 1', 'today':'Monday'}", "{key1=Value 1, today=Monday}", HashMap.class);
}
public void testInlineMapCreation02() {
evaluate("#{1:'January', 2:'February', 3:'March'}", "{2=February, 1=January, 3=March}", HashMap.class);
}
public void testInlineMapCreation03() {
evaluate("#{'key1':'Value 1', 'today':'Monday'}['key1']", "Value 1", String.class);
}
public void testInlineMapCreation04() {
evaluate("#{1:'January', 2:'February', 3:'March'}[3]", "March", String.class);
}
public void testInlineMapCreation05() {
evaluate("#{1:'January', 2:'February', 3:'March'}.get(2)", "February", String.class);
}
public void testInlineMapCreation06() {
evaluate("(#pos=3;#{1:'January', 2:'February', 3:'March'}[#pos])", "March", String.class);
}
// set construction
public void testSetConstruction01() {
evaluate("new HashSet().addAll({'a','b','c'})", "true", Boolean.class);
}
public void testSets01() {
evaluate("(#var=new HashSet();#var.addAll({'a','b','c'});#var[1])", "c", String.class);
}
// constructors
public void testConstructorInvocation01() {
evaluate("new String('hello')", "hello", String.class);
}
public void testConstructorInvocation02() {
evaluate("new String[3]", "java.lang.String[3]{null,null,null}", String[].class);
}
public void testConstructorInvocation03() {
evaluateAndCheckError("new String[]", SpelMessages.NO_SIZE_OR_INITIALIZER_FOR_ARRAY_CONSTRUCTION, 4);
}
public void testConstructorInvocation04() {
evaluateAndCheckError("new String[3]{'abc',3,'def'}", SpelMessages.INCORRECT_ELEMENT_TYPE_FOR_ARRAY, 4);
}
public void testConstructorInvocation05() {
evaluate("new java.lang.String('foobar')", "foobar", String.class);
}
// array construction
public void testArrayConstruction01() {
evaluate("new int[] {1, 2, 3, 4, 5}", "int[5]{1,2,3,4,5}", int[].class);
}
public void testArrayConstruction02() {
evaluate("new String[] {'abc', 'xyz'}", "java.lang.String[2]{abc,xyz}", String[].class);
}
// unary expressions
public void testUnaryMinus01() {
evaluate("-5", "-5", Integer.class);
}
public void testUnaryPlus01() {
evaluate("+5", "5", Integer.class);
}
public void testUnaryNot01() {
evaluate("!true", "false", Boolean.class);
}
// collection processors
// from spring.net: count,sum,max,min,average,sort,orderBy,distinct,nonNull
public void testProcessorsCount01() {
evaluate("new String[] {'abc','def','xyz'}.count()", "3", Integer.class);
}
public void testProcessorsCount02() {
evaluate("new int[] {1,2,3}.count()", "3", Integer.class);
}
public void testProcessorsMax01() {
evaluate("new int[] {1,2,3}.max()", "3", Integer.class);
}
public void testProcessorsMin01() {
evaluate("new int[] {1,2,3}.min()", "1", Integer.class);
}
public void testProcessorsKeys01() {
evaluate("#{1:'January', 2:'February', 3:'March'}.keySet().sort()", "[1, 2, 3]", ArrayList.class);
}
public void testProcessorsValues01() {
evaluate("#{1:'January', 2:'February', 3:'March'}.values().sort()", "[February, January, March]",
ArrayList.class);
}
public void testProcessorsAverage01() {
evaluate("new int[] {1,2,3}.average()", "2", Integer.class);
}
public void testProcessorsSort01() {
evaluate("new int[] {3,2,1}.sort()", "int[3]{1,2,3}", int[].class);
}
public void testCollectionProcessorsNonNull01() {
evaluate("{'a','b',null,'d',null}.nonnull()", "[a, b, d]", ArrayList.class);
}
public void testCollectionProcessorsDistinct01() {
evaluate("{'a','b','a','d','e'}.distinct()", "[a, b, d, e]", ArrayList.class);
}
// projection and selection
public void testProjection01() {
evaluate("{1,2,3,4,5,6,7,8,9,10}.!{#isEven(#this)}", "[n, y, n, y, n, y, n, y, n, y]", ArrayList.class);
}
public void testProjection02() {
evaluate("#{'a':'y','b':'n','c':'y'}.!{value=='y'?key:null}.nonnull().sort()", "[a, c]", ArrayList.class);
}
public void testProjection03() {
evaluate("{1,2,3,4,5,6,7,8,9,10}.!{#this>5}",
"[false, false, false, false, false, true, true, true, true, true]", ArrayList.class);
}
public void testProjection04() {
evaluate("{1,2,3,4,5,6,7,8,9,10}.!{$index>5?'y':'n'}", "[n, n, n, n, n, n, y, y, y, y]", ArrayList.class);
}
public void testSelection01() {
evaluate("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}", "[2, 4, 6, 8, 10]", ArrayList.class);
}
public void testSelectionError_NonBooleanSelectionCriteria() {
evaluateAndCheckError("{1,2,3,4,5,6,7,8,9,10}.?{'nonboolean'}",
SpelMessages.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN);
}
// TODO 3 Q Is $index within projection/selection useful or just cute?
public void testSelectionUsingIndex() {
evaluate("{1,2,3,4,5,6,7,8,9,10}.?{$index > 5 }", "[7, 8, 9, 10]", ArrayList.class);
}
public void testSelectionFirst01() {
evaluate("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}", "2", Integer.class);
}
public void testSelectionLast01() {
evaluate("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}", "10", Integer.class);
}
// assignment
public void testAssignmentToVariables01() {
evaluate("#var1='value1'", "value1", String.class);
}
public void testAssignmentToVariables02() {
eval("(#var1='value1';#var1)", "value1", String.class, true);
}
// Property setting
public void testAssignmentToProperty01() {
evaluate("placeOfBirth.city='SmilJan'", "SmilJan", String.class);
evaluate(
"(#oldPOB = placeOfBirth.city;placeOfBirth.city='FairOak';'From ' + #oldPOB + ' to ' + placeOfBirth.city)",
"From SmilJan to FairOak", String.class);
evaluate("placeOfBirth.city='SmilJan'", "SmilJan", String.class);
}
// Ternary operator
public void testTernaryOperator01() {
evaluate("{1}.#isEven(#this[0]) == 'y'?'it is even':'it is odd'", "it is odd", String.class);
}
public void testTernaryOperator02() {
evaluate("{2}.#isEven(#this[0]) == 'y'?'it is even':'it is odd'", "it is even", String.class);
}
// Indexer
public void testCutProcessor01() {
evaluate("{1,2,3,4,5}.cut(1,3)", "[2, 3, 4]", ArrayList.class);
}
public void testCutProcessor02() {
evaluate("{1,2,3,4,5}.cut(3,1)", "[4, 3, 2]", ArrayList.class);
}
public void testIndexer03() {
evaluate("'christian'[8]", "n", String.class);
}
// Bean references
public void testReferences01() {
eval("@(apple).name", "Apple", String.class, true);
}
public void testReferences02() {
eval("@(fruits:banana).name", "Banana", String.class, true);
}
public void testReferences03() {
evaluate("@(a.b.c)", null, null);
} // null - no context, a.b.c treated as name
public void testReferences05() {
eval("@(a/b/c:orange).name", "Orange", String.class, true);
}
// TODO 4 automatic/default imports for next line?
public void testReferences06() {
evaluate("@(apple).color.getRGB() == T(Color).green.getRGB()", "true", Boolean.class);
}
public void testReferences06b() {
evaluate("(#t='Color';@(apple).color.getRGB() == T(java.awt.Color).green.getRGB())", "true", Boolean.class);
}
public void testReferences07() {
evaluate("@(apple).color.getRGB().equals(T(java.awt.Color).green.getRGB())", "true", Boolean.class);
}
// value is not public, it is accessed through getRGB()
// public void testStaticRef01() {
// evaluate("T(Color).green.value!=0", "true", Boolean.class);
// }
public void testStaticRef02() {
evaluate("T(Color).green.getRGB()!=0", "true", Boolean.class);
}
// variables and functions
public void testVariableAccess01() {
eval("#answer", "42", Integer.class, true);
}
public void testFunctionAccess01() {
evaluate("#reverseInt(1,2,3)", "int[3]{3,2,1}", int[].class);
}
public void testFunctionAccess02() {
evaluate("#reverseString('hello')", "olleh", String.class);
}
// lambda
public void testLambdaNoArgs() {
evaluate("{|| true }", "{|| true }", Lambda.class);
}
public void testLambdaNoArgsReferenced() {
eval("(#fn={|| false };#fn)", "{|| false }", Lambda.class, true);
}
public void testLambda01() {
evaluate("{|x,y| $x > $y ? $x : $y }", "{|x,y| ($x > $y) ? $x : $y }",
org.springframework.expression.spel.ast.Lambda.class);
}
public void testLambda02() {
evaluate("(#max={|x,y| $x > $y ? $x : $y };true)", "true", Boolean.class);
}
public void testLambdaMax() {
evaluate("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "25", Integer.class);
}
public void testLambdaFactorial01() {
evaluate("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))", "120", Integer.class);
}
public void testLambdaFactorial02() {
evaluate("(#fact = {|n| $n <= 1 ? 1 : #fact($n-1) * $n }; #fact(5))", "120", Integer.class);
}
public void testLambdaAlphabet01() {
evaluate("(#alpha = {|l,s| $l>'z'?$s:#alpha($l+1,$s+$l)};#alphabet={||#alpha('a','')}; #alphabet())",
"abcdefghijklmnopqrstuvwxyz", String.class);
}
public void testLambdaAlphabet02() {
evaluate("(#alphabet = {|l,s| $l>'z'?$s:#alphabet($l+1,$s+$l)};#alphabet('a',''))",
"abcdefghijklmnopqrstuvwxyz", String.class);
}
public void testLambdaDelegation01() {
evaluate("(#sqrt={|n| T(Math).sqrt($n)};#delegate={|f,n| $f($n)};#delegate(#sqrt,4))", "2.0", Double.class);
}
// Soundex
public void testSoundex01() {
evaluate("'Rob' soundslike 'Rod'", "false", Boolean.class);
}
public void testSoundex02() {
evaluate("'Robert' soundslike 'Rupert'", "true", Boolean.class);
}
public void testSoundex03() {
evaluate("'Andy' soundslike 'Christian'", "false", Boolean.class);
}
public void testSoundex04() {
evaluate("@(fruits:).values().?{#this.colorName soundslike 'gren'}!=null", "true", Boolean.class);
}
public void testSoundex05() {
evaluate("@(fruits:).values().?{colorName soundslike 'gren'}!=null", "true", Boolean.class);
}
public void testSoundex06() {
evaluate("'Adrian' soundslike 'Adrain'", "true", Boolean.class);
}
// Word distance
public void testDistanceTo01() {
evaluate("'Saturday' distanceto 'Sunday'", "3", Integer.class);
evaluate("'Saturday' distanceto 'Monday'", "5", Integer.class);
evaluate("'Saturday' distanceto 'Saturdaz'", "1", Integer.class);
evaluate("'Saturday' distanceto 'Saturdab'", "1", Integer.class);
}
public void testDistanceTo02() {
evaluate("'Kitten' distanceto 'Sitting'", "3", Integer.class);
}
public void testVariableReferences() {
eval("(#answer=42;#answer)", "42", Integer.class, true);
eval("($answer=42;$answer)", "42", Integer.class, true);
}
// type references
public void testTypeReferences01() {
evaluate("T(java.lang.String)", "class java.lang.String", Class.class);
}
public void testTypeReferencesPrimitive() {
evaluate("T(int)", "int", Class.class);
evaluate("T(byte)", "byte", Class.class);
evaluate("T(char)", "char", Class.class);
evaluate("T(boolean)", "boolean", Class.class);
evaluate("T(long)", "long", Class.class);
evaluate("T(short)", "short", Class.class);
evaluate("T(double)", "double", Class.class);
evaluate("T(float)", "float", Class.class);
}
public void testTypeReferences02() {
evaluate("T(String)", "class java.lang.String", Class.class);
}
public void testStringType() {
evaluateAndAskForReturnType("getPlaceOfBirth().getCity()", "SmilJan", String.class);
}
public void testNumbers01() {
evaluateAndAskForReturnType("3*4+5",17,Integer.class);
evaluateAndAskForReturnType("3*4+5",17L,Long.class);
evaluateAndAskForReturnType("65",'A',Character.class);
evaluateAndAskForReturnType("3*4+5",(short)17,Short.class);
evaluateAndAskForReturnType("3*4+5","17",String.class);
}
}

View File

@@ -0,0 +1,430 @@
/*
* Copyright 2004-2008 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.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.expression.AccessException;
import org.springframework.expression.CacheablePropertyAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.PropertyReaderExecutor;
import org.springframework.expression.PropertyWriterExecutor;
import org.springframework.expression.spel.standard.StandardEvaluationContext;
import org.springframework.expression.spel.standard.StandardIndividualTypeConverter;
/**
* Testcases showing the common scenarios/use-cases for picking up the expression language support.
* The first test shows very basic usage, just drop it in and go. By 'standard infrastructure', it means:<br>
* <ul>
* <li>The context classloader is used (so, the default classpath)
* <li>Some basic type converters are included
* <li>properties/methods/constructors are discovered and invoked using reflection
* </ul>
* The scenarios after that then how how to plug in extensions:<br>
* <ul>
* <li>Adding entries to the classpath that will be used to load types and define well known 'imports'
* <li>Defining variables that are then accessible in the expression
* <li>Changing the root context object against which non-qualified references are resolved
* <li>Registering java methods as functions callable from the expression
* <li>Adding a basic property resolver
* <li>Adding an advanced (better performing) property resolver
* <li>Adding your own type converter to support conversion between any types you like
* </ul>
*
* @author Andy Clement
*/
public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
/**
* Scenario: using the standard infrastructure and running simple expression evaluation.
*/
public void testScenario_UsingStandardInfrastructure() {
try {
// Create a parser
SpelExpressionParser parser = new SpelExpressionParser();
// Parse an expression
Expression expr = parser.parseExpression("new String('hello world')");
// Evaluate it using a 'standard' context
Object value = expr.getValue();
// They are reusable
value = expr.getValue();
assertEquals("hello world", value);
assertEquals(String.class, value.getClass());
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
/**
* Scenario: using the standard context but adding a jar to the classpath and registering an import.
*/
public void testScenario_LoadingDifferentClassesAndUsingImports() {
try {
// Create a parser
SpelExpressionParser parser = new SpelExpressionParser();
// Use the standard evaluation context
StandardEvaluationContext ctx = new StandardEvaluationContext();
// Set the classpath (creates a new classloader with this classpath and uses it)
ctx.setClasspath("target/test-classes/testcode.jar");
// Register an import (so types in a.b.c can be referred to by their short name)
ctx.registerImport("a.b.c");
// Parse an expression (here, PackagedType is in package a.b.c)
Expression expr = parser.parseExpression("new PackagedType().sayHi('Andy')");
// Evaluate the expression in our context
Object value = expr.getValue(ctx);
assertEquals("Hi! Andy", value);
assertEquals(String.class, value.getClass());
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
/**
* Scenario: using the standard context but adding your own variables
*/
public void testScenario_DefiningVariablesThatWillBeAccessibleInExpressions() throws Exception {
// Create a parser
SpelExpressionParser parser = new SpelExpressionParser();
// Use the standard evaluation context
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("favouriteColour","blue");
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
ctx.setVariable("primes",primes);
Expression expr = parser.parseExpression("#favouriteColour");
Object value = expr.getValue(ctx);
assertEquals("blue", value);
expr = parser.parseExpression("#primes.get(1)");
value = expr.getValue(ctx);
assertEquals(3, value);
// all prime numbers > 10 from the list (using selection ?{...})
expr = parser.parseExpression("#primes.?{#this>10}");
value = expr.getValue(ctx);
assertEquals("[11, 13, 17]", value.toString());
}
static class TestClass {
public String str;
private int property;
public int getProperty() { return property; }
public void setProperty(int i) { property = i; }
}
/**
* Scenario: using your own root context object
*/
public void testScenario_UsingADifferentRootContextObject() throws Exception {
// Create a parser
SpelExpressionParser parser = new SpelExpressionParser();
// Use the standard evaluation context
StandardEvaluationContext ctx = new StandardEvaluationContext();
TestClass tc = new TestClass();
tc.setProperty(42);
tc.str = "wibble";
ctx.setRootObject(tc);
// read it, set it, read it again
Expression expr = parser.parseExpression("str");
Object value = expr.getValue(ctx);
assertEquals("wibble", value);
expr = parser.parseExpression("str");
expr.setValue(ctx,"wobble");
expr = parser.parseExpression("str");
value = expr.getValue(ctx);
assertEquals("wobble", value);
// or using assignment within the expression
expr = parser.parseExpression("str='wabble'");
value = expr.getValue(ctx);
expr = parser.parseExpression("str");
value = expr.getValue(ctx);
assertEquals("wabble", value);
// private property will be accessed through getter()
expr = parser.parseExpression("property");
value = expr.getValue(ctx);
assertEquals(42, value);
// ... and set through setter
expr = parser.parseExpression("property=4");
value = expr.getValue(ctx);
expr = parser.parseExpression("property");
value = expr.getValue(ctx);
assertEquals(4,value);
}
public static String repeat(String s) { return s+s; }
/**
* Scenario: using your own java methods and calling them from the expression
*/
public void testScenario_RegisteringJavaMethodsAsFunctionsAndCallingThem() throws SecurityException, NoSuchMethodException {
try {
// Create a parser
SpelExpressionParser parser = new SpelExpressionParser();
// Use the standard evaluation context
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.registerFunction("repeat",ExpressionLanguageScenarioTests.class.getDeclaredMethod("repeat",String.class));
Expression expr = parser.parseExpression("#repeat('hello')");
Object value = expr.getValue(ctx);
assertEquals("hellohello", value);
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
/**
* Scenario: add a property resolver that will get called in the resolver chain, this one only supports reading.
*/
public void testScenario_AddingYourOwnPropertyResolvers_1() throws SecurityException, NoSuchMethodException {
try {
// Create a parser
SpelExpressionParser parser = new SpelExpressionParser();
// Use the standard evaluation context
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.addPropertyAccessor(new FruitColourAccessor());
Expression expr = parser.parseExpression("orange");
Object value = expr.getValue(ctx);
assertEquals(Color.orange,value);
try {
expr.setValue(ctx,Color.blue);
fail("Should not be allowed to set oranges to be blue !");
} catch (EvaluationException ee) {
SpelException ele = (SpelException)ee;
assertEquals(ele.getMessageUnformatted(),SpelMessages.PROPERTY_OR_FIELD_SETTER_NOT_FOUND);
}
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
/**
* Regardless of the current context object, or root context object, this resolver can tell you what colour a fruit is !
* It only supports property reading, not writing. To support writing it would need to override canWrite() and write()
*/
static class FruitColourAccessor implements PropertyAccessor {
private static Map<String,Color> propertyMap = new HashMap<String,Color>();
static {
propertyMap.put("banana",Color.yellow);
propertyMap.put("apple",Color.red);
propertyMap.put("orange",Color.orange);
}
/**
* Null means you might be able to read any property, if an earlier property resolver hasn't beaten you to it
*/
public Class[] getSpecificTargetClasses() {
return null;
}
public boolean canRead(EvaluationContext context, Object target, Object name) throws AccessException {
return propertyMap.containsKey(name);
}
public Object read(EvaluationContext context, Object target, Object name) throws AccessException {
return propertyMap.get(name);
}
public boolean canWrite(EvaluationContext context, Object target, Object name) throws AccessException {
return false;
}
public void write(EvaluationContext context, Object target, Object name, Object newValue)
throws AccessException {
}
}
/**
* Scenario: add an optimized property resolver. Property resolution can be thought of it two parts: resolving (finding the property you mean) and accessing (reading or writing that property).
* In some cases the act of discovering which property is meant is expensive - and there is no benefit to rediscovering it every time the expression is evaluated as it will
* always be the same property. For example, with reflection it can be expensive to find out which field on an object maps to the property, but it will always be the same field
* for each evaluation. In these cases we use a Resolver/Executor based property accessor. In this setup the property resolver does not immediately return the value of the property,
* instead it returns an executor object that can be used to read the property. The executor can be cached and reused by SPEL so it does not go back to the resolver every time the
* expression is evaluated. In this testcase we use this different accessor mechanism to return the colours of vegetables.
*/
public void testScenario_AddingYourOwnPropertyResolvers_2() throws SecurityException, NoSuchMethodException {
try {
// Create a parser
SpelExpressionParser parser = new SpelExpressionParser();
// Use the standard evaluation context
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.addPropertyAccessor(new VegetableColourAccessor());
Expression expr = parser.parseExpression("pea");
Object value = expr.getValue(ctx);
assertEquals(Color.green,value);
try {
expr.setValue(ctx,Color.blue);
fail("Should not be allowed to set peas to be blue !");
} catch (EvaluationException ee) {
SpelException ele = (SpelException)ee;
assertEquals(ele.getMessageUnformatted(),SpelMessages.PROPERTY_OR_FIELD_SETTER_NOT_FOUND);
}
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
/**
* Regardless of the current context object, or root context object, this resolver can tell you what colour a vegetable is !
* It only supports property reading, not writing.
*/
static class VegetableColourAccessor extends CacheablePropertyAccessor {
private static Map<String,Color> propertyMap = new HashMap<String,Color>();
static {
propertyMap.put("carrot",Color.orange);
propertyMap.put("pea",Color.green);
}
/**
* Null means you might be able to read any property, if an earlier property resolver hasn't beaten you to it
*/
public Class[] getSpecificTargetClasses() {
return null;
}
/**
* Work out if we can resolve the named property and if so return an executor that can be cached and reused to
* discover the value.
*/
public PropertyReaderExecutor getReaderAccessor(EvaluationContext relatedContext, Object target, Object name) {
if (propertyMap.containsKey(name)) {
return new VegetableColourExecutor(propertyMap.get(name));
}
return null;
}
public PropertyWriterExecutor getWriterAccessor(EvaluationContext context, Object target, Object name) {
return null;
}
}
static class VegetableColourExecutor implements PropertyReaderExecutor {
private Color colour;
public VegetableColourExecutor(Color colour) {
this.colour = colour;
}
public Object execute(EvaluationContext context, Object target) throws AccessException {
return colour;
}
}
/**
* Scenario: adding your own type converter
*/
public void testScenario_AddingYourOwnTypeConverter() throws SecurityException, NoSuchMethodException {
try {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.registerFunction("functionTakesColour",ExpressionLanguageScenarioTests.class.getDeclaredMethod("functionTakesColour",Color.class));
Expression expr = parser.parseExpression("#functionTakesColour('orange')");
try {
Object value = expr.getValue(ctx);
fail("Should have failed, no type converter registered");
} catch (EvaluationException ee) {}
ctx.addTypeConverter(new StringToColorConverter());
Object value = expr.getValue(ctx);
assertEquals(Color.orange,value);
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
public static Color functionTakesColour(Color c) {return c;}
static class StringToColorConverter implements StandardIndividualTypeConverter {
public Object convert(Object value) throws EvaluationException {
String colourName = (String)value;
if (colourName.equals("orange")) return Color.orange;
else if (colourName.equals("red")) return Color.red;
else return Color.blue; // hmm, quite a simplification here
}
public Class<?>[] getFrom() {
return new Class[]{String.class};
}
public Class<?> getTo() {
return Color.class;
}
}
}

View File

@@ -0,0 +1,298 @@
/*
* Copyright 2004-2008 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 junit.framework.TestCase;
import org.springframework.expression.Expression;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.StandardEvaluationContext;
/**
* Common superclass for expression tests.
*
* @author Andy Clement
*/
public abstract class ExpressionTestCase extends TestCase {
private final static boolean DEBUG = false;
protected static SpelExpressionParser parser = new SpelExpressionParser();
protected static 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) {
try {
SpelExpression expr = parser.parseExpression(expression);
if (expr == null) {
fail("Parser returned null for expression");
}
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}
// Class<?> expressionType = expr.getValueType();
// assertEquals("Type of the expression is not as expected. Should be '"+expectedResultType+"' but is
// '"+expressionType+"'",
// expectedResultType,expressionType);
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);
// .equals/* isAssignableFrom */(resultType), truers);
// TODO isAssignableFrom would allow some room for compatibility
// in the above expression...
if (expectedValue instanceof String) {
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue,
ExpressionTestCase.stringValueOf(value));
} else {
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
}
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
public void evaluateAndAskForReturnType(String expression, Object expectedValue, Class<?> expectedResultType) {
try {
SpelExpression expr = (SpelExpression) parser.parseExpression(expression);
if (expr == null) {
fail("Parser returned null for expression");
}
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}
// Class<?> expressionType = expr.getValueType();
// assertEquals("Type of the expression is not as expected. Should be '"+expectedResultType+"' but is
// '"+expressionType+"'",
// expectedResultType,expressionType);
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);
// .equals/* isAssignableFrom */(resultType), truers);
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue, value);
// isAssignableFrom would allow some room for compatibility
// in the above expression...
} catch (EvaluationException ee) {
SpelException ex = (SpelException) ee;
ex.printStackTrace();
fail("Unexpected EvaluationException: " + ex.getMessage());
} catch (ParseException pe) {
fail("Unexpected ParseException: " + pe.getMessage());
}
}
/**
* 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 eval(String expression, Object expectedValue, Class<?> expectedClassOfResult, boolean shouldBeWritable) {
try {
SpelExpression e = (SpelExpression) parser.parseExpression(expression);
if (e == null) {
fail("Parser returned null for expression");
}
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, e);
}
Object value = e.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();
assertEquals("Did not get expected value for expression '" + expression + "'.", expectedValue,
ExpressionTestCase.stringValueOf(value));
assertEquals("Type of the result was not as expected. Expected '" + expectedClassOfResult
+ "' but result was of type '" + resultType + "'", expectedClassOfResult
.equals/* isAssignableFrom */(resultType), true);
// TODO 4 isAssignableFrom would allow some room for compatibility
// in the above expression...
boolean isWritable = e.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");
}
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
/**
* 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, SpelMessages expectedMessage, Object... otherProperties) {
try {
Expression expr = (Expression) parser.parseExpression(expression);
if (expr == null) {
fail("Parser returned null for expression");
}
@SuppressWarnings("unused")
Object value = expr.getValue(eContext);
fail("Should have failed with message " + expectedMessage);
} catch (EvaluationException ee) {
SpelException ex = (SpelException) ee;
if (ex.getMessageUnformatted() != expectedMessage) {
System.out.println(ex.getMessage());
ex.printStackTrace();
assertEquals("Failed to get expected message", expectedMessage, ex.getMessageUnformatted());
}
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) {
ex.printStackTrace();
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])) {
ex.printStackTrace();
fail("Insert does not match, expected '" + otherProperties[i] + "' but insert value was '"
+ inserts[i - 1] + "'");
}
}
}
}
} catch (ParseException pe) {
pe.printStackTrace();
fail("Unexpected Exception: " + pe.getMessage());
}
}
public static String stringValueOf(Object value) {
// do something nice for arrays
if (value==null) return "null";
if (value.getClass().isArray()) {
StringBuilder sb = new StringBuilder();
if (value.getClass().getComponentType().isPrimitive()) {
// TODO 4 ought to support other primitives!
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 {
List<Object> l = Arrays.asList((Object[]) value);
sb.append(value.getClass().getComponentType().getName()).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();
}
}
// protected void evaluateAndCheckError(String string, ELMessages expectedMessage, Object... otherProperties) {
// try {
// SpelExpression expr = (SpelExpression) parser.parseExpression(string);
// if (expr == null)
// fail("Parser returned null for expression");
// // expr.printAST(System.out);
// @SuppressWarnings("unused")
// Object value = expr.getValue(eContext);
// fail("Should have failed with message " + expectedMessage);
// } catch (ExpressionException ee) {
// ELException ex = (ELException) ee;
// if (expectedMessage != ex.getMessageUnformatted()) {
// System.out.println(ex.getMessage());
// ex.printStackTrace();
// assertEquals("Failed to get expected message", expectedMessage, ex.getMessageUnformatted());
// }
// 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());
// }
// }
//
// }
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2004-2008 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 org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.standard.StandardEvaluationContext;
/**
* Tests invocation of methods.
*
* @author Andy Clement
*/
@SuppressWarnings("unused")
public class MethodInvocationTests extends ExpressionTestCase {
public void testSimpleAccess01() {
evaluate("getPlaceOfBirth().getCity()", "SmilJan", String.class);
}
public void testBuiltInProcessors() {
evaluate("new int[]{1,2,3,4}.count()", 4, Integer.class);
evaluate("new int[]{4,3,2,1}.sort()[3]", 4, Integer.class);
evaluate("new int[]{4,3,2,1}.average()", 2, Integer.class);
evaluate("new int[]{4,3,2,1}.max()", 4, Integer.class);
evaluate("new int[]{4,3,2,1}.min()", 1, Integer.class);
evaluate("new int[]{4,3,2,1,2,3}.distinct().count()", 4, Integer.class);
evaluate("{1,2,3,null}.nonnull().count()", 3, Integer.class);
evaluate("new int[]{4,3,2,1,2,3}.distinct().count()", 4, Integer.class);
}
public void testStringClass() {
evaluate("new java.lang.String('hello').charAt(2)", 'l', Character.class);
// TODO 3 hmmm, have to do the second charAt() because all '' are strings, never chars and cannot do cast
evaluate("new java.lang.String('hello').charAt(2).equals('l'.charAt(0))", true, Boolean.class);
evaluate("'HELLO'.toLowerCase()", "hello", String.class);
evaluate("' abcba '.trim()", "abcba", String.class);
}
public void testNonExistentMethods() {
// name is ok but madeup() does not exist
evaluateAndCheckError("name.madeup()", SpelMessages.METHOD_NOT_FOUND, 5);
}
public void testWidening01() {
// widening of int 3 to double 3 is OK
evaluate("new Double(3.0d).compareTo(8)", -1, Integer.class);
evaluate("new Double(3.0d).compareTo(3)", 0, Integer.class);
evaluate("new Double(3.0d).compareTo(2)", 1, Integer.class);
}
public void testArgumentConversion01() {
// Rely on Double>String conversion for calling startsWith()
evaluate("new String('hello 2.0 to you').startsWith(7.0d)", false, Boolean.class);
evaluate("new String('7.0 foobar').startsWith(7.0d)", true, Boolean.class);
}
public void testVarargsInvocation01() {
// Calling 'public int aVarargsMethod(String... strings)'
evaluate("aVarargsMethod('a','b','c')",3,Integer.class);
evaluate("aVarargsMethod('a')",1,Integer.class);
evaluate("aVarargsMethod()",0,Integer.class);
evaluate("aVarargsMethod(1,2,3)",3,Integer.class); // all need converting to strings
evaluate("aVarargsMethod(1)",1,Integer.class); // needs string conversion
evaluate("aVarargsMethod(1,'a',3.0d)",3,Integer.class); // first and last need conversion
evaluate("aVarargsMethod(new String[]{'a','b','c'})",3,Integer.class);
}
public void testVarargsInvocation02() {
// Calling 'public int aVarargsMethod2(int i, String... strings)' - returns int+length_of_strings
evaluate("aVarargsMethod2(5,'a','b','c')",8,Integer.class);
evaluate("aVarargsMethod2(2,'a')",3,Integer.class);
evaluate("aVarargsMethod2(4)",4,Integer.class);
evaluate("aVarargsMethod2(8,2,3)",10,Integer.class);
evaluate("aVarargsMethod2(9)",9,Integer.class);
evaluate("aVarargsMethod2(2,'a',3.0d)",4,Integer.class);
evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})",11,Integer.class);
}
// Due to conversion there are two possible methods to call ...
public void testVarargsInvocation03() throws Exception {
// Calling 'm(String... strings)' and 'm(int i,String... strings)'
try {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.setClasspath("target/test-classes/testcode.jar");
Object v = null;
v = parser.parseExpression("new TestType().m(1,2,3)").getValue(ctx);
// v = parser.parseExpression("new TestType().m('a','b','c')").getValue(ctx);
// v = parser.parseExpression("new TestType().m(5,'a','b','c')").getValue(ctx);
// v = parser.parseExpression("new TestType().m()").getValue(ctx);
// v = parser.parseExpression("new TestType().m(1)").getValue(ctx);
// v = parser.parseExpression("new TestType().m(1,'a',3.0d)").getValue(ctx);
// v = parser.parseExpression("new TestType().m(new String[]{'a','b','c'})").getValue(ctx);
fail("Should have detected ambiguity, there are two possible matches");
} catch (EvaluationException ee) {
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2004-2008 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;
/**
* Tests the evaluation of expressions using relational operators.
*
* @author Andy Clement
*/
public class OperatorTests extends ExpressionTestCase {
public void testIntegerLiteral() {
evaluate("3", 3, Integer.class);
}
public void testRealLiteral() {
evaluate("3.5", 3.5d, Double.class);
}
public void testLessThan() {
evaluate("3 < 5", true, Boolean.class);
evaluate("5 < 3", false, Boolean.class);
}
public void testLessThanOrEqual() {
evaluate("3 <= 5", true, Boolean.class);
evaluate("5 <= 3", false, Boolean.class);
evaluate("6 <= 6", true, Boolean.class);
}
public void testEqual() {
evaluate("3 == 5", false, Boolean.class);
evaluate("5 == 3", false, Boolean.class);
evaluate("6 == 6", true, Boolean.class);
}
public void testGreaterThanOrEqual() {
evaluate("3 >= 5", false, Boolean.class);
evaluate("5 >= 3", true, Boolean.class);
evaluate("6 >= 6", true, Boolean.class);
}
public void testGreaterThan() {
evaluate("3 > 5", false, Boolean.class);
evaluate("5 > 3", true, Boolean.class);
}
public void testMultiplyStringInt() {
evaluate("'a' * 5", "aaaaa", String.class);
}
public void testMultiplyIntInt() {
evaluate("3 * 5", 15, Integer.class);
}
public void testMultiplyDoubleDoubleGivesDouble() {
evaluate("3.0d * 5.0d", 15.0d, Double.class);
}
}

View File

@@ -0,0 +1,463 @@
/*
* Copyright 2004-2008 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 junit.framework.TestCase;
import org.springframework.expression.ParseException;
/**
* Parse some expressions and check we get the AST we expect. Rather than inspecting each node in the AST, we ask it to
* write itself to a string form and check that is as expected.
*
* @author Andy Clement
*/
public class ParsingTests extends TestCase {
private SpelExpressionParser parser;
public void setUp() {
parser = new SpelExpressionParser();
}
// literals
public void testLiteralBoolean01() {
parseCheck("false");
}
public void testLiteralLong01() {
parseCheck("37L","37");
}
public void testLiteralBoolean02() {
parseCheck("true");
}
public void testLiteralInteger01() {
parseCheck("1");
}
public void testLiteralInteger02() {
parseCheck("1415");
}
public void testLiteralString01() {
parseCheck("'hello'");
}
public void testLiteralString02() {
parseCheck("'joe bloggs'");
}
public void testLiteralString03() {
parseCheck("'Tony''s Pizza'", "'Tony's Pizza'");
}
public void testLiteralReal01() {
parseCheck("6.0221415E+23", "6.0221415E23");
}
public void testLiteralHex01() {
parseCheck("0x7FFFFFFF", "2147483647");
}
public void testLiteralDate01() {
parseCheck("date('1974/08/24')");
}
public void testLiteralDate02() {
parseCheck("date('19740824T131030','yyyyMMddTHHmmss')");
}
public void testLiteralNull01() {
parseCheck("null");
}
// boolean operators
public void testBooleanOperatorsOr01() {
parseCheck("false or false", "(false or false)");
}
public void testBooleanOperatorsOr02() {
parseCheck("false or true", "(false or true)");
}
public void testBooleanOperatorsOr03() {
parseCheck("true or false", "(true or false)");
}
public void testBooleanOperatorsOr04() {
parseCheck("true or false", "(true or false)");
}
public void testBooleanOperatorsMix01() {
parseCheck("false or true and false", "(false or (true and false))");
}
// relational operators
public void testRelOperatorsGT01() {
parseCheck("3>6", "(3 > 6)");
}
public void testRelOperatorsLT01() {
parseCheck("3<6", "(3 < 6)");
}
public void testRelOperatorsLE01() {
parseCheck("3<=6", "(3 <= 6)");
}
public void testRelOperatorsGE01() {
parseCheck("3>=6", "(3 >= 6)");
}
public void testRelOperatorsGE02() {
parseCheck("3>=3", "(3 >= 3)");
}
public void testRelOperatorsIn01() {
parseCheck("3 in {1,2,3,4,5}", "(3 in {1,2,3,4,5})");
}
public void testRelOperatorsLike01() {
parseCheck("'Abc' like '[A-Z]b*'", "('Abc' like '[A-Z]b*')");
}
public void testRelOperatorsLike02() {
parseCheck("'Abc' like '?'", "('Abc' like '?')");
}
public void testRelOperatorsBetween01() {
parseCheck("1 between {1, 5}", "(1 between {1,5})");
}
public void testRelOperatorsBetween02() {
parseCheck("'efg' between {'abc', 'xyz'}", "('efg' between {'abc','xyz'})");
}// true
public void testRelOperatorsIs01() {
parseCheck("'xyz' is int", "('xyz' is int)");
}// false
public void testRelOperatorsIs02() {
parseCheck("{1, 2, 3, 4, 5} is List", "({1,2,3,4,5} is List)");
}// true
public void testRelOperatorsMatches01() {
parseCheck("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "('5.0067' matches '^-?\\d+(\\.\\d{2})?$')");
}// false
public void testRelOperatorsMatches02() {
parseCheck("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "('5.00' matches '^-?\\d+(\\.\\d{2})?$')");
}// true
// mathematical operators
public void testMathOperatorsAdd01() {
parseCheck("2+4", "(2 + 4)");
}
public void testMathOperatorsAdd02() {
parseCheck("'a'+'b'", "('a' + 'b')");
}
public void testMathOperatorsAdd03() {
parseCheck("'hello'+' '+'world'", "(('hello' + ' ') + 'world')");
}
public void testMathOperatorsSubtract01() {
parseCheck("5-4", "(5 - 4)");
}
public void testMathOperatorsMultiply01() {
parseCheck("7*4", "(7 * 4)");
}
public void testMathOperatorsDivide01() {
parseCheck("8/4", "(8 / 4)");
}
public void testMathOperatorModulus01() {
parseCheck("7 % 4", "(7 % 4)");
}
// mixed operators
public void testMixedOperators01() {
parseCheck("true and 5>3", "(true and (5 > 3))");
}
// collection processors
public void testCollectionProcessorsCount01() {
parseCheck("new String[] {'abc','def','xyz'}.count()");
}
public void testCollectionProcessorsCount02() {
parseCheck("new int[] {1,2,3}.count()");
}
public void testCollectionProcessorsMax01() {
parseCheck("new int[] {1,2,3}.max()");
}
public void testCollectionProcessorsMin01() {
parseCheck("new int[] {1,2,3}.min()");
}
public void testCollectionProcessorsAverage01() {
parseCheck("new int[] {1,2,3}.average()");
}
public void testCollectionProcessorsSort01() {
parseCheck("new int[] {3,2,1}.sort()");
}
public void testCollectionProcessorsNonNull01() {
parseCheck("{'a','b',null,'d',null}.nonNull()");
}
public void testCollectionProcessorsDistinct01() {
parseCheck("{'a','b','a','d','e'}.distinct()");
}
// references
public void testReferences01() {
parseCheck("@(foo)");
}
public void testReferences02() {
parseCheck("@(p:foo)");
}
public void testReferences04() {
parseCheck("@(a/b/c:foo)", "@(a.b.c:foo)");
}// normalized to '.' for separator in QualifiedIdentifier
// properties
public void testProperties01() {
parseCheck("name");
}
public void testProperties02() {
parseCheck("placeofbirth.CitY");
}
public void testProperties03() {
parseCheck("a.b.c.d.e");
}
// inline list creation
public void testInlineListCreation01() {
parseCheck("{1, 2, 3, 4, 5}", "{1,2,3,4,5}");
}
public void testInlineListCreation02() {
parseCheck("{'abc','xyz'}", "{'abc','xyz'}");
}
// inline map creation
public void testInlineMapCreation01() {
parseCheck("#{'key1':'Value 1', 'today':DateTime.Today}");
}
public void testInlineMapCreation02() {
parseCheck("#{1:'January', 2:'February', 3:'March'}");
}
public void testInlineMapCreation03() {
parseCheck("#{'key1':'Value 1', 'today':'Monday'}['key1']");
}
public void testInlineMapCreation04() {
parseCheck("#{1:'January', 2:'February', 3:'March'}[3]");
}
// methods
public void testMethods01() {
parseCheck("echo(12)");
}
public void testMethods02() {
parseCheck("echo(name)");
}
public void testMethods03() {
parseCheck("age.doubleItAndAdd(12)");
}
// constructors
public void testConstructors01() {
parseCheck("new String('hello')");
}
public void testConstructors02() {
parseCheck("new String[3]");
}
// array construction
public void testArrayConstruction01() {
parseCheck("new int[] {1, 2, 3, 4, 5}", "new int[] {1,2,3,4,5}");
}
public void testArrayConstruction02() {
parseCheck("new String[] {'abc','xyz'}", "new String[] {'abc','xyz'}");
}
// variables and functions
public void testVariables01() {
parseCheck("#foo");
}
public void testFunctions01() {
parseCheck("#fn(1,2,3)");
}
public void testFunctions02() {
parseCheck("#fn('hello')");
}
// projections and selections
public void testProjections01() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.!{#isEven()}");
}
public void testSelections01() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.?{#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.?{(#isEven(#this) == 'y')}");
}
public void testSelectionsFirst01() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.^{#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.^{(#isEven(#this) == 'y')}");
}
public void testSelectionsLast01() {
parseCheck("{1,2,3,4,5,6,7,8,9,10}.${#isEven(#this) == 'y'}",
"{1,2,3,4,5,6,7,8,9,10}.${(#isEven(#this) == 'y')}");
}
// assignment
public void testAssignmentToVariables01() {
parseCheck("#var1='value1'");
}
// ternary operator
public void testTernaryOperator01() {
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
"({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd'");
}
// lambda
public void testLambda01() {
parseCheck("{|x,y| $x > $y ? $x : $y }", "{|x,y| ($x > $y) ? $x : $y }");
}
public void testLambdaMax() {
parseCheck("(#max = {|x,y| $x > $y ? $x : $y }; #max(5,25))", "(#max={|x,y| ($x > $y) ? $x : $y };#max(5,25))");
}
public void testLambdaFactorial() {
parseCheck("(#fact = {|n| $n <= 1 ? 1 : $n * #fact($n-1) }; #fact(5))",
"(#fact={|n| ($n <= 1) ? 1 : ($n * #fact(($n - 1))) };#fact(5))");
} // 120
// Type references
public void testTypeReferences01() {
parseCheck("T(java.lang.String)");
}
public void testTypeReferences02() {
parseCheck("T(String)");
}
// Nesting expressions and expression lists
public void testExpressionLists01() {
parseCheck("(3;4;5)");
}
public void testExpressionLists02() {
parseCheck("( (3;4);5)", "((3;4);5)");
}
// TODO 4 parser recovery for this next one: missing semi
// public void testExpressionLists02a() { parseCheck("( (3;4)5)","((3;4);5)");}
// // badly formed, missing a semi
public void testExpressionLists03() {
parseCheck("(3;(4;5))");
}
public void testExpressionLists04() {
parseCheck("((3;4;5))", "(3;4;5)");
}
public void testExpressionLists05() {
parseCheck("((3;4)+(5;6))", "((3;4) + (5;6))");
}
public void testExpressionLists06() {
parseCheck("((3;4;)+(5;6))", "((3;4) + (5;6))");
}
public void testExpressionLists07() {
parseCheck("((3;4;)+(5;6;))", "((3;4) + (5;6))");
}
// TODO 3 too many close brackets - parser recover
// public void testExpressionLists07a() { parseCheck("((3;4;)+(5;6;)))","((3;4)
// + (5;6))");}
// parser warnings/errors
// public void testBrokenExpression01() {
// parseCheck("1 + ");
//
// }
// ---
/**
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the same
* as the original expression.
*
* @param expression the expression to parse *and* the expected value of the string form of the resultant AST
*/
public void parseCheck(String expression) {
parseCheck(expression, expression);
}
/**
* Parse the supplied expression and then create a string representation of the resultant AST, it should be the
* expected value.
*
* @param expression the expression to parse
* @param expectedStringFormOfAST the expected string form of the AST
*/
public void parseCheck(String expression, String expectedStringFormOfAST) {
try {
SpelExpression e = parser.parseExpression(expression);
if (e != null && !e.toStringAST().equals(expectedStringFormOfAST)) {
SpelUtilities.printAbstractSyntaxTree(System.err, e);
}
if (e == null) {
fail("Parsed exception was null");
}
assertEquals("String form of AST does not match expected output", expectedStringFormOfAST, e.toStringAST());
} catch (ParseException ee) {
ee.printStackTrace();
fail("Unexpected Exception: " + ee.getMessage());
}
}
}

View File

@@ -0,0 +1,296 @@
/*
* Copyright 2004-2008 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 junit.framework.TestCase;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.ast.ConstructorReference;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
/**
* Tests the evaluation of real expressions in a real context.
*
* @author Andy Clement
*/
@SuppressWarnings("unused")
public class PerformanceTests extends TestCase {
public static final int ITERATIONS = 1000;
public static final boolean report = true;
private static SpelExpressionParser parser = new SpelExpressionParser();
private static EvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();;
public void testPerformanceOfSimpleAccess() throws Exception {
long starttime = 0;
long endtime = 0;
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Expression expr = (Expression) parser.parseExpression("getPlaceOfBirth().getCity()");
if (expr == null)
fail("Parser returned null for expression");
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long freshParseTime = endtime - starttime;
Expression expr = (Expression) parser.parseExpression("getPlaceOfBirth().getCity()");
if (expr == null)
fail("Parser returned null for expression");
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long reuseTime = endtime - starttime;
if (reuseTime > freshParseTime) {
System.out.println("Fresh parse every time, ITERATIONS iterations = " + freshParseTime + "ms");
System.out.println("Reuse SpelExpression, ITERATIONS iterations = " + reuseTime + "ms");
fail("Should have been quicker to reuse!");
}
}
/**
* Testing that using a resolver/executor split for constructor invocation (ie. just doing the reflection once to
* find the constructor then executing it over and over) is faster than redoing the reflection and execution every
* time.
*
* MacBook speeds: 4-Aug-08 <br>
* Fresh parse every time, ITERATIONS iterations = 373ms <br>
* Reuse SpelExpression, ITERATIONS iterations = 1ms <br>
* Reuse SpelExpression (caching off), ITERATIONS iterations = 188ms <br>
*/
public void testConstructorResolverExecutorBenefit01() throws Exception {
long starttime = 0;
long endtime = 0;
// warmup
for (int i = 0; i < ITERATIONS; i++) {
Expression expr = (Expression) parser.parseExpression("new Integer(5)");
if (expr == null) {
fail("Parser returned null for expression");
}
Object value = expr.getValue(eContext);
}
// ITERATIONS calls, parsing fresh each time
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Expression expr = (Expression) parser.parseExpression("new Integer(5)");
if (expr == null) {
fail("Parser returned null for expression");
}
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long freshParseTime = endtime - starttime;
// ITERATIONS calls, parsing once and using cached executor
Expression expr = (Expression) parser.parseExpression("new Integer(5)");
if (expr == null) {
fail("Parser returned null for expression");
}
try {
ConstructorReference.useCaching = false;
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Object value = expr.getValue(eContext);
}
} finally {
ConstructorReference.useCaching = true;
}
endtime = System.currentTimeMillis();
long cachingOffReuseTime = endtime - starttime;
// ITERATIONS calls, parsing once and using cached executor
expr = (Expression) parser.parseExpression("new Integer(5)");
if (expr == null) {
fail("Parser returned null for expression");
}
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long reuseTime = endtime - starttime;
if (report) {
System.out.println("Timings for constructor execution 'new Integer(5)'");
System.out.println("Fresh parse every time, " + ITERATIONS + " iterations = " + freshParseTime + "ms");
System.out.println("Reuse SpelExpression (caching off), " + ITERATIONS + " iterations = "
+ cachingOffReuseTime + "ms");
System.out.println("Reuse SpelExpression, " + ITERATIONS + " iterations = " + reuseTime + "ms");
}
if (reuseTime > freshParseTime) {
fail("Should have been quicker to reuse a parsed expression!");
}
if (reuseTime > cachingOffReuseTime) {
fail("Should have been quicker to reuse cached!");
}
}
/**
* Testing that using a resolver/executor split for property access is faster than redoing the reflection and
* execution every time.
*
* MacBook speeds: <br>
*/
public void testPropertyResolverExecutorBenefit_Reading() throws Exception {
long starttime = 0;
long endtime = 0;
// warmup
for (int i = 0; i < ITERATIONS; i++) {
Expression expr = (Expression) parser.parseExpression("getPlaceOfBirth().city");
if (expr == null) {
fail("Parser returned null for expression");
}
Object value = expr.getValue(eContext);
}
// ITERATIONS calls, parsing fresh each time
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Expression expr = (Expression) parser.parseExpression("getPlaceOfBirth().city");
if (expr == null) {
fail("Parser returned null for expression");
}
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long freshParseTime = endtime - starttime;
// ITERATIONS calls, parsing once and using cached executor
Expression expr = (Expression) parser.parseExpression("getPlaceOfBirth().city");
if (expr == null) {
fail("Parser returned null for expression");
}
try {
PropertyOrFieldReference.useCaching = false;
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Object value = expr.getValue(eContext);
}
} finally {
PropertyOrFieldReference.useCaching = true;
}
endtime = System.currentTimeMillis();
long cachingOffReuseTime = endtime - starttime;
// ITERATIONS calls, parsing once and using cached executor
expr = (Expression) parser.parseExpression("getPlaceOfBirth().city");
if (expr == null) {
fail("Parser returned null for expression");
}
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long reuseTime = endtime - starttime;
if (report) {
System.out.println("Timings for property reader execution 'getPlaceOfBirth().city'");
System.out.println("Fresh parse every time, " + ITERATIONS + " iterations = " + freshParseTime + "ms");
System.out.println("Reuse SpelExpression (caching off), " + ITERATIONS + " iterations = "
+ cachingOffReuseTime + "ms");
System.out.println("Reuse SpelExpression, " + ITERATIONS + " iterations = " + reuseTime + "ms");
}
if (reuseTime > freshParseTime) {
fail("Should have been quicker to reuse a parsed expression!");
}
if (reuseTime > cachingOffReuseTime) {
fail("Should have been quicker to reuse cached!");
}
}
/**
* Testing that using a resolver/executor split for property writing is faster than redoing the reflection and
* execution every time.
*
* MacBook speeds: <br>
*/
public void testPropertyResolverExecutorBenefit_Writing() throws Exception {
long starttime = 0;
long endtime = 0;
// warmup
for (int i = 0; i < ITERATIONS; i++) {
Expression expr = (Expression) parser.parseExpression("randomField='Andy'");
if (expr == null) {
fail("Parser returned null for expression");
}
Object value = expr.getValue(eContext);
}
// ITERATIONS calls, parsing fresh each time
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Expression expr = (Expression) parser.parseExpression("randomField='Andy'");
if (expr == null) {
fail("Parser returned null for expression");
}
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long freshParseTime = endtime - starttime;
// ITERATIONS calls, parsing once and using cached executor
Expression expr = (Expression) parser.parseExpression("randomField='Andy'");
if (expr == null) {
fail("Parser returned null for expression");
}
try {
PropertyOrFieldReference.useCaching = false;
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Object value = expr.getValue(eContext);
}
} finally {
PropertyOrFieldReference.useCaching = true;
}
endtime = System.currentTimeMillis();
long cachingOffReuseTime = endtime - starttime;
// ITERATIONS calls, parsing once and using cached executor
expr = (Expression) parser.parseExpression("randomField='Andy'");
if (expr == null) {
fail("Parser returned null for expression");
}
starttime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
Object value = expr.getValue(eContext);
}
endtime = System.currentTimeMillis();
long reuseTime = endtime - starttime;
if (report) {
System.out.println("Timings for property writing execution 'randomField='Andy''");
System.out.println("Fresh parse every time, " + ITERATIONS + " iterations = " + freshParseTime + "ms");
System.out.println("Reuse SpelExpression (caching off), " + ITERATIONS + " iterations = "
+ cachingOffReuseTime + "ms");
System.out.println("Reuse SpelExpression, " + ITERATIONS + " iterations = " + reuseTime + "ms");
}
if (reuseTime > freshParseTime) {
fail("Should have been quicker to reuse a parsed expression!");
}
if (reuseTime > cachingOffReuseTime) {
fail("Should have been quicker to reuse cached!");
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2004-2008 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 org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.spel.reflection.ReflectionPropertyResolver;
import org.springframework.expression.spel.standard.StandardEvaluationContext;
/**
* Tests accessing of properties.
*
* @author Andy Clement
*/
public class PropertyAccessTests extends ExpressionTestCase {
public void testSimpleAccess01() {
evaluate("name", "Nikola Tesla", String.class);
}
public void testSimpleAccess02() {
evaluate("placeOfBirth.city", "SmilJan", String.class);
}
public void testSimpleAccess03() {
try {
ReflectionPropertyResolver.useResolverExecutorModel = false;
evaluate("name", "Nikola Tesla", String.class);
} finally {
ReflectionPropertyResolver.useResolverExecutorModel = true;
}
}
public void testSimpleAccess04() {
try {
ReflectionPropertyResolver.useResolverExecutorModel = false;
evaluate("placeOfBirth.city", "SmilJan", String.class);
} finally {
ReflectionPropertyResolver.useResolverExecutorModel = true;
}
}
public void testNonExistentPropertiesAndMethods() {
// madeup does not exist as a property
evaluateAndCheckError("madeup", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 0);
// name is ok but foobar does not exist:
evaluateAndCheckError("name.foobar", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 5);
}
// This can resolve the property 'flibbles' on any String (very useful...)
static class StringyPropertyAccessor implements PropertyAccessor {
int flibbles = 7;
public Class[] getSpecificTargetClasses() {
return new Class[]{String.class};
}
public boolean canRead(EvaluationContext context, Object target, Object name) throws AccessException {
if (!(target instanceof String)) throw new RuntimeException("Assertion Failed! target should be String");
return (name.equals("flibbles"));
}
public boolean canWrite(EvaluationContext context, Object target, Object name) throws AccessException {
if (!(target instanceof String)) throw new RuntimeException("Assertion Failed! target should be String");
return (name.equals("flibbles"));
}
public Object read(EvaluationContext context, Object target, Object name) throws AccessException {
if (!name.equals("flibbles") ) throw new RuntimeException("Assertion Failed! name should be flibbles");
return flibbles;
}
public void write(EvaluationContext context, Object target, Object name, Object newValue)
throws AccessException {
if (!name.equals("flibbles") ) throw new RuntimeException("Assertion Failed! name should be flibbles");
try {
flibbles = (Integer)context.getTypeUtils().getTypeConverter().convertValue(newValue, Integer.class);
} catch (EvaluationException e) {
throw new AccessException("Cannot set flibbles to an object of type '"+newValue.getClass()+"'");
}
}
}
// Adding a new property accessor just for a particular type
public void testAddingSpecificPropertyAccessor() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
// Even though this property accessor is added after the reflection one, it specifically
// names the String class as the type it is interested in so is chosen in preference to
// any 'default' ones
ctx.addPropertyAccessor(new StringyPropertyAccessor());
Expression expr = parser.parseExpression("new String('hello').flibbles");
Integer i = (Integer)expr.getValue(ctx,Integer.class);
assertEquals((int)i,7);
// The reflection one will be used for other properties...
expr = parser.parseExpression("new String('hello').CASE_INSENSITIVE_ORDER");
Object o = expr.getValue(ctx);
assertNotNull(o);
expr = parser.parseExpression("new String('hello').flibbles");
expr.setValue(ctx,99);
i = (Integer)expr.getValue(ctx,Integer.class);
assertEquals((int)i,99);
// Cannot set it to a string value
try {
expr.setValue(ctx,"not allowed");
fail("Should not have been allowed");
} catch (EvaluationException e) {
// success - message will be: EL1063E:(pos 20): A problem occurred whilst attempting to set the property 'flibbles': 'Cannot set flibbles to an object of type 'class java.lang.String''
System.out.println(e.getMessage());
}
}
}

View File

@@ -0,0 +1,292 @@
/*
* Copyright 2004-2008 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.lang.reflect.Method;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.reflection.ReflectionUtils;
import org.springframework.expression.spel.standard.StandardEvaluationContext;
/**
* Spring Security scenarios from https://wiki.springsource.com/display/SECURITY/Spring+Security+Expression-based+Authorization
*
* @author Andy Clement
*/
public class ScenariosForSpringSecurity extends ExpressionTestCase {
// Helper classes for the scenario:
static class Person {
private String n;
Person(String n) { this.n = n; }
public String[] getRoles() { return new String[]{"NONE"}; }
public boolean hasAnyRole(String... roles) {
if (roles==null) return true;
String[] myRoles = getRoles();
for (int i=0;i<myRoles.length;i++) {
for (int j=0;j<roles.length;j++) {
if (myRoles[i].equals(roles[j])) return true;
}
}
return false;
}
public boolean hasRole(String role) {
return hasAnyRole(role);
}
public boolean hasIpAddress(String ipaddr) {
return true;
}
public String getName() { return n; }
}
static class Manager extends Person {
Manager(String n) {
super(n);
}
public String[] getRoles() { return new String[]{"MANAGER"};}
}
static class Teller extends Person {
Teller(String n) {
super(n);
}
public String[] getRoles() { return new String[]{"TELLER"};}
}
static class Supervisor extends Person {
Supervisor(String n) {
super(n);
}
public String[] getRoles() { return new String[]{"SUPERVISOR"};}
}
// End of helper code
public void testScenario01_Roles() throws Exception {
try {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
Expression expr = parser.parseExpression("hasAnyRole('MANAGER','TELLER')");
ctx.setRootObject(new Person("Ben"));
Boolean value = (Boolean)expr.getValue(ctx,Boolean.class);
assertFalse(value);
ctx.setRootObject(new Manager("Luke"));
value = (Boolean)expr.getValue(ctx,Boolean.class);
assertTrue(value);
} catch (EvaluationException ee) {
ee.printStackTrace();
fail("Unexpected SpelException: " + ee.getMessage());
}
}
// ---
static class SecurityPrincipalAccessor implements PropertyAccessor {
static class Principal {
public String name = "Andy";
}
public boolean canRead(EvaluationContext context, Object target, Object name) throws AccessException {
return name.equals("principal");
}
public Object read(EvaluationContext context, Object target, Object name) throws AccessException {
return new Principal();
}
public boolean canWrite(EvaluationContext context, Object target, Object name) throws AccessException {
return false;
}
public void write(EvaluationContext context, Object target, Object name, Object newValue)
throws AccessException {
}
public Class[] getSpecificTargetClasses() {
return null;
}
}
static class PersonAccessor implements PropertyAccessor {
Person activePerson;
void setPerson(Person p) { this.activePerson = p; }
public boolean canRead(EvaluationContext context, Object target, Object name) throws AccessException {
return name.equals("p");
}
public Object read(EvaluationContext context, Object target, Object name) throws AccessException {
return activePerson;
}
public boolean canWrite(EvaluationContext context, Object target, Object name) throws AccessException {
return false;
}
public void write(EvaluationContext context, Object target, Object name, Object newValue)
throws AccessException {
}
public Class[] getSpecificTargetClasses() {
return null;
}
}
public void testScenario02_ComparingNames() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.addPropertyAccessor(new SecurityPrincipalAccessor());
// Multiple options for supporting this expression: "p.name == principal.name"
// (1) If the right person is the root context object then "name==principal.name" is good enough
Expression expr = parser.parseExpression("name == principal.name");
ctx.setRootObject(new Person("Andy"));
Boolean value = (Boolean)expr.getValue(ctx,Boolean.class);
assertTrue(value);
ctx.setRootObject(new Person("Christian"));
value = (Boolean)expr.getValue(ctx,Boolean.class);
assertFalse(value);
// (2) Or register an accessor that can understand 'p' and return the right person
expr = parser.parseExpression("p.name == principal.name");
PersonAccessor pAccessor = new PersonAccessor();
ctx.addPropertyAccessor(pAccessor);
ctx.setRootObject(null);
pAccessor.setPerson(new Person("Andy"));
value = (Boolean)expr.getValue(ctx,Boolean.class);
assertTrue(value);
pAccessor.setPerson(new Person("Christian"));
value = (Boolean)expr.getValue(ctx,Boolean.class);
assertFalse(value);
}
public void testScenario03_Arithmetic() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
// Might be better with a as a variable although it would work as a property too...
// Variable references using a '#'
Expression expr = parser.parseExpression("(hasRole('SUPERVISOR') or (#a < 1.042)) and hasIpAddress('10.10.0.0/16')");
Boolean value = null;
ctx.setVariable("a",1.0d); // referenced as #a in the expression
ctx.setRootObject(new Supervisor("Ben")); // so non-qualified references 'hasRole()' 'hasIpAddress()' are invoked against it
value = (Boolean)expr.getValue(ctx,Boolean.class);
assertTrue(value);
ctx.setRootObject(new Manager("Luke"));
ctx.setVariable("a",1.043d);
value = (Boolean)expr.getValue(ctx,Boolean.class);
assertFalse(value);
}
static class MyMethodResolver implements MethodResolver {
static class HasRoleExecutor implements MethodExecutor {
TypeConverter tc;
public HasRoleExecutor(TypeConverter typeConverter) {
this.tc = typeConverter;
}
public Object execute(EvaluationContext context, Object target, Object... methodArguments)
throws AccessException {
try {
Method m = HasRoleExecutor.class.getMethod("hasRole",new String[]{}.getClass());
Object[] args = ReflectionUtils.prepareArguments(tc,m,methodArguments);
return m.invoke(null,args);
} catch (Exception e) {
e.printStackTrace();
throw new AccessException("Problem invoking hasRole");
}
}
public static boolean hasRole(String... strings) {
return true;
}
}
public MethodExecutor resolve(EvaluationContext context, Object targetObject, String name, Class<?>[] arguments)
throws AccessException {
if (name.equals("hasRole")) {
return new HasRoleExecutor(context.getTypeUtils().getTypeConverter());
}
return null;
}
}
// Here i'm going to change which hasRole() executes and make it one of my own Java methods
public void testScenario04_ControllingWhichMethodsRun() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.insertMethodResolver(0, new MyMethodResolver()); // NEEDS TO OVERRIDE THE REFLECTION ONE - SHOW REORDERING MECHANISM
// Might be better with a as a variable although it would work as a property too...
// Variable references using a '#'
// SpelExpression expr = parser.parseExpression("(hasRole('SUPERVISOR') or (#a < 1.042)) and hasIpAddress('10.10.0.0/16')");
Expression expr = parser.parseExpression("(hasRole(3) or (#a < 1.042)) and hasIpAddress('10.10.0.0/16')");
Boolean value = null;
ctx.setVariable("a",1.0d); // referenced as #a in the expression
ctx.setRootObject(new Supervisor("Ben")); // so non-qualified references 'hasRole()' 'hasIpAddress()' are invoked against it
value = (Boolean)expr.getValue(ctx,Boolean.class);
assertTrue(value);
// ctx.setRootObject(new Manager("Luke"));
// ctx.setVariable("a",1.043d);
// value = (Boolean)expr.getValue(ctx,Boolean.class);
// assertFalse(value);
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2004-2008 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 org.springframework.expression.Expression;
import org.springframework.expression.common.DefaultTemplateParserContext;
/**
* Test parsing of template expressions
*
* @author Andy Clement
*/
public class TemplateExpressionParsing extends ExpressionTestCase {
public void testParsingSimpleTemplateExpression01() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("hello ${'world'}",DefaultTemplateParserContext.INSTANCE);
Object o = expr.getValue();
System.out.println(o);
assertEquals("hello world",o.toString());
}
public void testParsingSimpleTemplateExpression02() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("hello ${'to'} you",DefaultTemplateParserContext.INSTANCE);
Object o = expr.getValue();
System.out.println(o);
assertEquals("hello to you",o.toString());
}
public void testParsingSimpleTemplateExpression03() throws Exception {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expr = parser.parseExpression("The quick ${'brown'} fox jumped over the ${'lazy'} dog",DefaultTemplateParserContext.INSTANCE);
Object o = expr.getValue();
System.out.println(o);
assertEquals("The quick brown fox jumped over the lazy dog",o.toString());
}
// TODO need to support this case but what is the neatest way? Escapet the clasing delimiters in the expression string?
// public void testParsingTemplateExpressionThatEmbedsTheDelimiters() throws Exception {
// SpelExpressionParser parser = new SpelExpressionParser();
// Expression expr = parser.parseExpression("The quick ${{'green','brown'}.${true}} fox jumped over the ${'lazy'} dog",DefaultTemplateParserContext.INSTANCE);
// Object o = expr.getValue();
// System.out.println(o);
// assertEquals("The quick brown fox jumped over the lazy dog",o.toString());
// }
}