diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java index 4f4482a521..105a630b3a 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/EvaluationContext.java @@ -36,6 +36,13 @@ public interface EvaluationContext { * @return the root context object against which unqualified properties/methods/etc should be resolved */ TypedValue getRootObject(); + + + /** + * @param rootObject the root object against which unqualified properties/methods/etc should be resolved + */ + void setRootObject(Object object); + /** * Set a named variable within this execution context to a specified value. diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index 770e483587..18f4e3f3b7 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -48,17 +48,11 @@ public class ExpressionState { private final Stack contextObjects = new Stack(); - - public ExpressionState() { - this(null); - } - public ExpressionState(EvaluationContext context) { this.relatedContext = context; createVariableScope(); } - private void createVariableScope() { this.variableScopes.add(new VariableScope()); // create an empty top level VariableScope } @@ -95,7 +89,7 @@ public class ExpressionState { public TypedValue lookupVariable(String name) { Object value = this.relatedContext.lookupVariable(name); - return new TypedValue(value,TypeDescriptor.valueOf((value==null?null:value.getClass()))); + return new TypedValue(value,TypeDescriptor.forObject(value)); } public TypeComparator getTypeComparator() { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java index 08c3c51a57..1c5b00a856 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java @@ -113,10 +113,10 @@ public enum SpelMessages { "The value ''{0}'' cannot be parsed as a long"), PARSE_PROBLEM(Kind.ERROR, 1066, "Error occurred during expression parse: {0}"), INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1067, "First operand to matches operator must be a string. ''{0}'' is not"), INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR( - Kind.ERROR, 1068, "Second operand to matches operator must be a string. ''{0}'' is not"), FUNCTION_MUST_BE_STATIC( - Kind.ERROR, - 1069, - "Only static methods can be called via function references. The method ''{0}'' referred to by name ''{1}'' is not static."); + Kind.ERROR, 1068, "Second operand to matches operator must be a string. ''{0}'' is not"), // + FUNCTION_MUST_BE_STATIC(Kind.ERROR, 1069, + "Only static methods can be called via function references. The method ''{0}'' referred to by name ''{1}'' is not static."),// + CANNOT_INDEX_INTO_NULL_VALUE(Kind.ERROR, 1070, "Cannot index into a null value"); private Kind kind; private int code; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java index dc4aab2b90..06d690cddb 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java @@ -74,8 +74,9 @@ public class ConstructorReference extends SpelNodeImpl { Class[] argumentTypes = new Class[getChildCount() - 1]; for (int i = 0; i < arguments.length; i++) { TypedValue childValue = getChild(i + 1).getValueInternal(state); - arguments[i] = childValue.getValue(); - argumentTypes[i] = childValue.getValue().getClass(); + Object value = childValue.getValue(); + arguments[i] = value; + argumentTypes[i] = value==null?Object.class:value.getClass(); } ConstructorExecutor executorToUse = this.cachedExecutor; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java index 4811246750..df8268889d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/FunctionReference.java @@ -30,6 +30,7 @@ import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; import org.springframework.expression.spel.SpelMessages; import org.springframework.expression.spel.support.ReflectionHelper; +import org.springframework.util.ReflectionUtils; /** * A function reference is of the form "#someFunction(a,b,c)". Functions may be defined in the context prior to the @@ -101,7 +102,9 @@ public class FunctionReference extends SpelNodeImpl { } try { - return new TypedValue(m.invoke(m.getClass(), functionArgs),new TypeDescriptor(new MethodParameter(m,-1))); + ReflectionUtils.makeAccessible(m); + Object result = m.invoke(m.getClass(), functionArgs); + return new TypedValue(result, new TypeDescriptor(new MethodParameter(m,-1))); } catch (IllegalArgumentException e) { throw new SpelException(getCharPositionInLine(), e, SpelMessages.EXCEPTION_DURING_FUNCTION_CALL, name, e .getMessage()); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index c49afcd7fb..e6c8a2662e 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -60,9 +60,13 @@ public class Indexer extends SpelNodeImpl { int idx = state.convertValue(index, INTEGER_TYPE_DESCRIPTOR); - if (targetObjectTypeDescriptor.isArray()) { + if (targetObject == null) { + throw new SpelException(SpelMessages.CANNOT_INDEX_INTO_NULL_VALUE); + } + + if (targetObject.getClass().isArray()) { return new TypedValue(accessArrayElement(targetObject, idx),TypeDescriptor.valueOf(targetObjectTypeDescriptor.getElementType())); - } else if (targetObjectTypeDescriptor.isCollection()) { + } else if (targetObject instanceof Collection) { Collection c = (Collection) targetObject; if (idx >= c.size()) { throw new SpelException(SpelMessages.COLLECTION_INDEX_OUT_OF_BOUNDS, c.size(), idx); @@ -81,7 +85,7 @@ public class Indexer extends SpelNodeImpl { } return new TypedValue(String.valueOf(ctxString.charAt(idx)),STRING_TYPE_DESCRIPTOR); } - throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor); + throw new SpelException(SpelMessages.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetObjectTypeDescriptor.asString()); } @@ -98,6 +102,9 @@ public class Indexer extends SpelNodeImpl { TypeDescriptor targetObjectTypeDescriptor = contextObject.getTypeDescriptor(); TypedValue index = getChild(0).getValueInternal(state); + if (targetObject == null) { + throw new SpelException(SpelMessages.CANNOT_INDEX_INTO_NULL_VALUE); + } // Indexing into a Map if (targetObjectTypeDescriptor.isMap()) { Map map = (Map)targetObject; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java index 8725b2d402..3553dd4e4d 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java @@ -54,9 +54,9 @@ public class MethodReference extends SpelNodeImpl { for (int i = 0; i < arguments.length; i++) { arguments[i] = getChild(i).getValueInternal(state).getValue(); } - if (currentContext == null) { + if (currentContext.getValue() == null) { throw new SpelException(getCharPositionInLine(), SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT, - formatMethodForMessage(name, getTypes(arguments))); + FormatHelper.formatMethodForMessage(name, getTypes(arguments))); } MethodExecutor executorToUse = this.cachedExecutor; @@ -85,12 +85,9 @@ public class MethodReference extends SpelNodeImpl { } private Class[] getTypes(Object... arguments) { - if (arguments == null) { - return null; - } Class[] argumentTypes = new Class[arguments.length]; for (int i = 0; i < arguments.length; i++) { - argumentTypes[i] = arguments[i].getClass(); + argumentTypes[i] = (arguments[i]==null?Object.class:arguments[i].getClass()); } return argumentTypes; } @@ -108,38 +105,13 @@ public class MethodReference extends SpelNodeImpl { return sb.toString(); } - /** - * Produce a nice string for a given method name with specified arguments. - * @param name the name of the method - * @param argumentTypes the types of the arguments to the method - * @return nicely formatted string, eg. foo(String,int) - */ - private String formatMethodForMessage(String name, Class... argumentTypes) { - StringBuilder sb = new StringBuilder(); - sb.append(name); - sb.append("("); - if (argumentTypes != null) { - for (int i = 0; i < argumentTypes.length; i++) { - if (i > 0) - sb.append(","); - sb.append(argumentTypes[i].getClass()); - } - } - sb.append(")"); - return sb.toString(); - } - - protected MethodExecutor findAccessorForMethod(String name, Class[] argumentTypes, ExpressionState state) throws SpelException { TypedValue context = state.getActiveContextObject(); Object contextObject = context.getValue(); EvaluationContext eContext = state.getEvaluationContext(); - if (contextObject == null) { - throw new SpelException(SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT, - FormatHelper.formatMethodForMessage(name, argumentTypes)); - } + List mResolvers = eContext.getMethodResolvers(); if (mResolvers != null) { for (MethodResolver methodResolver : mResolvers) { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java index cb04fa4a13..48c0f809f9 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java @@ -145,7 +145,6 @@ public class PropertyOrFieldReference extends SpelNodeImpl { } } } catch (AccessException ae) { - ae.printStackTrace(); throw new SpelException(getCharPositionInLine(), ae, SpelMessages.EXCEPTION_DURING_PROPERTY_WRITE, name, ae.getMessage()); } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java index a7cdbdc0db..35af3d2cf3 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/Selection.java @@ -62,8 +62,10 @@ public class Selection extends SpelNodeImpl { Map mapdata = (Map) operand; // TODO don't lose generic info for the new map Map result = new HashMap(); + Object lastKey = null; for (Object k : mapdata.keySet()) { try { + lastKey = k; KeyValuePair kvp = new KeyValuePair(k,mapdata.get(k)); TypedValue kvpair = new TypedValue(kvp,TypeDescriptor.valueOf(KeyValuePair.class)); state.pushActiveContextObject(kvpair); @@ -83,12 +85,15 @@ public class Selection extends SpelNodeImpl { } finally { state.popActiveContextObject(); } - if ((variant == FIRST || variant == LAST) && result.size() == 0) { - return null; - } - if (variant == LAST) { - return new TypedValue(result.get(result.size() - 1),TypeDescriptor.valueOf(op.getTypeDescriptor().getElementType())); - } + } + if ((variant == FIRST || variant == LAST) && result.size() == 0) { + return new TypedValue(null,TypeDescriptor.NULL_TYPE_DESCRIPTOR); + } + if (variant == LAST) { + Object lastValue = result.get(lastKey); + Map resultMap = new HashMap(); + resultMap.put(lastKey,result.get(lastKey)); + return new TypedValue(resultMap,TypeDescriptor.valueOf(Map.class)); } return new TypedValue(result,op.getTypeDescriptor()); } else if (operand instanceof Collection) { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java index 12454e93d4..39c0327236 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeCode.java @@ -16,16 +16,14 @@ package org.springframework.expression.spel.ast; -enum TypeCode { +public enum TypeCode { - OBJECT(0, Object.class), BOOLEAN(1, Boolean.TYPE), BYTE(1, Byte.TYPE), CHAR(1, Character.TYPE), SHORT(2, Short.TYPE), INT( - 3, Integer.TYPE), LONG(4, Long.TYPE), FLOAT(5, Float.TYPE), DOUBLE(6, Double.TYPE); + OBJECT(Object.class), BOOLEAN(Boolean.TYPE), BYTE(Byte.TYPE), CHAR(Character.TYPE), // + SHORT(Short.TYPE), INT(Integer.TYPE), LONG(Long.TYPE), FLOAT(Float.TYPE), DOUBLE(Double.TYPE); - private int code; private Class type; - TypeCode(int code, Class type) { - this.code = code; + TypeCode(Class type) { this.type = type; } @@ -33,72 +31,15 @@ enum TypeCode { return type; } - public static TypeCode forClass(Class c) { - TypeCode[] allValues = TypeCode.values(); - for (int i = 0; i < allValues.length; i++) { - TypeCode typeCode = allValues[i]; - if (c == typeCode.getType()) { - return typeCode; - } - } - return OBJECT; - } +// public static TypeCode forClass(Class c) { +// TypeCode[] allValues = TypeCode.values(); +// for (int i = 0; i < allValues.length; i++) { +// TypeCode typeCode = allValues[i]; +// if (c == typeCode.getType()) { +// return typeCode; +// } +// } +// return OBJECT; +// } - /** - * For a primitive name this will determine the typecode value - supports - * int,byte,char,short,long,double,float,boolean - */ - public static TypeCode forName(String name) { - if (name.equals("int")) - return TypeCode.INT; - else if (name.equals("boolean")) - return TypeCode.BOOLEAN; - else if (name.equals("char")) - return TypeCode.CHAR; - else if (name.equals("long")) - return TypeCode.LONG; - else if (name.equals("float")) - return TypeCode.FLOAT; - else if (name.equals("double")) - return TypeCode.DOUBLE; - else if (name.equals("short")) - return TypeCode.SHORT; - else if (name.equals("byte")) - return TypeCode.BYTE; - return TypeCode.OBJECT; - } - - public int getCode() { - return code; - } - - public Object coerce(TypeCode fromTypeCode, Object fromObject) { - if (this == TypeCode.INT) { - switch (fromTypeCode) { - case BOOLEAN: - return ((Boolean) fromObject).booleanValue() ? 1 : 0; - } - } - // - // return Integer.valueOf - // } else if (this==TypeCode.BOOLEAN) { - // return new Boolean(left).intValue(); - return null; - } - - public static TypeCode forValue(Number op1) { - return forClass(op1.getClass()); - } - - public boolean isDouble() { - return this == DOUBLE; - } - - public boolean isFloat() { - return this == FLOAT; - } - - public boolean isLong() { - return this == LONG; - } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java index 999ee7c0ed..8368e9a399 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/TypeReference.java @@ -37,7 +37,7 @@ public class TypeReference extends SpelNodeImpl { // TODO possible optimization here if we cache the discovered type reference, but can we do that? String typename = (String) getChild(0).getValueInternal(state).getValue(); if (typename.indexOf(".") == -1 && Character.isLowerCase(typename.charAt(0))) { - TypeCode tc = TypeCode.forName(typename); + TypeCode tc = TypeCode.valueOf(typename.toUpperCase()); if (tc != TypeCode.OBJECT) { // it is a primitive type return new TypedValue(tc.getType(),CLASS_TYPE_DESCRIPTOR); diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java index ca35d2b772..a7cb23c96b 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/VariableReference.java @@ -20,7 +20,6 @@ import org.antlr.runtime.Token; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelException; -import org.springframework.expression.spel.SpelMessages; /** * Represents a variable reference, eg. #someVar. Note this is different to a *local* variable like $someVar @@ -52,9 +51,7 @@ public class VariableReference extends SpelNodeImpl { return state.getRootContextObject(); } TypedValue result = state.lookupVariable(this.name); - if (result == null) { - throw new SpelException(getCharPositionInLine(), SpelMessages.VARIABLE_NOT_FOUND, this.name); - } + // a null value will mean either the value was null or the variable was not found return result; } @@ -64,7 +61,7 @@ public class VariableReference extends SpelNodeImpl { } @Override - public String toStringAST() { + public String toStringAST() { return "#" + this.name; } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java index 414708f48d..e2d715d819 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectiveConstructorResolver.java @@ -70,11 +70,9 @@ public class ReflectiveConstructorResolver implements ConstructorResolver { if (matchInfo != null) { if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.EXACT) { return new ReflectiveConstructorExecutor(ctor, null); - } - else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) { + } else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.CLOSE) { closeMatch = ctor; - } - else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) { + } else if (matchInfo.kind == ReflectionHelper.ArgsMatchKind.REQUIRES_CONVERSION) { argsToConvert = matchInfo.argsRequiringConversion; matchRequiringConversion = ctor; } @@ -82,11 +80,9 @@ public class ReflectiveConstructorResolver implements ConstructorResolver { } if (closeMatch != null) { return new ReflectiveConstructorExecutor(closeMatch, null); - } - else if (matchRequiringConversion != null) { + } else if (matchRequiringConversion != null) { return new ReflectiveConstructorExecutor(matchRequiringConversion, argsToConvert); - } - else { + } else { return null; } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java index ae2f1d0578..34414a9e01 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyResolver.java @@ -72,7 +72,7 @@ public class ReflectivePropertyResolver implements PropertyAccessor { Method method = findGetterForProperty(name, type, target instanceof Class); if (method != null) { this.readerCache.put(cacheKey, method); - this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(new MethodParameter(method,0))); + this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(new MethodParameter(method,-1))); return true; } else { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java index 612a1c40d2..c96a49e88a 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardEvaluationContext.java @@ -85,8 +85,8 @@ public class StandardEvaluationContext implements EvaluationContext { this.variables.put(name, value); } - public void registerFunction(String name, Method m) { - this.variables.put(name, m); + public void registerFunction(String name, Method method) { + this.variables.put(name, method); } public Object lookupVariable(String name) { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java index dbd8c1ece3..802cb9b035 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ConstructorInvocationTests.java @@ -76,6 +76,31 @@ public class ConstructorInvocationTests extends ExpressionTestCase { // evaluate("new String(new char[]{'h','e','l','l','o'})", "hello", String.class); } + public void testNonExistentType() { + evaluateAndCheckError("new FooBar()",SpelMessages.PROBLEM_LOCATING_CONSTRUCTOR); + } + + public void testVarargsInvocation01() { + // Calling 'Fruit(String... strings)' + evaluate("new org.springframework.expression.spel.testresources.Fruit('a','b','c').stringscount()", 3, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit('a').stringscount()", 1, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit().stringscount()", 0, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit(1,2,3).stringscount()", 3, Integer.class); // all need converting to strings + evaluate("new org.springframework.expression.spel.testresources.Fruit(1).stringscount()", 1, Integer.class); // needs string conversion + evaluate("new org.springframework.expression.spel.testresources.Fruit(1,'a',3.0d).stringscount()", 3, Integer.class); // first and last need conversion + } + + public void testVarargsInvocation02() { + // Calling 'Fruit(int i, String... strings)' - returns int+length_of_strings + evaluate("new org.springframework.expression.spel.testresources.Fruit(5,'a','b','c').stringscount()", 8, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit(2,'a').stringscount()", 3, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit(4).stringscount()", 4, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit(8,2,3).stringscount()", 10, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit(9).stringscount()", 9, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit(2,'a',3.0d).stringscount()", 4, Integer.class); + evaluate("new org.springframework.expression.spel.testresources.Fruit(8,stringArrayOfThreeItems).stringscount()", 11, Integer.class); + } + /* * These tests are attempting to call constructors where we need to widen or convert the argument in order to * satisfy a suitable constructor. diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 973789f59d..2cd468d501 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -61,21 +61,25 @@ public class EvaluationTests extends ExpressionTestCase { // evaluate("name in {null, \"Anonymous\"}", "false", Boolean.class); // } // - // public void testRelOperatorsBetween01() { - // evaluate("1 between {1, 5}", "true", Boolean.class); - // } + public void testRelOperatorsBetween01() { + evaluate("1 between listOneFive", "true", Boolean.class); + // evaluate("1 between {1, 5}", "true", Boolean.class); // no inline list building at the moment + } // // 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 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 testRelOperatorsBetweenErrors03() { + evaluateAndCheckError("1 between listOfNumbersUpToTen", SpelMessages.BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST, 10); + } public void testRelOperatorsIs01() { evaluate("'xyz' instanceof T(int)", "false", Boolean.class); @@ -343,18 +347,30 @@ public class EvaluationTests extends ExpressionTestCase { // evaluate("{1,2,3,4,5,6,7,8,9,10}.?{$index > 5 }", "[7, 8, 9, 10]", ArrayList.class); // } - public void testSelection03() { + public void testSelection03() { evaluate("mapOfNumbersUpToTen.?{key>5}.size()", "5", Integer.class); - } + } + + public void testSelection04() { + evaluateAndCheckError("mapOfNumbersUpToTen.?{'hello'}.size()",SpelMessages.RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN); + } public void testSelectionFirst01() { evaluate("listOfNumbersUpToTen.^{#isEven(#this) == 'y'}", "2", Integer.class); } - + + public void testSelectionFirst02() { + evaluate("mapOfNumbersUpToTen.^{key>5}.size()", "1", Integer.class); + } + public void testSelectionLast01() { evaluate("listOfNumbersUpToTen.${#isEven(#this) == 'y'}", "10", Integer.class); } + public void testSelectionLast02() { + evaluate("mapOfNumbersUpToTen.${key>5}.size()", "1", Integer.class); + } + public void testSelectionAST() throws Exception { SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'.^{true}"); assertEquals("'abc'.^{true}",expr.toStringAST()); @@ -411,6 +427,10 @@ public class EvaluationTests extends ExpressionTestCase { public void testIndexer03() { evaluate("'christian'[8]", "n", String.class); } + + public void testIndexerError() { + evaluateAndCheckError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]",SpelMessages.CANNOT_INDEX_INTO_NULL_VALUE); + } // Bean references // public void testReferences01() { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestCase.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestCase.java index d1195301dc..d2830fe558 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestCase.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestCase.java @@ -27,6 +27,7 @@ import org.springframework.expression.ParseException; import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; +///CLOVER:OFF /** * Common superclass for expression tests. * diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/HelperTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/HelperTests.java index c845d50628..ffe5448103 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/HelperTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/HelperTests.java @@ -37,4 +37,9 @@ public class HelperTests extends ExpressionTestCase { assertEquals("goo(java.lang.String,int[])",FormatHelper.formatMethodForMessage("goo", String.class,new int[1].getClass())); assertEquals("boo()",FormatHelper.formatMethodForMessage("boo")); } + +// public void testReflectionHelper01() { +// ReflectionHelper.convertArguments(parameterTypes, isVarargs, converter, arguments) +//// ReflectionHelper.setupArgumentsForVarargsInvocation(;, args) +// } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java index be8b6ee8e7..56333cb3b9 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java @@ -85,5 +85,9 @@ public class MethodInvocationTests extends ExpressionTestCase { evaluate("aVarargsMethod2(2,'a',3.0d)", 4, Integer.class); // evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", 11, Integer.class); } + + public void testInvocationOnNullContextObject() { + evaluateAndCheckError("null.toString()",SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT); + } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java new file mode 100644 index 0000000000..675f4c25e7 --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorOverloaderTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2009 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.Operation; +import org.springframework.expression.OperatorOverloader; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * Test providing operator support + * + * @author Andy Clement + */ +public class OperatorOverloaderTests extends ExpressionTestCase { + + static class StringAndBooleanAddition implements OperatorOverloader { + + public Object operate(Operation operation, Object leftOperand, Object rightOperand) throws EvaluationException { + if (operation==Operation.ADD) { + return ((String)leftOperand)+((Boolean)rightOperand).toString(); + } else { + return leftOperand; + } + } + + public boolean overridesOperation(Operation operation, Object leftOperand, Object rightOperand) + throws EvaluationException { + if (leftOperand instanceof String && rightOperand instanceof Boolean) { + return true; + } + return false; + + } + + } + + public void testSimpleOperations() throws Exception { + // no built in support for this: + evaluateAndCheckError("'abc'+true",SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES); + + StandardEvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext(); + eContext.setOperatorOverloader(new StringAndBooleanAddition()); + + SpelExpression expr = (SpelExpression)parser.parseExpression("'abc'+true"); + assertEquals("abctrue",expr.getValue(eContext)); + + expr = (SpelExpression)parser.parseExpression("'abc'-true"); + assertEquals("abc",expr.getValue(eContext)); + } +} diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java index 97439e82bd..8cb48259f6 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/OperatorTests.java @@ -233,6 +233,9 @@ public class OperatorTests extends ExpressionTestCase { node = getOperatorNode((SpelExpression)parser.parseExpression("3>=4")); assertEquals(">=",node.getOperatorName()); + + node = getOperatorNode((SpelExpression)parser.parseExpression("3 between 4")); + assertEquals("between",node.getOperatorName()); } public void testOperatorOverloading() { diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java index 96984d9194..a31fdfacef 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PerformanceTests.java @@ -35,6 +35,8 @@ public class PerformanceTests extends TestCase { private static SpelAntlrExpressionParser parser = new SpelAntlrExpressionParser(); private static EvaluationContext eContext = TestScenarioCreator.getTestEvaluationContext(); + private static final boolean DEBUG = false; + public void testPerformanceOfPropertyAccess() throws Exception { long starttime = 0; long endtime = 0; @@ -58,7 +60,9 @@ public class PerformanceTests extends TestCase { } endtime = System.currentTimeMillis(); long freshParseTime = endtime - starttime; - System.out.println(freshParseTime); + if (DEBUG) { + System.out.println("PropertyAccess: Time for parsing and evaluation x 10000: "+freshParseTime+"ms"); + } Expression expr = parser.parseExpression("placeOfBirth.city"); if (expr == null) { @@ -70,7 +74,9 @@ public class PerformanceTests extends TestCase { } endtime = System.currentTimeMillis(); long reuseTime = endtime - starttime; - System.out.println(reuseTime); + if (DEBUG) { + System.out.println("PropertyAccess: Time for just evaluation x 10000: "+reuseTime+"ms"); + } if (reuseTime > freshParseTime) { System.out.println("Fresh parse every time, ITERATIONS iterations = " + freshParseTime + "ms"); System.out.println("Reuse SpelExpression, ITERATIONS iterations = " + reuseTime + "ms"); @@ -101,7 +107,9 @@ public class PerformanceTests extends TestCase { } endtime = System.currentTimeMillis(); long freshParseTime = endtime - starttime; - System.out.println(freshParseTime); + if (DEBUG) { + System.out.println("MethodExpression: Time for parsing and evaluation x 10000: "+freshParseTime+"ms"); + } Expression expr = parser.parseExpression("getPlaceOfBirth().getCity()"); if (expr == null) { @@ -113,7 +121,10 @@ public class PerformanceTests extends TestCase { } endtime = System.currentTimeMillis(); long reuseTime = endtime - starttime; - System.out.println(reuseTime); + if (DEBUG) { + System.out.println("MethodExpression: Time for just evaluation x 10000: "+reuseTime+"ms"); + } + if (reuseTime > freshParseTime) { System.out.println("Fresh parse every time, ITERATIONS iterations = " + freshParseTime + "ms"); System.out.println("Reuse SpelExpression, ITERATIONS iterations = " + reuseTime + "ms"); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java index 1948494dca..1460252956 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java @@ -41,6 +41,10 @@ public class PropertyAccessTests extends ExpressionTestCase { evaluate("placeOfBirth.city", "SmilJan", String.class); } + public void testSimpleAccess03() { + evaluate("stringArrayOfThreeItems.length", "3", Integer.class); + } + public void testNonExistentPropertiesAndMethods() { // madeup does not exist as a property evaluateAndCheckError("madeup", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 0); @@ -79,7 +83,7 @@ public class PropertyAccessTests extends ExpressionTestCase { } 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()); + // System.out.println(e.getMessage()); } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java index db236f23b8..216269c214 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ScenariosForSpringSecurity.java @@ -33,6 +33,7 @@ import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser; import org.springframework.expression.spel.support.ReflectionHelper; import org.springframework.expression.spel.support.StandardEvaluationContext; +///CLOVER:OFF /** * Spring Security scenarios from https://wiki.springsource.com/display/SECURITY/Spring+Security+Expression-based+Authorization * diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java index 939f747f4a..b915a0b622 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SetValueTests.java @@ -47,7 +47,11 @@ public class SetValueTests extends ExpressionTestCase { public void testSetArrayElementValue() { setValue("inventions[0]", "Just the telephone"); } - + + public void testSetElementOfNull() { + setValueExpectError("new org.springframework.expression.spel.testresources.Inventor().inventions[1]",SpelMessages.CANNOT_INDEX_INTO_NULL_VALUE); + } + public void testSetArrayElementValueAllPrimitiveTypes() { setValue("arrayContainer.ints[1]", 3); setValue("arrayContainer.floats[1]", 3.0f); diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java new file mode 100644 index 0000000000..2dbd1b8b55 --- /dev/null +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -0,0 +1,480 @@ +/* + * Copyright 2002-2009 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.Arrays; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParserContext; +import org.springframework.expression.spel.antlr.SpelAntlrExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.testresources.Inventor; +import org.springframework.expression.spel.testresources.PlaceOfBirth; + +/** + * Test the examples specified in the documentation. + * + * NOTE: any outgoing changes from this file upon synchronizing with the repo may indicate that + * you need to update the documentation too ! + * + * @author Andy Clement + */ +public class SpelDocumentationTests extends ExpressionTestCase { + + static Inventor tesla ; + static Inventor pupin ; + + + + static { + GregorianCalendar c = new GregorianCalendar(); + c.set(1856, 7, 9); + tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); + tesla.setPlaceOfBirth(new PlaceOfBirth("SmilJan")); + tesla.setInventions(new String[] { "Telephone repeater", "Rotating magnetic field principle", + "Polyphase alternating-current system", "Induction motor", "Alternating-current power transmission", + "Tesla coil transformer", "Wireless communication", "Radio", "Fluorescent lights" }); + + pupin = new Inventor("Pupin", c.getTime(), "Idvor"); + pupin.setPlaceOfBirth(new PlaceOfBirth("Idvor")); + + } + static class IEEE { + private String name; + + + public Inventor[] Members = new Inventor[1]; + public List Members2 = new ArrayList(); + public Map officers = new HashMap(); + IEEE() { + officers.put("president",pupin); + List linv = new ArrayList(); + linv.add(tesla); + officers.put("advisors",linv); + Members2.add(tesla); + Members2.add(pupin); + } + + public boolean isMember(String name) { + return true; + } + + public String getName() { return name; } + public void setName(String n) { this.name = n; } + } + + public void testMethodInvocation() { + evaluate("'Hello World'.concat('!')","Hello World!",String.class); + } + + public void testBeanPropertyAccess() { + evaluate("new String('Hello World'.bytes)","Hello World",String.class); + } + + public void testArrayLengthAccess() { + evaluate("'Hello World'.bytes.length",11,Integer.class); + } + + public void testRootObject() throws Exception { + GregorianCalendar c = new GregorianCalendar(); + c.set(1856, 7, 9); + + // The constructor arguments are name, birthday, and nationaltiy. + Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); + + ExpressionParser parser = new SpelAntlrExpressionParser(); + Expression exp = parser.parseExpression("name"); + + EvaluationContext context = new StandardEvaluationContext(); + context.setRootObject(tesla); + + String name = (String) exp.getValue(context); + assertEquals("Nikola Tesla",name); + } + + public void testEqualityCheck() throws Exception { + + ExpressionParser parser = new SpelAntlrExpressionParser(); + + EvaluationContext context = new StandardEvaluationContext(); + context.setRootObject(tesla); + + Expression exp = parser.parseExpression("name == 'Nikola Tesla'"); + boolean isEqual = exp.getValue(context, Boolean.class); // evaluates to true + assertTrue(isEqual); + } + + // Section 7.4.1 + + public void testXMLBasedConfig() { + evaluate("(T(java.lang.Math).random() * 100.0 )>0",true,Boolean.class); + } + + // Section 7.5 + public void testLiterals() throws Exception { + ExpressionParser parser = new SpelAntlrExpressionParser(); + + String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); // evals to "Hello World" + assertEquals("Hello World",helloWorld); + + double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); + assertEquals(6.0221415E+23,avogadrosNumber); + + int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); // evals to 2147483647 + assertEquals(Integer.MAX_VALUE,maxValue); + + boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); + assertTrue(trueValue); + + Object nullValue = parser.parseExpression("null").getValue(); + assertNull(nullValue); + } + + public void testPropertyAccess() throws Exception { + EvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); + int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); // 1856 + assertEquals(1856,year); + + String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context); + assertEquals("SmilJan",city); + } + + public void testPropertyNavigation() throws Exception { + ExpressionParser parser = new SpelAntlrExpressionParser(); + + // Inventions Array + StandardEvaluationContext teslaContext = TestScenarioCreator.getTestEvaluationContext(); +// teslaContext.setRootObject(tesla); + + // evaluates to "Induction motor" + String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class); + assertEquals("Induction motor",invention); + + // Members List + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + IEEE ieee = new IEEE(); + ieee.Members[0]= tesla; + societyContext.setRootObject(ieee); + + // evaluates to "Nikola Tesla" + String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class); + assertEquals("Nikola Tesla",name); + + // List and Array navigation + // evaluates to "Wireless communication" + invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext, String.class); + assertEquals("Wireless communication",invention); + } + + + public void testDictionaryAccess() throws Exception { + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); + // Officer's Dictionary +// Inventor pupin = parser.parseExpression("officers['president']").getValue(societyContext, Inventor.class); +// +// // evaluates to "Idvor" +// String city = parser.parseExpression("officers['president'].PlaceOfBirth.city").getValue(societyContext, String.class); + + // setting values + Inventor i = parser.parseExpression("officers['advisors'][0]").getValue(societyContext,Inventor.class); + assertEquals("Nikola Tesla",i.getName()); + + parser.parseExpression("officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia"); + + } + + // 7.5.3 + + public void testMethodInvocation2() throws Exception { + // string literal, evaluates to "bc" + String c = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class); + assertEquals("bc",c); + + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); + // evaluates to true + boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, Boolean.class); + assertTrue(isMember); + } + + // 7.5.4.1 + + public void testRelationalOperators() throws Exception { + boolean result = parser.parseExpression("2 == 2").getValue(Boolean.class); + assertTrue(result); + // evaluates to false + result = parser.parseExpression("2 < -5.0").getValue(Boolean.class); + assertFalse(result); + + // evaluates to true + result = parser.parseExpression("'black' < 'block'").getValue(Boolean.class); + assertTrue(result); + } + + public void testOtherOperators() throws Exception { + // evaluates to false + boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); + assertFalse(falseValue); + + // evaluates to true + boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); + assertTrue(trueValue); + + //evaluates to false + falseValue = parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); + assertFalse(falseValue); + } + + // 7.5.4.2 + + public void testLogicalOperators() throws Exception { + + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); + + // -- AND -- + + // evaluates to false + boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); + assertFalse(falseValue); + // evaluates to true + String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; + boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); + + // -- OR -- + + // evaluates to true + trueValue = parser.parseExpression("true or false").getValue(Boolean.class); + assertTrue(trueValue); + + // evaluates to true + expression = "isMember('Nikola Tesla') or isMember('Albert Einstien')"; + trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); + assertTrue(trueValue); + + // -- NOT -- + + // evaluates to false + falseValue = parser.parseExpression("!true").getValue(Boolean.class); + assertFalse(falseValue); + + + // -- AND and NOT -- + expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; + falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); + assertFalse(falseValue); + } + + // 7.5.4.3 + + public void testNumericalOperators() throws Exception { + // Addition + int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 + assertEquals(2,two); + + String testString = parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string' + assertEquals("test string",testString); + + // Subtraction + int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 + assertEquals(4,four); + + double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 + assertEquals(-9000.0d,d); + + // Multiplication + int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 + assertEquals(6,six); + + double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 + assertEquals(24.0d,twentyFour); + + // Division + int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 + assertEquals(-2,minusTwo); + + double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 + assertEquals(1.0d,one); + + // Modulus + int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 + assertEquals(3,three); + + int oneInt = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 + assertEquals(1,oneInt); + + // Operator precedence + int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 + assertEquals(-21,minusTwentyOne); + } + + // 7.5.5 + + public void testAssignment() throws Exception { + Inventor inventor = new Inventor(); + StandardEvaluationContext inventorContext = new StandardEvaluationContext(); + inventorContext.setRootObject(inventor); + + parser.parseExpression("foo").setValue(inventorContext, "Alexander Seovic2"); + + assertEquals("Alexander Seovic2",parser.parseExpression("foo").getValue(inventorContext,String.class)); + // alternatively + + String aleks = parser.parseExpression("foo = 'Alexandar Seovic'").getValue(inventorContext, String.class); + assertEquals("Alexandar Seovic",parser.parseExpression("foo").getValue(inventorContext,String.class)); + assertEquals("Alexandar Seovic",aleks); + } + + // 7.5.6 + + public void testTypes() throws Exception { + Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); + assertEquals(Date.class,dateClass); + boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class); + assertTrue(trueValue); + } + + // 7.5.7 + + public void testConstructors() throws Exception { + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); + Inventor einstein = + parser.parseExpression("new org.springframework.expression.spel.testresources.Inventor('Albert Einstein',new java.util.Date(), 'German')").getValue(Inventor.class); + assertEquals("Albert Einstein", einstein.getName()); + //create new inventor instance within add method of List + parser.parseExpression("Members2.add(new org.springframework.expression.spel.testresources.Inventor('Albert Einstein', 'German'))").getValue(societyContext); + } + + // 7.5.8 + + public void testVariables() throws Exception { + Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setVariable("newName", "Mike Tesla"); + + context.setRootObject(tesla); + + parser.parseExpression("foo = #newName").getValue(context); + + assertEquals("Mike Tesla",tesla.getFoo()); + } + + public void testSpecialVariables() throws Exception { + // create an array of integers + List primes = new ArrayList(); + primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); + + // create parser and set variable 'primes' as the array of integers + ExpressionParser parser = new SpelAntlrExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setVariable("primes",primes); + + // all prime numbers > 10 from the list (using selection ?{...}) + List primesGreaterThanTen = (List) parser.parseExpression("#primes.?{#this>10}").getValue(context); + assertEquals("[11, 13, 17]",primesGreaterThanTen.toString()); + } + + // 7.5.9 + + public void testFunctions() throws Exception { + ExpressionParser parser = new SpelAntlrExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + + context.registerFunction("reverseString", + StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class })); + + String helloWorldReversed = parser.parseExpression("#reverseString('hello world')").getValue(context, String.class); + assertEquals("dlrow olleh",helloWorldReversed); + } + + // 7.5.10 + + public void testTernary() throws Exception { + String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class); + assertEquals("falseExp",falseString); + + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); + + + parser.parseExpression("Name").setValue(societyContext, "IEEE"); + societyContext.setVariable("queryName", "Nikola Tesla"); + + String expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; + + String queryResultString = parser.parseExpression(expression).getValue(societyContext, String.class); + assertEquals("Nikola Tesla is a member of the IEEE Society",queryResultString); + // queryResultString = "Nikola Tesla is a member of the IEEE Society" + } + + // 7.5.11 + + public void testSelection() throws Exception { + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); + List list = (List) parser.parseExpression("Members2.?{nationality == 'Serbian'}").getValue(societyContext); + assertEquals(1,list.size()); + assertEquals("Nikola Tesla",list.get(0).getName()); + } + + // 7.5.12 + + public void testTemplating() throws Exception { + String randomPhrase = + parser.parseExpression("random number is ${T(java.lang.Math).random()}", new TemplatedParserContext()).getValue(String.class); + assertTrue(randomPhrase.startsWith("random number")); + } + + static class TemplatedParserContext implements ParserContext { + + public String getExpressionPrefix() { + return "${"; + } + + public String getExpressionSuffix() { + return "}"; + } + + public boolean isTemplate() { + return true; + } + } + + static class StringUtils { + + public static String reverseString(String input) { + StringBuilder backwards = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + backwards.append(input.charAt(input.length() - 1 - i)); + } + return backwards.toString(); + } + } + +} + diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java index 54de50d243..05327188c0 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/TestScenarioCreator.java @@ -22,6 +22,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.testresources.Inventor; import org.springframework.expression.spel.testresources.PlaceOfBirth; +///CLOVER:OFF /** * Builds an evaluation context for test expressions. Features of the test evaluation context are: *
    diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Fruit.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Fruit.java index ba3e5c8434..234375f58d 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Fruit.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Fruit.java @@ -10,7 +10,8 @@ public class Fruit { public String name; // accessible as property field public Color color; // accessible as property through getter/setter public String colorName; // accessible as property through getter/setter - + public int stringscount = -1; + public Fruit(String name, Color color, String colorName) { this.name = name; this.color = color; @@ -20,6 +21,18 @@ public class Fruit { public Color getColor() { return color; } + + public Fruit(String... strings) { + stringscount = strings.length; + } + + public Fruit(int i, String... strings) { + stringscount = i + strings.length; + } + + public int stringscount() { + return stringscount; + } public String toString() { return "A" + (colorName.startsWith("o") ? "n " : " ") + colorName + " " + name; diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java index cf7fd53119..4239336637 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java @@ -12,7 +12,7 @@ public class Inventor { private String name; public String publicName; private PlaceOfBirth placeOfBirth; - Date birthdate; + private Date birthdate; private int sinNumber; private String nationality; private String[] inventions; @@ -29,6 +29,9 @@ public class Inventor { public Map mapOfStringToBoolean = new HashMap(); public Map mapOfNumbersUpToTen = new HashMap(); public List listOfNumbersUpToTen = new ArrayList(); + public List listOneFive = new ArrayList(); + public String[] stringArrayOfThreeItems = new String[]{"1","2","3"}; + private String foo; public Inventor(String name, Date birthdate, String nationality) { this.name = name; @@ -43,6 +46,8 @@ public class Inventor { testMap.put("friday", "freitag"); testMap.put("saturday", "samstag"); testMap.put("sunday", "sonntag"); + listOneFive.add(1); + listOneFive.add(5); booleanList.add(false); booleanList.add(false); listOfNumbersUpToTen.add(1); @@ -148,4 +153,11 @@ public class Inventor { public void setSomeProperty(boolean b) { this.accessedThroughGetSet = b; } + + public Date getBirthdate() { return birthdate;} + + public String getFoo() { return foo; } + public void setFoo(String s) { foo = s; } + + public String getNationality() { return nationality; } } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java index 459b88827c..4541a44636 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/testresources/PlaceOfBirth.java @@ -4,6 +4,8 @@ package org.springframework.expression.spel.testresources; public class PlaceOfBirth { private String city; + public String Country; + @Override public String toString() {return "PlaceOfBirth("+city+")";}