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 1c5b00a8562..7f5265cddb4 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 05c7adede23..5696d113585 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 aae9ebcbc12..1325e9c9ceb 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 3553dd4e4df..2d60fe59f43 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 48c0f809f9f..6673cfcddea 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 218846a0948..1f75ea90c75 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 4ddc1d42ebe..5961cba5f5e 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; @@ -60,7 +58,7 @@ public class ReflectionHelper { /* || isWidenableTo(expectedArg, suppliedArg) */) { if (match != ArgsMatchKind.REQUIRES_CONVERSION) { match = ArgsMatchKind.CLOSE; - } + } } else if (typeConverter.canConvert(suppliedArg, expectedArg)) { if (argsRequiringConversion == null) { argsRequiringConversion = new ArrayList(); @@ -74,16 +72,14 @@ public class ReflectionHelper { } if (match == null) { return null; - } - else { + } 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 { + } else { 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 * 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 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 + * @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 - for (int i = 0; i < expectedArgTypes.length - 1 && match != null; i++) { + // 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) { @@ -117,8 +112,7 @@ public class ReflectionHelper { if (match != ArgsMatchKind.REQUIRES_CONVERSION) { match = ArgsMatchKind.CLOSE; } - } - else if (typeConverter.canConvert(suppliedArg, expectedArg)) { + } else if (typeConverter.canConvert(suppliedArg, expectedArg)) { if (argsRequiringConversion == null) { argsRequiringConversion = new ArrayList(); } @@ -129,7 +123,7 @@ public class ReflectionHelper { } } } - // 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 1619fafa4c5..8cbdd1fecb8 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 4a12d0a1947..e84f8af3d52 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 596f304ea04..33f827ca68a 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