ognl parser refinements
This commit is contained in:
@@ -32,7 +32,7 @@ public abstract class ConversionServiceAwareConverter extends AbstractConverter
|
||||
private ConversionService conversionService;
|
||||
|
||||
/**
|
||||
* Default constructor, expectes to conversion service to be injected using
|
||||
* Default constructor, expects to conversion service to be injected using
|
||||
* {@link #setConversionService(ConversionService)}.
|
||||
*/
|
||||
protected ConversionServiceAwareConverter() {
|
||||
|
||||
@@ -27,6 +27,8 @@ public interface ExpressionParser {
|
||||
* Is the provided expression string an "eval" expression: meaning an expression that validates to a dynamic value,
|
||||
* and not a literal expression? "Eval" expressions are normally enclosed in delimiters like #{}, where literal
|
||||
* expressions are not delimited.
|
||||
*
|
||||
* TODO - candidate for removal in a future milestone: is this really needed?
|
||||
* @param string the string
|
||||
* @return true if the expression is an eval expression string, false otherwise.
|
||||
*/
|
||||
@@ -37,6 +39,8 @@ public interface ExpressionParser {
|
||||
* against a target object. For example, the raw expression string "person.id" might become #{person.id}. If the
|
||||
* string is already an eval expression string, the string argument is returned unchanged. If the string is an
|
||||
* composite expression string that mixes eval and literal expressions, a parser exception is thrown.
|
||||
*
|
||||
* TODO - candidate for removal in a future milestone: is this really needed?
|
||||
* @param string the raw string to be transformed into a parseable eval expression string
|
||||
* @return the eval expression spring
|
||||
* @throws ParserException an exception occurred during parsing
|
||||
@@ -47,7 +51,7 @@ public interface ExpressionParser {
|
||||
* Parse the provided expression string, returning an expression evaluator capable of evaluating it. The expression
|
||||
* string may be a literal expression string like "foo", an eval-expression string like #{foo}, or a
|
||||
* composite-expression string like "foo#{foo}bar#{bar}".
|
||||
* @param expressionString the parseable expression string
|
||||
* @param expressionString the parseable expression string; cannot be null
|
||||
* @param expressionTargetType the class of target object this expression can successfully evaluate; for example,
|
||||
* <code>Map.class</code> for an expression that is expected to evaluate against Maps.
|
||||
* @param expectedEvaluationResultType the class of object this expression is expected to return or set: for
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.binding.expression.Expression;
|
||||
import org.springframework.binding.expression.ExpressionParser;
|
||||
import org.springframework.binding.expression.ExpressionVariable;
|
||||
import org.springframework.binding.expression.ParserException;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Abstract base class for expression parsers.
|
||||
@@ -90,6 +90,7 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
|
||||
|
||||
public Expression parseExpression(String expressionString, Class expressionTargetType,
|
||||
Class expectedEvaluationResultType, ExpressionVariable[] expressionVariables) throws ParserException {
|
||||
Assert.notNull(expressionString, "The expression string to parse is required");
|
||||
// TODO variables
|
||||
Expression[] expressions = parseExpressions(expressionString);
|
||||
if (expressions.length == 1) {
|
||||
@@ -118,52 +119,42 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
|
||||
*/
|
||||
private Expression[] parseExpressions(String expressionString) throws ParserException {
|
||||
List expressions = new LinkedList();
|
||||
if (StringUtils.hasText(expressionString)) {
|
||||
int startIdx = 0;
|
||||
while (startIdx < expressionString.length()) {
|
||||
int prefixIndex = expressionString.indexOf(getExpressionPrefix(), startIdx);
|
||||
if (prefixIndex >= startIdx) {
|
||||
// an expression was found
|
||||
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));
|
||||
startIdx = suffixIndex + 1;
|
||||
}
|
||||
} else {
|
||||
if (startIdx == 0) {
|
||||
// treat entire string as one expression
|
||||
expressions.add(doParseExpression(expressionString));
|
||||
} else {
|
||||
// no more ${expressions} found in string
|
||||
expressions.add(new StaticExpression(expressionString.substring(startIdx)));
|
||||
}
|
||||
startIdx = expressionString.length();
|
||||
int startIdx = 0;
|
||||
while (startIdx < expressionString.length()) {
|
||||
int prefixIndex = expressionString.indexOf(getExpressionPrefix(), startIdx);
|
||||
if (prefixIndex >= startIdx) {
|
||||
// an expression was found
|
||||
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));
|
||||
startIdx = suffixIndex + 1;
|
||||
}
|
||||
} else {
|
||||
// no more evaluatable ${expressions} found in string
|
||||
expressions.add(new StaticExpression(expressionString.substring(startIdx)));
|
||||
startIdx = expressionString.length();
|
||||
}
|
||||
} else {
|
||||
expressions.add(new StaticExpression(expressionString));
|
||||
}
|
||||
return (Expression[]) expressions.toArray(new Expression[expressions.size()]);
|
||||
}
|
||||
|
||||
@@ -1,143 +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.method;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.binding.convert.ConversionContext;
|
||||
import org.springframework.binding.convert.ConversionException;
|
||||
import org.springframework.binding.convert.ConversionService;
|
||||
import org.springframework.binding.convert.support.ConversionServiceAwareConverter;
|
||||
import org.springframework.binding.expression.Expression;
|
||||
|
||||
/**
|
||||
* Converter that takes an encoded string representation and produces a corresponding <code>MethodSignature</code>
|
||||
* object.
|
||||
* <p>
|
||||
* This converter supports the following encoded forms:
|
||||
* <ul>
|
||||
* <li> "methodName" - the name of the method to invoke, where the method is expected to have no arguments. </li>
|
||||
* <li> "methodName(param1Type param1Name, paramNType paramNName)" - the name of the method to invoke, where the method
|
||||
* is expected to have parameters delimited by a comma. In this example, the method has two parameters. The type is
|
||||
* either the fully-qualified class of the argument OR a known type alias OR left out althogether. The name is the
|
||||
* logical name of the argument, which is used during data binding to retrieve the argument value (typically an
|
||||
* expression). </li>
|
||||
* </ul>
|
||||
*
|
||||
* @see MethodSignature
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
*/
|
||||
public class TextToMethodSignature extends ConversionServiceAwareConverter {
|
||||
|
||||
/**
|
||||
* Create a new converter that converts strings to MethodSignature objects.
|
||||
*/
|
||||
public TextToMethodSignature() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new converter that converts strings to MethodSignature objects.
|
||||
* @param conversionService the conversion service to use
|
||||
*/
|
||||
public TextToMethodSignature(ConversionService conversionService) {
|
||||
super(conversionService);
|
||||
}
|
||||
|
||||
public Class[] getSourceClasses() {
|
||||
return new Class[] { String.class };
|
||||
}
|
||||
|
||||
public Class[] getTargetClasses() {
|
||||
return new Class[] { MethodSignature.class };
|
||||
}
|
||||
|
||||
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
|
||||
String encodedMethodSignature = (String) source;
|
||||
encodedMethodSignature = encodedMethodSignature.trim();
|
||||
int openParan = encodedMethodSignature.indexOf('(');
|
||||
if (openParan == -1) {
|
||||
// form "foo"
|
||||
return new MethodSignature(encodedMethodSignature);
|
||||
} else {
|
||||
// form "foo(...)"
|
||||
String methodName = encodedMethodSignature.substring(0, openParan);
|
||||
int closeParan = encodedMethodSignature.lastIndexOf(')');
|
||||
if (closeParan != (encodedMethodSignature.length() - 1)) {
|
||||
throw new ConversionException(encodedMethodSignature, MethodSignature.class,
|
||||
"Syntax error: No close parenthesis specified for method parameter list", null);
|
||||
}
|
||||
String delimitedParams = encodedMethodSignature.substring(openParan + 1, closeParan);
|
||||
String[] paramArray = splitParameters(encodedMethodSignature, delimitedParams);
|
||||
Parameters params = new Parameters(paramArray.length);
|
||||
for (int i = 0; i < paramArray.length; i++) {
|
||||
// param could be of the form "type name", "name", "type ${name}" or "${name}"
|
||||
String param = paramArray[i].trim();
|
||||
int space = param.indexOf(' ');
|
||||
int expr = param.indexOf('{');
|
||||
if (space == -1 || (expr != -1 && space > expr)) {
|
||||
// "name" or "${name}"
|
||||
params.add(new Parameter(null, parseExpression(param)));
|
||||
} else {
|
||||
// "type name" or "type ${name}"
|
||||
Class type = (Class) fromStringTo(Class.class).execute(param.substring(0, space).trim());
|
||||
Expression name = parseExpression(param.substring(space + 1).trim());
|
||||
params.add(new Parameter(type, name));
|
||||
}
|
||||
}
|
||||
return new MethodSignature(methodName, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split given parameter string into individual parameter definitions.
|
||||
*/
|
||||
private String[] splitParameters(String encodedMethodSignature, String parameters) {
|
||||
List res = new LinkedList();
|
||||
|
||||
int paramStart = 0;
|
||||
int blockNestingCount = 0;
|
||||
for (int i = 0; i < parameters.length(); i++) {
|
||||
switch (parameters.charAt(i)) {
|
||||
case '{':
|
||||
blockNestingCount++;
|
||||
break;
|
||||
case '}':
|
||||
blockNestingCount--;
|
||||
break;
|
||||
case ',':
|
||||
if (blockNestingCount == 0) {
|
||||
// only take comma delimiter into account when not inside
|
||||
// a block
|
||||
res.add(parameters.substring(paramStart, i));
|
||||
paramStart = i + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (blockNestingCount != 0) {
|
||||
throw new ConversionException(encodedMethodSignature, MethodSignature.class,
|
||||
"Syntax error: Curly braces do not match", null);
|
||||
}
|
||||
if (paramStart < parameters.length()) {
|
||||
res.add(parameters.substring(paramStart));
|
||||
}
|
||||
|
||||
return (String[]) res.toArray(new String[res.size()]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user