/* * Copyright 2004-2012 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.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; 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; /** * An expression parser that parses Ognl expressions. * * @author Keith Donald */ 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 delimiter prefix. */ private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX; /** * The marked expression delimiter suffix. */ private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX; /** * Should we allow delimited eval expressions like "${foo.bar}"? If not, evalutable expressions must not be enclosed * in delimiters like ${foo.bar} else an exception is thrown. Only here for compatability reasons, as Web Flow 1.0 * allows delimited eval expressions while 2.x does not. */ private boolean allowDelimitedEvalExpressions; /** * 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 allows delimited eval expressions like <code>${foo.bar}</code>. */ public boolean getAllowDelimitedEvalExpressions() { return allowDelimitedEvalExpressions; } /** * Sets if this parser allows eval expressions like ${foo.bar}. */ public void setAllowDelimitedEvalExpressions(boolean allowDelmitedEvalExpressions) { this.allowDelimitedEvalExpressions = allowDelmitedEvalExpressions; } // expression parser public Expression parseExpression(String expressionString, ParserContext context) throws ParserException { Assert.notNull(expressionString, "The expression string to parse is required"); if (context == null) { context = NullParserContext.INSTANCE; } if (context.isTemplate()) { return parseTemplate(expressionString, context); } else { if (expressionString.startsWith(getExpressionPrefix()) && expressionString.endsWith(getExpressionSuffix())) { if (!allowDelimitedEvalExpressions) { throw new ParserException( expressionString, "The expression '" + expressionString + "' being parsed is expected be a standard OGNL expression. Do not attempt to enclose such expression strings in ${} delimiters--this is redundant. If you need to parse a template that mixes literal text with evaluatable blocks, set the 'template' parser context attribute to true.", null); } else { int lastIndex = expressionString.length() - getExpressionSuffix().length(); String ognlExpression = expressionString.substring(getExpressionPrefix().length(), lastIndex); return doParseExpression(ognlExpression, context); } } else { return doParseExpression(expressionString, context); } } } private Expression parseTemplate(String expressionString, ParserContext context) throws ParserException { Assert.notNull(expressionString, "The expression string to parse is required"); if (expressionString.length() == 0) { return parseEmptyExpressionString(context); } Expression[] expressions = parseExpressions(expressionString, context); if (expressions.length == 1) { return expressions[0]; } else { return new CompositeStringExpression(expressions); } } // helper methods /** * Helper that handles a empty expression string. */ private Expression parseEmptyExpressionString(ParserContext context) { if (allowDelimitedEvalExpressions) { // let the parser handle it return doParseExpression("", context); } else { // return a literal expression containing the empty string return new LiteralExpression(""); } } /** * 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<Expression> expressions = new LinkedList<Expression>(); 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 LiteralExpression(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 (allowDelimitedEvalExpressions) { expressions.add(doParseExpression(expressionString, context)); } else { // treat entire string as a literal expressions.add(new LiteralExpression(expressionString)); } } else { // no more ${expressions} found in string, add rest as static text expressions.add(new LiteralExpression(expressionString.substring(startIdx))); } startIdx = expressionString.length(); } } return expressions.toArray(new Expression[expressions.size()]); } protected Map<String, Expression> parseVariableExpressions(ExpressionVariable[] variables) throws ParserException { if (variables == null || variables.length == 0) { return null; } Map<String, Expression> variableExpressions = new HashMap<String, Expression>(variables.length, 1); for (ExpressionVariable var : variables) { variableExpressions.put(var.getName(), parseExpression(var.getValueExpression(), var.getParserContext())); } return variableExpressions; } protected abstract Expression doParseExpression(String expressionString, ParserContext context) throws ParserException; }