Browse Source

Modified abstract aggregation and the path compiler to handle nested functions, working implementation committed

still need to support literal function arguments.  At the present time the document passed for parameter function parsing is the root document.  Additionally, the braces still exist within the tokenizer - need to handle error conditions and see the consiquence of removing those from the implementation
pull/167/head
Matthew J Greenwood 9 years ago
parent
commit
861c0b34bd
  1. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java
  2. 36
      json-path/src/main/java/com/jayway/jsonpath/internal/function/Parameter.java
  3. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/function/PassthruPathFunction.java
  4. 5
      json-path/src/main/java/com/jayway/jsonpath/internal/function/PathFunction.java
  5. 14
      json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java
  6. 20
      json-path/src/main/java/com/jayway/jsonpath/internal/path/FunctionPathToken.java
  7. 55
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
  8. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
  9. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathTokenFactory.java
  10. 2
      json-path/src/test/java/com/jayway/jsonpath/internal/function/NestedFunctionTest.java

4
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<Parameter> parameters) {
if(ctx.configuration().jsonProvider().isArray(model)){
return ctx.configuration().jsonProvider().length(model);
} else if(ctx.configuration().jsonProvider().isMap(model)){

36
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;
}
}

4
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<Parameter> parameters) {
return model;
}
}

5
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<Parameter> parameters);
}

14
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<Parameter> 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;
}
}

20
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<Path> functionParams;
private final List<Parameter> functionParams;
public FunctionPathToken(String pathFragment, List<Path> parameters) {
public FunctionPathToken(String pathFragment, List<Parameter> 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.

55
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<Path> functionParameters = null;
List<Parameter> 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<Path> parseFunctionParameters(int readPosition) {
private List<Parameter> parseFunctionParameters(int readPosition) {
PathToken currentToken;
List<Path> parameters = new ArrayList<Path>();
List<Parameter> parameters = new ArrayList<Parameter>();
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<Predicate> predicates = new LinkedList<Predicate>();
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<Predicate> predicates = new LinkedList<Predicate>();
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++;
}

2
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);

4
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<Path> parameters) {
public static PathToken createFunctionPathToken(String function, List<Parameter> parameters) {
return new FunctionPathToken(function, parameters);
}
}

2
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);
}
}

Loading…
Cancel
Save