From 571654f7fb22e81648f8c1b5dd708a143aa5eec1 Mon Sep 17 00:00:00 2001 From: Kalle Stenflo Date: Thu, 18 Sep 2014 16:01:17 +0200 Subject: [PATCH] Allow document references in inline filters. "store.book[?(@.display-price <= $.max-price)].display-price" --- .../java/com/jayway/jsonpath/Criteria.java | 90 +++++++------- .../java/com/jayway/jsonpath/JsonPath.java | 4 +- .../java/com/jayway/jsonpath/Predicate.java | 17 ++- .../jsonpath/internal/CompiledPath.java | 20 +++- .../jsonpath/internal/EvaluationContext.java | 7 ++ .../com/jayway/jsonpath/internal/Path.java | 20 +++- .../jsonpath/internal/PathCompiler.java | 110 ++++++++++++++---- .../compiler/EvaluationContextImpl.java | 10 +- .../compiler/PredicateContextImpl.java | 32 +++++ .../internal/compiler/PredicatePathToken.java | 26 +---- .../internal/compiler/RootPathToken.java | 2 +- .../internal/compiler/ScanPathToken.java | 4 +- .../java/com/jayway/jsonpath/BaseTest.java | 14 +-- .../java/com/jayway/jsonpath/FilterTest.java | 2 +- .../com/jayway/jsonpath/InlineFilterTest.java | 40 +++++++ .../com/jayway/jsonpath/old/FilterTest.java | 4 +- .../com/jayway/jsonpath/old/JsonPathTest.java | 2 +- .../old/internal/ScanPathTokenTest.java | 12 +- .../jsonpath/old/internal/TestInternal3.java | 6 +- 19 files changed, 302 insertions(+), 120 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicateContextImpl.java create mode 100644 json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java index aab2921d..34552521 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/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.PathCompiler; +import com.jayway.jsonpath.internal.compiler.PredicateContextImpl; +import com.sun.tools.doclets.formats.html.SourceToHTMLConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +36,7 @@ public class Criteria implements Predicate { private static enum CriteriaType { EQ { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { boolean res = (0 == safeCompare(expected, actual)); logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); return res; @@ -42,7 +44,7 @@ public class Criteria implements Predicate { }, NE { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { boolean res = (0 != safeCompare(expected, actual)); logger.debug("[{}] {} [{}] => {}", actual, name(), expected, res); return res; @@ -50,7 +52,7 @@ public class Criteria implements Predicate { }, GT { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { if ((expected == null) ^ (actual == null)) { return false; } @@ -61,7 +63,7 @@ public class Criteria implements Predicate { }, GTE { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { if ((expected == null) ^ (actual == null)) { return false; } @@ -72,7 +74,7 @@ public class Criteria implements Predicate { }, LT { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { if ((expected == null) ^ (actual == null)) { return false; } @@ -83,7 +85,7 @@ public class Criteria implements Predicate { }, LTE { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { if ((expected == null) ^ (actual == null)) { return false; } @@ -94,7 +96,7 @@ public class Criteria implements Predicate { }, IN { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { boolean res = false; Collection exps = (Collection) expected; for (Object exp : exps) { @@ -109,7 +111,7 @@ public class Criteria implements Predicate { }, NIN { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { Collection nexps = (Collection) expected; boolean res = !nexps.contains(actual); logger.debug("[{}] {} [{}] => {}", actual, name(), join(", ", nexps), res); @@ -118,13 +120,13 @@ public class Criteria implements Predicate { }, ALL { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { boolean res = true; Collection exps = (Collection) expected; - if (configuration.jsonProvider().isArray(actual)) { + if (ctx.configuration().jsonProvider().isArray(actual)) { for (Object exp : exps) { boolean found = false; - for (Object check : configuration.jsonProvider().toIterable(actual)) { + for (Object check : ctx.configuration().jsonProvider().toIterable(actual)) { if (0 == safeCompare(exp, check)) { found = true; break; @@ -135,7 +137,7 @@ public class Criteria implements Predicate { 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 { res = false; logger.debug("[{}] {} [{}] => {}", "", name(), join(", ", exps), res); @@ -145,11 +147,11 @@ public class Criteria implements Predicate { }, SIZE { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { int size = (Integer) expected; boolean res; - if (configuration.jsonProvider().isArray(actual)) { - int length = configuration.jsonProvider().length(actual); + if (ctx.configuration().jsonProvider().isArray(actual)) { + int length = ctx.configuration().jsonProvider().length(actual); res = (length == size); logger.debug("Array with size {} {} {} => {}", length, name(), size, res); } else if (actual instanceof String) { @@ -165,14 +167,14 @@ public class Criteria implements Predicate { }, EXISTS { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { //This must be handled outside throw new UnsupportedOperationException(); } }, TYPE { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { final Class expType = (Class) expected; final Class actType = actual == null ? null : actual.getClass(); @@ -181,7 +183,7 @@ public class Criteria implements Predicate { }, REGEX { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { boolean res = false; final Pattern pattern = (Pattern) expected; if (actual != null && actual instanceof String) { @@ -193,28 +195,18 @@ public class Criteria implements Predicate { }, MATCHES { @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; - return exp.apply(new PredicateContext() { - @Override - public Object target() { - return actual; - } - - @Override - public Configuration configuration() { - return configuration; - } - }); + return exp.apply(new PredicateContextImpl(actual, ctx.rootDocument(), ctx.configuration())); } }, NOT_EMPTY { @Override - boolean eval(Object expected, Object actual, Configuration configuration) { + boolean eval(Object expected, Object actual, PredicateContext ctx) { boolean res = false; if (actual != null) { - if (configuration.jsonProvider().isArray(actual)) { - int len = configuration.jsonProvider().length(actual); + if (ctx.configuration().jsonProvider().isArray(actual)) { + int len = ctx.configuration().jsonProvider().length(actual); res = (0 != len); logger.debug("array length = {} {} => {}", len, name(), res); } 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) { if ("==".equals(str)) { @@ -283,16 +275,23 @@ public class Criteria implements Predicate { boolean exists = ((Boolean) expected); try { 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; } catch (PathNotFoundException e) { return !exists; } } else { 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) { return false; } catch (PathNotFoundException e) { @@ -571,6 +570,10 @@ public class Criteria implements Predicate { private static int safeCompare(Object expected, Object providerParsed) throws ValueCompareException { + if(expected == providerParsed){ + return 0; + } + boolean expNullish = isNullish(expected); boolean provNullish = isNullish(providerParsed); @@ -611,12 +614,21 @@ public class Criteria implements Predicate { 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); } else if (operator.isEmpty()) { return Criteria.where(path).exists(true); } 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); + } + } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java index 304eac69..4dcc3490 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -172,9 +172,9 @@ public class JsonPath { try { if(optAsPathList){ - return (T)path.evaluate(jsonObject, configuration).getPath(); + return (T)path.evaluate(jsonObject, jsonObject, configuration).getPath(); } else { - Object res = path.evaluate(jsonObject, configuration).getValue(); + Object res = path.evaluate(jsonObject, jsonObject, configuration).getValue(); if(optAlwaysReturnList && path.isDefinite()){ Object array = configuration.jsonProvider().createArray(); configuration.jsonProvider().setProperty(array, 0, res); diff --git a/json-path/src/main/java/com/jayway/jsonpath/Predicate.java b/json-path/src/main/java/com/jayway/jsonpath/Predicate.java index f32064ac..5eeebd1c 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Predicate.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Predicate.java @@ -7,11 +7,24 @@ public interface Predicate { boolean apply(PredicateContext ctx); - 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(); } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/CompiledPath.java b/json-path/src/main/java/com/jayway/jsonpath/internal/CompiledPath.java index dc8fe82e..75a9e556 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/CompiledPath.java +++ b/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.LoggerFactory; -public class CompiledPath implements Path { +public class CompiledPath implements Path { private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class); private final PathToken root; + private final boolean isRootPath; - public CompiledPath(PathToken root) { + + public CompiledPath(PathToken root, boolean isRootPath) { this.root = root; + this.isRootPath = isRootPath; + } + + @Override + public boolean isRootPath() { + return isRootPath; } @Override - public EvaluationContext evaluate(Object model, Configuration configuration) { - if(logger.isDebugEnabled()) { + public EvaluationContext evaluate(Object document, Object rootDocument, Configuration configuration) { + if (logger.isDebugEnabled()) { logger.debug("Evaluating path: {}", toString()); } - EvaluationContextImpl ctx = new EvaluationContextImpl(this, configuration); - root.evaluate("", model, ctx); + EvaluationContextImpl ctx = new EvaluationContextImpl(this, rootDocument, configuration); + root.evaluate("", document, ctx); return ctx; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/EvaluationContext.java b/json-path/src/main/java/com/jayway/jsonpath/internal/EvaluationContext.java index 4cdae592..a6dad80d 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/EvaluationContext.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/EvaluationContext.java @@ -12,6 +12,13 @@ public interface EvaluationContext { */ 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 * configuration contains the {@link com.jayway.jsonpath.Option#ALWAYS_RETURN_LIST} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/Path.java b/json-path/src/main/java/com/jayway/jsonpath/internal/Path.java index b3ba15ea..bb2810c1 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/Path.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/Path.java @@ -7,8 +7,26 @@ import com.jayway.jsonpath.Configuration; */ 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(); + /** + * + * @return true id this path is starts with '$' and false if the path starts with '@' + */ + boolean isRootPath(); + } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java index f68f631f..0a52a113 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathCompiler.java +++ b/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 SPACE = ' '; private static final Cache cache = new Cache(200); - + private static final String[] OPERATORS = {"==", ">=", "<=", "!=", "<", ">",}; public static Path compile(String path, Predicate... filters) { notEmpty(path, "Path may not be null empty"); path = path.trim(); + + LinkedList filterList = new LinkedList(asList(filters)); - if (path.charAt(0) != '$') { + if (path.charAt(0) != '$' && path.charAt(0) != '@') { 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); - if(p != null){ + if (p != null) { + logger.debug("Using cached path"); return p; } @@ -73,7 +83,7 @@ public class PathCompiler { break; case BRACKET_OPEN: positions = fastForwardUntilClosed(path, i); - fragment = path.substring(i, i+positions); + fragment = path.substring(i, i + positions); i += positions; break; case PERIOD: @@ -92,7 +102,7 @@ public class PathCompiler { } else { 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; } @@ -104,7 +114,7 @@ public class PathCompiler { default: 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; break; } @@ -116,7 +126,7 @@ public class PathCompiler { } while (i < path.length()); - Path pa = new CompiledPath(root); + Path pa = new CompiledPath(root, isRootPath); cache.put(cacheKey, pa); @@ -222,7 +232,7 @@ public class PathCompiler { switch (current) { case '?': - return analyzeCriteriaSequence(); + return analyzeCriteriaSequence2(); case '\'': return analyzeProperty(); default: @@ -239,6 +249,69 @@ public class PathCompiler { 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 predicates = new ArrayList(); + + 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 == 'bar')] @@ -276,20 +349,20 @@ public class PathCompiler { break; case '(': - if(!propertyOpen) { + if (!propertyOpen) { functionBracketOpened = true; break; } case ')': - if(!propertyOpen) { + if (!propertyOpen) { functionBracketClosed = true; break; } default: - if('\'' == current){ - if (propertyOpen){ + if ('\'' == current) { + if (propertyOpen) { propertyOpen = false; } else { propertyOpen = true; @@ -303,7 +376,7 @@ public class PathCompiler { if (isLogicOperatorChar(pathFragment.charAt(i + 1))) { ++i; } - criteria.add(createCriteria(pathBuffer, operatorBuffer, valueBuffer)); + criteria.add(Criteria.create(pathBuffer.toString().trim(), operatorBuffer.toString().trim(), valueBuffer.toString().trim())); pathBuffer.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 [?()]"); } - 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); } - - 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) { return c == '&'; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java index 0a4131c7..6651ae53 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/EvaluationContextImpl.java +++ b/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 pathResult; private final Path path; + private final Object rootDocument; 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(rootDocument, "rootDocument can not be null"); notNull(configuration, "configuration can not be null"); this.path = path; + this.rootDocument = rootDocument; this.configuration = configuration; this.valueResult = configuration.jsonProvider().createArray(); this.pathResult = configuration.jsonProvider().createArray(); @@ -52,6 +55,11 @@ public class EvaluationContextImpl implements EvaluationContext { return configuration; } + @Override + public Object rootDocument() { + return rootDocument; + } + @SuppressWarnings("unchecked") @Override diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicateContextImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicateContextImpl.java new file mode 100644 index 00000000..7c2b9e9a --- /dev/null +++ b/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; + } + +} \ No newline at end of file diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicatePathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicatePathToken.java index 199e095f..75700cc7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicatePathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/PredicatePathToken.java @@ -35,7 +35,7 @@ public class PredicatePathToken extends PathToken { @Override public void evaluate(String currentPath, Object model, EvaluationContextImpl ctx) { if (ctx.jsonProvider().isMap(model)) { - if (accept(model, ctx.configuration())) { + if (accept(model, ctx.rootDocument(), ctx.configuration())) { if (isLeaf()) { ctx.addResult(currentPath, model); } else { @@ -47,7 +47,7 @@ public class PredicatePathToken extends PathToken { Iterable objects = ctx.jsonProvider().toIterable(model); for (Object idxModel : objects) { - if (accept(idxModel, ctx.configuration())) { + if (accept(idxModel, ctx.rootDocument(), ctx.configuration())) { handleArrayIndex(idx, currentPath, model, ctx); } idx++; @@ -57,8 +57,8 @@ public class PredicatePathToken extends PathToken { } } - public boolean accept(final Object obj, final Configuration configuration) { - Predicate.PredicateContext ctx = new PredicateContextImpl(obj, configuration); + public boolean accept(final Object obj, final Object root, final Configuration configuration) { + Predicate.PredicateContext ctx = new PredicateContextImpl(obj, root, configuration); for (Predicate predicate : predicates) { 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; - } - } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/RootPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/RootPathToken.java index 1db8466b..7f25750b 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/RootPathToken.java +++ b/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); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java index 3747c046..65a72afb 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/compiler/ScanPathToken.java @@ -112,7 +112,7 @@ public class ScanPathToken extends PathToken { public String getPathFragment() { return ".."; } - + private static interface Predicate { Class clazz(); @@ -147,7 +147,7 @@ public class ScanPathToken extends PathToken { @Override public boolean matches(Object model) { - return predicatePathToken.accept(model, ctx.configuration()); + return predicatePathToken.accept(model, ctx.rootDocument(), ctx.configuration()); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java b/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java index 079e1f67..fd72c748 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java @@ -1,5 +1,6 @@ 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.json.GsonProvider; import com.jayway.jsonpath.spi.converter.ConversionProvider; @@ -40,6 +41,7 @@ public class BaseTest { " \"boolean-property\" : true, \n" + " \"null-property\" : null, \n" + " \"int-small-property\" : 1, \n" + + " \"max-price\" : 10, \n" + " \"store\" : {\n" + " \"book\" : [\n" + " {\n" + @@ -83,16 +85,6 @@ public class BaseTest { "}"; public Predicate.PredicateContext createPredicateContext(final Object check) { - return new Predicate.PredicateContext() { - @Override - public Object target() { - return check; - } - - @Override - public Configuration configuration() { - return Configuration.defaultConfiguration(); - } - }; + return new PredicateContextImpl(check, check, Configuration.defaultConfiguration()); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java index 5ced853d..10452608 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java @@ -374,7 +374,7 @@ public class FilterTest extends BaseTest { Predicate p = new Predicate() { @Override public boolean apply(PredicateContext ctx) { - Map t = (Map) ctx.target(); + Map t = (Map) ctx.contextDocument(); Object o = t.get("int-key"); diff --git a/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java new file mode 100644 index 00000000..eb634ad3 --- /dev/null +++ b/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 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); + + } +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/FilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/FilterTest.java index d5faddbf..15f9620d 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/FilterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/old/FilterTest.java @@ -347,7 +347,7 @@ public class FilterTest extends BaseTest { Predicate customFilter = new Predicate () { @Override 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 false; @@ -370,7 +370,7 @@ public class FilterTest extends BaseTest { Predicate customFilter = new Predicate() { @Override public boolean apply(PredicateContext ctx) { - return 1 == (Integer)ctx.target(); + return 1 == (Integer)ctx.contextDocument(); } }; diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java index a314bcc4..7c7da71f 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java +++ b/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"); */ - List result = PathCompiler.compile("$.store.*").evaluate(OBJ_DOCUMENT, Configuration.defaultConfiguration()).getPathList(); + List result = PathCompiler.compile("$.store.*").evaluate(OBJ_DOCUMENT, OBJ_DOCUMENT, Configuration.defaultConfiguration()).getPathList(); Assertions.assertThat(result).containsOnly( "$['store']['bicycle']", diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java index 5bf12e76..a3f18e0b 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/old/internal/ScanPathTokenTest.java @@ -118,7 +118,7 @@ public class ScanPathTokenTest { @Test public void a_document_can_be_scanned_for_wildcard() { - List result = PathCompiler.compile("$..[*]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); + List result = PathCompiler.compile("$..[*]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList(); assertThat(result).containsOnly( "$['store']", @@ -169,7 +169,7 @@ public class ScanPathTokenTest { @Test public void a_document_can_be_scanned_for_wildcard2() { - List result = PathCompiler.compile("$.store.book[0]..*").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); + List result = PathCompiler.compile("$.store.book[0]..*").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList(); assertThat(result).containsOnly( "$['store']['book'][0]['address']", @@ -185,7 +185,7 @@ public class ScanPathTokenTest { @Test public void a_document_can_be_scanned_for_wildcard3() { - List result = PathCompiler.compile("$.phoneNumbers[0]..*").evaluate(DOCUMENT2, Configuration.defaultConfiguration()).getPathList(); + List result = PathCompiler.compile("$.phoneNumbers[0]..*").evaluate(DOCUMENT2, DOCUMENT, Configuration.defaultConfiguration()).getPathList(); assertThat(result).containsOnly( "$['phoneNumbers'][0]['number']", @@ -196,7 +196,7 @@ public class ScanPathTokenTest { @Test public void a_document_can_be_scanned_for_predicate_match() { - List result = PathCompiler.compile("$..[?(@.address.city == 'Stockholm')]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); + List result = PathCompiler.compile("$..[?(@.address.city == 'Stockholm')]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList(); assertThat(result).containsOnly( "$['store']['bicycle']", @@ -207,7 +207,7 @@ public class ScanPathTokenTest { @Test public void a_document_can_be_scanned_for_existence() { - List result = PathCompiler.compile("$..[?(@.isbn)]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); + List result = PathCompiler.compile("$..[?(@.isbn)]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList(); assertThat(result).containsOnly( "$['store']['book'][2]"); @@ -216,7 +216,7 @@ public class ScanPathTokenTest { @Test public void a_document_can_be_scanned_for_array_indexes() { - List result = PathCompiler.compile("$..[(@.length - 1)]").evaluate(DOCUMENT, Configuration.defaultConfiguration()).getPathList(); + List result = PathCompiler.compile("$..[(@.length - 1)]").evaluate(DOCUMENT, DOCUMENT, Configuration.defaultConfiguration()).getPathList(); assertThat(result).containsOnly( "$['store']['bicycle']['items'][5]", diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/internal/TestInternal3.java b/json-path/src/test/java/com/jayway/jsonpath/old/internal/TestInternal3.java index c54d1426..8a075095 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/internal/TestInternal3.java +++ b/json-path/src/test/java/com/jayway/jsonpath/old/internal/TestInternal3.java @@ -29,7 +29,7 @@ public class TestInternal3 extends TestBase { @Test public void a_root_object_can_be_evaluated() { - Map result = compile("$").evaluate(DOC, CONF).getValue(); + Map result = compile("$").evaluate(DOC, DOC, CONF).getValue(); assertThat(result) .containsKey("store") @@ -39,7 +39,7 @@ public class TestInternal3 extends TestBase { @Test 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"); } @@ -47,7 +47,7 @@ public class TestInternal3 extends TestBase { @Test 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( "Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien");