objects flowing around in expression evaluation are now TypedValue's - these carry generics info, used for conversions.
This commit is contained in:
@@ -23,12 +23,14 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
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.TypedValue;
|
||||
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
@@ -235,6 +237,7 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
|
||||
private static class FruitColourAccessor implements PropertyAccessor {
|
||||
|
||||
private static Map<String,Color> propertyMap = new HashMap<String,Color>();
|
||||
private static TypeDescriptor mapElementTypeDescriptor = TypeDescriptor.valueOf(Color.class);
|
||||
|
||||
static {
|
||||
propertyMap.put("banana",Color.yellow);
|
||||
@@ -253,8 +256,8 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
|
||||
return propertyMap.containsKey(name);
|
||||
}
|
||||
|
||||
public Object read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return propertyMap.get(name);
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return new TypedValue(propertyMap.get(name),mapElementTypeDescriptor);
|
||||
}
|
||||
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
@@ -292,8 +295,8 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
|
||||
return propertyMap.containsKey(name);
|
||||
}
|
||||
|
||||
public Object read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return propertyMap.get(name);
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return new TypedValue(propertyMap.get(name),TypeDescriptor.valueOf(Color.class));
|
||||
}
|
||||
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
|
||||
@@ -22,7 +22,9 @@ import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
|
||||
import org.springframework.expression.spel.ast.CommonTypeDescriptors;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
@@ -67,8 +69,8 @@ public class MapAccessTests extends ExpressionTestCase {
|
||||
return (((Map) target).containsKey(name));
|
||||
}
|
||||
|
||||
public Object read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return ((Map) target).get(name);
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return new TypedValue(((Map) target).get(name),CommonTypeDescriptors.OBJECT_TYPE_DESCRIPTOR);
|
||||
}
|
||||
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import org.springframework.expression.spel.ast.Operator;
|
||||
|
||||
/**
|
||||
* Tests the evaluation of expressions using relational operators.
|
||||
*
|
||||
@@ -48,6 +50,12 @@ public class OperatorTests extends ExpressionTestCase {
|
||||
evaluate("6 == 6", true, Boolean.class);
|
||||
}
|
||||
|
||||
public void testNotEqual() {
|
||||
evaluate("3 != 5", true, Boolean.class);
|
||||
evaluate("5 != 3", true, Boolean.class);
|
||||
evaluate("6 != 6", false, Boolean.class);
|
||||
}
|
||||
|
||||
public void testGreaterThanOrEqual() {
|
||||
evaluate("3 >= 5", false, Boolean.class);
|
||||
evaluate("5 >= 3", true, Boolean.class);
|
||||
@@ -79,6 +87,10 @@ public class OperatorTests extends ExpressionTestCase {
|
||||
evaluate("3 % 2", 1, Integer.class);
|
||||
}
|
||||
|
||||
public void testDivide() {
|
||||
evaluate("4L/2L",2L,Long.class);
|
||||
}
|
||||
|
||||
public void testMathOperatorDivide_ConvertToDouble() {
|
||||
evaluateAndAskForReturnType("8/4", new Double(2.0), Double.class);
|
||||
}
|
||||
@@ -92,6 +104,10 @@ public class OperatorTests extends ExpressionTestCase {
|
||||
// }
|
||||
|
||||
public void testDoubles() {
|
||||
evaluate("3.0d == 5.0d", false, Boolean.class);
|
||||
evaluate("3.0d == 3.0d", true, Boolean.class);
|
||||
evaluate("3.0d != 5.0d", true, Boolean.class);
|
||||
evaluate("3.0d != 3.0d", false, Boolean.class);
|
||||
evaluate("3.0d + 5.0d", 8.0d, Double.class);
|
||||
evaluate("3.0d - 5.0d", -2.0d, Double.class);
|
||||
evaluate("3.0d * 5.0d", 15.0d, Double.class);
|
||||
@@ -100,6 +116,10 @@ public class OperatorTests extends ExpressionTestCase {
|
||||
}
|
||||
|
||||
public void testFloats() {
|
||||
evaluate("3.0f == 5.0f", false, Boolean.class);
|
||||
evaluate("3.0f == 3.0f", true, Boolean.class);
|
||||
evaluate("3.0f != 5.0f", true, Boolean.class);
|
||||
evaluate("3.0f != 3.0f", false, Boolean.class);
|
||||
evaluate("3.0f + 5.0f", 8.0d, Double.class);
|
||||
evaluate("3.0f - 5.0f", -2.0d, Double.class);
|
||||
evaluate("3.0f * 5.0f", 15.0d, Double.class);
|
||||
@@ -107,6 +127,17 @@ public class OperatorTests extends ExpressionTestCase {
|
||||
evaluate("5.0f % 3.1f", 1.9d, Double.class);
|
||||
}
|
||||
|
||||
public void testOperatorStrings() throws Exception {
|
||||
Operator node = getOperatorNode((SpelExpression)parser.parseExpression("1==3"));
|
||||
assertEquals("==",node.getOperatorName());
|
||||
|
||||
node = getOperatorNode((SpelExpression)parser.parseExpression("1!=3"));
|
||||
assertEquals("!=",node.getOperatorName());
|
||||
|
||||
node = getOperatorNode((SpelExpression)parser.parseExpression("3/3"));
|
||||
assertEquals("/",node.getOperatorName());
|
||||
}
|
||||
|
||||
public void testMixedOperands_FloatsAndDoubles() {
|
||||
evaluate("3.0d + 5.0f", 8.0d, Double.class);
|
||||
evaluate("3.0D - 5.0f", -2.0d, Double.class);
|
||||
@@ -125,4 +156,37 @@ public class OperatorTests extends ExpressionTestCase {
|
||||
evaluate("5.5D % 3", 2.5, Double.class);
|
||||
}
|
||||
|
||||
public void testStrings() {
|
||||
evaluate("'abc' == 'abc'",true,Boolean.class);
|
||||
evaluate("'abc' == 'def'",false,Boolean.class);
|
||||
evaluate("'abc' != 'abc'",false,Boolean.class);
|
||||
evaluate("'abc' != 'def'",true,Boolean.class);
|
||||
}
|
||||
|
||||
public void testLongs() {
|
||||
evaluate("3L == 4L", false, Boolean.class);
|
||||
evaluate("3L == 3L", true, Boolean.class);
|
||||
evaluate("3L != 4L", true, Boolean.class);
|
||||
evaluate("3L != 3L", false, Boolean.class);
|
||||
}
|
||||
|
||||
private Operator getOperatorNode(SpelExpression e) {
|
||||
SpelNode node = e.getAST();
|
||||
return (Operator)findNode(node,Operator.class);
|
||||
}
|
||||
|
||||
private SpelNode findNode(SpelNode node, Class<Operator> clazz) {
|
||||
if (clazz.isAssignableFrom(node.getClass())) {
|
||||
return node;
|
||||
}
|
||||
int childCount = node.getChildCount();
|
||||
for (int i=0;i<childCount;i++) {
|
||||
SpelNode possible = findNode(node.getChild(i),clazz);
|
||||
if (possible!=null) {
|
||||
return possible;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import junit.framework.TestCase;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
|
||||
import org.springframework.expression.spel.ast.ConstructorReference;
|
||||
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
|
||||
|
||||
/**
|
||||
* Tests the evaluation of real expressions in a real context.
|
||||
@@ -31,7 +29,7 @@ import org.springframework.expression.spel.ast.PropertyOrFieldReference;
|
||||
*/
|
||||
public class PerformanceTests extends TestCase {
|
||||
|
||||
public static final int ITERATIONS = 100000;
|
||||
public static final int ITERATIONS = 10000;
|
||||
public static final boolean report = true;
|
||||
|
||||
private static SpelAntlrExpressionParser parser = new SpelAntlrExpressionParser();
|
||||
@@ -41,6 +39,15 @@ public class PerformanceTests extends TestCase {
|
||||
long starttime = 0;
|
||||
long endtime = 0;
|
||||
|
||||
// warmup
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
Expression expr = parser.parseExpression("placeOfBirth.city");
|
||||
if (expr == null) {
|
||||
fail("Parser returned null for expression");
|
||||
}
|
||||
Object value = expr.getValue(eContext);
|
||||
}
|
||||
|
||||
starttime = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
Expression expr = parser.parseExpression("placeOfBirth.city");
|
||||
@@ -74,6 +81,15 @@ public class PerformanceTests extends TestCase {
|
||||
public void testPerformanceOfMethodAccess() throws Exception {
|
||||
long starttime = 0;
|
||||
long endtime = 0;
|
||||
|
||||
// warmup
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
Expression expr = parser.parseExpression("getPlaceOfBirth().getCity()");
|
||||
if (expr == null) {
|
||||
fail("Parser returned null for expression");
|
||||
}
|
||||
Object value = expr.getValue(eContext);
|
||||
}
|
||||
|
||||
starttime = System.currentTimeMillis();
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
|
||||
@@ -21,7 +21,9 @@ import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
|
||||
import org.springframework.expression.spel.ast.CommonTypeDescriptors;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
@@ -57,7 +59,7 @@ public class PropertyAccessTests extends ExpressionTestCase {
|
||||
// any 'default' ones
|
||||
ctx.addPropertyAccessor(new StringyPropertyAccessor());
|
||||
Expression expr = parser.parseExpression("new String('hello').flibbles");
|
||||
Integer i = (Integer) expr.getValue(ctx, Integer.class);
|
||||
Integer i = expr.getValue(ctx, Integer.class);
|
||||
assertEquals((int) i, 7);
|
||||
|
||||
// The reflection one will be used for other properties...
|
||||
@@ -67,7 +69,7 @@ public class PropertyAccessTests extends ExpressionTestCase {
|
||||
|
||||
expr = parser.parseExpression("new String('hello').flibbles");
|
||||
expr.setValue(ctx, 99);
|
||||
i = (Integer) expr.getValue(ctx, Integer.class);
|
||||
i = expr.getValue(ctx, Integer.class);
|
||||
assertEquals((int) i, 99);
|
||||
|
||||
// Cannot set it to a string value
|
||||
@@ -103,10 +105,10 @@ public class PropertyAccessTests extends ExpressionTestCase {
|
||||
return (name.equals("flibbles"));
|
||||
}
|
||||
|
||||
public Object read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
if (!name.equals("flibbles"))
|
||||
throw new RuntimeException("Assertion Failed! name should be flibbles");
|
||||
return flibbles;
|
||||
return new TypedValue(flibbles,CommonTypeDescriptors.STRING_TYPE_DESCRIPTOR);
|
||||
}
|
||||
|
||||
public void write(EvaluationContext context, Object target, String name, Object newValue)
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.expression.spel;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.expression.MethodExecutor;
|
||||
import org.springframework.expression.MethodResolver;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser;
|
||||
import org.springframework.expression.spel.support.ReflectionHelper;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
@@ -84,11 +86,11 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase {
|
||||
ctx.setRootObject(null);
|
||||
|
||||
pAccessor.setPerson(new Person("Andy"));
|
||||
value = (Boolean)expr.getValue(ctx,Boolean.class);
|
||||
value = expr.getValue(ctx,Boolean.class);
|
||||
assertTrue(value);
|
||||
|
||||
pAccessor.setPerson(new Person("Christian"));
|
||||
value = (Boolean)expr.getValue(ctx,Boolean.class);
|
||||
value = expr.getValue(ctx,Boolean.class);
|
||||
assertFalse(value);
|
||||
}
|
||||
|
||||
@@ -210,8 +212,8 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase {
|
||||
return name.equals("principal");
|
||||
}
|
||||
|
||||
public Object read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return new Principal();
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return new TypedValue(new Principal(),TypeDescriptor.valueOf(Principal.class));
|
||||
}
|
||||
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
@@ -240,8 +242,8 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase {
|
||||
return name.equals("p");
|
||||
}
|
||||
|
||||
public Object read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return activePerson;
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
return new TypedValue(activePerson,TypeDescriptor.valueOf(Person.class));
|
||||
}
|
||||
|
||||
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
@@ -269,7 +271,7 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase {
|
||||
this.tc = typeConverter;
|
||||
}
|
||||
|
||||
public Object execute(EvaluationContext context, Object target, Object... arguments)
|
||||
public TypedValue execute(EvaluationContext context, Object target, Object... arguments)
|
||||
throws AccessException {
|
||||
try {
|
||||
Method m = HasRoleExecutor.class.getMethod("hasRole", String[].class);
|
||||
@@ -280,7 +282,7 @@ public class ScenariosForSpringSecurity extends ExpressionTestCase {
|
||||
if (m.isVarArgs()) {
|
||||
args = ReflectionHelper.setupArgumentsForVarargsInvocation(m.getParameterTypes(), args);
|
||||
}
|
||||
return m.invoke(null, args);
|
||||
return new TypedValue(m.invoke(null, args), new TypeDescriptor(new MethodParameter(m,-1)));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new AccessException("Problem invoking hasRole", ex);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
@@ -77,10 +79,15 @@ public class SetValueTests extends ExpressionTestCase {
|
||||
setValue("placesLivedList[0]", new PlaceOfBirth("Wien"));
|
||||
}
|
||||
|
||||
public void testSetGenericListElementValueTypeCoersion() {
|
||||
setValue("placesLivedList[0]", "Wien");
|
||||
public void testSetGenericListElementValueTypeCoersionFail() {
|
||||
// no type converter registered for String > PlaceOfBirth
|
||||
setValueExpectError("placesLivedList[0]", "Wien");
|
||||
}
|
||||
|
||||
public void testSetGenericListElementValueTypeCoersionOK() {
|
||||
setValue("booleanList[0]", "true", Boolean.TRUE);
|
||||
}
|
||||
|
||||
public void testSetListElementNestedValue() {
|
||||
setValue("placesLived[0].city", "Wien");
|
||||
}
|
||||
@@ -106,6 +113,46 @@ public class SetValueTests extends ExpressionTestCase {
|
||||
setValue("SomeProperty", "true", Boolean.TRUE);
|
||||
}
|
||||
|
||||
public void testAssign() throws Exception {
|
||||
StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
|
||||
Expression e = parse("publicName='Andy'");
|
||||
assertFalse(e.isWritable(eContext));
|
||||
assertEquals("Andy",e.getValue(eContext));
|
||||
}
|
||||
|
||||
/*
|
||||
* Testing the coercion of both the keys and the values to the correct type
|
||||
*/
|
||||
public void testSetGenericMapElementRequiresCoercion() throws Exception {
|
||||
StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext();
|
||||
Expression e = parse("mapOfStringToBoolean[42]");
|
||||
assertNull(e.getValue(eContext));
|
||||
|
||||
// Key should be coerced to string representation of 42
|
||||
e.setValue(eContext, "true");
|
||||
|
||||
// All keys should be strings
|
||||
Set ks = parse("mapOfStringToBoolean.keySet()").getValue(eContext,Set.class);
|
||||
for (Object o: ks) {
|
||||
assertEquals(String.class,o.getClass());
|
||||
}
|
||||
|
||||
// All values should be booleans
|
||||
Collection vs = parse("mapOfStringToBoolean.values()").getValue(eContext,Collection.class);
|
||||
for (Object o: vs) {
|
||||
assertEquals(Boolean.class,o.getClass());
|
||||
}
|
||||
|
||||
// One final test check coercion on the key for a map lookup
|
||||
Object o = e.getValue(eContext);
|
||||
assertEquals(Boolean.TRUE,o);
|
||||
}
|
||||
|
||||
|
||||
private Expression parse(String expressionString) throws Exception {
|
||||
return parser.parseExpression(expressionString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call setValue() but expect it to fail.
|
||||
*/
|
||||
@@ -167,7 +214,12 @@ public class SetValueTests extends ExpressionTestCase {
|
||||
StandardEvaluationContext lContext = TestScenarioCreator.getTestEvaluationContext();
|
||||
assertTrue("Expression is not writeable but should be", e.isWritable(lContext));
|
||||
e.setValue(lContext, value);
|
||||
assertEquals("Retrieved value was not equal to set value", expectedValue, e.getValue(lContext));
|
||||
Object a = expectedValue;
|
||||
Object b = e.getValue(lContext);
|
||||
if (!a.equals(b)) {
|
||||
fail("Not the same: ["+a+"] type="+a.getClass()+" ["+b+"] type="+b.getClass());
|
||||
// assertEquals("Retrieved value was not equal to set value", expectedValue, e.getValue(lContext));
|
||||
}
|
||||
} catch (EvaluationException ee) {
|
||||
ee.printStackTrace();
|
||||
fail("Unexpected Exception: " + ee.getMessage());
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.Map;
|
||||
@SuppressWarnings("unused")
|
||||
public class Inventor {
|
||||
private String name;
|
||||
public String publicName;
|
||||
private PlaceOfBirth placeOfBirth;
|
||||
Date birthdate;
|
||||
private int sinNumber;
|
||||
@@ -23,6 +24,8 @@ public class Inventor {
|
||||
public boolean publicBoolean;
|
||||
private boolean accessedThroughGetSet;
|
||||
public List<Integer> listOfInteger = new ArrayList<Integer>();
|
||||
public List<Boolean> booleanList = new ArrayList<Boolean>();
|
||||
public Map<String,Boolean> mapOfStringToBoolean = new HashMap<String,Boolean>();
|
||||
|
||||
public Inventor(String name, Date birthdate, String nationality) {
|
||||
this.name = name;
|
||||
@@ -37,6 +40,8 @@ public class Inventor {
|
||||
testMap.put("friday", "freitag");
|
||||
testMap.put("saturday", "samstag");
|
||||
testMap.put("sunday", "sonntag");
|
||||
booleanList.add(false);
|
||||
booleanList.add(false);
|
||||
}
|
||||
|
||||
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth2) {
|
||||
|
||||
@@ -21,4 +21,16 @@ public class PlaceOfBirth {
|
||||
return i*2;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof PlaceOfBirth)) {
|
||||
return false;
|
||||
}
|
||||
PlaceOfBirth oPOB = (PlaceOfBirth)o;
|
||||
return (city.equals(oPOB.city));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return city.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user