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 1c5b00a856..7f5265cddb 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 @@ -30,8 +30,7 @@ import java.text.MessageFormat; * EL1004E: (pos 34): Type cannot be found 'String' * * - * 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. + * 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; diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/antlr/SpelAntlrExpressionParser.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/antlr/SpelAntlrExpressionParser.java index 05c7adede2..5696d11358 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/antlr/SpelAntlrExpressionParser.java +++ b/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.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); 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 aae9ebcbc1..1325e9c9ce 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 @@ -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(); 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 3553dd4e4d..2d60fe59f4 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 @@ -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(); 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 48c0f809f9..6673cfcdde 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 @@ -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 resolversToTry = getPropertyAccessorsToTry( - (contextObject instanceof Class) ? ((Class) contextObject) : contextObject.getClass(), state); + + List resolversToTry = getPropertyAccessorsToTry(getObjectClass(contextObject),state); + if (resolversToTry != null) { for (PropertyAccessor pfResolver : resolversToTry) { try { diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 218846a094..1f75ea90c7 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/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 { if (expressionState != null) { return getValueInternal(expressionState).getValue(); - } - else { + } else { return getValue(new ExpressionState(new StandardEvaluationContext())); } } diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java index 4ddc1d42eb..5961cba5f5 100644 --- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/ReflectionHelper.java +++ b/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 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(); + } + 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 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 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(); - } - 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;i0) { + sb.append(","); + } + sb.append(argsRequiringConversion[i]); + } + sb.append(")"); + } + return sb.toString(); + } } } 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 1619fafa4c..8cbdd1fecb 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 @@ -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"); } diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java index 4a12d0a194..e84f8af3d5 100644 --- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java +++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionLanguageScenarioTests.java @@ -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); } } 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 596f304ea0..33f827ca68 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 @@ -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