diff --git a/json-path/src/main/java/com/jayway/jsonpath/ParseContext.java b/json-path/src/main/java/com/jayway/jsonpath/ParseContext.java index 16000b3e..edbdf03e 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/ParseContext.java +++ b/json-path/src/main/java/com/jayway/jsonpath/ParseContext.java @@ -17,7 +17,6 @@ package com.jayway.jsonpath; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URL; public interface ParseContext { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java b/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java index d56128b1..d4919ca8 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/DefaultsImpl.java @@ -1,8 +1,5 @@ package com.jayway.jsonpath.internal; -import java.util.EnumSet; -import java.util.Set; - import com.jayway.jsonpath.Configuration.Defaults; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.spi.json.JsonProvider; @@ -10,6 +7,9 @@ import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider; import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider; import com.jayway.jsonpath.spi.mapper.MappingProvider; +import java.util.EnumSet; +import java.util.Set; + public final class DefaultsImpl implements Defaults { public static final DefaultsImpl INSTANCE = new DefaultsImpl(); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java b/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java index 7f207802..5e072bfb 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java @@ -23,6 +23,8 @@ import com.jayway.jsonpath.ParseContext; import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.ReadContext; import com.jayway.jsonpath.TypeRef; +import com.jayway.jsonpath.spi.cache.Cache; +import com.jayway.jsonpath.spi.cache.CacheProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,11 +32,13 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.LinkedList; import java.util.List; import static com.jayway.jsonpath.JsonPath.compile; import static com.jayway.jsonpath.internal.Utils.notEmpty; import static com.jayway.jsonpath.internal.Utils.notNull; +import static java.util.Arrays.asList; public class JsonReader implements ParseContext, DocumentContext { @@ -132,7 +136,21 @@ public class JsonReader implements ParseContext, DocumentContext { @Override public T read(String path, Predicate... filters) { notEmpty(path, "path can not be null or empty"); - return read(compile(path, filters)); + Cache cache = CacheProvider.getCache(); + + path = path.trim(); + LinkedList filterStack = new LinkedList(asList(filters)); + String cacheKey = Utils.concat(path, filterStack.toString()); + + JsonPath jsonPath = cache.get(cacheKey); + if(jsonPath != null){ + return read(jsonPath); + }else { + jsonPath = compile(path, filters); + cache.put(cacheKey, jsonPath); + return read(jsonPath); + } + } @Override 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 a0760fc2..a42c74c1 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 @@ -5,7 +5,6 @@ import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.Predicate; import com.jayway.jsonpath.internal.token.ArrayIndexOperation; import com.jayway.jsonpath.internal.token.ArraySliceOperation; -import com.jayway.jsonpath.internal.token.FunctionPathToken; import com.jayway.jsonpath.internal.token.PathTokenAppender; import com.jayway.jsonpath.internal.token.PathTokenFactory; import com.jayway.jsonpath.internal.token.RootPathToken; @@ -64,12 +63,12 @@ public class PathCompiler { fail("Path must not end wid a scan operation '..'"); } LinkedList filterStack = new LinkedList(asList(filters)); - String cacheKey = Utils.concat(path, filterStack.toString()); - Path p = cache.get(cacheKey); - if (p == null) { - p = new PathCompiler(path.trim(), filterStack).compile(); - cache.put(cacheKey, p); - } +// String cacheKey = Utils.concat(path, filterStack.toString()); +// Path p = cache.get(cacheKey); +// if (p == null) { + Path p = new PathCompiler(path.trim(), filterStack).compile(); +// cache.put(cacheKey, p); +// } return p; } catch (Exception e) { InvalidPathException ipe; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/FunctionFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/FunctionFactory.java index 35104ab3..a03e72a3 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/FunctionFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/FunctionFactory.java @@ -2,7 +2,11 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.Function; import com.jayway.jsonpath.InvalidPathException; -import com.jayway.jsonpath.internal.function.numeric.*; +import com.jayway.jsonpath.internal.function.numeric.Average; +import com.jayway.jsonpath.internal.function.numeric.Max; +import com.jayway.jsonpath.internal.function.numeric.Min; +import com.jayway.jsonpath.internal.function.numeric.StandardDeviation; +import com.jayway.jsonpath.internal.function.numeric.Sum; import java.util.Collections; import java.util.HashMap; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java index 12188833..1fdf1278 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/Length.java @@ -3,9 +3,6 @@ package com.jayway.jsonpath.internal.function; import com.jayway.jsonpath.Function; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.PathRef; -import net.minidev.json.JSONArray; - -import java.util.*; /** * Provides the length of a JSONArray Object diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java index d7bb97c1..ddb2ad7d 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/AbstractAggregation.java @@ -3,9 +3,6 @@ package com.jayway.jsonpath.internal.function.numeric; import com.jayway.jsonpath.Function; import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.PathRef; -import net.minidev.json.JSONArray; - -import java.util.Iterator; /** * Defines the pattern for processing numerical values via an abstract implementation that iterates over the collection diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java index 47b76383..d9bc9292 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java @@ -15,16 +15,10 @@ package com.jayway.jsonpath.internal.token; import com.jayway.jsonpath.Function; -import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.internal.PathRef; import com.jayway.jsonpath.internal.Utils; -import com.jayway.jsonpath.internal.function.FunctionFactory; -import com.jayway.jsonpath.internal.function.numeric.Average; -import com.jayway.jsonpath.internal.function.Length; -import com.jayway.jsonpath.internal.function.PassthruFunction; -import com.jayway.jsonpath.internal.function.numeric.Sum; import com.jayway.jsonpath.spi.json.JsonProvider; import java.util.List; diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java new file mode 100644 index 00000000..bef6e7c2 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/Cache.java @@ -0,0 +1,23 @@ +package com.jayway.jsonpath.spi.cache; + +import com.jayway.jsonpath.InvalidJsonException; +import com.jayway.jsonpath.JsonPath; + +public interface Cache { + + /** + * Get the Cached JsonPath + * @param key cache key to lookup the JsonPath + * @return JsonPath + */ + public JsonPath get(String key); + + /** + * Add JsonPath to the cache + * @param key cache key to store the JsonPath + * @param value JsonPath to be cached + * @return void + * @throws InvalidJsonException + */ + public void put(String key, JsonPath value); +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java new file mode 100644 index 00000000..1a256f3d --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/CacheProvider.java @@ -0,0 +1,21 @@ +package com.jayway.jsonpath.spi.cache; + +import com.jayway.jsonpath.JsonPathException; + +public class CacheProvider { + private static Cache cache = new LRUCache(200); + + public static void setCache(Cache cache){ + if (cache != null){ + CacheProvider.cache = cache; + } + } + + public static Cache getCache() { + try { + return cache; + } catch (Exception e) { + throw new JsonPathException("Failed to get cache", e); + } + } +} \ No newline at end of file diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/LRUCache.java b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/LRUCache.java new file mode 100644 index 00000000..3646e2c8 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/LRUCache.java @@ -0,0 +1,112 @@ +/* + * Copyright 2011 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jayway.jsonpath.spi.cache; + +import com.jayway.jsonpath.JsonPath; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +public class LRUCache implements Cache { + + private final ReentrantLock lock = new ReentrantLock(); + + private final Map map = new ConcurrentHashMap(); + private final Deque queue = new LinkedList(); + private final int limit; + + public LRUCache(int limit) { + this.limit = limit; + } + + public void put(String key, JsonPath value) { + JsonPath oldValue = map.put(key, value); + if (oldValue != null) { + removeThenAddKey(key); + } else { + addKey(key); + } + if (map.size() > limit) { + map.remove(removeLast()); + } + } + + public JsonPath get(String key) { + JsonPath jsonPath = map.get(key); + if(jsonPath != null){ + removeThenAddKey(key); + } + return jsonPath; + } + + private void addKey(String key) { + lock.lock(); + try { + queue.addFirst(key); + } finally { + lock.unlock(); + } + } + + private String removeLast() { + lock.lock(); + try { + final String removedKey = queue.removeLast(); + return removedKey; + } finally { + lock.unlock(); + } + } + + private void removeThenAddKey(String key) { + lock.lock(); + try { + queue.removeFirstOccurrence(key); + queue.addFirst(key); + } finally { + lock.unlock(); + } + + } + + private void removeFirstOccurrence(String key) { + lock.lock(); + try { + queue.removeFirstOccurrence(key); + } finally { + lock.unlock(); + } + } + + public JsonPath getSilent(String key) { + return map.get(key); + } + + public void remove(String key) { + removeFirstOccurrence(key); + map.remove(key); + } + + public int size() { + return map.size(); + } + + public String toString() { + return map.toString(); + } +} \ No newline at end of file diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/cache/NOOPCache.java b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/NOOPCache.java new file mode 100644 index 00000000..66cd93c4 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/cache/NOOPCache.java @@ -0,0 +1,14 @@ +package com.jayway.jsonpath.spi.cache; + +import com.jayway.jsonpath.JsonPath; + +public class NOOPCache implements Cache { + @Override + public JsonPath get(String key) { + return null; + } + + @Override + public void put(String key, JsonPath value) { + } +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/JsonReaderTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/JsonReaderTest.java new file mode 100644 index 00000000..7cac7629 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/JsonReaderTest.java @@ -0,0 +1,33 @@ +package com.jayway.jsonpath.internal; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import com.jayway.jsonpath.BaseTest; +import com.jayway.jsonpath.Criteria; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.Filter; +import com.jayway.jsonpath.JsonPath; + +public class JsonReaderTest extends BaseTest { + + @Test + public void cached_path_with_predicates() { + + Filter feq = Filter.filter(Criteria.where("category").eq("reference")); + Filter fne = Filter.filter(Criteria.where("category").ne("reference")); + + DocumentContext JsonDoc = JsonPath.parse(JSON_DOCUMENT); + + List eq = JsonDoc.read("$.store.book[?].category", feq); + List ne = JsonDoc.read("$.store.book[?].category", fne); + + Assertions.assertThat(eq).contains("reference"); + Assertions.assertThat(ne).doesNotContain("reference"); + } + +}