ObjectIdWithPolymorphicTest.java

package tools.jackson.databind.objectid;

import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.*;
import tools.jackson.databind.cfg.EnumFeature;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.testutil.NoCheckSubTypeValidator;

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

public class ObjectIdWithPolymorphicTest extends DatabindTestUtil
{
    // // // Simple polymorphic roundtrip

    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
    @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="id")
    static abstract class Base
    {
        public int value;
        public Base next;

        public Base() { this(0); }
        protected Base(int v) { value = v; }
    }

    static class Impl extends Base
    {
        public int extra;

        public Impl() { this(0, 0); }
        protected Impl(int v, int e) {
            super(v);
            extra = e;
        }
    }

    // // // [databind#811] types -- deeply nested polymorphic BPEL-like structure

    @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
    public static class Base811 {
        public int id;
        public Base811 owner;

        protected Base811() {}
        protected Base811(Process owner) {
            this.owner = owner;
            if (owner == null) {
                id = 0;
            } else {
                id = ++owner.childIdCounter;
                owner.children.add(this);
            }
        }
    }

    public static class Process extends Base811 {
        protected int childIdCounter = 0;
        protected List<Base811> children = new ArrayList<>();

        public Process() { super(null); }
    }

    public static abstract class Activity extends Base811 {
        protected Activity parent;
        public Activity(Process owner, Activity parent) {
            super(owner);
            this.parent = parent;
        }
        protected Activity() { super(); }
    }

    public static class Scope extends Activity {
        public final List<FaultHandler> faultHandlers = new ArrayList<>();
        public Scope(Process owner, Activity parent) { super(owner, parent); }
        protected Scope() { super(); }
    }

    public static class FaultHandler extends Base811 {
        public final List<Catch> catchBlocks = new ArrayList<>();

        public FaultHandler(Process owner) { super(owner); }
        protected FaultHandler() {}
    }

    public static class Catch extends Scope {
        public Catch(Process owner, Activity parent) { super(owner, parent); }
        protected Catch() {}
    }

    // // // [databind#877]: interface + abstract class with ObjectId and default typing

    interface BaseInterface877 { }

    @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
    static class BaseInterfaceImpl877 implements BaseInterface877 {
        @JsonProperty
        private List<BaseInterfaceImpl877> myInstances = new ArrayList<>();

        void addInstance(BaseInterfaceImpl877 instance) {
            myInstances.add(instance);
        }
    }

    static class ListWrapper877<T extends BaseInterface877> {
        @JsonProperty
        private List<T> myList = new ArrayList<>();

        void add(T t) { myList.add(t); }
        int size() { return myList.size(); }
    }

    // // // [TestObjectId / databind#(no issue)]: ObjectId + JsonTypeInfo combination

    @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
    @JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
    public static class BaseEntity { }

    public static class Foo extends BaseEntity {
        public BaseEntity ref;
    }

    public static class Bar extends BaseEntity {
        public Foo next;
    }

    /*
    /**********************************************************
    /* Unit tests for polymorphic type handling
    /**********************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    @Test
    public void testPolymorphicRoundtrip() throws Exception
    {
        // create simple 2 node loop:
        Impl in1 = new Impl(123, 456);
        in1.next = new Impl(111, 222);
        in1.next.next = in1;

        String json = MAPPER.writeValueAsString(in1);

        Base result0 = MAPPER.readValue(json, Base.class);
        assertNotNull(result0);
        assertSame(Impl.class, result0.getClass());
        Impl result = (Impl) result0;
        assertEquals(123, result.value);
        assertEquals(456, result.extra);
        Impl result2 = (Impl) result.next;
        assertEquals(111, result2.value);
        assertEquals(222, result2.extra);
        assertSame(result, result2.next);
    }

    @Test
    public void testIssue811() throws Exception
    {
        ObjectMapper om = jsonMapperBuilder()
                .activateDefaultTypingAsProperty(NoCheckSubTypeValidator.instance,
                        DefaultTyping.NON_FINAL, "@class")
                .enable(SerializationFeature.INDENT_OUTPUT)
                .enable(EnumFeature.WRITE_ENUMS_USING_INDEX)
                .build();

        Process p = new Process();
        Scope s = new Scope(p, null);
        FaultHandler fh = new FaultHandler(p);
        Catch c = new Catch(p, s);
        fh.catchBlocks.add(c);
        s.faultHandlers.add(fh);

        String json = om.writeValueAsString(p);
        Process restored = om.readValue(json, Process.class);
        assertNotNull(restored);

        assertEquals(0, p.id);
        assertEquals(3, p.children.size());
        assertSame(p, p.children.get(0).owner);
        assertSame(p, p.children.get(1).owner);
        assertSame(p, p.children.get(2).owner);
    }

    // [databind#877]
    @Test
    public void testIssue877() throws Exception
    {
        BaseInterfaceImpl877 one = new BaseInterfaceImpl877();
        BaseInterfaceImpl877 two = new BaseInterfaceImpl877();
        one.addInstance(two);
        two.addInstance(one);

        ListWrapper877<BaseInterfaceImpl877> myList = new ListWrapper877<>();
        myList.add(one);
        myList.add(two);

        ObjectMapper mapper = jsonMapperBuilder()
                .activateDefaultTypingAsProperty(NoCheckSubTypeValidator.instance,
                        DefaultTyping.NON_FINAL, "@class")
                .build();

        String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(myList);
        ListWrapper877<BaseInterfaceImpl877> result =
                mapper.readValue(json, new TypeReference<ListWrapper877<BaseInterfaceImpl877>>() { });

        assertNotNull(result);
        assertEquals(2, result.size());
    }

    @Test
    public void testObjectAndTypeId() throws Exception
    {
        Bar inputRoot = new Bar();
        Foo inputChild = new Foo();
        inputRoot.next = inputChild;
        inputChild.ref = inputRoot;

        String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(inputRoot);

        BaseEntity resultRoot = MAPPER.readValue(json, BaseEntity.class);
        assertNotNull(resultRoot);
        assertInstanceOf(Bar.class, resultRoot);
        Bar first = (Bar) resultRoot;

        assertNotNull(first.next);
        assertInstanceOf(Foo.class, first.next);
        Foo second = (Foo) first.next;
        assertNotNull(second.ref);
        assertSame(first, second.ref);
    }
}