diff --git a/.gitignore b/.gitignore index c8dfb82e..fdf8d269 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ nbproject/ *err_pid*.log /tck-impl/.gradle/ /tck-impl/build/ +/.sdkmanrc diff --git a/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java b/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java index 217af798..459d1fb3 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java @@ -25,6 +25,7 @@ import java.util.AbstractMap; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; @@ -190,25 +191,26 @@ public Stream getArrayStream() { } Spliterator spliterator = new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { - @Override - public Spliterator trySplit() { - return null; - } - @Override - public boolean tryAdvance(Consumer action) { - if (action == null) { - throw new NullPointerException(); - } - if (! hasNext()) { - return false; - } - if (next() == JsonParser.Event.END_ARRAY) { - return false; - } - action.accept(getValue()); - return true; - } - }; + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } + if (!hasNext()) { + return false; + } + if (next() == JsonParser.Event.END_ARRAY) { + return false; + } + action.accept(getValue()); + return true; + } + }; return StreamSupport.stream(spliterator, false); } @@ -220,35 +222,36 @@ public Stream> getObjectStream() { } Spliterator> spliterator = new Spliterators.AbstractSpliterator>(Long.MAX_VALUE, Spliterator.ORDERED) { - @Override - public Spliterator> trySplit() { - return null; - } - @Override - public boolean tryAdvance(Consumer> action) { - if (action == null) { - throw new NullPointerException(); - } - if (! hasNext()) { - return false; - } - JsonParser.Event e = next(); - if (e == JsonParser.Event.END_OBJECT) { - return false; - } - if (e != JsonParser.Event.KEY_NAME) { - throw new JsonException(JsonMessages.INTERNAL_ERROR()); - } - String key = getString(); - if (! hasNext()) { - throw new JsonException(JsonMessages.INTERNAL_ERROR()); - } - next(); - JsonValue value = getValue(); - action.accept(new AbstractMap.SimpleImmutableEntry<>(key, value)); - return true; - } - }; + @Override + public Spliterator> trySplit() { + return null; + } + + @Override + public boolean tryAdvance(Consumer> action) { + if (action == null) { + throw new NullPointerException(); + } + if (!hasNext()) { + return false; + } + JsonParser.Event e = next(); + if (e == JsonParser.Event.END_OBJECT) { + return false; + } + if (e != JsonParser.Event.KEY_NAME) { + throw new JsonException(JsonMessages.INTERNAL_ERROR()); + } + String key = getString(); + if (!hasNext()) { + throw new JsonException(JsonMessages.INTERNAL_ERROR()); + } + next(); + JsonValue value = getValue(); + action.accept(new AbstractMap.SimpleImmutableEntry<>(key, value)); + return true; + } + }; return StreamSupport.stream(spliterator, false); } @@ -260,29 +263,30 @@ public Stream getValueStream() { } Spliterator spliterator = new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { - @Override - public Spliterator trySplit() { - return null; - } - @Override - public boolean tryAdvance(Consumer action) { - if (action == null) { - throw new NullPointerException(); - } - if (! hasNext()) { - return false; - } - next(); - action.accept(getValue()); - return true; - } - }; + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public boolean tryAdvance(Consumer action) { + if (action == null) { + throw new NullPointerException(); + } + if (!hasNext()) { + return false; + } + next(); + action.accept(getValue()); + return true; + } + }; return StreamSupport.stream(spliterator, false); } @Override public void skipArray() { - if (currentEvent == Event.START_ARRAY) { + if (currentContext instanceof ArrayContext) { currentContext.skip(); currentContext = stack.pop(); currentEvent = Event.END_ARRAY; @@ -291,7 +295,7 @@ public void skipArray() { @Override public void skipObject() { - if (currentEvent == Event.START_OBJECT) { + if (currentContext instanceof ObjectContext) { currentContext.skip(); currentContext = stack.pop(); currentEvent = Event.END_OBJECT; @@ -418,18 +422,15 @@ private boolean isEmpty() { } } - private abstract static class Context { + private abstract class Context { Context next; abstract Event getNextEvent(); abstract void skip(); - } - private final class NoneContext extends Context { - @Override - public Event getNextEvent() { - // Handle 1. { 2. [ 3. value - JsonToken token = tokenizer.nextToken(); - if (token == JsonToken.CURLYOPEN) { + protected Event nextEventIfValueOrObjectOrArrayStart(JsonToken token) { + if (token.isValue()) { + return token.getEvent(); + } else if (token == JsonToken.CURLYOPEN) { stack.push(currentContext); currentContext = new ObjectContext(); return Event.START_OBJECT; @@ -437,8 +438,19 @@ public Event getNextEvent() { stack.push(currentContext); currentContext = new ArrayContext(); return Event.START_ARRAY; - } else if (token.isValue()) { - return token.getEvent(); + } + return null; + } + } + + private final class NoneContext extends Context { + @Override + public Event getNextEvent() { + // Handle 1. { 2. [ 3. value + JsonToken token = tokenizer.nextToken(); + Event event = nextEventIfValueOrObjectOrArrayStart(token); + if (event != null) { + return event; } throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]"); } @@ -455,9 +467,38 @@ private JsonParsingException parsingException(JsonToken token, String expectedTo JsonMessages.PARSER_INVALID_TOKEN(token, location, expectedTokens), location); } - private final class ObjectContext extends Context { + private abstract class SkippingContext extends Context { + private final JsonToken openToken; + private final JsonToken closeToken; + + private SkippingContext(JsonToken openToken, JsonToken closeToken) { + this.openToken = Objects.requireNonNull(openToken); + this.closeToken = Objects.requireNonNull(closeToken); + } + + @Override + void skip() { + JsonToken token; + int depth = 1; + do { + token = tokenizer.nextToken(); + if (token == closeToken) { + depth--; + } + if (token == openToken) { + depth++; + } + } while (!(token == closeToken && depth == 0)); + } + } + + private final class ObjectContext extends SkippingContext { private boolean firstValue = true; + private ObjectContext() { + super(JsonToken.CURLYOPEN, JsonToken.CURLYCLOSE); + } + /* * Some more things could be optimized. For example, instead * tokenizer.nextToken(), one could use tokenizer.matchColonToken() to @@ -484,16 +525,9 @@ public Event getNextEvent() { throw parsingException(token, "[COLON]"); } token = tokenizer.nextToken(); - if (token.isValue()) { - return token.getEvent(); - } else if (token == JsonToken.CURLYOPEN) { - stack.push(currentContext); - currentContext = new ObjectContext(); - return Event.START_OBJECT; - } else if (token == JsonToken.SQUAREOPEN) { - stack.push(currentContext); - currentContext = new ArrayContext(); - return Event.START_ARRAY; + Event event = nextEventIfValueOrObjectOrArrayStart(token); + if (event != null) { + return event; } throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]"); } else { @@ -516,29 +550,15 @@ public Event getNextEvent() { throw parsingException(token, "[STRING]"); } } - - @Override - void skip() { - JsonToken token; - int depth = 1; - do { - token = tokenizer.nextToken(); - switch (token) { - case CURLYCLOSE: - depth--; - break; - case CURLYOPEN: - depth++; - break; - } - } while (!(token == JsonToken.CURLYCLOSE && depth == 0)); - } - } - private final class ArrayContext extends Context { + private final class ArrayContext extends SkippingContext { private boolean firstValue = true; + private ArrayContext() { + super(JsonToken.SQUAREOPEN, JsonToken.SQUARECLOSE); + } + // Handle 1. ] 2. value 3. ,value @Override public Event getNextEvent() { @@ -563,36 +583,12 @@ public Event getNextEvent() { } token = tokenizer.nextToken(); } - if (token.isValue()) { - return token.getEvent(); - } else if (token == JsonToken.CURLYOPEN) { - stack.push(currentContext); - currentContext = new ObjectContext(); - return Event.START_OBJECT; - } else if (token == JsonToken.SQUAREOPEN) { - stack.push(currentContext); - currentContext = new ArrayContext(); - return Event.START_ARRAY; + + Event event = nextEventIfValueOrObjectOrArrayStart(token); + if (event != null) { + return event; } throw parsingException(token, "[CURLYOPEN, SQUAREOPEN, STRING, NUMBER, TRUE, FALSE, NULL]"); } - - @Override - void skip() { - JsonToken token; - int depth = 1; - do { - token = tokenizer.nextToken(); - switch (token) { - case SQUARECLOSE: - depth--; - break; - case SQUAREOPEN: - depth++; - break; - } - } while (!(token == JsonToken.SQUARECLOSE && depth == 0)); - } } - } diff --git a/impl/src/test/java/org/eclipse/parsson/JsonParserFixture.java b/impl/src/test/java/org/eclipse/parsson/JsonParserFixture.java new file mode 100644 index 00000000..3642ec38 --- /dev/null +++ b/impl/src/test/java/org/eclipse/parsson/JsonParserFixture.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.eclipse.parsson; + +import java.io.StringReader; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.stream.JsonParser; + +/** + * Class with methods that creates JsonParser with different configuration from different sources and runs the test code with this parser + */ +public class JsonParserFixture { + /** + * Runs the test code with JsonParser created from the given JsonObject + * + * @param object JsonObject to create JsonParser from + * @param parserConsumer test code to run with the created JsonParser + */ + public static void testWithCreateParserFromObject(JsonObject object, Consumer parserConsumer) { + testWithParser(() -> Json.createParserFactory(null).createParser(object), parserConsumer); + } + + /** + * Runs the test code with JsonParser created from the given JsonArray + * + * @param array JsonArray to create JsonParser from + * @param parserConsumer test code to run with the created JsonParser + */ + public static void testWithCreateParserFromArray(JsonArray array, Consumer parserConsumer) { + testWithParser(() -> Json.createParserFactory(null).createParser(array), parserConsumer); + } + + /** + * Runs the test code with JsonParser created from the given String + * + * @param string String with JSON to create JsonParser from + * @param parserConsumer test code to run with the created JsonParser + */ + public static void testWithCreateParserFromString(String string, Consumer parserConsumer) { + testWithParser(() -> Json.createParser(new StringReader(string)), parserConsumer); + } + + /** + * Runs the test code with JsonParser created from the given String + * + * @param parserSupplier Supplier of JsonParser to create JsonParser from + * @param parserConsumer test code to run with the created JsonParser + */ + private static void testWithParser(Supplier parserSupplier, Consumer parserConsumer) { + try (JsonParser parser = parserSupplier.get()) { + parserConsumer.accept(parser); + } + } +} diff --git a/impl/src/test/java/org/eclipse/parsson/tests/JsonParserSkipTest.java b/impl/src/test/java/org/eclipse/parsson/tests/JsonParserSkipTest.java index 56ea6af0..f138cb13 100644 --- a/impl/src/test/java/org/eclipse/parsson/tests/JsonParserSkipTest.java +++ b/impl/src/test/java/org/eclipse/parsson/tests/JsonParserSkipTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -16,13 +16,13 @@ package org.eclipse.parsson.tests; -import java.io.StringReader; import jakarta.json.Json; import jakarta.json.stream.JsonParser; import junit.framework.TestCase; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertTrue; + +import static org.eclipse.parsson.JsonParserFixture.testWithCreateParserFromArray; +import static org.eclipse.parsson.JsonParserFixture.testWithCreateParserFromObject; +import static org.eclipse.parsson.JsonParserFixture.testWithCreateParserFromString; /** * @@ -31,44 +31,70 @@ public class JsonParserSkipTest extends TestCase { public void testSkipArrayReader() { - try (JsonParser parser = Json.createParser(new StringReader("[[],[[]]]"))) { - testSkipArray(parser); - } + testWithCreateParserFromString("[[],[[]]]", JsonParserSkipTest::testSkipArray); } public void testSkipArrayStructure() { - try (JsonParser parser = Json.createParserFactory(null).createParser( - Json.createArrayBuilder() - .add(Json.createArrayBuilder()) - .add(Json.createArrayBuilder() - .add(Json.createArrayBuilder())) - .build())) { - testSkipArray(parser); - } + testWithCreateParserFromArray(Json.createArrayBuilder() + .add(Json.createArrayBuilder()) + .add(Json.createArrayBuilder() + .add(Json.createArrayBuilder())) + .build(), JsonParserSkipTest::testSkipArray); } private static void testSkipArray(JsonParser parser) { assertEquals(JsonParser.Event.START_ARRAY, parser.next()); parser.skipArray(); - assertEquals(false, parser.hasNext()); + assertFalse(parser.hasNext()); + } + + public void testSkipInsideArrayReader() { + testWithCreateParserFromString("[\"test\"]", JsonParserSkipTest::testSkipInsideArray); + } + + public void testSkipInsideArrayStructure() { + testWithCreateParserFromArray(Json.createArrayBuilder() + .add("test") + .build(), JsonParserSkipTest::testSkipInsideArray); + } + + private static void testSkipInsideArray(JsonParser parser) { + assertEquals(JsonParser.Event.START_ARRAY, parser.next()); + assertEquals(JsonParser.Event.VALUE_STRING, parser.next()); + parser.skipArray(); + assertFalse(parser.hasNext()); + } + + public void testNoSkipArrayReader() { + testWithCreateParserFromString("{\"key\":\"value\"}", JsonParserSkipTest::testNoSkipArray); + } + + public void testNoSkipArrayStructure() { + testWithCreateParserFromObject(Json.createObjectBuilder() + .add("key","value") + .build(), JsonParserSkipTest::testNoSkipArray); + } + + private static void testNoSkipArray(JsonParser parser) { + assertEquals(JsonParser.Event.START_OBJECT, parser.next()); + assertEquals(JsonParser.Event.KEY_NAME, parser.next()); + parser.skipArray(); + assertEquals(JsonParser.Event.VALUE_STRING, parser.next()); + assertEquals(JsonParser.Event.END_OBJECT, parser.next()); + assertFalse(parser.hasNext()); } public void testSkipArrayInObjectReader() { - try (JsonParser parser = Json.createParser(new StringReader("{\"array\":[[],[[]]],\"object\":\"value2\"}"))) { - testSkipArrayInObject(parser); - } + testWithCreateParserFromString("{\"array\":[[],[[]]],\"object\":\"value2\"}", JsonParserSkipTest::testSkipArrayInObject); } public void testSkipArrayInObjectStructure() { - try (JsonParser parser = Json.createParserFactory(null).createParser( - Json.createObjectBuilder().add("array", Json.createArrayBuilder() + testWithCreateParserFromObject(Json.createObjectBuilder().add("array", Json.createArrayBuilder() .add(Json.createArrayBuilder()) .add(Json.createArrayBuilder() .add(Json.createArrayBuilder())) ).add("object", "value2") - .build())) { - testSkipArrayInObject(parser); - } + .build(), JsonParserSkipTest::testSkipArrayInObject); } private static void testSkipArrayInObject(JsonParser parser) { @@ -84,20 +110,15 @@ private static void testSkipArrayInObject(JsonParser parser) { } public void testSkipObjectReader() { - try (JsonParser parser = Json.createParser(new StringReader("{\"array\":[],\"objectToSkip\":{\"huge key\":\"huge value\"},\"simple\":2}"))) { - testSkipObject(parser); - } + testWithCreateParserFromString("{\"array\":[],\"objectToSkip\":{\"huge key\":\"huge value\"},\"simple\":2}", JsonParserSkipTest::testSkipObject); } public void testSkipObjectStructure() { - try (JsonParser parser = Json.createParserFactory(null).createParser( - Json.createObjectBuilder() - .add("array", Json.createArrayBuilder().build()) - .add("objectToSkip", Json.createObjectBuilder().add("huge key", "huge value")) - .add("simple", 2) - .build())) { - testSkipObject(parser); - } + testWithCreateParserFromObject(Json.createObjectBuilder() + .add("array", Json.createArrayBuilder().build()) + .add("objectToSkip", Json.createObjectBuilder().add("huge key", "huge value")) + .add("simple", 2) + .build(), JsonParserSkipTest::testSkipObject); } private static void testSkipObject(JsonParser parser) { @@ -111,6 +132,45 @@ private static void testSkipObject(JsonParser parser) { assertEquals(JsonParser.Event.KEY_NAME, parser.next()); assertEquals(JsonParser.Event.VALUE_NUMBER, parser.next()); assertEquals(JsonParser.Event.END_OBJECT, parser.next()); - assertEquals(false, parser.hasNext()); + assertFalse(parser.hasNext()); + } + + public void testSkipInsideObjectReader() { + testWithCreateParserFromString("{\"objectToSkip\":{\"huge key\":\"huge value\"},\"simple\":2}", JsonParserSkipTest::testSkipInsideObject); + } + + public void testSkipInsideObjectStructure() { + testWithCreateParserFromObject(Json.createObjectBuilder() + .add("objectToSkip", Json.createObjectBuilder().add("huge key", "huge value")) + .add("simple", 2) + .build(), JsonParserSkipTest::testSkipInsideObject); + } + + private static void testSkipInsideObject(JsonParser parser) { + assertEquals(JsonParser.Event.START_OBJECT, parser.next()); + assertEquals(JsonParser.Event.KEY_NAME, parser.next()); + assertEquals(JsonParser.Event.START_OBJECT, parser.next()); + parser.skipObject(); + assertEquals(JsonParser.Event.KEY_NAME, parser.next()); + assertEquals(JsonParser.Event.VALUE_NUMBER, parser.next()); + assertEquals(JsonParser.Event.END_OBJECT, parser.next()); + assertFalse(parser.hasNext()); + } + + public void testNoSkipObjectReader() { + testWithCreateParserFromString("{\"key\":\"value\"}", JsonParserSkipTest::testNoSkipObject); + } + + public void testNoSkipObjectStructure() { + testWithCreateParserFromObject(Json.createObjectBuilder() + .add("Key", "value") + .build(), JsonParserSkipTest::testNoSkipObject); + } + + private static void testNoSkipObject(JsonParser parser) { + assertEquals(JsonParser.Event.START_OBJECT, parser.next()); + assertEquals(JsonParser.Event.KEY_NAME, parser.next()); + parser.skipObject(); + assertFalse(parser.hasNext()); } } diff --git a/pom.xml b/pom.xml index 64168c70..cac37a4e 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ ${config.dir}/exclude.xml false Low - 4.7.3.6 + 4.8.1.0 2.1.3 @@ -323,7 +323,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.0 + 3.6.2 org.apache.maven.plugins @@ -343,7 +343,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.6.0 + 3.6.1 org.apache.maven.plugins @@ -353,7 +353,7 @@ org.apache.maven.plugins maven-clean-plugin - 3.3.1 + 3.3.2 org.apache.maven.plugins @@ -368,7 +368,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.1.2 + 3.2.2 org.apache.maven.plugins @@ -429,7 +429,7 @@ org.hamcrest hamcrest-core - 1.3 + 2.2 test @@ -458,7 +458,7 @@ com.sun.xml.bind jaxb-impl - 3.0.1 + 4.0.4