More tests. First pass on messages review.

This commit is contained in:
Andy Clement 2009-04-12 16:45:00 +00:00
parent f6ecffdba7
commit af6457a67d
12 changed files with 310 additions and 175 deletions

View File

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

View File

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

View File

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

View File

@ -55,7 +55,7 @@ public class MethodReference extends SpelNodeImpl {
arguments[i] = getChild(i).getValueInternal(state).getValue();
}
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)));
}
@ -105,7 +105,7 @@ public class MethodReference extends SpelNodeImpl {
return sb.toString();
}
protected MethodExecutor findAccessorForMethod(String name, Class<?>[] argumentTypes, ExpressionState state)
private MethodExecutor findAccessorForMethod(String name, Class<?>[] argumentTypes, ExpressionState state)
throws SpelException {
TypedValue context = state.getActiveContextObject();

View File

@ -111,8 +111,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
throw new SpelException(ae, SpelMessages.EXCEPTION_DURING_PROPERTY_READ, name, ae.getMessage());
}
}
throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_NOT_FOUND, name,
FormatHelper.formatClassNameForMessage(contextObjectClass));
if (contextObject.getValue() == null) {
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 {
@ -149,18 +153,20 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
name, ae.getMessage());
}
}
throw new SpelException(SpelMessages.PROPERTY_OR_FIELD_SETTER_NOT_FOUND, name, FormatHelper
.formatClassNameForMessage(contextObjectClass));
if (contextObject.getValue()==null) {
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 {
Object contextObject = state.getActiveContextObject().getValue();
EvaluationContext eContext = state.getEvaluationContext();
if (contextObject == null) {
throw new SpelException(SpelMessages.ATTEMPTED_PROPERTY_FIELD_REF_ON_NULL_CONTEXT_OBJECT, name);
}
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(
(contextObject instanceof Class) ? ((Class<?>) contextObject) : contextObject.getClass(), state);
List<PropertyAccessor> resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state);
if (resolversToTry != null) {
for (PropertyAccessor pfResolver : resolversToTry) {
try {

View File

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

View File

@ -43,11 +43,9 @@ public class ReflectionHelper {
* @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 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
*/
static ArgumentsMatchInfo compareArguments(
public static ArgumentsMatchInfo compareArguments(
Class[] expectedArgTypes, Class[] suppliedArgTypes, TypeConverter typeConverter) {
ArgsMatchKind match = ArgsMatchKind.EXACT;
@ -58,6 +56,59 @@ public class ReflectionHelper {
if (expectedArg != suppliedArg) {
if (ClassUtils.isAssignable(expectedArg, suppliedArg)
/* || isWidenableTo(expectedArg, suppliedArg) */) {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE;
}
} else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
if (argsRequiringConversion == null) {
argsRequiringConversion = new ArrayList<Integer>();
}
argsRequiringConversion.add(i);
match = ArgsMatchKind.REQUIRES_CONVERSION;
} else {
match = null;
}
}
}
if (match == null) {
return null;
} else {
if (match == ArgsMatchKind.REQUIRES_CONVERSION) {
int[] argsArray = new int[argsRequiringConversion.size()];
for (int i = 0; i < argsRequiringConversion.size(); i++) {
argsArray[i] = argsRequiringConversion.get(i);
}
return new ArgumentsMatchInfo(match, argsArray);
} else {
return new ArgumentsMatchInfo(match);
}
}
}
/**
* 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
* 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 suppliedArgTypes the array of types that are being supplied at the point of invocation
* @param typeConverter a registered type converter
* @return a MatchInfo object indicating what kind of match it was or null if it was not a match
*/
static ArgumentsMatchInfo compareArgumentsVarargs(
Class[] expectedArgTypes, Class[] suppliedArgTypes, TypeConverter typeConverter) {
ArgsMatchKind match = ArgsMatchKind.EXACT;
List<Integer> argsRequiringConversion = null;
// Check up until the varargs argument:
// Deal with the arguments up to 'expected number' - 1 (that is everything but the varargs argument)
int argCountUpToVarargs = expectedArgTypes.length-1;
for (int i = 0; i < argCountUpToVarargs && match != null; i++) {
Class suppliedArg = suppliedArgTypes[i];
Class expectedArg = expectedArgTypes[i];
if (expectedArg != suppliedArg) {
if (expectedArg.isAssignableFrom(suppliedArg) || ClassUtils.isAssignableValue(expectedArg, suppliedArg)) {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE;
}
@ -72,64 +123,7 @@ public class ReflectionHelper {
}
}
}
if (match == null) {
return null;
}
else {
if (match == ArgsMatchKind.REQUIRES_CONVERSION) {
int[] argsArray = new int[argsRequiringConversion.size()];
for (int i = 0; i < argsRequiringConversion.size(); i++) {
argsArray[i] = argsRequiringConversion.get(i);
}
return new ArgumentsMatchInfo(match, argsArray);
}
else {
return new ArgumentsMatchInfo(match);
}
}
}
/**
* 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
* type by the converter. This variant of compareArguments allows for a varargs match.
* @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 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
*/
static ArgumentsMatchInfo compareArgumentsVarargs(
Class[] expectedArgTypes, Class[] suppliedArgTypes, TypeConverter typeConverter) {
ArgsMatchKind match = ArgsMatchKind.EXACT;
List<Integer> argsRequiringConversion = null;
// Check up until the varargs argument:
// Deal with the arguments up to 'expected number' - 1
for (int i = 0; i < expectedArgTypes.length - 1 && match != null; i++) {
Class suppliedArg = suppliedArgTypes[i];
Class expectedArg = expectedArgTypes[i];
if (expectedArg != suppliedArg) {
if (expectedArg.isAssignableFrom(suppliedArg) || ClassUtils.isAssignableValue(expectedArg, suppliedArg)) {
if (match != ArgsMatchKind.REQUIRES_CONVERSION) {
match = ArgsMatchKind.CLOSE;
}
}
else if (typeConverter.canConvert(suppliedArg, expectedArg)) {
if (argsRequiringConversion == null) {
argsRequiringConversion = new ArrayList<Integer>();
}
argsRequiringConversion.add(i);
match = ArgsMatchKind.REQUIRES_CONVERSION;
} else {
match = null;
}
}
}
// Already does not match
// If already confirmed it cannot be a match, then returnW
if (match == null) {
return null;
}
@ -138,9 +132,7 @@ public class ReflectionHelper {
// that is a match, the caller has already built the array
if (suppliedArgTypes.length == expectedArgTypes.length
&& expectedArgTypes[expectedArgTypes.length - 1] == suppliedArgTypes[suppliedArgTypes.length - 1]) {
} else {
// 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.
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 {
Class varargsType = null;
if (isVarargs) {
@ -219,8 +211,7 @@ public class ReflectionHelper {
Class<?> targetType = null;
if (isVarargs && i >= (parameterTypes.length - 1)) {
targetType = varargsType;
}
else {
} else {
targetType = parameterTypes[i];
}
if (converter == null) {
@ -284,8 +275,7 @@ public class ReflectionHelper {
}
static enum ArgsMatchKind {
public static enum ArgsMatchKind {
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
* listed in the argsRequiringConversion array.
*/
static class ArgumentsMatchInfo {
public static class ArgumentsMatchInfo {
public ArgsMatchKind kind;
@ -310,6 +300,34 @@ public class ReflectionHelper {
ArgumentsMatchInfo(ArgsMatchKind 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();
}
}
}

View File

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

View File

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

View File

@ -17,9 +17,15 @@ package org.springframework.expression.spel;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.springframework.expression.ParseException;
import org.springframework.expression.TypedValue;
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.
@ -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(" 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]);
}
}
}
}

View File

@ -87,7 +87,7 @@ public class MethodInvocationTests extends ExpressionTestCase {
}
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);
}
}

View File

@ -47,10 +47,41 @@ public class PropertyAccessTests extends ExpressionTestCase {
public void testNonExistentPropertiesAndMethods() {
// 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:
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