diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java index beef14db..9c5c2cef 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java @@ -1,9 +1,12 @@ package com.jayway.jsonpath.internal.path; +import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.PathRef; import com.jayway.jsonpath.internal.function.PathFunction; import com.jayway.jsonpath.internal.function.PathFunctionFactory; +import java.util.List; + /** * Token representing a Function call to one of the functions produced via the FunctionFactory * @@ -15,13 +18,16 @@ public class FunctionPathToken extends PathToken { private final String functionName; private final String pathFragment; + private final List functionParams; - public FunctionPathToken(String pathFragment) { + public FunctionPathToken(String pathFragment, List parameters) { this.pathFragment = pathFragment; - if(pathFragment.endsWith("()")){ - functionName = pathFragment.substring(0, pathFragment.length()-2); + if(null != pathFragment){ + functionName = pathFragment; + functionParams = parameters; } else { functionName = null; + functionParams = null; } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java index eccd1189..dd4e22c4 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java @@ -22,6 +22,9 @@ public class PathCompiler { private static final char OPEN_SQUARE_BRACKET = '['; private static final char CLOSE_SQUARE_BRACKET = ']'; private static final char OPEN_PARENTHESIS = '('; + private static final char CLOSE_PARENTHESIS = ')'; + private static final char OPEN_BRACE = '{'; + private static final char CLOSE_BRACE = '}'; private static final char WILDCARD = '*'; private static final char PERIOD = '.'; @@ -151,26 +154,48 @@ public class PathCompiler { int readPosition = startPosition; int endPosition = 0; + boolean isFunction = false; + while (path.inBounds(readPosition)) { char c = path.charAt(readPosition); if (c == SPACE) { throw new InvalidPathException("Use bracket notion ['my prop'] if your property contains blank characters. position: " + path.position()); } - if (c == PERIOD || c == OPEN_SQUARE_BRACKET) { + else if (c == PERIOD || c == OPEN_SQUARE_BRACKET) { endPosition = readPosition; break; } + else if (c == OPEN_PARENTHESIS) { + isFunction = true; + endPosition = readPosition++; + break; + } readPosition++; } if (endPosition == 0) { endPosition = path.length(); } - path.setPosition(endPosition); + + List functionParameters = null; + if (isFunction) { + // read the next token to determine if we have a simple no-args function call + char c = path.charAt(readPosition++); + if (c != CLOSE_PARENTHESIS) { + // parse the arguments of the function - arguments that are inner queries will be single quoted parameters + functionParameters = parseFunctionParameters(readPosition); + } + else { + path.setPosition(readPosition); + } + } + else { + path.setPosition(endPosition); + } String property = path.subSequence(startPosition, endPosition).toString(); - if(property.endsWith("()")){ - appender.appendPathToken(PathTokenFactory.createFunctionPathToken(property)); + if(isFunction){ + appender.appendPathToken(PathTokenFactory.createFunctionPathToken(property, functionParameters)); } else { appender.appendPathToken(PathTokenFactory.createSinglePropertyPathToken(property, SINGLE_QUOTE)); } @@ -178,6 +203,49 @@ public class PathCompiler { return path.currentIsTail() || readNextToken(appender); } + private List parseFunctionParameters(int readPosition) { + PathToken currentToken; + List parameters = new ArrayList(); + StringBuffer parameter = new StringBuffer(); + Boolean insideParameter = false; + int braceCount = 0, parenCount = 1; + while (path.inBounds(readPosition)) { + char c = path.charAt(readPosition); + + if (c == OPEN_BRACE) { + braceCount++; + } + else if (c == CLOSE_BRACE) { + braceCount--; + if (0 == braceCount && parameter.length() > 0) { + // inner parse the parameter expression to pass along to the function + LinkedList predicates = new LinkedList(); + PathCompiler compiler = new PathCompiler(parameter.toString(), predicates); + parameters.add(compiler.compile()); + } + } + else if (c == COMMA && braceCount == 0) { + parameter.delete(0, parameter.length()); + } + else { + parameter.append(c); + if (c == CLOSE_PARENTHESIS) { + parenCount--; + if (parenCount == 0) { + break; + } + } + else if (c == OPEN_PARENTHESIS) { + parenCount++; + } + + } + readPosition++; + } + path.setPosition(readPosition); + return parameters; + } + // // [?], [?,?, ..] // diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java index e242253d..70319046 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java @@ -1,6 +1,7 @@ package com.jayway.jsonpath.internal.path; import com.jayway.jsonpath.Predicate; +import com.jayway.jsonpath.internal.Path; import java.util.Collection; import java.util.List; @@ -45,7 +46,7 @@ public class PathTokenFactory { return new PredicatePathToken(predicate); } - public static PathToken createFunctionPathToken(String function) { - return new FunctionPathToken((function)); + public static PathToken createFunctionPathToken(String function, List parameters) { + return new FunctionPathToken(function, parameters); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java new file mode 100644 index 00000000..3f8bfd22 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java @@ -0,0 +1,34 @@ +package com.jayway.jsonpath.internal.function; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.Configurations; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by matt@mjgreenwood.net on 12/10/15. + */ +@RunWith(Parameterized.class) +public class NestedFunctionTest extends BaseFunctionTest { + private static final Logger logger = LoggerFactory.getLogger(NumericPathFunctionTest.class); + + private Configuration conf = Configurations.GSON_CONFIGURATION; + + public NestedFunctionTest(Configuration conf) { + logger.debug("Testing with configuration {}", conf.getClass().getName()); + this.conf = conf; + } + + @Parameterized.Parameters + public static Iterable configurations() { + return Configurations.configurations(); + } + + @Test + public void testAverageOfDoubles() { + verifyMathFunction(conf, "$.sum({$.numbers.min()}, {$.numbers.max()})", 5.5); + } +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java index f3903aa0..3b9a3cb7 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java @@ -57,7 +57,7 @@ public class NumericPathFunctionTest extends BaseFunctionTest { @Test public void testMinOfDouble() { - verifyMathFunction(conf, "$.numbers.min()", 1d); + verifyMathFunction(conf, "$.numbers.min(foobar)", 1d); } @Test