further EL improvements - OGNL behavior for variable handling is now correct, EL impl simplfiied.

This commit is contained in:
Keith Donald
2008-01-21 23:03:33 +00:00
parent c181b5d075
commit 72fe878fc9
11 changed files with 314 additions and 289 deletions

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

@@ -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) {

View File

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

View File

@@ -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() {

View File

@@ -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 <code>foo.bar</code>.
*/
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 <code>true</code> if this
* OGNL expression parser is configured to <b>not</b> 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:
* <ol>
* <li>static literal text like "hello world". In this case, evaluating the expression simply returns the literal
* text.
* <li>a single eval expression like ${requestParameters.foo}. In this case, evaluating the expression returns the
* evaluated value.
* <li>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).
* </ol>
*
* 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;
}
}

View File

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