diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java index 5ef7ecbb..28674ca0 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java @@ -3,6 +3,8 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.PathRef; +import java.util.List; + /** * Provides the length of a JSONArray Object * @@ -11,7 +13,7 @@ import com.jayway.jsonpath.internal.PathRef; public class Length implements PathFunction { @Override - public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx) { + public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { if(ctx.configuration().jsonProvider().isArray(model)){ return ctx.configuration().jsonProvider().length(model); } else if(ctx.configuration().jsonProvider().isMap(model)){ 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 new file mode 100644 index 00000000..ea016bf4 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java @@ -0,0 +1,36 @@ +package com.jayway.jsonpath.internal.function; + +import com.jayway.jsonpath.internal.Path; + +/** + * Created by matt@mjgreenwood.net on 12/10/15. + */ +public class Parameter { + private final Path path; + private Object cachedValue; + private Boolean evaluated = false; + + public Parameter(Path path) { + this.path = path; + } + + public Object getCachedValue() { + return cachedValue; + } + + public void setCachedValue(Object cachedValue) { + this.cachedValue = cachedValue; + } + + public Path getPath() { + return path; + } + + public void setEvaluated(Boolean evaluated) { + this.evaluated = evaluated; + } + + public boolean hasEvaluated() { + return evaluated; + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java index 1d1a8f64..36d7da77 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java @@ -3,6 +3,8 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.PathRef; +import java.util.List; + /** * Defines the default behavior which is to return the model that is provided as input as output * @@ -11,7 +13,7 @@ import com.jayway.jsonpath.internal.PathRef; public class PassthruPathFunction implements PathFunction { @Override - public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx) { + public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { return model; } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java index 611c2611..7f0a3802 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java @@ -3,6 +3,8 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.PathRef; +import java.util.List; + /** * Defines the pattern by which a function can be executed over the result set in the particular path * being grabbed. The Function's input is the content of the data from the json path selector and its output @@ -27,7 +29,8 @@ public interface PathFunction { * @param ctx * Eval context, state bag used as the path is traversed, maintains the result of executing * + * @param parameters * @return */ - Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx); + Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java index 036da19b..0f5cb954 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java @@ -2,8 +2,11 @@ package com.jayway.jsonpath.internal.function.numeric; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.PathRef; +import com.jayway.jsonpath.internal.function.Parameter; import com.jayway.jsonpath.internal.function.PathFunction; +import java.util.List; + /** * Defines the pattern for processing numerical values via an abstract implementation that iterates over the collection * of JSONArray entities and verifies that each is a numerical value and then passes that along the abstract methods @@ -30,7 +33,7 @@ public abstract class AbstractAggregation implements PathFunction { protected abstract Number getValue(); @Override - public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx) { + public Object invoke(String currentPath, PathRef parent, Object model, EvaluationContext ctx, List parameters) { if(ctx.configuration().jsonProvider().isArray(model)){ Iterable objects = ctx.configuration().jsonProvider().toIterable(model); @@ -42,6 +45,15 @@ public abstract class AbstractAggregation implements PathFunction { } return getValue(); } + else if (parameters != null) { + for (Parameter param : parameters) { + if (param.getCachedValue() instanceof Number) { + Number value = (Number)param.getCachedValue(); + next(value); + } + } + return getValue(); + } return null; } } 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 9c5c2cef..4371993b 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,7 +1,7 @@ package com.jayway.jsonpath.internal.path; -import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.PathRef; +import com.jayway.jsonpath.internal.function.Parameter; import com.jayway.jsonpath.internal.function.PathFunction; import com.jayway.jsonpath.internal.function.PathFunctionFactory; @@ -18,9 +18,9 @@ public class FunctionPathToken extends PathToken { private final String functionName; private final String pathFragment; - private final List functionParams; + private final List functionParams; - public FunctionPathToken(String pathFragment, List parameters) { + public FunctionPathToken(String pathFragment, List parameters) { this.pathFragment = pathFragment; if(null != pathFragment){ functionName = pathFragment; @@ -34,10 +34,22 @@ public class FunctionPathToken extends PathToken { @Override public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { PathFunction pathFunction = PathFunctionFactory.newFunction(functionName); - Object result = pathFunction.invoke(currentPath, parent, model, ctx); + evaluateParameters(currentPath, parent, model, ctx); + Object result = pathFunction.invoke(currentPath, parent, model, ctx, functionParams); ctx.addResult(currentPath, parent, result); } + private void evaluateParameters(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { + + if (null != functionParams) { + for (Parameter param : functionParams) { + if (!param.hasEvaluated()) { + param.setCachedValue(param.getPath().evaluate(ctx.rootDocument(), ctx.rootDocument(), ctx.configuration()).getValue()); + } + } + } + } + /** * Return the actual value by indicating true. If this return was false then we'd return the value in an array which * isn't what is desired - true indicates the raw value is returned. 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 dd4e22c4..bb9089f7 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.Parameter; import java.util.ArrayList; import java.util.Collection; @@ -29,6 +30,9 @@ public class PathCompiler { private static final char WILDCARD = '*'; private static final char PERIOD = '.'; private static final char SPACE = ' '; + private static final char TAB = '\t'; + private static final char CR = '\r'; + private static final char LF = '\r'; private static final char BEGIN_FILTER = '?'; private static final char COMMA = ','; private static final char SPLIT = ':'; @@ -73,9 +77,21 @@ public class PathCompiler { } } + private void readWhitespace() { + while (path.inBounds()) { + char c = path.currentChar(); + if (c != SPACE && c != TAB && c != LF && c != CR) { + break; + } + path.incrementPosition(1); + } + } + //[$ | @] private RootPathToken readContextToken() { + readWhitespace(); + if (!path.currentCharIs(DOC_CONTEXT) && !path.currentCharIs(EVAL_CONTEXT)) { throw new InvalidPathException("Path must start with '$' or '@'"); } @@ -177,13 +193,17 @@ public class PathCompiler { } - List functionParameters = null; + 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); + if (path.inBounds(readPosition+1)) { + // 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 + functionParameters = parseFunctionParameters(readPosition); + } else { + path.setPosition(readPosition + 1); + } } else { path.setPosition(readPosition); @@ -203,9 +223,9 @@ public class PathCompiler { return path.currentIsTail() || readNextToken(appender); } - private List parseFunctionParameters(int readPosition) { + private List parseFunctionParameters(int readPosition) { PathToken currentToken; - List parameters = new ArrayList(); + List parameters = new ArrayList(); StringBuffer parameter = new StringBuffer(); Boolean insideParameter = false; int braceCount = 0, parenCount = 1; @@ -221,24 +241,37 @@ public class PathCompiler { // 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()); + Path path = compiler.compile(); + parameters.add(new Parameter(path)); + parameter.delete(0, parameter.length()); } } else if (c == COMMA && braceCount == 0) { parameter.delete(0, parameter.length()); } else { - parameter.append(c); if (c == CLOSE_PARENTHESIS) { parenCount--; if (parenCount == 0) { + if (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(new Parameter(compiler.compile())); + } break; } + else { + parameter.append(c); + } } else if (c == OPEN_PARENTHESIS) { parenCount++; + parameter.append(c); + } + else { + parameter.append(c); } - } readPosition++; } 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 d6300c40..dc22dda1 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 @@ -205,7 +205,7 @@ public abstract class PathToken { } public void invoke(PathFunction pathFunction, String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { - ctx.addResult(currentPath, parent, pathFunction.invoke(currentPath, parent, model, ctx)); + ctx.addResult(currentPath, parent, pathFunction.invoke(currentPath, parent, model, ctx, null)); } public abstract void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx); 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 70319046..995c73e4 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,7 +1,7 @@ package com.jayway.jsonpath.internal.path; import com.jayway.jsonpath.Predicate; -import com.jayway.jsonpath.internal.Path; +import com.jayway.jsonpath.internal.function.Parameter; import java.util.Collection; import java.util.List; @@ -46,7 +46,7 @@ public class PathTokenFactory { return new PredicatePathToken(predicate); } - public static PathToken createFunctionPathToken(String function, List parameters) { + 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 index 3f8bfd22..6878087e 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,6 +29,6 @@ public class NestedFunctionTest extends BaseFunctionTest { @Test public void testAverageOfDoubles() { - verifyMathFunction(conf, "$.sum({$.numbers.min()}, {$.numbers.max()})", 5.5); + verifyMathFunction(conf, "$.avg({$.numbers.min()}, {$.numbers.max()})", 5.5); } }