AsyncNaNHandlingTest.java

package com.fasterxml.jackson.core.json.async;

import java.io.IOException;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.async.AsyncTestBase;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.testsupport.AsyncReaderWrapper;

import static org.junit.jupiter.api.Assertions.*;

class AsyncNaNHandlingTest extends AsyncTestBase
{
    private final JsonFactory DEFAULT_F = new JsonFactory();

    @SuppressWarnings("deprecation")
    @Test
    void defaultsForAsync() throws Exception {
        assertFalse(DEFAULT_F.isEnabled(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS));
    }

    @Test
    void disallowNaN() throws Exception
    {
        final String JSON = "[ NaN]";

        // without enabling, should get an exception
        AsyncReaderWrapper p = createParser(DEFAULT_F, JSON, 1);
        assertToken(JsonToken.START_ARRAY, p.nextToken());
        try {
            p.nextToken();
            fail("Expected exception");
        } catch (Exception e) {
            verifyException(e, "non-standard");
        } finally {
            p.close();
        }
    }

    @Test
    void allowNaN() throws Exception
    {
        final String JSON = "[ NaN]";
        JsonFactory f = JsonFactory.builder()
                .enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS)
                .build();
        _testAllowNaN(f, JSON, 99);
        _testAllowNaN(f, JSON, 5);
        _testAllowNaN(f, JSON, 3);
        _testAllowNaN(f, JSON, 2);
        _testAllowNaN(f, JSON, 1);
    }

    private void _testAllowNaN(JsonFactory f, String doc, int readBytes) throws Exception
    {
        AsyncReaderWrapper p = createParser(f, doc, readBytes);
        assertToken(JsonToken.START_ARRAY, p.nextToken());
        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());

        double d = p.getDoubleValue();
        assertTrue(Double.isNaN(d));
        assertEquals("NaN", p.currentText());

        try {
            /*BigDecimal dec =*/ p.getDecimalValue();
            fail("Should fail when trying to access NaN as BigDecimal");
        } catch (NumberFormatException e) {
            verifyException(e, "can not be deserialized as `java.math.BigDecimal`");
        }

        assertToken(JsonToken.END_ARRAY, p.nextToken());
        p.close();

        // finally, should also work with skipping
        f = JsonFactory.builder()
                .configure(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS, true)
                .build();
        p = createParser(f, doc, readBytes);
        assertToken(JsonToken.START_ARRAY, p.nextToken());
        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
        assertToken(JsonToken.END_ARRAY, p.nextToken());
        p.close();
    }

    @Test
    void disallowInf() throws Exception
    {
        // these are serializations of JDK itself:
        _testDisallowInf(DEFAULT_F, "Infinity", 99);
        _testDisallowInf(DEFAULT_F, "Infinity", 1);
        _testDisallowInf(DEFAULT_F, "-Infinity", 99);
        _testDisallowInf(DEFAULT_F, "-Infinity", 1);
        // and this is sort of alias for first one
        _testDisallowInf(DEFAULT_F, "+Infinity", 99);
        _testDisallowInf(DEFAULT_F, "+Infinity", 1);

        // And these may or may not be supported as further aliases

        // 06-Jun-2017, tatu: Problematic for now since they share same prefix; can
        //   be supported, eventually, if really care. For now leave it be.
//        _testDisallowInf(DEFAULT_F, "-INF");
//        _testDisallowInf(DEFAULT_F, "+INF");
    }

    private void _testDisallowInf(JsonFactory f, String token, int readBytes) throws Exception
    {
        final String JSON = String.format("[%s]", token);

        // without enabling, should get an exception
        AsyncReaderWrapper p = createParser(f, JSON, readBytes);
        assertToken(JsonToken.START_ARRAY, p.nextToken());
        try {
            JsonToken t = p.nextToken();
            fail("Expected exception; got "+t+" (text ["+p.currentText()+"])");
        } catch (Exception e) {
            verifyException(e, "Non-standard token '"+token+"'");
        } finally {
            p.close();
        }
    }

    @Test
    void allowInf() throws Exception
    {
        JsonFactory f = JsonFactory.builder()
                .enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS)
                .build();
        String JSON = "[ Infinity, +Infinity, -Infinity ]";
        _testAllowInf(f, JSON, 99);
        _testAllowInf(f, JSON, 5);
        _testAllowInf(f, JSON, 3);
        _testAllowInf(f, JSON, 2);
        _testAllowInf(f, JSON, 1);

        JSON = "[Infinity,+Infinity,-Infinity]";
        _testAllowInf(f, JSON, 99);
        _testAllowInf(f, JSON, 1);

        JSON = "[Infinity  ,   +Infinity   ,   -Infinity]";
        _testAllowInf(f, JSON, 99);
        _testAllowInf(f, JSON, 1);
    }

    private void _testAllowInf(JsonFactory f, String doc, int readBytes) throws Exception
    {
        // 06-Jun-2017, tatu: Leave out "-INF" and "+INF" for now due to overlap with
        //   somewhat more standard (wrt JDK) "-Infinity" and "+Infinity"

        AsyncReaderWrapper p = createParser(f, doc, readBytes);
        assertToken(JsonToken.START_ARRAY, p.nextToken());

        double d;

        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
        d = p.getDoubleValue();
        assertEquals("Infinity", p.currentText());
        assertTrue(Double.isInfinite(d));
        assertEquals(Double.POSITIVE_INFINITY, d);

        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
        d = p.getDoubleValue();
        assertEquals("+Infinity", p.currentText());
        assertTrue(Double.isInfinite(d));
        assertEquals(Double.POSITIVE_INFINITY, d);

        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
        d = p.getDoubleValue();
        assertEquals("-Infinity", p.currentText());
        assertTrue(Double.isInfinite(d));
        assertEquals(Double.NEGATIVE_INFINITY, d);

        assertToken(JsonToken.END_ARRAY, p.nextToken());
        p.close();

        // finally, should also work with skipping
        f = JsonFactory.builder()
                .configure(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS, true)
                .build();
        p = createParser(f, doc, readBytes);

        assertToken(JsonToken.START_ARRAY, p.nextToken());
        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
        assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
        assertToken(JsonToken.END_ARRAY, p.nextToken());

        p.close();
    }

    private AsyncReaderWrapper createParser(JsonFactory f, String doc, int readBytes) throws IOException
    {
        return asyncForBytes(f, readBytes, _jsonDoc(doc), 1);
    }
}