diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java index 018a8b5e..383fde89 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/CompiledPath.java @@ -19,9 +19,13 @@ import com.jayway.jsonpath.internal.EvaluationAbortException; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.PathRef; +import com.jayway.jsonpath.internal.function.ParamType; +import com.jayway.jsonpath.internal.function.Parameter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; + public class CompiledPath implements Path { private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class); @@ -32,7 +36,7 @@ public class CompiledPath implements Path { public CompiledPath(RootPathToken root, boolean isRootPath) { - this.root = root; + this.root = invertScannerFunctionRelationship(root); this.isRootPath = isRootPath; } @@ -41,6 +45,48 @@ public class CompiledPath implements Path { return isRootPath; } + + + /** + * In the event the writer of the path referenced a function at the tail end of a scanner, augment the query such + * that the root node is the function and the parameter to the function is the scanner. This way we maintain + * relative sanity in the path expression, functions either evaluate scalar values or arrays, they're + * not re-entrant nor should they maintain state, they do however take parameters. + * + * @param path + * this is our old root path which will become a parameter (assuming there's a scanner terminated by a function + * + * @return + * A function with the scanner as input, or if this situation doesn't exist just the input path + */ + private RootPathToken invertScannerFunctionRelationship(final RootPathToken path) { + if (path.isFunctionPath() && path.next() instanceof ScanPathToken) { + PathToken token = path; + PathToken prior = null; + while (null != (token = token.next()) && !(token instanceof FunctionPathToken)) { + prior = token; + } + // Invert the relationship $..path.function() to $.function($..path) + if (token instanceof FunctionPathToken) { + prior.setNext(null); + path.setTail(prior); + + // Now generate a new parameter from our path + Parameter parameter = new Parameter(); + parameter.setPath(new CompiledPath(path, true)); + parameter.setType(ParamType.PATH); + ((FunctionPathToken)token).setParameters(Arrays.asList(parameter)); + RootPathToken functionRoot = new RootPathToken('$'); + functionRoot.setTail(token); + functionRoot.setNext(token); + + // Define the function as the root + return functionRoot; + } + } + return path; + } + @Override public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration, boolean forUpdate) { if (logger.isDebugEnabled()) { 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 b1563815..ee6b8691 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 @@ -20,7 +20,7 @@ public class FunctionPathToken extends PathToken { private final String functionName; private final String pathFragment; - private final List functionParams; + private List functionParams; public FunctionPathToken(String pathFragment, List parameters) { this.pathFragment = pathFragment + ((parameters != null && parameters.size() > 0) ? "(...)" : "()"); @@ -81,4 +81,7 @@ public class FunctionPathToken extends PathToken { } + public void setParameters(List parameters) { + this.functionParams = parameters; + } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java index e4d23604..5a5046d8 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java @@ -215,4 +215,7 @@ public abstract class PathToken { protected abstract String getPathFragment(); + public void setNext(final PathToken next) { + this.next = next; + } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java index 2b2db7b1..349af9f7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/RootPathToken.java @@ -76,4 +76,8 @@ public class RootPathToken extends PathToken { public boolean isFunctionPath() { return (tail instanceof FunctionPathToken); } + + public void setTail(PathToken token) { + this.tail = token; + } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java index ede4799a..c8f9cb7d 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/Issue191.java @@ -27,6 +27,14 @@ public class Issue191 { Long.valueOf(35679716813L), value); } + @Test + public void testResultSetNumericComputationTail() { + InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json"); + Long value = JsonPath.parse(stream).read("$..timestamp.sum()", Long.class); + assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set", + Long.valueOf(35679716813L), value); + } + @Test public void testMultipleResultSetSums() { InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");