OgnlExpressionParser now supports OGNL collection construction syntax (SWF-274).
This commit is contained in:
@@ -120,7 +120,7 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
|
||||
* 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 concating the results produces the complete evaluated string.
|
||||
* 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
|
||||
@@ -137,8 +137,18 @@ public abstract class AbstractExpressionParser implements ExpressionParser {
|
||||
expressions.add(new StaticExpression(expressionString.substring(startIdx, prefixIndex)));
|
||||
startIdx = prefixIndex;
|
||||
}
|
||||
int suffixIndex = expressionString.indexOf(getExpressionSuffix(), prefixIndex);
|
||||
if (suffixIndex == -1) {
|
||||
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);
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
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;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Converter that takes an encoded string representation and produces a
|
||||
@@ -69,27 +71,30 @@ public class TextToMethodSignature extends ConversionServiceAwareConverter {
|
||||
}
|
||||
|
||||
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
|
||||
String encodedMethodKey = (String)source;
|
||||
encodedMethodKey = encodedMethodKey.trim();
|
||||
int openParan = encodedMethodKey.indexOf('(');
|
||||
String encodedMethodSignature = (String)source;
|
||||
encodedMethodSignature = encodedMethodSignature.trim();
|
||||
int openParan = encodedMethodSignature.indexOf('(');
|
||||
if (openParan == -1) {
|
||||
return new MethodSignature(encodedMethodKey);
|
||||
// form "foo"
|
||||
return new MethodSignature(encodedMethodSignature);
|
||||
}
|
||||
else {
|
||||
String methodName = encodedMethodKey.substring(0, openParan);
|
||||
int closeParan = encodedMethodKey.lastIndexOf(')');
|
||||
if (closeParan == -1) {
|
||||
throw new ConversionException(encodedMethodKey, MethodSignature.class,
|
||||
// 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 delimParamList = encodedMethodKey.substring(openParan + 1, closeParan);
|
||||
String[] paramArray = StringUtils.commaDelimitedListToStringArray(delimParamList);
|
||||
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(' ');
|
||||
if (space == -1 || space > param.indexOf('$')) {
|
||||
int expr = param.indexOf('{');
|
||||
if (space == -1 || (expr != -1 && space > expr)) {
|
||||
// "name" or "${name}"
|
||||
params.add(new Parameter(null, parseExpression(param)));
|
||||
}
|
||||
@@ -103,4 +108,41 @@ public class TextToMethodSignature extends ConversionServiceAwareConverter {
|
||||
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()]);
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,13 @@ public class OgnlExpressionParserTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testSyntaxError1() {
|
||||
try {
|
||||
parser.parseExpression("${");
|
||||
fail();
|
||||
}
|
||||
catch (ParserException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
String exp = "hello ${flag} ${abcd defg";
|
||||
parser.parseExpression(exp);
|
||||
@@ -86,6 +93,13 @@ public class OgnlExpressionParserTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testSyntaxError2() {
|
||||
try {
|
||||
parser.parseExpression("${}");
|
||||
fail("Should've failed - not intended use");
|
||||
}
|
||||
catch (ParserException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
String exp = "hello ${flag} ${}";
|
||||
parser.parseExpression(exp);
|
||||
@@ -94,7 +108,7 @@ public class OgnlExpressionParserTests extends TestCase {
|
||||
catch (ParserException e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testIsDelimitedExpression() {
|
||||
assertTrue(parser.isDelimitedExpression("${foo}"));
|
||||
assertTrue(parser.isDelimitedExpression("${foo ${foo}}"));
|
||||
@@ -107,4 +121,25 @@ public class OgnlExpressionParserTests extends TestCase {
|
||||
assertFalse(parser.isDelimitedExpression("$foo}"));
|
||||
assertFalse(parser.isDelimitedExpression("foo ${}"));
|
||||
}
|
||||
|
||||
public void testCollectionContructionSyntax() {
|
||||
// lists
|
||||
parser.parseExpression("name in {null, \"Untitled\"}");
|
||||
parser.parseExpression("${name in {null, \"Untitled\"}}");
|
||||
|
||||
// native arrays
|
||||
parser.parseExpression("new int[] {1, 2, 3}");
|
||||
parser.parseExpression("${new int[] {1, 2, 3}}");
|
||||
|
||||
// maps
|
||||
parser.parseExpression("#{ 'foo' : 'foo value', 'bar' : 'bar value' }");
|
||||
parser.parseExpression("${#{ 'foo' : 'foo value', 'bar' : 'bar value' }}");
|
||||
parser.parseExpression("#@java.util.LinkedHashMap@{ 'foo' : 'foo value', 'bar' : 'bar value' }");
|
||||
parser.parseExpression("${#@java.util.LinkedHashMap@{ 'foo' : 'foo value', 'bar' : 'bar value' }}");
|
||||
|
||||
// complex examples
|
||||
parser.parseExpression("b,#{1:2}");
|
||||
parser.parseExpression("${b,#{1:2}}");
|
||||
parser.parseExpression("a${b,#{1:2},e}f${g,#{3:4},j}k");
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package org.springframework.binding.method;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.binding.convert.ConversionException;
|
||||
import org.springframework.binding.convert.support.DefaultConversionService;
|
||||
import org.springframework.binding.convert.support.TextToExpression;
|
||||
import org.springframework.binding.expression.support.OgnlExpressionParser;
|
||||
@@ -41,7 +42,7 @@ public class TextToMethodSignatureTests extends TestCase {
|
||||
assertEquals("foo", signature.getMethodName());
|
||||
assertEquals(0, signature.getParameters().size());
|
||||
|
||||
signature = (MethodSignature)converter.convert("foo");
|
||||
signature = (MethodSignature)converter.convert("foo()");
|
||||
assertEquals("foo", signature.getMethodName());
|
||||
assertEquals(0, signature.getParameters().size());
|
||||
}
|
||||
@@ -94,4 +95,45 @@ public class TextToMethodSignatureTests extends TestCase {
|
||||
assertEquals(String.class, signature.getParameters().getParameter(1).getType());
|
||||
assertEquals("externalContext.requestParameterMap.test", signature.getParameters().getParameter(1).getName().toString());
|
||||
}
|
||||
|
||||
public void testCollectionConstructionSyntax() {
|
||||
MethodSignature signature = (MethodSignature)converter.convert("foo({1, 2, 3})");
|
||||
assertEquals("foo", signature.getMethodName());
|
||||
assertEquals(1, signature.getParameters().size());
|
||||
assertNull(signature.getParameters().getParameter(0).getType());
|
||||
assertEquals("{1, 2, 3}", signature.getParameters().getParameter(0).getName().toString());
|
||||
}
|
||||
|
||||
public void testCollectionConstructionSyntaxWithType() {
|
||||
MethodSignature signature = (MethodSignature)converter.convert("foo(java.util.List {1, 2, 3})");
|
||||
assertEquals("foo", signature.getMethodName());
|
||||
assertEquals(1, signature.getParameters().size());
|
||||
assertEquals(java.util.List.class, signature.getParameters().getParameter(0).getType());
|
||||
assertEquals("{1, 2, 3}", signature.getParameters().getParameter(0).getName().toString());
|
||||
|
||||
signature = (MethodSignature)converter.convert("foo(a${b,#{1:2},e}f${g,#{3:4},j}k)");
|
||||
}
|
||||
|
||||
public void testSyntaxErrors() {
|
||||
try {
|
||||
converter.convert("foo(");
|
||||
fail();
|
||||
}
|
||||
catch (ConversionException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
converter.convert("foo(long 1, ${bar()}");
|
||||
fail();
|
||||
}
|
||||
catch (ConversionException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
converter.convert("foo(long 1, {1, 2)");
|
||||
fail();
|
||||
}
|
||||
catch (ConversionException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ Package org.springframework.binding
|
||||
* Fixed possbile NullPointerException in MethodKey.parameterTypesString() (SWF-265).
|
||||
* OgnlExpression now unwraps the ognl.OgnlException to make the real exception available to the
|
||||
caller (SWF-255).
|
||||
* OgnlExpressionParser now supports OGNL collection construction syntax (SWF-274).
|
||||
|
||||
Package org.springframework.webflow.action
|
||||
* FormAction methods doBind() and createBinder() now declare "throws Exception".
|
||||
|
||||
Reference in New Issue
Block a user