From 72fe878fc90fa82660418c6bcaa080ae5e3ccb71 Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Mon, 21 Jan 2008 23:03:33 +0000 Subject: [PATCH] further EL improvements - OGNL behavior for variable handling is now correct, EL impl simplfiied. --- .../binding/expression/ExpressionParser.java | 10 - .../expression/ExpressionVariable.java | 15 +- .../el/DefaultElContextFactory.java | 5 +- .../expression/el/ELContextFactory.java | 4 +- .../binding/expression/el/ELExpression.java | 11 +- .../expression/el/ELExpressionParser.java | 27 +- .../expression/ognl/OgnlExpression.java | 42 +-- .../expression/ognl/OgnlExpressionParser.java | 246 +++++++++++++++++- .../support/AbstractExpressionParser.java | 178 ------------- .../el/ELExpressionParserTests.java | 10 +- .../ognl/OgnlExpressionParserTests.java | 55 ++-- 11 files changed, 314 insertions(+), 289 deletions(-) delete mode 100644 spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java 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 882e52e2..82a146bb 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 @@ -23,16 +23,6 @@ package org.springframework.binding.expression; */ public interface ExpressionParser { - /** - * Is the provided string an explicitly delimited expression this parser knows how to parse? For example, this - * method may return true if the string provided is enclosed in "${}". It may also return true if the string - * provided is a mix of literal text and delimited expression syntax, for example "hello world ${name}!" The exact - * semantics are determined by the parser implementation. - * @param string the string - * @return true if the string is a delimited expression, false otherwise. - */ - public boolean hasDelimitedExpression(String string); - /** * Parse the provided expression string, returning an expression evaluator capable of evaluating it. * @param expressionString the parseable expression string; cannot be null (required) 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 840409b9..dcbc6347 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,21 +9,22 @@ import org.springframework.util.Assert; public class ExpressionVariable { private String name; - private Object value; + private String valueExpression; /** * 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 + * @param value the value expression */ - public ExpressionVariable(String name, Object value) { + public ExpressionVariable(String name, String value) { Assert.hasText(name, "The expression variable must be named"); + Assert.hasText(value, "The expression variable's value expression is required"); this.name = name; - this.value = value; + this.valueExpression = value; } /** - * Returns the variable name, typically vary simple like "index". + * Returns the variable name. * @return the variable name */ public String getName() { @@ -34,8 +35,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 Object getValue() { - return value; + public String getValueExpression() { + return valueExpression; } public boolean equals(Object o) { 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 index a19b748b..a8f96fdb 100644 --- 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 @@ -1,10 +1,9 @@ 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); + public ELContext getELContext(Object target) { + return new DefaultELContext(new DefaultELResolver(target, null), null, null); } } diff --git a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELContextFactory.java b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELContextFactory.java index 01508de7..76b8347d 100644 --- a/spring-binding/src/main/java/org/springframework/binding/expression/el/ELContextFactory.java +++ b/spring-binding/src/main/java/org/springframework/binding/expression/el/ELContextFactory.java @@ -2,7 +2,6 @@ package org.springframework.binding.expression.el; import javax.el.ELContext; import javax.el.ELResolver; -import javax.el.VariableMapper; /** * A factory for creating a EL context object that will be used to evaluate a target object of an EL expression. @@ -16,9 +15,8 @@ public interface ELContextFactory { * object. In certain environments the target will be null and the base object of the expression is expected to be * resolved via the ELContext's {@link ELResolver} chain. * @param target The base object for the expression evaluation - * @param variableMapper The mapping storing variables needed during expression evaluation * @return ELContext The configured ELContext instance for evaluating expressions. */ - public ELContext getELContext(Object target, VariableMapper variableMapper); + public ELContext getELContext(Object target); } \ No newline at end of file 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 1aeea15d..49e7b137 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 @@ -3,7 +3,6 @@ package org.springframework.binding.expression.el; import javax.el.ELContext; import javax.el.ELException; import javax.el.ValueExpression; -import javax.el.VariableMapper; import org.springframework.binding.expression.EvaluationAttempt; import org.springframework.binding.expression.EvaluationException; @@ -21,24 +20,20 @@ public class ELExpression implements Expression { private ValueExpression valueExpression; - private VariableMapper variableMapper; - /** * Creates a new el expression * @param factory the el context factory for creating the EL context that will be used during expression evaluation * @param valueExpression the value expression to evaluate - * @param variableMapper the variable mapper containing variables needed during expression evaluation */ - public ELExpression(ELContextFactory factory, ValueExpression valueExpression, VariableMapper variableMapper) { + public ELExpression(ELContextFactory factory, ValueExpression valueExpression) { Assert.notNull(factory, "The ELContextFactory is required to evaluate EL expressions"); Assert.notNull(valueExpression, "The EL value expression is required for evaluation"); this.elContextFactory = factory; this.valueExpression = valueExpression; - this.variableMapper = variableMapper; } public Object getValue(Object context) throws EvaluationException { - ELContext ctx = elContextFactory.getELContext(context, variableMapper); + ELContext ctx = elContextFactory.getELContext(context); try { return valueExpression.getValue(ctx); } catch (ELException ex) { @@ -47,7 +42,7 @@ public class ELExpression implements Expression { } public void setValue(Object context, Object value) throws EvaluationException { - ELContext ctx = elContextFactory.getELContext(context, variableMapper); + ELContext ctx = elContextFactory.getELContext(context); try { valueExpression.setValue(ctx, value); } catch (ELException 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 74cbcea8..6b7b0574 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 @@ -26,21 +26,6 @@ import org.springframework.util.Assert; */ public class ELExpressionParser implements ExpressionParser { - /** - * The expression prefix. - */ - private static final String EXPRESSION_PREFIX_IMMEDIATE = "${"; - - /** - * The expression prefix. - */ - private static final String EXPRESSION_PREFIX_DEFERRED = "#{"; - - /** - * The expression suffix. - */ - private static final String EXPRESSION_SUFFIX = "}"; - /** * The ExpressionFactory for constructing EL expressions */ @@ -65,12 +50,6 @@ public class ELExpressionParser implements ExpressionParser { contextFactories.put(contextType, contextFactory); } - public boolean hasDelimitedExpression(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, ParserContext context) throws ParserException { Assert.notNull(expressionString, "The expression string to parse is required"); if (context == null) { @@ -82,7 +61,7 @@ public class ELExpressionParser implements ExpressionParser { ValueExpression expression = expressionFactory.createValueExpression(elContext, expressionString, getExpectedType(context)); ELContextFactory contextFactory = getContextFactory(context.getEvaluationContextType(), expressionString); - return new ELExpression(contextFactory, expression, elContext.getVariableMapper()); + return new ELExpression(contextFactory, expression); } catch (ELException e) { throw new ParserException(expressionString, e); } @@ -132,8 +111,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, - String.valueOf(var.getValue()), Object.class); + ValueExpression expr = expressionFactory.createValueExpression(this, var.getValueExpression(), + 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 9ec4e2a6..b639a8d6 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 @@ -17,6 +17,7 @@ package org.springframework.binding.expression.ognl; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import ognl.Ognl; @@ -25,7 +26,6 @@ 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; @@ -44,14 +44,21 @@ class OgnlExpression implements Expression { /** * Expression variable initial values. */ - private Map variableMap;; + private Map variableExpressions; + + /** + * The expected type of object returned from evaluating the expression. + */ + private Class expectedResultType; /** * Creates a new OGNL expression. * @param expression the parsed expression */ - public OgnlExpression(Object expression, ExpressionVariable[] variables) { - init(expression, variables); + public OgnlExpression(Object expression, Map variableExpressions, Class expectedResultType) { + this.expression = expression; + this.variableExpressions = variableExpressions; + this.expectedResultType = expectedResultType; } public int hashCode() { @@ -68,7 +75,7 @@ class OgnlExpression implements Expression { public Object getValue(Object context) throws EvaluationException { try { - return Ognl.getValue(expression, variableMap, context); + return Ognl.getValue(expression, getVariables(context), context, expectedResultType); } catch (OgnlException e) { if (e.getReason() != null && e.getReason() != e) { // unwrap the OgnlException since the actual exception is wrapped inside it @@ -83,28 +90,23 @@ class OgnlExpression implements Expression { public void setValue(Object context, Object value) { Assert.notNull(context, "The context to set the provided value in is required"); try { - Ognl.setValue(expression, variableMap, context, value); + Ognl.setValue(expression, getVariables(context), context, value); } catch (OgnlException e) { throw new EvaluationException(new SetValueAttempt(this, context, value), e); } } - private void init(Object expression, ExpressionVariable[] variables) { - this.expression = expression; - variableMap = createVariableMap(variables); - } - - private Map createVariableMap(ExpressionVariable[] variables) { - if (variables != null && variables.length > 0) { - Map variableMap = new HashMap(variables.length); - for (int i = 0; i < variables.length; i++) { - ExpressionVariable var = variables[i]; - variableMap.put(var.getName(), var.getValue()); - } - return variableMap; - } else { + private Map getVariables(Object context) { + if (variableExpressions == null) { return Collections.EMPTY_MAP; } + Map variables = new HashMap(variableExpressions.size(), 1); + for (Iterator it = variableExpressions.entrySet().iterator(); it.hasNext();) { + Map.Entry var = (Map.Entry) it.next(); + Expression valueExpression = (Expression) var.getValue(); + variables.put(var.getKey(), valueExpression.getValue(context)); + } + return variables; } public String toString() { 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 5e716355..6f4e05eb 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 @@ -15,33 +15,109 @@ */ package org.springframework.binding.expression.ognl; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + import ognl.Ognl; import ognl.OgnlException; import ognl.OgnlRuntime; import ognl.PropertyAccessor; 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.AbstractExpressionParser; +import org.springframework.binding.expression.support.CompositeStringExpression; import org.springframework.binding.expression.support.NullParserContext; +import org.springframework.binding.expression.support.StaticExpression; +import org.springframework.util.Assert; /** * An expression parser that parses Ognl expressions. * * @author Keith Donald */ -public class OgnlExpressionParser extends AbstractExpressionParser { +public class OgnlExpressionParser implements ExpressionParser { - public Expression doParseExpression(String expressionString, ParserContext context) throws ParserException { - if (context == null) { - context = NullParserContext.INSTANCE; - } - try { - return new OgnlExpression(Ognl.parseExpression(expressionString), context.getExpressionVariables()); - } catch (OgnlException e) { - throw new ParserException(expressionString, e); - } + /** + * The expression prefix. + */ + private static final String DEFAULT_EXPRESSION_PREFIX = "${"; + + /** + * The expression suffix. + */ + private static final String DEFAULT_EXPRESSION_SUFFIX = "}"; + + /** + * The marked expression delimter prefix. + */ + private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX; + + /** + * The marked expression delimiter suffix. + */ + private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX; + + /** + * Should we allow undelimited OGNL eval expressions like "foo.bar"? If not, evalutable OGNL expressions must be + * enclosed in delimiters like ${foo.bar} else they are treated as literal expressions. Mainly here for + * compatability reasons, as Web Flow 1.0 allows undelimited OGNL eval expressions by default. + */ + private boolean allowUndelimitedEvalExpressions; + + /** + * Returns the configured expression delimiter prefix. Defaults to "${". + */ + public String getExpressionPrefix() { + return expressionPrefix; + } + + /** + * Sets the expression delimiter prefix. + */ + public void setExpressionPrefix(String expressionPrefix) { + this.expressionPrefix = expressionPrefix; + } + + /** + * Returns the expression delimiter suffix. Defaults to "}". + */ + public String getExpressionSuffix() { + return expressionSuffix; + } + + /** + * Sets the expression delimiter suffix. + */ + public void setExpressionSuffix(String expressionSuffix) { + this.expressionSuffix = expressionSuffix; + } + + /** + * Returns if this parser should we allow undelimited OGNL eval expressions like foo.bar. + */ + public boolean getAllowUndelimitedEvalExpressions() { + return allowUndelimitedEvalExpressions; + } + + /** + * Sets if this parser should allow undelimited OGNL eval expressions like "foo.bar"? If not, evalutable OGNL + * expressions must be enclosed in delimiters like ${foo.bar}, else they are treated as literal expressions. + */ + public void setAllowUndelimitedEvalExpressions(boolean allowUndelmitedEvalExpressions) { + this.allowUndelimitedEvalExpressions = allowUndelmitedEvalExpressions; + } + + public static String getDEFAULT_EXPRESSION_PREFIX() { + return DEFAULT_EXPRESSION_PREFIX; + } + + public static String getDEFAULT_EXPRESSION_SUFFIX() { + return DEFAULT_EXPRESSION_SUFFIX; } /** @@ -52,4 +128,152 @@ public class OgnlExpressionParser extends AbstractExpressionParser { public void addPropertyAccessor(Class clazz, PropertyAccessor propertyAccessor) { OgnlRuntime.setPropertyAccessor(clazz, propertyAccessor); } + + // expression parser + + public Expression parseExpression(String expressionString, ParserContext context) throws ParserException { + Assert.notNull(expressionString, "The expression string to parse is required"); + Expression[] expressions = parseExpressions(expressionString, context); + if (expressions.length == 1) { + return expressions[0]; + } else { + return new CompositeStringExpression(expressions); + } + } + + /** + * Is the provided string a template expression this parser can parse? Always returns true if this + * OGNL expression parser is configured to not allow undelimited OGNL expressions. If undelimited OGNL + * expressions are allowed like "foo.bar", this method only returns true if an explicitly delimited expression is + * present in the string like "hello my name is ${name}" or "${foo.bar}". + * + * In general, a template expression is either: + *
    + *
  1. static literal text like "hello world". In this case, evaluating the expression simply returns the literal + * text. + *
  2. a single eval expression like ${requestParameters.foo}. In this case, evaluating the expression returns the + * evaluated value. + *
  3. a mix of literal text with one or more eval expressions like "hello #{name}". In this case, evaluating the + * expression returns a string with the result of #{name} evaluated (often called a composite expression). + *
+ * + * This method and the {@link #getAllowUndelimitedEvalExpressions()} flag primarily exist for compatibility reasons. + * The OgnlExpressionParser in SWF 1.0 does not treat literal text like "hello world" as a template expression, but + * rather a standard, evaluatable OGNL expression. Therefore, callers expecting standard template evaluation + * semantics are expected to work with these literal string values themselves, and not pass those strings to + * {@link #parseExpression(String, ParserContext)}. + * + * @param string the string + * @return true if the string is a template expression, false otherwise. + */ + public boolean isTemplateExpression(String string) { + if (!allowUndelimitedEvalExpressions) { + // every string provided is a "template" style expression - return true + return true; + } + // only returns true when there is ${} somewhere in the string + // this is version 1.0 semantics, there for compatability reasons + int prefixIndex = string.indexOf(getExpressionPrefix()); + if (prefixIndex == -1) { + return false; + } + int suffixIndex = string.indexOf(getExpressionSuffix(), prefixIndex); + if (suffixIndex == -1) { + return false; + } else { + // make sure there is actually something inside the ${} + if (suffixIndex == prefixIndex + getExpressionPrefix().length()) { + return false; + } else { + return true; + } + } + } + + // helper methods + + /** + * 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 + * pieces of text will also be returned as Expressions that just return that static piece of text. As a result, + * evaluating all returned expressions and concatenating the results produces the complete evaluated string. + * @param expressionString the expression string + * @return the parsed expressions + * @throws ParserException when the expressions cannot be parsed + */ + private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParserException { + List expressions = new LinkedList(); + int startIdx = 0; + while (startIdx < expressionString.length()) { + int prefixIndex = expressionString.indexOf(getExpressionPrefix(), startIdx); + if (prefixIndex >= startIdx) { + // a inner expression was found - this is a composite + if (prefixIndex > startIdx) { + expressions.add(new StaticExpression(expressionString.substring(startIdx, prefixIndex))); + startIdx = prefixIndex; + } + int nextPrefixIndex = expressionString.indexOf(getExpressionPrefix(), prefixIndex + + getExpressionPrefix().length()); + int suffixIndex; + if (nextPrefixIndex == -1) { + // this is the last expression in the expression string + suffixIndex = expressionString.lastIndexOf(getExpressionSuffix()); + } else { + // another expression exists after this one in the expression string + suffixIndex = expressionString.lastIndexOf(getExpressionSuffix(), nextPrefixIndex); + } + if (suffixIndex < (prefixIndex + getExpressionPrefix().length())) { + throw new ParserException(expressionString, "No ending suffix '" + getExpressionSuffix() + + "' for expression starting at character " + prefixIndex + ": " + + expressionString.substring(prefixIndex), null); + } else if (suffixIndex == prefixIndex + getExpressionPrefix().length()) { + throw new ParserException(expressionString, "No expression defined within delimiter '" + + getExpressionPrefix() + getExpressionSuffix() + "' at character " + prefixIndex, null); + } else { + String expr = expressionString.substring(prefixIndex + getExpressionPrefix().length(), suffixIndex); + expressions.add(doParseExpression(expr, context)); + startIdx = suffixIndex + 1; + } + } else { + if (startIdx == 0) { + // treat the entire string as one expression + if (allowUndelimitedEvalExpressions) { + expressions.add(doParseExpression(expressionString, context)); + } else { + // treat entire string as a literal + expressions.add(new StaticExpression(expressionString)); + } + } else { + // no more ${expressions} found in string, add rest as static text + expressions.add(new StaticExpression(expressionString.substring(startIdx))); + } + startIdx = expressionString.length(); + } + } + return (Expression[]) expressions.toArray(new Expression[expressions.size()]); + } + + private Expression doParseExpression(String expressionString, ParserContext context) throws ParserException { + if (context == null) { + context = NullParserContext.INSTANCE; + } + try { + return new OgnlExpression(Ognl.parseExpression(expressionString), parseVariableExpressions(context + .getExpressionVariables()), context.getExpectedEvaluationResultType()); + } catch (OgnlException e) { + throw new ParserException(expressionString, e); + } + } + + private Map parseVariableExpressions(ExpressionVariable[] variables) throws OgnlException { + if (variables == null || variables.length == 0) { + return null; + } + Map variableExpressions = new HashMap(variables.length, 1); + for (int i = 0; i < variables.length; i++) { + ExpressionVariable var = variables[i]; + variableExpressions.put(var.getName(), parseExpression(var.getValueExpression(), null)); + } + return variableExpressions; + } } \ No newline at end of file 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 deleted file mode 100644 index 1b6ee43e..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/expression/support/AbstractExpressionParser.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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 java.util.LinkedList; -import java.util.List; - -import org.springframework.binding.expression.Expression; -import org.springframework.binding.expression.ExpressionParser; -import org.springframework.binding.expression.ParserContext; -import org.springframework.binding.expression.ParserException; -import org.springframework.util.Assert; - -/** - * Abstract base class for expression parsers. - * - * @author Keith Donald - * @author Erwin Vervaet - */ -public abstract class AbstractExpressionParser implements ExpressionParser { - - /** - * The expression prefix. - */ - private static final String DEFAULT_EXPRESSION_PREFIX = "${"; - - /** - * The expression suffix. - */ - private static final String DEFAULT_EXPRESSION_SUFFIX = "}"; - - /** - * The marked expression delimter prefix. - */ - private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX; - - /** - * The marked expression delimiter suffix. - */ - private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX; - - /** - * Returns the configured expression delimiter prefix. Defaults to "${". - */ - public String getExpressionPrefix() { - return expressionPrefix; - } - - /** - * Sets the expression delimiter prefix. - */ - public void setExpressionPrefix(String expressionPrefix) { - this.expressionPrefix = expressionPrefix; - } - - /** - * Returns the expression delimiter suffix. Defaults to "}". - */ - public String getExpressionSuffix() { - return expressionSuffix; - } - - /** - * Sets the expression delimiter suffix. - */ - public void setExpressionSuffix(String expressionSuffix) { - this.expressionSuffix = expressionSuffix; - } - - public boolean hasDelimitedExpression(String string) { - int prefixIndex = string.indexOf(getExpressionPrefix()); - if (prefixIndex == -1) { - return false; - } - int suffixIndex = string.indexOf(getExpressionSuffix(), prefixIndex); - if (suffixIndex == -1) { - return false; - } else { - // make sure there is actually something inside the ${} - if (suffixIndex == prefixIndex + getExpressionPrefix().length()) { - return false; - } else { - return true; - } - } - } - - public Expression parseExpression(String expressionString, ParserContext context) throws ParserException { - Assert.notNull(expressionString, "The expression string to parse is required"); - Expression[] expressions = parseExpressions(expressionString, context); - if (expressions.length == 1) { - return expressions[0]; - } else { - return new CompositeStringExpression(expressions); - } - } - - /** - * 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 - * pieces of text will also be returned as Expressions that just return that static piece of text. As a result, - * evaluating all returned expressions and concatenating the results produces the complete evaluated string. - * @param expressionString the expression string - * @return the parsed expressions - * @throws ParserException when the expressions cannot be parsed - */ - private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParserException { - List expressions = new LinkedList(); - int startIdx = 0; - while (startIdx < expressionString.length()) { - int prefixIndex = expressionString.indexOf(getExpressionPrefix(), startIdx); - if (prefixIndex >= startIdx) { - // a inner expression was found - this is a composite - if (prefixIndex > startIdx) { - expressions.add(new StaticExpression(expressionString.substring(startIdx, prefixIndex))); - startIdx = prefixIndex; - } - int nextPrefixIndex = expressionString.indexOf(getExpressionPrefix(), prefixIndex - + getExpressionPrefix().length()); - int suffixIndex; - if (nextPrefixIndex == -1) { - // this is the last expression in the expression string - suffixIndex = expressionString.lastIndexOf(getExpressionSuffix()); - } else { - // another expression exists after this one in the expression string - suffixIndex = expressionString.lastIndexOf(getExpressionSuffix(), nextPrefixIndex); - } - if (suffixIndex < (prefixIndex + getExpressionPrefix().length())) { - throw new ParserException(expressionString, "No ending suffix '" + getExpressionSuffix() - + "' for expression starting at character " + prefixIndex + ": " - + expressionString.substring(prefixIndex), null); - } else if (suffixIndex == prefixIndex + getExpressionPrefix().length()) { - throw new ParserException(expressionString, "No expression defined within delimiter '" - + getExpressionPrefix() + getExpressionSuffix() + "' at character " + prefixIndex, null); - } else { - String expr = expressionString.substring(prefixIndex + getExpressionPrefix().length(), suffixIndex); - expressions.add(doParseExpression(expr, context)); - startIdx = suffixIndex + 1; - } - } else { - if (startIdx == 0) { - // treat the entire string as one expression - expressions.add(doParseExpression(expressionString, context)); - } else { - // no more ${expressions} found in string, add rest as static text - expressions.add(new StaticExpression(expressionString.substring(startIdx))); - } - startIdx = expressionString.length(); - } - } - return (Expression[]) expressions.toArray(new Expression[expressions.size()]); - } - - // template methods - - /** - * Template method for parsing a filtered expression string. Subclasses should override. - * @param expressionString the expression string - * @return the parsed expression - * @throws ParserException an exception occurred during parsing - */ - protected abstract Expression doParseExpression(String expressionString, ParserContext context) - throws ParserException; - -} \ No newline at end of file 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 bf820c68..b447bf65 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 @@ -46,12 +46,6 @@ public class ELExpressionParserTests extends TestCase { assertEquals(new Integer(7), exp.getValue(null)); } - public void testAssignment() { - String expressionString = "#{value = 12345}"; - Expression exp = parser.parseExpression(expressionString, new ParserContextImpl().expect(Integer.class)); - assertEquals(new Integer(7), exp.getValue(null)); - } - public void testParseBeanEvalExpressionNoParserContext() { String expressionString = "#{value}"; Expression exp = parser.parseExpression(expressionString, null); @@ -140,7 +134,7 @@ public class ELExpressionParserTests extends TestCase { } private static class TestELContextFactory implements ELContextFactory { - public ELContext getELContext(final Object target, final VariableMapper variableMapper) { + public ELContext getELContext(final Object target) { return new ELContext() { public ELResolver getELResolver() { return new ELResolver() { @@ -179,7 +173,7 @@ public class ELExpressionParserTests extends TestCase { } public VariableMapper getVariableMapper() { - return variableMapper; + return null; } }; } 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 d2834958..43673d03 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 @@ -22,9 +22,6 @@ 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}. - */ public class OgnlExpressionParserTests extends TestCase { private OgnlExpressionParser parser = new OgnlExpressionParser(); @@ -33,14 +30,31 @@ public class OgnlExpressionParserTests extends TestCase { public void testParseSimple() { String exp = "${flag}"; - assertTrue(parser.hasDelimitedExpression(exp)); Expression e = parser.parseExpression(exp, null); assertNotNull(e); Boolean b = (Boolean) e.getValue(bean); assertFalse(b.booleanValue()); } - public void testHasDelimitedExpression() { + public void testIsTemplateExpressionAllowUndelimitedOgnlExpressions() { + String exp = "some literal text"; + String exp2 = "${a delimited expression}"; + String exp3 = "${a malformed delimited expression"; + String exp4 = "a malformed delimited expression}"; + String exp5 = "${}"; + String exp6 = "a ${composite} expression"; + String exp7 = "a ${composite} ${malformed expression"; + assertTrue(parser.isTemplateExpression(exp)); + assertTrue(parser.isTemplateExpression(exp2)); + assertTrue(parser.isTemplateExpression(exp3)); + assertTrue(parser.isTemplateExpression(exp4)); + assertTrue(parser.isTemplateExpression(exp5)); + assertTrue(parser.isTemplateExpression(exp6)); + assertTrue(parser.isTemplateExpression(exp7)); + } + + public void testIsTemplateExpressionDoNotAllowUndelimitedOgnlExpressions() { + parser.setAllowUndelimitedEvalExpressions(true); String exp = "some literal text"; String exp2 = "${a delimited expression}"; String exp3 = "${a malformed delimited expression"; @@ -49,24 +63,31 @@ public class OgnlExpressionParserTests extends TestCase { String exp6 = "a ${composite} expression"; String exp7 = "a ${composite} ${malformed expression"; - assertFalse(parser.hasDelimitedExpression(exp)); - assertTrue(parser.hasDelimitedExpression(exp2)); - assertFalse(parser.hasDelimitedExpression(exp3)); - assertFalse(parser.hasDelimitedExpression(exp4)); - assertFalse(parser.hasDelimitedExpression(exp5)); - assertTrue(parser.hasDelimitedExpression(exp6)); - assertTrue(parser.hasDelimitedExpression(exp7)); + assertFalse(parser.isTemplateExpression(exp)); + assertTrue(parser.isTemplateExpression(exp2)); + assertFalse(parser.isTemplateExpression(exp3)); + assertFalse(parser.isTemplateExpression(exp4)); + assertFalse(parser.isTemplateExpression(exp5)); + assertTrue(parser.isTemplateExpression(exp6)); + assertTrue(parser.isTemplateExpression(exp7)); } - public void testParseSimpleNotDelimited() { + public void testParseSimpleNotDelimitedAllowUndelimited() { + parser.setAllowUndelimitedEvalExpressions(true); String exp = "flag"; - assertFalse(parser.hasDelimitedExpression(exp)); Expression e = parser.parseExpression(exp, null); assertNotNull(e); Boolean b = (Boolean) e.getValue(bean); assertFalse(b.booleanValue()); } + public void testParseSimpleLiteral() { + String exp = "flag"; + Expression e = parser.parseExpression(exp, null); + assertNotNull(e); + assertEquals("flag", e.getValue(bean)); + } + public void testParseEmpty() { Expression e = parser.parseExpression("", null); assertNotNull(e); @@ -75,7 +96,7 @@ public class OgnlExpressionParserTests extends TestCase { public void testParseComposite() { String exp = "hello ${flag} ${flag} ${flag}"; - assertTrue(parser.hasDelimitedExpression(exp)); + assertTrue(parser.isTemplateExpression(exp)); Expression e = parser.parseExpression(exp, null); assertNotNull(e); String str = (String) e.getValue(bean); @@ -142,7 +163,7 @@ public class OgnlExpressionParserTests extends TestCase { public void testVariables() { Expression exp = parser.parseExpression("${#var}", new ParserContextImpl().variable(new ExpressionVariable( - "var", new Integer(1)))); - assertEquals(new Integer(1), exp.getValue(bean)); + "var", "${flag}"))); + assertEquals(false, ((Boolean) exp.getValue(bean)).booleanValue()); } } \ No newline at end of file