diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/support/TextToExpression.java b/spring-binding/src/main/java/org/springframework/binding/convert/support/TextToExpression.java index 747c3d87..cad1e67b 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/support/TextToExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/support/TextToExpression.java @@ -60,6 +60,6 @@ public class TextToExpression extends AbstractConverter { protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception { String expressionString = (String) source; - return expressionParser.parseExpression(expressionString, Object.class, Object.class, null); + return expressionParser.parseExpression(expressionString, null); } } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationAttempt.java b/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationAttempt.java index 76777a2e..dc95d4a2 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationAttempt.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationAttempt.java @@ -18,44 +18,37 @@ package org.springframework.binding.expression; import org.springframework.core.style.ToStringCreator; /** - * A simple holder for information about an evaluation attempt. + * A simple holder for information about an expression evaluation attempt. * * @author Keith Donald */ public class EvaluationAttempt { - /** - * The expression that attempted to evaluate. - */ private Expression expression; - - /** - * The target object being evaluated. - */ - private Object target; + private Object context; /** * Create an evaluation attempt. * @param expression the expression that failed to evaluate - * @param target the target of the expression + * @param context the context of the expression evaluation */ - public EvaluationAttempt(Expression expression, Object target) { + public EvaluationAttempt(Expression expression, Object context) { this.expression = expression; - this.target = target; + this.context = context; } /** - * Returns the expression that attempted to evaluate. + * Returns the expression that attempted an evaluation. */ public Expression getExpression() { return expression; } /** - * Returns the target object upon which evaluation was attempted. + * Returns the context object in which expression evaluation was attempted. */ - public Object getTarget() { - return target; + public Object getContext() { + return context; } public String toString() { @@ -63,6 +56,6 @@ public class EvaluationAttempt { } protected ToStringCreator createToString(ToStringCreator creator) { - return creator.append("expression", expression).append("target", target); + return creator.append("expression", expression).append("context", context); } } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationException.java b/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationException.java index bde5e58b..14cc817a 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationException.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/EvaluationException.java @@ -36,7 +36,7 @@ public class EvaluationException extends NestedRuntimeException { */ public EvaluationException(EvaluationAttempt evaluationAttempt, Throwable cause) { super("Expression " + evaluationAttempt - + " failed - make sure the expression is evaluatable on the target object", cause); + + " failed - make sure the expression is evaluatable in the context provided", cause); this.evaluationAttempt = evaluationAttempt; } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/Expression.java b/spring-binding/src/main/java/org/springframework/binding/expression/Expression.java index e98e32d6..bf6fcb6d 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/Expression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/Expression.java @@ -24,19 +24,18 @@ package org.springframework.binding.expression; public interface Expression { /** - * Evaluate the expression encapsulated by this evaluator against the provided target object and return the result - * of the evaluation. - * @param target the target of the expression + * Evaluate this expression in the provided context and return the result of evaluation. + * @param context the context to evaluate this expression in * @return the evaluation result * @throws EvaluationException an exception occured during evaluation */ - public Object getValue(Object target) throws EvaluationException; + public Object getValue(Object context) throws EvaluationException; /** - * Evaluate this expression against the target object to set its value to the value provided. - * @param target the target object + * Set this expression in the provided context to the value provided. + * @param context the context to apply this value to * @param value the new value to be set * @throws EvaluationException an exception occurred during evaluation */ - public void setValue(Object target, Object value) throws EvaluationException; + public void setValue(Object context, Object value) throws EvaluationException; } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionParser.java index d14367cf..14f69011 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionParser.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionParser.java @@ -24,30 +24,20 @@ 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? + * Is the provided string an explicitly delimited expression this parser knows how to parse? For example, this + * method might return true if the string provided is enclosed in ${}. * @param string the string - * @return true if the expression is an eval expression string, false otherwise. + * @return true if the string is a delimited expression, false otherwise. */ - public boolean isEvalExpressionString(String string); - + public boolean isDelimitedExpression(String string); + /** * 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, - * Map.class for an expression that is expected to evaluate against Maps. - * @param expectedEvaluationResultType the class of object this expression is expected to return or set: for - * example, Boolean.class 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 (required). If the type of the evaluation result cannot be determined, use Object.class. - * @param expressionVariables variables providing aliases for this expression during evaluation (optional). + * @param expressionString the parseable expression string; cannot be null (required) + * @param context a context used to set attributes that influence expression parsing routine (optional) * @return the evaluator for the parsed expression * @throws ParserException an exception occurred during parsing */ - public Expression parseExpression(String expressionString, Class expressionTargetType, - Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException; + public Expression parseExpression(String expressionString, ParserContext context) throws ParserException; + } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionVariable.java b/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionVariable.java index 23340df3..840409b9 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionVariable.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ExpressionVariable.java @@ -9,17 +9,15 @@ import org.springframework.util.Assert; public class ExpressionVariable { private String name; - - private String value; + private Object value; /** * Creates a new expression variable * @param name the name of the variable, acting as an convenient alias * @param value the initial value of the variable */ - public ExpressionVariable(String name, String value) { + public ExpressionVariable(String name, Object value) { Assert.hasText(name, "The expression variable must be named"); - Assert.hasText(value, "The expression variable value is required"); this.name = name; this.value = value; } @@ -36,7 +34,7 @@ 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 getValue() { + public Object getValue() { return value; } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ParserContext.java b/spring-binding/src/main/java/org/springframework/binding/expression/ParserContext.java new file mode 100644 index 00000000..3810eb9c --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ParserContext.java @@ -0,0 +1,28 @@ +package org.springframework.binding.expression; + +/** + * Input provided to an expression parser that can influence an expression parsing/compilation routine. + * @author Keith Donald + */ +public interface ParserContext { + + /** + * Returns the type of context object the parsed expression will evaluate in. An expression parser may use this + * value to install custom variable resolves for that particular type of context. + * @return the evaluation context type + */ + public Class getEvaluationContextType(); + + /** + * Returns the expected type of object returned from evaluating the parsed expression. An expression parser may use + * this value to coerce an raw evaluation result before it is returned. + * @return the expected evaluation result type + */ + public Class getExpectedEvaluationResultType(); + + /** + * Returns additional expression variables or aliases that can be referenced during expression evaluation. An + * expression parser will register these variables for reference during evaluation. + */ + public ExpressionVariable[] getExpressionVariables(); +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/SetValueAttempt.java b/spring-binding/src/main/java/org/springframework/binding/expression/SetValueAttempt.java index 24eabe4b..ebabcecd 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/SetValueAttempt.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/SetValueAttempt.java @@ -32,11 +32,11 @@ public class SetValueAttempt extends EvaluationAttempt { /** * Creates a new set attempt. * @param expression the settable expression - * @param target the target of the expression + * @param context the target of the expression * @param value the value that was attempted to be set */ - public SetValueAttempt(Expression expression, Object target, Object value) { - super(expression, target); + public SetValueAttempt(Expression expression, Object context, Object value) { + super(expression, context); this.value = value; } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/DefaultELContext.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/DefaultELContext.java new file mode 100644 index 00000000..3626f0c5 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/DefaultELContext.java @@ -0,0 +1,38 @@ +package org.springframework.binding.expression.el; + +import javax.el.ELContext; +import javax.el.ELResolver; +import javax.el.FunctionMapper; +import javax.el.VariableMapper; + +public class DefaultELContext extends ELContext { + + private VariableMapper variableMapper; + + private ELResolver resolver; + + private FunctionMapper functionMapper; + + public DefaultELContext(ELResolver resolver, VariableMapper variableMapper, FunctionMapper functionMapper) { + this.resolver = resolver; + this.variableMapper = variableMapper; + this.functionMapper = functionMapper; + } + + public static ELContext createDefaultELContext() { + return new DefaultELContext(new DefaultELResolver(null, null), null, null); + } + + public ELResolver getELResolver() { + return resolver; + } + + public VariableMapper getVariableMapper() { + return variableMapper; + } + + public FunctionMapper getFunctionMapper() { + return functionMapper; + } + +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/DefaultElContextFactory.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/DefaultElContextFactory.java new file mode 100644 index 00000000..a19b748b --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/DefaultElContextFactory.java @@ -0,0 +1,10 @@ +package org.springframework.binding.expression.el; + +import javax.el.ELContext; +import javax.el.VariableMapper; + +public class DefaultElContextFactory implements ELContextFactory { + public ELContext getELContext(Object target, VariableMapper variableMapper) { + return new DefaultELContext(new DefaultELResolver(target, null), variableMapper, null); + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java index 0edddb6a..1aeea15d 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpression.java @@ -37,21 +37,21 @@ public class ELExpression implements Expression { this.variableMapper = variableMapper; } - public Object getValue(Object target) throws EvaluationException { - ELContext ctx = elContextFactory.getELContext(target, variableMapper); + public Object getValue(Object context) throws EvaluationException { + ELContext ctx = elContextFactory.getELContext(context, variableMapper); try { return valueExpression.getValue(ctx); } catch (ELException ex) { - throw new EvaluationException(new EvaluationAttempt(this, target), ex); + throw new EvaluationException(new EvaluationAttempt(this, context), ex); } } - public void setValue(Object target, Object value) throws EvaluationException { - ELContext ctx = elContextFactory.getELContext(target, variableMapper); + public void setValue(Object context, Object value) throws EvaluationException { + ELContext ctx = elContextFactory.getELContext(context, variableMapper); try { valueExpression.setValue(ctx, value); } catch (ELException ex) { - throw new EvaluationException(new EvaluationAttempt(this, target), ex); + throw new EvaluationException(new EvaluationAttempt(this, context), ex); } } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpressionParser.java index b9b569cf..bca56b1f 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpressionParser.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELExpressionParser.java @@ -14,18 +14,27 @@ import javax.el.VariableMapper; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; import org.springframework.binding.expression.ExpressionVariable; +import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.ParserException; +import org.springframework.binding.expression.support.NullParserContext; +import org.springframework.util.Assert; /** * An expression parser that parses EL expressions. * @author Jeremy Grelle + * @author Keith Donald */ public class ELExpressionParser implements ExpressionParser { /** * The expression prefix. */ - private static final String EXPRESSION_PREFIX = "#{"; + private static final String EXPRESSION_PREFIX_IMMEDIATE = "${"; + + /** + * The expression prefix. + */ + private static final String EXPRESSION_PREFIX_DEFERRED = "#{"; /** * The expression suffix. @@ -43,47 +52,63 @@ public class ELExpressionParser implements ExpressionParser { * Creates a new EL expression parser for standalone usage. */ public ELExpressionParser(ExpressionFactory expressionFactory) { - this.expressionFactory = expressionFactory; + init(expressionFactory); } /** - * Register the ELContextFactory for expressions that evaluate the given class of target object. - * @param expressionTargetType the expression target class - * @param contextFactory the context factory to use for expressions that evaluate those types of targets + * Register the ELContextFactory for expressions that evaluate the given class of context object. + * @param contextType the expression context class + * @param contextFactory the context factory to use for expressions that evaluate those types of contexts */ - public void putContextFactory(Class expressionTargetType, ELContextFactory contextFactory) { - this.contextFactories.put(expressionTargetType, contextFactory); + public void putContextFactory(Class contextType, ELContextFactory contextFactory) { + Assert.notNull(contextFactory, "The EL context factory cannot be null"); + contextFactories.put(contextType, contextFactory); } - public boolean isEvalExpressionString(String expressionString) { - return expressionString.startsWith(EXPRESSION_PREFIX) && expressionString.endsWith(EXPRESSION_SUFFIX); + public boolean isDelimitedExpression(String expressionString) { + return (expressionString.startsWith(EXPRESSION_PREFIX_DEFERRED) && expressionString.endsWith(EXPRESSION_SUFFIX)) + || (expressionString.startsWith(EXPRESSION_PREFIX_IMMEDIATE) && expressionString + .endsWith(EXPRESSION_SUFFIX)); } - public Expression parseExpression(String expressionString, Class expressionTargetType, - Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException { - if (expectedEvaluationResultType == null) { - throw new ParserException(expressionString, "The 'expectedEvaluationResultType' argument is required; " - + "specify Object.class if the type is unknown", new NullPointerException()); + public Expression parseExpression(String expressionString, ParserContext context) throws ParserException { + if (context == null) { + context = NullParserContext.INSTANCE; } try { - ParserELContext context = new ParserELContext(); - context.mapVariables(expressionVariables, expressionFactory); - ValueExpression expression = expressionFactory.createValueExpression(context, expressionString, - expectedEvaluationResultType); - ELContextFactory contextFactory = getContextFactory(expressionTargetType, expressionString); - return new ELExpression(contextFactory, expression, context.getVariableMapper()); - } catch (ELException ex) { - throw new ParserException(expressionString, ex); + ParserELContext elContext = new ParserELContext(); + elContext.mapVariables(context.getExpressionVariables(), expressionFactory); + ValueExpression expression = expressionFactory.createValueExpression(elContext, expressionString, + getExpectedType(context)); + ELContextFactory contextFactory = getContextFactory(context.getEvaluationContextType(), expressionString); + return new ELExpression(contextFactory, expression, elContext.getVariableMapper()); + } catch (ELException e) { + throw new ParserException(expressionString, e); + } + } + + private Class getExpectedType(ParserContext context) { + Class expectedType = context.getExpectedEvaluationResultType(); + if (expectedType != null) { + return expectedType; + } else { + return Object.class; } } private ELContextFactory getContextFactory(Class expressionTargetType, String expressionString) { - if (!contextFactories.containsKey(expressionTargetType)) { - throw new ParserException(expressionString, new IllegalArgumentException( - "No ELContextFactory registered for expressionTargetType [" + expressionTargetType + "]; " - + "Please ensure a factory is registered for this type.")); + if (contextFactories.containsKey(expressionTargetType)) { + return (ELContextFactory) contextFactories.get(expressionTargetType); + } else { + return (ELContextFactory) contextFactories.get(Object.class); } - return (ELContextFactory) contextFactories.get(expressionTargetType); + } + + private void init(ExpressionFactory expressionFactory) { + this.expressionFactory = expressionFactory; + DefaultElContextFactory defaultContextFactory = new DefaultElContextFactory(); + putContextFactory(null, defaultContextFactory); + putContextFactory(Object.class, defaultContextFactory); } private static class ParserELContext extends ELContext { @@ -106,7 +131,8 @@ 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.getValue(), Object.class); + ValueExpression expr = expressionFactory.createValueExpression(this, + String.valueOf(var.getValue()), Object.class); variableMapper.setVariable(var.getName(), expr); } } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java index b1e41427..aa136b3f 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpression.java @@ -47,15 +47,14 @@ class OgnlExpression implements Expression { /** * Expression variable initial values. */ - private ExpressionVariable[] variables; + private Map variableMap;; /** * Creates a new OGNL expression. * @param expression the parsed expression */ public OgnlExpression(Object expression, ExpressionVariable[] variables) { - this.expression = expression; - this.variables = variables; + init(expression, variables); } public int hashCode() { @@ -72,34 +71,43 @@ class OgnlExpression implements Expression { return expression.equals(other.expression); } - public Object getValue(Object target) throws EvaluationException { - Assert.notNull(target, "The target object to evaluate is required"); + public Object getValue(Object context) throws EvaluationException { try { - return Ognl.getValue(expression, getContext(), target); + return Ognl.getValue(expression, variableMap, context); } catch (OgnlException e) { if (e.getReason() != null && e.getReason() != e) { // unwrap the OgnlException since the actual exception is wrapped inside it // and there is not generic (getCause) way to get to it later on - throw new EvaluationException(new EvaluationAttempt(this, target), e.getReason()); + throw new EvaluationException(new EvaluationAttempt(this, context), e.getReason()); } else { - throw new EvaluationException(new EvaluationAttempt(this, target), e); + throw new EvaluationException(new EvaluationAttempt(this, context), e); } } } - public void setValue(Object target, Object value) { - Assert.notNull(target, "The target object to evaluate is required"); + public void setValue(Object context, Object value) { + Assert.notNull(context, "The context to set the provided value in is required"); try { - // TODO context map - Ognl.setValue(expression, getContext(), target, value); + Ognl.setValue(expression, variableMap, context, value); } catch (OgnlException e) { - throw new EvaluationException(new SetValueAttempt(this, target, value), e); + throw new EvaluationException(new SetValueAttempt(this, context, value), e); } } - private Map getContext() { + private void init(Object expression, ExpressionVariable[] variables) { + this.expression = expression; + variableMap = createVariableMap(variables); + } + + private Map createVariableMap(ExpressionVariable[] variables) { if (variables != null && variables.length > 0) { - return new HashMap(variables.length); + Map variableMap = new HashMap(variables.length); + for (int i = 0; i < variables.length; i++) { + ExpressionVariable var = variables[i]; + variableMap.put(var.getName(), var.getValue()); + } + System.out.println(variableMap); + return variableMap; } else { return Collections.EMPTY_MAP; } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java index e210bdcf..5e716355 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/ognl/OgnlExpressionParser.java @@ -21,9 +21,10 @@ import ognl.OgnlRuntime; import ognl.PropertyAccessor; import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionVariable; +import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.ParserException; import org.springframework.binding.expression.support.AbstractExpressionParser; +import org.springframework.binding.expression.support.NullParserContext; /** * An expression parser that parses Ognl expressions. @@ -32,10 +33,12 @@ import org.springframework.binding.expression.support.AbstractExpressionParser; */ public class OgnlExpressionParser extends AbstractExpressionParser { - protected Expression doParseExpression(String expressionString, Class expressionTargetType, - Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException { + public Expression doParseExpression(String expressionString, ParserContext context) throws ParserException { + if (context == null) { + context = NullParserContext.INSTANCE; + } try { - return new OgnlExpression(Ognl.parseExpression(expressionString), expressionVariables); + return new OgnlExpression(Ognl.parseExpression(expressionString), context.getExpressionVariables()); } catch (OgnlException e) { throw new ParserException(expressionString, e); } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java b/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java index aa9626bb..87b59527 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java @@ -20,7 +20,7 @@ import java.util.List; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.ExpressionVariable; +import org.springframework.binding.expression.ParserContext; import org.springframework.binding.expression.ParserException; import org.springframework.util.Assert; @@ -80,15 +80,13 @@ public abstract class AbstractExpressionParser implements ExpressionParser { this.expressionSuffix = expressionSuffix; } - public boolean isEvalExpressionString(String string) { + public boolean isDelimitedExpression(String string) { return string.startsWith(expressionPrefix) && string.endsWith(expressionSuffix); } - public Expression parseExpression(String expressionString, Class expressionTargetType, - Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException { + public Expression parseExpression(String expressionString, ParserContext context) throws ParserException { Assert.notNull(expressionString, "The expression string to parse is required"); - Expression[] expressions = parseExpressions(expressionString, expressionTargetType, - expectedEvaluationResultType, expressionVariables); + Expression[] expressions = parseExpressions(expressionString, context); if (expressions.length == 1) { return expressions[0]; } else { @@ -105,8 +103,7 @@ public abstract class AbstractExpressionParser implements ExpressionParser { * @return the parsed expressions * @throws ParserException when the expressions cannot be parsed */ - private Expression[] parseExpressions(String expressionString, Class expressionTargetType, - Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException { + private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParserException { List expressions = new LinkedList(); int startIdx = 0; while (startIdx < expressionString.length()) { @@ -136,8 +133,7 @@ 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, expressionTargetType, expectedEvaluationResultType, - expressionVariables)); + expressions.add(doParseExpression(expr, context)); startIdx = suffixIndex + 1; } } else { @@ -157,7 +153,7 @@ public abstract class AbstractExpressionParser implements ExpressionParser { * @return the parsed expression * @throws ParserException an exception occurred during parsing */ - protected abstract Expression doParseExpression(String expressionString, Class expressionTargetType, - Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException; + protected abstract Expression doParseExpression(String expressionString, ParserContext context) + throws ParserException; } \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/support/CollectionAddingExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/support/CollectionAddingExpression.java index 4950828b..2e49093d 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/support/CollectionAddingExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/support/CollectionAddingExpression.java @@ -43,14 +43,14 @@ public class CollectionAddingExpression implements Expression { this.collectionExpression = collectionExpression; } - public Object getValue(Object target) throws EvaluationException { - return collectionExpression.getValue(target); + public Object getValue(Object context) throws EvaluationException { + return collectionExpression.getValue(context); } - public void setValue(Object target, Object value) throws EvaluationException { - Object result = getValue(target); + public void setValue(Object context, Object value) throws EvaluationException { + Object result = getValue(context); if (result == null) { - throw new EvaluationException(new SetValueAttempt(this, target, value), new IllegalArgumentException( + throw new EvaluationException(new SetValueAttempt(this, context, value), new IllegalArgumentException( "The collection expression evaluated to a [null] reference")); } Assert.isInstanceOf(Collection.class, result, "Not a collection: "); diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/support/CompositeStringExpression.java b/spring-binding/src/main/java/org/springframework/binding/expression/support/CompositeStringExpression.java index 72795c28..44e53c21 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/support/CompositeStringExpression.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/support/CompositeStringExpression.java @@ -40,15 +40,15 @@ public class CompositeStringExpression implements Expression { this.expressions = expressions; } - public Object getValue(Object target) throws EvaluationException { + public Object getValue(Object context) throws EvaluationException { StringBuffer buffer = new StringBuffer(128); for (int i = 0; i < expressions.length; i++) { - buffer.append(expressions[i].getValue(target)); + buffer.append(expressions[i].getValue(context)); } return buffer.toString(); } - public void setValue(Object target, Object value) throws EvaluationException { + public void setValue(Object context, Object value) throws EvaluationException { throw new UnsupportedOperationException("Cannot set a composite string expression value"); } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/support/NullParserContext.java b/spring-binding/src/main/java/org/springframework/binding/expression/support/NullParserContext.java new file mode 100644 index 00000000..ee7c91cc --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/support/NullParserContext.java @@ -0,0 +1,39 @@ +/* + * Copyright 2004-2007 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.binding.expression.support; + +import org.springframework.binding.expression.ExpressionVariable; +import org.springframework.binding.expression.ParserContext; + +public final class NullParserContext implements ParserContext { + + public static final ParserContext INSTANCE = new NullParserContext(); + + private NullParserContext() { + } + + public Class getEvaluationContextType() { + return null; + } + + public Class getExpectedEvaluationResultType() { + return null; + } + + public ExpressionVariable[] getExpressionVariables() { + return null; + } +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/support/ParserContextImpl.java b/spring-binding/src/main/java/org/springframework/binding/expression/support/ParserContextImpl.java new file mode 100644 index 00000000..9a50ad40 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/expression/support/ParserContextImpl.java @@ -0,0 +1,57 @@ +package org.springframework.binding.expression.support; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.binding.expression.ExpressionVariable; +import org.springframework.binding.expression.ParserContext; + +public class ParserContextImpl implements ParserContext { + + private Class evaluationContextType; + + private Class evaluationResultType; + + private List expressionVariables; + + public ParserContextImpl() { + init(); + } + + public Class getEvaluationContextType() { + return evaluationContextType; + } + + public Class getExpectedEvaluationResultType() { + return evaluationResultType; + } + + public ExpressionVariable[] getExpressionVariables() { + return (ExpressionVariable[]) expressionVariables.toArray(new ExpressionVariable[expressionVariables.size()]); + } + + public ParserContextImpl context(Class contextType) { + evaluationContextType = contextType; + return ParserContextImpl.this; + } + + public ParserContextImpl expect(Class resultType) { + evaluationResultType = resultType; + return ParserContextImpl.this; + } + + public ParserContextImpl variable(ExpressionVariable variable) { + expressionVariables.add(variable); + return ParserContextImpl.this; + } + + public ParserContextImpl variables(ExpressionVariable[] variables) { + expressionVariables.addAll(Arrays.asList(variables)); + return ParserContextImpl.this; + } + + private void init() { + expressionVariables = new ArrayList(); + } +} diff --git a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java index 7ce80446..6d0cfa6f 100644 --- a/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java +++ b/spring-binding/src/main/java/org/springframework/binding/mapping/MappingBuilder.java @@ -101,7 +101,7 @@ public class MappingBuilder { * @return this, to support call-chaining */ public MappingBuilder source(String expressionString) { - sourceExpression = expressionParser.parseExpression(expressionString, Object.class, Object.class, null); + sourceExpression = expressionParser.parseExpression(expressionString, null); return this; } @@ -111,7 +111,7 @@ public class MappingBuilder { * @return this, to support call-chaining */ public MappingBuilder target(String expressionString) { - targetExpression = expressionParser.parseExpression(expressionString, Object.class, Object.class, null); + targetExpression = expressionParser.parseExpression(expressionString, null); return this; } @@ -121,8 +121,7 @@ public class MappingBuilder { * @return this, to support call-chaining */ public MappingBuilder targetCollection(String expressionString) { - targetExpression = new CollectionAddingExpression(expressionParser.parseExpression(expressionString, - Object.class, Object.class, null)); + targetExpression = new CollectionAddingExpression(expressionParser.parseExpression(expressionString, null)); return this; } diff --git a/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java b/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java index 6dfbceb6..3fafaa77 100644 --- a/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/expression/el/ELExpressionParserTests.java @@ -1,8 +1,8 @@ package org.springframework.binding.expression.el; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.Map; import javax.el.ELContext; import javax.el.ELResolver; @@ -14,7 +14,7 @@ import junit.framework.TestCase; import org.jboss.el.ExpressionFactoryImpl; import org.springframework.binding.expression.Expression; import org.springframework.binding.expression.ExpressionVariable; -import org.springframework.binding.expression.ParserException; +import org.springframework.binding.expression.support.ParserContextImpl; public class ELExpressionParserTests extends TestCase { @@ -24,120 +24,54 @@ public class ELExpressionParserTests extends TestCase { parser.putContextFactory(TestBean.class, new TestELContextFactory()); } - private static class TestELContextFactory implements ELContextFactory { - public ELContext getELContext(final Object target, final VariableMapper variableMapper) { - return new ELContext() { - public ELResolver getELResolver() { - return new DefaultELResolver(target, null); - } - - public FunctionMapper getFunctionMapper() { - return null; - } - - public VariableMapper getVariableMapper() { - return variableMapper; - } - }; - } + public void testParseSimpleEvalExpressionNoParserContext() { + String expressionString = "#{3 + 4}"; + Expression exp = parser.parseExpression(expressionString, null); + assertEquals(new Long(7), exp.getValue(null)); } - public void testParseEvalExpressionExpectedResultTypeNull() { - String expressionString = "#{value}"; - Class expressionTargetType = null; - Class expectedEvaluationResultType = null; - ExpressionVariable[] expressionVariables = null; - try { - parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); - fail("Should have failed"); - } catch (ParserException e) { - - } + public void testParseSimpleEvalExpressionNoEvalContextWithTypeCoersion() { + String expressionString = "#{3 + 4}"; + Expression exp = parser.parseExpression(expressionString, new ParserContextImpl().expect(Integer.class)); + assertEquals(new Integer(7), exp.getValue(null)); } - public void testParseEvalExpression() { + public void testParseBeanEvalExpressionNoParserContext() { String expressionString = "#{value}"; - Class expressionTargetType = TestBean.class; - Class expectedEvaluationResultType = String.class; - ExpressionVariable[] expressionVariables = null; - Expression exp = parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); - TestBean target = new TestBean(); - assertEquals("foo", exp.getValue(target)); + Expression exp = parser.parseExpression(expressionString, null); + assertEquals("foo", exp.getValue(new TestBean())); } - public void testParseEvalExpressionNoTargetType() { - String expressionString = "#{value}"; - Class expressionTargetType = null; - Class expectedEvaluationResultType = Object.class; - ExpressionVariable[] expressionVariables = null; - try { - parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); - fail("Should have failed"); - } catch (ParserException e) { - - } + public void testParseEvalExpressionWithContextTypeCoersion() { + String expressionString = "#{maximum}"; + Expression exp = parser.parseExpression(expressionString, new ParserContextImpl().expect(Long.class)); + assertEquals(new Long(2), exp.getValue(new TestBean())); } - public void testParseEvalExpressionNotRegisteredTargetType() { - String expressionString = "#{value}"; - Class expressionTargetType = Map.class; - Class expectedEvaluationResultType = Object.class; - ExpressionVariable[] expressionVariables = null; - try { - parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); - fail("Should have failed"); - } catch (ParserException e) { - } + public void testParseEvalExpressionWithContextCustomTestBeanResolver() { + String expressionString = "#{specialProperty}"; + Expression exp = parser.parseExpression(expressionString, new ParserContextImpl().context(TestBean.class)); + assertEquals("Custom resolver resolved this special property!", exp.getValue(new TestBean())); } public void testParseLiteralExpression() { String expressionString = "value"; - Class expressionTargetType = TestBean.class; - Class expectedEvaluationResultType = String.class; - ExpressionVariable[] expressionVariables = null; - Expression exp = parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); - TestBean target = new TestBean(); - assertEquals("value", exp.getValue(target)); + Expression exp = parser.parseExpression(expressionString, null); + assertEquals("value", exp.getValue(null)); } public void testParseExpressionWithVariables() { String expressionString = "#{value}#{max}"; - Class expressionTargetType = TestBean.class; - Class expectedEvaluationResultType = String.class; - ExpressionVariable[] expressionVariables = new ExpressionVariable[] { new ExpressionVariable("max", - "#{maximum}") }; - Expression exp = parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); + Expression exp = parser.parseExpression(expressionString, new ParserContextImpl() + .variable(new ExpressionVariable("max", "#{maximum}"))); TestBean target = new TestBean(); assertEquals("foo2", exp.getValue(target)); } - public void testParseExpressionWithVariables2() { - String expressionString = "#{value}#{bean.encode(value)}"; - Class expressionTargetType = TestBean.class; - Class expectedEvaluationResultType = String.class; - ExpressionVariable[] expressionVariables = null; - Expression exp = parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); - TestBean target = new TestBean(new TestBean()); - assertEquals("foo!foo", exp.getValue(target)); - } - - public void testParseExpressionCoerceToInteger() { - String expressionString = "#{maximum}#{max}"; - Class expressionTargetType = TestBean.class; - Class expectedEvaluationResultType = Integer.class; - ExpressionVariable[] expressionVariables = new ExpressionVariable[] { new ExpressionVariable("max", - "#{maximum}") }; - Expression exp = parser.parseExpression(expressionString, expressionTargetType, expectedEvaluationResultType, - expressionVariables); - TestBean target = new TestBean(); - assertEquals(new Integer(22), exp.getValue(target)); + public void testParseImmediateEvalExpression() { + String expressionString = "${3 + 4}"; + Expression exp = parser.parseExpression(expressionString, null); + assertEquals(new Long(7), exp.getValue(null)); } public static class TestBean { @@ -187,6 +121,52 @@ public class ELExpressionParserTests extends TestCase { public void setMaximum(int maximum) { this.maximum = maximum; } - } + + private static class TestELContextFactory implements ELContextFactory { + public ELContext getELContext(final Object target, final VariableMapper variableMapper) { + return new ELContext() { + public ELResolver getELResolver() { + return new ELResolver() { + public Class getCommonPropertyType(ELContext arg0, Object arg1) { + return Object.class; + } + + public Iterator getFeatureDescriptors(ELContext arg0, Object arg1) { + return null; + } + + public Class getType(ELContext arg0, Object arg1, Object arg2) { + return String.class; + } + + public Object getValue(ELContext arg0, Object arg1, Object arg2) { + if (arg1 == null && arg2.equals("specialProperty")) { + return "Custom resolver resolved this special property!"; + } else { + throw new IllegalStateException(); + } + } + + public boolean isReadOnly(ELContext arg0, Object arg1, Object arg2) { + return true; + } + + public void setValue(ELContext arg0, Object arg1, Object arg2, Object arg3) { + throw new UnsupportedOperationException("Not supported"); + } + }; + } + + public FunctionMapper getFunctionMapper() { + return null; + } + + public VariableMapper getVariableMapper() { + return variableMapper; + } + }; + } + } + } diff --git a/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java b/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java index 150bd611..365a4cab 100644 --- a/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java +++ b/spring-binding/src/test/java/org/springframework/binding/expression/ognl/OgnlExpressionParserTests.java @@ -18,7 +18,9 @@ package org.springframework.binding.expression.ognl; import junit.framework.TestCase; import org.springframework.binding.expression.Expression; +import org.springframework.binding.expression.ExpressionVariable; import org.springframework.binding.expression.ParserException; +import org.springframework.binding.expression.support.ParserContextImpl; /** * Unit tests for {@link org.springframework.binding.expression.ognl.OgnlExpressionParser}. @@ -31,21 +33,21 @@ public class OgnlExpressionParserTests extends TestCase { public void testParseSimple() { String exp = "${flag}"; - Expression e = parser.parseExpression(exp, null, null, null); + Expression e = parser.parseExpression(exp, null); assertNotNull(e); Boolean b = (Boolean) e.getValue(bean); assertFalse(b.booleanValue()); } public void testParseEmpty() { - Expression e = parser.parseExpression("", null, null, null); + Expression e = parser.parseExpression("", null); assertNotNull(e); assertEquals("", e.getValue(bean)); } public void testParseComposite() { String exp = "hello ${flag} ${flag} ${flag}"; - Expression e = parser.parseExpression(exp, null, null, null); + Expression e = parser.parseExpression(exp, null); assertNotNull(e); String str = (String) e.getValue(bean); assertEquals("hello false false false", str); @@ -54,7 +56,7 @@ public class OgnlExpressionParserTests extends TestCase { public void testEnclosedCompositeNotSupported() { String exp = "${hello ${flag} ${flag} ${flag}}"; try { - parser.parseExpression(exp, null, null, null); + parser.parseExpression(exp, null); fail("Should've failed - not intended use"); } catch (ParserException e) { } @@ -62,13 +64,13 @@ public class OgnlExpressionParserTests extends TestCase { public void testSyntaxError1() { try { - parser.parseExpression("${", null, null, null); + parser.parseExpression("${", null); fail(); } catch (ParserException e) { } try { String exp = "hello ${flag} ${abcd defg"; - parser.parseExpression(exp, null, null, null); + parser.parseExpression(exp, null); fail("Should've failed - not intended use"); } catch (ParserException e) { } @@ -76,13 +78,13 @@ public class OgnlExpressionParserTests extends TestCase { public void testSyntaxError2() { try { - parser.parseExpression("${}", null, null, null); + parser.parseExpression("${}", null); fail("Should've failed - not intended use"); } catch (ParserException e) { } try { String exp = "hello ${flag} ${}"; - parser.parseExpression(exp, null, null, null); + parser.parseExpression(exp, null); fail("Should've failed - not intended use"); } catch (ParserException e) { } @@ -90,24 +92,28 @@ public class OgnlExpressionParserTests extends TestCase { public void testCollectionConstructionSyntax() { // lists - parser.parseExpression("name in {null, \"Untitled\"}", null, null, null); - parser.parseExpression("${name in {null, \"Untitled\"}}", null, null, null); + parser.parseExpression("name in {null, \"Untitled\"}", null); + parser.parseExpression("${name in {null, \"Untitled\"}}", null); // native arrays - parser.parseExpression("new int[] {1, 2, 3}", null, null, null); - parser.parseExpression("${new int[] {1, 2, 3}}", null, null, null); + parser.parseExpression("new int[] {1, 2, 3}", null); + parser.parseExpression("${new int[] {1, 2, 3}}", null); // maps - parser.parseExpression("#{ 'foo' : 'foo value', 'bar' : 'bar value' }", null, null, null); - parser.parseExpression("${#{ 'foo' : 'foo value', 'bar' : 'bar value' }}", null, null, null); - parser.parseExpression("#@java.util.LinkedHashMap@{ 'foo' : 'foo value', 'bar' : 'bar value' }", null, null, - null); - parser.parseExpression("${#@java.util.LinkedHashMap@{ 'foo' : 'foo value', 'bar' : 'bar value' }}", null, null, - null); + parser.parseExpression("#{ 'foo' : 'foo value', 'bar' : 'bar value' }", null); + parser.parseExpression("${#{ 'foo' : 'foo value', 'bar' : 'bar value' }}", null); + parser.parseExpression("#@java.util.LinkedHashMap@{ 'foo' : 'foo value', 'bar' : 'bar value' }", null); + parser.parseExpression("${#@java.util.LinkedHashMap@{ 'foo' : 'foo value', 'bar' : 'bar value' }}", null); // complex examples - parser.parseExpression("b,#{1:2}", null, null, null); - parser.parseExpression("${b,#{1:2}}", null, null, null); - parser.parseExpression("a${b,#{1:2},e}f${g,#{3:4},j}k", null, null, null); + parser.parseExpression("b,#{1:2}", null); + parser.parseExpression("${b,#{1:2}}", null); + parser.parseExpression("a${b,#{1:2},e}f${g,#{3:4},j}k", null); + } + + public void testVariables() { + Expression exp = parser.parseExpression("${#var}", new ParserContextImpl().variable(new ExpressionVariable( + "var", new Integer(1)))); + assertEquals(new Integer(1), exp.getValue(bean)); } } \ No newline at end of file