Skip to content

Commit

Permalink
[eclipse-ee4j#630]Fixed the state and context de-synchronisation in t…
Browse files Browse the repository at this point in the history
…he YassonParser's skipXXX and streamXXX methods

Signed-off-by: Anton Pinsky <[email protected]>
  • Loading branch information
api-from-the-ion committed Nov 13, 2023
1 parent 375eb1f commit ac9da78
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 63 deletions.
107 changes: 107 additions & 0 deletions src/main/java/org/eclipse/yasson/internal/JsonParserStreamCreator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* 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,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/

package org.eclipse.yasson.internal;

import java.util.AbstractMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Stream;

import jakarta.json.JsonException;
import jakarta.json.JsonValue;
import jakarta.json.stream.JsonParser;

import org.eclipse.yasson.internal.properties.MessageKeys;
import org.eclipse.yasson.internal.properties.Messages;

public class JsonParserStreamCreator {

private final JsonParser parser;
private final boolean nextBeforeCreationOfValueStream;
private final Supplier<Boolean> canProduceArrayStream;
private final Supplier<Boolean> canProduceObjectStream;
private final Supplier<Boolean> canProduceValueStream;

public JsonParserStreamCreator(JsonParser parser, boolean nextBeforeCreationOfValueStream, Supplier<Boolean> canProduceArrayStream,
Supplier<Boolean> canProduceObjectStream,
Supplier<Boolean> canProduceValueStream) {

this.parser = Objects.requireNonNull(parser);
this.nextBeforeCreationOfValueStream = nextBeforeCreationOfValueStream;
this.canProduceArrayStream = canProduceArrayStream;
this.canProduceObjectStream = canProduceObjectStream;
this.canProduceValueStream = canProduceValueStream;
}

/**
* Creates new {@link Stream} from values from {@link Supplier}. The stream delivers the values as long as supplier delivers non-null values
*
* @param supplier supplier of the values
* @param <T> type of the values which are delivered by the supplier and the stream
* @return stream of values from given supplier
*/
private static <T> Stream<T> streamFromSupplier(Supplier<T> supplier) {
return Stream.iterate(Objects.requireNonNull(supplier).get(), Objects::nonNull, value -> supplier.get());
}

public Stream<JsonValue> getArrayStream() {
if (canProduceArrayStream.get()) {
return streamFromSupplier(() -> (parser.hasNext() && parser.next() != JsonParser.Event.END_ARRAY) ? parser.getValue() : null);
} else {
throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of array context"));
}
}

public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
if (canProduceObjectStream.get()) {
return streamFromSupplier(() -> {
JsonParser.Event e = parser.next();
if (e == JsonParser.Event.END_OBJECT) {
return null;
} else if (e != JsonParser.Event.KEY_NAME) {
throw new JsonException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Cannot read object key"));
} else {
String key = parser.getString();
if (!parser.hasNext()) {
throw new JsonException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Cannot read object value"));
} else {
parser.next();
return new AbstractMap.SimpleImmutableEntry<>(key, parser.getValue());
}
}
});
} else {
throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of object context"));
}
}

public Stream<JsonValue> getValueStream() {
if (canProduceValueStream.get()) {
if (nextBeforeCreationOfValueStream) {
parser.next();
}

return streamFromSupplier(() -> {
if (parser.hasNext()) {
return parser.getValue();
} else {
return null;
}
});
} else {
throw new IllegalStateException(
Messages.getMessage(MessageKeys.INTERNAL_ERROR, "getValueStream can be only called at the root level of JSON structure"));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
Expand All @@ -24,6 +24,7 @@
import jakarta.json.stream.JsonParser;

import org.eclipse.yasson.internal.DeserializationContextImpl;
import org.eclipse.yasson.internal.JsonParserStreamCreator;

/**
* Yasson {@link YassonParser} parser wrapper.
Expand All @@ -34,12 +35,15 @@ class YassonParser implements JsonParser {

private final JsonParser delegate;
private final DeserializationContextImpl context;
private final JsonParserStreamCreator streamCreator;
private int level;

YassonParser(JsonParser delegate, Event firstEvent, DeserializationContextImpl context) {
this.delegate = delegate;
this.context = context;
this.level = determineLevelValue(firstEvent);
streamCreator = new JsonParserStreamCreator(this, false, () -> context.getLastValueEvent() == Event.START_ARRAY,
() -> context.getLastValueEvent() == Event.START_OBJECT, () -> level == 1 && context.getLastValueEvent() == Event.START_OBJECT);
}

private int determineLevelValue(Event firstEvent) {
Expand Down Expand Up @@ -150,36 +154,35 @@ public JsonArray getArray() {
@Override
public Stream<JsonValue> getArrayStream() {
validate();
level--;
return delegate.getArrayStream();
return streamCreator.getArrayStream();
}

@Override
public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
validate();
level--;
return delegate.getObjectStream();
return streamCreator.getObjectStream();
}

@Override
public Stream<JsonValue> getValueStream() {
validate();
level--;
return delegate.getValueStream();
return streamCreator.getValueStream();
}

@Override
public void skipArray() {
validate();
level--;
delegate.skipArray();
context.setLastValueEvent(Event.END_ARRAY);
}

@Override
public void skipObject() {
validate();
level--;
delegate.skipObject();
context.setLastValueEvent(Event.END_OBJECT);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,16 @@
package org.eclipse.yasson.internal.jsonstructure;

import java.math.BigDecimal;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

import jakarta.json.JsonArray;
import jakarta.json.JsonException;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonStructure;
Expand All @@ -35,6 +32,7 @@
import jakarta.json.stream.JsonLocation;
import jakarta.json.stream.JsonParser;

import org.eclipse.yasson.internal.JsonParserStreamCreator;
import org.eclipse.yasson.internal.properties.MessageKeys;
import org.eclipse.yasson.internal.properties.Messages;

Expand All @@ -56,6 +54,10 @@ public class JsonStructureToParserAdapter implements JsonParser {
private final JsonStructure rootStructure;
private final JsonProvider jsonProvider;

private final JsonParserStreamCreator streamCreator = new JsonParserStreamCreator(this,
//JsonParserImpl delivers the whole object - so we have to call next() before creation of the stream
true, () -> iterators.peek() instanceof JsonArrayIterator, () -> iterators.peek() instanceof JsonObjectIterator, iterators::isEmpty);

private Event currentEvent;

/**
Expand All @@ -69,17 +71,6 @@ public JsonStructureToParserAdapter(JsonStructure structure, JsonProvider jsonPr
this.jsonProvider = jsonProvider;
}

/**
* Creates new {@link Stream} from values from {@link Supplier}. The stream delivers the values as long as supplier delivers non-null values
* @param supplier supplier of the values
* @return stream of values from given supplier
* @param <T> type of the values which are delivered by the supplier and the stream
*/
private static <T> Stream<T> streamFromSupplier(Supplier<T> supplier){
Objects.requireNonNull(supplier);
return Stream.iterate(supplier.get(), Objects::nonNull, value -> supplier.get());
}

@Override
public boolean hasNext() {
JsonStructureIterator iterator = iterators.peek();
Expand Down Expand Up @@ -220,54 +211,17 @@ public JsonArray getArray() {

@Override
public Stream<JsonValue> getArrayStream() {
JsonStructureIterator current = iterators.peek();
if (current instanceof JsonArrayIterator) {
return streamFromSupplier(() -> (hasNext() && next() != Event.END_ARRAY) ? getValue() : null);
} else {
throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of array context"));
}
return streamCreator.getArrayStream();
}

@Override
public Stream<Map.Entry<String, JsonValue>> getObjectStream() {
JsonStructureIterator current = iterators.peek();
if (current instanceof JsonObjectIterator) {
return streamFromSupplier(() -> {
Event e = next();
if (e == Event.END_OBJECT) {
return null;
} else if (e != Event.KEY_NAME) {
throw new JsonException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Cannot read object key"));
} else {
String key = getString();
if (!hasNext()) {
throw new JsonException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Cannot read object value"));
} else {
next();
return new AbstractMap.SimpleImmutableEntry<>(key, getValue());
}
}
});
} else {
throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of object context"));
}
return streamCreator.getObjectStream();
}

@Override
public Stream<JsonValue> getValueStream() {
if (iterators.isEmpty()) {
//JsonParserImpl delivers the whole object - so we have to do this the same way
JsonStructureToParserAdapter.this.next();
return streamFromSupplier(() -> {
if (hasNext()) {
return getValue();
} else {
return null;
}
});
} else {
throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "getValueStream can be only called at the root level of JSON structure"));
}
return streamCreator.getValueStream();
}

@Override
Expand All @@ -294,4 +248,4 @@ private void skipJsonPart(Predicate<JsonStructureIterator> predicate) {
public void close() {
//noop
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TimeZone;
Expand Down Expand Up @@ -63,14 +64,18 @@
import org.eclipse.yasson.serializers.model.ExplicitJsonbSerializer;
import org.eclipse.yasson.serializers.model.GenericPropertyPojo;
import org.eclipse.yasson.serializers.model.ImplicitJsonbSerializer;
import org.eclipse.yasson.serializers.model.JsonParserTestDeserializers;
import org.eclipse.yasson.serializers.model.JsonParserTestPojo;
import org.eclipse.yasson.serializers.model.NumberDeserializer;
import org.eclipse.yasson.serializers.model.NumberSerializer;
import org.eclipse.yasson.serializers.model.TwoObjectsComparer;
import org.eclipse.yasson.serializers.model.RecursiveDeserializer;
import org.eclipse.yasson.serializers.model.RecursiveSerializer;
import org.eclipse.yasson.serializers.model.SimpleAnnotatedSerializedArrayContainer;
import org.eclipse.yasson.serializers.model.SimpleContainer;
import org.eclipse.yasson.serializers.model.StringWrapper;
import org.eclipse.yasson.serializers.model.SupertypeSerializerPojo;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static java.util.Collections.singletonMap;
Expand Down Expand Up @@ -925,4 +930,64 @@ public void testNoJsonbPropertyInEnum() {
assertEquals(expected, defaultJsonb.fromJson(expectedJson, Cars.class));
}

}
@Nested
class YassonParserTests{
@Test
public void testJsonParserFunctions() {
JsonParserTestPojo expected = new JsonParserTestPojo().init();

JsonParserTestDeserializers.JsonParserTestObjectDeserializer
deserializer = new JsonParserTestDeserializers.JsonParserTestObjectDeserializer();
testWithJsonbBuilderCreate(new JsonbConfig().withDeserializers(deserializer), jsonb -> {
String expectedJson = new StringBuilder(jsonb.toJson(expected))
.insert(1, "\"stringList_skip\":[\"string7\",\"string8\"],\"subPojo_skip\":{\"name\":\"subPojo_skip\"},").toString();

assertTrue(TwoObjectsComparer.getDifferentFieldInTwoObjects(expected, jsonb.fromJson(expectedJson, JsonParserTestPojo.class))
.isEmpty());
assertEquals(List.of("stringList_skip", "subPojo_skip", "bigDecimal", "integer", "longValue", "string", "stringList",
"stringList_getStream", "stringList_getValue", "string_getValue", "subPojo", "subPojo_getStream", "subPojo_getValue"),
deserializer.getKeyNames());

List<JsonParser.Event> expectedListOfEvents = List.of(JsonParser.Event.START_OBJECT, JsonParser.Event.END_ARRAY,
JsonParser.Event.END_OBJECT, JsonParser.Event.VALUE_NUMBER, JsonParser.Event.VALUE_NUMBER, JsonParser.Event.VALUE_NUMBER,
JsonParser.Event.VALUE_STRING, JsonParser.Event.END_ARRAY, JsonParser.Event.END_ARRAY, JsonParser.Event.END_ARRAY,
JsonParser.Event.VALUE_STRING, JsonParser.Event.END_OBJECT, JsonParser.Event.END_OBJECT);

assertEquals(expectedListOfEvents, deserializer.getContextEvents());
assertEquals("(line no=1, column no=96, offset=95)", deserializer.getLocation());
assertTrue(deserializer.isIntegralNumber());
});
}

@Test
public void testJsonParserValueStream() {
JsonParserTestPojo expected = new JsonParserTestPojo().init();

JsonParserTestDeserializers.JsonParserTestValueStreamDeserializer
deserializer = new JsonParserTestDeserializers.JsonParserTestValueStreamDeserializer();
testWithJsonbBuilderCreate(new JsonbConfig().withDeserializers(deserializer), jsonb -> {
String expectedJson = jsonb.toJson(expected);

assertTrue(TwoObjectsComparer.getDifferentFieldInTwoObjects(expected, jsonb.fromJson(expectedJson, JsonParserTestPojo.class))
.isEmpty());
});
}

@Test
public void testJsonParser_NoSuchElementException() {
JsonParserTestDeserializers.JsonParserTestNoSuchElementExceptionDeserializer
deserializer = new JsonParserTestDeserializers.JsonParserTestNoSuchElementExceptionDeserializer();
testWithJsonbBuilderCreate(new JsonbConfig().withDeserializers(deserializer), jsonb -> {
Throwable throwable = null;
try {
jsonb.fromJson("5", JsonParserTestPojo.class);
fail("NoSuchElementException should be thrown");
} catch (JsonbException jbe) {
throwable = jbe.getCause();
}

assertInstanceOf(NoSuchElementException.class, throwable);
});
}
}
}
Loading

0 comments on commit ac9da78

Please sign in to comment.