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.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef; import com.jayway.jsonpath.internal.PathRef;
import java.util.List;
/** /**
* Provides the length of a JSONArray Object * Provides the length of a JSONArray Object
* *
@ -11,7 +13,7 @@ import com.jayway.jsonpath.internal.PathRef;
public class Length implements PathFunction { public class Length implements PathFunction {
@Override @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)){ if(ctx.configuration().jsonProvider().isArray(model)){
return ctx.configuration().jsonProvider().length(model); return ctx.configuration().jsonProvider().length(model);
} else if(ctx.configuration().jsonProvider().isMap(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.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef; 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 * 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 { public class PassthruPathFunction implements PathFunction {
@Override @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; 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.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef; 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 * 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 * 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 * @param ctx
* Eval context, state bag used as the path is traversed, maintains the result of executing * Eval context, state bag used as the path is traversed, maintains the result of executing
* *
* @param parameters
* @return * @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.EvaluationContext;
import com.jayway.jsonpath.internal.PathRef; 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.PathFunction;
import java.util.List;
/** /**
* Defines the pattern for processing numerical values via an abstract implementation that iterates over the collection * 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 * 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(); protected abstract Number getValue();
@Override @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)){ if(ctx.configuration().jsonProvider().isArray(model)){
Iterable<?> objects = ctx.configuration().jsonProvider().toIterable(model); Iterable<?> objects = ctx.configuration().jsonProvider().toIterable(model);
@ -42,6 +45,15 @@ public abstract class AbstractAggregation implements PathFunction {
} }
return getValue(); 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; 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; package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathRef; 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.PathFunction;
import com.jayway.jsonpath.internal.function.PathFunctionFactory; import com.jayway.jsonpath.internal.function.PathFunctionFactory;
@ -18,9 +18,9 @@ public class FunctionPathToken extends PathToken {
private final String functionName; private final String functionName;
private final String pathFragment; 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; this.pathFragment = pathFragment;
if(null != pathFragment){ if(null != pathFragment){
functionName = pathFragment; functionName = pathFragment;
@ -34,10 +34,22 @@ public class FunctionPathToken extends PathToken {
@Override @Override
public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) { public void evaluate(String currentPath, PathRef parent, Object model, EvaluationContextImpl ctx) {
PathFunction pathFunction = PathFunctionFactory.newFunction(functionName); 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); 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 * 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. * 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.CharacterIndex;
import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.filter.FilterCompiler; import com.jayway.jsonpath.internal.filter.FilterCompiler;
import com.jayway.jsonpath.internal.function.Parameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -29,6 +30,9 @@ public class PathCompiler {
private static final char WILDCARD = '*'; private static final char WILDCARD = '*';
private static final char PERIOD = '.'; private static final char PERIOD = '.';
private static final char SPACE = ' '; 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 BEGIN_FILTER = '?';
private static final char COMMA = ','; private static final char COMMA = ',';
private static final char SPLIT = ':'; 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() { private RootPathToken readContextToken() {
readWhitespace();
if (!path.currentCharIs(DOC_CONTEXT) && !path.currentCharIs(EVAL_CONTEXT)) { if (!path.currentCharIs(DOC_CONTEXT) && !path.currentCharIs(EVAL_CONTEXT)) {
throw new InvalidPathException("Path must start with '$' or '@'"); 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) { if (isFunction) {
// read the next token to determine if we have a simple no-args function call if (path.inBounds(readPosition+1)) {
char c = path.charAt(readPosition++); // read the next token to determine if we have a simple no-args function call
if (c != CLOSE_PARENTHESIS) { char c = path.charAt(readPosition + 1);
// parse the arguments of the function - arguments that are inner queries will be single quoted parameters if (c != CLOSE_PARENTHESIS) {
functionParameters = parseFunctionParameters(readPosition); // 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 { else {
path.setPosition(readPosition); path.setPosition(readPosition);
@ -203,9 +223,9 @@ public class PathCompiler {
return path.currentIsTail() || readNextToken(appender); return path.currentIsTail() || readNextToken(appender);
} }
private List<Path> parseFunctionParameters(int readPosition) { private List<Parameter> parseFunctionParameters(int readPosition) {
PathToken currentToken; PathToken currentToken;
List<Path> parameters = new ArrayList<Path>(); List<Parameter> parameters = new ArrayList<Parameter>();
StringBuffer parameter = new StringBuffer(); StringBuffer parameter = new StringBuffer();
Boolean insideParameter = false; Boolean insideParameter = false;
int braceCount = 0, parenCount = 1; int braceCount = 0, parenCount = 1;
@ -221,24 +241,37 @@ public class PathCompiler {
// inner parse the parameter expression to pass along to the function // inner parse the parameter expression to pass along to the function
LinkedList<Predicate> predicates = new LinkedList<Predicate>(); LinkedList<Predicate> predicates = new LinkedList<Predicate>();
PathCompiler compiler = new PathCompiler(parameter.toString(), predicates); 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) { else if (c == COMMA && braceCount == 0) {
parameter.delete(0, parameter.length()); parameter.delete(0, parameter.length());
} }
else { else {
parameter.append(c);
if (c == CLOSE_PARENTHESIS) { if (c == CLOSE_PARENTHESIS) {
parenCount--; parenCount--;
if (parenCount == 0) { 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; break;
} }
else {
parameter.append(c);
}
} }
else if (c == OPEN_PARENTHESIS) { else if (c == OPEN_PARENTHESIS) {
parenCount++; parenCount++;
parameter.append(c);
}
else {
parameter.append(c);
} }
} }
readPosition++; 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) { 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); 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; package com.jayway.jsonpath.internal.path;
import com.jayway.jsonpath.Predicate; 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.Collection;
import java.util.List; import java.util.List;
@ -46,7 +46,7 @@ public class PathTokenFactory {
return new PredicatePathToken(predicate); 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); 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 @Test
public void testAverageOfDoubles() { public void testAverageOfDoubles() {
verifyMathFunction(conf, "$.sum({$.numbers.min()}, {$.numbers.max()})", 5.5); verifyMathFunction(conf, "$.avg({$.numbers.min()}, {$.numbers.max()})", 5.5);
} }
} }

Loading…
Cancel
Save