JsonParserClosedCaseTest.java

package com.fasterxml.jackson.core.json;

import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import com.fasterxml.jackson.core.JUnit5TestBase;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.StreamReadFeature;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.core.testsupport.MockDataInput;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;

/**
 * Tests asserts that using closed `JsonParser` doesn't cause ArrayIndexOutOfBoundsException
 * with `nextXxx()` methods but returns `null` as expected.
 */
public class JsonParserClosedCaseTest
    extends JUnit5TestBase
{
    private static final JsonFactory JSON_F = new JsonFactory();

    JsonParser parser;

    /**
     * Creates a list of parsers to tests.
     *
     * @return List of Object[2]. Object[0] is is the name of the class, Object[1] is instance itself.
     * @throws IOException when closing stream fails.
     */
    public static Collection<Object[]> parsers() throws IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] { '{', '}' });

        return closeParsers(
                (ReaderBasedJsonParser) JSON_F.createParser(new InputStreamReader(inputStream)),
                (UTF8StreamJsonParser) JSON_F.createParser(inputStream),
                (UTF8DataInputJsonParser) JSON_F.createParser(new MockDataInput("{}"))
        );
    }

    public void initJsonParserClosedCaseTest(String parserName, JsonParser parser) {
        this.parser = parser;
    }

    @MethodSource("parsers")
    @ParameterizedTest(name = "{0}")
    void nullReturnedOnClosedParserOnNextFieldName(String parserName, JsonParser parser) throws Exception {
        initJsonParserClosedCaseTest(parserName, parser);
        assertNull(parser.nextFieldName());
    }

    @MethodSource("parsers")
    @ParameterizedTest(name = "{0}")
    void falseReturnedOnClosedParserOnNextFieldNameSerializedString(String parserName, JsonParser parser) throws Exception {
        initJsonParserClosedCaseTest(parserName, parser);
        assertFalse(parser.nextFieldName(new SerializedString("")));
    }

    @MethodSource("parsers")
    @ParameterizedTest(name = "{0}")
    void nullReturnedOnClosedParserOnNextToken(String parserName, JsonParser parser) throws Exception {
        initJsonParserClosedCaseTest(parserName, parser);
        assertNull(parser.nextToken());
    }

    @MethodSource("parsers")
    @ParameterizedTest(name = "{0}")
    void nullReturnedOnClosedParserOnNextValue(String parserName, JsonParser parser) throws Exception {
        initJsonParserClosedCaseTest(parserName, parser);
        assertNull(parser.nextValue());
    }

    // [core#1441]: StreamReadFeature.CLEAR_CURRENT_TOKEN_ON_CLOSE
    @Test
    void clearCurrentTokenOnCloseEnabled() throws Exception {
         JsonFactory f = JsonFactory.builder()
                 .enable(StreamReadFeature.CLEAR_CURRENT_TOKEN_ON_CLOSE)
                 .build();
         _clearCurrentTokenOnCloseEnabled(f, MODE_INPUT_STREAM);
         _clearCurrentTokenOnCloseEnabled(f, MODE_INPUT_STREAM_THROTTLED);
         _clearCurrentTokenOnCloseEnabled(f, MODE_READER);
         _clearCurrentTokenOnCloseEnabled(f, MODE_DATA_INPUT);
                 
    }

    @Test
    void clearCurrentTokenOnCloseDisabled() throws Exception {
         JsonFactory f = JsonFactory.builder()
                 .disable(StreamReadFeature.CLEAR_CURRENT_TOKEN_ON_CLOSE)
                 .build();
         _clearCurrentTokenOnCloseDisabled(f, MODE_INPUT_STREAM);
         _clearCurrentTokenOnCloseDisabled(f, MODE_INPUT_STREAM_THROTTLED);
         _clearCurrentTokenOnCloseDisabled(f, MODE_READER);
         _clearCurrentTokenOnCloseDisabled(f, MODE_DATA_INPUT);
                 
    }

    private void _clearCurrentTokenOnCloseEnabled(JsonFactory f, int mode) throws Exception
    {
        try (JsonParser p = f.createParser("[ 1 ]")) {
            assertToken(JsonToken.START_ARRAY, p.nextToken());
            p.close();
            assertNull(p.currentToken());
        }
    }
    
    private void _clearCurrentTokenOnCloseDisabled(JsonFactory f, int mode) throws Exception
    {
        try (JsonParser p = f.createParser("[ 1 ]")) {
            assertToken(JsonToken.START_ARRAY, p.nextToken());
            p.close();
            assertToken(JsonToken.START_ARRAY, p.currentToken());
        }
    }

    private static Collection<Object[]> closeParsers(JsonParser... parsersToClose) throws IOException {
        List<Object[]> list = new ArrayList<>();
        for (JsonParser p : parsersToClose) {
            p.close();
            list.add(new Object[] { p.getClass().getSimpleName(), p });
        }
        return list;
    }
}