diff --git a/jsurfer-benchmark/pom.xml b/jsurfer-benchmark/pom.xml index 9d9c483..6819abd 100644 --- a/jsurfer-benchmark/pom.xml +++ b/jsurfer-benchmark/pom.xml @@ -5,7 +5,7 @@ jsurfer com.github.jsurfer - 1.2.6 + 1.2.7 4.0.0 diff --git a/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkCollectObject.java b/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkCollectObject.java new file mode 100644 index 0000000..c036f65 --- /dev/null +++ b/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkCollectObject.java @@ -0,0 +1,104 @@ +/* + * The MIT License + * + * Copyright (c) 2015 WANG Lingsong + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jsfr.json; + +import com.google.common.io.Resources; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) +@Threads(1) +@Fork(1) +@State(Scope.Benchmark) +public class BenchmarkCollectObject { + + private JsonSurfer gsonSurfer; + private JsonSurfer jacksonSurfer; + private JsonSurfer simpleSurfer; + private SurfingConfiguration surfingConfiguration; + private String json; + + @Setup + public void setup() throws Exception { + gsonSurfer = JsonSurfer.gson(); + jacksonSurfer = JsonSurfer.jackson(); + simpleSurfer = JsonSurfer.simple(); + TypedJsonPathListener collectOneListener = new TypedJsonPathListener() { + + private Blackhole blackhole = new Blackhole(); + + @Override + public void onTypedValue(Object value, ParsingContext context) throws Exception { + blackhole.consume(value); + } + }; + surfingConfiguration = SurfingConfiguration.builder().bind("$.store.book[*]", Map.class, collectOneListener).build(); + json = Resources.toString(Resources.getResource("sample.json"), StandardCharsets.UTF_8); + } + + + @Benchmark + public boolean benchmarkGsonWithJsonSurfer() { + gsonSurfer.surf(json, surfingConfiguration); + return true; + } + + @Benchmark + public boolean benchmarkJacksonWithJsonSurfer() { + jacksonSurfer.surf(json, surfingConfiguration); + return true; + } + + @Benchmark + public boolean benchmarkJsonSimpleWithJsonSurfer() { + simpleSurfer.surf(json, surfingConfiguration); + return true; + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(BenchmarkCollectObject.class.getSimpleName()) +// .addProfiler(FlightRecordingProfiler.class) + .build(); + new Runner(opt).run(); + } + +} diff --git a/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLargeJsonWithoutStreaming.java b/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLargeJsonWithoutStreaming.java index 4ad4b64..b3da7fd 100644 --- a/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLargeJsonWithoutStreaming.java +++ b/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLargeJsonWithoutStreaming.java @@ -53,9 +53,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -/** - * Created by Administrator on 2015/7/20 0020. - */ @Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) @Threads(1) diff --git a/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLongText.java b/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLongText.java index 20aded1..a4bb0ad 100644 --- a/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLongText.java +++ b/jsurfer-benchmark/src/main/java/org/jsfr/json/BenchmarkParseLongText.java @@ -48,9 +48,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -/** - * Created by Administrator on 2015/7/18 0018. - */ @Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) @Threads(1) diff --git a/jsurfer-core/pom.xml b/jsurfer-core/pom.xml index 4165f5a..c7534b8 100644 --- a/jsurfer-core/pom.xml +++ b/jsurfer-core/pom.xml @@ -5,7 +5,7 @@ jsurfer com.github.jsurfer - 1.2.6 + 1.2.7 4.0.0 diff --git a/jsurfer-core/src/main/java/org/jsfr/json/BuilderFactory.java b/jsurfer-core/src/main/java/org/jsfr/json/BuilderFactory.java deleted file mode 100644 index 25e5816..0000000 --- a/jsurfer-core/src/main/java/org/jsfr/json/BuilderFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2015 WANG Lingsong - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.jsfr.json; - -import org.jsfr.json.path.JsonPath; - -public class BuilderFactory { - - public static SurfingConfiguration.Builder config() { - return SurfingConfiguration.builder(); - } - - public static JsonPath.Builder root() { - return JsonPath.Builder.start(); - } - -} diff --git a/jsurfer-core/src/main/java/org/jsfr/json/ContentDispatcher.java b/jsurfer-core/src/main/java/org/jsfr/json/ContentDispatcher.java index 0b2fc58..20b7772 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/ContentDispatcher.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/ContentDispatcher.java @@ -27,9 +27,6 @@ import java.util.Iterator; import java.util.LinkedList; -/** - * Created by Administrator on 2015/3/21. - */ class ContentDispatcher implements JsonSaxHandler { private LinkedList receiver = new LinkedList(); diff --git a/jsurfer-core/src/main/java/org/jsfr/json/JsonCollector.java b/jsurfer-core/src/main/java/org/jsfr/json/JsonCollector.java index a901959..b14e882 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/JsonCollector.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/JsonCollector.java @@ -26,16 +26,13 @@ import java.util.Collection; -/** - * Created by Administrator on 2015/3/21. - */ class JsonCollector extends JsonDomBuilder { private ErrorHandlingStrategy errorHandlingStrategy; - private JsonPathListener[] jsonPathListeners; + private Collection jsonPathListeners; private ParsingContext context; - public JsonCollector(JsonPathListener[] jsonPathListeners, ParsingContext context, ErrorHandlingStrategy errorHandlingStrategy) { + public JsonCollector(Collection jsonPathListeners, ParsingContext context, ErrorHandlingStrategy errorHandlingStrategy) { this.jsonPathListeners = jsonPathListeners; this.context = context; this.errorHandlingStrategy = errorHandlingStrategy; @@ -45,7 +42,7 @@ public JsonCollector(JsonPathListener[] jsonPathListeners, ParsingContext contex public boolean endObject() { super.endObject(); if (isInRoot()) { - Object result = getCurrentNode(); + Object result = peekValue(); for (JsonPathListener jsonPathListener : jsonPathListeners) { if (!context.isStopped()) { try { @@ -65,27 +62,7 @@ public boolean endObject() { public boolean endArray() { super.endArray(); if (isInRoot()) { - Object result = getCurrentNode(); - for (JsonPathListener jsonPathListener : jsonPathListeners) { - if (!context.isStopped()) { - try { - jsonPathListener.onValue(result, context); - } catch (Exception e) { - errorHandlingStrategy.handleExceptionFromListener(e, context); - } - } - } - this.clear(); - return false; - } - return true; - } - - @Override - public boolean primitive(PrimitiveHolder value) { - super.primitive(value); - if (isInRoot()) { - Object result = getCurrentNode(); + Object result = peekValue(); for (JsonPathListener jsonPathListener : jsonPathListeners) { if (!context.isStopped()) { try { diff --git a/jsurfer-core/src/main/java/org/jsfr/json/JsonDomBuilder.java b/jsurfer-core/src/main/java/org/jsfr/json/JsonDomBuilder.java index 59614eb..0796549 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/JsonDomBuilder.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/JsonDomBuilder.java @@ -26,25 +26,67 @@ import org.jsfr.json.provider.JsonProvider; -import java.util.Stack; - -/** - * Created by Leo on 2015/4/2. - */ public class JsonDomBuilder implements JsonSaxHandler { - private enum SCOPE { - IN_OBJECT, - IN_ARRAY, - IN_ROOT + private static final int ROOT = 0; + private static final int IN_OBJECT = 1; + private static final int IN_ARRAY = 2; + + private static class Node { + + private int scope; + private Object value; + } - private SCOPE scope = SCOPE.IN_ROOT; private JsonProvider provider; - private Stack stack = new Stack(); - private Object currentNode; private String propertyName; + private Node[] stack = new Node[32]; + + private int stackSize = 0; + + { + this.push(ROOT, null); + } + + private void push(int newTop, Object topValue) { + if (stackSize == stack.length) { + Node[] newStack = new Node[stackSize * 2]; + System.arraycopy(stack, 0, newStack, 0, stackSize); + stack = newStack; + } + + Node next = stack[stackSize]; + if (next == null) { + next = new Node(); + stack[stackSize] = next; + } + next.value = topValue; + next.scope = newTop; + stackSize++; + } + + private Node peekNode() { + return stack[stackSize - 1]; + } + + private int peek() { + return peekNode().scope; + } + + protected Object peekValue() { + return peekNode().value; + } + + private void replaceTop(Object value) { + stack[stackSize - 1].value = value; + } + + private void pop() { + stackSize--; + } + public void setProvider(JsonProvider provider) { this.provider = provider; } @@ -62,31 +104,32 @@ public boolean endJSON() { @Override public boolean startObject() { Object newObject = provider.createObject(); - switch (scope) { + Node top = peekNode(); + switch (top.scope) { + case ROOT: + replaceTop(newObject); + break; case IN_OBJECT: - provider.put(currentNode, propertyName, newObject); + provider.put(top.value, propertyName, newObject); + propertyName = null; break; case IN_ARRAY: - provider.add(currentNode, newObject); - break; - case IN_ROOT: + provider.add(top.value, newObject); break; + default: + throw new IllegalStateException(); } - scope = SCOPE.IN_OBJECT; - stack.push(newObject); - currentNode = newObject; - propertyName = null; + push(IN_OBJECT, newObject); return true; } @Override public boolean startObjectEntry(String key) { - switch (scope) { + switch (peek()) { case IN_OBJECT: propertyName = key; break; case IN_ARRAY: - case IN_ROOT: throw new IllegalStateException(); } return true; @@ -94,93 +137,74 @@ public boolean startObjectEntry(String key) { @Override public boolean endObject() { - switch (scope) { + switch (peek()) { case IN_OBJECT: - stepOut(); + pop(); break; case IN_ARRAY: - case IN_ROOT: throw new IllegalStateException(); } return false; } - private void stepOut() { - stack.pop(); - if (!stack.isEmpty()) { - currentNode = stack.peek(); - // TODO better way to determine scope? - if (provider.isObject(currentNode)) { - scope = SCOPE.IN_OBJECT; - } else if (provider.isArray(currentNode)) { - scope = SCOPE.IN_ARRAY; - } else { - throw new IllegalStateException(); - } - } else { - scope = SCOPE.IN_ROOT; - } - } - @Override public boolean startArray() { Object newArray = provider.createArray(); - switch (scope) { + Node top = peekNode(); + switch (top.scope) { + case ROOT: + replaceTop(newArray); + break; case IN_OBJECT: - provider.put(currentNode, propertyName, newArray); + provider.put(top.value, propertyName, newArray); + propertyName = null; break; case IN_ARRAY: - provider.add(currentNode, newArray); - break; - case IN_ROOT: + provider.add(top.value, newArray); break; + default: + throw new IllegalStateException(); } - scope = SCOPE.IN_ARRAY; - stack.push(newArray); - currentNode = newArray; + push(IN_ARRAY, newArray); return true; } @Override public boolean endArray() { - stepOut(); + pop(); return true; } - private void consumePrimitive(Object value) { - switch (scope) { + @Override + public boolean primitive(PrimitiveHolder primitiveHolder) { + Object value = primitiveHolder.getValue(); + Node top = peekNode(); + switch (top.scope) { + case ROOT: + replaceTop(value); + break; case IN_OBJECT: - provider.put(currentNode, propertyName, value); + provider.put(top.value, propertyName, value); + propertyName = null; break; case IN_ARRAY: - provider.add(currentNode, value); - break; - case IN_ROOT: - currentNode = value; + provider.add(top.value, value); break; + default: + throw new IllegalStateException(); } - } - - - @Override - public boolean primitive(PrimitiveHolder primitiveHolder) { - consumePrimitive(primitiveHolder.getValue()); return true; } public boolean isInRoot() { - return scope == SCOPE.IN_ROOT; + return peek() == ROOT; } - public Object getCurrentNode() { - return currentNode; - } public void clear() { propertyName = null; provider = null; stack = null; - currentNode = null; } } diff --git a/jsurfer-core/src/main/java/org/jsfr/json/JsonSaxHandler.java b/jsurfer-core/src/main/java/org/jsfr/json/JsonSaxHandler.java index e51a763..0eeefb8 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/JsonSaxHandler.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/JsonSaxHandler.java @@ -25,7 +25,7 @@ package org.jsfr.json; /** - * Created by Leo on 2015/4/2. + * SAX like handler API */ public interface JsonSaxHandler { diff --git a/jsurfer-core/src/main/java/org/jsfr/json/JsonSurfer.java b/jsurfer-core/src/main/java/org/jsfr/json/JsonSurfer.java index 841f1d3..600d333 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/JsonSurfer.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/JsonSurfer.java @@ -34,7 +34,6 @@ import java.io.StringReader; import java.util.Collection; -import static org.jsfr.json.SurfingConfiguration.builder; import static org.jsfr.json.compiler.JsonPathCompiler.compile; @@ -88,8 +87,21 @@ public JsonSurfer(JsonParserAdapter jsonParserAdapter, JsonProvider jsonProvider this.errorHandlingStrategy = errorHandlingStrategy; } + /** + * Create SurfingConfiguration builder associated with this surfer + * + * @return SurfingConfiguration builder + */ + public SurfingConfiguration.Builder configBuilder() { + return SurfingConfiguration.builder().withSurfer(this); + } + + /** + * @param json json + * @param configuration SurfingConfiguration that holds JsonPath binding + */ public void surf(String json, SurfingConfiguration configuration) { - surf(new StringReader(json), configuration); + surf(read(json), configuration); } /** @@ -102,7 +114,7 @@ public void surf(Reader reader, SurfingConfiguration configuration) { } public Collection collectAll(String json, JsonPath... paths) { - return collectAll(new StringReader(json), paths); + return collectAll(read(json), paths); } /** @@ -116,8 +128,15 @@ public Collection collectAll(Reader reader, JsonPath... paths) { return collectAll(reader, Object.class, paths); } + /** + * @param json json + * @param tClass target class + * @param paths JsonPath + * @param target class + * @return typed value + */ public Collection collectAll(String json, Class tClass, JsonPath... paths) { - return collectAll(new StringReader(json), tClass, paths); + return collectAll(read(json), tClass, paths); } /** @@ -131,7 +150,7 @@ public Collection collectAll(String json, Class tClass, JsonPath... pa */ public Collection collectAll(Reader reader, Class tClass, JsonPath... paths) { CollectAllListener listener = new CollectAllListener(jsonProvider, tClass); - SurfingConfiguration.Builder builder = builder(); + SurfingConfiguration.Builder builder = configBuilder(); for (JsonPath jsonPath : paths) { builder.bind(jsonPath, listener); } @@ -139,8 +158,15 @@ public Collection collectAll(Reader reader, Class tClass, JsonPath... return listener.getCollection(); } + /** + * @param json json + * @param tClass target class + * @param paths JsonPath + * @param target class + * @return typed value + */ public Collection collectAll(String json, Class tClass, String... paths) { - return collectAll(new StringReader(json), tClass, paths); + return collectAll(read(json), tClass, paths); } /** @@ -157,7 +183,7 @@ public Collection collectAll(Reader reader, Class tClass, String... pa } public Collection collectAll(String json, String... paths) { - return collectAll(new StringReader(json), paths); + return collectAll(read(json), paths); } /** @@ -172,7 +198,7 @@ public Collection collectAll(Reader reader, String... paths) { } public Object collectOne(String json, JsonPath... paths) { - return collectOne(new StringReader(json), paths); + return collectOne(read(json), paths); } /** @@ -187,7 +213,7 @@ public Object collectOne(Reader reader, JsonPath... paths) { } public T collectOne(String json, Class tClass, JsonPath... paths) { - return collectOne(new StringReader(json), tClass, paths); + return collectOne(read(json), tClass, paths); } /** @@ -202,7 +228,7 @@ public T collectOne(String json, Class tClass, JsonPath... paths) { @SuppressWarnings("unchecked") public T collectOne(Reader reader, Class tClass, JsonPath... paths) { CollectOneListener listener = new CollectOneListener(true); - SurfingConfiguration.Builder builder = builder().skipOverlappedPath(); + SurfingConfiguration.Builder builder = configBuilder().skipOverlappedPath(); for (JsonPath jsonPath : paths) { builder.bind(jsonPath, listener); } @@ -212,7 +238,7 @@ public T collectOne(Reader reader, Class tClass, JsonPath... paths) { } public T collectOne(String json, Class tClass, String... paths) { - return collectOne(new StringReader(json), tClass, paths); + return collectOne(read(json), tClass, paths); } /** @@ -228,8 +254,13 @@ public T collectOne(Reader reader, Class tClass, String... paths) { return collectOne(reader, tClass, compile(paths)); } + /** + * @param json json + * @param paths JsonPath + * @return value + */ public Object collectOne(String json, String... paths) { - return collectOne(new StringReader(json), paths); + return collectOne(read(json), paths); } /** @@ -252,4 +283,8 @@ private void ensureSetting(SurfingConfiguration configuration) { } } + public Reader read(String json) { + return new StringReader(json); + } + } diff --git a/jsurfer-core/src/main/java/org/jsfr/json/SurfingConfiguration.java b/jsurfer-core/src/main/java/org/jsfr/json/SurfingConfiguration.java index 9ff61a5..c14176b 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/SurfingConfiguration.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/SurfingConfiguration.java @@ -27,6 +27,7 @@ import org.jsfr.json.path.JsonPath; import org.jsfr.json.provider.JsonProvider; +import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -78,6 +79,7 @@ public int compare(IndefinitePathBinding o1, IndefinitePathBinding o2) { public static class Builder { + private JsonSurfer jsonSurfer; private SurfingConfiguration configuration; private Map> definiteBindings = new HashMap>(); private ArrayList indefiniteBindings = new ArrayList(); @@ -96,6 +98,35 @@ public SurfingConfiguration build() { return configuration; } + /** + * Associated with a JsonSurfer + * + * @param jsonSurfer JsonSurfer + * @return builder + */ + public Builder withSurfer(JsonSurfer jsonSurfer) { + this.jsonSurfer = jsonSurfer; + return this; + } + + /** + * Build the configuration and then surf with it and the associated JsonSurfer + * + * @param json json + */ + public void buildAndSurf(String json) { + this.buildAndSurf(this.jsonSurfer.read(json)); + } + + /** + * Build the configuration and then surf with it and the associated JsonSurfer + * + * @param jsonReader jsonReader + */ + public void buildAndSurf(Reader jsonReader) { + this.jsonSurfer.surf(jsonReader, this.build()); + } + public Builder bind(String path, JsonPathListener... jsonPathListeners) { return bind(compile(path), jsonPathListeners); } diff --git a/jsurfer-core/src/main/java/org/jsfr/json/SurfingContext.java b/jsurfer-core/src/main/java/org/jsfr/json/SurfingContext.java index 4138704..13cc652 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/SurfingContext.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/SurfingContext.java @@ -25,7 +25,6 @@ package org.jsfr.json; import org.jsfr.json.path.ArrayIndex; -import org.jsfr.json.path.ChildNode; import org.jsfr.json.path.PathOperator; import org.jsfr.json.path.PathOperator.Type; @@ -42,48 +41,13 @@ class SurfingContext implements ParsingContext, JsonSaxHandler { private ContentDispatcher dispatcher = new ContentDispatcher(); private SurfingConfiguration config; private PrimitiveHolder currentValue; + private String currentKey; public SurfingContext(SurfingConfiguration config) { this.config = config; } - @Override - public boolean startJSON() { - if (stopped) { - return true; - } - currentPosition = JsonPosition.start(); - doMatching(config, currentPosition, dispatcher, null); - dispatcher.startJSON(); - return true; - } - - @Override - public boolean endJSON() { - if (stopped) { - return true; - } - dispatcher.endJSON(); - // clear resources - currentPosition.clear(); - currentPosition = null; - return true; - } - - @Override - public boolean startObject() { - if (stopped) { - return false; - } - if (currentPosition.accumulateArrayIndex()) { - doMatching(config, currentPosition, dispatcher, null); - } - currentPosition.stepIntoObject(); - dispatcher.startObject(); - return true; - } - - private void doMatching(SurfingConfiguration config, JsonPosition currentPosition, ContentDispatcher dispatcher, PrimitiveHolder primitiveHolder) { + private void doMatching(PrimitiveHolder primitiveHolder) { // skip matching if "skipOverlappedPath" is enable if (config.isSkipOverlappedPath() && !dispatcher.isEmpty()) { @@ -125,7 +89,7 @@ private void doMatching(SurfingConfiguration config, JsonPosition currentPositio } if (listeners != null) { - JsonCollector collector = new JsonCollector(listeners.toArray(new JsonPathListener[listeners.size()]), this, config.getErrorHandlingStrategy()); + JsonCollector collector = new JsonCollector(listeners, this, config.getErrorHandlingStrategy()); collector.setProvider(config.getJsonProvider()); dispatcher.addReceiver(collector); } @@ -144,11 +108,59 @@ private void dispatchPrimitive(SurfingConfiguration.Binding binding, Object prim } } + @Override + public boolean startJSON() { + if (stopped) { + return true; + } + currentPosition = JsonPosition.start(); + doMatching(null); + dispatcher.startJSON(); + return true; + } + + @Override + public boolean endJSON() { + if (stopped) { + return true; + } + dispatcher.endJSON(); + // clear resources + currentPosition.clear(); + currentPosition = null; + return true; + } + + @Override + public boolean startObject() { + if (stopped) { + return false; + } + PathOperator currentNode = currentPosition.peek(); + switch (currentNode.getType()) { + case OBJECT: + doMatching(null); + break; + case ARRAY: + accumulateArrayIndex((ArrayIndex) currentNode); + doMatching(null); + break; + case ROOT: + break; + default: + throw new IllegalStateException(); + } + currentPosition.stepIntoObject(); + dispatcher.startObject(); + return true; + } + @Override public boolean endObject() { if (stopped) { return false; } + this.currentKey = null; currentPosition.stepOutObject(); dispatcher.endObject(); return true; @@ -159,26 +171,41 @@ public boolean startObjectEntry(String key) { if (stopped) { return false; } - dispatcher.startObjectEntry(key); + currentKey = key; currentPosition.updateObjectEntry(key); - doMatching(config, currentPosition, dispatcher, null); + dispatcher.startObjectEntry(key); return true; } - @Override public boolean startArray() { if (stopped) { return false; } - if (currentPosition.accumulateArrayIndex()) { - doMatching(config, currentPosition, dispatcher, null); + PathOperator currentNode = currentPosition.peek(); + switch (currentNode.getType()) { + case OBJECT: + doMatching(null); + break; + case ARRAY: + accumulateArrayIndex((ArrayIndex) currentNode); + doMatching(null); + break; + case ROOT: + break; + default: + throw new IllegalStateException(); } + currentPosition.stepIntoArray(); dispatcher.startArray(); return true; } + private void accumulateArrayIndex(ArrayIndex arrayIndex) { + arrayIndex.increaseArrayIndex(); + } + @Override public boolean endArray() { if (stopped) { @@ -195,9 +222,21 @@ public boolean primitive(PrimitiveHolder primitiveHolder) { return false; } this.currentValue = primitiveHolder; - if (currentPosition.accumulateArrayIndex()) { - doMatching(config, currentPosition, dispatcher, primitiveHolder); + PathOperator currentNode = currentPosition.peek(); + switch (currentNode.getType()) { + case OBJECT: + doMatching(primitiveHolder); + break; + case ARRAY: + accumulateArrayIndex((ArrayIndex) currentNode); + doMatching(primitiveHolder); + break; + case ROOT: + break; + default: + throw new IllegalStateException(); } + dispatcher.primitive(primitiveHolder); return true; } @@ -209,12 +248,7 @@ public String getJsonPath() { @Override public String getCurrentFieldName() { - PathOperator top = this.currentPosition.peek(); - if (top.getType() == Type.OBJECT) { - return ((ChildNode)top).getKey(); - } else { - return null; - } + return currentKey; } @Override @@ -226,7 +260,7 @@ public PrimitiveHolder getCurrentValue() { public int getCurrentArrayIndex() { PathOperator top = this.currentPosition.peek(); if (top.getType() == Type.ARRAY) { - return ((ArrayIndex)top).getArrayIndex(); + return ((ArrayIndex) top).getArrayIndex(); } else { return -1; } diff --git a/jsurfer-core/src/main/java/org/jsfr/json/path/JsonPath.java b/jsurfer-core/src/main/java/org/jsfr/json/path/JsonPath.java index c3266b9..aeaabf6 100644 --- a/jsurfer-core/src/main/java/org/jsfr/json/path/JsonPath.java +++ b/jsurfer-core/src/main/java/org/jsfr/json/path/JsonPath.java @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; +import java.util.NoSuchElementException; public class JsonPath implements Iterable { @@ -45,7 +46,11 @@ public boolean hasNext() { @Override public PathOperator next() { - return operators[current++]; + if (current >= size) { + throw new NoSuchElementException(); + } else { + return operators[current++]; + } } @Override diff --git a/jsurfer-core/src/test/java/org/jsfr/json/JsonSurferTest.java b/jsurfer-core/src/test/java/org/jsfr/json/JsonSurferTest.java index 795b36f..76b1c8d 100644 --- a/jsurfer-core/src/test/java/org/jsfr/json/JsonSurferTest.java +++ b/jsurfer-core/src/test/java/org/jsfr/json/JsonSurferTest.java @@ -27,7 +27,6 @@ import com.google.common.io.Resources; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; -import org.jsfr.json.SurfingConfiguration.Builder; import org.jsfr.json.provider.JavaCollectionProvider; import org.jsfr.json.provider.JsonProvider; import org.jsfr.json.provider.JsonSimpleProvider; @@ -42,8 +41,8 @@ import java.util.HashMap; import java.util.Iterator; -import static org.jsfr.json.BuilderFactory.config; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.argThat; @@ -75,13 +74,12 @@ public void setUp() throws Exception { @Test public void testSampleJson() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$.store.book[0].category", mockListener) + surfer.configBuilder().bind("$.store.book[0].category", mockListener) .bind("$.store.book[0]", mockListener) .bind("$.store.car", mockListener) - .bind("$.store.bicycle", mockListener); - surfer.surf(read("sample.json"), builder.build()); + .bind("$.store.bicycle", mockListener) + .buildAndSurf(read("sample.json")); Object book = provider.createObject(); provider.put(book, "category", provider.primitive("reference")); @@ -105,22 +103,16 @@ public void testSampleJson() throws Exception { @Test public void testSample2() throws Exception { - - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$[0].aiRuleEditorOriginal.+.barrierLevel", mockListener); - surfer.surf(read("sample2.json"), builder.build()); + surfer.configBuilder() + .bind("$[0].aiRuleEditorOriginal.+.barrierLevel", mockListener) + .buildAndSurf(read("sample2.json")); verify(mockListener).onValue(eq(provider.primitive("0.8065")), any(ParsingContext.class)); - } @Test public void testStoppableParsing() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$.store.book[0,1,2]", mockListener) - .bind("$.store.book[3]", mockListener); - doNothing().when(mockListener) .onValue(anyObject(), argThat(new TypeSafeMatcher() { @@ -135,7 +127,10 @@ public void describeTo(Description description) { } })); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder() + .bind("$.store.book[0,1,2]", mockListener) + .bind("$.store.book[3]", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener, times(1)) .onValue(anyObject(), any(ParsingContext.class)); @@ -143,20 +138,20 @@ public void describeTo(Description description) { @Test public void testChildNodeWildcard() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$.store.*", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder() + .bind("$.store.*", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener, times(3)) .onValue(anyObject(), any(ParsingContext.class)); } @Test public void testAnyIndex() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$.store.book[*]", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder() + .bind("$.store.book[*]", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener, times(4)) .onValue(anyObject(), any(ParsingContext.class)); } @@ -167,27 +162,25 @@ private String read(String resourceName) throws IOException { @Test public void testWildcardCombination() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$.store.book[*].*", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$.store.book[*].*", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener, times(18)).onValue(anyObject(), any(ParsingContext.class)); } @Test public void testArraySlicing() throws Exception { - Builder builder = config(); JsonPathListener mock1 = mock(JsonPathListener.class); - builder.bind("$[:2]", mock1); JsonPathListener mock2 = mock(JsonPathListener.class); - builder.bind("$[0:2]", mock2); JsonPathListener mock3 = mock(JsonPathListener.class); - builder.bind("$[2:]", mock3); JsonPathListener mock4 = mock(JsonPathListener.class); - builder.bind("$[:]", mock4); - - surfer.surf(read("array.json"), builder.build()); + surfer.configBuilder() + .bind("$[:2]", mock1) + .bind("$[0:2]", mock2) + .bind("$[2:]", mock3) + .bind("$[:]", mock4) + .buildAndSurf(read("array.json")); verify(mock1, times(2)).onValue(anyObject(), any(ParsingContext.class)); verify(mock2, times(2)).onValue(anyObject(), any(ParsingContext.class)); verify(mock3, times(3)).onValue(anyObject(), any(ParsingContext.class)); @@ -196,7 +189,6 @@ public void testArraySlicing() throws Exception { @Test public void testParsingArray() throws Exception { - Builder builder = config(); JsonPathListener wholeArray = mock(JsonPathListener.class); JsonPathListener stringElement = mock(JsonPathListener.class); JsonPathListener numberElement = mock(JsonPathListener.class); @@ -204,13 +196,14 @@ public void testParsingArray() throws Exception { JsonPathListener nullElement = mock(JsonPathListener.class); JsonPathListener objectElement = mock(JsonPathListener.class); - builder.bind("$", wholeArray); - builder.bind("$[0]", stringElement); - builder.bind("$[1]", numberElement); - builder.bind("$[2]", booleanElement); - builder.bind("$[3]", nullElement); - builder.bind("$[4]", objectElement); - surfer.surf(read("array.json"), builder.build()); + surfer.configBuilder().bind("$", wholeArray) + .bind("$[0]", stringElement) + .bind("$[1]", numberElement) + .bind("$[2]", booleanElement) + .bind("$[3]", nullElement) + .bind("$[4]", objectElement) + .buildAndSurf(read("array.json")); + Object object = provider.createObject(); provider.put(object, "key", provider.primitive("value")); Object array = provider.createArray(); @@ -230,11 +223,10 @@ public void testParsingArray() throws Exception { @Test public void testDeepScan() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$..author", mockListener); - builder.bind("$..store..bicycle..color", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$..author", mockListener) + .bind("$..store..bicycle..color", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener).onValue(eq(provider.primitive("Nigel Rees")), any(ParsingContext.class)); verify(mockListener).onValue(eq(provider.primitive("Evelyn Waugh")), any(ParsingContext.class)); verify(mockListener).onValue(eq(provider.primitive("Herman Melville")), any(ParsingContext.class)); @@ -245,10 +237,9 @@ public void testDeepScan() throws Exception { @Test public void testDeepScan2() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$..store..price", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$..store..price", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener).onValue(eq(provider.primitive(8.95)), any(ParsingContext.class)); verify(mockListener).onValue(eq(provider.primitive(12.99)), any(ParsingContext.class)); verify(mockListener).onValue(eq(provider.primitive(8.99)), any(ParsingContext.class)); @@ -258,32 +249,30 @@ public void testDeepScan2() throws Exception { @Test public void testAny() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$.store..bicycle..*", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$.store..bicycle..*", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener).onValue(eq(provider.primitive("red")), any(ParsingContext.class)); verify(mockListener).onValue(eq(provider.primitive(19.95)), any(ParsingContext.class)); } @Test public void testFindEverything() throws Exception { - Builder builder = config(); - builder.bind("$..*", new JsonPathListener() { - @Override - public void onValue(Object value, ParsingContext context) { - LOGGER.trace("value: {}", value); - } - }); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder() + .bind("$..*", new JsonPathListener() { + @Override + public void onValue(Object value, ParsingContext context) { + LOGGER.trace("value: {}", value); + } + }) + .buildAndSurf(read("sample.json")); } @Test public void testIndexesAndChildrenOperator() throws Exception { - Builder builder = config(); JsonPathListener mockListener = mock(JsonPathListener.class); - builder.bind("$..book[1,3][author,title]", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$..book[1,3][author,title]", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener).onValue(eq(provider.primitive("Evelyn Waugh")), any(ParsingContext.class)); verify(mockListener).onValue(eq(provider.primitive("Sword of Honour")), any(ParsingContext.class)); verify(mockListener).onValue(eq(provider.primitive("J. R. R. Tolkien")), any(ParsingContext.class)); @@ -320,117 +309,119 @@ public void testCollectOne() throws Exception { @Test public void testGetCurrentFieldName() throws Exception { - surfer.surf(read("sample.json"), config().bind("$.store.book[0].title", new JsonPathListener() { - @Override - public void onValue(Object value, ParsingContext context) throws Exception { - assertEquals(context.getCurrentFieldName(), "title"); - } - }).build()); + surfer.configBuilder() + .bind("$.store.book[0].title", new JsonPathListener() { + @Override + public void onValue(Object value, ParsingContext context) throws Exception { + assertEquals(context.getCurrentFieldName(), "title"); + } + }) + .bind("$.store.book[0]", new JsonPathListener() { + @Override + public void onValue(Object value, ParsingContext context) throws Exception { + assertNull(context.getCurrentFieldName()); + } + }) + .buildAndSurf(read("sample.json")); } @Test public void testGetCurrentArrayIndex() throws Exception { - surfer.surf(read("sample.json"), config().bind("$.store.book[3]", new JsonPathListener() { - @Override - public void onValue(Object value, ParsingContext context) throws Exception { - assertEquals(context.getCurrentArrayIndex(), 3); - } - }).build()); + surfer.configBuilder() + .bind("$.store.book[3]", new JsonPathListener() { + @Override + public void onValue(Object value, ParsingContext context) throws Exception { + assertEquals(context.getCurrentArrayIndex(), 3); + } + }) + .bind("$.store", new JsonPathListener() { + @Override + public void onValue(Object value, ParsingContext context) throws Exception { + assertEquals(context.getCurrentArrayIndex(), -1); + } + }) + .buildAndSurf(read("sample.json")); } @Test public void testExample1() throws Exception { - Builder builder = config(); - builder.bind("$.store.book[*].author", print); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$.store.book[*].author", print).buildAndSurf(read("sample.json")); } @Test public void testExample2() throws Exception { - Builder builder = config(); - builder.bind("$..author", print); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$..author", print).buildAndSurf(read("sample.json")); } @Test public void testExample3() throws Exception { - Builder builder = config(); - builder.bind("$.store.*", print); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$.store.*", print).buildAndSurf(read("sample.json")); } @Test public void testExample4() throws Exception { - Builder builder = config(); - builder.bind("$.store..price", print); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$.store..price", print).buildAndSurf(read("sample.json")); } @Test public void testExample5() throws Exception { - Builder builder = config(); - builder.bind("$..book[2]", print); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$..book[2]", print).buildAndSurf(read("sample.json")); } @Test public void testExample6() throws Exception { - Builder builder = config(); - builder.bind("$..book[0,1]", print); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$..book[0,1]", print).buildAndSurf(read("sample.json")); } @Test public void testStoppable() throws Exception { - Builder builder = config(); - builder.bind("$..book[0,1]", new JsonPathListener() { + surfer.configBuilder().bind("$..book[0,1]", new JsonPathListener() { @Override public void onValue(Object value, ParsingContext parsingContext) { parsingContext.stopParsing(); System.out.println(value); } - }); - surfer.surf(read("sample.json"), builder.build()); + }).buildAndSurf(read("sample.json")); } @Test public void testPlugableProvider() throws Exception { JsonPathListener mockListener = mock(JsonPathListener.class); - Builder builder = config().withJsonProvider(JavaCollectionProvider.INSTANCE); - builder.bind("$.store", mockListener); - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().withJsonProvider(JavaCollectionProvider.INSTANCE) + .bind("$.store", mockListener) + .buildAndSurf(read("sample.json")); verify(mockListener).onValue(isA(HashMap.class), any(ParsingContext.class)); } @Test public void testErrorStrategySuppressException() throws Exception { - Builder builder = config(); - JsonPathListener mock = mock(JsonPathListener.class); - builder.bind("$.store.book[*]", mock); - builder.withErrorStrategy(new ErrorHandlingStrategy() { - @Override - public void handleParsingException(Exception e) { - // suppress exception - } - @Override - public void handleExceptionFromListener(Exception e, ParsingContext context) { - // suppress exception - } - }); + JsonPathListener mock = mock(JsonPathListener.class); doNothing().doThrow(Exception.class).doThrow(Exception.class).when(mock).onValue(anyObject(), any(ParsingContext.class)); - surfer.surf(read("sample.json"), builder.build()); + + surfer.configBuilder().bind("$.store.book[*]", mock) + .withErrorStrategy(new ErrorHandlingStrategy() { + @Override + public void handleParsingException(Exception e) { + // suppress exception + } + + @Override + public void handleExceptionFromListener(Exception e, ParsingContext context) { + // suppress exception + } + }) + .buildAndSurf(read("sample.json")); verify(mock, times(4)).onValue(anyObject(), any(ParsingContext.class)); } @Test public void testErrorStrategyThrowException() throws Exception { - Builder builder = config(); + JsonPathListener mock = mock(JsonPathListener.class); - builder.bind("$.store.book[*]", mock); doNothing().doThrow(Exception.class).doThrow(Exception.class).when(mock).onValue(anyObject(), any(ParsingContext.class)); try { - surfer.surf(read("sample.json"), builder.build()); + surfer.configBuilder().bind("$.store.book[*]", mock).buildAndSurf(read("sample.json")); } catch (Exception e) { // catch mock exception } diff --git a/pom.xml b/pom.xml index ca9d829..922009e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.github.jsurfer jsurfer - 1.2.6 + 1.2.7 pom JsonSurfer Let's surf on json