From fadc946032e4664321b91dc6a0394930d022e2e7 Mon Sep 17 00:00:00 2001 From: Matthew J Greenwood Date: Fri, 11 Dec 2015 07:35:27 -0500 Subject: [PATCH] removed braces added support for JSON - need to parse JSON now... --- .../jsonpath/internal/function/ParamType.java | 9 ++ .../jsonpath/internal/function/Parameter.java | 17 ++- .../jsonpath/internal/path/PathCompiler.java | 136 ++++++++++++------ .../internal/function/NestedFunctionTest.java | 4 +- 4 files changed, 123 insertions(+), 43 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java new file mode 100644 index 00000000..20288d47 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/ParamType.java @@ -0,0 +1,9 @@ +package com.jayway.jsonpath.internal.function; + +/** + * Created by mgreenwood on 12/11/15. + */ +public enum ParamType { + JSON, + PATH +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java index ea016bf4..c275fd81 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java @@ -6,10 +6,13 @@ import com.jayway.jsonpath.internal.Path; * Created by matt@mjgreenwood.net on 12/10/15. */ public class Parameter { - private final Path path; + private ParamType type; + private Path path; private Object cachedValue; private Boolean evaluated = false; + public Parameter() {} + public Parameter(Path path) { this.path = path; } @@ -33,4 +36,16 @@ public class Parameter { public boolean hasEvaluated() { return evaluated; } + + public ParamType getType() { + return type; + } + + public void setType(ParamType type) { + this.type = type; + } + + public void setPath(Path path) { + this.path = path; + } } 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 4dbb0b51..bc079bfa 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 @@ -5,6 +5,7 @@ import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.internal.CharacterIndex; import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.filter.FilterCompiler; +import com.jayway.jsonpath.internal.function.ParamType; import com.jayway.jsonpath.internal.function.Parameter; import java.util.ArrayList; @@ -80,19 +81,23 @@ public class PathCompiler { private void readWhitespace() { while (path.inBounds()) { char c = path.currentChar(); - if (c != SPACE && c != TAB && c != LF && c != CR) { + if (!isWhitespace(c)) { break; } path.incrementPosition(1); } } + private Boolean isPathContext(char c) { + return (c == DOC_CONTEXT || c == EVAL_CONTEXT); + } + //[$ | @] private RootPathToken readContextToken() { readWhitespace(); - if (!path.currentCharIs(DOC_CONTEXT) && !path.currentCharIs(EVAL_CONTEXT)) { + if (!isPathContext(path.currentChar())) { throw new InvalidPathException("Path must start with '$' or '@'"); } @@ -199,7 +204,7 @@ public class PathCompiler { // read the next token to determine if we have a simple no-args function call char c = path.charAt(readPosition + 1); if (c != CLOSE_PARENTHESIS) { - // parse the arguments of the function - arguments that are inner queries will be single quoted parameters + // parse the arguments of the function - arguments that are inner queries or JSON document(s) functionParameters = parseFunctionParameters(readPosition); } else { path.setPosition(readPosition + 1); @@ -230,6 +235,23 @@ public class PathCompiler { * set if the parameter is an expression. If the parameter is a JSON document then the value of the cachedValue is * set on the object. * + * Sequence for parsing out the parameters: + * + * This code has its own tokenizer - it does some rudimentary level of lexing in that it can distinguish between JSON block parameters + * and sub-JSON blocks - it effectively regex's out the parameters into string blocks that can then be passed along to the appropriate parser. + * Since sub-jsonpath expressions can themselves contain other function calls this routine needs to be sensitive to token counting to + * determine the boundaries. Since the Path parser isn't aware of JSON processing this uber routine is needed. + * + * Parameters are separated by COMMAs ',' + * + *
+     * doc = {"numbers": [1,2,3,4,5,6,7,8,9,10]}
+     *
+     * $.sum({10}, $.numbers.avg())
+     * 
+ * + * The above is a valid function call, we're first summing 10 + avg of 1...10 (5.5) so the total should be 15.5 + * * @param readPosition * The current position within the stream we've advanced - TODO remove the need for this... * @return @@ -239,59 +261,93 @@ public class PathCompiler { */ private List parseFunctionParameters(int readPosition) { PathToken currentToken; + ParamType type = null; + + // Parenthesis starts at 1 since we're marking the start of a function call, the close paren will denote the + // last parameter boundary + Integer groupParen = 1, groupBracket = 0, groupBrace = 0; + Boolean endOfStream = false; List parameters = new ArrayList(); StringBuffer parameter = new StringBuffer(); - Boolean insideParameter = false; - int braceCount = 0, parenCount = 1; - while (path.inBounds(readPosition)) { + while (path.inBounds(readPosition) && !endOfStream) { 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); - Path path = compiler.compile(); - parameters.add(new Parameter(path)); - parameter.delete(0, parameter.length()); + // we're at the start of the stream, and don't know what type of parameter we have + if (type == null) { + if (isWhitespace(c)) { + continue; + } + + if (c == OPEN_BRACE) { + type = ParamType.JSON; + } + else if (isPathContext(c)) { + type = ParamType.PATH; // read until we reach a terminating comma and we've reset grouping to zero } } - else if (c == COMMA && braceCount == 0) { - parameter.delete(0, parameter.length()); - } - else { - if (c == CLOSE_PARENTHESIS) { - parenCount--; - if (parenCount == 0) { - if (parameter.length() > 0) { - // inner parse the parameter expression to pass along to the function + + switch (c) { + case OPEN_PARENTHESIS: + groupParen++; + break; + case OPEN_BRACE: + groupBrace++; + break; + case OPEN_SQUARE_BRACKET: + groupBracket++; + break; + + case CLOSE_BRACE: + groupBrace--; + break; + case CLOSE_SQUARE_BRACKET: + groupBracket--; + break; + + // In either the close paren case where we have zero paren groups left, capture the parameter, or where + // we've encountered a COMMA do the same + case CLOSE_PARENTHESIS: + groupParen--; + if (0 != groupParen) { + parameter.append(c); + } + case COMMA: + // In this state we've reach the end of a function parameter and we can pass along the parameter string + // to the parser + if (null != type && (0 == groupBrace && 0 == groupBracket && ((0 == groupParen && CLOSE_PARENTHESIS == c) || 1 == groupParen))) { + Parameter param = new Parameter(); + param.setType(type); + if (type == ParamType.JSON) { + // parse the json and set the value + param.setCachedValue(parameter.toString()); + param.setEvaluated(true); + } + else if (type == ParamType.PATH) { LinkedList predicates = new LinkedList(); PathCompiler compiler = new PathCompiler(parameter.toString(), predicates); - parameters.add(new Parameter(compiler.compile())); + param.setPath(compiler.compile()); } - break; - } - else { - parameter.append(c); + parameters.add(param); + parameter.delete(0, parameter.length()); + + type = null; + endOfStream = (0 == groupParen); } - } - else if (c == OPEN_PARENTHESIS) { - parenCount++; - parameter.append(c); - } - else { - parameter.append(c); - } + break; + } + + if (type != null && !(c == COMMA && 0 == groupBrace && 0 == groupBracket && 1 == groupParen)) { + parameter.append(c); } } path.setPosition(readPosition); return parameters; } + private boolean isWhitespace(char c) { + return (c == SPACE || c == TAB || c == LF || c == CR); + } + // // [?], [?,?, ..] // 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 index c763e5eb..28fb7adb 100644 --- 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 @@ -29,7 +29,7 @@ public class NestedFunctionTest extends BaseFunctionTest { @Test public void testParameterAverageFunctionCall() { - verifyMathFunction(conf, "$.avg({$.numbers.min()}, {$.numbers.max()})", 5.5); + verifyMathFunction(conf, "$.avg($.numbers.min(), $.numbers.max())", 5.5); } @Test @@ -47,6 +47,6 @@ public class NestedFunctionTest extends BaseFunctionTest { */ @Test public void testArrayAverageFunctionCallWithParameters() { - verifyMathFunction(conf, "$.numbers.sum({$.numbers.min()}, {$.numbers.max()})", 66.0); + verifyMathFunction(conf, "$.numbers.sum($.numbers.min(), $.numbers.max())", 66.0); } }