All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.expression.common.TemplateAwareExpressionParser Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright 2002-2024 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
 *
 *      https://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.expression.common;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Abstract base class for {@linkplain ExpressionParser expression parsers} that
 * support templates.
 *
 * 

Can be subclassed by expression parsers that offer first class support for * templating. * * @author Keith Donald * @author Juergen Hoeller * @author Andy Clement * @author Sam Brannen * @since 3.0 */ public abstract class TemplateAwareExpressionParser implements ExpressionParser { @Override public Expression parseExpression(String expressionString) throws ParseException { return parseExpression(expressionString, null); } @Override public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException { if (context != null && context.isTemplate()) { Assert.notNull(expressionString, "'expressionString' must not be null"); return parseTemplate(expressionString, context); } else { Assert.hasText(expressionString, "'expressionString' must not be null or blank"); return doParseExpression(expressionString, context); } } private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException { if (expressionString.isEmpty()) { return new LiteralExpression(""); } Expression[] expressions = parseExpressions(expressionString, context); if (expressions.length == 1) { return expressions[0]; } else { return new CompositeStringExpression(expressionString, 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. Unwrapping is only done of the outermost delimiters * found, so the string 'hello ${foo${abc}}' would break into the pieces 'hello ' and * 'foo${abc}'. This means that expression languages that use ${..} as part of their * functionality are supported without any problem. The parsing is aware of the * structure of an embedded expression. It assumes that parentheses '(', square * brackets '[', and curly brackets '}' must be in pairs within the expression unless * they are within a string literal and the string literal starts and terminates with a * single quote '. * @param expressionString the expression string * @return the parsed expressions * @throws ParseException if the expressions cannot be parsed */ private Expression[] parseExpressions(String expressionString, ParserContext context) throws ParseException { List expressions = new ArrayList<>(); String prefix = context.getExpressionPrefix(); String suffix = context.getExpressionSuffix(); int startIdx = 0; while (startIdx < expressionString.length()) { int prefixIndex = expressionString.indexOf(prefix, startIdx); if (prefixIndex >= startIdx) { // an inner expression was found - this is a composite if (prefixIndex > startIdx) { expressions.add(new LiteralExpression(expressionString.substring(startIdx, prefixIndex))); } int afterPrefixIndex = prefixIndex + prefix.length(); int suffixIndex = skipToCorrectEndSuffix(suffix, expressionString, afterPrefixIndex); if (suffixIndex == -1) { throw new ParseException(expressionString, prefixIndex, "No ending suffix '" + suffix + "' for expression starting at character " + prefixIndex + ": " + expressionString.substring(prefixIndex)); } if (suffixIndex == afterPrefixIndex) { throw new ParseException(expressionString, prefixIndex, "No expression defined within delimiter '" + prefix + suffix + "' at character " + prefixIndex); } String expr = expressionString.substring(prefixIndex + prefix.length(), suffixIndex); expr = expr.trim(); if (expr.isEmpty()) { throw new ParseException(expressionString, prefixIndex, "No expression defined within delimiter '" + prefix + suffix + "' at character " + prefixIndex); } expressions.add(doParseExpression(expr, context)); startIdx = suffixIndex + suffix.length(); } 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[0]); } /** * Return true if the specified suffix can be found at the supplied position in the * supplied expression string. * @param expressionString the expression string which may contain the suffix * @param pos the start position at which to check for the suffix * @param suffix the suffix string */ private boolean isSuffixHere(String expressionString, int pos, String suffix) { int suffixPosition = 0; for (int i = 0; i < suffix.length() && pos < expressionString.length(); i++) { if (expressionString.charAt(pos++) != suffix.charAt(suffixPosition++)) { return false; } } if (suffixPosition != suffix.length()) { // the expressionString ran out before the suffix could entirely be found return false; } return true; } /** * Copes with nesting, for example '${...${...}}' where the correct end for the first * ${ is the final }. * @param suffix the suffix * @param expressionString the expression string * @param afterPrefixIndex the most recently found prefix location for which the * matching end suffix is being sought * @return the position of the correct matching nextSuffix or -1 if none can be found */ private int skipToCorrectEndSuffix(String suffix, String expressionString, int afterPrefixIndex) throws ParseException { // Chew on the expression text - relying on the rules: // brackets must be in pairs: () [] {} // string literals are "..." or '...' and these may contain unmatched brackets int pos = afterPrefixIndex; int maxlen = expressionString.length(); int nextSuffix = expressionString.indexOf(suffix, afterPrefixIndex); if (nextSuffix == -1) { return -1; // the suffix is missing } Deque stack = new ArrayDeque<>(); while (pos < maxlen) { if (isSuffixHere(expressionString, pos, suffix) && stack.isEmpty()) { break; } char ch = expressionString.charAt(pos); switch (ch) { case '{', '[', '(' -> { stack.push(new Bracket(ch, pos)); } case '}', ']', ')' -> { if (stack.isEmpty()) { throw new ParseException(expressionString, pos, "Found closing '" + ch + "' at position " + pos + " without an opening '" + Bracket.theOpenBracketFor(ch) + "'"); } Bracket p = stack.pop(); if (!p.compatibleWithCloseBracket(ch)) { throw new ParseException(expressionString, pos, "Found closing '" + ch + "' at position " + pos + " but most recent opening is '" + p.bracket + "' at position " + p.pos); } } case '\'', '"' -> { // jump to the end of the literal int endLiteral = expressionString.indexOf(ch, pos + 1); if (endLiteral == -1) { throw new ParseException(expressionString, pos, "Found non terminating string literal starting at position " + pos); } pos = endLiteral; } } pos++; } if (!stack.isEmpty()) { Bracket p = stack.pop(); throw new ParseException(expressionString, p.pos, "Missing closing '" + Bracket.theCloseBracketFor(p.bracket) + "' for '" + p.bracket + "' at position " + p.pos); } if (!isSuffixHere(expressionString, pos, suffix)) { return -1; } return pos; } /** * Actually parse the expression string and return an Expression object. * @param expressionString the raw expression string to parse * @param context a context for influencing this expression parsing routine (optional) * @return an evaluator for the parsed expression * @throws ParseException if an exception occurred during parsing */ protected abstract Expression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException; /** * This captures a type of bracket and the position in which it occurs in the * expression. The positional information is used if an error has to be reported * because the related end bracket cannot be found. Bracket is used to describe * square brackets [], round brackets (), and curly brackets {}. */ private record Bracket(char bracket, int pos) { boolean compatibleWithCloseBracket(char closeBracket) { return switch (this.bracket) { case '{' -> closeBracket == '}'; case '[' -> closeBracket == ']'; default -> closeBracket == ')'; }; } static char theOpenBracketFor(char closeBracket) { return switch (closeBracket) { case '}' -> '{'; case ']' -> '['; default -> '('; }; } static char theCloseBracketFor(char openBracket) { return switch (openBracket) { case '{' -> '}'; case '[' -> ']'; default -> ')'; }; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy