objects flowing around in expression evaluation are now TypedValue's - these carry generics info, used for conversions.

This commit is contained in:
Andy Clement
2009-04-03 23:39:14 +00:00
parent f6c1144e8d
commit 4c5854d017
66 changed files with 749 additions and 382 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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++) {

View File

@@ -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)

View File

@@ -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);

View File

@@ -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());

View File

@@ -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) {

View File

@@ -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();
}
}