Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.jayway.jsonpath.internal.function;

import com.jayway.jsonpath.internal.EvaluationContext;
import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.function.latebinding.ILateBindingValue;
import com.jayway.jsonpath.internal.function.latebinding.PathLateBindingValue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* Created by [email protected] on 12/10/15.
* Defines a parameter as passed to a function with late binding support for lazy evaluation.
*/
public class Parameter {
private ParamType type;
Expand Down Expand Up @@ -65,4 +69,63 @@ public String getJson() {
public void setJson(String json) {
this.json = json;
}

/**
* Translate the collection of parameters into a collection of values of type T.
*
* @param type
* The type to translate the collection into.
*
* @param ctx
* Context.
*
* @param parameters
* Collection of parameters.
*
* @param <T>
* Type T returned as a List of T.
*
* @return
* List of T either empty or containing contents.
*/
public static <T> List<T> toList(final Class<T> type, final EvaluationContext ctx, final List<Parameter> parameters) {
List<T> values = new ArrayList();
if (null != parameters) {
for (Parameter param : parameters) {
consume(type, ctx, values, param.getValue());
}
}
return values;
}

/**
* Either consume the object as an array and add each element to the collection, or alternatively add each element
*
* @param expectedType
* the expected class type to consume, if null or not of this type the element is not added to the array.
*
* @param ctx
* the JSON context to determine if this is an array or value.
*
* @param collection
* The collection to append into.
*
* @param value
* The value to evaluate.
*/
public static void consume(Class expectedType, EvaluationContext ctx, Collection collection, Object value) {
if (ctx.configuration().jsonProvider().isArray(value)) {
for (Object o : ctx.configuration().jsonProvider().toIterable(value)) {
if (o != null && expectedType.isAssignableFrom(o.getClass())) {
collection.add(o);
} else if (o != null && expectedType == String.class) {
collection.add(o.toString());
}
}
} else {
if (value != null && expectedType.isAssignableFrom(value.getClass())) {
collection.add(value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
* Leverages the function's name in order to determine which function to execute which is maintained internally
* here via a static map
*
* Created by mattg on 6/27/15.
*/
public class PathFunctionFactory {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,9 @@ public Object invoke(String currentPath, PathRef parent, Object model, Evaluatio
}
}
if (parameters != null) {
for (Parameter param : parameters) {
Object value = param.getValue();
if (null != value && value instanceof Number) {
count++;
next((Number)value);
}
for (Number value : Parameter.toList(Number.class, ctx, parameters)) {
count++;
next(value);
}
}
if (count != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
* String function concat - simple takes a list of arguments and/or an array and concatenates them together to form a
* single string
*
* Created by mgreenwood on 12/11/15.
*/
public class Concatenate implements PathFunction {
@Override
Expand All @@ -26,11 +25,8 @@ public Object invoke(String currentPath, PathRef parent, Object model, Evaluatio
}
}
if (parameters != null) {
for (Parameter param : parameters) {
Object value = param.getValue();
if (value != null) {
result.append(value.toString());
}
for (String value : Parameter.toList(String.class, ctx, parameters)) {
result.append(value);
}
}
return result.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
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);
Expand All @@ -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;
}

Expand All @@ -41,6 +45,48 @@ public boolean isRootPath() {
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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class FunctionPathToken extends PathToken {

private final String functionName;
private final String pathFragment;
private final List<Parameter> functionParams;
private List<Parameter> functionParams;

public FunctionPathToken(String pathFragment, List<Parameter> parameters) {
this.pathFragment = pathFragment + ((parameters != null && parameters.size() > 0) ? "(...)" : "()");
Expand Down Expand Up @@ -81,4 +81,7 @@ public String getPathFragment() {
}


public void setParameters(List<Parameter> parameters) {
this.functionParams = parameters;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,7 @@ public void invoke(PathFunction pathFunction, String currentPath, PathRef parent

protected abstract String getPathFragment();

public void setNext(final PathToken next) {
this.next = next;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ public boolean isTokenDefinite() {
public boolean isFunctionPath() {
return (tail instanceof FunctionPathToken);
}

public void setTail(PathToken token) {
this.tail = token;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.jayway.jsonpath.internal.function;

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Configurations;
import com.jayway.jsonpath.JsonPath;
import org.junit.Test;

import java.io.InputStream;

import static org.junit.Assert.assertEquals;

/**
* TDD for Issue 191
*
* Shows aggregation across fields rather than within a single entity.
*
*/
public class Issue191 {

private Configuration conf = Configurations.GSON_CONFIGURATION;

@Test
public void testResultSetNumericComputation() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
Long value = JsonPath.parse(stream).read("$.sum($..timestamp)", 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 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 testResultSetNumericComputationRecursiveReplacement() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
Long value = JsonPath.parse(stream).read("$.max($..timestamp.avg(), $..timestamp.stddev())", Long.class);
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
Long.valueOf(1427188672L), value);
}

@Test
public void testMultipleResultSetSums() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
Long value = JsonPath.parse(stream).read("$.sum($..timestamp, $..cpus)", Long.class);
assertEquals("Expected the max function to consume the aggregation parameters and calculate the max over the result set",
Long.valueOf(35679716835L), value);
}

@Test
public void testConcatResultSet() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
String concatResult = JsonPath.parse(stream).read("$.concat($..state)", String.class);
assertEquals("Expected a string length to be a concat of all of the states", concatResult.length(), 806);
}

@Test
public void testConcatWithNumericValueAsString() {
InputStream stream = ClassLoader.getSystemResourceAsStream("issue_191.json");
String concatResult = JsonPath.parse(stream).read("$.concat($..cpus)", String.class);
assertEquals("Expected a string length to be a concat of all of the cpus", concatResult.length(), 489);
}
}
Loading