org.springframework.data.expression.DefaultValueExpressionParser Maven / Gradle / Ivy
The newest version!
/*
* Copyright 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.data.expression;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.springframework.util.SystemPropertyUtils;
/**
* Default {@link ValueExpressionParser} implementation. Instances are thread-safe.
*
* @author Mark Paluch
* @since 3.3
*/
class DefaultValueExpressionParser implements ValueExpressionParser {
public static final String PLACEHOLDER_PREFIX = SystemPropertyUtils.PLACEHOLDER_PREFIX;
public static final String EXPRESSION_PREFIX = ParserContext.TEMPLATE_EXPRESSION.getExpressionPrefix();
public static final char SUFFIX = '}';
public static final int PLACEHOLDER_PREFIX_LENGTH = PLACEHOLDER_PREFIX.length();
public static final char[] QUOTE_CHARS = { '\'', '"' };
public static final ValueExpressionParser DEFAULT = new DefaultValueExpressionParser(SpelExpressionParser::new);
private final ValueParserConfiguration configuration;
public DefaultValueExpressionParser(ValueParserConfiguration configuration) {
Assert.notNull(configuration, "ValueParserConfiguration must not be null");
this.configuration = configuration;
}
@Override
public ValueExpression parse(String expressionString) {
int placerholderIndex = expressionString.indexOf(PLACEHOLDER_PREFIX);
int expressionIndex = expressionString.indexOf(EXPRESSION_PREFIX);
if (placerholderIndex == -1 && expressionIndex == -1) {
return new LiteralValueExpression(expressionString);
}
if (placerholderIndex != -1 && expressionIndex == -1
&& findPlaceholderEndIndex(expressionString, placerholderIndex) != expressionString.length()) {
return createPlaceholder(expressionString);
}
if (placerholderIndex == -1
&& findPlaceholderEndIndex(expressionString, expressionIndex) != expressionString.length()) {
return createExpression(expressionString);
}
return parseComposite(expressionString, placerholderIndex, expressionIndex);
}
private CompositeValueExpression parseComposite(String expressionString, int placerholderIndex, int expressionIndex) {
List expressions = new ArrayList<>(PLACEHOLDER_PREFIX_LENGTH);
int startIndex = getStartIndex(placerholderIndex, expressionIndex);
if (startIndex != 0) {
expressions.add(new LiteralValueExpression(expressionString.substring(0, startIndex)));
}
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(expressionString, startIndex);
if (endIndex == -1) {
throw new ParseException(expressionString, startIndex,
"No ending suffix '}' for expression starting at character %d: %s".formatted(startIndex,
expressionString.substring(startIndex)));
}
int afterClosingParenthesisIndex = endIndex + 1;
String part = expressionString.substring(startIndex, afterClosingParenthesisIndex);
if (part.startsWith(PLACEHOLDER_PREFIX)) {
expressions.add(createPlaceholder(part));
} else {
expressions.add(createExpression(part));
}
placerholderIndex = expressionString.indexOf(PLACEHOLDER_PREFIX, endIndex);
expressionIndex = expressionString.indexOf(EXPRESSION_PREFIX, endIndex);
startIndex = getStartIndex(placerholderIndex, expressionIndex);
if (startIndex == -1) {
// no next expression but we're capturing everything after the expression as literal.
expressions.add(new LiteralValueExpression(expressionString.substring(afterClosingParenthesisIndex)));
} else {
// capture literal after the expression ends and before the next starts.
expressions
.add(new LiteralValueExpression(expressionString.substring(afterClosingParenthesisIndex, startIndex)));
}
}
return new CompositeValueExpression(expressionString, expressions);
}
private static int getStartIndex(int placerholderIndex, int expressionIndex) {
return placerholderIndex != -1 && expressionIndex != -1 ? Math.min(placerholderIndex, expressionIndex)
: placerholderIndex != -1 ? placerholderIndex : expressionIndex;
}
private PlaceholderExpression createPlaceholder(String part) {
return new PlaceholderExpression(part);
}
private ExpressionExpression createExpression(String expression) {
Expression expr = configuration.getExpressionParser().parseExpression(expression,
ParserContext.TEMPLATE_EXPRESSION);
ExpressionDependencies dependencies = ExpressionDependencies.discover(expr);
return new ExpressionExpression(expr, dependencies);
}
private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + PLACEHOLDER_PREFIX_LENGTH;
char quotationChar = 0;
char nestingLevel = 0;
boolean skipEscape = false;
while (index < buf.length()) {
char c = buf.charAt(index);
if (!skipEscape && c == '\\') {
skipEscape = true;
} else if (skipEscape) {
skipEscape = false;
} else if (quotationChar == 0) {
for (char quoteChar : QUOTE_CHARS) {
if (quoteChar == c) {
quotationChar = c;
break;
}
}
} else if (quotationChar == c) {
quotationChar = 0;
}
if (!skipEscape && quotationChar == 0) {
if (nestingLevel != 0 && c == SUFFIX) {
nestingLevel--;
} else if (c == '{') {
nestingLevel++;
} else if (nestingLevel == 0 && c == SUFFIX) {
return index;
}
}
index++;
}
return -1;
}
}