Browse Source

Allow document references in inline filters.

"store.book[?(@.display-price <= $.max-price)].display-price"
pull/56/head
Kalle Stenflo 10 years ago
parent
commit
571654f7fb
  1. 90
      json-path/src/main/java/com/jayway/jsonpath/Criteria.java
  2. 4
      json-path/src/main/java/com/jayway/jsonpath/JsonPath.java
  3. 17
      json-path/src/main/java/com/jayway/jsonpath/Predicate.java
  4. 20
      json-path/src/main/java/com/jayway/jsonpath/internal/CompiledPath.java
  5. 7
      json-path/src/main/java/com/jayway/jsonpath/internal/EvaluationContext.java
  6. 20
      json-path/src/main/java/com/jayway/jsonpath/internal/Path.java
  7. 110
      json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java
  8. 10
      json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java
  9. 32
      json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicateContextImpl.java
  10. 26
      json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicatePathToken.java
  11. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/compiler/RootPathToken.java
  12. 4
      json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java
  13. 14
      json-path/src/test/java/com/jayway/jsonpath/BaseTest.java
  14. 2
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
  15. 40
      json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java
  16. 4
      json-path/src/test/java/com/jayway/jsonpath/old/FilterTest.java
  17. 2
      json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java
  18. 12
      json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java
  19. 6
      json-path/src/test/java/com/jayway/jsonpath/old/internal/TestInternal3.java

90
json-path/src/main/java/com/jayway/jsonpath/Criteria.java

@ -3,6 +3,8 @@ package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.Path;
import com.jayway.jsonpath.internal.PathCompiler; import com.jayway.jsonpath.internal.PathCompiler;
import com.jayway.jsonpath.internal.compiler.PredicateContextImpl;
import com.sun.tools.doclets.formats.html.SourceToHTMLConverter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -34,7 +36,7 @@ public class Criteria implements Predicate {
private static enum CriteriaType { private static enum CriteriaType {
EQ { EQ {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = (0 == safeCompare(expected, actual)); boolean res = (0 == safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res; return res;
@ -42,7 +44,7 @@ public class Criteria implements Predicate {
}, },
NE { NE {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = (0 != safeCompare(expected, actual)); boolean res = (0 != safeCompare(expected, actual));
logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res);
return res; return res;
@ -50,7 +52,7 @@ public class Criteria implements Predicate {
}, },
GT { GT {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) { if ((expected == null) ^ (actual == null)) {
return false; return false;
} }
@ -61,7 +63,7 @@ public class Criteria implements Predicate {
}, },
GTE { GTE {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) { if ((expected == null) ^ (actual == null)) {
return false; return false;
} }
@ -72,7 +74,7 @@ public class Criteria implements Predicate {
}, },
LT { LT {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) { if ((expected == null) ^ (actual == null)) {
return false; return false;
} }
@ -83,7 +85,7 @@ public class Criteria implements Predicate {
}, },
LTE { LTE {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
if ((expected == null) ^ (actual == null)) { if ((expected == null) ^ (actual == null)) {
return false; return false;
} }
@ -94,7 +96,7 @@ public class Criteria implements Predicate {
}, },
IN { IN {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = false; boolean res = false;
Collection exps = (Collection) expected; Collection exps = (Collection) expected;
for (Object exp : exps) { for (Object exp : exps) {
@ -109,7 +111,7 @@ public class Criteria implements Predicate {
}, },
NIN { NIN {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
Collection nexps = (Collection) expected; Collection nexps = (Collection) expected;
boolean res = !nexps.contains(actual); boolean res = !nexps.contains(actual);
logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", nexps), res); logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", nexps), res);
@ -118,13 +120,13 @@ public class Criteria implements Predicate {
}, },
ALL { ALL {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = true; boolean res = true;
Collection exps = (Collection) expected; Collection exps = (Collection) expected;
if (configuration.jsonProvider().isArray(actual)) { if (ctx.configuration().jsonProvider().isArray(actual)) {
for (Object exp : exps) { for (Object exp : exps) {
boolean found = false; boolean found = false;
for (Object check : configuration.jsonProvider().toIterable(actual)) { for (Object check : ctx.configuration().jsonProvider().toIterable(actual)) {
if (0 == safeCompare(exp, check)) { if (0 == safeCompare(exp, check)) {
found = true; found = true;
break; break;
@ -135,7 +137,7 @@ public class Criteria implements Predicate {
break; break;
} }
} }
logger.debug("[{}] {} [{}] => {}", join(", ", configuration.jsonProvider().toIterable(actual)), name(), join(", ", exps), res); logger.debug("[{}] {} [{}] => {}", join(", ", ctx.configuration().jsonProvider().toIterable(actual)), name(), join(", ", exps), res);
} else { } else {
res = false; res = false;
logger.debug("[{}] {} [{}] => {}", "<NOT AN ARRAY>", name(), join(", ", exps), res); logger.debug("[{}] {} [{}] => {}", "<NOT AN ARRAY>", name(), join(", ", exps), res);
@ -145,11 +147,11 @@ public class Criteria implements Predicate {
}, },
SIZE { SIZE {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
int size = (Integer) expected; int size = (Integer) expected;
boolean res; boolean res;
if (configuration.jsonProvider().isArray(actual)) { if (ctx.configuration().jsonProvider().isArray(actual)) {
int length = configuration.jsonProvider().length(actual); int length = ctx.configuration().jsonProvider().length(actual);
res = (length == size); res = (length == size);
logger.debug("Array with size {} {} {} => {}", length, name(), size, res); logger.debug("Array with size {} {} {} => {}", length, name(), size, res);
} else if (actual instanceof String) { } else if (actual instanceof String) {
@ -165,14 +167,14 @@ public class Criteria implements Predicate {
}, },
EXISTS { EXISTS {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
//This must be handled outside //This must be handled outside
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
}, },
TYPE { TYPE {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
final Class<?> expType = (Class<?>) expected; final Class<?> expType = (Class<?>) expected;
final Class<?> actType = actual == null ? null : actual.getClass(); final Class<?> actType = actual == null ? null : actual.getClass();
@ -181,7 +183,7 @@ public class Criteria implements Predicate {
}, },
REGEX { REGEX {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = false; boolean res = false;
final Pattern pattern = (Pattern) expected; final Pattern pattern = (Pattern) expected;
if (actual != null && actual instanceof String) { if (actual != null && actual instanceof String) {
@ -193,28 +195,18 @@ public class Criteria implements Predicate {
}, },
MATCHES { MATCHES {
@Override @Override
boolean eval(Object expected, final Object actual, final Configuration configuration) { boolean eval(Object expected, final Object actual, final PredicateContext ctx) {
Predicate exp = (Predicate) expected; Predicate exp = (Predicate) expected;
return exp.apply(new PredicateContext() { return exp.apply(new PredicateContextImpl(actual, ctx.rootDocument(), ctx.configuration()));
@Override
public Object target() {
return actual;
}
@Override
public Configuration configuration() {
return configuration;
}
});
} }
}, },
NOT_EMPTY { NOT_EMPTY {
@Override @Override
boolean eval(Object expected, Object actual, Configuration configuration) { boolean eval(Object expected, Object actual, PredicateContext ctx) {
boolean res = false; boolean res = false;
if (actual != null) { if (actual != null) {
if (configuration.jsonProvider().isArray(actual)) { if (ctx.configuration().jsonProvider().isArray(actual)) {
int len = configuration.jsonProvider().length(actual); int len = ctx.configuration().jsonProvider().length(actual);
res = (0 != len); res = (0 != len);
logger.debug("array length = {} {} => {}", len, name(), res); logger.debug("array length = {} {} => {}", len, name(), res);
} else if (actual instanceof String) { } else if (actual instanceof String) {
@ -227,7 +219,7 @@ public class Criteria implements Predicate {
} }
}; };
abstract boolean eval(Object expected, Object actual, Configuration configuration); abstract boolean eval(Object expected, Object actual, PredicateContext ctx);
public static CriteriaType parse(String str) { public static CriteriaType parse(String str) {
if ("==".equals(str)) { if ("==".equals(str)) {
@ -283,16 +275,23 @@ public class Criteria implements Predicate {
boolean exists = ((Boolean) expected); boolean exists = ((Boolean) expected);
try { try {
Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options().build(); Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options().build();
path.evaluate(ctx.target(), c).getValue(); path.evaluate(ctx.contextDocument(), ctx.rootDocument(), c).getValue();
return exists; return exists;
} catch (PathNotFoundException e) { } catch (PathNotFoundException e) {
return !exists; return !exists;
} }
} else { } else {
try { try {
final Object actual = path.evaluate(ctx.target(), ctx.configuration()).getValue(); final Object actual = path.evaluate(ctx.contextDocument(), ctx.rootDocument(), ctx.configuration()).getValue();
return criteriaType.eval(expected, actual, ctx.configuration()); Object expectedVal = expected;
if(expected instanceof Path){
Path expectedPath = (Path) expected;
Object doc = expectedPath.isRootPath()?ctx.rootDocument():ctx.contextDocument();
expectedVal = expectedPath.evaluate(doc, ctx.rootDocument(), ctx.configuration()).getValue();
}
return criteriaType.eval(expectedVal, actual, ctx);
} catch (ValueCompareException e) { } catch (ValueCompareException e) {
return false; return false;
} catch (PathNotFoundException e) { } catch (PathNotFoundException e) {
@ -571,6 +570,10 @@ public class Criteria implements Predicate {
private static int safeCompare(Object expected, Object providerParsed) throws ValueCompareException { private static int safeCompare(Object expected, Object providerParsed) throws ValueCompareException {
if(expected == providerParsed){
return 0;
}
boolean expNullish = isNullish(expected); boolean expNullish = isNullish(expected);
boolean provNullish = isNullish(providerParsed); boolean provNullish = isNullish(providerParsed);
@ -611,12 +614,21 @@ public class Criteria implements Predicate {
Path p = PathCompiler.compile(path); Path p = PathCompiler.compile(path);
if ("$".equals(path) && (operator == null || operator.isEmpty()) && (expected == null || expected.isEmpty())) { if (("$".equals(path) || "@".equals(path) )&& (operator == null || operator.isEmpty()) && (expected == null || expected.isEmpty())) {
return new Criteria(p, CriteriaType.NE, null); return new Criteria(p, CriteriaType.NE, null);
} else if (operator.isEmpty()) { } else if (operator.isEmpty()) {
return Criteria.where(path).exists(true); return Criteria.where(path).exists(true);
} else { } else {
return new Criteria(p, CriteriaType.parse(operator), expected); if(expected.startsWith("$") || expected.startsWith("@")){
Path compile = PathCompiler.compile(expected);
if(!compile.isDefinite()){
throw new InvalidPathException("the predicate path: " + expected + " is not definite");
}
return new Criteria(p, CriteriaType.parse(operator), compile);
} else {
return new Criteria(p, CriteriaType.parse(operator), expected);
}
} }
} }

4
json-path/src/main/java/com/jayway/jsonpath/JsonPath.java

@ -172,9 +172,9 @@ public class JsonPath {
try { try {
if(optAsPathList){ if(optAsPathList){
return (T)path.evaluate(jsonObject, configuration).getPath(); return (T)path.evaluate(jsonObject, jsonObject, configuration).getPath();
} else { } else {
Object res = path.evaluate(jsonObject, configuration).getValue(); Object res = path.evaluate(jsonObject, jsonObject, configuration).getValue();
if(optAlwaysReturnList && path.isDefinite()){ if(optAlwaysReturnList && path.isDefinite()){
Object array = configuration.jsonProvider().createArray(); Object array = configuration.jsonProvider().createArray();
configuration.jsonProvider().setProperty(array, 0, res); configuration.jsonProvider().setProperty(array, 0, res);

17
json-path/src/main/java/com/jayway/jsonpath/Predicate.java

@ -7,11 +7,24 @@ public interface Predicate {
boolean apply(PredicateContext ctx); boolean apply(PredicateContext ctx);
public interface PredicateContext { public interface PredicateContext {
Object target(); /**
* Returns the current element being evaluated by this predicate
* @return current document
*/
Object contextDocument();
/**
* Returns the root document (the complete JSON)
* @return root document
*/
Object rootDocument();
/**
* Configuration to use when evaluating
* @return configuration
*/
Configuration configuration(); Configuration configuration();
} }
} }

20
json-path/src/main/java/com/jayway/jsonpath/internal/CompiledPath.java

@ -6,25 +6,33 @@ import com.jayway.jsonpath.internal.compiler.PathToken;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class CompiledPath implements Path { public class CompiledPath implements Path {
private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class); private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class);
private final PathToken root; private final PathToken root;
private final boolean isRootPath;
public CompiledPath(PathToken root) {
public CompiledPath(PathToken root, boolean isRootPath) {
this.root = root; this.root = root;
this.isRootPath = isRootPath;
}
@Override
public boolean isRootPath() {
return isRootPath;
} }
@Override @Override
public EvaluationContext evaluate(Object model, Configuration configuration) { public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration) {
if(logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Evaluating path: {}", toString()); logger.debug("Evaluating path: {}", toString());
} }
EvaluationContextImpl ctx = new EvaluationContextImpl(this, configuration); EvaluationContextImpl ctx = new EvaluationContextImpl(this, rootDocument, configuration);
root.evaluate("", model, ctx); root.evaluate("", document, ctx);
return ctx; return ctx;
} }

7
json-path/src/main/java/com/jayway/jsonpath/internal/EvaluationContext.java

@ -12,6 +12,13 @@ public interface EvaluationContext {
*/ */
Configuration configuration(); Configuration configuration();
/**
* The json document that is evaluated
*
* @return the document
*/
Object rootDocument();
/** /**
* This method does not adhere to configuration settings. It will return a single object (not wrapped in a List) even if the * This method does not adhere to configuration settings. It will return a single object (not wrapped in a List) even if the
* configuration contains the {@link com.jayway.jsonpath.Option#ALWAYS_RETURN_LIST} * configuration contains the {@link com.jayway.jsonpath.Option#ALWAYS_RETURN_LIST}

20
json-path/src/main/java/com/jayway/jsonpath/internal/Path.java

@ -7,8 +7,26 @@ import com.jayway.jsonpath.Configuration;
*/ */
public interface Path { public interface Path {
EvaluationContext evaluate(Object model, Configuration configuration); /**
* Evaluates this path
*
* @param document the json document to apply the path on
* @param rootDocument the root json document that started this evaluation
* @param configuration configuration to use
* @return EvaluationContext containing results of evaluation
*/
EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration);
/**
*
* @return true id this path is definite
*/
boolean isDefinite(); boolean isDefinite();
/**
*
* @return true id this path is starts with '$' and false if the path starts with '@'
*/
boolean isRootPath();
} }

110
json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java

@ -35,22 +35,32 @@ public class PathCompiler {
private static final char BRACKET_CLOSE = ']'; private static final char BRACKET_CLOSE = ']';
private static final char SPACE = ' '; private static final char SPACE = ' ';
private static final Cache cache = new Cache(200); private static final Cache cache = new Cache(200);
private static final String[] OPERATORS = {"==", ">=", "<=", "!=", "<", ">",};
public static Path compile(String path, Predicate... filters) { public static Path compile(String path, Predicate... filters) {
notEmpty(path, "Path may not be null empty"); notEmpty(path, "Path may not be null empty");
path = path.trim(); path = path.trim();
LinkedList<Predicate> filterList = new LinkedList<Predicate>(asList(filters)); LinkedList<Predicate> filterList = new LinkedList<Predicate>(asList(filters));
if (path.charAt(0) != '$') { if (path.charAt(0) != '$' && path.charAt(0) != '@') {
path = "$." + path; path = "$." + path;
} }
String cacheKey = path + filterList.toString(); boolean isRootPath = (path.charAt(0) == '$');
if(path.charAt(0) == '@'){
path = "$" + path.substring(1);
}
String cacheKey = path + isRootPath + filterList.toString();
Path p = cache.get(cacheKey); Path p = cache.get(cacheKey);
if(p != null){ if (p != null) {
logger.debug("Using cached path");
return p; return p;
} }
@ -73,7 +83,7 @@ public class PathCompiler {
break; break;
case BRACKET_OPEN: case BRACKET_OPEN:
positions = fastForwardUntilClosed(path, i); positions = fastForwardUntilClosed(path, i);
fragment = path.substring(i, i+positions); fragment = path.substring(i, i + positions);
i += positions; i += positions;
break; break;
case PERIOD: case PERIOD:
@ -92,7 +102,7 @@ public class PathCompiler {
} else { } else {
assertValidFieldChars(path, i, positions); assertValidFieldChars(path, i, positions);
fragment = PROPERTY_OPEN + path.substring(i, i+positions) + PROPERTY_CLOSE; fragment = PROPERTY_OPEN + path.substring(i, i + positions) + PROPERTY_CLOSE;
} }
i += positions; i += positions;
} }
@ -104,7 +114,7 @@ public class PathCompiler {
default: default:
positions = fastForward(path, i); positions = fastForward(path, i);
fragment = PROPERTY_OPEN + path.substring(i, i+positions) + PROPERTY_CLOSE; fragment = PROPERTY_OPEN + path.substring(i, i + positions) + PROPERTY_CLOSE;
i += positions; i += positions;
break; break;
} }
@ -116,7 +126,7 @@ public class PathCompiler {
} while (i < path.length()); } while (i < path.length());
Path pa = new CompiledPath(root); Path pa = new CompiledPath(root, isRootPath);
cache.put(cacheKey, pa); cache.put(cacheKey, pa);
@ -222,7 +232,7 @@ public class PathCompiler {
switch (current) { switch (current) {
case '?': case '?':
return analyzeCriteriaSequence(); return analyzeCriteriaSequence2();
case '\'': case '\'':
return analyzeProperty(); return analyzeProperty();
default: default:
@ -239,6 +249,69 @@ public class PathCompiler {
throw new InvalidPathException("Could not analyze path component: " + pathFragment); throw new InvalidPathException("Could not analyze path component: " + pathFragment);
} }
private int findNext(char charToFind, boolean disregardStringContent) {
int x = i;
boolean inProperty = false;
char analyzing;
boolean found = false;
while(!found){
analyzing = pathFragment.charAt(x);
if(analyzing == '\'' && disregardStringContent){
inProperty = !inProperty;
}
if(!inProperty){
found = (analyzing == charToFind);
}
x++;
}
return x-1;
}
//[?(@.foo)]
//[?(@['foo'])]
//[?(@.foo == 'bar')]
//[?(@['foo']['bar'] == 'bar')]
//[?(@ == 'bar')]
//[?(@.category == $.store.book[0].category)]
//[?(@.category == @['category'])]
private PathToken analyzeCriteriaSequence2() {
List<Predicate> predicates = new ArrayList<Predicate>();
int startPos = findNext('(', true) + 1;
int stopPos = findNext(')', true);
String[] split = pathFragment.substring(startPos, stopPos).split("&&");
for (String criteria : split) {
int operatorIndex = -1;
String left = "";
String operator = "";
String right = "";
for (int y = 0; y < OPERATORS.length; y++) {
operatorIndex = criteria.indexOf(OPERATORS[y]);
if (operatorIndex != -1) {
operator = OPERATORS[y];
break;
}
}
if (!operator.isEmpty()) {
left = criteria.substring(0, operatorIndex).trim();
right = criteria.substring(operatorIndex + operator.length()).trim();
} else {
left = criteria.trim();
}
predicates.add(Criteria.create(left, operator, right));
}
i = stopPos;
return new PredicatePathToken(Filter.filter(predicates));
}
/*
//[?(@.foo)] //[?(@.foo)]
//[?(@['foo'])] //[?(@['foo'])]
//[?(@.foo == 'bar')] //[?(@.foo == 'bar')]
@ -276,20 +349,20 @@ public class PathCompiler {
break; break;
case '(': case '(':
if(!propertyOpen) { if (!propertyOpen) {
functionBracketOpened = true; functionBracketOpened = true;
break; break;
} }
case ')': case ')':
if(!propertyOpen) { if (!propertyOpen) {
functionBracketClosed = true; functionBracketClosed = true;
break; break;
} }
default: default:
if('\'' == current){ if ('\'' == current) {
if (propertyOpen){ if (propertyOpen) {
propertyOpen = false; propertyOpen = false;
} else { } else {
propertyOpen = true; propertyOpen = true;
@ -303,7 +376,7 @@ public class PathCompiler {
if (isLogicOperatorChar(pathFragment.charAt(i + 1))) { if (isLogicOperatorChar(pathFragment.charAt(i + 1))) {
++i; ++i;
} }
criteria.add(createCriteria(pathBuffer, operatorBuffer, valueBuffer)); criteria.add(Criteria.create(pathBuffer.toString().trim(), operatorBuffer.toString().trim(), valueBuffer.toString().trim()));
pathBuffer.setLength(0); pathBuffer.setLength(0);
operatorBuffer.setLength(0); operatorBuffer.setLength(0);
@ -324,16 +397,13 @@ public class PathCompiler {
throw new InvalidPathException("Function wrapping brackets are not matching. A filter function must match [?(<statement>)]"); throw new InvalidPathException("Function wrapping brackets are not matching. A filter function must match [?(<statement>)]");
} }
criteria.add(createCriteria(pathBuffer, operatorBuffer, valueBuffer)); criteria.add(Criteria.create(pathBuffer.toString().trim(), operatorBuffer.toString().trim(), valueBuffer.toString().trim()));
Filter filter2 = Filter.filter(criteria); Filter filter2 = Filter.filter(criteria);
return new PredicatePathToken(filter2); return new PredicatePathToken(filter2);
} }
*/
private static Criteria createCriteria(StringBuilder pathBuffer, StringBuilder operatorBuffer, StringBuilder valueBuffer) {
return Criteria.create(pathBuffer.toString().trim(), operatorBuffer.toString().trim(), valueBuffer.toString().trim());
}
private static boolean isAnd(char c) { private static boolean isAnd(char c) {
return c == '&'; return c == '&';

10
json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java

@ -22,12 +22,15 @@ public class EvaluationContextImpl implements EvaluationContext {
private final Object valueResult; private final Object valueResult;
private final Object pathResult; private final Object pathResult;
private final Path path; private final Path path;
private final Object rootDocument;
private int resultIndex = 0; private int resultIndex = 0;
public EvaluationContextImpl(Path path, Configuration configuration) { public EvaluationContextImpl(Path path, Object rootDocument, Configuration configuration) {
notNull(path, "path can not be null"); notNull(path, "path can not be null");
notNull(rootDocument, "rootDocument can not be null");
notNull(configuration, "configuration can not be null"); notNull(configuration, "configuration can not be null");
this.path = path; this.path = path;
this.rootDocument = rootDocument;
this.configuration = configuration; this.configuration = configuration;
this.valueResult = configuration.jsonProvider().createArray(); this.valueResult = configuration.jsonProvider().createArray();
this.pathResult = configuration.jsonProvider().createArray(); this.pathResult = configuration.jsonProvider().createArray();
@ -52,6 +55,11 @@ public class EvaluationContextImpl implements EvaluationContext {
return configuration; return configuration;
} }
@Override
public Object rootDocument() {
return rootDocument;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override

32
json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicateContextImpl.java

@ -0,0 +1,32 @@
package com.jayway.jsonpath.internal.compiler;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.Predicate;
public class PredicateContextImpl implements Predicate.PredicateContext {
private final Object contextDocument;
private final Object rootDocument;
private final Configuration configuration;
public PredicateContextImpl(Object contextDocument, Object rootDocument, Configuration configuration) {
this.contextDocument = contextDocument;
this.rootDocument = rootDocument;
this.configuration = configuration;
}
@Override
public Object contextDocument() {
return contextDocument;
}
@Override
public Object rootDocument() {
return rootDocument;
}
@Override
public Configuration configuration() {
return configuration;
}
}

26
json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicatePathToken.java

@ -35,7 +35,7 @@ public class PredicatePathToken extends PathToken {
@Override @Override
public void evaluate(String currentPath, Object model, EvaluationContextImpl ctx) { public void evaluate(String currentPath, Object model, EvaluationContextImpl ctx) {
if (ctx.jsonProvider().isMap(model)) { if (ctx.jsonProvider().isMap(model)) {
if (accept(model, ctx.configuration())) { if (accept(model, ctx.rootDocument(), ctx.configuration())) {
if (isLeaf()) { if (isLeaf()) {
ctx.addResult(currentPath, model); ctx.addResult(currentPath, model);
} else { } else {
@ -47,7 +47,7 @@ public class PredicatePathToken extends PathToken {
Iterable<?> objects = ctx.jsonProvider().toIterable(model); Iterable<?> objects = ctx.jsonProvider().toIterable(model);
for (Object idxModel : objects) { for (Object idxModel : objects) {
if (accept(idxModel, ctx.configuration())) { if (accept(idxModel, ctx.rootDocument(), ctx.configuration())) {
handleArrayIndex(idx, currentPath, model, ctx); handleArrayIndex(idx, currentPath, model, ctx);
} }
idx++; idx++;
@ -57,8 +57,8 @@ public class PredicatePathToken extends PathToken {
} }
} }
public boolean accept(final Object obj, final Configuration configuration) { public boolean accept(final Object obj, final Object root, final Configuration configuration) {
Predicate.PredicateContext ctx = new PredicateContextImpl(obj, configuration); Predicate.PredicateContext ctx = new PredicateContextImpl(obj, root, configuration);
for (Predicate predicate : predicates) { for (Predicate predicate : predicates) {
if (!predicate.apply (ctx)) { if (!predicate.apply (ctx)) {
@ -79,23 +79,5 @@ public class PredicatePathToken extends PathToken {
} }
private static class PredicateContextImpl implements Predicate.PredicateContext {
private final Object obj;
private final Configuration configuration;
private PredicateContextImpl(Object obj, Configuration configuration) {
this.obj = obj;
this.configuration = configuration;
}
@Override
public Object target() {
return obj;
}
@Override
public Configuration configuration() {
return configuration;
}
}
} }

2
json-path/src/main/java/com/jayway/jsonpath/internal/compiler/RootPathToken.java

@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory;
/** /**
* *
*/ */
public class RootPathToken extends PathToken /*implements Path*/ { public class RootPathToken extends PathToken {
private static final Logger logger = LoggerFactory.getLogger(RootPathToken.class); private static final Logger logger = LoggerFactory.getLogger(RootPathToken.class);

4
json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java

@ -112,7 +112,7 @@ public class ScanPathToken extends PathToken {
public String getPathFragment() { public String getPathFragment() {
return ".."; return "..";
} }
private static interface Predicate { private static interface Predicate {
Class<?> clazz(); Class<?> clazz();
@ -147,7 +147,7 @@ public class ScanPathToken extends PathToken {
@Override @Override
public boolean matches(Object model) { public boolean matches(Object model) {
return predicatePathToken.accept(model, ctx.configuration()); return predicatePathToken.accept(model, ctx.rootDocument(), ctx.configuration());
} }
} }

14
json-path/src/test/java/com/jayway/jsonpath/BaseTest.java

@ -1,5 +1,6 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.compiler.PredicateContextImpl;
import com.jayway.jsonpath.internal.spi.converter.DefaultConversionProvider; import com.jayway.jsonpath.internal.spi.converter.DefaultConversionProvider;
import com.jayway.jsonpath.internal.spi.json.GsonProvider; import com.jayway.jsonpath.internal.spi.json.GsonProvider;
import com.jayway.jsonpath.spi.converter.ConversionProvider; import com.jayway.jsonpath.spi.converter.ConversionProvider;
@ -40,6 +41,7 @@ public class BaseTest {
" \"boolean-property\" : true, \n" + " \"boolean-property\" : true, \n" +
" \"null-property\" : null, \n" + " \"null-property\" : null, \n" +
" \"int-small-property\" : 1, \n" + " \"int-small-property\" : 1, \n" +
" \"max-price\" : 10, \n" +
" \"store\" : {\n" + " \"store\" : {\n" +
" \"book\" : [\n" + " \"book\" : [\n" +
" {\n" + " {\n" +
@ -83,16 +85,6 @@ public class BaseTest {
"}"; "}";
public Predicate.PredicateContext createPredicateContext(final Object check) { public Predicate.PredicateContext createPredicateContext(final Object check) {
return new Predicate.PredicateContext() { return new PredicateContextImpl(check, check, Configuration.defaultConfiguration());
@Override
public Object target() {
return check;
}
@Override
public Configuration configuration() {
return Configuration.defaultConfiguration();
}
};
} }
} }

2
json-path/src/test/java/com/jayway/jsonpath/FilterTest.java

@ -374,7 +374,7 @@ public class FilterTest extends BaseTest {
Predicate p = new Predicate() { Predicate p = new Predicate() {
@Override @Override
public boolean apply(PredicateContext ctx) { public boolean apply(PredicateContext ctx) {
Map<String, Object> t = (Map<String, Object>) ctx.target(); Map<String, Object> t = (Map<String, Object>) ctx.contextDocument();
Object o = t.get("int-key"); Object o = t.get("int-key");

40
json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java

@ -0,0 +1,40 @@
package com.jayway.jsonpath;
import org.junit.Test;
import java.util.List;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
public class InlineFilterTest extends BaseTest {
private static ReadContext reader = JsonPath.parse(JSON_DOCUMENT);
@Test
public void root_context_can_be_referred_in_predicate() {
List<Double> prices = reader.read("store.book[?(@.display-price <= $.max-price)].display-price", List.class);
assertThat(prices).containsAll(asList(8.95D, 8.99D));
}
@Test
public void multiple_context_object_can_be_refered() {
List all = reader.read("store.book[ ?(@.category == @.category) ]", List.class);
assertThat(all.size()).isEqualTo(4);
List all2 = reader.read("store.book[ ?(@.category == @['category']) ]", List.class);
assertThat(all2.size()).isEqualTo(4);
List all3 = reader.read("store.book[ ?(@ == @) ]", List.class);
assertThat(all3.size()).isEqualTo(4);
List none = reader.read("store.book[ ?(@.category != @.category) ]", List.class);
assertThat(none.size()).isEqualTo(0);
List none2 = reader.read("store.book[ ?(@.category != @) ]", List.class);
assertThat(none2.size()).isEqualTo(0);
}
}

4
json-path/src/test/java/com/jayway/jsonpath/old/FilterTest.java

@ -347,7 +347,7 @@ public class FilterTest extends BaseTest {
Predicate customFilter = new Predicate () { Predicate customFilter = new Predicate () {
@Override @Override
public boolean apply(PredicateContext ctx) { public boolean apply(PredicateContext ctx) {
if (ctx.configuration().jsonProvider().getMapValue(ctx.target(), "name").equals("rootGrandChild_A")) { if (ctx.configuration().jsonProvider().getMapValue(ctx.contextDocument(), "name").equals("rootGrandChild_A")) {
return true; return true;
} }
return false; return false;
@ -370,7 +370,7 @@ public class FilterTest extends BaseTest {
Predicate customFilter = new Predicate() { Predicate customFilter = new Predicate() {
@Override @Override
public boolean apply(PredicateContext ctx) { public boolean apply(PredicateContext ctx) {
return 1 == (Integer)ctx.target(); return 1 == (Integer)ctx.contextDocument();
} }
}; };

2
json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java

@ -237,7 +237,7 @@ public class JsonPathTest extends BaseTest {
assertEquals(JsonPath.read(itemsInStore, "$.[0].[0].author"), "Nigel Rees"); assertEquals(JsonPath.read(itemsInStore, "$.[0].[0].author"), "Nigel Rees");
assertEquals(JsonPath.read(itemsInStore, "$.[0][0].author"), "Nigel Rees"); assertEquals(JsonPath.read(itemsInStore, "$.[0][0].author"), "Nigel Rees");
*/ */
List<String> result = PathCompiler.compile("$.store.*").evaluate(OBJ_DOCUMENT, Configuration.defaultConfiguration()).getPathList(); List<String> result = PathCompiler.compile("$.store.*").evaluate(OBJ_DOCUMENT, OBJ_DOCUMENT, Configuration.defaultConfiguration()).getPathList();
Assertions.assertThat(result).containsOnly( Assertions.assertThat(result).containsOnly(
"$['store']['bicycle']", "$['store']['bicycle']",

12
json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java

@ -118,7 +118,7 @@ public class ScanPathTokenTest {
@Test @Test
public void a_document_can_be_scanned_for_wildcard() { public void a_document_can_be_scanned_for_wildcard() {
List<String> result = PathCompiler.compile("$..[*]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); List<String> result = PathCompiler.compile("$..[*]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList();
assertThat(result).containsOnly( assertThat(result).containsOnly(
"$['store']", "$['store']",
@ -169,7 +169,7 @@ public class ScanPathTokenTest {
@Test @Test
public void a_document_can_be_scanned_for_wildcard2() { public void a_document_can_be_scanned_for_wildcard2() {
List<String> result = PathCompiler.compile("$.store.book[0]..*").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); List<String> result = PathCompiler.compile("$.store.book[0]..*").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList();
assertThat(result).containsOnly( assertThat(result).containsOnly(
"$['store']['book'][0]['address']", "$['store']['book'][0]['address']",
@ -185,7 +185,7 @@ public class ScanPathTokenTest {
@Test @Test
public void a_document_can_be_scanned_for_wildcard3() { public void a_document_can_be_scanned_for_wildcard3() {
List<String> result = PathCompiler.compile("$.phoneNumbers[0]..*").evaluate(DOCUMENT2, Configuration.defaultConfiguration()).getPathList(); List<String> result = PathCompiler.compile("$.phoneNumbers[0]..*").evaluate(DOCUMENT2, DOCUMENT, Configuration.defaultConfiguration()).getPathList();
assertThat(result).containsOnly( assertThat(result).containsOnly(
"$['phoneNumbers'][0]['number']", "$['phoneNumbers'][0]['number']",
@ -196,7 +196,7 @@ public class ScanPathTokenTest {
@Test @Test
public void a_document_can_be_scanned_for_predicate_match() { public void a_document_can_be_scanned_for_predicate_match() {
List<String> result = PathCompiler.compile("$..[?(@.address.city == 'Stockholm')]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); List<String> result = PathCompiler.compile("$..[?(@.address.city == 'Stockholm')]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList();
assertThat(result).containsOnly( assertThat(result).containsOnly(
"$['store']['bicycle']", "$['store']['bicycle']",
@ -207,7 +207,7 @@ public class ScanPathTokenTest {
@Test @Test
public void a_document_can_be_scanned_for_existence() { public void a_document_can_be_scanned_for_existence() {
List<String> result = PathCompiler.compile("$..[?(@.isbn)]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); List<String> result = PathCompiler.compile("$..[?(@.isbn)]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList();
assertThat(result).containsOnly( assertThat(result).containsOnly(
"$['store']['book'][2]"); "$['store']['book'][2]");
@ -216,7 +216,7 @@ public class ScanPathTokenTest {
@Test @Test
public void a_document_can_be_scanned_for_array_indexes() { public void a_document_can_be_scanned_for_array_indexes() {
List<String> result = PathCompiler.compile("$..[(@.length - 1)]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); List<String> result = PathCompiler.compile("$..[(@.length - 1)]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList();
assertThat(result).containsOnly( assertThat(result).containsOnly(
"$['store']['bicycle']['items'][5]", "$['store']['bicycle']['items'][5]",

6
json-path/src/test/java/com/jayway/jsonpath/old/internal/TestInternal3.java

@ -29,7 +29,7 @@ public class TestInternal3 extends TestBase {
@Test @Test
public void a_root_object_can_be_evaluated() { public void a_root_object_can_be_evaluated() {
Map<String, Object> result = compile("$").evaluate(DOC, CONF).getValue(); Map<String, Object> result = compile("$").evaluate(DOC, DOC, CONF).getValue();
assertThat(result) assertThat(result)
.containsKey("store") .containsKey("store")
@ -39,7 +39,7 @@ public class TestInternal3 extends TestBase {
@Test @Test
public void a_definite_array_item_property_can_be_evaluated() { public void a_definite_array_item_property_can_be_evaluated() {
String result = compile("$.store.book[0].author").evaluate(DOC, CONF).getValue(); String result = compile("$.store.book[0].author").evaluate(DOC, DOC, CONF).getValue();
assertThat(result).isEqualTo("Nigel Rees"); assertThat(result).isEqualTo("Nigel Rees");
} }
@ -47,7 +47,7 @@ public class TestInternal3 extends TestBase {
@Test @Test
public void a_wildcard_array_item_property_can_be_evaluated() { public void a_wildcard_array_item_property_can_be_evaluated() {
List result = compile("$.store.book[*].author").evaluate(DOC, CONF).getValue(); List result = compile("$.store.book[*].author").evaluate(DOC, DOC, CONF).getValue();
assertThat(result).containsOnly( assertThat(result).containsOnly(
"Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"); "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien");

Loading…
Cancel
Save