From c20917350e1863c34a3c7b4b5ffc646ee73a9533 Mon Sep 17 00:00:00 2001 From: Kalle Stenflo Date: Wed, 1 Oct 2014 09:49:28 +0200 Subject: [PATCH] Added result cache for root ($) queries performed by predicates. --- .../java/com/jayway/jsonpath/Criteria.java | 16 +++++++--- .../internal/token/EvaluationContextImpl.java | 8 +++++ .../internal/token/PredicateContextImpl.java | 32 ++++++++++++++++++- .../internal/token/PredicatePathToken.java | 8 ++--- .../internal/token/ScanPathToken.java | 2 +- .../java/com/jayway/jsonpath/BaseTest.java | 7 +++- .../com/jayway/jsonpath/InlineFilterTest.java | 9 ++++++ 7 files changed, 71 insertions(+), 11 deletions(-) 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 a25a9ff2..0640cb2e 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -208,8 +208,9 @@ public class Criteria implements Predicate { MATCHES { @Override boolean eval(Object expected, final Object actual, final PredicateContext ctx) { + PredicateContextImpl pci = (PredicateContextImpl) ctx; Predicate exp = (Predicate) expected; - return exp.apply(new PredicateContextImpl(actual, ctx.root(), ctx.configuration())); + return exp.apply(new PredicateContextImpl(actual, ctx.root(), ctx.configuration(), pci.documentPathCache())); } }, NOT_EMPTY { @@ -286,7 +287,7 @@ public class Criteria implements Predicate { if (CriteriaType.EXISTS == criteriaType) { boolean exists = ((Boolean) expected); try { - Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options().build(); + Configuration c = Configuration.builder().jsonProvider(ctx.configuration().jsonProvider()).options(Option.REQUIRE_PROPERTIES).build(); path.evaluate(ctx.item(), ctx.root(), c).getValue(); return exists; } catch (PathNotFoundException e) { @@ -299,8 +300,15 @@ public class Criteria implements Predicate { Object expectedVal = expected; if(expected instanceof Path){ Path expectedPath = (Path) expected; - Object doc = expectedPath.isRootPath()?ctx.root():ctx.item(); - expectedVal = expectedPath.evaluate(doc, ctx.root(), ctx.configuration()).getValue(); + + if(ctx instanceof PredicateContextImpl){ + //This will use cache for document ($) queries + PredicateContextImpl ctxi = (PredicateContextImpl) ctx; + expectedVal = ctxi.evaluate(expectedPath); + } else { + Object doc = expectedPath.isRootPath()?ctx.root():ctx.item(); + expectedVal = expectedPath.evaluate(doc, ctx.root(), ctx.configuration()).getValue(); + } } return criteriaType.eval(expectedVal, actual, ctx); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/EvaluationContextImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/EvaluationContextImpl.java index e4f9ae86..806c17d6 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/EvaluationContextImpl.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/EvaluationContextImpl.java @@ -22,7 +22,9 @@ import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.spi.json.JsonProvider; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.Set; import static com.jayway.jsonpath.internal.Utils.notNull; @@ -37,8 +39,10 @@ public class EvaluationContextImpl implements EvaluationContext { private final Object pathResult; private final Path path; private final Object rootDocument; + private final HashMap documentEvalCache = new HashMap(); private int resultIndex = 0; + public EvaluationContextImpl(Path path, Object rootDocument, Configuration configuration) { notNull(path, "path can not be null"); notNull(rootDocument, "root can not be null"); @@ -50,6 +54,10 @@ public class EvaluationContextImpl implements EvaluationContext { this.pathResult = configuration.jsonProvider().createArray(); } + public HashMap documentEvalCache() { + return documentEvalCache; + } + public void addResult(String path, Object model) { configuration.jsonProvider().setProperty(valueResult, resultIndex, model); configuration.jsonProvider().setProperty(pathResult, resultIndex, path); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicateContextImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicateContextImpl.java index 98924a8b..3abc68d5 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicateContextImpl.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicateContextImpl.java @@ -16,17 +16,47 @@ package com.jayway.jsonpath.internal.token; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Predicate; +import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.spi.mapper.MappingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; public class PredicateContextImpl implements Predicate.PredicateContext { + + private static final Logger logger = LoggerFactory.getLogger(PredicateContextImpl.class); + private final Object contextDocument; private final Object rootDocument; private final Configuration configuration; + private final HashMap documentPathCache; - public PredicateContextImpl(Object contextDocument, Object rootDocument, Configuration configuration) { + public PredicateContextImpl(Object contextDocument, Object rootDocument, Configuration configuration, HashMap documentPathCache) { this.contextDocument = contextDocument; this.rootDocument = rootDocument; this.configuration = configuration; + this.documentPathCache = documentPathCache; + } + + public Object evaluate(Path path){ + Object result; + if(path.isRootPath()){ + if(documentPathCache.containsKey(path)){ + logger.debug("Using cached result for root path: " + path.toString()); + result = documentPathCache.get(path); + } else { + result = path.evaluate(rootDocument, rootDocument, configuration).getValue(); + documentPathCache.put(path, result); + } + } else { + result = path.evaluate(contextDocument, rootDocument, configuration).getValue(); + } + return result; + } + + public HashMap documentPathCache() { + return documentPathCache; } @Override diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicatePathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicatePathToken.java index 47648b9c..963ea51e 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicatePathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PredicatePathToken.java @@ -49,7 +49,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.rootDocument(), ctx.configuration())) { + if (accept(model, ctx.rootDocument(), ctx.configuration(), ctx)) { if (isLeaf()) { ctx.addResult(currentPath, model); } else { @@ -61,7 +61,7 @@ public class PredicatePathToken extends PathToken { Iterable objects = ctx.jsonProvider().toIterable(model); for (Object idxModel : objects) { - if (accept(idxModel, ctx.rootDocument(), ctx.configuration())) { + if (accept(idxModel, ctx.rootDocument(), ctx.configuration(), ctx)) { handleArrayIndex(idx, currentPath, model, ctx); } idx++; @@ -71,8 +71,8 @@ public class PredicatePathToken extends PathToken { } } - public boolean accept(final Object obj, final Object root, final Configuration configuration) { - Predicate.PredicateContext ctx = new PredicateContextImpl(obj, root, configuration); + public boolean accept(final Object obj, final Object root, final Configuration configuration, EvaluationContextImpl evaluationContext) { + Predicate.PredicateContext ctx = new PredicateContextImpl(obj, root, configuration, evaluationContext.documentEvalCache()); for (Predicate predicate : predicates) { if (!predicate.apply (ctx)) { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java index c288efb9..1e591d9d 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/ScanPathToken.java @@ -161,7 +161,7 @@ public class ScanPathToken extends PathToken { @Override public boolean matches(Object model) { - return predicatePathToken.accept(model, ctx.rootDocument(), ctx.configuration()); + return predicatePathToken.accept(model, ctx.rootDocument(), ctx.configuration(), ctx); } } 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 80b42ed1..5db5ce1a 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/BaseTest.java @@ -1,9 +1,13 @@ package com.jayway.jsonpath; +import com.jayway.jsonpath.internal.Path; import com.jayway.jsonpath.internal.spi.json.GsonJsonProvider; import com.jayway.jsonpath.internal.spi.mapper.GsonMappingProvider; +import com.jayway.jsonpath.internal.token.EvaluationContextImpl; import com.jayway.jsonpath.internal.token.PredicateContextImpl; +import java.util.HashMap; + public class BaseTest { /* static { @@ -84,6 +88,7 @@ public class BaseTest { "}"; public Predicate.PredicateContext createPredicateContext(final Object check) { - return new PredicateContextImpl(check, check, Configuration.defaultConfiguration()); + + return new PredicateContextImpl(check, check, Configuration.defaultConfiguration(), new HashMap()); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java index eb634ad3..68477fec 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/InlineFilterTest.java @@ -37,4 +37,13 @@ public class InlineFilterTest extends BaseTest { assertThat(none2.size()).isEqualTo(0); } + + @Test + public void document_queries_are_cached() { + + Object read = reader.read("$.store.book[?(@.display-price <= $.max-price)]"); + + System.out.println(read); + + } }