simplification of EL parsing api

improvements to messages API
tests
This commit is contained in:
Keith Donald
2008-01-14 23:20:51 +00:00
parent 7661cb7675
commit 3e99a26b8e
10 changed files with 356 additions and 332 deletions

View File

@@ -24,33 +24,7 @@ package org.springframework.binding.expression;
public interface ExpressionParser {
/**
* Is the provided expression string an "eval" expression: meaning an expression that validates to a dynamic value,
* and not a literal expression? "Eval" expressions are normally enclosed in delimiters like #{}, where literal
* expressions are not delimited.
*
* TODO - candidate for removal in a future milestone: is this really needed?
* @param string the string
* @return true if the expression is an eval expression string, false otherwise.
*/
public boolean isEvalExpressionString(String string);
/**
* Parse the raw string into an "eval" expression string that when parsed produces a dynamic value when evaluated
* against a target object. For example, the raw expression string "person.id" might become #{person.id}. If the
* string is already an eval expression string, the string argument is returned unchanged. If the string is an
* composite expression string that mixes eval and literal expressions, a parser exception is thrown.
*
* TODO - candidate for removal in a future milestone: is this really needed?
* @param string the raw string to be transformed into a parseable eval expression string
* @return the eval expression spring
* @throws ParserException an exception occurred during parsing
*/
public String parseEvalExpressionString(String string) throws ParserException;
/**
* Parse the provided expression string, returning an expression evaluator capable of evaluating it. The expression
* string may be a literal expression string like "foo", an eval-expression string like #{foo}, or a
* composite-expression string like "foo#{foo}bar#{bar}".
* Parse the provided expression string, returning an expression evaluator capable of evaluating it.
* @param expressionString the parseable expression string; cannot be null
* @param expressionTargetType the class of target object this expression can successfully evaluate; for example,
* <code>Map.class</code> for an expression that is expected to evaluate against Maps.
@@ -58,8 +32,8 @@ public interface ExpressionParser {
* example, <code>Boolean.class</code> for an expression that is expected to get or set a boolean value. Typically
* used to facilitate type conversion by the expression evaluator; for example, if a evaluated expression equates to
* a String value 'true', with an expected Boolean result the string value could be converted to a typed Boolean
* value.
* @param expressionVariables variables providing aliases for this expression during evaluation. Optional.
* value (required).
* @param expressionVariables variables providing aliases for this expression during evaluation (optional).
* @return the evaluator for the parsed expression
* @throws ParserException an exception occurred during parsing
*/

View File

@@ -3,28 +3,25 @@ package org.springframework.binding.expression;
import org.springframework.util.Assert;
/**
* A simple, convenient alias for a more-complex expression.
*
* TODO - consider making the valueExpressionString a parsed Expression object for more flexibility.
*
* An expression variable.
* @author Keith Donald
*/
public class ExpressionVariable {
private String name;
private String valueExpressionString;
private String value;
/**
* Creates a new expression variable
* @param name the name of the variable, acting as an convenient alias
* @param valueExpressionString the complex expression to be aliased in string form
* @param value the initial value of the variable
*/
public ExpressionVariable(String name, String valueExpressionString) {
public ExpressionVariable(String name, String value) {
Assert.hasText(name, "The expression variable must be named");
Assert.hasText(valueExpressionString, "The expression value expression string is required");
Assert.hasText(value, "The expression variable value is required");
this.name = name;
this.valueExpressionString = valueExpressionString;
this.value = value;
}
/**
@@ -39,8 +36,8 @@ public class ExpressionVariable {
* Returns the expression that will be evaluated when the variable is referenced by its name in another expression.
* @return the expression value.
*/
public String getValueExpressionString() {
return valueExpressionString;
public String getValue() {
return value;
}
public boolean equals(Object o) {

View File

@@ -22,16 +22,6 @@ import org.springframework.binding.expression.ParserException;
*/
public class ELExpressionParser implements ExpressionParser {
/**
* The expression prefix.
*/
private static final String EXPRESSION_PREFIX = "#{";
/**
* The expression suffix.
*/
private static final String EXPRESSION_SUFFIX = "}";
/**
* The ExpressionFactory for constructing EL expressions
*/
@@ -55,40 +45,29 @@ public class ELExpressionParser implements ExpressionParser {
this.contextFactories.put(expressionTargetType, contextFactory);
}
public boolean isEvalExpressionString(String expressionString) {
return expressionString.startsWith(EXPRESSION_PREFIX) && expressionString.endsWith(EXPRESSION_SUFFIX);
}
public String parseEvalExpressionString(String string) {
return encloseInDelimitersIfNecessary(string);
}
public Expression parseExpression(String expressionString, Class expressionTargetType,
Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException {
ParserELContext context = new ParserELContext();
if (expectedEvaluationResultType == null) {
throw new ParserException(expressionString, "The 'expectedEvaluationResultType' argument is required; "
+ "specify Object.class if the type is unknown", new NullPointerException());
}
try {
ParserELContext context = new ParserELContext();
context.mapVariables(expressionVariables, expressionFactory);
ValueExpression expression = expressionFactory.createValueExpression(context, expressionString,
expectedEvaluationResultType);
ELContextFactory contextFactory = getContextFactory(expressionString, expressionTargetType);
ELContextFactory contextFactory = getContextFactory(expressionTargetType, expressionString);
return new ELExpression(contextFactory, expression, context.getVariableMapper());
} catch (ELException ex) {
throw new ParserException(expressionString, ex);
}
}
private String encloseInDelimitersIfNecessary(String expressionString) {
if (isEvalExpressionString(expressionString)) {
return expressionString;
} else {
return EXPRESSION_PREFIX + expressionString + EXPRESSION_SUFFIX;
}
}
private ELContextFactory getContextFactory(String expressionString, Class expressionTargetType) {
private ELContextFactory getContextFactory(Class expressionTargetType, String expressionString) {
if (!contextFactories.containsKey(expressionTargetType)) {
throw new ParserException(expressionString, new IllegalArgumentException(
"No ELContextFactory registered for expressionTargetType [" + expressionTargetType + "]"));
"No ELContextFactory registered for expressionTargetType [" + expressionTargetType + "]; "
+ "Please ensure a factory is registered for this type."));
}
return (ELContextFactory) contextFactories.get(expressionTargetType);
}
@@ -113,8 +92,7 @@ public class ELExpressionParser implements ExpressionParser {
variableMapper = new VariableMapperImpl();
for (int i = 0; i < variables.length; i++) {
ExpressionVariable var = variables[i];
ValueExpression expr = expressionFactory.createValueExpression(this,
var.getValueExpressionString(), Object.class);
ValueExpression expr = expressionFactory.createValueExpression(this, var.getValue(), Object.class);
variableMapper.setVariable(var.getName(), expr);
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.binding.expression.ognl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import ognl.Ognl;
import ognl.OgnlException;
@@ -23,6 +25,7 @@ import ognl.OgnlException;
import org.springframework.binding.expression.EvaluationAttempt;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionVariable;
import org.springframework.binding.expression.SetValueAttempt;
import org.springframework.util.Assert;
@@ -41,12 +44,18 @@ class OgnlExpression implements Expression {
*/
private Object expression;
/**
* Expression variable initial values.
*/
private ExpressionVariable[] variables;
/**
* Creates a new OGNL expression.
* @param expression the parsed expression
*/
public OgnlExpression(Object expression) {
public OgnlExpression(Object expression, ExpressionVariable[] variables) {
this.expression = expression;
this.variables = variables;
}
public int hashCode() {
@@ -66,8 +75,13 @@ class OgnlExpression implements Expression {
public Object getValue(Object target) throws EvaluationException {
Assert.notNull(target, "The target object to evaluate is required");
try {
// TODO context map
return Ognl.getValue(expression, Collections.EMPTY_MAP, target);
Map context;
if (variables != null && variables.length > 0) {
context = new HashMap(variables.length);
} else {
context = Collections.EMPTY_MAP;
}
return Ognl.getValue(expression, context, target);
} catch (OgnlException e) {
if (e.getReason() != null && e.getReason() != e) {
// unwrap the OgnlException since the actual exception is wrapped inside it

View File

@@ -21,6 +21,7 @@ import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionVariable;
import org.springframework.binding.expression.ParserException;
import org.springframework.binding.expression.support.AbstractExpressionParser;
@@ -31,9 +32,10 @@ import org.springframework.binding.expression.support.AbstractExpressionParser;
*/
public class OgnlExpressionParser extends AbstractExpressionParser {
protected Expression doParseExpression(String expressionString) throws ParserException {
protected Expression doParseExpression(String expressionString, Class expressionTargetType,
Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException {
try {
return new OgnlExpression(Ognl.parseExpression(expressionString));
return new OgnlExpression(Ognl.parseExpression(expressionString), expressionVariables);
} catch (OgnlException e) {
throw new ParserException(expressionString, e);
}

View File

@@ -80,19 +80,11 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
this.expressionSuffix = expressionSuffix;
}
public boolean isEvalExpressionString(String string) {
return string.startsWith(expressionPrefix) && string.endsWith(expressionSuffix);
}
public String parseEvalExpressionString(String string) {
return encloseInDelimitersIfNecessary(string);
}
public Expression parseExpression(String expressionString, Class expressionTargetType,
Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException {
Assert.notNull(expressionString, "The expression string to parse is required");
// TODO variables
Expression[] expressions = parseExpressions(expressionString);
Expression[] expressions = parseExpressions(expressionString, expressionTargetType,
expectedEvaluationResultType, expressionVariables);
if (expressions.length == 1) {
return expressions[0];
} else {
@@ -100,14 +92,6 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
}
}
private String encloseInDelimitersIfNecessary(String expressionString) {
if (isEvalExpressionString(expressionString)) {
return expressionString;
} else {
return expressionPrefix + expressionString + expressionSuffix;
}
}
/**
* Helper that parses given expression string using the configured parser. The expression string can contain any
* number of expressions all contained in "${...}" markers. For instance: "foo${expr0}bar${expr1}". The static
@@ -117,7 +101,8 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
* @return the parsed expressions
* @throws ParserException when the expressions cannot be parsed
*/
private Expression[] parseExpressions(String expressionString) throws ParserException {
private Expression[] parseExpressions(String expressionString, Class expressionTargetType,
Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException {
List expressions = new LinkedList();
int startIdx = 0;
while (startIdx < expressionString.length()) {
@@ -147,7 +132,8 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
+ getExpressionPrefix() + getExpressionSuffix() + "' at character " + prefixIndex, null);
} else {
String expr = expressionString.substring(prefixIndex + getExpressionPrefix().length(), suffixIndex);
expressions.add(doParseExpression(expr));
expressions.add(doParseExpression(expr, expressionTargetType, expectedEvaluationResultType,
expressionVariables));
startIdx = suffixIndex + 1;
}
} else {
@@ -165,8 +151,9 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
* Template method for parsing a filtered expression string. Subclasses should override.
* @param expressionString the expression string
* @return the parsed expression
* @throws ParserException an exception occured during parsing
* @throws ParserException an exception occurred during parsing
*/
protected abstract Expression doParseExpression(String expressionString) throws ParserException;
protected abstract Expression doParseExpression(String expressionString, Class expressionTargetType,
Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException;
}

View File

@@ -0,0 +1,173 @@
package org.springframework.binding.message;
import java.util.Locale;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
/**
* A convenient builder for building {@link MessageResolver} objects programmatically. Often used by model code such as
* validation logic to conveniently record validation messages. Supports the production of message resolvers that
* hard-code their message text, as well as message resolvers that retrieve their text from a
* {@link MessageSource message resource bundle}.
*
* Usage example:
* <p>
* <code>
* new MessageBuilder().error().source(this).code(&quot;mycode&quot;).args(new Object[] { arg1, arg2 }).defaultText(&quot;Fallback text&quot;)
* .build();
* </code>
* </p>
* @author Keith Donald
*/
public class MessageBuilder {
private Object source;
private String[] codes;
private Severity severity;
private Object[] args;
private String defaultText;
/**
* Records that the message being built is from the source provided.
* @param source the source generating the message
* @return this, for fluent API usage
*/
public MessageBuilder source(Object source) {
this.source = source;
return this;
}
/**
* Records that the message being built should have its text resolved using the code provided.
* @param code the message code
* @return this, for fluent API usage
*/
public MessageBuilder code(String code) {
codes = new String[] { code };
return this;
}
/**
* Records that the message being built should have its text resolved using the codes provided. The codes are tried
* in-order until their is a match.
* @param codes the message codes
* @return this, for fluent API usage
*/
public MessageBuilder codes(String[] codes) {
this.codes = codes;
return this;
}
/**
* Records that the message being built is an informational message.
* @return this, for fluent API usage
*/
public MessageBuilder info() {
severity = Severity.INFO;
return this;
}
/**
* Records that the message being built is a warning message.
* @return this, for fluent API usage
*/
public MessageBuilder warning() {
severity = Severity.WARNING;
return this;
}
/**
* Records that the message being built is an error message.
* @return this, for fluent API usage
*/
public MessageBuilder error() {
severity = Severity.ERROR;
return this;
}
/**
* Records that the message being built has a single argument.
* @param arg the message argument
* @return this, for fluent API usage
*/
public MessageBuilder arg(Object arg) {
this.args = new Object[] { arg };
return this;
}
/**
* Records that the message being built has arguments.
* @param args the message arguments
* @return this, for fluent API usage
*/
public MessageBuilder args(Object[] args) {
this.args = args;
return this;
}
/**
* Records the fallback text of the message being built. If the message has no codes, this will always be used as
* the text.
* @param text the default text
* @return this, for fluent API usage
*/
public MessageBuilder defaultText(String text) {
defaultText = text;
return this;
}
/**
* Builds the message that will be resolved. Called after the end of recording builder instructions.
* @return the built message resolver
*/
public MessageResolver build() {
if (severity == null) {
severity = Severity.INFO;
}
if (codes == null && defaultText == null) {
throw new IllegalArgumentException(
"A message code or the message text is required to build this message resolver");
}
return new BuiltMessageResolver(source, codes, severity, args, defaultText);
}
private static class BuiltMessageResolver implements MessageResolver, MessageSourceResolvable {
private Object source;
private String[] codes;
private Severity severity;
private Object[] args;
private String defaultText;
public BuiltMessageResolver(Object source, String[] codes, Severity severity, Object[] args, String defaultText) {
this.source = source;
this.codes = codes;
this.severity = severity;
this.args = args;
this.defaultText = defaultText;
}
public Message resolveMessage(MessageSource messageSource, Locale locale) {
return new Message(source, messageSource.getMessage(this, locale), severity);
}
// implementing MessageSourceResolver
public String[] getCodes() {
return codes;
}
public Object[] getArguments() {
return args;
}
public String getDefaultMessage() {
return defaultText;
}
}
}

View File

@@ -1,225 +0,0 @@
package org.springframework.binding.message;
import java.util.Locale;
import org.springframework.context.MessageSource;
/**
* A convenient factory for creating {@link MessageResolver} objects programmatically. Often used by model code such as
* validation logic to conveniently record validation messages. Supports the production of "text" message resolvers that
* hard-code their message text, as well as message resolvers that retrieve their text from a
* {@link MessageSource message resource bundle}.
*
* @author Keith Donald
*/
public class Messages implements MessageResolver {
private Object source;
private String code;
private Severity severity;
private Object[] args;
private String defaultText;
private Messages(Object source, String code, Severity severity, Object[] args, String defaultText) {
this.source = source;
this.code = code;
this.severity = severity;
this.args = args;
this.defaultText = defaultText;
}
public Message resolveMessage(MessageSource messageSource, Locale locale) {
if (messageSource != null && (code != null && code.length() > 0)) {
return new Message(source, getMessageText(messageSource, locale), severity);
} else {
return new Message(source, defaultText, severity);
}
}
/**
* Creates a message resolver that creates a INFO {@link Message} with the text provided.
* @param text the raw message text that will be used as-is
* @return the message resolver
*/
public static Messages text(String text) {
return new Messages(null, null, Severity.INFO, null, text);
}
/**
* Creates a message resolver that creates a {@link Message} with the text and severity provided.
* @param text the raw message text that will be used as-is
* @param severity the desired message severity
* @return the message resolver
*/
public static Messages text(String text, Severity severity) {
return new Messages(null, null, severity, null, text);
}
/**
* Creates a message resolver that creates a {@link Severity#INFO info} {@link Message message} with its text
* resolved from a message bundle by using the provided message code.
* @param code the message code
* @return the message resolver
*/
public static Messages info(String code) {
return new Messages(null, code, Severity.INFO, null, null);
}
/**
* Creates a message resolver that creates a {@link Severity#WARNING warning} {@link Message message} with its text
* resolved from a message bundle by using the provided message code.
* @param code the message code
* @return the message resolver
*/
public static Messages warning(String code) {
return new Messages(null, code, Severity.WARNING, null, null);
}
/**
* Creates a message resolver that creates a {@link Severity#ERROR error} {@link Message message} with its text
* resolved from a message bundle by using the provided message code.
* @param code the message code
* @return the message resolver
*/
public static Messages error(String code) {
return new Messages(null, code, Severity.ERROR, null, null);
}
/**
* Creates a message resolver that creates a {@link Severity#INFO info} {@link Message message} with its text
* resolved from a message bundle by using the provided message code and message arguments.
* @param code the message code
* @param args the message arguments
* @return the message resolver
*/
public static Messages info(String code, Object[] args) {
return new Messages(null, code, Severity.INFO, args, null);
}
/**
* Creates a message resolver that creates a {@link Severity#WARNING warning} {@link Message message} with its text
* resolved from a message bundle by using the provided message code and message arguments.
* @param code the message code
* @param args the message arguments
* @return the message resolver
*/
public static Messages warning(String code, Object[] args) {
return new Messages(null, code, Severity.WARNING, args, null);
}
/**
* Creates a message resolver that creates a {@link Severity#ERROR error} {@link Message message} with its text
* resolved from a message bundle by using the provided message code and message arguments.
* @param code the message code
* @param args the message arguments
* @return the message resolver
*/
public static Messages error(String code, Object[] args) {
return new Messages(null, code, Severity.ERROR, args, null);
}
/**
* Creates a message resolver that creates a {@link Severity#INFO info} {@link Message message} from the source with
* the text provided.
* @param source the source of the message
* @param text the message text
* @return the message resolver
*/
public static Messages text(Object source, String text) {
return new Messages(source, null, Severity.INFO, null, text);
}
/**
* Creates a message resolver that creates a {@link Message message} from the source with the text and severity
* provided.
* @param source the source of the message
* @param text the message text
* @param severity the message severity
* @return the message resolver
*/
public static Messages text(Object source, String text, Severity severity) {
return new Messages(source, null, severity, null, text);
}
/**
* Creates a message resolver that creates a {@link Severity#INFO info} {@link Message message} from the source with
* its text resolved from a message bundle by using the provided message code.
* @param source the source of the message
* @param code the message code
* @return the message resolver
*/
public static Messages info(Object source, String code) {
return new Messages(source, code, Severity.INFO, null, null);
}
/**
* Creates a message resolver that creates a {@link Severity#WARNING warning} {@link Message message} from the
* source with its text resolved from a message bundle by using the provided message code.
* @param source the source of the message
* @param code the message code
* @return the message resolver
*/
public static Messages warning(Object source, String code) {
return new Messages(source, code, Severity.WARNING, null, null);
}
/**
* Creates a message resolver that creates a {@link Severity#ERROR error} {@link Message message} from the source
* with its text resolved from a message bundle by using the provided message code.
* @param source the source of the message
* @param code the message code
* @return the message resolver
*/
public static Messages error(Object source, String code) {
return new Messages(source, code, Severity.ERROR, null, null);
}
/**
* Creates a message resolver that creates a {@link Severity#INFO info} {@link Message message} from the source with
* its text resolved from a message bundle by using the provided message code and message arguments.
* @param source the source of the message
* @param code the message code
* @param args the message arguments
* @return the message resolver
*/
public static Messages info(Object source, String code, Object[] args) {
return new Messages(source, code, Severity.INFO, args, null);
}
/**
* Creates a message resolver that creates a {@link Severity#WARNING warning} {@link Message message} from the
* source with its text resolved from a message bundle by using the provided message code and message arguments.
* @param source the source of the message
* @param code the message code
* @param args the message arguments
* @return the message resolver
*/
public static Messages warning(Object source, String code, Object[] args) {
return new Messages(source, code, Severity.WARNING, args, null);
}
/**
* Creates a message resolver that creates a {@link Severity#ERROR error} {@link Message message} from the source
* with its text resolved from a message bundle by using the provided message code and message arguments.
* @param source the source of the message
* @param code the message code
* @param args the message arguments
* @return the message resolver
*/
public static Messages error(Object source, String code, Object[] args) {
return new Messages(source, code, Severity.ERROR, args, null);
}
private String getMessageText(MessageSource source, Locale locale) {
if (defaultText == null) {
return source.getMessage(code, args, locale);
} else {
return source.getMessage(code, args, defaultText, locale);
}
}
}