ParserSequenceTest.java
package tools.jackson.core.unittest.read;
import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.core.ObjectReadContext;
import tools.jackson.core.TreeNode;
import tools.jackson.core.unittest.*;
import tools.jackson.core.util.JsonParserSequence;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("resource")
class ParserSequenceTest
extends JacksonCoreTestBase
{
@Test
void simple() throws Exception
{
JsonParser p1 = JSON_FACTORY.createParser(ObjectReadContext.empty(), "[ 1 ]");
JsonParser p2 = JSON_FACTORY.createParser(ObjectReadContext.empty(), "[ 2 ]");
JsonParserSequence seq = JsonParserSequence.createFlattened(false, p1, p2);
assertEquals(2, seq.containedParsersCount());
assertFalse(p1.isClosed());
assertFalse(p2.isClosed());
assertFalse(seq.isClosed());
assertToken(JsonToken.START_ARRAY, seq.nextToken());
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertEquals(1, seq.getIntValue());
assertToken(JsonToken.END_ARRAY, seq.nextToken());
assertFalse(p1.isClosed());
assertFalse(p2.isClosed());
assertFalse(seq.isClosed());
assertToken(JsonToken.START_ARRAY, seq.nextToken());
// first parser ought to be closed now
assertTrue(p1.isClosed());
assertFalse(p2.isClosed());
assertFalse(seq.isClosed());
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertEquals(2, seq.getIntValue());
assertToken(JsonToken.END_ARRAY, seq.nextToken());
assertTrue(p1.isClosed());
assertFalse(p2.isClosed());
assertFalse(seq.isClosed());
assertNull(seq.nextToken());
assertTrue(p1.isClosed());
assertTrue(p2.isClosed());
assertTrue(seq.isClosed());
seq.close();
}
@Test
void multiLevel() throws Exception
{
JsonParser p1 = JSON_FACTORY.createParser(ObjectReadContext.empty(), "[ 1 ] ");
JsonParser p2 = JSON_FACTORY.createParser(ObjectReadContext.empty(), " 5");
JsonParser p3 = JSON_FACTORY.createParser(ObjectReadContext.empty(), " { } ");
JsonParserSequence seq1 = JsonParserSequence.createFlattened(true, p1, p2);
JsonParserSequence seq = JsonParserSequence.createFlattened(false, seq1, p3);
assertEquals(3, seq.containedParsersCount());
assertToken(JsonToken.START_ARRAY, seq.nextToken());
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertToken(JsonToken.END_ARRAY, seq.nextToken());
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertToken(JsonToken.START_OBJECT, seq.nextToken());
assertToken(JsonToken.END_OBJECT, seq.nextToken());
assertNull(seq.nextToken());
assertTrue(p1.isClosed());
assertTrue(p2.isClosed());
assertTrue(p3.isClosed());
assertTrue(seq.isClosed());
}
// for [jackson-core#296]
@Test
void initializationDisabled() throws Exception
{
// // First, with old legacy settings
JsonParser p1 = JSON_FACTORY.createParser(ObjectReadContext.empty(), "1 2");
JsonParser p2 = JSON_FACTORY.createParser(ObjectReadContext.empty(), "3 true");
assertToken(JsonToken.VALUE_NUMBER_INT, p1.nextToken());
assertEquals(1, p1.getIntValue());
assertToken(JsonToken.VALUE_NUMBER_INT, p2.nextToken());
assertEquals(3, p2.getIntValue());
// with legacy settings, will see neither '1' nor '3'
JsonParserSequence seq = JsonParserSequence.createFlattened(false, p1, p2);
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertEquals(2, seq.getIntValue());
assertToken(JsonToken.VALUE_TRUE, seq.nextToken());
assertNull(seq.nextToken());
seq.close();
}
// for [jackson-core#296]
@Test
void initializationEnabled() throws Exception
{
// // and then with new "check for current":
JsonParser p1 = JSON_FACTORY.createParser(ObjectReadContext.empty(), "1 2");
JsonParser p2 = JSON_FACTORY.createParser(ObjectReadContext.empty(), "3 true");
assertToken(JsonToken.VALUE_NUMBER_INT, p1.nextToken());
assertEquals(1, p1.getIntValue());
assertToken(JsonToken.VALUE_NUMBER_INT, p2.nextToken());
assertEquals(3, p2.getIntValue());
// with new settings, both '1' and '3' will be visible
JsonParserSequence seq = JsonParserSequence.createFlattened(true, p1, p2);
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertEquals(1, seq.getIntValue());
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertEquals(2, seq.getIntValue());
assertToken(JsonToken.VALUE_NUMBER_INT, seq.nextToken());
assertEquals(3, seq.getIntValue());
assertToken(JsonToken.VALUE_TRUE, seq.nextToken());
assertNull(seq.nextToken());
seq.close();
}
// [jackson-core#1616]: read methods (readValueAsTree / readValueAs) must
// drive databind through the sequence's own token stream, so that parsers
// beyond the first one are actually used.
@Test
void readValueAsTreeUsesAllParsers() throws Exception
{
CountingReadContext ctxt = new CountingReadContext();
// First parser yields 3 tokens, second yields 2: 5 total
JsonParser p1 = JSON_FACTORY.createParser(ctxt, "1 2 3");
JsonParser p2 = JSON_FACTORY.createParser(ctxt, "4 5");
JsonParserSequence seq = JsonParserSequence.createFlattened(false, p1, p2);
seq.readValueAsTree();
assertEquals(5, ctxt.tokenCount,
"readValueAsTree() must consume tokens from all parsers in sequence");
seq.close();
}
// [jackson-core#1616]: same, but mirroring the actual failing scenario more
// closely -- as in `AsPropertyTypeDeserializer`, the sequence is built with
// `checkForExistingToken=true` over a first parser that already points at a
// token (a `TokenBuffer` in real usage). The existing-token path must still
// continue into the remaining parsers.
@Test
void readValueAsTreeUsesAllParsersWithExistingToken() throws Exception
{
CountingReadContext ctxt = new CountingReadContext();
JsonParser p1 = JSON_FACTORY.createParser(ctxt, "1 2 3");
JsonParser p2 = JSON_FACTORY.createParser(ctxt, "4 5");
// advance first parser so it already points at a token before sequencing
assertToken(JsonToken.VALUE_NUMBER_INT, p1.nextToken());
JsonParserSequence seq = JsonParserSequence.createFlattened(true, p1, p2);
seq.readValueAsTree();
assertEquals(5, ctxt.tokenCount,
"readValueAsTree() must consume tokens from all parsers in sequence");
seq.close();
}
// [jackson-core#1616]: the same routing must apply to the non-tree
// `readValueAs(...)` overloads, not just `readValueAsTree()`.
@Test
void readValueAsUsesAllParsers() throws Exception
{
CountingReadContext ctxt = new CountingReadContext();
JsonParser p1 = JSON_FACTORY.createParser(ctxt, "1 2 3");
JsonParser p2 = JSON_FACTORY.createParser(ctxt, "4 5");
JsonParserSequence seq = JsonParserSequence.createFlattened(false, p1, p2);
seq.readValueAs(Object.class);
assertEquals(5, ctxt.tokenCount,
"readValueAs(Class) must consume tokens from all parsers in sequence");
seq.close();
}
// Helper context whose databind callbacks drain (and count) every token of
// the parser handed to them; mimics how real databind drives the parser.
static class CountingReadContext extends ObjectReadContext.Base {
int tokenCount;
private void _drain(JsonParser p) {
tokenCount = 0;
while (p.nextToken() != null) {
++tokenCount;
}
}
@Override
public <T extends TreeNode> T readTree(JsonParser p) {
_drain(p);
return null;
}
@Override
public <T> T readValue(JsonParser p, Class<T> valueType) {
_drain(p);
return null;
}
}
}