First drop of SPEL

This commit is contained in:
Andy Clement
2008-08-12 16:25:32 +00:00
parent d8743000a0
commit a2401ea10f
6 changed files with 770 additions and 0 deletions

View File

@@ -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<VariableScope> environment = new Stack<VariableScope>();
private final Stack<Object> contextObjects = new Stack<Object>();
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<String, Object> 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<PropertyAccessor> getPropertyAccessors() {
return relatedContext.getPropertyAccessors();
}
public EvaluationContext getEvaluationContext() {
return relatedContext;
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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.
* <p>
* When a message is formatted, it will have this kind of form
*
* <pre><code>
* EL1004E: (pos 34): Type cannot be found 'String'
* </pre></code> The prefix captures the code and the error kind, whilst the position is included if it is known and the
* message has had all relevant inserts applied to it.
*
* @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();
}
}

View File

@@ -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 + " ");
}
}
}
}