From a2401ea10f3594ea90ab07070180febe152f061d Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Tue, 12 Aug 2008 16:25:32 +0000 Subject: [PATCH] First drop of SPEL --- .../expression/spel/ExpressionState.java | 196 ++++++++++++++++++ .../expression/spel/SpelException.java | 108 ++++++++++ .../expression/spel/SpelExpression.java | 163 +++++++++++++++ .../expression/spel/SpelExpressionParser.java | 88 ++++++++ .../expression/spel/SpelMessages.java | 154 ++++++++++++++ .../expression/spel/SpelUtilities.java | 61 ++++++ 6 files changed, 770 insertions(+) create mode 100644 org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java create mode 100644 org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelException.java create mode 100644 org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpression.java create mode 100644 org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParser.java create mode 100644 org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java create mode 100644 org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelUtilities.java diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java new file mode 100644 index 0000000000..e742ce4a0c --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -0,0 +1,196 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression.spel; + +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Operation; +import org.springframework.expression.OperatorOverloader; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypeComparator; +import org.springframework.expression.TypeUtils; +import org.springframework.expression.spel.internal.VariableScope; + +/** + * An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other + * expressions but it gives a place to hold local variables and for component expressions in a compound expression to + * communicate state. This is in contrast to the EvaluationContext, which is shared amongst expression evaluations, and + * any changes to it will be seen by other expressions or any code that chooses to ask questions of the context. + * + * It also acts as a place for to define common utility routines that the various Ast nodes might need. + * + * @author Andy Clement + */ +public class ExpressionState { + + private EvaluationContext relatedContext; + + private final Stack environment = new Stack(); + + private final Stack contextObjects = new Stack(); + + public ExpressionState(EvaluationContext context) { + this.relatedContext = context; + createEnvironment(); + } + + public ExpressionState() { + createEnvironment(); + } + + private void createEnvironment() { + environment.add(new VariableScope()); // create an empty top level VariableScope + } + + /** + * The active context object is what unqualified references to properties/etc are resolved against. + */ + public Object getActiveContextObject() { + if (contextObjects.isEmpty()) { + return relatedContext.getRootContextObject(); + } + return contextObjects.peek(); + } + + public void pushActiveContextObject(Object obj) { + contextObjects.push(obj); + } + + public void popActiveContextObject() { + contextObjects.pop(); + } + + public Object getRootContextObject() { + return relatedContext.getRootContextObject(); + } + + public Object lookupReference(Object contextName, Object objectName) throws EvaluationException { + return relatedContext.lookupReference(contextName, objectName); + } + + public TypeUtils getTypeUtilities() { + return relatedContext.getTypeUtils(); + } + + public TypeComparator getTypeComparator() { + return relatedContext.getTypeUtils().getTypeComparator(); + } + + public Class findType(String type) throws EvaluationException { + return getTypeUtilities().getTypeLocator().findType(type); + } + + public boolean toBoolean(Object value) throws EvaluationException { + // TODO cache TypeConverter when it is set/changed? + return ((Boolean) getTypeUtilities().getTypeConverter().convertValue(value, Boolean.TYPE)).booleanValue(); + } + + public char toCharacter(Object value) throws EvaluationException { + return ((Character) getTypeUtilities().getTypeConverter().convertValue(value, Character.TYPE)).charValue(); + } + + public short toShort(Object value) throws EvaluationException { + return ((Short) getTypeUtilities().getTypeConverter().convertValue(value, Short.TYPE)).shortValue(); + } + + public int toInteger(Object value) throws EvaluationException { + return ((Integer) getTypeUtilities().getTypeConverter().convertValue(value, Integer.TYPE)).intValue(); + } + + public double toDouble(Object value) throws EvaluationException { + return ((Double) getTypeUtilities().getTypeConverter().convertValue(value, Double.TYPE)).doubleValue(); + } + + public float toFloat(Object value) throws EvaluationException { + return ((Float) getTypeUtilities().getTypeConverter().convertValue(value, Float.TYPE)).floatValue(); + } + + public long toLong(Object value) throws EvaluationException { + return ((Long) getTypeUtilities().getTypeConverter().convertValue(value, Long.TYPE)).longValue(); + } + + public byte toByte(Object value) throws EvaluationException { + return ((Byte) getTypeUtilities().getTypeConverter().convertValue(value, Byte.TYPE)).byteValue(); + } + + public void setVariable(String name, Object value) { + relatedContext.setVariable(name, value); + } + + public Object lookupVariable(String name) { + return relatedContext.lookupVariable(name); + } + + /** + * A new scope is entered when a function is invoked + */ + public void enterScope(Map argMap) { + environment.push(new VariableScope(argMap)); + } + + public void enterScope(String name, Object value) { + environment.push(new VariableScope(name, value)); + } + + public void exitScope() { + environment.pop(); + } + + public void setLocalVariable(String name, Object value) { + environment.peek().setVariable(name, value); + } + + public Object lookupLocalVariable(String name) { + int scopeNumber = environment.size() - 1; + for (int i = scopeNumber; i >= 0; i--) { + if (environment.get(i).definesVariable(name)) { + return environment.get(i).lookupVariable(name); + } + } + return null; + } + + public Object operate(Operation op, Object left, Object right) throws SpelException { + OperatorOverloader overloader = relatedContext.getTypeUtils().getOperatorOverloader(); + try { + if (overloader != null && overloader.overridesOperation(op, left, right)) { + return overloader.operate(op, left, right); + } else { + throw new SpelException(SpelMessages.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, left, right); + } + } catch (EvaluationException e) { + if (e instanceof SpelException) { + throw (SpelException) e; + } else { + throw new SpelException(e, SpelMessages.UNEXPECTED_PROBLEM_INVOKING_OPERATOR, op, left, right, e + .getMessage()); + } + } + } + + public List getPropertyAccessors() { + return relatedContext.getPropertyAccessors(); + } + + public EvaluationContext getEvaluationContext() { + return relatedContext; + } + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelException.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelException.java new file mode 100644 index 0000000000..8ae54f3645 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelException.java @@ -0,0 +1,108 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression.spel; + +import org.springframework.expression.EvaluationException; + +/** + * Root exception for Spring EL related exceptions. Rather than holding a hard coded string indicating the problem, it + * records a message key and the inserts for the message. See {@link SpelMessages} for the list of all possible messages + * that can occur. + * + * @author Andy Clement + * + */ +public class SpelException extends EvaluationException { + + private SpelMessages message; + private int position = -1; + private Object[] inserts; + + public SpelException(int position, Throwable cause, SpelMessages message, Object... inserts) { + super(cause); + this.position = position; + this.message = message; + this.inserts = inserts; + } + + public SpelException(Throwable cause, SpelMessages message, Object... inserts) { + super(cause); + this.message = message; + this.inserts = inserts; + } + + public SpelException(int position, SpelMessages message, Object... inserts) { + super((Throwable)null); + this.position = position; + this.message = message; + this.inserts = inserts; + } + + public SpelException(SpelMessages message, Object... inserts) { + super((Throwable)null); + this.message = message; + this.inserts = inserts; + } + + public SpelException(String expressionString, int position, Throwable cause, SpelMessages message, Object... inserts) { + super(expressionString, cause); + this.position = position; + this.message = message; + this.inserts = inserts; + } + + /** + * @return a formatted message with inserts applied + */ + @Override + public String getMessage() { + if (message != null) + return message.formatMessage(position, inserts); + else + return super.getMessage(); + } + + /** + * @return the position within the expression that gave rise to the exception (or -1 if unknown) + */ + public int getPosition() { + return this.position; + } + + /** + * @return the unformatted message + */ + public SpelMessages getMessageUnformatted() { + return this.message; + } + + /** + * Set the position in the related expression which gave rise to this exception. + * + * @param position the position in the expression that gave rise to the exception + */ + public void setPosition(int position) { + this.position = position; + } + + /** + * @return the message inserts + */ + public Object[] getInserts() { + return inserts; + } + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpression.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpression.java new file mode 100644 index 0000000000..348e8df3d2 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpression.java @@ -0,0 +1,163 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression.spel; + +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.spel.ast.SpelNode; +import org.springframework.expression.spel.standard.StandardEvaluationContext; + +// TODO 3 Do we need more getValue() options - for example with just a root object or with a set of variables? +// (these things are currently captured in the Context) + +/** + * A SpelExpressions represents a parsed (valid) expression that is ready to be evaluated in a specified context. An + * expression can be evaluated standalone or in a specified context. During expression evaluation the context may be + * asked to resolve references to types, beans, properties, methods. + * + * @author Andy Clement + * + */ +public class SpelExpression implements Expression { + private final String expression; + public final SpelNode ast; + + /** + * Construct an expression, only used by the parser. + * + * @param expression + * @param ast + */ + SpelExpression(String expression, SpelNode ast) { + this.expression = expression; + this.ast = ast; + } + + /** + * @return the expression string that was parsed to create this expression instance + */ + public String getExpressionString() { + return expression; + } + + /** + * Evaluate the expression in a default context that knows nothing (and therefore cannot resolve references to + * properties/etc). Only useful for trivial expressions like '3+4'. + * + * @return the value of the expression + * @throws SpelException if there is a problem with evaluation of the expression + */ + public Object getValue() throws EvaluationException { + EvaluationContext eContext = new StandardEvaluationContext(); + return ast.getValue(new ExpressionState(eContext)); + } + + // public Class getValueType() throws ELException { + // return ast.getValueType(new ExpressionState()); + // } + + /** + * Evaluate the expression in a specified context which can resolve references to properties, methods, types, etc. + * The {@link StandardEvaluationContext} can be sub classed and overridden where necessary, rather than implementing + * the entire EvaluationContext interface. + * + * @param context the context in which to evaluate the expression + * @return the value of the expression + * @throws SpelException if there is a problem with evaluation of the expression. + */ + public Object getValue(EvaluationContext context) throws EvaluationException { + return ast.getValue(new ExpressionState(context)); + } + + /** + * Evaluate the expression in a specified context which can resolve references to properties, methods, types, etc - + * the type of the evaluation result is expected to be of a particular class and an exception will be thrown if it + * is not and cannot be converted to that type. The {@link StandardEvaluationContext} can be sub classed and + * overridden where necessary, rather than implementing the entire EvaluationContext interface. + * + * @param context the context in which to evaluate the expression + * @param expectedResultType the class the caller would like the result to be + * @return the value of the expression + * @throws SpelException if there is a problem with evaluation of the expression. + */ + public Object getValue(EvaluationContext context, Class expectedResultType) throws EvaluationException { + Object result = ast.getValue(new ExpressionState(context)); + + if (result != null && expectedResultType != null) { + Class resultType = result.getClass(); + if (expectedResultType.isAssignableFrom(resultType)) { + return result; + } + // Attempt conversion to the requested type, may throw an exception + return context.getTypeUtils().getTypeConverter().convertValue(result, expectedResultType); + } + return result; + } + + /** + * Evaluate an expression and set the result to the specified value. This only makes sense when working with the + * expression against a context. + * + * @param context the context in which to evaluate the expression + * @param value the new value + * @return the previous value + * @throws SpelException if there is a problem with evaluation of the expression. + */ + public void setValue(EvaluationContext context, Object value) throws EvaluationException { + ast.setValue(new ExpressionState(context), value); + } + + /** + * Determine if an expression evaluates to an value that can be set with a value (for example, a property). + * + * @param context the context in which to evaluate the expression + * @return true if the expression supports setValue() + * @throws SpelException if there is a problem with evaluation of the expression. + */ + public boolean isWritable(EvaluationContext context) throws EvaluationException { + return ast.isWritable(new ExpressionState(context)); + } + + /** + * @return return the Abstract Syntax Tree for the expression + */ + public SpelNode getAST() { + return ast; + } + + /** + * Produce a string representation of the Abstract Syntax Tree for the expression, this should ideally look like the + * input expression, but properly formatted since any unnecessary whitespace will have been discarded during the + * parse of the expression. + * + * @return the string representation of the AST + */ + public String toStringAST() { + return ast.toStringAST(); + } + + public Class getValueType(EvaluationContext context) throws EvaluationException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + public Object getValue(Class expectedResultType) throws EvaluationException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Auto-generated method stub"); + } + +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParser.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParser.java new file mode 100644 index 0000000000..73ba1e72a6 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelExpressionParser.java @@ -0,0 +1,88 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression.spel; + +import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.RecognitionException; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.ParseException; +import org.springframework.expression.ParserContext; +import org.springframework.expression.common.DefaultNonTemplateParserContext; +import org.springframework.expression.common.TemplateAwareExpressionParser; +import org.springframework.expression.spel.ast.SpelNode; +import org.springframework.expression.spel.generated.SpringExpressionsLexer; +import org.springframework.expression.spel.generated.SpringExpressionsParser.expr_return; +import org.springframework.expression.spel.internal.InternalELException; +import org.springframework.expression.spel.internal.SpelTreeAdaptor; +import org.springframework.expression.spel.internal.SpringExpressionsLexerExtender; +import org.springframework.expression.spel.internal.SpringExpressionsParserExtender; + +/** + * Instances of this parser class can process Spring Expression Language format expressions. The result of parsing an + * expression is a SpelExpression instance that can be repeatedly evaluated (possibly against different evaluation + * contexts) or serialized for later evaluation. + * + * @author Andy Clement + */ +public class SpelExpressionParser extends TemplateAwareExpressionParser { + + private final SpringExpressionsLexer lexer; + private final SpringExpressionsParserExtender parser; + + /** + * Should be constructed through the SpelParserFactory + */ + public SpelExpressionParser() { + lexer = new SpringExpressionsLexerExtender(); + CommonTokenStream tokens = new CommonTokenStream(lexer); + parser = new SpringExpressionsParserExtender(tokens); + parser.setTreeAdaptor(new SpelTreeAdaptor()); + } + + /** + * Parse an expression string. + * + * @param expressionString the expression to parse + * @param context the parser context in which to perform the parse + * @return a parsed expression object + * @throws EvaluationException if the expression is invalid + */ + protected Expression doParseExpression(String expressionString, ParserContext context) + throws ParseException { + try { + lexer.setCharStream(new ANTLRStringStream(expressionString)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + parser.setTokenStream(tokens); + expr_return exprReturn = parser.expr(); + return new SpelExpression(expressionString, (SpelNode) exprReturn.getTree()); + } catch (RecognitionException re) { + ParseException exception = new ParseException(expressionString, "Recognition error at position: "+re.charPositionInLine+": "+re.getMessage(), re); + throw exception; + } catch (InternalELException e) { + SpelException wrappedException = e.getCause(); + throw new ParseException(expressionString,"Parsing problem: "+wrappedException.getMessage(),wrappedException); + } + } + + /** + * Simple override with covariance to return a nicer type + */ + public SpelExpression parseExpression(String expressionString) throws ParseException { + return (SpelExpression)super.parseExpression(expressionString, DefaultNonTemplateParserContext.INSTANCE); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..bd9ec6537d --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelMessages.java @@ -0,0 +1,154 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression.spel; + +import java.text.MessageFormat; + +/** + * Contains all the messages that can be produced by the Spring Expression Language. Each message has a kind (info, + * warn, error) and a code number. Tests can be written to expect particular code numbers rather than particular text, + * enabling the message text to more easily be modified and the tests to run successfully in different locales. + *

+ * When a message is formatted, it will have this kind of form + * + *


+ * 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. + * + * @author Andy Clement + * + */ +public enum SpelMessages { + // TODO put keys and messages into bundles for easy NLS + // TODO damn code formatter keeps messing up the layout + + 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 2 poor message + 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}''"), IS_OPERATOR_NEEDS_CLASS_OPERAND( + Kind.ERROR, 1048, "The operator 'is' 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}''"), + ; + + private Kind kind; + private int code; + private String message; + + public static enum Kind { + INFO, WARNING, ERROR + }; + + private SpelMessages(Kind kind, int code, String message) { + this.kind = kind; + this.code = code; + this.message = message; + } + + /** + * Produce a complete message including the prefix, the position (if known) and with the inserts applied to the + * message. + * + * @param pos the position, if less than zero it is ignored and not included in the message + * @param inserts the inserts to put into the formatted message + * @return a formatted message + */ + public String formatMessage(int pos, Object... inserts) { + StringBuilder formattedMessage = new StringBuilder(); + formattedMessage.append("EL").append(code); + switch (kind) { + case WARNING: + formattedMessage.append("W"); + break; + case INFO: + formattedMessage.append("I"); + break; + case ERROR: + formattedMessage.append("E"); + break; + } + formattedMessage.append(":"); + if (pos != -1) { + formattedMessage.append("(pos ").append(pos).append("): "); + } + formattedMessage.append(MessageFormat.format(this.message, inserts)); + return formattedMessage.toString(); + } +} diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelUtilities.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelUtilities.java new file mode 100644 index 0000000000..209a4e6617 --- /dev/null +++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/SpelUtilities.java @@ -0,0 +1,61 @@ +/* + * Copyright 2004-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.expression.spel; + +import java.io.PrintStream; + +import org.springframework.expression.spel.ast.SpelNode; + +/** + * Utilities for working with Spring Expressions. + * + * @author Andy Clement + * + */ +public class SpelUtilities { + + /** + * Output an indented representation of the expression syntax tree to the specified output stream. + * + * @param printStream the output stream to print into + * @param expression the expression to be displayed + */ + public static void printAbstractSyntaxTree(PrintStream printStream, SpelExpression expression) { + printStream.println("===> Expression '" + expression.getExpressionString() + "' - AST start"); + printAST(printStream, expression.getAST(), ""); + printStream.println("===> Expression '" + expression.getExpressionString() + "' - AST end"); + } + + /* + * Helper method for printing the AST with indentation + */ + private static void printAST(PrintStream out, SpelNode t, String indent) { + if (t != null) { + StringBuffer sb = new StringBuffer(); + String s = null; + if (t.getType() == -1) + s = "EOF"; + else { + s = t.getClass().getSimpleName(); + } + sb.append(indent + s + (t.getChildCount() < 2 ? "" : " #" + t.getChildCount())); + out.println(sb.toString()); + for (int i = 0; i < t.getChildCount(); i++) { + printAST(out, t.getChild(i), indent + " "); + } + } + } +}