Browse Source

More tests. First pass on messages review.

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@979 50f2f4bb-b051-0410-bef5-90022cba6387
pull/1/head
Andy Clement 17 years ago
parent
commit
8a980b8364
  1. 139
      org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java
  2. 7
      org.springframework.expression/src/main/java/org/springframework/expression/spel/antlr/SpelAntlrExpressionParser.java
  3. 4
      org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java
  4. 4
      org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java
  5. 24
      org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java
  6. 3
      org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java
  7. 70
      org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java
  8. 2
      org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java
  9. 7
      org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java
  10. 102
      org.springframework.expression/src/test/java/org/springframework/expression/spel/HelperTests.java
  11. 2
      org.springframework.expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java
  12. 35
      org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java

139
org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java

@ -30,8 +30,7 @@ import java.text.MessageFormat;
* EL1004E: (pos 34): Type cannot be found 'String' * EL1004E: (pos 34): Type cannot be found 'String'
* </pre> * </pre>
* *
* </code> The prefix captures the code and the error kind, whilst the position is included if it is known and the * </code> The prefix captures the code and the error kind, whilst the position is included if it is known.
* message has had all relevant inserts applied to it.
* *
* @author Andy Clement * @author Andy Clement
* @since 3.0 * @since 3.0
@ -41,82 +40,66 @@ public enum SpelMessages {
// TODO review if any messages are not used // TODO review if any messages are not used
// TODO sort messages into better groups if possible, sharing a name prefix perhaps // TODO sort messages into better groups if possible, sharing a name prefix perhaps
INITIALIZER_LENGTH_INCORRECT(Kind.ERROR, 1001, TYPE_CONVERSION_ERROR(Kind.ERROR, 1001, "Type conversion problem, cannot convert from {0} to {1}"), //
"Array constructor call: initializer size of {0} does not match declared length of {1}"), TYPE_CONVERSION_ERROR( CONSTRUCTOR_NOT_FOUND(Kind.ERROR, 1002, "Constructor call: No suitable constructor found on type {0} for arguments {1}"), //
Kind.ERROR, 1002, "Type conversion problem, cannot convert from {0} to {1}"), CONSTRUCTOR_NOT_FOUND( METHOD_NOT_FOUND(Kind.ERROR, 1003, "Method call: Method {0} cannot be found on {1} type"), //
Kind.ERROR, 1003, "Constructor call: No suitable constructor on type {0} for arguments {1}"), TYPE_NOT_FOUND( TYPE_NOT_FOUND(Kind.ERROR, 1004, "Type cannot be found ''{0}''"), //
Kind.ERROR, 1004, "Type cannot be found ''{0}''"), ADDITION_NOT_DEFINED(Kind.ERROR, 1005, VARIABLE_NOT_FOUND(Kind.ERROR, 1005, "Variable named ''{0}'' cannot be found"), //
"Addition not defined between operands of type {0} and {1}"), METHOD_NOT_FOUND(Kind.ERROR, 1006, LOCAL_VARIABLE_NOT_DEFINED(Kind.ERROR, 1006, "Local variable named ''{0}'' could not be found"), //
"Method call: Method {0} cannot be found on {1} type"), ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT( FUNCTION_NOT_DEFINED(Kind.ERROR, 1007, "The function ''{0}'' could not be found"), //
Kind.ERROR, 1007, "Method call: Attempted to call method {0} on null context object"), ATTEMPTED_PROPERTY_FIELD_REF_ON_NULL_CONTEXT_OBJECT( PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL(Kind.ERROR, 1008, "Field or property ''{0}'' cannot be found on null"), //
Kind.ERROR, 1008, PROPERTY_OR_FIELD_NOT_READABLE(Kind.ERROR, 1009, "Field or property ''{0}'' cannot be found on object of type ''{1}''"), //
"Field or property reference: Attempted to refer to field or property ''{0}'' on null context object"), PROPERTY_OR_FIELD_NOT_FOUND( PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL(Kind.ERROR, 1010, "Field or property ''{0}'' cannot be set on null"), //
Kind.ERROR, 1009, "Field or property ''{0}'' cannot be found on object of type ''{1}''"), PROPERTY_OR_FIELD_SETTER_NOT_FOUND( PROPERTY_OR_FIELD_NOT_WRITABLE(Kind.ERROR, 1010, "Field or property ''{0}'' cannot be set on object of type ''{1}''"), //
Kind.ERROR, 1010, "Field or property ''{0}'' cannot be set on object of type ''{1}''"), MULTIPLY_NOT_DEFINED(
Kind.ERROR, 1011, "Multiply not defined between operands of type {0} and {1}"), NOT_COMPARABLE(Kind.ERROR, METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED(Kind.ERROR, 1011, "Method call: Attempted to call method {0} on null context object"), //
1012, "Cannot compare instances of {0} and {1}"), NOT_COMPARABLE_CANNOT_COERCE(Kind.ERROR, 1013, PROPERTY_OR_FIELD_ACCESS_ON_NULL_OBJECT_NOT_ALLOWED(Kind.ERROR, 1012, "Field or property reference: Attempted to refer to field or property ''{0}'' on null context object"), //
"Cannot compare instances of {0} and {1} because they cannot be coerced to the same type"), VARIABLE_NOT_FOUND( CANNOT_INDEX_INTO_NULL_VALUE(Kind.ERROR, 1013, "Cannot index into a null value"),
Kind.ERROR, 1014, "Variable named ''{0}'' cannot be found"), INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION(
Kind.ERROR, 1015, "Incorrect number of arguments for function, {0} supplied but function takes {1}"), NO_SUCH_FUNCTION( NOT_COMPARABLE(Kind.ERROR, 1014, "Cannot compare instances of {0} and {1}"), //
Kind.ERROR, 1016, "No such function named ''{0}''"), NOT_A_FUNCTION(Kind.ERROR, 1017, NOT_COMPARABLE_CANNOT_COERCE(Kind.ERROR, 1015, "Cannot compare instances of {0} and {1} because they cannot be coerced to the same type"), //
"The name ''{0}'' did not map to a function, it mapped to a ''{1}''"), INVALID_TYPE_FOR_SELECTION( INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION(Kind.ERROR, 1016, "Incorrect number of arguments for function, {0} supplied but function takes {1}"), //
Kind.ERROR, 1018, "Cannot perform selection on input data of type ''{0}''"), RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN( INVALID_TYPE_FOR_SELECTION(Kind.ERROR, 1017, "Cannot perform selection on input data of type ''{0}''"), //
Kind.ERROR, 1019, "Result of selection criteria is not boolean"), MODULUS_NOT_DEFINED(Kind.ERROR, 1020, RESULT_OF_SELECTION_CRITERIA_IS_NOT_BOOLEAN(Kind.ERROR, 1018, "Result of selection criteria is not boolean"), //
"Modulus not defined between operands of type ''{0}'' and ''{1}''"), NULL_OPERAND_TO_OPERATOR(Kind.ERROR, NULL_OPERAND_TO_OPERATOR(Kind.ERROR, 1019, "Operand evaluated to null and that is not supported for this operator"), //
1021, "Operand evaluated to null and that is not supported for this operator"), NO_SIZE_OR_INITIALIZER_FOR_ARRAY_CONSTRUCTION( BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST(Kind.ERROR, 1020, "Right operand for the 'between' operator has to be a two-element list"), //
Kind.ERROR, 1022, "No array size or initializer was supplied to construct the array"), INCORRECT_ELEMENT_TYPE_FOR_ARRAY( UNABLE_TO_ACCESS_FIELD(Kind.ERROR, 1021, "Unable to access field ''{0}'' on type ''{1}''"), //
Kind.ERROR, 1023, "The array of type ''{0}'' cannot have an element of type ''{1}'' inserted"), BETWEEN_RIGHT_OPERAND_MUST_BE_TWO_ELEMENT_LIST( UNABLE_TO_ACCESS_PROPERTY_THROUGH_GETTER(Kind.ERROR, 1022, "Unable to access property ''{0}'' through getter on type ''{1}''"), //
Kind.ERROR, 1024, "Right operand for the 'between' operator has to be a two-element list"), TYPE_NOT_SUPPORTED_BY_PROCESSOR( UNABLE_TO_ACCESS_PROPERTY_THROUGH_SETTER(Kind.ERROR, 1023, "Unable to access property ''{0}'' through setter on type ''{1}''"), //
Kind.ERROR, 1025, INVALID_PATTERN(Kind.ERROR, 1024, "Pattern is not valid ''{0}''"), //
"The collection processor ''{0}'' does not understand and input collection of elements of type {1}"), UNABLE_TO_ACCESS_FIELD( RECOGNITION_ERROR(Kind.ERROR, 1025, "Recognition error: {0}"), // TODO poor message when a recognition exception occurs
Kind.ERROR, 1026, "Unable to access field ''{0}'' on type ''{1}''"), UNABLE_TO_ACCESS_PROPERTY_THROUGH_GETTER( PROJECTION_NOT_SUPPORTED_ON_TYPE(Kind.ERROR, 1026, "Projection is not supported on the type ''{0}''"), //
Kind.ERROR, 1027, "Unable to access property ''{0}'' through getter on type ''{1}''"), UNABLE_TO_ACCESS_PROPERTY_THROUGH_SETTER( ARGLIST_SHOULD_NOT_BE_EVALUATED(Kind.ERROR, 1027, "The argument list of a lambda expression should never have getValue() called upon it"), //
Kind.ERROR, 1028, "Unable to access property ''{0}'' through setter on type ''{1}''"), INVALID_PATTERN( MAPENTRY_SHOULD_NOT_BE_EVALUATED(Kind.ERROR, 1028, "A map entry should never have getValue() called upon it"), //
Kind.ERROR, 1029, "Pattern is not valid ''{0}''"), RECOGNITION_ERROR(Kind.ERROR, 1030, EXCEPTION_DURING_PROPERTY_READ(Kind.ERROR, 1029, "A problem occurred whilst attempting to access the property ''{0}'': ''{1}''"), //
"Recognition error: {0}"), // TODO poor message when a recognition exception occurs EXCEPTION_DURING_CONSTRUCTOR_INVOCATION(Kind.ERROR, 1030, "A problem occurred whilst attempting to construct ''{0}'': ''{1}''"), //
PROJECTION_NOT_SUPPORTED_ON_TYPE(Kind.ERROR, 1031, "Projection is not supported on the type ''{0}''"), ARGLIST_SHOULD_NOT_BE_EVALUATED( DATE_CANNOT_BE_PARSED(Kind.ERROR, 1031, "Unable to parse date ''{0}'' using format ''{1}''"), //
Kind.ERROR, 1032, "The argument list of a lambda expression should never have getValue() called upon it"), MAPENTRY_SHOULD_NOT_BE_EVALUATED( FUNCTION_REFERENCE_CANNOT_BE_INVOKED(Kind.ERROR, 1032, "The function ''{0}'' mapped to an object of type ''{1}'' which cannot be invoked"), //
Kind.ERROR, 1033, "A map entry should never have getValue() called upon it"), EXCEPTION_DURING_PROPERTY_READ( EXCEPTION_DURING_FUNCTION_CALL(Kind.ERROR, 1033, "A problem occurred whilst attempting to invoke the function ''{0}'': ''{1}''"), //
Kind.ERROR, 1034, "A problem occurred whilst attempting to access the property ''{0}'': ''{1}''"), EXCEPTION_DURING_CONSTRUCTOR_INVOCATION(
Kind.ERROR, 1035, "A problem occurred whilst attempting to construct ''{0}'': ''{1}''"), DATE_CANNOT_BE_PARSED( // indexing
Kind.ERROR, 1036, "Unable to parse date ''{0}'' using format ''{1}''"), FUNCTION_REFERENCE_CANNOT_BE_INVOKED( ARRAY_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1034, "The array has ''{0}'' elements, index ''{1}'' is invalid"), //
Kind.ERROR, 1037, "The function ''{0}'' mapped to an object of type ''{1}'' which cannot be invoked"), FUNCTION_NOT_DEFINED( COLLECTION_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1035, "The collection has ''{0}'' elements, index ''{1}'' is invalid"), //
Kind.ERROR, 1038, "The function ''{0}'' could not be found"), EXCEPTION_DURING_FUNCTION_CALL(Kind.ERROR, STRING_INDEX_OUT_OF_BOUNDS(Kind.ERROR, 1036, "The string has ''{0}'' characters, index ''{1}'' is invalid"), //
1039, "A problem occurred whilst attempting to invoke the function ''{0}'': ''{1}''"), ARRAY_INDEX_OUT_OF_BOUNDS( INDEXING_NOT_SUPPORTED_FOR_TYPE(Kind.ERROR, 1037, "Indexing into type ''{0}'' is not supported"), //
Kind.ERROR, 1040, "The array has ''{0}'' elements, index ''{1}'' is invalid"), COLLECTION_INDEX_OUT_OF_BOUNDS(
Kind.ERROR, 1041, "The collection has ''{0}'' elements, index ''{1}'' is invalid"), STRING_INDEX_OUT_OF_BOUNDS( INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND(Kind.ERROR, 1038, "The operator 'instanceof' needs the right operand to be a class, not a ''{0}''"), //
Kind.ERROR, 1042, "The string has ''{0}'' characters, index ''{1}'' is invalid"), INDEXING_NOT_SUPPORTED_FOR_TYPE( EXCEPTION_DURING_METHOD_INVOCATION(Kind.ERROR, 1039, "A problem occurred when trying to execute method ''{0}'' on object of type ''{1}'': ''{2}''"), //
Kind.ERROR, 1043, "Indexing into type ''{0}'' is not supported"), OPERATOR_IN_CANNOT_DETERMINE_MEMBERSHIP( OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES(Kind.ERROR, 1040, "The operator ''{0}'' is not supported between objects of type ''{1}'' and ''{2}''"), //
Kind.ERROR, 1044, "Operator 'in' not implemented for detecting membership of a ''{0}'' in a ''{1}''"), CANNOT_NEGATE_TYPE( UNEXPECTED_PROBLEM_INVOKING_OPERATOR(Kind.ERROR, 1041, "Unexpected problem invoking operator ''{0}'' between objects of type ''{1}'' and ''{2}'': {3}"), //
Kind.ERROR, 1045, "Cannot determine negation of type ''{0}''"), CUT_ARGUMENTS_MUST_BE_INTS(Kind.ERROR, PROBLEM_LOCATING_METHOD(Kind.ERROR, 1042, "Problem locating method {0} cannot on type {1}"),
1046, "Both arguments to the cut() processor must be Integers, but they are ''{0}'' and ''{1}''"), SOUNDSLIKE_NEEDS_STRING_OPERAND( PROBLEM_LOCATING_CONSTRUCTOR(Kind.ERROR, 1043, "A problem occurred whilst attempting to construct an object of type ''{0}'' using arguments ''{1}''"), //
Kind.ERROR, 1047, "The soundslike operator needs String operands, but found a ''{0}''"), INSTANCEOF_OPERATOR_NEEDS_CLASS_OPERAND( SETVALUE_NOT_SUPPORTED( Kind.ERROR, 1044, "setValue(ExpressionState, Object) not implemented for ''{0}'' (''{1}''"), //
Kind.ERROR, 1048, "The operator 'instanceof' needs the right operand to be a class, not a ''{0}''"), LOCAL_VARIABLE_NOT_DEFINED( PROBLEM_DURING_TYPE_CONVERSION(Kind.ERROR, 1045, "Problem occurred during type conversion: {0}"), //
Kind.ERROR, 1049, "Local variable named ''{0}'' could not be found"), EXCEPTION_DURING_METHOD_INVOCATION( MULTIPLE_POSSIBLE_METHODS(Kind.ERROR, 1046, "Method call of ''{0}'' is ambiguous, supported type conversions allow multiple variants to match"), //
Kind.ERROR, 1050, EXCEPTION_DURING_PROPERTY_WRITE(Kind.ERROR, 1047, "A problem occurred whilst attempting to set the property ''{0}'': {1}"), //
"A problem occurred when trying to execute method ''{0}'' on object of type ''{1}'': ''{2}''"), PLACEHOLDER_SHOULD_NEVER_BE_EVALUATED( NOT_AN_INTEGER(Kind.ERROR, 1048, "The value ''{0}'' cannot be parsed as an int"), //
Kind.ERROR, 1051, "InternalError: A placeholder node in the Ast should never be evaluated!"), OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES( NOT_A_LONG(Kind.ERROR, 1049, "The value ''{0}'' cannot be parsed as a long"), //
Kind.ERROR, 1052, "The operator ''{0}'' is not supported between objects of type ''{1}'' and ''{2}''"), UNEXPECTED_PROBLEM_INVOKING_OPERATOR( PARSE_PROBLEM(Kind.ERROR, 1050, "Error occurred during expression parse: {0}"), //
Kind.ERROR, 1054, INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1051, "First operand to matches operator must be a string. ''{0}'' is not"), //
"Unexpected problem invoking operator ''{0}'' between objects of type ''{1}'' and ''{2}'': {3}"), PROBLEM_LOCATING_METHOD( INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR(Kind.ERROR, 1052, "Second operand to matches operator must be a string. ''{0}'' is not"), //
Kind.ERROR, 1055, "Problem locating method {0} cannot on type {1}"), PROBLEM_LOCATING_CONSTRUCTOR( FUNCTION_MUST_BE_STATIC(Kind.ERROR, 1053, "Only static methods can be called via function references. The method ''{0}'' referred to by name ''{1}'' is not static."),//
Kind.ERROR, 1056, ;
"A problem occurred whilst attempting to construct an object of type ''{0}'' using arguments ''{1}''"), INVALID_FIRST_OPERAND_FOR_LIKE_OPERATOR(
Kind.ERROR, 1057, "First operand to like operator must be a string. ''{0}'' is not"), INVALID_SECOND_OPERAND_FOR_LIKE_OPERATOR(
Kind.ERROR, 1058, "Second operand to like operator must be a string (regex). ''{0}'' is not"), SETVALUE_NOT_SUPPORTED(
Kind.ERROR, 1059, "setValue(ExpressionState, Object) not implemented for ''{0}'' (''{1}''"), TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION(
Kind.ERROR, 1060, "Expected the type of the new array to be specified as a String but found ''{0}''"), PROBLEM_DURING_TYPE_CONVERSION(
Kind.ERROR, 1061, "Problem occurred during type conversion: {0}"), MULTIPLE_POSSIBLE_METHODS(Kind.ERROR,
1062, "Method call of ''{0}'' is ambiguous, supported type conversions allow multiple variants to match"), EXCEPTION_DURING_PROPERTY_WRITE(
Kind.ERROR, 1063, "A problem occurred whilst attempting to set the property ''{0}'': {1}"), NOT_AN_INTEGER(
Kind.ERROR, 1064, "The value ''{0}'' cannot be parsed as an int"), NOT_A_LONG(Kind.ERROR, 1065,
"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."),//
CANNOT_INDEX_INTO_NULL_VALUE(Kind.ERROR, 1070, "Cannot index into a null value");
private Kind kind; private Kind kind;
private int code; private int code;

7
org.springframework.expression/src/main/java/org/springframework/expression/spel/antlr/SpelAntlrExpressionParser.java

@ -19,7 +19,6 @@ package org.springframework.expression.spel.antlr;
import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException; import org.antlr.runtime.RecognitionException;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.ParseException; import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext; import org.springframework.expression.ParserContext;
@ -67,12 +66,10 @@ public class SpelAntlrExpressionParser extends TemplateAwareExpressionParser {
this.parser.setTokenStream(tokens); this.parser.setTokenStream(tokens);
expr_return exprReturn = this.parser.expr(); expr_return exprReturn = this.parser.expr();
return new SpelExpression(expressionString, (SpelNode) exprReturn.getTree()); return new SpelExpression(expressionString, (SpelNode) exprReturn.getTree());
} } catch (RecognitionException re) {
catch (RecognitionException re) {
throw new ParseException(expressionString, throw new ParseException(expressionString,
"Recognition error at position: " + re.charPositionInLine + ": " + re.getMessage(), re); "Recognition error at position: " + re.charPositionInLine + ": " + re.getMessage(), re);
} } catch (WrappedSpelException ex) {
catch (WrappedSpelException ex) {
SpelException wrappedException = ex.getCause(); SpelException wrappedException = ex.getCause();
throw new ParseException(expressionString, throw new ParseException(expressionString,
"Parsing problem: " + wrappedException.getMessage(), wrappedException); "Parsing problem: " + wrappedException.getMessage(), wrappedException);

4
org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/ConstructorReference.java

@ -49,7 +49,7 @@ public class ConstructorReference extends SpelNodeImpl {
/** /**
* The cached executor that may be reused on subsequent evaluations. * The cached executor that may be reused on subsequent evaluations.
*/ */
private ConstructorExecutor cachedExecutor; private volatile ConstructorExecutor cachedExecutor;
public ConstructorReference(Token payload) { public ConstructorReference(Token payload) {
super(payload); super(payload);
@ -112,7 +112,7 @@ public class ConstructorReference extends SpelNodeImpl {
* @return a reusable ConstructorExecutor that can be invoked to run the constructor or null * @return a reusable ConstructorExecutor that can be invoked to run the constructor or null
* @throws SpelException if there is a problem locating the constructor * @throws SpelException if there is a problem locating the constructor
*/ */
protected ConstructorExecutor findExecutorForConstructor( private ConstructorExecutor findExecutorForConstructor(
String typename, Class<?>[] argumentTypes, ExpressionState state) throws SpelException { String typename, Class<?>[] argumentTypes, ExpressionState state) throws SpelException {
EvaluationContext eContext = state.getEvaluationContext(); EvaluationContext eContext = state.getEvaluationContext();

4
org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java

@ -55,7 +55,7 @@ public class MethodReference extends SpelNodeImpl {
arguments[i] = getChild(i).getValueInternal(state).getValue(); arguments[i] = getChild(i).getValueInternal(state).getValue();
} }
if (currentContext.getValue() == null) { if (currentContext.getValue() == null) {
throw new SpelException(getCharPositionInLine(), SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT, throw new SpelException(getCharPositionInLine(), SpelMessages.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED,
FormatHelper.formatMethodForMessage(name, getTypes(arguments))); FormatHelper.formatMethodForMessage(name, getTypes(arguments)));
} }
@ -105,7 +105,7 @@ public class MethodReference extends SpelNodeImpl {
return sb.toString(); return sb.toString();
} }
protected MethodExecutor findAccessorForMethod(String name, Class<?>[] argumentTypes, ExpressionState state) private MethodExecutor findAccessorForMethod(String name, Class<?>[] argumentTypes, ExpressionState state)
throws SpelException { throws SpelException {
TypedValue context = state.getActiveContextObject(); TypedValue context = state.getActiveContextObject();

24
org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java

@ -111,8 +111,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
throw new SpelException(ae, SpelMessages.EXCEPTION_DURING_PROPERTY_READ, name, ae.getMessage()); throw new SpelException(ae, SpelMessages.EXCEPTION_DURING_PROPERTY_READ, name, ae.getMessage());
} }
} }
throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, name, if (contextObject.getValue() == null) {
FormatHelper.formatClassNameForMessage(contextObjectClass)); throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL, name);
} else {
throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_READABLE, name,
FormatHelper.formatClassNameForMessage(contextObjectClass));
}
} }
private void writeProperty(ExpressionState state, String name, Object newValue) throws SpelException { private void writeProperty(ExpressionState state, String name, Object newValue) throws SpelException {
@ -149,18 +153,20 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
name, ae.getMessage()); name, ae.getMessage());
} }
} }
throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_SETTER_NOT_FOUND, name, FormatHelper if (contextObject.getValue()==null) {
.formatClassNameForMessage(contextObjectClass)); throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL, name);
} else {
throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_WRITABLE, name, FormatHelper
.formatClassNameForMessage(contextObjectClass));
}
} }
public boolean isWritableProperty(String name, ExpressionState state) throws SpelException { public boolean isWritableProperty(String name, ExpressionState state) throws SpelException {
Object contextObject = state.getActiveContextObject().getValue(); Object contextObject = state.getActiveContextObject().getValue();
EvaluationContext eContext = state.getEvaluationContext(); EvaluationContext eContext = state.getEvaluationContext();
if (contextObject == null) {
throw new SpelException(SpelMessages.ATTEMPTED_PROPERTY_FIELD_REF_ON_NULL_CONTEXT_OBJECT, name); List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);
}
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(
(contextObject instanceof Class) ? ((Class<?>) contextObject) : contextObject.getClass(), state);
if (resolversToTry != null) { if (resolversToTry != null) {
for (PropertyAccessor pfResolver : resolversToTry) { for (PropertyAccessor pfResolver : resolversToTry) {
try { try {

3
org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java

@ -49,8 +49,7 @@ public abstract class SpelNodeImpl extends CommonTree implements SpelNode, Seria
public final Object getValue(ExpressionState expressionState) throws EvaluationException { public final Object getValue(ExpressionState expressionState) throws EvaluationException {
if (expressionState != null) { if (expressionState != null) {
return getValueInternal(expressionState).getValue(); return getValueInternal(expressionState).getValue();
} } else {
else {
return getValue(new ExpressionState(new StandardEvaluationContext())); return getValue(new ExpressionState(new StandardEvaluationContext()));
} }
} }

70
org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java

@ -43,11 +43,9 @@ public class ReflectionHelper {
* @param expectedArgTypes the array of types the method/constructor is expecting * @param expectedArgTypes the array of types the method/constructor is expecting
* @param suppliedArgTypes the array of types that are being supplied at the point of invocation * @param suppliedArgTypes the array of types that are being supplied at the point of invocation
* @param typeConverter a registered type converter * @param typeConverter a registered type converter
* @param conversionAllowed if true then allow for what the type converter can do when seeing if a supplied type can
* match an expected type
* @return a MatchInfo object indicating what kind of match it was or null if it was not a match * @return a MatchInfo object indicating what kind of match it was or null if it was not a match
*/ */
static ArgumentsMatchInfo compareArguments( public static ArgumentsMatchInfo compareArguments(
Class[] expectedArgTypes, Class[] suppliedArgTypes, TypeConverter typeConverter) { Class[] expectedArgTypes, Class[] suppliedArgTypes, TypeConverter typeConverter) {
ArgsMatchKind match = ArgsMatchKind.EXACT; ArgsMatchKind match = ArgsMatchKind.EXACT;
@ -60,7 +58,7 @@ public class ReflectionHelper {
/* || isWidenableTo(expectedArg, suppliedArg) */) { /* || isWidenableTo(expectedArg, suppliedArg) */) {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) { if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE; match = ArgsMatchKind.CLOSE;
} }
} else if (typeConverter.canConvert(suppliedArg, expectedArg)) { } else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
if (argsRequiringConversion == null) { if (argsRequiringConversion == null) {
argsRequiringConversion = new ArrayList<Integer>(); argsRequiringConversion = new ArrayList<Integer>();
@ -74,16 +72,14 @@ public class ReflectionHelper {
} }
if (match == null) { if (match == null) {
return null; return null;
} } else {
else {
if (match == ArgsMatchKind.REQUIRES_CONVERSION) { if (match == ArgsMatchKind.REQUIRES_CONVERSION) {
int[] argsArray = new int[argsRequiringConversion.size()]; int[] argsArray = new int[argsRequiringConversion.size()];
for (int i = 0; i < argsRequiringConversion.size(); i++) { for (int i = 0; i < argsRequiringConversion.size(); i++) {
argsArray[i] = argsRequiringConversion.get(i); argsArray[i] = argsRequiringConversion.get(i);
} }
return new ArgumentsMatchInfo(match, argsArray); return new ArgumentsMatchInfo(match, argsArray);
} } else {
else {
return new ArgumentsMatchInfo(match); return new ArgumentsMatchInfo(match);
} }
} }
@ -92,24 +88,23 @@ public class ReflectionHelper {
/** /**
* Compare argument arrays and return information about whether they match. A supplied type converter and * Compare argument arrays and return information about whether they match. A supplied type converter and
* conversionAllowed flag allow for matches to take into account that a type may be transformed into a different * conversionAllowed flag allow for matches to take into account that a type may be transformed into a different
* type by the converter. This variant of compareArguments allows for a varargs match. * type by the converter. This variant of compareArguments also allows for a varargs match.
* @param expectedArgTypes the array of types the method/constructor is expecting * @param expectedArgTypes the array of types the method/constructor is expecting
* @param suppliedArgTypes the array of types that are being supplied at the point of invocation * @param suppliedArgTypes the array of types that are being supplied at the point of invocation
* @param typeConverter a registered type converter * @param typeConverter a registered type converter
* @param conversionAllowed if true then allow for what the type converter can do when seeing if a supplied type can
* match an expected type
* @return a MatchInfo object indicating what kind of match it was or null if it was not a match * @return a MatchInfo object indicating what kind of match it was or null if it was not a match
*/ */
static ArgumentsMatchInfo compareArgumentsVarargs( static ArgumentsMatchInfo compareArgumentsVarargs(
Class[] expectedArgTypes, Class[] suppliedArgTypes, TypeConverter typeConverter) { Class[] expectedArgTypes, Class[] suppliedArgTypes, TypeConverter typeConverter) {
ArgsMatchKind match = ArgsMatchKind.EXACT; ArgsMatchKind match = ArgsMatchKind.EXACT;
List<Integer> argsRequiringConversion = null; List<Integer> argsRequiringConversion = null;
// Check up until the varargs argument: // Check up until the varargs argument:
// Deal with the arguments up to 'expected number' - 1 // Deal with the arguments up to 'expected number' - 1 (that is everything but the varargs argument)
for (int i = 0; i < expectedArgTypes.length - 1 && match != null; i++) { int argCountUpToVarargs = expectedArgTypes.length-1;
for (int i = 0; i < argCountUpToVarargs && match != null; i++) {
Class suppliedArg = suppliedArgTypes[i]; Class suppliedArg = suppliedArgTypes[i];
Class expectedArg = expectedArgTypes[i]; Class expectedArg = expectedArgTypes[i];
if (expectedArg != suppliedArg) { if (expectedArg != suppliedArg) {
@ -117,8 +112,7 @@ public class ReflectionHelper {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) { if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE; match = ArgsMatchKind.CLOSE;
} }
} } else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
if (argsRequiringConversion == null) { if (argsRequiringConversion == null) {
argsRequiringConversion = new ArrayList<Integer>(); argsRequiringConversion = new ArrayList<Integer>();
} }
@ -129,7 +123,7 @@ public class ReflectionHelper {
} }
} }
} }
// Already does not match // If already confirmed it cannot be a match, then returnW
if (match == null) { if (match == null) {
return null; return null;
} }
@ -138,9 +132,7 @@ public class ReflectionHelper {
// that is a match, the caller has already built the array // that is a match, the caller has already built the array
if (suppliedArgTypes.length == expectedArgTypes.length if (suppliedArgTypes.length == expectedArgTypes.length
&& expectedArgTypes[expectedArgTypes.length - 1] == suppliedArgTypes[suppliedArgTypes.length - 1]) { && expectedArgTypes[expectedArgTypes.length - 1] == suppliedArgTypes[suppliedArgTypes.length - 1]) {
} else { } else {
// Now... we have the final argument in the method we are checking as a match and we have 0 or more other // Now... we have the final argument in the method we are checking as a match and we have 0 or more other
// arguments left to pass to it. // arguments left to pass to it.
Class varargsParameterType = expectedArgTypes[expectedArgTypes.length - 1].getComponentType(); Class varargsParameterType = expectedArgTypes[expectedArgTypes.length - 1].getComponentType();
@ -185,7 +177,7 @@ public class ReflectionHelper {
} }
} }
static void convertArguments(Class[] parameterTypes, boolean isVarargs, TypeConverter converter, public static void convertArguments(Class[] parameterTypes, boolean isVarargs, TypeConverter converter,
int[] argsRequiringConversion, Object... arguments) throws EvaluationException { int[] argsRequiringConversion, Object... arguments) throws EvaluationException {
Class varargsType = null; Class varargsType = null;
if (isVarargs) { if (isVarargs) {
@ -219,8 +211,7 @@ public class ReflectionHelper {
Class<?> targetType = null; Class<?> targetType = null;
if (isVarargs && i >= (parameterTypes.length - 1)) { if (isVarargs && i >= (parameterTypes.length - 1)) {
targetType = varargsType; targetType = varargsType;
} } else {
else {
targetType = parameterTypes[i]; targetType = parameterTypes[i];
} }
if (converter == null) { if (converter == null) {
@ -284,8 +275,7 @@ public class ReflectionHelper {
} }
static enum ArgsMatchKind { public static enum ArgsMatchKind {
EXACT, CLOSE, REQUIRES_CONVERSION EXACT, CLOSE, REQUIRES_CONVERSION
} }
@ -296,7 +286,7 @@ public class ReflectionHelper {
* indicates that conversion is required for some of the arguments then the arguments that require conversion are * indicates that conversion is required for some of the arguments then the arguments that require conversion are
* listed in the argsRequiringConversion array. * listed in the argsRequiringConversion array.
*/ */
static class ArgumentsMatchInfo { public static class ArgumentsMatchInfo {
public ArgsMatchKind kind; public ArgsMatchKind kind;
@ -310,6 +300,34 @@ public class ReflectionHelper {
ArgumentsMatchInfo(ArgsMatchKind kind) { ArgumentsMatchInfo(ArgsMatchKind kind) {
this.kind = kind; this.kind = kind;
} }
public boolean isExactMatch() {
return kind==ArgsMatchKind.EXACT;
}
public boolean isCloseMatch() {
return kind==ArgsMatchKind.CLOSE;
}
public boolean isMatchRequiringConversion() {
return kind==ArgsMatchKind.REQUIRES_CONVERSION;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("ArgumentMatch: ").append(kind);
if (argsRequiringConversion!=null) {
sb.append(" (argsForConversion:");
for (int i=0;i<argsRequiringConversion.length;i++) {
if (i>0) {
sb.append(",");
}
sb.append(argsRequiringConversion[i]);
}
sb.append(")");
}
return sb.toString();
}
} }
} }

2
org.springframework.expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java

@ -94,7 +94,7 @@ public class EvaluationTests extends ExpressionTestCase {
public void testPropertyField01() { public void testPropertyField01() {
evaluate("name", "Nikola Tesla", String.class, false); evaluate("name", "Nikola Tesla", String.class, false);
// not writable because (1) name is private (2) there is no setter, only a getter // not writable because (1) name is private (2) there is no setter, only a getter
evaluateAndCheckError("madeup", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 0, "madeup", evaluateAndCheckError("madeup", SpelMessages.PROPERTY_OR_FIELD_NOT_READABLE, 0, "madeup",
"org.springframework.expression.spel.testresources.Inventor"); "org.springframework.expression.spel.testresources.Inventor");
} }

7
org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java

@ -203,9 +203,8 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
try { try {
expr.setValue(ctx, Color.blue); expr.setValue(ctx, Color.blue);
fail("Should not be allowed to set oranges to be blue !"); fail("Should not be allowed to set oranges to be blue !");
} } catch (SpelException ee) {
catch (SpelException ee) { assertEquals(ee.getMessageUnformatted(), SpelMessages.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
assertEquals(ee.getMessageUnformatted(), SpelMessages.PROPERTY_OR_FIELD_SETTER_NOT_FOUND);
} }
} }
@ -225,7 +224,7 @@ public class ExpressionLanguageScenarioTests extends ExpressionTestCase {
fail("Should not be allowed to set peas to be blue !"); fail("Should not be allowed to set peas to be blue !");
} }
catch (SpelException ee) { catch (SpelException ee) {
assertEquals(ee.getMessageUnformatted(), SpelMessages.PROPERTY_OR_FIELD_SETTER_NOT_FOUND); assertEquals(ee.getMessageUnformatted(), SpelMessages.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
} }
} }

102
org.springframework.expression/src/test/java/org/springframework/expression/spel/HelperTests.java

@ -17,9 +17,15 @@ package org.springframework.expression.spel;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.springframework.expression.ParseException; import org.springframework.expression.ParseException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.ast.FormatHelper; import org.springframework.expression.spel.ast.FormatHelper;
import org.springframework.expression.spel.support.ReflectionHelper;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.expression.spel.support.ReflectionHelper.ArgsMatchKind;
/** /**
* Tests for any helper code. * Tests for any helper code.
@ -71,4 +77,100 @@ public class HelperTests extends ExpressionTestCase {
assertTrue(s.indexOf("===> Expression '3+4+5+6+7-2' - AST start")!=-1); assertTrue(s.indexOf("===> Expression '3+4+5+6+7-2' - AST start")!=-1);
assertTrue(s.indexOf(" OperatorPlus value:((((3 + 4) + 5) + 6) + 7) #children:2")!=-1); assertTrue(s.indexOf(" OperatorPlus value:((((3 + 4) + 5) + 6) + 7) #children:2")!=-1);
} }
public void testTypedValue() {
TypedValue tValue = new TypedValue("hello");
assertEquals(String.class,tValue.getTypeDescriptor().getType());
assertEquals("TypedValue: hello of type java.lang.String",tValue.toString());
}
public void testReflectionHelperCompareArguments_ExactMatching() {
StandardTypeConverter typeConverter = new StandardTypeConverter();
// Calling foo(String) with (String) is exact match
checkMatch(new Class[]{String.class},new Class[]{String.class},typeConverter,ArgsMatchKind.EXACT);
// Calling foo(String,Integer) with (String,Integer) is exact match
checkMatch(new Class[]{String.class,Integer.class},new Class[]{String.class,Integer.class},typeConverter,ArgsMatchKind.EXACT);
}
public void testReflectionHelperCompareArguments_Varargs_ExactMatching() {
StandardTypeConverter tc = new StandardTypeConverter();
// Calling foo(String) with (String) is exact match
checkMatch(new Class[]{String.class},new Class[]{String.class},tc,ArgsMatchKind.EXACT);
}
public void testReflectionHelperCompareArguments_CloseMatching() {
StandardTypeConverter typeConverter = new StandardTypeConverter();
// Calling foo(List) with (ArrayList) is close match (no conversion required)
checkMatch(new Class[]{ArrayList.class},new Class[]{List.class},typeConverter,ArgsMatchKind.CLOSE);
// Passing (Sub,String) on call to foo(Super,String) is close match
checkMatch(new Class[]{Sub.class,String.class},new Class[]{Super.class,String.class},typeConverter,ArgsMatchKind.CLOSE);
// Passing (String,Sub) on call to foo(String,Super) is close match
checkMatch(new Class[]{String.class,Sub.class},new Class[]{String.class,Super.class},typeConverter,ArgsMatchKind.CLOSE);
}
public void testReflectionHelperCompareArguments_RequiresConversionMatching() {
StandardTypeConverter typeConverter = new StandardTypeConverter();
// Calling foo(String,int) with (String,Integer) requires boxing conversion of argument one
checkMatch(new Class[]{String.class,Integer.TYPE},new Class[]{String.class,Integer.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,1);
// Passing (int,String) on call to foo(Integer,String) requires boxing conversion of argument zero
checkMatch(new Class[]{Integer.TYPE,String.class},new Class[]{Integer.class, String.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,0);
// Passing (int,Sub) on call to foo(Integer,Super) requires boxing conversion of argument zero
checkMatch(new Class[]{Integer.TYPE,Sub.class},new Class[]{Integer.class, Super.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,0);
// Passing (int,Sub,boolean) on call to foo(Integer,Super,Boolean) requires boxing conversion of arguments zero and two
checkMatch(new Class[]{Integer.TYPE,Sub.class,Boolean.TYPE},new Class[]{Integer.class, Super.class,Boolean.class},typeConverter,ArgsMatchKind.REQUIRES_CONVERSION,0,2);
}
public void testReflectionHelperCompareArguments_NotAMatch() {
StandardTypeConverter typeConverter = new StandardTypeConverter();
// Passing (Super,String) on call to foo(Sub,String) is not a match
checkMatch(new Class[]{Super.class,String.class},new Class[]{Sub.class,String.class},typeConverter,null);
}
static class Super {
}
static class Sub extends Super {
}
// ---
/**
* Used to validate the match returned from a compareArguments call.
*/
private void checkMatch(Class[] inputTypes, Class[] expectedTypes, StandardTypeConverter typeConverter,ArgsMatchKind expectedMatchKind,int... argsForConversion) {
ReflectionHelper.ArgumentsMatchInfo matchInfo = ReflectionHelper.compareArguments(expectedTypes, inputTypes, typeConverter);
if (expectedMatchKind==null) {
assertNull("Did not expect them to match in any way", matchInfo);
} else {
assertNotNull("Should not be a null match", matchInfo);
}
if (expectedMatchKind==ArgsMatchKind.EXACT) {
assertTrue(matchInfo.isExactMatch());
assertNull(matchInfo.argsRequiringConversion);
} else if (expectedMatchKind==ArgsMatchKind.CLOSE) {
assertTrue(matchInfo.isCloseMatch());
assertNull(matchInfo.argsRequiringConversion);
} else if (expectedMatchKind==ArgsMatchKind.REQUIRES_CONVERSION) {
assertTrue("expected to be a match requiring conversion, but was "+matchInfo,matchInfo.isMatchRequiringConversion());
if (argsForConversion==null) {
fail("there are arguments that need conversion");
}
assertEquals("The array of args that need conversion is different length to that expected",argsForConversion.length, matchInfo.argsRequiringConversion.length);
for (int a=0;a<argsForConversion.length;a++) {
assertEquals(argsForConversion[a],matchInfo.argsRequiringConversion[a]);
}
}
}
} }

2
org.springframework.expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java

@ -87,7 +87,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
} }
public void testInvocationOnNullContextObject() { public void testInvocationOnNullContextObject() {
evaluateAndCheckError("null.toString()",SpelMessages.ATTEMPTED_METHOD_CALL_ON_NULL_CONTEXT_OBJECT); evaluateAndCheckError("null.toString()",SpelMessages.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED);
} }
} }

35
org.springframework.expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java

@ -47,10 +47,41 @@ public class PropertyAccessTests extends ExpressionTestCase {
public void testNonExistentPropertiesAndMethods() { public void testNonExistentPropertiesAndMethods() {
// madeup does not exist as a property // madeup does not exist as a property
evaluateAndCheckError("madeup", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 0); evaluateAndCheckError("madeup", SpelMessages.PROPERTY_OR_FIELD_NOT_READABLE, 0);
// name is ok but foobar does not exist: // name is ok but foobar does not exist:
evaluateAndCheckError("name.foobar", SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, 5); evaluateAndCheckError("name.foobar", SpelMessages.PROPERTY_OR_FIELD_NOT_READABLE, 5);
}
/**
* The standard reflection resolver cannot find properties on null objects but some
* supplied resolver might be able to - so null shouldn't crash the reflection resolver.
*/
public void testAccessingOnNullObject() throws Exception {
SpelExpression expr = (SpelExpression)parser.parseExpression("madeup");
EvaluationContext context = new StandardEvaluationContext(null);
try {
expr.getValue(context);
fail("Should have failed - default property resolver cannot resolve on null");
} catch (Exception e) {
checkException(e,SpelMessages.PROPERTY_OR_FIELD_NOT_READABLE_ON_NULL);
}
assertFalse(expr.isWritable(context));
try {
expr.setValue(context,"abc");
fail("Should have failed - default property resolver cannot resolve on null");
} catch (Exception e) {
checkException(e,SpelMessages.PROPERTY_OR_FIELD_NOT_WRITABLE_ON_NULL);
}
}
private void checkException(Exception e, SpelMessages expectedMessage) {
if (e instanceof SpelException) {
SpelMessages sm = ((SpelException)e).getMessageUnformatted();
assertEquals("Expected exception type did not occur",expectedMessage,sm);
} else {
fail("Should be a SpelException "+e);
}
} }
// Adding a new property accessor just for a particular type // Adding a new property accessor just for a particular type

Loading…
Cancel
Save