MultiArgConstructorTest.java

package tools.jackson.databind.deser.creators;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;

import tools.jackson.databind.*;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.introspect.AnnotatedParameter;
import tools.jackson.databind.introspect.JacksonAnnotationIntrospector;
import tools.jackson.databind.introspect.VisibilityChecker;

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

import static tools.jackson.databind.testutil.DatabindTestUtil.*;

public class MultiArgConstructorTest
{
    static class MultiArgCtorBean
    {
        protected int _a, _b;

        public int c;

        public MultiArgCtorBean(int a, int b) {
            _a = a;
            _b = b;
        }
    }

    static class MultiArgCtorBeanWithAnnotations
    {
        protected int _a, _b;

        public int c;

        public MultiArgCtorBeanWithAnnotations(int a, @JsonProperty("b2") int b) {
            _a = a;
            _b = b;
        }
    }

    // Test(s) for "big" creators; ones with at least 32 arguments (sic!).
    // Needed because code paths diverge wrt handling of bitset.
    static class Biggie {
        final int[] stuff;

        @JsonCreator
        public Biggie(
                @JsonProperty("v1") int v1, @JsonProperty("v2") int v2,
                @JsonProperty("v3") int v3, @JsonProperty("v4") int v4,
                @JsonProperty("v5") int v5, @JsonProperty("v6") int v6,
                @JsonProperty("v7") int v7, @JsonProperty("v8") int v8,
                @JsonProperty("v9") int v9, @JsonProperty("v10") int v10,
                @JsonProperty("v11") int v11, @JsonProperty("v12") int v12,
                @JsonProperty("v13") int v13, @JsonProperty("v14") int v14,
                @JsonProperty("v15") int v15, @JsonProperty("v16") int v16,
                @JsonProperty("v17") int v17, @JsonProperty("v18") int v18,
                @JsonProperty("v19") int v19, @JsonProperty("v20") int v20,
                @JsonProperty("v21") int v21, @JsonProperty("v22") int v22,
                @JsonProperty("v23") int v23, @JsonProperty("v24") int v24,
                @JsonProperty("v25") int v25, @JsonProperty("v26") int v26,
                @JsonProperty("v27") int v27, @JsonProperty("v28") int v28,
                @JsonProperty("v29") int v29, @JsonProperty("v30") int v30,
                @JsonProperty("v31") int v31, @JsonProperty("v32") int v32,
                @JsonProperty("v33") int v33, @JsonProperty("v34") int v34,
                @JsonProperty("v35") int v35, @JsonProperty("v36") int v36,
                @JsonProperty("v37") int v37, @JsonProperty("v38") int v38,
                @JsonProperty("v39") int v39, @JsonProperty("v40") int v40
                ) {
            stuff = new int[] {
                    v1, v2, v3, v4, v5, v6, v7, v8, v9, v10,
                    v11, v12, v13, v14, v15, v16, v17, v18, v19, v20,
                    v21, v22, v23, v24, v25, v26, v27, v28, v29, v30,
                    v31, v32, v33, v34, v35, v36, v37, v38, v39, v40,
            };
        }
    }

    /* Before JDK8, we won't have parameter names available, so let's
     * fake it before that...
     */
    @SuppressWarnings("serial")
    static class MyParamIntrospector extends JacksonAnnotationIntrospector
    {
        @Override
        public String findImplicitPropertyName(MapperConfig<?> config, AnnotatedMember param) {
            if (param instanceof AnnotatedParameter ap) {
                switch (ap.getIndex()) {
                case 0: return "a";
                case 1: return "b";
                default:
                    return "param"+ap.getIndex();
                }
            }
            return super.findImplicitPropertyName(config, param);
        }
    }

    /*
    /**********************************************************************
    /* Test methods
    /**********************************************************************
     */

    @Test
    public void testMultiArgVisible() throws Exception
    {
        final ObjectMapper mapper = jsonMapperBuilder()
                .annotationIntrospector(new MyParamIntrospector())
                .build();
        MultiArgCtorBean bean = mapper.readValue(a2q("{'b':13, 'c':2, 'a':-99}"),
                MultiArgCtorBean.class);
        assertNotNull(bean);
        assertEquals(13, bean._b);
        assertEquals(-99, bean._a);
        assertEquals(2, bean.c);
    }

    // But besides visibility, also allow overrides
    @Test
    public void testMultiArgWithPartialOverride() throws Exception
    {
        final ObjectMapper mapper = jsonMapperBuilder()
                .annotationIntrospector(new MyParamIntrospector())
                .build();
        MultiArgCtorBeanWithAnnotations bean = mapper.readValue(a2q("{'b2':7, 'c':222, 'a':-99}"),
                MultiArgCtorBeanWithAnnotations.class);
        assertNotNull(bean);
        assertEquals(7, bean._b);
        assertEquals(-99, bean._a);
        assertEquals(222, bean.c);
    }

    // but let's also ensure that it is possible to prevent use of that constructor
    // with different visibility
    @Test
    public void testMultiArgNotVisible() throws Exception
    {
        final ObjectMapper mapper = jsonMapperBuilder()
                .annotationIntrospector(new MyParamIntrospector())
                .changeDefaultVisibility(vc -> VisibilityChecker.construct
                        (JsonAutoDetect.Value.noOverrides()
                        .withCreatorVisibility(Visibility.NONE)))
                .build();
        try {
            /*MultiArgCtorBean bean =*/ mapper.readValue(a2q("{'b':13,  'a':-99}"),
                MultiArgCtorBean.class);
            fail("Should not have passed");
        } catch (InvalidDefinitionException e) {
            verifyException(e, "no Creators");
        }
    }

    @Test
    public void testBigPartial() throws Exception
    {
        ObjectReader biggieReader = sharedMapper()
                .readerFor(Biggie.class)
                .without(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
        Biggie value = biggieReader.readValue(a2q(
                "{'v7':7, 'v8':8,'v29':29, 'v35':35}"
                ));
        int[] stuff = value.stuff;
        for (int i = 0; i < stuff.length; ++i) {
            int exp;

            switch (i) {
            case 6: // These are off-by-one...
            case 7:
            case 28:
            case 34:
                exp = i+1;
                break;
            default:
                exp = 0;
            }
            assertEquals(exp, stuff[i], "Entry #"+i);
        }
    }
}